1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
package runtime
import (
"internal/task"
"runtime/interrupt"
"tinygo"
"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(frame *deferFrame)
// Compiler intrinsic.
// Returns whether recover is supported on the current architecture.
func supportsRecover() bool
// Compile intrinsic.
// Returns which strategy is used. This is usually "print" but can be changed
// using the -panic= compiler flag.
func panicStrategy() uint8
// DeferFrame is a stack allocated object that stores information for the
// current "defer frame", which is used in functions that use the `defer`
// keyword.
// The compiler knows about the JumpPC struct offset, so it should not be moved
// without also updating compiler/defer.go.
type deferFrame struct {
JumpSP unsafe.Pointer // stack pointer to return to
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
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()
}
// Note: recover is not supported inside interrupts.
// (This could be supported, like defer, but we currently don't).
if supportsRecover() && !interrupt.In() {
frame := (*deferFrame)(task.Current().DeferFrame)
if frame != nil {
frame.PanicValue = message
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()
abort()
}
// Cause a runtime panic, which is (currently) always a string.
func runtimePanic(msg string) {
// As long as this function is inined, llvm.returnaddress(0) will return
// something sensible.
runtimePanicAt(returnAddress(0), msg)
}
func runtimePanicAt(addr unsafe.Pointer, msg string) {
if panicStrategy() == tinygo.PanicStrategyTrap {
trap()
}
if hasReturnAddr {
printstring("panic: runtime error at ")
printptr(uintptr(addr) - callInstSize)
printstring(": ")
} else {
printstring("panic: runtime error: ")
}
println(msg)
abort()
}
// 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 *deferFrame, jumpSP unsafe.Pointer) {
if interrupt.In() {
// Defer is not currently allowed in interrupts.
// We could add support for this, but since defer might also allocate
// (especially in loops) it might not be a good idea anyway.
runtimePanicAt(returnAddress(0), "defer in interrupt")
}
currentTask := task.Current()
frame.Previous = (*deferFrame)(currentTask.DeferFrame)
frame.JumpSP = jumpSP
frame.Panicking = panicFalse
currentTask.DeferFrame = unsafe.Pointer(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 *deferFrame) {
task.Current().DeferFrame = unsafe.Pointer(frame.Previous)
if frame.Panicking != panicFalse {
// We're still panicking!
// Re-raise the panic now.
panicOrGoexit(frame.PanicValue, frame.Panicking)
}
}
// _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() || interrupt.In() {
// Either we're compiling without stack unwinding support, or we're
// inside an interrupt where panic/recover is not supported. Either way,
// make this a no-op since panic() won't do any long jumps to a deferred
// function.
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 := (*deferFrame)(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 != 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 = panicFalse
return frame.PanicValue
}
// Not panicking, so return a nil interface.
return nil
}
// Panic when trying to dereference a nil pointer.
func nilPanic() {
runtimePanicAt(returnAddress(0), "nil pointer dereference")
}
// Panic when trying to add an entry to a nil map
func nilMapPanic() {
runtimePanicAt(returnAddress(0), "assignment to entry in nil map")
}
// Panic when trying to access an array or slice out of bounds.
func lookupPanic() {
runtimePanicAt(returnAddress(0), "index out of range")
}
// Panic when trying to slice a slice out of bounds.
func slicePanic() {
runtimePanicAt(returnAddress(0), "slice out of range")
}
// Panic when trying to convert a slice to an array pointer (Go 1.17+) and the
// slice is shorter than the array.
func sliceToArrayPointerPanic() {
runtimePanicAt(returnAddress(0), "slice smaller than array")
}
// Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+)
// with a len that's too large (which includes if the ptr is nil and len is
// nonzero).
func unsafeSlicePanic() {
runtimePanicAt(returnAddress(0), "unsafe.Slice/String: len out of range")
}
// Panic when trying to create a new channel that is too big.
func chanMakePanic() {
runtimePanicAt(returnAddress(0), "new channel is too big")
}
// Panic when a shift value is negative.
func negativeShiftPanic() {
runtimePanicAt(returnAddress(0), "negative shift")
}
// Panic when there is a divide by zero.
func divideByZeroPanic() {
runtimePanicAt(returnAddress(0), "divide by zero")
}
func blockingPanic() {
runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function")
}
|