Skip to content

Commit 676a244

Browse files
committed
compiler: avoid runtime panic string heap allocs
Lower constant-string calls to runtime.runtimePanic and runtime.runtimePanicAt so they pass a runtime.plainError interface value to runtimePanicAtMsg. This uses static interface backing storage for recoverable runtime panics instead of allocating a string header at run time. Keep dynamic runtimePanic(msg) as the exact fallback. It constructs the runtime.Error value only when a recover frame is present, so trap/abort paths do not eagerly box the string.
1 parent 3071e33 commit 676a244

4 files changed

Lines changed: 49 additions & 5 deletions

File tree

builder/sizes_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
4242
// This is a small number of very diverse targets that we want to test.
4343
tests := []sizeTest{
4444
// microcontrollers
45-
{"hifive1b", "examples/echo", 4277, 307, 0, 2260},
46-
{"microbit", "examples/serial", 2836, 368, 8, 2256},
47-
{"wioterminal", "examples/pininterrupt", 8013, 1663, 132, 7488},
45+
{"hifive1b", "examples/echo", 4349, 371, 0, 2260},
46+
{"microbit", "examples/serial", 2858, 414, 8, 2256},
47+
{"wioterminal", "examples/pininterrupt", 8045, 1727, 132, 7488},
4848

4949
// TODO: also check wasm. Right now this is difficult, because
5050
// wasm binaries are run through wasm-opt and therefore the

compiler/compiler.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,6 +2034,22 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
20342034
for _, param := range instr.Args {
20352035
params = append(params, b.getValue(param, getPos(instr)))
20362036
}
2037+
if fn := instr.StaticCallee(); fn != nil {
2038+
switch b.getFunctionInfo(fn).linkName {
2039+
case "runtime.runtimePanic":
2040+
if len(params) == 1 && params[0].IsConstant() {
2041+
errType := b.program.ImportedPackage("runtime").Members["plainError"].(*ssa.Type).Type()
2042+
err := b.createMakeInterface(params[0], errType, instr.Pos())
2043+
return b.createRuntimeCall("runtimePanicValue", []llvm.Value{err, params[0]}, ""), nil
2044+
}
2045+
case "runtime.runtimePanicAt":
2046+
if len(params) == 2 && params[1].IsConstant() {
2047+
errType := b.program.ImportedPackage("runtime").Members["plainError"].(*ssa.Type).Type()
2048+
err := b.createMakeInterface(params[1], errType, instr.Pos())
2049+
return b.createRuntimeCall("runtimePanicAtMsg", []llvm.Value{params[0], err, params[1]}, ""), nil
2050+
}
2051+
}
2052+
}
20372053

20382054
// Try to call the function directly for trivially static calls.
20392055
var callee, context llvm.Value

main_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,23 @@ func TestBuild(t *testing.T) {
140140
runTestWithConfig("print.go", t, opts, nil, nil)
141141
})
142142

143+
t.Run("gc=none-runtime-panic", func(t *testing.T) {
144+
t.Parallel()
145+
opts := optionsFromTarget("cortex-m-qemu", sema)
146+
opts.GC = "none"
147+
opts.Scheduler = "none"
148+
config, err := builder.NewConfig(&opts)
149+
if err != nil {
150+
t.Fatal(err)
151+
}
152+
err = Build("testdata/trivialpanic.go", t.TempDir()+"/trivialpanic", config)
153+
if err != nil {
154+
w := &bytes.Buffer{}
155+
diagnostics.CreateDiagnostics(err).WriteTo(w, "")
156+
t.Fatal(w.String())
157+
}
158+
})
159+
143160
t.Run("ldflags", func(t *testing.T) {
144161
t.Parallel()
145162
opts := optionsFromTarget("", sema)

src/runtime/panic.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,20 @@ func panicOrGoexit(message interface{}, panicking panicState) {
8686

8787
// Cause a runtime panic, which is (currently) always a string.
8888
func runtimePanic(msg string) {
89-
// As long as this function is inined, llvm.returnaddress(0) will return
89+
// As long as this function is inlined, llvm.returnaddress(0) will return
9090
// something sensible.
9191
runtimePanicAt(returnAddress(0), msg)
9292
}
9393

9494
func runtimePanicAt(addr unsafe.Pointer, msg string) {
95+
runtimePanicAtMsg(addr, nil, msg)
96+
}
97+
98+
func runtimePanicValue(err interface{}, msg string) {
99+
runtimePanicAtMsg(returnAddress(0), err, msg)
100+
}
101+
102+
func runtimePanicAtMsg(addr unsafe.Pointer, err interface{}, msg string) {
95103
if panicStrategy() == tinygo.PanicStrategyTrap {
96104
trap()
97105
}
@@ -100,7 +108,10 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
100108
if frame != nil {
101109
// Use the normal panic mechanism so that this runtime error
102110
// can be recovered with recover().
103-
frame.PanicValue = plainError(msg)
111+
if err == nil {
112+
err = plainError(msg)
113+
}
114+
frame.PanicValue = err
104115
frame.Panicking = panicTrue | (frame.Panicking & panicGoexit)
105116
tinygo_longjmp(frame)
106117
// unreachable

0 commit comments

Comments
 (0)