Skip to content

Commit 929295f

Browse files
committed
runtime: pre-box plain errors to avoid allocs
1 parent 3fb0a90 commit 929295f

9 files changed

Lines changed: 83 additions & 37 deletions

File tree

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/chan.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func (ch *channel) trySend(value unsafe.Pointer) bool {
207207
// Note: we cannot currently recover from this panic.
208208
// There's some state in the select statement especially that would be
209209
// corrupted if we allowed recovering from this panic.
210-
runtimePanic("send on closed channel")
210+
runtimePanicAt(returnAddress(0), &errSendOnClosedChannel)
211211
}
212212

213213
// There is no value in the buffer and we have a receiver available. Copy
@@ -264,7 +264,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
264264
// closed while sending).
265265
if t.DataUint32() == chanOperationClosed {
266266
// Oops, this channel was closed while sending!
267-
runtimePanic("send on closed channel")
267+
runtimePanicAt(returnAddress(0), &errSendOnClosedChannel)
268268
}
269269
}
270270

@@ -345,7 +345,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
345345
func chanClose(ch *channel) {
346346
if ch == nil {
347347
// Not allowed by the language spec.
348-
runtimePanic("close of nil channel")
348+
runtimePanicAt(returnAddress(0), &errCloseOfNilChannel)
349349
}
350350

351351
mask := interrupt.Disable()
@@ -355,7 +355,7 @@ func chanClose(ch *channel) {
355355
// Not allowed by the language spec.
356356
ch.lock.Unlock()
357357
interrupt.Restore(mask)
358-
runtimePanic("close of closed channel")
358+
runtimePanicAt(returnAddress(0), &errCloseOfClosedChannel)
359359
}
360360

361361
// Proceed all receiving operations that are blocked.

src/runtime/error.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,33 @@ type Error interface {
77
RuntimeError()
88
}
99

10-
// plainError is a runtime.Error implementation for plain string messages.
10+
// plainError backs preallocated runtime.Error values.
1111
type plainError string
1212

13-
func (e plainError) Error() string { return string(e) }
14-
func (e plainError) RuntimeError() {}
13+
func (e *plainError) Error() string { return string(*e) }
14+
func (e *plainError) RuntimeError() {}
15+
16+
var (
17+
errRuntime = plainError("runtime error")
18+
errDeferInInterrupt = plainError("defer in interrupt")
19+
errNilPointerDereference = plainError("nil pointer dereference")
20+
errAssignmentToEntryInNilMap = plainError("assignment to entry in nil map")
21+
errIndexOutOfRange = plainError("index out of range")
22+
errSliceOutOfRange = plainError("slice out of range")
23+
errSliceSmallerThanArray = plainError("slice smaller than array")
24+
errUnsafeSliceStringLenOutOfRange = plainError("unsafe.Slice/String: len out of range")
25+
errNewChannelIsTooBig = plainError("new channel is too big")
26+
errNegativeShift = plainError("negative shift")
27+
errDivideByZero = plainError("divide by zero")
28+
errTryingToDoBlockingOperationInExported = plainError("trying to do blocking operation in exported function")
29+
errComparingUncomparableType = plainError("comparing un-comparable type")
30+
errTypeAssertFailed = plainError("type assert failed")
31+
errSendOnClosedChannel = plainError("send on closed channel")
32+
errCloseOfNilChannel = plainError("close of nil channel")
33+
errCloseOfClosedChannel = plainError("close of closed channel")
34+
errHeapAllocInInterrupt = plainError("heap alloc in interrupt")
35+
errOutOfMemory = plainError("out of memory")
36+
errSignal = plainError("signal")
37+
errIntegerOverflow = plainError("integer overflow")
38+
errUnsupportedSignalNumber = plainError("unsupported signal number")
39+
)

src/runtime/gc_blocks.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
406406
}
407407

408408
if interrupt.In() {
409-
runtimePanicAt(returnAddress(0), "heap alloc in interrupt")
409+
runtimePanicAt(returnAddress(0), &errHeapAllocInInterrupt)
410410
}
411411

