runtime, testing: support Goexit, SkipNow, FailNow

Keep a pending Goexit separate from the active panic state so recovered
deferred panics do not cancel the original Goexit unwind. Goexit should
also ignore -panic=trap, since it is not a panic.

Wire testing.FailNow and SkipNow through runtime.Goexit, and run tests
and benchmarks in goroutines. This lets Goexit terminate the user
function without skipping test bookkeeping. Add Goexit/recover coverage
and enable crypto/ecdh in the Linux stdlib test list.
This commit is contained in:
Jake Bailey
2026-07-03 16:30:32 -07:00
committed by Ron Evans
parent fb1bf2e6bf
commit 16eefc59eb
15 changed files with 201 additions and 23 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ const GOARCH = "386"
// The bitness of the CPU (e.g. 8, 32, 64).
const TargetBits = 32
const deferExtraRegs = 0
const deferExtraRegs = 4
const callInstSize = 5 // "call someFunction" is 5 bytes
+4
View File
@@ -43,6 +43,10 @@ tinygo_longjmp:
// Note: the code we jump to assumes eax is set to a non-zero value if we
// jump from here.
movl 4(%esp), %eax
movl 8(%eax), %ebx
movl 12(%eax), %esi
movl 16(%eax), %edi
movl 20(%eax), %ebp
movl 0(%eax), %esp // jumpSP
movl 4(%eax), %eax // jumpPC (stash in volatile register)
jmpl *%eax
+11 -1
View File
@@ -41,6 +41,7 @@ type deferFrame struct {
Panicking panicState // not panicking, panicking, or in Goexit
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
@@ -57,7 +58,7 @@ func _panic(message interface{}) {
}
func panicOrGoexit(message interface{}, panicking panicState) {
if panicStrategy() == tinygo.PanicStrategyTrap {
if panicking != panicGoexit && panicStrategy() == tinygo.PanicStrategyTrap {
trap()
}
// Note: recover is not supported inside interrupts.
@@ -65,6 +66,9 @@ 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
frame.Panicking = panicking
tinygo_longjmp(frame)
@@ -138,6 +142,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
frame.JumpSP = jumpSP
frame.Panicking = panicFalse
frame.DeferPtr = nil
frame.Goexit = false
currentTask.DeferFrame = unsafe.Pointer(frame)
}
@@ -154,6 +159,11 @@ func destroyDeferFrame(frame *deferFrame) {
// Re-raise the panic now.
panicOrGoexit(frame.PanicValue, frame.Panicking)
}
if frame.Goexit {
// A deferred function panicked during Goexit, and that panic was
// recovered. Continue the original Goexit instead of returning.
panicOrGoexit(nil, panicGoexit)
}
}
// _recover is the built-in recover() function. It tries to recover a currently