Skip to content

Commit 7e5c7ef

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 generic fallback. A small runtime helper preserves return-address handling without target-specific logic in the compiler.
1 parent 3fb0a90 commit 7e5c7ef

4 files changed

Lines changed: 46 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", 3699, 297, 0, 2252},
46-
{"microbit", "examples/serial", 2736, 356, 8, 2248},
47-
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
45+
{"hifive1b", "examples/echo", 3760, 352, 0, 2252},
46+
{"microbit", "examples/serial", 2761, 395, 8, 2248},
47+
{"wioterminal", "examples/pininterrupt", 7990, 1710, 132, 7480},
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
@@ -2032,6 +2032,22 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
20322032
for _, param := range instr.Args {
20332033
params = append(params, b.getValue(param, getPos(instr)))
20342034
}
2035+
if fn := instr.StaticCallee(); fn != nil {
2036+
switch b.getFunctionInfo(fn).linkName {
2037+
case "runtime.runtimePanic":
2038+
if len(params) == 1 && params[0].IsConstant() {
2039+
errType := b.program.ImportedPackage("runtime").Members["plainError"].(*ssa.Type).Type()
2040+
err := b.createMakeInterface(params[0], errType, instr.Pos())
2041+
return b.createRuntimeCall("runtimePanicValue", []llvm.Value{err, params[0]}, ""), nil
2042+
}
2043+
case "runtime.runtimePanicAt":
2044+
if len(params) == 2 && params[1].IsConstant() {
2045+
errType := b.program.ImportedPackage("runtime").Members["plainError"].(*ssa.Type).Type()
2046+
err := b.createMakeInterface(params[1], errType, instr.Pos())
2047+
return b.createRuntimeCall("runtimePanicAtMsg", []llvm.Value{params[0], err, params[1]}, ""), nil
2048+
}
2049+
}
2050+
}
20352051

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

main_test.go

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

151+
t.Run("gc=none-runtime-panic", func(t *testing.T) {
152+
t.Parallel()
153+
opts := optionsFromTarget("cortex-m-qemu", sema)
154+
opts.GC = "none"
155+
opts.Scheduler = "none"
156+
config, err := builder.NewConfig(&opts)
157+
if err != nil {
158+
t.Fatal(err)
159+
}
160+
err = Build("testdata/trivialpanic.go", t.TempDir()+"/trivialpanic", config)
161+
if err != nil {
162+
w := &bytes.Buffer{}
163+
diagnostics.CreateDiagnostics(err).WriteTo(w, "")
164+
t.Fatal(w.String())
165+
}
166+
})
167+
151168
t.Run("ldflags", func(t *testing.T) {
152169
t.Parallel()
153170
opts := optionsFromTarget("", sema)

src/runtime/panic.go

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

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

9292
func runtimePanicAt(addr unsafe.Pointer, msg string) {
93+
runtimePanicAtMsg(addr, plainError(msg), msg)
94+
}
95+
96+
func runtimePanicValue(err interface{}, msg string) {
97+
runtimePanicAtMsg(returnAddress(0), err, msg)
98+
}
99+
100+
func runtimePanicAtMsg(addr unsafe.Pointer, err interface{}, msg string) {
93101
if panicStrategy() == tinygo.PanicStrategyTrap {
94102
trap()
95103
}
@@ -98,7 +106,7 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
98106
if frame != nil {
99107
// Use the normal panic mechanism so that this runtime error
100108
// can be recovered with recover().
101-
frame.PanicValue = plainError(msg)
109+
frame.PanicValue = err
102110
frame.Panicking = panicTrue
103111
tinygo_longjmp(frame)
104112
// unreachable

0 commit comments

Comments
 (0)