WIP cores scheduler

This commit is contained in:
Ayke van Laethem
2025-01-13 17:31:34 +01:00
parent cce44b5ebb
commit 04242fd620
28 changed files with 737 additions and 74 deletions
+2 -2
View File
@@ -490,9 +490,9 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
continue
}
if section.Type == elf.SHT_NOBITS {
if section.Name == ".stack" {
if section.Name == ".stack" || section.Name == ".stack1" {
// TinyGo emits stack sections on microcontroller using the
// ".stack" name.
// ".stack" (or ".stack1") name.
// This is a bit ugly, but I don't think there is a way to
// mark the stack section in a linker script.
sections = append(sections, memorySection{
+5
View File
@@ -99,6 +99,11 @@ func (c *Config) BuildTags() []string {
"math_big_pure_go", // to get math/big to work
"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
"serial." + c.Serial()}...) // used inside the machine package
switch c.Scheduler() {
case "threads", "cores":
default:
tags = append(tags, "tinygo.unicore")
}
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var (
validBuildModeOptions = []string{"default", "c-shared"}
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads"}
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"}
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
validPrintSizeOptions = []string{"none", "short", "full", "html"}
validPanicStrategyOptions = []string{"print", "trap"}
+1
View File
@@ -501,6 +501,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
}
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_cores.c")
}
// Configure the emulator.
+2
View File
@@ -208,6 +208,8 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// > circumstances, and should not be exposed to source languages.
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
}
case "tinygo_exitTask", "tinygo_schedulerUnlock":
llvmutil.AppendToGlobal(c.mod, "llvm.used", llvmFn)
}
// External/exported functions may not retain pointer values.
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore
package task
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build scheduler.threads
//go:build !tinygo.unicore
package task
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore
package task
+64
View File
@@ -0,0 +1,64 @@
//go:build scheduler.cores
package task
import "runtime/interrupt"
// A futex is a way for userspace to wait with the pointer as the key, and for
// another thread to wake one or all waiting threads keyed on the same pointer.
//
// A futex does not change the underlying value, it only reads it before to prevent
// lost wake-ups.
type Futex struct {
Uint32
waiters Stack
}
// Atomically check for cmp to still be equal to the futex value and if so, go
// to sleep. Return true if we were definitely awoken by a call to Wake or
// WakeAll, and false if we can't be sure of that.
func (f *Futex) Wait(cmp uint32) (awoken bool) {
mask := futexLock()
if f.Uint32.Load() != cmp {
futexUnlock(mask)
return false
}
// Push the current goroutine onto the waiter stack.
f.waiters.Push(Current())
futexUnlock(mask)
// Pause until this task is awoken by Wake/WakeAll.
Pause()
// We were awoken by a call to Wake or WakeAll. There is no chance for
// spurious wakeups.
return true
}
// Wake a single waiter.
func (f *Futex) Wake() {
mask := futexLock()
if t := f.waiters.Pop(); t != nil {
scheduleTask(t)
}
futexUnlock(mask)
}
// Wake all waiters.
func (f *Futex) WakeAll() {
mask := futexLock()
for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() {
scheduleTask(t)
}
futexUnlock(mask)
}
//go:linkname futexLock runtime.futexLock
func futexLock() interrupt.State
//go:linkname futexUnlock runtime.futexUnlock
func futexUnlock(interrupt.State)
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore
package task
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build scheduler.threads
//go:build !tinygo.unicore
package task
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore
package task
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build scheduler.threads
//go:build !tinygo.unicore
package task
+8
View File
@@ -53,3 +53,11 @@ func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
//go:linkname scheduleTask runtime.scheduleTask
func scheduleTask(*Task)
//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)))
-8
View File
@@ -6,14 +6,6 @@ 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 {
+50
View File
@@ -0,0 +1,50 @@
//go:build scheduler.cores
#include <inttypes.h>
__attribute__((naked))
void tinygo_cores_startTask(void) {
asm volatile(
"bl tinygo_schedulerUnlock\n\t"
"ldr r0, =tinygo_exitTask\n\t"
"mov lr, r0\n\t"
"pop {r0, pc}\n\t"
);
}
void tinygo_switchTask(uintptr_t *oldStack, uintptr_t newStack) {
#if defined(__thumb__)
register uintptr_t *oldStackReg asm("r0");
oldStackReg = oldStack;
register uintptr_t newStackReg asm("r1");
newStackReg = newStack;
asm volatile(
// Push PC to switch back to.
// Note: adding 1 to set the Thumb bit.
"ldr r2, =1f+1\n\t"
"push {r2}\n\t"
// Save stack pointer in oldStack for the switch back.
"mov r2, sp\n\t"
"str r2, [%[oldStack]]\n\t"
// Switch to the new stack.
"mov sp, %[newStack]\n\t"
// Return into the new stack.
"pop {pc}\n\t"
// address where we should resume
"1:"
: [oldStack]"+r"(oldStackReg),
[newStack]"+r"(newStackReg)
:
: "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "lr", "cc", "memory"
);
#else
#error unknown architecture
#endif
}
+266
View File
@@ -0,0 +1,266 @@
//go:build scheduler.cores
package task
import (
"runtime/interrupt"
"unsafe"
)
import "C" // dummy import, to make sure task_cores.c is included in the build
type runState uint8
const (
runStateRunning runState = iota
runStateResuming
runStatePaused
)
type state struct {
// Which state the task is currently in.
// The state is protected by the scheduler lock, and must only be
// read/modified with that lock held.
runState runState
// The stack pointer while the task is switched away.
sp unsafe.Pointer
// 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
}
var (
runQueue *Task
sleepQueue *Task
)
//go:linkname runtimeCurrentTask runtime.currentTask
func runtimeCurrentTask() *Task
// Current returns the current task, or nil if we're in the scheduler.
func Current() *Task {
return runtimeCurrentTask()
}
func Init(mainTask *Task, canaryPtr *uintptr) {
// The topmost word of the default stack is used as a stack canary.
*canaryPtr = stackCanary
mainTask.state.canaryPtr = canaryPtr
}
func Pause() {
// Check whether the canary (the lowest address of the stack) is still
// valid. If it is not, a stack overflow has occurred.
if *Current().state.canaryPtr != stackCanary {
runtimePanic("goroutine stack overflow")
}
if interrupt.In() {
runtimePanic("blocked inside interrupt")
}
// Note: Pause() must be called with the scheduler lock locked!
schedulerLock()
pauseLocked()
schedulerUnlock()
}
var schedulerIsRunning bool
func pauseLocked() {
t := Current()
for {
if t.state.runState == runStateResuming {
t.state.runState = runStateRunning
return
}
// Make sure only one core is calling sleepTicks etc.
if schedulerIsRunning {
schedulerUnlock()
waitForEvents()
schedulerLock()
continue
}
if runnable := runQueue; runnable != nil {
// Resume it now.
runQueue = runQueue.Next
runnable.Next = nil
if t == runnable {
// We're actually the task that's supposed to be resumed, so we
// are ready!
} else {
// It's not us that's ready, so switch to this other task.
setCurrentTask(runnable)
t.state.runState = runStatePaused
// Switch away!
switchTask(&t.state.sp, runnable.state.sp)
// We got back from the switch, so another task resumed us.
t.state.runState = runStateRunning
}
return
}
// Check whether there's a sleeping task that is ready to run.
if sleepingTask := sleepQueue; sleepingTask != nil {
now := runtimeTicks()
if now >= sleepingTask.Data {
// This task is done sleeping.
// Resume it now.
sleepQueue = sleepQueue.Next
sleepingTask.Next = nil
if t == sleepingTask {
// We're actually the task that's sleeping, so we are ready!
} else {
// It's not us that's ready, so switch to this other task.
setCurrentTask(sleepingTask)
t.state.runState = runStatePaused
// Switch away!
switchTask(&t.state.sp, sleepingTask.state.sp)
// We got back from the switch, so another task resumed us.
t.state.runState = runStateRunning
}
return
} else {
// Sleep for a bit until the next task is ready to run.
schedulerIsRunning = true
schedulerUnlock()
delay := sleepingTask.Data - now
runtimeSleepTicks(delay)
schedulerLock()
schedulerIsRunning = false
continue
}
}
}
}
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
stack := runtime_alloc(stackSize, nil)
stackTop := unsafe.Add(stack, stackSize-16)
topRegs := unsafe.Slice((*uintptr)(stackTop), 4)
topRegs[0] = uintptr(unsafe.Pointer(&startTask))
topRegs[1] = uintptr(args)
topRegs[2] = fn
t.state.sp = stackTop
canaryPtr := (*uintptr)(stack)
*canaryPtr = stackCanary
t.state.canaryPtr = canaryPtr
schedulerLock()
addToRunqueue(t)
schedulerUnlock()
}
func GCScan() {
panic("todo: task.GCScan")
}
func StackTop() uintptr {
println("todo: task.StackTop")
for {
}
}
func Sleep(wakeup uint64) {
schedulerLock()
addSleepTask(Current(), wakeup)
pauseLocked()
schedulerUnlock()
}
func Resume(t *Task) {
schedulerLock()
switch t.state.runState {
case runStatePaused:
// Paused, state is saved on the stack.
addToRunqueue(t)
case runStateRunning:
// Going to pause soon, so let the Pause() function know it can resume
// immediately.
t.state.runState = runStateResuming
default:
println("unknown run state??")
for {
}
}
schedulerUnlock()
}
// May only be called with the scheduler lock held!
func addToRunqueue(t *Task) {
t.Next = runQueue
runQueue = t
}
func addSleepTask(t *Task, wakeup uint64) {
// Save the timestamp when the task should be woken up.
t.Data = wakeup
// Find the position where we should insert this task in the queue.
q := &sleepQueue
for {
if *q == nil {
// Found the end of the time queue. Insert it here, at the end.
break
}
if (*q).Data > t.Data {
// Found a task in the queue that has a timeout before the
// to-be-sleeping task. Insert our task right before.
break
}
q = &(*q).Next
}
// Insert the task into the queue (this could be at the end, if *q is nil).
t.Next = *q
*q = t
}
//go:linkname schedulerLock runtime.schedulerLock
func schedulerLock()
//go:linkname schedulerUnlock runtime.schedulerUnlock
func schedulerUnlock()
//go:linkname runtimeTicks runtime.runtimeTicks
func runtimeTicks() uint64
//go:linkname runtimeSleepTicks runtime.runtimeSleepTicks
func runtimeSleepTicks(duration uint64)
// 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_cores_startTask
var startTask [0]uint8
//export tinygo_exitTask
func exitTask() {
Pause()
}
//export tinygo_schedulerUnlock
func tinygo_schedulerUnlock() {
schedulerUnlock()
}
//export tinygo_switchTask
func switchTask(oldStack *unsafe.Pointer, newStack unsafe.Pointer)
//go:linkname waitForEvents runtime.waitForEvents
func waitForEvents()
//go:linkname setCurrentTask runtime.setCurrentTask
func setCurrentTask(task *Task)
-3
View File
@@ -7,9 +7,6 @@ import "unsafe"
// There is only one goroutine so the task struct can be a global.
var mainTask Task
//go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(str string)
func Pause() {
runtimePanic("scheduler is disabled")
}
-8
View File
@@ -7,14 +7,6 @@ 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 {
-3
View File
@@ -244,9 +244,6 @@ func StackTop() uintptr {
return Current().state.stackTop
}
//go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(msg string)
// Using //go:linkname instead of //export so that we don't tell the compiler
// that the 't' parameter won't escape (because it will).
//
+30 -31
View File
@@ -6,7 +6,6 @@
package runtime
import (
"runtime/interrupt"
_ "unsafe"
)
@@ -23,27 +22,27 @@ import (
func __atomic_load_2(ptr *uint16, ordering uintptr) uint16 {
// The LLVM docs for this say that there is a val argument after the pointer.
// That is a typo, and the GCC docs omit it.
mask := interrupt.Disable()
mask := atomicLock()
val := *ptr
interrupt.Restore(mask)
atomicUnlock(mask)
return val
}
//export __atomic_store_2
func __atomic_store_2(ptr *uint16, val uint16, ordering uintptr) {
mask := interrupt.Disable()
mask := atomicLock()
*ptr = val
interrupt.Restore(mask)
atomicUnlock(mask)
}
//go:inline
func doAtomicCAS16(ptr *uint16, expected, desired uint16) uint16 {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
if old == expected {
*ptr = desired
}
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -61,10 +60,10 @@ func __atomic_compare_exchange_2(ptr, expected *uint16, desired uint16, successO
//go:inline
func doAtomicSwap16(ptr *uint16, new uint16) uint16 {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -80,11 +79,11 @@ func __atomic_exchange_2(ptr *uint16, new uint16, ordering uintptr) uint16 {
//go:inline
func doAtomicAdd16(ptr *uint16, value uint16) (old, new uint16) {
mask := interrupt.Disable()
mask := atomicLock()
old = *ptr
new = old + value
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old, new
}
@@ -112,27 +111,27 @@ func __atomic_add_fetch_2(ptr *uint16, value uint16, ordering uintptr) uint16 {
func __atomic_load_4(ptr *uint32, ordering uintptr) uint32 {
// The LLVM docs for this say that there is a val argument after the pointer.
// That is a typo, and the GCC docs omit it.
mask := interrupt.Disable()
mask := atomicLock()
val := *ptr
interrupt.Restore(mask)
atomicUnlock(mask)
return val
}
//export __atomic_store_4
func __atomic_store_4(ptr *uint32, val uint32, ordering uintptr) {
mask := interrupt.Disable()
mask := atomicLock()
*ptr = val
interrupt.Restore(mask)
atomicUnlock(mask)
}
//go:inline
func doAtomicCAS32(ptr *uint32, expected, desired uint32) uint32 {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
if old == expected {
*ptr = desired
}
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -150,10 +149,10 @@ func __atomic_compare_exchange_4(ptr, expected *uint32, desired uint32, successO
//go:inline
func doAtomicSwap32(ptr *uint32, new uint32) uint32 {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -169,11 +168,11 @@ func __atomic_exchange_4(ptr *uint32, new uint32, ordering uintptr) uint32 {
//go:inline
func doAtomicAdd32(ptr *uint32, value uint32) (old, new uint32) {
mask := interrupt.Disable()
mask := atomicLock()
old = *ptr
new = old + value
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old, new
}
@@ -201,27 +200,27 @@ func __atomic_add_fetch_4(ptr *uint32, value uint32, ordering uintptr) uint32 {
func __atomic_load_8(ptr *uint64, ordering uintptr) uint64 {
// The LLVM docs for this say that there is a val argument after the pointer.
// That is a typo, and the GCC docs omit it.
mask := interrupt.Disable()
mask := atomicLock()
val := *ptr
interrupt.Restore(mask)
atomicUnlock(mask)
return val
}
//export __atomic_store_8
func __atomic_store_8(ptr *uint64, val uint64, ordering uintptr) {
mask := interrupt.Disable()
mask := atomicLock()
*ptr = val
interrupt.Restore(mask)
atomicUnlock(mask)
}
//go:inline
func doAtomicCAS64(ptr *uint64, expected, desired uint64) uint64 {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
if old == expected {
*ptr = desired
}
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -239,10 +238,10 @@ func __atomic_compare_exchange_8(ptr, expected *uint64, desired uint64, successO
//go:inline
func doAtomicSwap64(ptr *uint64, new uint64) uint64 {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -258,11 +257,11 @@ func __atomic_exchange_8(ptr *uint64, new uint64, ordering uintptr) uint64 {
//go:inline
func doAtomicAdd64(ptr *uint64, value uint64) (old, new uint64) {
mask := interrupt.Disable()
mask := atomicLock()
old = *ptr
new = old + value
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old, new
}
+13
View File
@@ -0,0 +1,13 @@
//go:build baremetal && !tinygo.unicore
package runtime
import "runtime/interrupt"
func atomicLock() interrupt.State {
return atomicLockImpl()
}
func atomicUnlock(mask interrupt.State) {
atomicUnlockImpl(mask)
}
+13
View File
@@ -0,0 +1,13 @@
//go:build baremetal && tinygo.unicore
package runtime
import "runtime/interrupt"
func atomicLock() interrupt.State {
return interrupt.Disable()
}
func atomicUnlock(mask interrupt.State) {
interrupt.Restore(mask)
}
+165
View File
@@ -4,8 +4,12 @@ package runtime
import (
"device/arm"
"device/rp"
"machine"
"machine/usb/cdc"
"reflect"
"runtime/interrupt"
"unsafe"
)
// machineTicks is provided by package machine.
@@ -16,6 +20,8 @@ func machineLightSleep(uint64)
type timeUnit int64
const numCPU = 2
// ticks returns the number of ticks (microseconds) elapsed since power up.
func ticks() timeUnit {
t := machineTicks()
@@ -50,14 +56,18 @@ func waitForEvents() {
}
func putchar(c byte) {
mask := serialLock()
machine.Serial.WriteByte(c)
serialUnlock(mask)
}
func getchar() byte {
mask := serialLock()
for machine.Serial.Buffered() == 0 {
Gosched()
}
v, _ := machine.Serial.ReadByte()
serialUnlock(mask)
return v
}
@@ -71,9 +81,11 @@ func machineInit()
func init() {
machineInit()
mask := serialLock()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial()
serialUnlock(mask)
}
//export Reset_Handler
@@ -82,3 +94,156 @@ func main() {
run()
exit(0)
}
func multicore_fifo_rvalid() bool {
return rp.SIO.FIFO_ST.Get()&rp.SIO_FIFO_ST_VLD != 0
}
func multicore_fifo_wready() bool {
return rp.SIO.FIFO_ST.Get()&rp.SIO_FIFO_ST_RDY != 0
}
func multicore_fifo_drain() {
for multicore_fifo_rvalid() {
rp.SIO.FIFO_RD.Get()
}
}
func multicore_fifo_push_blocking(data uint32) {
for !multicore_fifo_wready() {
}
rp.SIO.FIFO_WR.Set(data)
arm.Asm("sev")
}
func multicore_fifo_pop_blocking() uint32 {
for !multicore_fifo_rvalid() {
arm.Asm("wfe")
}
return rp.SIO.FIFO_RD.Get()
}
//go:extern __isr_vector
var __isr_vector [0]uint32
//go:extern _stack1_top
var _stack1_top [0]uint32
var core1StartSequence = [...]uint32{
0, 0, 1,
uint32(uintptr(unsafe.Pointer(&__isr_vector))),
uint32(uintptr(unsafe.Pointer(&_stack1_top))),
uint32(uintptr(reflect.ValueOf(runCore1).Pointer())),
}
func startOtherCores() {
// Start the second core of the RP2040.
// See section 2.8.2 in the datasheet.
seq := 0
for {
cmd := core1StartSequence[seq]
if cmd == 0 {
multicore_fifo_drain()
arm.Asm("sev")
}
multicore_fifo_push_blocking(cmd)
response := multicore_fifo_pop_blocking()
if cmd != response {
seq = 0
continue
}
seq = seq + 1
if seq >= len(core1StartSequence) {
break
}
}
}
func runCore1() {
// Just blink a LED to show that this core is running.
// TODO: use a real scheduler.
led := machine.GP0
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
const cycles = 7000_000
for {
for i := 0; i < cycles; i++ {
led.Low()
}
for i := 0; i < cycles; i++ {
led.High()
}
}
}
func currentCPU() uint32 {
return rp.SIO.CPUID.Get()
}
const (
spinlockAtomic = iota
spinlockFutex
spinlockScheduler
)
func atomicLockImpl() interrupt.State {
mask := interrupt.Disable()
for rp.SIO.SPINLOCK0.Get() == 0 {
}
return mask
}
func atomicUnlockImpl(mask interrupt.State) {
rp.SIO.SPINLOCK0.Set(0)
interrupt.Restore(mask)
}
func futexLock() interrupt.State {
// Disable interrupts.
// This is necessary since we might do some futex operations (like Wake)
// inside an interrupt and we don't want to deadlock with a non-interrupt
// goroutine that has taken the spinlock at the same time.
mask := interrupt.Disable()
// Acquire the spinlock.
for rp.SIO.SPINLOCK1.Get() == 0 {
// Spin, until the lock is released.
}
return mask
}
func futexUnlock(mask interrupt.State) {
// Release the spinlock.
rp.SIO.SPINLOCK1.Set(0)
// Restore interrupts.
interrupt.Restore(mask)
}
var schedulerLockMasks [numCPU]interrupt.State
// WARNING: doesn't check for deadlocks!
func schedulerLock() {
//schedulerLockMasks[currentCPU()] = interrupt.Disable()
for rp.SIO.SPINLOCK2.Get() == 0 {
}
}
func schedulerUnlock() {
rp.SIO.SPINLOCK2.Set(0)
//interrupt.Restore(schedulerLockMasks[currentCPU()])
}
func serialLock() interrupt.State {
mask := interrupt.Disable()
for rp.SIO.SPINLOCK3.Get() == 0 {
}
return mask
}
func serialUnlock(mask interrupt.State) {
rp.SIO.SPINLOCK3.Set(0)
interrupt.Restore(mask)
}
+91
View File
@@ -0,0 +1,91 @@
//go:build scheduler.cores
package runtime
import (
"internal/task"
"unsafe"
)
const hasScheduler = true
const hasParallelism = true
var (
mainTask task.Task
cpuTasks [numCPU]*task.Task
)
func deadlock() {
// Call yield without requesting a wakeup.
task.Pause()
trap()
}
func scheduleTask(t *task.Task) {
task.Resume(t)
}
func Gosched() {
// TODO
}
// NumCPU returns the number of logical CPUs usable by the current process.
func NumCPU() int {
// Return the hardcoded number of physical CPU cores.
return numCPU
}
func addTimer(tn *timerNode) {
runtimePanic("todo: timers")
}
func removeTimer(t *timer) bool {
runtimePanic("todo: timers")
return false
}
func schedulerRunQueue() *task.Queue {
println("todo: schedulerRunQueue")
for {
}
return nil
}
// Pause the current task for a given time.
//
//go:linkname sleep time.Sleep
func sleep(duration int64) {
if duration <= 0 {
return
}
wakeup := ticks() + nanosecondsToTicks(duration)
task.Sleep(uint64(wakeup))
}
func run() {
initHeap()
cpuTasks[0] = &mainTask
task.Init(&mainTask, (*uintptr)(unsafe.Pointer(&stackTopSymbol)))
initAll()
startOtherCores()
callMain()
mainExited = true
}
func currentTask() *task.Task {
return cpuTasks[currentCPU()]
}
func setCurrentTask(task *task.Task) {
cpuTasks[currentCPU()] = task
}
func runtimeTicks() uint64 {
return uint64(ticks())
}
func runtimeSleepTicks(delay uint64) {
sleepTicks(timeUnit(delay))
}
+9
View File
@@ -32,6 +32,15 @@ SECTIONS
_stack_top = .;
} >RAM
/* Stack for second core (core 1), if there is one. This memory area won't be
* reserved if there is no second core. */
.stack1 (NOLOAD) :
{
. = ALIGN(4);
. += _stack_size;
_stack1_top = .;
} >RAM
/* Start address (in flash) of .data, used by startup code. */
_sidata = LOADADDR(.data);
@@ -26,7 +26,6 @@ package runtime
import (
_ "unsafe"
"runtime/interrupt"
)
// Documentation:
@@ -41,29 +40,29 @@ import (
func __atomic_load_{{.}}(ptr *uint{{$bits}}, ordering uintptr) uint{{$bits}} {
// The LLVM docs for this say that there is a val argument after the pointer.
// That is a typo, and the GCC docs omit it.
mask := interrupt.Disable()
mask := atomicLock()
val := *ptr
interrupt.Restore(mask)
atomicUnlock(mask)
return val
}
{{end}}
{{- define "store"}}{{$bits := mul . 8 -}}
//export __atomic_store_{{.}}
func __atomic_store_{{.}}(ptr *uint{{$bits}}, val uint{{$bits}}, ordering uintptr) {
mask := interrupt.Disable()
mask := atomicLock()
*ptr = val
interrupt.Restore(mask)
atomicUnlock(mask)
}
{{end}}
{{- define "cas"}}{{$bits := mul . 8 -}}
//go:inline
func doAtomicCAS{{$bits}}(ptr *uint{{$bits}}, expected, desired uint{{$bits}}) uint{{$bits}} {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
if old == expected {
*ptr = desired
}
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -82,10 +81,10 @@ func __atomic_compare_exchange_{{.}}(ptr, expected *uint{{$bits}}, desired uint{
{{- define "swap"}}{{$bits := mul . 8 -}}
//go:inline
func doAtomicSwap{{$bits}}(ptr *uint{{$bits}}, new uint{{$bits}}) uint{{$bits}} {
mask := interrupt.Disable()
mask := atomicLock()
old := *ptr
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old
}
@@ -111,11 +110,11 @@ func __atomic_exchange_{{.}}(ptr *uint{{$bits}}, new uint{{$bits}}, ordering uin
//go:inline
func {{$opfn}}(ptr *{{$type}}, value {{$type}}) (old, new {{$type}}) {
mask := interrupt.Disable()
mask := atomicLock()
old = *ptr
{{$opdef}}
*ptr = new
interrupt.Restore(mask)
atomicUnlock(mask)
return old, new
}