mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 12:33:42 +00:00
compiler: implement recover() built-in function
This commit is contained in:
committed by
Ron Evans
parent
79ba6a50c3
commit
8d6b210c09
@@ -21,3 +21,13 @@ tinygo_scanCurrentStack:
|
||||
// were only pushed to be discoverable by the GC.
|
||||
addl $28, %esp
|
||||
retl
|
||||
|
||||
|
||||
.section .text.tinygo_longjmp
|
||||
.global tinygo_longjmp
|
||||
tinygo_longjmp:
|
||||
// Note: the code we jump to assumes eax is set to a non-zero value if we
|
||||
// jump from here.
|
||||
movl 8(%esp), %eax // jumpPC (stash in volatile register)
|
||||
movl 4(%esp), %esp // jumpSP
|
||||
jmpl *%eax
|
||||
@@ -28,6 +28,22 @@ _tinygo_scanCurrentStack:
|
||||
addq $56, %rsp
|
||||
retq
|
||||
|
||||
|
||||
#ifdef __ELF__
|
||||
.section .text.tinygo_longjmp
|
||||
.global tinygo_longjmp
|
||||
tinygo_longjmp:
|
||||
#else // Darwin
|
||||
.global _tinygo_longjmp
|
||||
_tinygo_longjmp:
|
||||
#endif
|
||||
// Note: the code we jump to assumes rax is non-zero so we have to load it
|
||||
// with some value here.
|
||||
movq $1, %rax
|
||||
movq %rdi, %rsp // jumpSP
|
||||
jmpq *%rsi // jumpPC
|
||||
|
||||
|
||||
#ifdef __MACH__ // Darwin
|
||||
// allow these symbols to stripped as dead code
|
||||
.subsections_via_symbols
|
||||
@@ -20,3 +20,12 @@ tinygo_scanCurrentStack:
|
||||
// were only pushed to be discoverable by the GC.
|
||||
addq $72, %rsp
|
||||
retq
|
||||
|
||||
.section .text.tinygo_longjmp,"ax"
|
||||
.global tinygo_longjmp
|
||||
tinygo_longjmp:
|
||||
// Note: the code we jump to assumes rax is non-zero so we have to load it
|
||||
// with some value here.
|
||||
movq $1, %rax
|
||||
movq %rcx, %rsp // jumpSP
|
||||
jmpq *%rdx // jumpPC
|
||||
@@ -31,3 +31,16 @@ tinygo_scanCurrentStack:
|
||||
pop {pc}
|
||||
.cfi_endproc
|
||||
.size tinygo_scanCurrentStack, .-tinygo_scanCurrentStack
|
||||
|
||||
|
||||
.section .text.tinygo_longjmp
|
||||
.global tinygo_longjmp
|
||||
.type tinygo_longjmp, %function
|
||||
tinygo_longjmp:
|
||||
.cfi_startproc
|
||||
// Note: the code we jump to assumes r0 is set to a non-zero value if we
|
||||
// jump from here (which is conveniently already the case).
|
||||
mov sp, r0 // jumpSP
|
||||
mov pc, r1 // jumpPC
|
||||
.cfi_endproc
|
||||
.size tinygo_longjmp, .-tinygo_longjmp
|
||||
@@ -30,3 +30,18 @@ tinygo_scanCurrentStack:
|
||||
// Restore stack state and return.
|
||||
ldp x29, x30, [sp], #96
|
||||
ret
|
||||
|
||||
|
||||
#ifdef __MACH__
|
||||
.global _tinygo_longjmp
|
||||
_tinygo_longjmp:
|
||||
#else
|
||||
.section .text.tinygo_longjmp
|
||||
.global tinygo_longjmp
|
||||
.type tinygo_longjmp, %function
|
||||
tinygo_longjmp:
|
||||
#endif
|
||||
// Note: the code we jump to assumes x0 is set to a non-zero value if we
|
||||
// jump from here (which is conveniently already the case).
|
||||
mov sp, x0 // jumpSP
|
||||
br x1 // jumpPC
|
||||
@@ -40,3 +40,12 @@ tinygo_scanCurrentStack:
|
||||
|
||||
// Return to the caller.
|
||||
ret
|
||||
|
||||
|
||||
.section .text.tinygo_longjmp
|
||||
.global tinygo_longjmp
|
||||
tinygo_longjmp:
|
||||
// Note: the code we jump to assumes a0 is non-zero, which is already the
|
||||
// case because that's jumpSP (the stack pointer).
|
||||
mv sp, a0 // jumpSP
|
||||
jr a1 // jumpPC
|
||||
+76
-4
@@ -1,12 +1,35 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"internal/task"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// trap is a compiler hint that this function cannot be executed. It is
|
||||
// translated into either a trap instruction or a call to abort().
|
||||
//export llvm.trap
|
||||
func trap()
|
||||
|
||||
// Inline assembly stub. It is essentially C longjmp but modified a bit for the
|
||||
// purposes of TinyGo. It restores the stack pointer and jumps to the given pc.
|
||||
//export tinygo_longjmp
|
||||
func tinygo_longjmp(sp, pc unsafe.Pointer)
|
||||
|
||||
// Compiler intrinsic.
|
||||
// Returns whether recover is supported on the current architecture.
|
||||
func supportsRecover() bool
|
||||
|
||||
// Builtin function panic(msg), used as a compiler intrinsic.
|
||||
func _panic(message interface{}) {
|
||||
if supportsRecover() {
|
||||
frame := task.Current().DeferFrame
|
||||
if frame != nil {
|
||||
frame.PanicValue = message
|
||||
frame.Panicking = true
|
||||
tinygo_longjmp(frame.JumpSP, frame.JumpPC)
|
||||
// unreachable
|
||||
}
|
||||
}
|
||||
printstring("panic: ")
|
||||
printitf(message)
|
||||
printnl()
|
||||
@@ -20,10 +43,59 @@ func runtimePanic(msg string) {
|
||||
abort()
|
||||
}
|
||||
|
||||
// Try to recover a panicking goroutine.
|
||||
func _recover() interface{} {
|
||||
// Deferred functions are currently not executed during panic, so there is
|
||||
// no way this can return anything besides nil.
|
||||
// Called at the start of a function that includes a deferred call.
|
||||
// It gets passed in the stack-allocated defer frame and configures it.
|
||||
// Note that the frame is not zeroed yet, so we need to initialize all values
|
||||
// that will be used.
|
||||
//go:inline
|
||||
//go:nobounds
|
||||
func setupDeferFrame(frame *task.DeferFrame, jumpSP unsafe.Pointer) {
|
||||
currentTask := task.Current()
|
||||
frame.Previous = currentTask.DeferFrame
|
||||
frame.JumpSP = jumpSP
|
||||
frame.Panicking = false
|
||||
currentTask.DeferFrame = frame
|
||||
}
|
||||
|
||||
// Called right before the return instruction. It pops the defer frame from the
|
||||
// linked list of defer frames. It also re-raises a panic if the goroutine is
|
||||
// still panicking.
|
||||
//go:inline
|
||||
//go:nobounds
|
||||
func destroyDeferFrame(frame *task.DeferFrame) {
|
||||
task.Current().DeferFrame = frame.Previous
|
||||
if frame.Panicking {
|
||||
// We're still panicking!
|
||||
// Re-raise the panic now.
|
||||
_panic(frame.PanicValue)
|
||||
}
|
||||
}
|
||||
|
||||
// _recover is the built-in recover() function. It tries to recover a currently
|
||||
// panicking goroutine.
|
||||
// useParentFrame is set when the caller of runtime._recover has a defer frame
|
||||
// itself. In that case, recover() shouldn't check that frame but one frame up.
|
||||
func _recover(useParentFrame bool) interface{} {
|
||||
if !supportsRecover() {
|
||||
// Compiling without stack unwinding support, so make this a no-op.
|
||||
return nil
|
||||
}
|
||||
// TODO: somehow check that recover() is called directly by a deferred
|
||||
// function in a panicking goroutine. Maybe this can be done by comparing
|
||||
// the frame pointer?
|
||||
frame := task.Current().DeferFrame
|
||||
if useParentFrame {
|
||||
// Don't recover panic from the current frame (which can't be panicking
|
||||
// already), but instead from the previous frame.
|
||||
frame = frame.Previous
|
||||
}
|
||||
if frame != nil && frame.Panicking {
|
||||
// Only the first call to recover returns the panic value. It also stops
|
||||
// the panicking sequence, hence setting panicking to false.
|
||||
frame.Panicking = false
|
||||
return frame.PanicValue
|
||||
}
|
||||
// Not panicking, so return a nil interface.
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user