412412
// Round the size up to a multiple of blocks, adding space for the header.
@@ -415,7 +415,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
415415
size += bytesPerBlock - 1
416416
if size < rawSize {
417417
// The size overflowed.
418-
runtimePanicAt(returnAddress(0), "out of memory")
418+
runtimePanicAt(returnAddress(0), &errOutOfMemory)
419419
}
420420
neededBlocks := size / bytesPerBlock
421421
size = neededBlocks * bytesPerBlock
@@ -464,7 +464,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
464464
// Unfortunately the heap could not be increased. This
465465
// happens on baremetal systems for example (where all
466466
// available RAM has already been dedicated to the heap).
467-
runtimePanicAt(returnAddress(0), "out of memory")
467+
runtimePanicAt(returnAddress(0), &errOutOfMemory)
468468
}
469469

470470
// Set the block states.

src/runtime/hashmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
817817
}
818818
return hash
819819
default:
820-
runtimePanic("comparing un-comparable type")
820+
runtimePanicAt(returnAddress(0), &errComparingUncomparableType)
821821
return 0 // unreachable
822822
}
823823
}

src/runtime/interface.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func reflectValueEqual(x, y reflectlite.Value) bool {
7777
case reflectlite.Interface:
7878
return reflectValueEqual(x.Elem(), y.Elem())
7979
default:
80-
runtimePanic("comparing un-comparable type")
80+
runtimePanicAt(returnAddress(0), &errComparingUncomparableType)
8181
return false // unreachable
8282
}
8383
}
@@ -86,7 +86,7 @@ func reflectValueEqual(x, y reflectlite.Value) bool {
8686
// returns false.
8787
func interfaceTypeAssert(ok bool) {
8888
if !ok {
89-
runtimePanic("type assert failed")
89+
runtimePanicAt(returnAddress(0), &errTypeAssertFailed)
9090
}
9191
}
9292

src/runtime/panic.go

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,16 @@ 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.
89-
runtimePanicAt(returnAddress(0), msg)
89+
runtimePanicAtMsg(returnAddress(0), &errRuntime, msg)
9090
}
9191

