mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 11:07:46 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea2f0e444d | |||
| 04242fd620 | |||
| cce44b5ebb | |||
| 3767decf9e | |||
| 8d6e16019a | |||
| 3cc9c44c26 | |||
| c9bb33a73c | |||
| 8659b18d6f | |||
| 2599db2990 |
@@ -113,6 +113,7 @@ var libMusl = Library{
|
|||||||
librarySources: func(target string) ([]string, error) {
|
librarySources: func(target string) ([]string, error) {
|
||||||
arch := compileopts.MuslArchitecture(target)
|
arch := compileopts.MuslArchitecture(target)
|
||||||
globs := []string{
|
globs := []string{
|
||||||
|
"conf/*.c",
|
||||||
"env/*.c",
|
"env/*.c",
|
||||||
"errno/*.c",
|
"errno/*.c",
|
||||||
"exit/*.c",
|
"exit/*.c",
|
||||||
|
|||||||
+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"}
|
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"}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
func TestVerifyOptions(t *testing.T) {
|
func TestVerifyOptions(t *testing.T) {
|
||||||
|
|
||||||
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`)
|
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`)
|
||||||
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`)
|
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify, threads`)
|
||||||
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`)
|
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`)
|
||||||
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
|
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
|
||||||
|
|
||||||
|
|||||||
@@ -248,7 +248,6 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
|||||||
GOARCH: options.GOARCH,
|
GOARCH: options.GOARCH,
|
||||||
BuildTags: []string{options.GOOS, options.GOARCH},
|
BuildTags: []string{options.GOOS, options.GOARCH},
|
||||||
GC: "precise",
|
GC: "precise",
|
||||||
Scheduler: "tasks",
|
|
||||||
Linker: "cc",
|
Linker: "cc",
|
||||||
DefaultStackSize: 1024 * 64, // 64kB
|
DefaultStackSize: 1024 * 64, // 64kB
|
||||||
GDB: []string{"gdb"},
|
GDB: []string{"gdb"},
|
||||||
@@ -381,6 +380,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
|||||||
platformVersion = "11.0.0" // first macosx platform with arm64 support
|
platformVersion = "11.0.0" // first macosx platform with arm64 support
|
||||||
}
|
}
|
||||||
llvmvendor = "apple"
|
llvmvendor = "apple"
|
||||||
|
spec.Scheduler = "tasks"
|
||||||
spec.Linker = "ld.lld"
|
spec.Linker = "ld.lld"
|
||||||
spec.Libc = "darwin-libSystem"
|
spec.Libc = "darwin-libSystem"
|
||||||
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer to
|
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer to
|
||||||
@@ -398,6 +398,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
|||||||
"src/runtime/runtime_unix.c",
|
"src/runtime/runtime_unix.c",
|
||||||
"src/runtime/signal.c")
|
"src/runtime/signal.c")
|
||||||
case "linux":
|
case "linux":
|
||||||
|
spec.Scheduler = "threads"
|
||||||
spec.Linker = "ld.lld"
|
spec.Linker = "ld.lld"
|
||||||
spec.RTLib = "compiler-rt"
|
spec.RTLib = "compiler-rt"
|
||||||
spec.Libc = "musl"
|
spec.Libc = "musl"
|
||||||
@@ -418,9 +419,11 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
|||||||
}
|
}
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
spec.ExtraFiles = append(spec.ExtraFiles,
|
||||||
"src/internal/futex/futex_linux.c",
|
"src/internal/futex/futex_linux.c",
|
||||||
|
"src/internal/task/task_threads.c",
|
||||||
"src/runtime/runtime_unix.c",
|
"src/runtime/runtime_unix.c",
|
||||||
"src/runtime/signal.c")
|
"src/runtime/signal.c")
|
||||||
case "windows":
|
case "windows":
|
||||||
|
spec.Scheduler = "tasks"
|
||||||
spec.Linker = "ld.lld"
|
spec.Linker = "ld.lld"
|
||||||
spec.Libc = "mingw-w64"
|
spec.Libc = "mingw-w64"
|
||||||
// Note: using a medium code model, low image base and no ASLR
|
// Note: using a medium code model, low image base and no ASLR
|
||||||
@@ -498,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,3 +1,5 @@
|
|||||||
|
//go:build tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
// Atomics implementation for cooperative systems. The atomic types here aren't
|
// Atomics implementation for cooperative systems. The atomic types here aren't
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build !tinygo.unicore
|
||||||
|
|
||||||
|
package task
|
||||||
|
|
||||||
|
// Atomics implementation for non-cooperative systems (multithreaded, etc).
|
||||||
|
// These atomic types use real atomic instructions.
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
type (
|
||||||
|
Uintptr = atomic.Uintptr
|
||||||
|
Uint32 = atomic.Uint32
|
||||||
|
Uint64 = atomic.Uint64
|
||||||
|
)
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
// A futex is a way for userspace to wait with the pointer as the key, and for
|
// A futex is a way for userspace to wait with the pointer as the key, and for
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build scheduler.threads
|
||||||
|
|
||||||
|
package task
|
||||||
|
|
||||||
|
import "internal/futex"
|
||||||
|
|
||||||
|
type Futex = futex.Futex
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build linux && !baremetal
|
||||||
|
|
||||||
|
package task
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
// Musl uses a pointer (or unsigned long for C++) so unsafe.Pointer should be
|
||||||
|
// fine.
|
||||||
|
type threadID unsafe.Pointer
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
type Mutex struct {
|
type Mutex struct {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
//go:build !tinygo.unicore
|
||||||
|
|
||||||
|
package task
|
||||||
|
|
||||||
|
// Futex-based mutex.
|
||||||
|
// This is largely based on the paper "Futexes are Tricky" by Ulrich Drepper.
|
||||||
|
// It describes a few ways to implement mutexes using a futex, and how some
|
||||||
|
// seemingly-obvious implementations don't exactly work as intended.
|
||||||
|
// Unfortunately, Go atomic operations work slightly differently so we can't
|
||||||
|
// copy the algorithm verbatim.
|
||||||
|
//
|
||||||
|
// The implementation works like this. The futex can have 3 different values,
|
||||||
|
// depending on the state:
|
||||||
|
//
|
||||||
|
// - 0: the futex is currently unlocked.
|
||||||
|
// - 1: the futex is locked, but is uncontended. There is one special case: if
|
||||||
|
// a contended futex is unlocked, it is set to 0. It is possible for another
|
||||||
|
// thread to lock the futex before the next waiter is woken. But because a
|
||||||
|
// waiter will be woken (if there is one), it will always change to 2
|
||||||
|
// regardless. So this is not a problem.
|
||||||
|
// - 2: the futex is locked, and is contended. At least one thread is trying
|
||||||
|
// to obtain the lock (and is in the contended loop, see below).
|
||||||
|
//
|
||||||
|
// For the paper, see:
|
||||||
|
// https://dept-info.labri.fr/~denis/Enseignement/2008-IR/Articles/01-futex.pdf)
|
||||||
|
|
||||||
|
type Mutex struct {
|
||||||
|
futex Futex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mutex) Lock() {
|
||||||
|
// Fast path: try to take an uncontended lock.
|
||||||
|
if m.futex.CompareAndSwap(0, 1) {
|
||||||
|
// We obtained the mutex.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The futex is contended, so we enter the contended loop.
|
||||||
|
// If we manage to change the futex from 0 to 2, we managed to take the
|
||||||
|
// lock. Else, we have to wait until a call to Unlock unlocks this mutex.
|
||||||
|
// (Unlock will wake one waiter when it finds the futex is set to 2 when
|
||||||
|
// unlocking).
|
||||||
|
for m.futex.Swap(2) != 0 {
|
||||||
|
// Wait until we get resumed in Unlock.
|
||||||
|
m.futex.Wait(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mutex) Unlock() {
|
||||||
|
if old := m.futex.Swap(0); old == 0 {
|
||||||
|
// Mutex wasn't locked before.
|
||||||
|
panic("sync: unlock of unlocked Mutex")
|
||||||
|
} else if old == 2 {
|
||||||
|
// Mutex was a contended lock, so we need to wake the next waiter.
|
||||||
|
m.futex.Wake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryLock tries to lock m and reports whether it succeeded.
|
||||||
|
//
|
||||||
|
// Note that while correct uses of TryLock do exist, they are rare,
|
||||||
|
// and use of TryLock is often a sign of a deeper problem
|
||||||
|
// in a particular use of mutexes.
|
||||||
|
func (m *Mutex) TryLock() bool {
|
||||||
|
// Fast path: try to take an uncontended lock.
|
||||||
|
if m.futex.CompareAndSwap(0, 1) {
|
||||||
|
// We obtained the mutex.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build tinygo.unicore
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
// PMutex is a real mutex on systems that can be either preemptive or threaded,
|
// PMutex is a real mutex on systems that can be either preemptive or threaded,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build !tinygo.unicore
|
||||||
|
|
||||||
|
package task
|
||||||
|
|
||||||
|
// PMutex is a real mutex on systems that can be either preemptive or threaded,
|
||||||
|
// and a dummy lock on other (purely cooperative) systems.
|
||||||
|
//
|
||||||
|
// It is mainly useful for short operations that need a lock when threading may
|
||||||
|
// be involved, but which do not need a lock with a purely cooperative
|
||||||
|
// scheduler.
|
||||||
|
type PMutex = Mutex
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
// Barebones semaphore implementation.
|
||||||
|
// The main limitation is that if there are multiple waiters, a single Post()
|
||||||
|
// call won't do anything. Only when Post() has been called to awaken all
|
||||||
|
// waiters will the waiters proceed.
|
||||||
|
// This limitation is not a problem when there will only be a single waiter.
|
||||||
|
type Semaphore struct {
|
||||||
|
futex Futex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post (unlock) the semaphore, incrementing the value in the semaphore.
|
||||||
|
func (s *Semaphore) Post() {
|
||||||
|
newValue := s.futex.Add(1)
|
||||||
|
if newValue == 0 {
|
||||||
|
s.futex.WakeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait (lock) the semaphore, decrementing the value in the semaphore.
|
||||||
|
func (s *Semaphore) Wait() {
|
||||||
|
delta := int32(-1)
|
||||||
|
value := s.futex.Add(uint32(delta))
|
||||||
|
for {
|
||||||
|
if int32(value) >= 0 {
|
||||||
|
// Semaphore unlocked!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.futex.Wait(value)
|
||||||
|
value = s.futex.Load()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,11 +21,19 @@ type Task struct {
|
|||||||
// state is the underlying running state of the task.
|
// state is the underlying running state of the task.
|
||||||
state state
|
state state
|
||||||
|
|
||||||
|
RunState uint8
|
||||||
|
|
||||||
// DeferFrame stores a pointer to the (stack allocated) defer frame of the
|
// DeferFrame stores a pointer to the (stack allocated) defer frame of the
|
||||||
// goroutine that is used for the recover builtin.
|
// goroutine that is used for the recover builtin.
|
||||||
DeferFrame unsafe.Pointer
|
DeferFrame unsafe.Pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
RunStatePaused = iota
|
||||||
|
RunStateRunning
|
||||||
|
RunStateResuming
|
||||||
|
)
|
||||||
|
|
||||||
// DataUint32 returns the Data field as a uint32. The value is only valid after
|
// DataUint32 returns the Data field as a uint32. The value is only valid after
|
||||||
// setting it through SetDataUint32 or by storing to it using DataAtomicUint32.
|
// setting it through SetDataUint32 or by storing to it using DataAtomicUint32.
|
||||||
func (t *Task) DataUint32() uint32 {
|
func (t *Task) DataUint32() uint32 {
|
||||||
@@ -53,3 +61,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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build scheduler.tasks
|
//go:build scheduler.tasks || scheduler.cores
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -33,40 +25,54 @@ type state struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// currentTask is the current running task, or nil if currently in the scheduler.
|
// currentTask is the current running task, or nil if currently in the scheduler.
|
||||||
var currentTask *Task
|
//var currentTask *Task
|
||||||
|
|
||||||
// Current returns the current active task.
|
// Current returns the current active task.
|
||||||
func Current() *Task {
|
//func Current() *Task {
|
||||||
return currentTask
|
// return currentTask
|
||||||
|
//}
|
||||||
|
|
||||||
|
//go:linkname Current runtime.currentTask
|
||||||
|
func Current() *Task
|
||||||
|
|
||||||
|
//go:linkname schedulerLock runtime.schedulerLock
|
||||||
|
func schedulerLock()
|
||||||
|
|
||||||
|
func Pause() {
|
||||||
|
schedulerLock()
|
||||||
|
PauseLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause suspends the current task and returns to the scheduler.
|
// Pause suspends the current task and returns to the scheduler.
|
||||||
// This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt.
|
// This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt.
|
||||||
func Pause() {
|
func PauseLocked() {
|
||||||
// Check whether the canary (the lowest address of the stack) is still
|
// Check whether the canary (the lowest address of the stack) is still
|
||||||
// valid. If it is not, a stack overflow has occurred.
|
// valid. If it is not, a stack overflow has occurred.
|
||||||
if *currentTask.state.canaryPtr != stackCanary {
|
if *Current().state.canaryPtr != stackCanary {
|
||||||
runtimePanic("goroutine stack overflow")
|
runtimePanic("goroutine stack overflow")
|
||||||
}
|
}
|
||||||
if interrupt.In() {
|
if interrupt.In() {
|
||||||
runtimePanic("blocked inside interrupt")
|
runtimePanic("blocked inside interrupt")
|
||||||
}
|
}
|
||||||
currentTask.state.pause()
|
current := Current()
|
||||||
|
current.RunState = RunStatePaused
|
||||||
|
current.state.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
//export tinygo_pause
|
//export tinygo_task_exit
|
||||||
func pause() {
|
func taskExit() {
|
||||||
|
println("-- exiting task")
|
||||||
Pause()
|
Pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resume the task until it pauses or completes.
|
// Resume the task until it pauses or completes.
|
||||||
// This may only be called from the scheduler.
|
// This may only be called from the scheduler.
|
||||||
func (t *Task) Resume() {
|
func (t *Task) Resume() {
|
||||||
currentTask = t
|
//currentTask = t
|
||||||
t.gcData.swap()
|
//t.gcData.swap()
|
||||||
t.state.resume()
|
t.state.resume()
|
||||||
t.gcData.swap()
|
//t.gcData.swap()
|
||||||
currentTask = nil
|
//currentTask = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the state and prepare to call the specified function with the specified argument bundle.
|
// initialize the state and prepare to call the specified function with the specified argument bundle.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ tinygo_startTask:
|
|||||||
blx r4
|
blx r4
|
||||||
|
|
||||||
// After return, exit this goroutine. This is a tail call.
|
// After return, exit this goroutine. This is a tail call.
|
||||||
bl tinygo_pause
|
bl tinygo_task_exit
|
||||||
.cfi_endproc
|
.cfi_endproc
|
||||||
.size tinygo_startTask, .-tinygo_startTask
|
.size tinygo_startTask, .-tinygo_startTask
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build scheduler.tasks && cortexm
|
//go:build (scheduler.tasks || scheduler.cores) && cortexm
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
//go:build none
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// BDWGC also uses SIGRTMIN+6 on Linux, which seems like a reasonable choice.
|
||||||
|
#ifdef __linux__
|
||||||
|
#define taskPauseSignal (SIGRTMIN + 6)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Pointer to the current task.Task structure.
|
||||||
|
// Ideally the entire task.Task structure would be a thread-local variable but
|
||||||
|
// this also works.
|
||||||
|
static __thread void *current_task;
|
||||||
|
|
||||||
|
struct state_pass {
|
||||||
|
void *(*start)(void*);
|
||||||
|
void *args;
|
||||||
|
void *task;
|
||||||
|
uintptr_t *stackTop;
|
||||||
|
sem_t startlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle the GC pause in Go.
|
||||||
|
void tinygo_task_gc_pause(int sig);
|
||||||
|
|
||||||
|
// Initialize the main thread.
|
||||||
|
void tinygo_task_init(void *mainTask, pthread_t *thread, int *numCPU, void *context) {
|
||||||
|
// Make sure the current task pointer is set correctly for the main
|
||||||
|
// goroutine as well.
|
||||||
|
current_task = mainTask;
|
||||||
|
|
||||||
|
// Store the thread ID of the main thread.
|
||||||
|
*thread = pthread_self();
|
||||||
|
|
||||||
|
// Register the "GC pause" signal for the entire process.
|
||||||
|
// Using pthread_kill, we can still send the signal to a specific thread.
|
||||||
|
struct sigaction act = { 0 };
|
||||||
|
act.sa_flags = SA_SIGINFO;
|
||||||
|
act.sa_handler = &tinygo_task_gc_pause;
|
||||||
|
sigaction(taskPauseSignal, &act, NULL);
|
||||||
|
|
||||||
|
// Obtain the number of CPUs available on program start (for NumCPU).
|
||||||
|
int num = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
|
if (num <= 0) {
|
||||||
|
// Fallback in case there is an error.
|
||||||
|
num = 1;
|
||||||
|
}
|
||||||
|
*numCPU = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tinygo_task_exited(void*);
|
||||||
|
|
||||||
|
// Helper to start a goroutine while also storing the 'task' structure.
|
||||||
|
static void* start_wrapper(void *arg) {
|
||||||
|
struct state_pass *state = arg;
|
||||||
|
void *(*start)(void*) = state->start;
|
||||||
|
void *args = state->args;
|
||||||
|
current_task = state->task;
|
||||||
|
|
||||||
|
// Save the current stack pointer in the goroutine state, for the GC.
|
||||||
|
int stackAddr;
|
||||||
|
*(state->stackTop) = (uintptr_t)(&stackAddr);
|
||||||
|
|
||||||
|
// Notify the caller that the thread has successfully started and
|
||||||
|
// initialized.
|
||||||
|
sem_post(&state->startlock);
|
||||||
|
|
||||||
|
// Run the goroutine function.
|
||||||
|
start(args);
|
||||||
|
|
||||||
|
// Notify the Go side this thread will exit.
|
||||||
|
tinygo_task_exited(current_task);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start a new goroutine in an OS thread.
|
||||||
|
int tinygo_task_start(uintptr_t fn, void *args, void *task, pthread_t *thread, uintptr_t *stackTop, void *context) {
|
||||||
|
// Sanity check. Should get optimized away.
|
||||||
|
if (sizeof(pthread_t) != sizeof(void*)) {
|
||||||
|
__builtin_trap();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct state_pass state = {
|
||||||
|
.start = (void*)fn,
|
||||||
|
.args = args,
|
||||||
|
.task = task,
|
||||||
|
.stackTop = stackTop,
|
||||||
|
};
|
||||||
|
sem_init(&state.startlock, 0, 0);
|
||||||
|
int result = pthread_create(thread, NULL, &start_wrapper, &state);
|
||||||
|
|
||||||
|
// Wait until the thread has been created and read all state_pass variables.
|
||||||
|
sem_wait(&state.startlock);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the current task (for task.Current()).
|
||||||
|
void* tinygo_task_current(void) {
|
||||||
|
return current_task;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a signal to cause the task to pause for the GC mark phase.
|
||||||
|
void tinygo_task_send_gc_signal(pthread_t thread) {
|
||||||
|
pthread_kill(thread, taskPauseSignal);
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
//go:build scheduler.threads
|
||||||
|
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If true, print verbose debug logs.
|
||||||
|
const verbose = false
|
||||||
|
|
||||||
|
// Scheduler-specific state.
|
||||||
|
type state struct {
|
||||||
|
// Goroutine ID. The number here is not really significant and after a while
|
||||||
|
// it could wrap around. But it is useful for debugging.
|
||||||
|
id uintptr
|
||||||
|
|
||||||
|
// Thread ID, pthread_t or similar (typically implemented as a pointer).
|
||||||
|
thread threadID
|
||||||
|
|
||||||
|
// Highest address of the stack. It is stored when the goroutine starts, and
|
||||||
|
// is needed to be able to scan the stack.
|
||||||
|
stackTop uintptr
|
||||||
|
|
||||||
|
// Next task in the activeTasks queue.
|
||||||
|
QueueNext *Task
|
||||||
|
|
||||||
|
// Semaphore to pause/resume the thread atomically.
|
||||||
|
pauseSem Semaphore
|
||||||
|
|
||||||
|
// Semaphore used for stack scanning.
|
||||||
|
// We can't reuse pauseSem here since the thread might have been paused for
|
||||||
|
// other reasons (for example, because it was waiting on a channel).
|
||||||
|
gcSem Semaphore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Goroutine counter, starting at 0 for the main goroutine.
|
||||||
|
var goroutineID uintptr
|
||||||
|
|
||||||
|
var numCPU int32
|
||||||
|
|
||||||
|
var mainTask Task
|
||||||
|
|
||||||
|
// Queue of tasks (see QueueNext) that currently exist in the program.
|
||||||
|
var activeTasks = &mainTask
|
||||||
|
var activeTaskLock PMutex
|
||||||
|
|
||||||
|
func OnSystemStack() bool {
|
||||||
|
runtimePanic("todo: task.OnSystemStack")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the main goroutine state. Must be called by the runtime on
|
||||||
|
// startup, before starting any other goroutines.
|
||||||
|
func Init(sp uintptr) {
|
||||||
|
mainTask.state.stackTop = sp
|
||||||
|
tinygo_task_init(&mainTask, &mainTask.state.thread, &numCPU)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the task struct for the current thread.
|
||||||
|
func Current() *Task {
|
||||||
|
t := (*Task)(tinygo_task_current())
|
||||||
|
if t == nil {
|
||||||
|
runtimePanic("unknown current task")
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause pauses the current task, until it is resumed by another task.
|
||||||
|
// It is possible that another task has called Resume() on the task before it
|
||||||
|
// hits Pause(), in which case the task won't be paused but continues
|
||||||
|
// immediately.
|
||||||
|
func Pause() {
|
||||||
|
// Wait until resumed
|
||||||
|
t := Current()
|
||||||
|
if verbose {
|
||||||
|
println("*** pause: ", t.state.id)
|
||||||
|
}
|
||||||
|
t.state.pauseSem.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume the given task.
|
||||||
|
// It is legal to resume a task before it gets paused, it means that the next
|
||||||
|
// call to Pause() won't pause but will continue immediately. This happens in
|
||||||
|
// practice sometimes in channel operations, where the Resume() might get called
|
||||||
|
// between the channel unlock and the call to Pause().
|
||||||
|
func (t *Task) Resume() {
|
||||||
|
if verbose {
|
||||||
|
println("*** resume: ", t.state.id)
|
||||||
|
}
|
||||||
|
// Increment the semaphore counter.
|
||||||
|
// If the task is currently paused in Wait(), it will resume.
|
||||||
|
// If the task is not yet paused, the next call to Wait() will continue
|
||||||
|
// immediately.
|
||||||
|
t.state.pauseSem.Post()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a new OS thread.
|
||||||
|
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
||||||
|
t := &Task{}
|
||||||
|
t.state.id = atomic.AddUintptr(&goroutineID, 1)
|
||||||
|
if verbose {
|
||||||
|
println("*** start: ", t.state.id, "from", Current().state.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the new thread, and add it to the list of threads.
|
||||||
|
// Do this with a lock so that only started threads are part of the queue
|
||||||
|
// and the stop-the-world GC won't see threads that haven't started yet or
|
||||||
|
// are not fully started yet.
|
||||||
|
activeTaskLock.Lock()
|
||||||
|
errCode := tinygo_task_start(fn, args, t, &t.state.thread, &t.state.stackTop)
|
||||||
|
if errCode != 0 {
|
||||||
|
runtimePanic("could not start thread")
|
||||||
|
}
|
||||||
|
t.state.QueueNext = activeTasks
|
||||||
|
activeTasks = t
|
||||||
|
activeTaskLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tinygo_task_exited
|
||||||
|
func taskExited(t *Task) {
|
||||||
|
if verbose {
|
||||||
|
println("*** exit:", t.state.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from the queue.
|
||||||
|
// TODO: this can be made more efficient by using a doubly linked list.
|
||||||
|
activeTaskLock.Lock()
|
||||||
|
found := false
|
||||||
|
for q := &activeTasks; *q != nil; q = &(*q).state.QueueNext {
|
||||||
|
if *q == t {
|
||||||
|
*q = t.state.QueueNext
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activeTaskLock.Unlock()
|
||||||
|
|
||||||
|
// Sanity check.
|
||||||
|
if !found {
|
||||||
|
runtimePanic("taskExited failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Futex to wait on until all tasks have finished scanning the stack.
|
||||||
|
// This is basically a sync.WaitGroup.
|
||||||
|
var scanDoneFutex Futex
|
||||||
|
|
||||||
|
// GC scan phase. Because we need to stop the world while scanning, this kinda
|
||||||
|
// needs to be done in the tasks package.
|
||||||
|
func GCScan() {
|
||||||
|
current := Current()
|
||||||
|
|
||||||
|
// Don't allow new goroutines to be started while pausing/resuming threads
|
||||||
|
// in the stop-the-world phase.
|
||||||
|
activeTaskLock.Lock()
|
||||||
|
|
||||||
|
// Pause all other threads.
|
||||||
|
numOtherThreads := uint32(0)
|
||||||
|
for t := activeTasks; t != nil; t = t.state.QueueNext {
|
||||||
|
if t != current {
|
||||||
|
numOtherThreads++
|
||||||
|
tinygo_task_send_gc_signal(t.state.thread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the number of threads to wait for in the futex.
|
||||||
|
// This is the equivalent of doing an initial wg.Add(numOtherThreads).
|
||||||
|
scanDoneFutex.Store(numOtherThreads)
|
||||||
|
|
||||||
|
// Scan the current stack, and all current registers.
|
||||||
|
scanCurrentStack()
|
||||||
|
|
||||||
|
// Wake each paused thread for the first time so it will scan the stack.
|
||||||
|
for t := activeTasks; t != nil; t = t.state.QueueNext {
|
||||||
|
if t != current {
|
||||||
|
t.state.gcSem.Post()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until all threads have finished scanning their stack.
|
||||||
|
// This is the equivalent of wg.Wait()
|
||||||
|
for {
|
||||||
|
val := scanDoneFutex.Load()
|
||||||
|
if val == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
scanDoneFutex.Wait(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan all globals (implemented in the runtime).
|
||||||
|
gcScanGlobals()
|
||||||
|
|
||||||
|
// Wake each paused thread for the second time, so they will resume normal
|
||||||
|
// operation.
|
||||||
|
for t := activeTasks; t != nil; t = t.state.QueueNext {
|
||||||
|
if t != current {
|
||||||
|
t.state.gcSem.Post()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow goroutines to start and exit again.
|
||||||
|
activeTaskLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan globals, implemented in the runtime package.
|
||||||
|
func gcScanGlobals()
|
||||||
|
|
||||||
|
var stackScanLock PMutex
|
||||||
|
|
||||||
|
//export tinygo_task_gc_pause
|
||||||
|
func tingyo_task_gc_pause() {
|
||||||
|
// Wait until we get the signal to start scanning the stack.
|
||||||
|
Current().state.gcSem.Wait()
|
||||||
|
|
||||||
|
// Scan the thread stack.
|
||||||
|
// Only scan a single thread stack at a time, because the GC marking phase
|
||||||
|
// doesn't support parallelism.
|
||||||
|
// TODO: it may be possible to call markRoots directly (without saving
|
||||||
|
// registers) since we are in a signal handler that already saved a bunch of
|
||||||
|
// registers. This is an optimization left for a future time.
|
||||||
|
stackScanLock.Lock()
|
||||||
|
scanCurrentStack()
|
||||||
|
stackScanLock.Unlock()
|
||||||
|
|
||||||
|
// Equivalent of wg.Done(): subtract one from the futex and if the result is
|
||||||
|
// 0 (meaning we were the last in the waitgroup), wake the waiting thread.
|
||||||
|
n := uint32(1)
|
||||||
|
if scanDoneFutex.Add(-n) == 0 {
|
||||||
|
scanDoneFutex.Wake()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until we get the signal we can resume normally (after the mark phase
|
||||||
|
// has finished).
|
||||||
|
Current().state.gcSem.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export tinygo_scanCurrentStack
|
||||||
|
func scanCurrentStack()
|
||||||
|
|
||||||
|
// Return the highest address of the current stack.
|
||||||
|
func StackTop() uintptr {
|
||||||
|
return Current().state.stackTop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using //go:linkname instead of //export so that we don't tell the compiler
|
||||||
|
// that the 't' parameter won't escape (because it will).
|
||||||
|
//
|
||||||
|
//go:linkname tinygo_task_init tinygo_task_init
|
||||||
|
func tinygo_task_init(t *Task, thread *threadID, numCPU *int32)
|
||||||
|
|
||||||
|
// Here same as for tinygo_task_init.
|
||||||
|
//
|
||||||
|
//go:linkname tinygo_task_start tinygo_task_start
|
||||||
|
func tinygo_task_start(fn uintptr, args unsafe.Pointer, t *Task, thread *threadID, stackTop *uintptr) int32
|
||||||
|
|
||||||
|
// Pause the thread by sending it a signal.
|
||||||
|
//
|
||||||
|
//export tinygo_task_send_gc_signal
|
||||||
|
func tinygo_task_send_gc_signal(threadID)
|
||||||
|
|
||||||
|
//export tinygo_task_current
|
||||||
|
func tinygo_task_current() unsafe.Pointer
|
||||||
|
|
||||||
|
func NumCPU() int {
|
||||||
|
return int(numCPU)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,14 +1,5 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
// NumCPU returns the number of logical CPUs usable by the current process.
|
|
||||||
//
|
|
||||||
// The set of available CPUs is checked by querying the operating system
|
|
||||||
// at process startup. Changes to operating system CPU allocation after
|
|
||||||
// process startup are not reflected.
|
|
||||||
func NumCPU() int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub for NumCgoCall, does not return the real value
|
// Stub for NumCgoCall, does not return the real value
|
||||||
func NumCgoCall() int {
|
func NumCgoCall() int {
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ var (
|
|||||||
gcMallocs uint64 // total number of allocations
|
gcMallocs uint64 // total number of allocations
|
||||||
gcFrees uint64 // total number of objects freed
|
gcFrees uint64 // total number of objects freed
|
||||||
gcFreedBlocks uint64 // total number of freed blocks
|
gcFreedBlocks uint64 // total number of freed blocks
|
||||||
|
gcLock task.PMutex // lock to avoid race conditions on multicore systems
|
||||||
)
|
)
|
||||||
|
|
||||||
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
|
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
|
||||||
@@ -317,6 +318,10 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
|||||||
runtimePanicAt(returnAddress(0), "heap alloc in interrupt")
|
runtimePanicAt(returnAddress(0), "heap alloc in interrupt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure there are no concurrent allocations. The heap is not currently
|
||||||
|
// designed for concurrent alloc/GC.
|
||||||
|
gcLock.Lock()
|
||||||
|
|
||||||
gcTotalAlloc += uint64(size)
|
gcTotalAlloc += uint64(size)
|
||||||
gcMallocs++
|
gcMallocs++
|
||||||
|
|
||||||
@@ -399,6 +404,9 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
|||||||
i.setState(blockStateTail)
|
i.setState(blockStateTail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've claimed this allocation, now we can unlock the heap.
|
||||||
|
gcLock.Unlock()
|
||||||
|
|
||||||
// Return a pointer to this allocation.
|
// Return a pointer to this allocation.
|
||||||
pointer := thisAlloc.pointer()
|
pointer := thisAlloc.pointer()
|
||||||
if preciseHeap {
|
if preciseHeap {
|
||||||
@@ -444,7 +452,9 @@ func free(ptr unsafe.Pointer) {
|
|||||||
|
|
||||||
// GC performs a garbage collection cycle.
|
// GC performs a garbage collection cycle.
|
||||||
func GC() {
|
func GC() {
|
||||||
|
gcLock.Lock()
|
||||||
runGC()
|
runGC()
|
||||||
|
gcLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// runGC performs a garbage collection cycle. It is the internal implementation
|
// runGC performs a garbage collection cycle. It is the internal implementation
|
||||||
@@ -456,8 +466,7 @@ func runGC() (freeBytes uintptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark phase: mark all reachable objects, recursively.
|
// Mark phase: mark all reachable objects, recursively.
|
||||||
markStack()
|
gcMarkReachable()
|
||||||
findGlobals(markRoots)
|
|
||||||
|
|
||||||
if baremetal && hasScheduler {
|
if baremetal && hasScheduler {
|
||||||
// Channel operations in interrupts may move task pointers around while we are marking.
|
// Channel operations in interrupts may move task pointers around while we are marking.
|
||||||
@@ -714,6 +723,7 @@ func dumpHeap() {
|
|||||||
// The returned memory statistics are up to date as of the
|
// The returned memory statistics are up to date as of the
|
||||||
// call to ReadMemStats. This would not do GC implicitly for you.
|
// call to ReadMemStats. This would not do GC implicitly for you.
|
||||||
func ReadMemStats(m *MemStats) {
|
func ReadMemStats(m *MemStats) {
|
||||||
|
gcLock.Lock()
|
||||||
m.HeapIdle = 0
|
m.HeapIdle = 0
|
||||||
m.HeapInuse = 0
|
m.HeapInuse = 0
|
||||||
for block := gcBlock(0); block < endBlock; block++ {
|
for block := gcBlock(0); block < endBlock; block++ {
|
||||||
@@ -733,6 +743,7 @@ func ReadMemStats(m *MemStats) {
|
|||||||
m.Sys = uint64(heapEnd - heapStart)
|
m.Sys = uint64(heapEnd - heapStart)
|
||||||
m.HeapAlloc = (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock)
|
m.HeapAlloc = (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock)
|
||||||
m.Alloc = m.HeapAlloc
|
m.Alloc = m.HeapAlloc
|
||||||
|
gcLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func gcMarkReachable() {
|
||||||
|
markStack()
|
||||||
|
findGlobals(markRoots)
|
||||||
|
}
|
||||||
|
|
||||||
//go:extern runtime.stackChainStart
|
//go:extern runtime.stackChainStart
|
||||||
var stackChainStart *stackChainObject
|
var stackChainStart *stackChainObject
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
//go:build (gc.conservative || gc.precise) && !tinygo.wasm
|
//go:build (gc.conservative || gc.precise) && !tinygo.wasm && !scheduler.threads && !scheduler.cores
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import "internal/task"
|
import "internal/task"
|
||||||
|
|
||||||
|
func gcMarkReachable() {
|
||||||
|
markStack()
|
||||||
|
findGlobals(markRoots)
|
||||||
|
}
|
||||||
|
|
||||||
// markStack marks all root pointers found on the stack.
|
// markStack marks all root pointers found on the stack.
|
||||||
//
|
//
|
||||||
// This implementation is conservative and relies on the stack top (provided by
|
// This implementation is conservative and relies on the stack top (provided by
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//go:build scheduler.threads
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "internal/task"
|
||||||
|
|
||||||
|
func gcMarkReachable() {
|
||||||
|
task.GCScan()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan globals inside the stop-the-world phase. Called from the STW
|
||||||
|
// implementation in the internal/task package.
|
||||||
|
//
|
||||||
|
//go:linkname gcScanGlobals internal/task.gcScanGlobals
|
||||||
|
func gcScanGlobals() {
|
||||||
|
findGlobals(markRoots)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function called from assembly with all registers pushed, to actually scan the
|
||||||
|
// stack.
|
||||||
|
//
|
||||||
|
//go:export tinygo_scanstack
|
||||||
|
func scanstack(sp uintptr) {
|
||||||
|
markRoots(sp, task.StackTop())
|
||||||
|
}
|
||||||
@@ -13,11 +13,15 @@ type stringer interface {
|
|||||||
// This is a no-op lock on systems that do not have parallelism.
|
// This is a no-op lock on systems that do not have parallelism.
|
||||||
var printLock task.PMutex
|
var printLock task.PMutex
|
||||||
|
|
||||||
|
//var printLocked interrupt.State
|
||||||
|
|
||||||
func printlock() {
|
func printlock() {
|
||||||
|
//printLocked = serialLock()
|
||||||
printLock.Lock()
|
printLock.Lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func printunlock() {
|
func printunlock() {
|
||||||
|
//serialUnlock(printLocked)
|
||||||
printLock.Unlock()
|
printLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ type timeUnit int64
|
|||||||
//go:linkname systemInit SystemInit
|
//go:linkname systemInit SystemInit
|
||||||
func systemInit()
|
func systemInit()
|
||||||
|
|
||||||
|
const numCPU = 1
|
||||||
|
|
||||||
//export Reset_Handler
|
//export Reset_Handler
|
||||||
func main() {
|
func main() {
|
||||||
if nrf.FPUPresent {
|
if nrf.FPUPresent {
|
||||||
@@ -145,3 +147,31 @@ func rtc_sleep(ticks uint32) {
|
|||||||
waitForEvents()
|
waitForEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func atomicLockImpl() interrupt.State {
|
||||||
|
mask := interrupt.Disable()
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
|
func atomicUnlockImpl(mask interrupt.State) {
|
||||||
|
interrupt.Restore(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func futexLock() interrupt.State {
|
||||||
|
mask := interrupt.Disable()
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
|
func futexUnlock(mask interrupt.State) {
|
||||||
|
interrupt.Restore(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func schedulerLock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func schedulerUnlock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func currentCPU() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,13 @@ package runtime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"device/arm"
|
"device/arm"
|
||||||
|
"device/rp"
|
||||||
|
"internal/task"
|
||||||
"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 +21,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 +57,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 +82,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 +95,174 @@ 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 startSecondaryCores() {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var core1Task task.Task
|
||||||
|
|
||||||
|
func runCore1() {
|
||||||
|
//until := ticks() + nanosecondsToTicks(1900e6)
|
||||||
|
//for ticks() < until {
|
||||||
|
//}
|
||||||
|
println("starting core 1")
|
||||||
|
|
||||||
|
runSecondary(1, &core1Task)
|
||||||
|
|
||||||
|
// Just blink a LED to show that this core is running.
|
||||||
|
// TODO: use a real scheduler.
|
||||||
|
//led := machine.GP16
|
||||||
|
//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
|
||||||
|
var schedulerLocked = false
|
||||||
|
|
||||||
|
// WARNING: doesn't check for deadlocks!
|
||||||
|
func schedulerLock() {
|
||||||
|
//schedulerLockMasks[currentCPU()] = interrupt.Disable()
|
||||||
|
for rp.SIO.SPINLOCK2.Get() == 0 {
|
||||||
|
}
|
||||||
|
schedulerLocked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func schedulerUnlock() {
|
||||||
|
if !schedulerLocked {
|
||||||
|
println("!!! not locked at unlock")
|
||||||
|
for {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schedulerLocked = false
|
||||||
|
rp.SIO.SPINLOCK2.Set(0)
|
||||||
|
//interrupt.Restore(schedulerLockMasks[currentCPU()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialLock() interrupt.State {
|
||||||
|
//mask := interrupt.Disable()
|
||||||
|
for rp.SIO.SPINLOCK3.Get() == 0 {
|
||||||
|
}
|
||||||
|
//return mask
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialUnlock(mask interrupt.State) {
|
||||||
|
rp.SIO.SPINLOCK3.Set(0)
|
||||||
|
//interrupt.Restore(mask)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const numCPU = 1
|
||||||
|
|
||||||
|
func currentCPU() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
//export write
|
//export write
|
||||||
func libc_write(fd int32, buf unsafe.Pointer, count uint) int
|
func libc_write(fd int32, buf unsafe.Pointer, count uint) int
|
||||||
|
|
||||||
@@ -73,6 +79,7 @@ type timespec struct {
|
|||||||
tv_nsec int64 // unsigned 64-bit integer on all time64 platforms
|
tv_nsec int64 // unsigned 64-bit integer on all time64 platforms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highest address of the stack of the main thread.
|
||||||
var stackTop uintptr
|
var stackTop uintptr
|
||||||
|
|
||||||
// Entry point for Go. Initialize all packages and call main.main().
|
// Entry point for Go. Initialize all packages and call main.main().
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ const schedulerDebug = false
|
|||||||
|
|
||||||
var mainExited bool
|
var mainExited bool
|
||||||
|
|
||||||
|
var timerQueue *timerNode
|
||||||
|
|
||||||
// Simple logging, for debugging.
|
// Simple logging, for debugging.
|
||||||
func scheduleLog(msg string) {
|
func scheduleLog(msg string) {
|
||||||
if schedulerDebug {
|
if schedulerDebug {
|
||||||
@@ -27,6 +29,34 @@ func scheduleLogChan(msg string, ch *channel, t *task.Task) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timerQueueAdd(tn *timerNode) {
|
||||||
|
q := &timerQueue
|
||||||
|
for ; *q != nil; q = &(*q).next {
|
||||||
|
if tn.whenTicks() < (*q).whenTicks() {
|
||||||
|
// this will finish earlier than the next - insert here
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tn.next = *q
|
||||||
|
*q = tn
|
||||||
|
}
|
||||||
|
|
||||||
|
func timerQueueRemove(t *timer) bool {
|
||||||
|
removedTimer := false
|
||||||
|
for q := &timerQueue; *q != nil; q = &(*q).next {
|
||||||
|
if (*q).timer == t {
|
||||||
|
scheduleLog("removed timer")
|
||||||
|
*q = (*q).next
|
||||||
|
removedTimer = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !removedTimer {
|
||||||
|
scheduleLog("did not remove timer")
|
||||||
|
}
|
||||||
|
return removedTimer
|
||||||
|
}
|
||||||
|
|
||||||
// Goexit terminates the currently running goroutine. No other goroutines are affected.
|
// Goexit terminates the currently running goroutine. No other goroutines are affected.
|
||||||
func Goexit() {
|
func Goexit() {
|
||||||
panicOrGoexit(nil, panicGoexit)
|
panicOrGoexit(nil, panicGoexit)
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ var (
|
|||||||
runqueue task.Queue
|
runqueue task.Queue
|
||||||
sleepQueue *task.Task
|
sleepQueue *task.Task
|
||||||
sleepQueueBaseTime timeUnit
|
sleepQueueBaseTime timeUnit
|
||||||
timerQueue *timerNode
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// deadlock is called when a goroutine cannot proceed any more, but is in theory
|
// deadlock is called when a goroutine cannot proceed any more, but is in theory
|
||||||
@@ -58,6 +57,11 @@ func Gosched() {
|
|||||||
task.Pause()
|
task.Pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumCPU returns the number of logical CPUs usable by the current process.
|
||||||
|
func NumCPU() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
// Add this task to the sleep queue, assuming its state is set to sleeping.
|
// Add this task to the sleep queue, assuming its state is set to sleeping.
|
||||||
func addSleepTask(t *task.Task, duration timeUnit) {
|
func addSleepTask(t *task.Task, duration timeUnit) {
|
||||||
if schedulerDebug {
|
if schedulerDebug {
|
||||||
@@ -100,36 +104,15 @@ func addSleepTask(t *task.Task, duration timeUnit) {
|
|||||||
// sleepQueue.
|
// sleepQueue.
|
||||||
func addTimer(tim *timerNode) {
|
func addTimer(tim *timerNode) {
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
|
timerQueueAdd(tim)
|
||||||
// Add to timer queue.
|
|
||||||
q := &timerQueue
|
|
||||||
for ; *q != nil; q = &(*q).next {
|
|
||||||
if tim.whenTicks() < (*q).whenTicks() {
|
|
||||||
// this will finish earlier than the next - insert here
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tim.next = *q
|
|
||||||
*q = tim
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeTimer is the implementation of time.stopTimer. It removes a timer from
|
// removeTimer is the implementation of time.stopTimer. It removes a timer from
|
||||||
// the timer queue, returning true if the timer is present in the timer queue.
|
// the timer queue, returning true if the timer is present in the timer queue.
|
||||||
func removeTimer(tim *timer) bool {
|
func removeTimer(tim *timer) bool {
|
||||||
removedTimer := false
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
for t := &timerQueue; *t != nil; t = &(*t).next {
|
removedTimer := timerQueueRemove(tim)
|
||||||
if (*t).timer == tim {
|
|
||||||
scheduleLog("removed timer")
|
|
||||||
*t = (*t).next
|
|
||||||
removedTimer = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !removedTimer {
|
|
||||||
scheduleLog("did not remove timer")
|
|
||||||
}
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
return removedTimer
|
return removedTimer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
//go:build scheduler.cores
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/arm"
|
||||||
|
"internal/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasScheduler = true
|
||||||
|
|
||||||
|
const hasParallelism = true
|
||||||
|
|
||||||
|
const coresVerbose = false
|
||||||
|
|
||||||
|
var (
|
||||||
|
mainTask task.Task
|
||||||
|
cpuTasks [numCPU]*task.Task
|
||||||
|
sleepQueue *task.Task
|
||||||
|
runQueue *task.Task
|
||||||
|
)
|
||||||
|
|
||||||
|
func deadlock() {
|
||||||
|
// Call yield without requesting a wakeup.
|
||||||
|
task.Pause()
|
||||||
|
trap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheduleTask(t *task.Task) {
|
||||||
|
schedulerLock()
|
||||||
|
switch t.RunState {
|
||||||
|
case task.RunStatePaused:
|
||||||
|
// Paused, state is saved on the stack.
|
||||||
|
|
||||||
|
if coresVerbose {
|
||||||
|
println("## schedule: add to runQueue")
|
||||||
|
}
|
||||||
|
addToRunQueue(t)
|
||||||
|
arm.Asm("sev")
|
||||||
|
case task.RunStateRunning:
|
||||||
|
// Not yet paused (probably going to pause very soon), so let the
|
||||||
|
// Pause() function know it can resume immediately.
|
||||||
|
t.RunState = task.RunStateResuming
|
||||||
|
if coresVerbose {
|
||||||
|
println("## schedule: mark as resuming")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
println("Unknown run state??")
|
||||||
|
for {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schedulerUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add task to runQueue.
|
||||||
|
// Scheduler lock must be held when calling this function.
|
||||||
|
func addToRunQueue(t *task.Task) {
|
||||||
|
t.Next = runQueue
|
||||||
|
runQueue = t
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSleepTask(t *task.Task, wakeup timeUnit) {
|
||||||
|
// Save the timestamp when the task should be woken up.
|
||||||
|
t.Data = uint64(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 timeUnit((*q).Data) > timeUnit(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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
schedulerLock()
|
||||||
|
addSleepTask(task.Current(), wakeup)
|
||||||
|
task.PauseLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() {
|
||||||
|
initHeap()
|
||||||
|
cpuTasks[0] = &mainTask
|
||||||
|
initAll() // TODO: move into main goroutine!
|
||||||
|
|
||||||
|
until := ticks() + nanosecondsToTicks(200e6)
|
||||||
|
for ticks() < until {
|
||||||
|
}
|
||||||
|
println("\n\n=====")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
//initAll()
|
||||||
|
startSecondaryCores()
|
||||||
|
callMain()
|
||||||
|
mainExited = true
|
||||||
|
}()
|
||||||
|
schedulerLock()
|
||||||
|
scheduler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSecondary(core uint32, t *task.Task) {
|
||||||
|
println("-- runSecondary")
|
||||||
|
cpuTasks[core] = t
|
||||||
|
println("-- locking for 2nd core")
|
||||||
|
schedulerLock()
|
||||||
|
println("-- locked!")
|
||||||
|
scheduler()
|
||||||
|
}
|
||||||
|
|
||||||
|
var schedulerIsRunning = false
|
||||||
|
|
||||||
|
func scheduler() {
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler on core:", currentCPU())
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
//until := ticks() + nanosecondsToTicks(100e6)
|
||||||
|
//for ticks() < until {
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Check for ready-to-run tasks.
|
||||||
|
if runnable := runQueue; runnable != nil {
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler", currentCPU(), "run runnable")
|
||||||
|
}
|
||||||
|
// Pop off the run queue.
|
||||||
|
runQueue = runnable.Next
|
||||||
|
runnable.Next = nil
|
||||||
|
|
||||||
|
// Resume it now.
|
||||||
|
setCurrentTask(runnable)
|
||||||
|
schedulerUnlock()
|
||||||
|
runnable.Resume()
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler", currentCPU(), " returned (from runqueue resume)")
|
||||||
|
}
|
||||||
|
setCurrentTask(nil)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If another core is using the clock, let it handle the sleep queue.
|
||||||
|
if schedulerIsRunning {
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler", currentCPU(), "wait for other core")
|
||||||
|
}
|
||||||
|
schedulerUnlock()
|
||||||
|
waitForEvents()
|
||||||
|
schedulerLock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if sleepingTask := sleepQueue; sleepingTask != nil {
|
||||||
|
now := ticks()
|
||||||
|
if now >= timeUnit(sleepingTask.Data) {
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler", currentCPU(), "run sleeping")
|
||||||
|
}
|
||||||
|
// This task is done sleeping.
|
||||||
|
// Resume it now.
|
||||||
|
sleepQueue = sleepQueue.Next
|
||||||
|
sleepingTask.Next = nil
|
||||||
|
|
||||||
|
setCurrentTask(sleepingTask)
|
||||||
|
schedulerUnlock()
|
||||||
|
sleepingTask.Resume()
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler", currentCPU(), " returned (from sleepQueue resume)")
|
||||||
|
}
|
||||||
|
setCurrentTask(nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := timeUnit(sleepingTask.Data) - now
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler", currentCPU(), "sleep", ticksToNanoseconds(delay)/1e6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep for a bit until the next task is ready to run.
|
||||||
|
schedulerIsRunning = true
|
||||||
|
schedulerUnlock()
|
||||||
|
sleepTicks(delay)
|
||||||
|
schedulerLock()
|
||||||
|
schedulerIsRunning = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if coresVerbose {
|
||||||
|
println("** scheduler", currentCPU(), "wait for events")
|
||||||
|
}
|
||||||
|
schedulerUnlock()
|
||||||
|
waitForEvents()
|
||||||
|
schedulerLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tinygo_schedulerUnlock
|
||||||
|
func tinygo_schedulerUnlock() {
|
||||||
|
schedulerUnlock()
|
||||||
|
}
|
||||||
@@ -40,6 +40,11 @@ func Gosched() {
|
|||||||
// There are no other goroutines, so there's nothing to schedule.
|
// There are no other goroutines, so there's nothing to schedule.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumCPU returns the number of logical CPUs usable by the current process.
|
||||||
|
func NumCPU() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
func addTimer(tim *timerNode) {
|
func addTimer(tim *timerNode) {
|
||||||
runtimePanic("timers not supported without a scheduler")
|
runtimePanic("timers not supported without a scheduler")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
//go:build scheduler.threads
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "internal/task"
|
||||||
|
|
||||||
|
const hasScheduler = false // not using the cooperative scheduler
|
||||||
|
|
||||||
|
// We use threads, so yes there is parallelism.
|
||||||
|
const hasParallelism = true
|
||||||
|
|
||||||
|
var (
|
||||||
|
timerQueueLock task.PMutex
|
||||||
|
timerQueueStarted bool
|
||||||
|
timerFutex task.Futex
|
||||||
|
)
|
||||||
|
|
||||||
|
// Because we just use OS threads, we don't need to do anything special here. We
|
||||||
|
// can just initialize everything and run main.main on the main thread.
|
||||||
|
func run() {
|
||||||
|
initHeap()
|
||||||
|
task.Init(stackTop)
|
||||||
|
initAll()
|
||||||
|
callMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause the current task for a given time.
|
||||||
|
//
|
||||||
|
//go:linkname sleep time.Sleep
|
||||||
|
func sleep(duration int64) {
|
||||||
|
if duration <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepTicks(nanosecondsToTicks(duration))
|
||||||
|
}
|
||||||
|
|
||||||
|
func deadlock() {
|
||||||
|
// TODO: exit the thread via pthread_exit.
|
||||||
|
task.Pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheduleTask(t *task.Task) {
|
||||||
|
t.Resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gosched() {
|
||||||
|
// Each goroutine runs in a thread, so there's not much we can do here.
|
||||||
|
// There is sched_yield but it's only really intended for realtime
|
||||||
|
// operation, so is probably best not to use.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumCPU returns the number of logical CPUs usable by the current process.
|
||||||
|
func NumCPU() int {
|
||||||
|
return task.NumCPU()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate goroutine (thread) that runs timer callbacks when they expire.
|
||||||
|
func timerRunner() {
|
||||||
|
for {
|
||||||
|
timerQueueLock.Lock()
|
||||||
|
|
||||||
|
if timerQueue == nil {
|
||||||
|
// No timer in the queue, so wait until one becomes available.
|
||||||
|
val := timerFutex.Load()
|
||||||
|
timerQueueLock.Unlock()
|
||||||
|
timerFutex.Wait(val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
now := ticks()
|
||||||
|
if now < timerQueue.whenTicks() {
|
||||||
|
// There is a timer in the queue, but we need to wait until it
|
||||||
|
// expires.
|
||||||
|
// Using a futex, so that the wait is exited early when adding a new
|
||||||
|
// (sooner-to-expire) timer.
|
||||||
|
val := timerFutex.Load()
|
||||||
|
timerQueueLock.Unlock()
|
||||||
|
timeout := ticksToNanoseconds(timerQueue.whenTicks() - now)
|
||||||
|
timerFutex.WaitUntil(val, uint64(timeout))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop timer from queue.
|
||||||
|
tn := timerQueue
|
||||||
|
timerQueue = tn.next
|
||||||
|
tn.next = nil
|
||||||
|
|
||||||
|
timerQueueLock.Unlock()
|
||||||
|
|
||||||
|
// Run the callback stored in this timer node.
|
||||||
|
delay := ticksToNanoseconds(now - tn.whenTicks())
|
||||||
|
tn.callback(tn, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTimer(tim *timerNode) {
|
||||||
|
timerQueueLock.Lock()
|
||||||
|
|
||||||
|
if !timerQueueStarted {
|
||||||
|
timerQueueStarted = true
|
||||||
|
go timerRunner()
|
||||||
|
}
|
||||||
|
|
||||||
|
timerQueueAdd(tim)
|
||||||
|
|
||||||
|
timerFutex.Add(1)
|
||||||
|
timerFutex.Wake()
|
||||||
|
|
||||||
|
timerQueueLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTimer(tim *timer) bool {
|
||||||
|
timerQueueLock.Lock()
|
||||||
|
removed := timerQueueRemove(tim)
|
||||||
|
timerQueueLock.Unlock()
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
func schedulerRunQueue() *task.Queue {
|
||||||
|
// This function is not actually used, it is only called when hasScheduler
|
||||||
|
// is true. So we can just return nil here.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runqueueForGC() *task.Queue {
|
||||||
|
// There is only a runqueue when using the cooperative scheduler.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+62
-97
@@ -6,131 +6,96 @@ import (
|
|||||||
|
|
||||||
type Mutex = task.Mutex
|
type Mutex = task.Mutex
|
||||||
|
|
||||||
|
//go:linkname runtimePanic runtime.runtimePanic
|
||||||
|
func runtimePanic(msg string)
|
||||||
|
|
||||||
type RWMutex struct {
|
type RWMutex struct {
|
||||||
// waitingWriters are all of the tasks waiting for write locks.
|
// Reader count, with the number of readers that currently have read-locked
|
||||||
waitingWriters task.Stack
|
// this mutex.
|
||||||
|
// The value can be in two states: one where 0 means no readers and another
|
||||||
|
// where -rwMutexMaxReaders means no readers. A base of 0 is normal
|
||||||
|
// uncontended operation, a base of -rwMutexMaxReaders means a writer has
|
||||||
|
// the lock or is trying to get the lock. In the second case, readers should
|
||||||
|
// wait until the reader count becomes non-negative again to give the writer
|
||||||
|
// a chance to obtain the lock.
|
||||||
|
readers task.Futex
|
||||||
|
|
||||||
// waitingReaders are all of the tasks waiting for a read lock.
|
// Writer futex, normally 0. If there is a writer waiting until all readers
|
||||||
waitingReaders task.Stack
|
// have unlocked, this value is 1. It will be changed to a 2 (and get a
|
||||||
|
// wake) when the last reader unlocks.
|
||||||
|
writer task.Futex
|
||||||
|
|
||||||
// state is the current state of the RWMutex.
|
// Writer lock. Held between Lock() and Unlock().
|
||||||
// Iff the mutex is completely unlocked, it contains rwMutexStateUnlocked (aka 0).
|
writerLock Mutex
|
||||||
// Iff the mutex is write-locked, it contains rwMutexStateWLocked.
|
|
||||||
// While the mutex is read-locked, it contains the current number of readers.
|
|
||||||
state uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const rwMutexMaxReaders = 1 << 30
|
||||||
rwMutexStateUnlocked = uint32(0)
|
|
||||||
rwMutexStateWLocked = ^uint32(0)
|
|
||||||
rwMutexMaxReaders = rwMutexStateWLocked - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
func (rw *RWMutex) Lock() {
|
func (rw *RWMutex) Lock() {
|
||||||
if rw.state == 0 {
|
// Exclusive lock for writers.
|
||||||
// The mutex is completely unlocked.
|
rw.writerLock.Lock()
|
||||||
// Lock without waiting.
|
|
||||||
rw.state = rwMutexStateWLocked
|
// Flag that we need to be awakened after the last read-lock unlocks.
|
||||||
|
rw.writer.Store(1)
|
||||||
|
|
||||||
|
// Signal to readers that they can't lock this mutex anymore.
|
||||||
|
n := uint32(rwMutexMaxReaders)
|
||||||
|
waiting := rw.readers.Add(-n)
|
||||||
|
if int32(waiting) == -rwMutexMaxReaders {
|
||||||
|
// All readers were already unlocked, so we don't need to wait for them.
|
||||||
|
rw.writer.Store(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the lock to be released.
|
// There is at least one reader.
|
||||||
rw.waitingWriters.Push(task.Current())
|
// Wait until all readers are unlocked. The last reader to unlock will set
|
||||||
task.Pause()
|
// rw.writer to 2 and awaken us.
|
||||||
|
for rw.writer.Load() == 1 {
|
||||||
|
rw.writer.Wait(1)
|
||||||
|
}
|
||||||
|
rw.writer.Store(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWMutex) Unlock() {
|
func (rw *RWMutex) Unlock() {
|
||||||
switch rw.state {
|
// Signal that new readers can lock this mutex.
|
||||||
case rwMutexStateWLocked:
|
waiting := rw.readers.Add(rwMutexMaxReaders)
|
||||||
// This is correct.
|
if waiting != 0 {
|
||||||
|
// Awaken all waiting readers.
|
||||||
case rwMutexStateUnlocked:
|
rw.readers.WakeAll()
|
||||||
// The mutex is already unlocked.
|
|
||||||
panic("sync: unlock of unlocked RWMutex")
|
|
||||||
|
|
||||||
default:
|
|
||||||
// The mutex is read-locked instead of write-locked.
|
|
||||||
panic("sync: write-unlock of read-locked RWMutex")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
// Done with this lock (next writer can try to get a lock).
|
||||||
case rw.maybeUnblockReaders():
|
rw.writerLock.Unlock()
|
||||||
// Switched over to read mode.
|
|
||||||
|
|
||||||
case rw.maybeUnblockWriter():
|
|
||||||
// Transferred to another writer.
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Nothing is waiting for the lock.
|
|
||||||
rw.state = rwMutexStateUnlocked
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWMutex) RLock() {
|
func (rw *RWMutex) RLock() {
|
||||||
if rw.state == rwMutexStateWLocked {
|
// Add us as a reader.
|
||||||
// Wait for the write lock to be released.
|
newVal := rw.readers.Add(1)
|
||||||
rw.waitingReaders.Push(task.Current())
|
|
||||||
task.Pause()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if rw.state == rwMutexMaxReaders {
|
// Wait until the RWMutex is available for readers.
|
||||||
panic("sync: too many readers on RWMutex")
|
for int32(newVal) <= 0 {
|
||||||
|
rw.readers.Wait(newVal)
|
||||||
|
newVal = rw.readers.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increase the reader count.
|
|
||||||
rw.state++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWMutex) RUnlock() {
|
func (rw *RWMutex) RUnlock() {
|
||||||
switch rw.state {
|
// Remove us as a reader.
|
||||||
case rwMutexStateUnlocked:
|
one := uint32(1)
|
||||||
// The mutex is already unlocked.
|
readers := int32(rw.readers.Add(-one))
|
||||||
panic("sync: unlock of unlocked RWMutex")
|
|
||||||
|
|
||||||
case rwMutexStateWLocked:
|
// Check whether RUnlock was called too often.
|
||||||
// The mutex is write-locked instead of read-locked.
|
if readers == -1 || readers == (-rwMutexMaxReaders)-1 {
|
||||||
panic("sync: read-unlock of write-locked RWMutex")
|
runtimePanic("sync: RUnlock of unlocked RWMutex")
|
||||||
}
|
}
|
||||||
|
|
||||||
rw.state--
|
if readers == -rwMutexMaxReaders {
|
||||||
|
// This was the last read lock. Check whether we need to wake up a write
|
||||||
if rw.state == rwMutexStateUnlocked {
|
// lock.
|
||||||
// This was the last reader.
|
if rw.writer.CompareAndSwap(1, 2) {
|
||||||
// Try to unblock a writer.
|
rw.writer.Wake()
|
||||||
rw.maybeUnblockWriter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *RWMutex) maybeUnblockReaders() bool {
|
|
||||||
var n uint32
|
|
||||||
for {
|
|
||||||
t := rw.waitingReaders.Pop()
|
|
||||||
if t == nil {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n++
|
|
||||||
scheduleTask(t)
|
|
||||||
}
|
}
|
||||||
if n == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.state = n
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *RWMutex) maybeUnblockWriter() bool {
|
|
||||||
t := rw.waitingWriters.Pop()
|
|
||||||
if t == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.state = rwMutexStateWLocked
|
|
||||||
scheduleTask(t)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Locker interface {
|
type Locker interface {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
@@ -12,6 +12,7 @@ var wg sync.WaitGroup
|
|||||||
type intchan chan int
|
type intchan chan int
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
ch := make(chan int, 2)
|
ch := make(chan int, 2)
|
||||||
ch <- 1
|
ch <- 1
|
||||||
println("len, cap of channel:", len(ch), cap(ch), ch == nil)
|
println("len, cap of channel:", len(ch), cap(ch), ch == nil)
|
||||||
|
|||||||
Vendored
+15
-5
@@ -5,19 +5,27 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
//func init() {
|
||||||
println("init")
|
// println("\n----")
|
||||||
go println("goroutine in init")
|
// println("init")
|
||||||
time.Sleep(1 * time.Millisecond)
|
// go println("goroutine in init")
|
||||||
}
|
// time.Sleep(1 * time.Millisecond)
|
||||||
|
//}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
//for i := 0; i < 2; i++ {
|
||||||
|
// println("...")
|
||||||
|
// time.Sleep(time.Second)
|
||||||
|
//}
|
||||||
|
|
||||||
println("main 1")
|
println("main 1")
|
||||||
go sub()
|
go sub()
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
println("main 2")
|
println("main 2")
|
||||||
time.Sleep(2 * time.Millisecond)
|
time.Sleep(2 * time.Millisecond)
|
||||||
println("main 3")
|
println("main 3")
|
||||||
|
//time.Sleep(2 * time.Millisecond)
|
||||||
|
//println("main 4")
|
||||||
|
|
||||||
// Await a blocking call.
|
// Await a blocking call.
|
||||||
println("wait:")
|
println("wait:")
|
||||||
@@ -101,6 +109,8 @@ func sub() {
|
|||||||
println("sub 1")
|
println("sub 1")
|
||||||
time.Sleep(2 * time.Millisecond)
|
time.Sleep(2 * time.Millisecond)
|
||||||
println("sub 2")
|
println("sub 2")
|
||||||
|
//time.Sleep(2 * time.Millisecond)
|
||||||
|
//println("sub 3")
|
||||||
}
|
}
|
||||||
|
|
||||||
func wait() {
|
func wait() {
|
||||||
|
|||||||
@@ -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