From dd8d1e1007f2672c154c9f74cf8d338bd29ceb16 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 21 Nov 2024 09:20:14 +0100 Subject: [PATCH] runtime: implement Goexit This is needed for full support for the testing package --- compiler/testdata/defer-cortex-m-qemu.ll | 2 +- src/runtime/panic.go | 36 +++++++++++++++++++----- src/runtime/scheduler.go | 7 +---- testdata/recover.go | 19 +++++++++++++ testdata/recover.txt | 3 ++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index 52a3bfbabf..cdcfd0ea44 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -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, i1, %runtime._interface } +%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface } %runtime._interface = type { ptr, ptr } %runtime._defer = type { i32, ptr } diff --git a/src/runtime/panic.go b/src/runtime/panic.go index d429171ec6..ec33a4469c 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -38,12 +38,24 @@ 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 bool // true iff this defer frame is panicking + Panicking panicState // not panicking, panicking, or in Goexit PanicValue interface{} // panic value, might be nil for panic(nil) for example } +type panicState uint8 + +const ( + panicFalse panicState = iota + panicTrue + panicGoexit +) + // Builtin function panic(msg), used as a compiler intrinsic. func _panic(message interface{}) { + panicOrGoexit(message, panicTrue) +} + +func panicOrGoexit(message interface{}, panicking panicState) { if panicStrategy() == tinygo.PanicStrategyTrap { trap() } @@ -53,11 +65,16 @@ func _panic(message interface{}) { frame := (*deferFrame)(task.Current().DeferFrame) if frame != nil { frame.PanicValue = message - frame.Panicking = true + frame.Panicking = panicking tinygo_longjmp(frame) // unreachable } } + if panicking == panicGoexit { + // Call to Goexit() instead of a panic. + // Exit the goroutine instead of printing a panic message. + deadlock() + } printstring("panic: ") printitf(message) printnl() @@ -103,7 +120,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { currentTask := task.Current() frame.Previous = (*deferFrame)(currentTask.DeferFrame) frame.JumpSP = jumpSP - frame.Panicking = false + frame.Panicking = panicFalse currentTask.DeferFrame = unsafe.Pointer(frame) } @@ -115,10 +132,10 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { //go:nobounds func destroyDeferFrame(frame *deferFrame) { task.Current().DeferFrame = unsafe.Pointer(frame.Previous) - if frame.Panicking { + if frame.Panicking != panicFalse { // We're still panicking! // Re-raise the panic now. - _panic(frame.PanicValue) + panicOrGoexit(frame.PanicValue, frame.Panicking) } } @@ -143,10 +160,15 @@ func _recover(useParentFrame bool) interface{} { // already), but instead from the previous frame. frame = frame.Previous } - if frame != nil && frame.Panicking { + if frame != nil && frame.Panicking != panicFalse { + if frame.Panicking == panicGoexit { + // 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 = false + frame.Panicking = panicFalse return frame.PanicValue } // Not panicking, so return a nil interface. diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index d84ccf3e0e..727c7f5f2c 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -28,11 +28,6 @@ func scheduleLogChan(msg string, ch *channel, t *task.Task) { } // Goexit terminates the currently running goroutine. No other goroutines are affected. -// -// Unlike the main Go implementation, no deferred calls will be run. -// -//go:inline func Goexit() { - // TODO: run deferred functions - deadlock() + panicOrGoexit(nil, panicGoexit) } diff --git a/testdata/recover.go b/testdata/recover.go index c7c02c94a4..260bf91bdd 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -1,5 +1,10 @@ package main +import ( + "runtime" + "time" +) + func main() { println("# simple recover") recoverSimple() @@ -22,6 +27,9 @@ func main() { println("\n# defer panic") deferPanic() + + println("\n# runtime.Goexit") + runtimeGoexit() } func recoverSimple() { @@ -104,6 +112,17 @@ func deferPanic() { println("defer panic") } +func runtimeGoexit() { + go func() { + defer func() { + println("Goexit deferred function, recover is nil:", recover() == nil) + }() + + runtime.Goexit() + }() + time.Sleep(time.Millisecond) +} + func printitf(msg string, itf interface{}) { switch itf := itf.(type) { case string: diff --git a/testdata/recover.txt b/testdata/recover.txt index 3575058812..87e4ba5d17 100644 --- a/testdata/recover.txt +++ b/testdata/recover.txt @@ -27,3 +27,6 @@ recovered: panic 2 # defer panic defer panic recovered from deferred call: deferred panic + +# runtime.Goexit +Goexit deferred function, recover is nil: true