mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 19:17:47 +00:00
WIP cores scheduler
This commit is contained in:
+2
-2
@@ -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{
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
//go:build !scheduler.threads
|
||||
//go:build tinygo.unicore
|
||||
|
||||
package task
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build scheduler.threads
|
||||
//go:build !tinygo.unicore
|
||||
|
||||
package task
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !scheduler.threads
|
||||
//go:build tinygo.unicore
|
||||
|
||||
package task
|
||||
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
//go:build !scheduler.threads
|
||||
//go:build tinygo.unicore
|
||||
|
||||
package task
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build scheduler.threads
|
||||
//go:build !tinygo.unicore
|
||||
|
||||
package task
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !scheduler.threads
|
||||
//go:build tinygo.unicore
|
||||
|
||||
package task
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build scheduler.threads
|
||||
//go:build !tinygo.unicore
|
||||
|
||||
package task
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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).
|
||||
//
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user