Skip to content

Commit

Permalink
runtime: implement Goexit
Browse files Browse the repository at this point in the history
This is needed for full support for the testing package
  • Loading branch information
aykevl committed Nov 30, 2024
1 parent ecc6d16 commit dd8d1e1
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 14 deletions.
2 changes: 1 addition & 1 deletion compiler/testdata/defer-cortex-m-qemu.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
36 changes: 29 additions & 7 deletions src/runtime/panic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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()
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}
}

Expand All @@ -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.
Expand Down
7 changes: 1 addition & 6 deletions src/runtime/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
19 changes: 19 additions & 0 deletions testdata/recover.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package main

import (
"runtime"
"time"
)

func main() {
println("# simple recover")
recoverSimple()
Expand All @@ -22,6 +27,9 @@ func main() {

println("\n# defer panic")
deferPanic()

println("\n# runtime.Goexit")
runtimeGoexit()
}

func recoverSimple() {
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions testdata/recover.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit dd8d1e1

Please sign in to comment.