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.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3765, 307, 0, 2260},
{"microbit", "examples/serial", 2824, 368, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8041, 1663, 132, 7488},
{"hifive1b", "examples/echo", 3771, 309, 0, 2260},
{"microbit", "examples/serial", 2832, 368, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8053, 1663, 132, 7488},
// TODO: also check wasm. Right now this is difficult, because
// 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 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 }
; Function Attrs: nounwind
@@ -111,9 +111,9 @@ rundefers.end3: ; preds = %rundefers.loophead6
; Function Attrs: nocallback nofree nosync nounwind willreturn
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
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
ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture)
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
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
const (
panicFalse panicState = iota
panicTrue
panicTrue panicState = 1 << iota
panicGoexit
)
@@ -66,10 +64,10 @@ func panicOrGoexit(message interface{}, panicking panicState) {
if supportsRecover() && !interrupt.In() {
frame := (*deferFrame)(task.Current().DeferFrame)
if frame != nil {
if panicking == panicGoexit {
frame.Goexit = true
}
frame.PanicValue = message
if panicking&panicTrue != 0 {
panicking |= frame.Panicking & panicGoexit
}
frame.Panicking = panicking
tinygo_longjmp(frame)
// unreachable
@@ -103,7 +101,7 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
// Use the normal panic mechanism so that this runtime error
// can be recovered with recover().
frame.PanicValue = plainError(msg)
frame.Panicking = panicTrue
frame.Panicking = panicTrue | (frame.Panicking & panicGoexit)
tinygo_longjmp(frame)
// unreachable
}
@@ -140,9 +138,8 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
currentTask := task.Current()
frame.Previous = (*deferFrame)(currentTask.DeferFrame)
frame.JumpSP = jumpSP
frame.Panicking = panicFalse
frame.Panicking = 0
frame.DeferPtr = nil
frame.Goexit = false
currentTask.DeferFrame = unsafe.Pointer(frame)
}
@@ -154,12 +151,12 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
//go:nobounds
func destroyDeferFrame(frame *deferFrame) {
task.Current().DeferFrame = unsafe.Pointer(frame.Previous)
if frame.Panicking != panicFalse {
if frame.Panicking&panicTrue != 0 {
// We're still panicking!
// 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
// recovered. Continue the original Goexit instead of returning.
panicOrGoexit(nil, panicGoexit)
@@ -194,15 +191,15 @@ func _recover(useParentFrame bool) interface{} {
// already), but instead from the previous frame.
frame = frame.Previous
}
if frame != nil && frame.Panicking != panicFalse {
if frame.Panicking == panicGoexit {
if frame != nil && frame.Panicking != 0 {
if frame.Panicking&panicTrue == 0 {
// Special value that indicates we're exiting the goroutine using
// Goexit(). Therefore, make this recover call a no-op.
return nil
}
// Only the first call to recover returns the panic value. It also stops
// the panicking sequence, hence setting panicking to false.
frame.Panicking = panicFalse
frame.Panicking &^= panicTrue
return frame.PanicValue
}
// Not panicking, so return a nil interface.