mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 10:37:46 +00:00
d1fe02df23
This moves all scheduler code into a separate file that is only compiled
when there's a scheduler in use (the tasks or asyncify scheduler, which
are both cooperative). The main goal of this change is to make it easier
to add a new "scheduler" based on OS threads.
It also fixes a few subtle issues with `-gc=none`:
- Gosched() panicked. This is now fixed to just return immediately
(the only logical thing to do when there's only one goroutine).
- Timers aren't supported without a scheduler, but the relevant code
was still present and would happily add a timer to the queue. It
just never ran. So now it exits with a runtime error, similar to any
blocking operation.
134 lines
4.0 KiB
Go
134 lines
4.0 KiB
Go
//go:build scheduler.asyncify
|
|
|
|
package task
|
|
|
|
import (
|
|
"unsafe"
|
|
)
|
|
|
|
// Stack canary, to detect a stack overflow. The number is a random number
|
|
// generated by random.org. The bit fiddling dance is necessary because
|
|
// otherwise Go wouldn't allow the cast to a smaller integer size.
|
|
const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
|
|
|
|
//go:linkname runtimePanic runtime.runtimePanic
|
|
func runtimePanic(str string)
|
|
|
|
// state is a structure which holds a reference to the state of the task.
|
|
// When the task is suspended, the stack pointers are saved here.
|
|
type state struct {
|
|
// entry is the entry function of the task.
|
|
// This is needed every time the function is invoked so that asyncify knows what to rewind.
|
|
entry uintptr
|
|
|
|
// args are a pointer to a struct holding the arguments of the function.
|
|
args unsafe.Pointer
|
|
|
|
// stackState is the state of the stack while unwound.
|
|
stackState
|
|
|
|
launched bool
|
|
}
|
|
|
|
// stackState is the saved state of a stack while unwound.
|
|
// The stack is arranged with asyncify at the bottom, C stack at the top, and a gap of available stack space between the two.
|
|
type stackState struct {
|
|
// asyncify is the stack pointer of the asyncify stack.
|
|
// This starts from the bottom and grows upwards.
|
|
asyncifysp unsafe.Pointer
|
|
|
|
// asyncify is stack pointer of the C stack.
|
|
// This starts from the top and grows downwards.
|
|
csp unsafe.Pointer
|
|
|
|
// Pointer to the first (lowest address) of the stack. It must never be
|
|
// overwritten. It can be checked from time to time to see whether a stack
|
|
// overflow happened in the past.
|
|
canaryPtr *uintptr
|
|
}
|
|
|
|
// start creates and starts a new goroutine with the given function and arguments.
|
|
// The new goroutine is immediately started.
|
|
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
|
t := &Task{}
|
|
t.state.initialize(fn, args, stackSize)
|
|
scheduleTask(t)
|
|
}
|
|
|
|
//export tinygo_launch
|
|
func (*state) launch()
|
|
|
|
//go:linkname align runtime.align
|
|
func align(p uintptr) uintptr
|
|
|
|
// initialize the state and prepare to call the specified function with the specified argument bundle.
|
|
func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
|
// Save the entry call.
|
|
s.entry = fn
|
|
s.args = args
|
|
|
|
// Create a stack.
|
|
stack := runtime_alloc(stackSize, nil)
|
|
|
|
// Set up the stack canary, a random number that should be checked when
|
|
// switching from the task back to the scheduler. The stack canary pointer
|
|
// points to the first word of the stack. If it has changed between now and
|
|
// the next stack switch, there was a stack overflow.
|
|
s.canaryPtr = (*uintptr)(stack)
|
|
*s.canaryPtr = stackCanary
|
|
|
|
// Calculate stack base addresses.
|
|
s.asyncifysp = unsafe.Add(stack, unsafe.Sizeof(uintptr(0)))
|
|
s.csp = unsafe.Add(stack, stackSize)
|
|
}
|
|
|
|
// currentTask is the current running task, or nil if currently in the scheduler.
|
|
var currentTask *Task
|
|
|
|
// Current returns the current active task.
|
|
func Current() *Task {
|
|
return currentTask
|
|
}
|
|
|
|
// Pause suspends the current task and returns to the scheduler.
|
|
// This function may only be called when running on a goroutine stack, not when running on the system stack.
|
|
func Pause() {
|
|
if *currentTask.state.canaryPtr != stackCanary {
|
|
runtimePanic("stack overflow")
|
|
}
|
|
|
|
currentTask.state.unwind()
|
|
}
|
|
|
|
//export tinygo_unwind
|
|
func (*stackState) unwind()
|
|
|
|
// Resume the task until it pauses or completes.
|
|
// This may only be called from the scheduler.
|
|
func (t *Task) Resume() {
|
|
// The current task must be saved and restored because this can nest on WASM with JS.
|
|
prevTask := currentTask
|
|
t.gcData.swap()
|
|
currentTask = t
|
|
if !t.state.launched {
|
|
t.state.launch()
|
|
t.state.launched = true
|
|
} else {
|
|
t.state.rewind()
|
|
}
|
|
currentTask = prevTask
|
|
t.gcData.swap()
|
|
if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) {
|
|
runtimePanic("stack overflow")
|
|
}
|
|
}
|
|
|
|
//export tinygo_rewind
|
|
func (*state) rewind()
|
|
|
|
// OnSystemStack returns whether the caller is running on the system stack.
|
|
func OnSystemStack() bool {
|
|
// If there is not an active goroutine, then this must be running on the system stack.
|
|
return Current() == nil
|
|
}
|