mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 02:27:48 +00:00
60f8a62978
This commit adds support for a scheduler that runs a scheduler on all available cores. It is meant to be used on baremetal systems with a fixed number of cores, such as the RP2040. The initial implementation adds support for multicore scheduling to the riscv-qemu target as a convenient testing target. This means that this new multicore scheduler is tested in CI, including a bunch of standard library tests (`make tinygo-test-baremetal`). This should ensure the new scheduler is reasonably well tested before trying to use it on harder-to-debug targets like the RP2040.
85 lines
3.2 KiB
Go
85 lines
3.2 KiB
Go
//go:build scheduler.tasks || scheduler.cores
|
|
|
|
package task
|
|
|
|
import (
|
|
"unsafe"
|
|
)
|
|
|
|
//go:linkname runtimePanic runtime.runtimePanic
|
|
func runtimePanic(str string)
|
|
|
|
// 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)))
|
|
|
|
// state is a structure which holds a reference to the state of the task.
|
|
// When the task is suspended, the registers are stored onto the stack and the stack pointer is stored into sp.
|
|
type state struct {
|
|
// sp is the stack pointer of the saved state.
|
|
// When the task is inactive, the saved registers are stored at the top of the stack.
|
|
// Note: this should ideally be a unsafe.Pointer for the precise GC. The GC
|
|
// will find the stack through canaryPtr though so it's not currently a
|
|
// problem to store this value as uintptr.
|
|
sp uintptr
|
|
|
|
// canaryPtr points to the top word of the stack (the lowest address).
|
|
// This is used to detect stack overflows.
|
|
// When initializing the goroutine, the stackCanary constant is stored there.
|
|
// If the stack overflowed, the word will likely no longer equal stackCanary.
|
|
canaryPtr *uintptr
|
|
}
|
|
|
|
//export tinygo_task_exit
|
|
func taskExit() {
|
|
// TODO: explicitly free the stack after switching back to the scheduler.
|
|
Pause()
|
|
}
|
|
|
|
// 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) {
|
|
// 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
|
|
|
|
// Get a pointer to the top of the stack, where the initial register values
|
|
// are stored. They will be popped off the stack on the first stack switch
|
|
// to the goroutine, and will start running tinygo_startTask (this setup
|
|
// happens in archInit).
|
|
r := (*calleeSavedRegs)(unsafe.Add(stack, stackSize-unsafe.Sizeof(calleeSavedRegs{})))
|
|
|
|
// Invoke architecture-specific initialization.
|
|
s.archInit(r, fn, args)
|
|
}
|
|
|
|
//export tinygo_swapTask
|
|
func swapTask(oldStack uintptr, newStack *uintptr)
|
|
|
|
// startTask is a small wrapper function that sets up the first (and only)
|
|
// argument to the new goroutine and makes sure it is exited when the goroutine
|
|
// finishes.
|
|
//
|
|
//go:extern tinygo_startTask
|
|
var startTask [0]uint8
|
|
|
|
// start creates and starts a new goroutine with the given function and arguments.
|
|
// The new goroutine is scheduled to run later.
|
|
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
|
t := &Task{}
|
|
t.state.initialize(fn, args, stackSize)
|
|
scheduleTask(t)
|
|
}
|
|
|
|
// 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
|
|
}
|