92-
func runtimePanicAt(addr unsafe.Pointer, msg string) {
92+
func runtimePanicAt(addr unsafe.Pointer, err *plainError) {
93+
runtimePanicAtMsg(addr, err, err.Error())
94+
}
95+
96+
func runtimePanicAtMsg(addr unsafe.Pointer, err *plainError, msg string) {
9397
if panicStrategy() == tinygo.PanicStrategyTrap {
9498
trap()
9599
}
@@ -98,7 +102,7 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
98102
if frame != nil {
99103
// Use the normal panic mechanism so that this runtime error
100104
// can be recovered with recover().
101-
frame.PanicValue = plainError(msg)
105+
frame.PanicValue = err
102106
frame.Panicking = panicTrue
103107
tinygo_longjmp(frame)
104108
// unreachable
@@ -131,7 +135,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
131135
// Defer is not currently allowed in interrupts.
132136
// We could add support for this, but since defer might also allocate
133137
// (especially in loops) it might not be a good idea anyway.
134-
runtimePanicAt(returnAddress(0), "defer in interrupt")
138+
runtimePanicAt(returnAddress(0), &errDeferInInterrupt)
135139
}
136140
currentTask := task.Current()
137141
frame.Previous = (*deferFrame)(currentTask.DeferFrame)
@@ -201,54 +205,54 @@ func _recover(useParentFrame bool) interface{} {
201205

202206
// Panic when trying to dereference a nil pointer.
203207
func nilPanic() {
204-
runtimePanicAt(returnAddress(0), "nil pointer dereference")
208+
runtimePanicAt(returnAddress(0), &errNilPointerDereference)
205209
}
206210

207211
// Panic when trying to add an entry to a nil map
208212
func nilMapPanic() {
209-
runtimePanicAt(returnAddress(0), "assignment to entry in nil map")
213+
runtimePanicAt(returnAddress(0), &errAssignmentToEntryInNilMap)
210214
}
211215

212216
// Panic when trying to access an array or slice out of bounds.
213217
func lookupPanic() {
214-
runtimePanicAt(returnAddress(0), "index out of range")
218+
runtimePanicAt(returnAddress(0), &errIndexOutOfRange)
215219
}
216220

217221
// Panic when trying to slice a slice out of bounds.
218222
func slicePanic() {
219-
runtimePanicAt(returnAddress(0), "slice out of range")
223+
runtimePanicAt(returnAddress(0), &errSliceOutOfRange)
220224
}
221225

222226
// Panic when trying to convert a slice to an array pointer (Go 1.17+) and the
223227
// slice is shorter than the array.
224228
func sliceToArrayPointerPanic() {
225-
runtimePanicAt(returnAddress(0), "slice smaller than array")
229+
runtimePanicAt(returnAddress(0), &errSliceSmallerThanArray)
226230
}
227231

228232
// Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+)
229233
// with a len that's too large (which includes if the ptr is nil and len is
230234
// nonzero).
231235
func unsafeSlicePanic() {
232-
runtimePanicAt(returnAddress(0), "unsafe.Slice/String: len out of range")
236+
runtimePanicAt(returnAddress(0), &errUnsafeSliceStringLenOutOfRange)
233237
}
234238

235239
// Panic when trying to create a new channel that is too big.
236240
func chanMakePanic() {
237-
runtimePanicAt(returnAddress(0), "new channel is too big")
241+
runtimePanicAt(returnAddress(0), &errNewChannelIsTooBig)
238242
}
239243

240244
// Panic when a shift value is negative.
241245
func negativeShiftPanic() {
242-
runtimePanicAt(returnAddress(0), "negative shift")
246+
runtimePanicAt(returnAddress(0), &errNegativeShift)
243247
}
244248

245249
// Panic when there is a divide by zero.
246250
func divideByZeroPanic() {
247-
runtimePanicAt(returnAddress(0), "divide by zero")
251+
runtimePanicAt(returnAddress(0), &errDivideByZero)
248252
}
249253

250254
func blockingPanic() {
251-
runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function")
255+
runtimePanicAt(returnAddress(0), &errTryingToDoBlockingOperationInExported)
252256
}
253257

254258
//go:linkname fips_fatal crypto/internal/fips140.fatal

src/runtime/runtime_unix.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ func tinygo_sigpanic() {
151151
sig := tinygo_caught_signal
152152
switch sig {
153153
case sig_SIGSEGV, sig_SIGBUS:
154-
runtimePanic("nil pointer dereference")
154+
runtimePanicAt(returnAddress(0), &errNilPointerDereference)
155155
case sig_SIGFPE:
156-
runtimePanic("divide by zero")
156+
runtimePanicAt(returnAddress(0), &errDivideByZero)
157157
default:
158-
runtimePanic("signal")
158+
runtimePanicAt(returnAddress(0), &errSignal)
159159
}
160160
}
161161

@@ -382,7 +382,7 @@ func signal_enable(s uint32) {
382382
if s >= 32 {
383383
// TODO: to support higher signal numbers, we need to turn
384384
// receivedSignals into a uint32 array.
385-
runtimePanicAt(returnAddress(0), "unsupported signal number")
385+
runtimePanicAt(returnAddress(0), &errUnsupportedSignalNumber)
386386
}
387387

388388
// This is intentonally a non-atomic store. This is safe, since hasSignals
@@ -399,7 +399,7 @@ func signal_ignore(s uint32) {
399399
if s >= 32 {
400400
// TODO: to support higher signal numbers, we need to turn
401401
// receivedSignals into a uint32 array.
402-
runtimePanicAt(returnAddress(0), "unsupported signal number")
402+
runtimePanicAt(returnAddress(0), &errUnsupportedSignalNumber)
403403
}
404404
tinygo_signal_ignore(s)
405405
}
@@ -409,7 +409,7 @@ func signal_disable(s uint32) {
409409
if s >= 32 {
410410
// TODO: to support higher signal numbers, we need to turn
411411
// receivedSignals into a uint32 array.
412-
runtimePanicAt(returnAddress(0), "unsupported signal number")
412+
runtimePanicAt(returnAddress(0), &errUnsupportedSignalNumber)
413413
}
414414
tinygo_signal_disable(s)
415415
}

src/runtime/runtime_windows.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,11 @@ func tinygo_init_exception_handler()
313313
func tinygo_sigpanic_windows(exceptionCode int32) {
314314
switch uint32(exceptionCode) {
315315
case _EXCEPTION_ACCESS_VIOLATION, _EXCEPTION_IN_PAGE_ERROR:
316-
runtimePanic("nil pointer dereference")
316+
runtimePanicAt(returnAddress(0), &errNilPointerDereference)
317317
case _EXCEPTION_INT_DIVIDE_BY_ZERO:
318-
runtimePanic("divide by zero")
318+
runtimePanicAt(returnAddress(0), &errDivideByZero)
319319
case _EXCEPTION_INT_OVERFLOW:
320-
runtimePanic("integer overflow")
320+
runtimePanicAt(returnAddress(0), &errIntegerOverflow)
321321
default:
322322
runtimePanic("unknown exception")
323323
}

0 commit comments

Comments
 (0)