runtime: encode pending Goexit in panic state

Treat panicState as a bitmask so a recovered panic during Goexit
can clear the panic bit while preserving the pending Goexit bit.
This removes the separate deferFrame Goexit field and restores the
previous defer frame size.
This commit is contained in:
Jake Bailey
2026-07-04 14:08:16 -07:00
committed by Ron Evans
parent d59c706e60
commit e039ce131c
3 changed files with 19 additions and 22 deletions
+3 -3
View File
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test. // This is a small number of very diverse targets that we want to test.
tests := []sizeTest{ tests := []sizeTest{
// microcontrollers // microcontrollers
{"hifive1b", "examples/echo", 3765, 307, 0, 2260}, {"hifive1b", "examples/echo", 3771, 309, 0, 2260},
{"microbit", "examples/serial", 2824, 368, 8, 2256}, {"microbit", "examples/serial", 2832, 368, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8041, 1663, 132, 7488}, {"wioterminal", "examples/pininterrupt", 8053, 1663, 132, 7488},
// TODO: also check wasm. Right now this is difficult, because // TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the // wasm binaries are run through wasm-opt and therefore the
+3 -3
View File
@@ -3,7 +3,7 @@ source_filename = "defer.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi" target triple = "thumbv7m-unknown-unknown-eabi"
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr, i1 } %runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr }
%runtime._interface = type { ptr, ptr } %runtime._interface = type { ptr, ptr }
; Function Attrs: nounwind ; Function Attrs: nounwind
@@ -111,9 +111,9 @@ rundefers.end3: ; preds = %rundefers.loophead6
; Function Attrs: nocallback nofree nosync nounwind willreturn ; Function Attrs: nocallback nofree nosync nounwind willreturn
declare ptr @llvm.stacksave.p0() #2 declare ptr @llvm.stacksave.p0() #2
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(32), ptr, ptr) #1 declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(28), ptr, ptr) #1
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(32), ptr) #1 declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(28), ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 { define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 {
+13 -16
View File
@@ -38,17 +38,15 @@ type deferFrame struct {
JumpPC unsafe.Pointer // pc to return to JumpPC unsafe.Pointer // pc to return to
ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture) ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture)
Previous *deferFrame // previous recover buffer pointer Previous *deferFrame // previous recover buffer pointer
Panicking panicState // not panicking, panicking, or in Goexit Panicking panicState // panic/Goexit state
PanicValue interface{} // panic value, might be nil for panic(nil) for example PanicValue interface{} // panic value, might be nil for panic(nil) for example
DeferPtr unsafe.Pointer // head of the stack-allocated defer list DeferPtr unsafe.Pointer // head of the stack-allocated defer list
Goexit bool // whether Goexit is still pending after a recovered deferred panic
} }
type panicState uint8 type panicState uint8
const ( const (
panicFalse panicState = iota panicTrue panicState = 1 << iota
panicTrue
panicGoexit panicGoexit
) )
@@ -66,10 +64,10 @@ func panicOrGoexit(message interface{}, panicking panicState) {
if supportsRecover() && !interrupt.In() { if supportsRecover() && !interrupt.In() {
frame := (*deferFrame)(task.Current().DeferFrame) frame := (*deferFrame)(task.Current().DeferFrame)
if frame != nil { if frame != nil {
if panicking == panicGoexit {
frame.Goexit = true
}
frame.PanicValue = message frame.PanicValue = message
if panicking&panicTrue != 0 {
panicking |= frame.Panicking & panicGoexit
}
frame.Panicking = panicking frame.Panicking = panicking
tinygo_longjmp(frame) tinygo_longjmp(frame)
// unreachable // unreachable
@@ -103,7 +101,7 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
// Use the normal panic mechanism so that this runtime error // Use the normal panic mechanism so that this runtime error
// can be recovered with recover(). // can be recovered with recover().
frame.PanicValue = plainError(msg) frame.PanicValue = plainError(msg)
frame.Panicking = panicTrue frame.Panicking = panicTrue | (frame.Panicking & panicGoexit)
tinygo_longjmp(frame) tinygo_longjmp(frame)
// unreachable // unreachable
} }
@@ -140,9 +138,8 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
currentTask := task.Current() currentTask := task.Current()
frame.Previous = (*deferFrame)(currentTask.DeferFrame) frame.Previous = (*deferFrame)(currentTask.DeferFrame)
frame.JumpSP = jumpSP frame.JumpSP = jumpSP
frame.Panicking = panicFalse frame.Panicking = 0
frame.DeferPtr = nil frame.DeferPtr = nil
frame.Goexit = false
currentTask.DeferFrame = unsafe.Pointer(frame) currentTask.DeferFrame = unsafe.Pointer(frame)
} }
@@ -154,12 +151,12 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
//go:nobounds //go:nobounds
func destroyDeferFrame(frame *deferFrame) { func destroyDeferFrame(frame *deferFrame) {
task.Current().DeferFrame = unsafe.Pointer(frame.Previous) task.Current().DeferFrame = unsafe.Pointer(frame.Previous)
if frame.Panicking != panicFalse { if frame.Panicking&panicTrue != 0 {
// We're still panicking! // We're still panicking!
// Re-raise the panic now. // Re-raise the panic now.
panicOrGoexit(frame.PanicValue, frame.Panicking) panicOrGoexit(frame.PanicValue, panicTrue)
} }
if frame.Goexit { if frame.Panicking&panicGoexit != 0 {
// A deferred function panicked during Goexit, and that panic was // A deferred function panicked during Goexit, and that panic was
// recovered. Continue the original Goexit instead of returning. // recovered. Continue the original Goexit instead of returning.
panicOrGoexit(nil, panicGoexit) panicOrGoexit(nil, panicGoexit)
@@ -194,15 +191,15 @@ func _recover(useParentFrame bool) interface{} {
// already), but instead from the previous frame. // already), but instead from the previous frame.
frame = frame.Previous frame = frame.Previous
} }
if frame != nil && frame.Panicking != panicFalse { if frame != nil && frame.Panicking != 0 {
if frame.Panicking == panicGoexit { if frame.Panicking&panicTrue == 0 {
// Special value that indicates we're exiting the goroutine using // Special value that indicates we're exiting the goroutine using
// Goexit(). Therefore, make this recover call a no-op. // Goexit(). Therefore, make this recover call a no-op.
return nil return nil
} }
// Only the first call to recover returns the panic value. It also stops // Only the first call to recover returns the panic value. It also stops
// the panicking sequence, hence setting panicking to false. // the panicking sequence, hence setting panicking to false.
frame.Panicking = panicFalse frame.Panicking &^= panicTrue
return frame.PanicValue return frame.PanicValue
} }
// Not panicking, so return a nil interface. // Not panicking, so return a nil interface.