mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-17 03:03:27 +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
|
continue
|
||||||
}
|
}
|
||||||
if section.Type == elf.SHT_NOBITS {
|
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
|
// 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
|
// This is a bit ugly, but I don't think there is a way to
|
||||||
// mark the stack section in a linker script.
|
// mark the stack section in a linker script.
|
||||||
sections = append(sections, memorySection{
|
sections = append(sections, memorySection{
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ func (c *Config) BuildTags() []string {
|
|||||||
"math_big_pure_go", // to get math/big to work
|
"math_big_pure_go", // to get math/big to work
|
||||||
"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
|
"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
|
||||||
"serial." + c.Serial()}...) // used inside the machine 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++ {
|
for i := 1; i <= c.GoMinorVersion; i++ {
|
||||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
validBuildModeOptions = []string{"default", "c-shared"}
|
validBuildModeOptions = []string{"default", "c-shared"}
|
||||||
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
|
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"}
|
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
|
||||||
validPrintSizeOptions = []string{"none", "short", "full", "html"}
|
validPrintSizeOptions = []string{"none", "short", "full", "html"}
|
||||||
validPanicStrategyOptions = []string{"print", "trap"}
|
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/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_stack_"+asmGoarch+suffix+".S")
|
||||||
|
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_cores.c")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the emulator.
|
// 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.
|
// > circumstances, and should not be exposed to source languages.
|
||||||
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
|
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.
|
// External/exported functions may not retain pointer values.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !scheduler.threads
|
//go:build tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build scheduler.threads
|
//go:build !tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !scheduler.threads
|
//go:build tinygo.unicore
|
||||||
|
|
||||||
package task
|
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
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build scheduler.threads
|
//go:build !tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !scheduler.threads
|
//go:build tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build scheduler.threads
|
//go:build !tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -53,3 +53,11 @@ func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
|
|||||||
|
|
||||||
//go:linkname scheduleTask runtime.scheduleTask
|
//go:linkname scheduleTask runtime.scheduleTask
|
||||||
func scheduleTask(*Task)
|
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"
|
"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.
|
// 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.
|
// When the task is suspended, the stack pointers are saved here.
|
||||||
type state struct {
|
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.
|
// There is only one goroutine so the task struct can be a global.
|
||||||
var mainTask Task
|
var mainTask Task
|
||||||
|
|
||||||
//go:linkname runtimePanic runtime.runtimePanic
|
|
||||||
func runtimePanic(str string)
|
|
||||||
|
|
||||||
func Pause() {
|
func Pause() {
|
||||||
runtimePanic("scheduler is disabled")
|
runtimePanic("scheduler is disabled")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ import (
|
|||||||
"unsafe"
|
"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.
|
// 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.
|
// When the task is suspended, the registers are stored onto the stack and the stack pointer is stored into sp.
|
||||||
type state struct {
|
type state struct {
|
||||||
|
|||||||
@@ -244,9 +244,6 @@ func StackTop() uintptr {
|
|||||||
return Current().state.stackTop
|
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
|
// Using //go:linkname instead of //export so that we don't tell the compiler
|
||||||
// that the 't' parameter won't escape (because it will).
|
// that the 't' parameter won't escape (because it will).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime/interrupt"
|
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,27 +22,27 @@ import (
|
|||||||
func __atomic_load_2(ptr *uint16, ordering uintptr) uint16 {
|
func __atomic_load_2(ptr *uint16, ordering uintptr) uint16 {
|
||||||
// The LLVM docs for this say that there is a val argument after the pointer.
|
// 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.
|
// That is a typo, and the GCC docs omit it.
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
val := *ptr
|
val := *ptr
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
//export __atomic_store_2
|
//export __atomic_store_2
|
||||||
func __atomic_store_2(ptr *uint16, val uint16, ordering uintptr) {
|
func __atomic_store_2(ptr *uint16, val uint16, ordering uintptr) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
*ptr = val
|
*ptr = val
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicCAS16(ptr *uint16, expected, desired uint16) uint16 {
|
func doAtomicCAS16(ptr *uint16, expected, desired uint16) uint16 {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
if old == expected {
|
if old == expected {
|
||||||
*ptr = desired
|
*ptr = desired
|
||||||
}
|
}
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +60,10 @@ func __atomic_compare_exchange_2(ptr, expected *uint16, desired uint16, successO
|
|||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicSwap16(ptr *uint16, new uint16) uint16 {
|
func doAtomicSwap16(ptr *uint16, new uint16) uint16 {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,11 +79,11 @@ func __atomic_exchange_2(ptr *uint16, new uint16, ordering uintptr) uint16 {
|
|||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicAdd16(ptr *uint16, value uint16) (old, new uint16) {
|
func doAtomicAdd16(ptr *uint16, value uint16) (old, new uint16) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old = *ptr
|
old = *ptr
|
||||||
new = old + value
|
new = old + value
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old, new
|
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 {
|
func __atomic_load_4(ptr *uint32, ordering uintptr) uint32 {
|
||||||
// The LLVM docs for this say that there is a val argument after the pointer.
|
// 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.
|
// That is a typo, and the GCC docs omit it.
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
val := *ptr
|
val := *ptr
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
//export __atomic_store_4
|
//export __atomic_store_4
|
||||||
func __atomic_store_4(ptr *uint32, val uint32, ordering uintptr) {
|
func __atomic_store_4(ptr *uint32, val uint32, ordering uintptr) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
*ptr = val
|
*ptr = val
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicCAS32(ptr *uint32, expected, desired uint32) uint32 {
|
func doAtomicCAS32(ptr *uint32, expected, desired uint32) uint32 {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
if old == expected {
|
if old == expected {
|
||||||
*ptr = desired
|
*ptr = desired
|
||||||
}
|
}
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,10 +149,10 @@ func __atomic_compare_exchange_4(ptr, expected *uint32, desired uint32, successO
|
|||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicSwap32(ptr *uint32, new uint32) uint32 {
|
func doAtomicSwap32(ptr *uint32, new uint32) uint32 {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,11 +168,11 @@ func __atomic_exchange_4(ptr *uint32, new uint32, ordering uintptr) uint32 {
|
|||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicAdd32(ptr *uint32, value uint32) (old, new uint32) {
|
func doAtomicAdd32(ptr *uint32, value uint32) (old, new uint32) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old = *ptr
|
old = *ptr
|
||||||
new = old + value
|
new = old + value
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old, new
|
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 {
|
func __atomic_load_8(ptr *uint64, ordering uintptr) uint64 {
|
||||||
// The LLVM docs for this say that there is a val argument after the pointer.
|
// 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.
|
// That is a typo, and the GCC docs omit it.
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
val := *ptr
|
val := *ptr
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
//export __atomic_store_8
|
//export __atomic_store_8
|
||||||
func __atomic_store_8(ptr *uint64, val uint64, ordering uintptr) {
|
func __atomic_store_8(ptr *uint64, val uint64, ordering uintptr) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
*ptr = val
|
*ptr = val
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicCAS64(ptr *uint64, expected, desired uint64) uint64 {
|
func doAtomicCAS64(ptr *uint64, expected, desired uint64) uint64 {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
if old == expected {
|
if old == expected {
|
||||||
*ptr = desired
|
*ptr = desired
|
||||||
}
|
}
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,10 +238,10 @@ func __atomic_compare_exchange_8(ptr, expected *uint64, desired uint64, successO
|
|||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicSwap64(ptr *uint64, new uint64) uint64 {
|
func doAtomicSwap64(ptr *uint64, new uint64) uint64 {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,11 +257,11 @@ func __atomic_exchange_8(ptr *uint64, new uint64, ordering uintptr) uint64 {
|
|||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicAdd64(ptr *uint64, value uint64) (old, new uint64) {
|
func doAtomicAdd64(ptr *uint64, value uint64) (old, new uint64) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old = *ptr
|
old = *ptr
|
||||||
new = old + value
|
new = old + value
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old, new
|
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 (
|
import (
|
||||||
"device/arm"
|
"device/arm"
|
||||||
|
"device/rp"
|
||||||
"machine"
|
"machine"
|
||||||
"machine/usb/cdc"
|
"machine/usb/cdc"
|
||||||
|
"reflect"
|
||||||
|
"runtime/interrupt"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// machineTicks is provided by package machine.
|
// machineTicks is provided by package machine.
|
||||||
@@ -16,6 +20,8 @@ func machineLightSleep(uint64)
|
|||||||
|
|
||||||
type timeUnit int64
|
type timeUnit int64
|
||||||
|
|
||||||
|
const numCPU = 2
|
||||||
|
|
||||||
// ticks returns the number of ticks (microseconds) elapsed since power up.
|
// ticks returns the number of ticks (microseconds) elapsed since power up.
|
||||||
func ticks() timeUnit {
|
func ticks() timeUnit {
|
||||||
t := machineTicks()
|
t := machineTicks()
|
||||||
@@ -50,14 +56,18 @@ func waitForEvents() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func putchar(c byte) {
|
func putchar(c byte) {
|
||||||
|
mask := serialLock()
|
||||||
machine.Serial.WriteByte(c)
|
machine.Serial.WriteByte(c)
|
||||||
|
serialUnlock(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getchar() byte {
|
func getchar() byte {
|
||||||
|
mask := serialLock()
|
||||||
for machine.Serial.Buffered() == 0 {
|
for machine.Serial.Buffered() == 0 {
|
||||||
Gosched()
|
Gosched()
|
||||||
}
|
}
|
||||||
v, _ := machine.Serial.ReadByte()
|
v, _ := machine.Serial.ReadByte()
|
||||||
|
serialUnlock(mask)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +81,11 @@ func machineInit()
|
|||||||
func init() {
|
func init() {
|
||||||
machineInit()
|
machineInit()
|
||||||
|
|
||||||
|
mask := serialLock()
|
||||||
cdc.EnableUSBCDC()
|
cdc.EnableUSBCDC()
|
||||||
machine.USBDev.Configure(machine.UARTConfig{})
|
machine.USBDev.Configure(machine.UARTConfig{})
|
||||||
machine.InitSerial()
|
machine.InitSerial()
|
||||||
|
serialUnlock(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Reset_Handler
|
//export Reset_Handler
|
||||||
@@ -82,3 +94,156 @@ func main() {
|
|||||||
run()
|
run()
|
||||||
exit(0)
|
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 = .;
|
_stack_top = .;
|
||||||
} >RAM
|
} >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. */
|
/* Start address (in flash) of .data, used by startup code. */
|
||||||
_sidata = LOADADDR(.data);
|
_sidata = LOADADDR(.data);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ package runtime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
"runtime/interrupt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Documentation:
|
// Documentation:
|
||||||
@@ -41,29 +40,29 @@ import (
|
|||||||
func __atomic_load_{{.}}(ptr *uint{{$bits}}, ordering uintptr) uint{{$bits}} {
|
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.
|
// 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.
|
// That is a typo, and the GCC docs omit it.
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
val := *ptr
|
val := *ptr
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{- define "store"}}{{$bits := mul . 8 -}}
|
{{- define "store"}}{{$bits := mul . 8 -}}
|
||||||
//export __atomic_store_{{.}}
|
//export __atomic_store_{{.}}
|
||||||
func __atomic_store_{{.}}(ptr *uint{{$bits}}, val uint{{$bits}}, ordering uintptr) {
|
func __atomic_store_{{.}}(ptr *uint{{$bits}}, val uint{{$bits}}, ordering uintptr) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
*ptr = val
|
*ptr = val
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{- define "cas"}}{{$bits := mul . 8 -}}
|
{{- define "cas"}}{{$bits := mul . 8 -}}
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicCAS{{$bits}}(ptr *uint{{$bits}}, expected, desired uint{{$bits}}) uint{{$bits}} {
|
func doAtomicCAS{{$bits}}(ptr *uint{{$bits}}, expected, desired uint{{$bits}}) uint{{$bits}} {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
if old == expected {
|
if old == expected {
|
||||||
*ptr = desired
|
*ptr = desired
|
||||||
}
|
}
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,10 +81,10 @@ func __atomic_compare_exchange_{{.}}(ptr, expected *uint{{$bits}}, desired uint{
|
|||||||
{{- define "swap"}}{{$bits := mul . 8 -}}
|
{{- define "swap"}}{{$bits := mul . 8 -}}
|
||||||
//go:inline
|
//go:inline
|
||||||
func doAtomicSwap{{$bits}}(ptr *uint{{$bits}}, new uint{{$bits}}) uint{{$bits}} {
|
func doAtomicSwap{{$bits}}(ptr *uint{{$bits}}, new uint{{$bits}}) uint{{$bits}} {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old := *ptr
|
old := *ptr
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +110,11 @@ func __atomic_exchange_{{.}}(ptr *uint{{$bits}}, new uint{{$bits}}, ordering uin
|
|||||||
|
|
||||||
//go:inline
|
//go:inline
|
||||||
func {{$opfn}}(ptr *{{$type}}, value {{$type}}) (old, new {{$type}}) {
|
func {{$opfn}}(ptr *{{$type}}, value {{$type}}) (old, new {{$type}}) {
|
||||||
mask := interrupt.Disable()
|
mask := atomicLock()
|
||||||
old = *ptr
|
old = *ptr
|
||||||
{{$opdef}}
|
{{$opdef}}
|
||||||
*ptr = new
|
*ptr = new
|
||||||
interrupt.Restore(mask)
|
atomicUnlock(mask)
|
||||||
return old, new
|
return old, new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user