Skip to content

Baremetal multicore support #4851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion builder/sizes.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
continue
}
if section.Type == elf.SHT_NOBITS {
if section.Name == ".stack" {
if strings.HasPrefix(section.Name, ".stack") {
// TinyGo emits stack sections on microcontroller using the
// ".stack" name.
// This is a bit ugly, but I don't think there is a way to
Expand Down
5 changes: 5 additions & 0 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ func (c *Config) BuildTags() []string {
"math_big_pure_go", // to get math/big to work
"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
"serial." + c.Serial()}...) // used inside the machine package
switch c.Scheduler() {
case "threads", "cores":
default:
tags = append(tags, "tinygo.unicore")
}
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
Expand Down
2 changes: 1 addition & 1 deletion compileopts/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
var (
validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"}
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"}
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads"}
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"}
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
validPrintSizeOptions = []string{"none", "short", "full", "html"}
validPanicStrategyOptions = []string{"print", "trap"}
Expand Down
2 changes: 1 addition & 1 deletion compileopts/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func TestVerifyOptions(t *testing.T) {

expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise, boehm`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify, threads`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify, threads, cores`)
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`)

Expand Down
37 changes: 36 additions & 1 deletion src/device/riscv/start.S
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,43 @@
.type _start,@function

_start:
// If we're on a multicore system, we need to wait for hart 0 to wake us up.
#if TINYGO_CORES > 1
csrr a0, mhartid

// Hart 0 stack
bnez a0, 1f
la sp, _stack0_top

1:
// Hart 1 stack
li a1, 1
bne a0, a1, 2f
la sp, _stack1_top

2:
// Hart 2 stack
#if TINYGO_CORES >= 3
li a1, 2
bne a0, a1, 3f
la sp, _stack2_top
#endif

3:
// Hart 3 stack
#if TINYGO_CORES >= 4
li a1, 3
bne a0, a1, 4f
la sp, _stack3_top
#endif

4:
// done

#else
// Load the stack pointer.
la sp, _stack_top
la sp, _stack0_top
#endif

// Load the globals pointer. The program will load pointers relative to this
// register, so it must be set to the right value on startup.
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/atomic-cooperative.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore

package task

Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/atomic-preemptive.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build scheduler.threads
//go:build !tinygo.unicore

package task

Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/futex-cooperative.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore

package task

Expand Down
64 changes: 64 additions & 0 deletions src/internal/task/futex-cores.go
Original file line number Diff line number Diff line change
@@ -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 := lockFutex()

if f.Uint32.Load() != cmp {
unlockFutex(mask)
return false
}

// Push the current goroutine onto the waiter stack.
f.waiters.Push(Current())

unlockFutex(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 := lockFutex()
if t := f.waiters.Pop(); t != nil {
scheduleTask(t)
}
unlockFutex(mask)
}

// Wake all waiters.
func (f *Futex) WakeAll() {
mask := lockFutex()
for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() {
scheduleTask(t)
}
unlockFutex(mask)
}

//go:linkname lockFutex runtime.lockFutex
func lockFutex() interrupt.State

//go:linkname unlockFutex runtime.unlockFutex
func unlockFutex(interrupt.State)
2 changes: 1 addition & 1 deletion src/internal/task/mutex-cooperative.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore

package task

Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/mutex-preemptive.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build scheduler.threads
//go:build !tinygo.unicore

package task

Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/pmutex-cooperative.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !scheduler.threads
//go:build tinygo.unicore

package task

Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/pmutex-preemptive.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build scheduler.threads
//go:build !tinygo.unicore

package task

Expand Down
17 changes: 17 additions & 0 deletions src/internal/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,28 @@ type Task struct {
// This is needed for some crypto packages.
FipsIndicator uint8

// State of the goroutine: running, paused, or must-resume-next-pause.
// This extra field doesn't increase memory usage on 32-bit CPUs and above,
// since it falls into the padding of the FipsIndicator bit above.
RunState uint8

// DeferFrame stores a pointer to the (stack allocated) defer frame of the
// goroutine that is used for the recover builtin.
DeferFrame unsafe.Pointer
}

const (
// Initial state: the goroutine state is saved on the stack.
RunStatePaused = iota

// The goroutine is running right now.
RunStateRunning

// The goroutine is running, but already marked as "can resume".
// The next call to Pause() won't actually pause the goroutine.
RunStateResuming
)

// 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.
func (t *Task) DataUint32() uint32 {
Expand Down
40 changes: 4 additions & 36 deletions src/internal/task/task_stack.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//go:build scheduler.tasks
//go:build scheduler.tasks || scheduler.cores

package task

import (
"runtime/interrupt"
"unsafe"
)

Expand Down Expand Up @@ -32,43 +31,12 @@ type state struct {
canaryPtr *uintptr
}

// currentTask is the current running task, or nil if currently in the scheduler.
var currentTask *Task

// Current returns the current active task.
func Current() *Task {
return currentTask
}

// 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.
func Pause() {
// Check whether the canary (the lowest address of the stack) is still
// valid. If it is not, a stack overflow has occurred.
if *currentTask.state.canaryPtr != stackCanary {
runtimePanic("goroutine stack overflow")
}
if interrupt.In() {
runtimePanic("blocked inside interrupt")
}
currentTask.state.pause()
}

//export tinygo_pause
func pause() {
//export tinygo_task_exit
func taskExit() {
// TODO: explicitly free the stack after switching back to the scheduler.
Pause()
}

// Resume the task until it pauses or completes.
// This may only be called from the scheduler.
func (t *Task) Resume() {
currentTask = t
t.gcData.swap()
t.state.resume()
t.gcData.swap()
currentTask = nil
}

// initialize the state and prepare to call the specified function with the specified argument bundle.
func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
// Create a stack.
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_386.S
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ tinygo_startTask:
addl $4, %esp

// After return, exit this goroutine. This is a tail call.
jmp tinygo_pause
jmp tinygo_task_exit
.cfi_endproc

.global tinygo_swapTask
Expand Down
4 changes: 2 additions & 2 deletions src/internal/task/task_stack_amd64.S
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ tinygo_startTask:

// After return, exit this goroutine. This is a tail call.
#ifdef __MACH__
jmp _tinygo_pause
jmp _tinygo_task_exit
#else
jmp tinygo_pause
jmp tinygo_task_exit
#endif
.cfi_endproc

Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_amd64_windows.S
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ tinygo_startTask:

// After return, exit this goroutine.
// This has to be a call, not a jump, to keep the stack correctly aligned.
callq tinygo_pause
callq tinygo_task_exit

.global tinygo_swapTask
.section .text.tinygo_swapTask,"ax"
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_arm.S
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ tinygo_startTask:
blx r4

// After return, exit this goroutine. This is a tail call.
bl tinygo_pause
bl tinygo_task_exit
.cfi_endproc
.size tinygo_startTask, .-tinygo_startTask

Expand Down
4 changes: 2 additions & 2 deletions src/internal/task/task_stack_arm64.S
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ tinygo_startTask:

// After return, exit this goroutine. This is a tail call.
#ifdef __MACH__
b _tinygo_pause
b _tinygo_task_exit
#else
b tinygo_pause
b tinygo_task_exit
#endif
.cfi_endproc
#ifndef __MACH__
Expand Down
4 changes: 2 additions & 2 deletions src/internal/task/task_stack_avr.S
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ tinygo_startTask:
// Note that they will probably not be able to run more than the main
// goroutine anyway, but this file is compiled for all AVRs so it needs to
// compile at least.
rcall tinygo_pause
rcall tinygo_task_exit
#else
// Other devices can (and must) use the regular call instruction.
call tinygo_pause
call tinygo_task_exit
#endif

.global tinygo_swapTask
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_cortexm.S
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ tinygo_startTask:
blx r4

// After return, exit this goroutine. This is a tail call.
bl tinygo_pause
bl tinygo_task_exit
.cfi_endproc
.size tinygo_startTask, .-tinygo_startTask

Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_esp32.S
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ tinygo_startTask:
callx4 a3

// After return, exit this goroutine. This call never returns.
call4 tinygo_pause
call4 tinygo_task_exit

.section .text.tinygo_swapTask,"ax",@progbits
.global tinygo_swapTask
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_esp8266.S
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ tinygo_startTask:
callx0 a12

// After return, exit this goroutine. This is a tail call.
call0 tinygo_pause
call0 tinygo_task_exit
.size tinygo_startTask, .-tinygo_startTask

.global tinygo_swapTask
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_mipsx.S
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ tinygo_startTask:
nop

// After return, exit this goroutine. This is a tail call.
j tinygo_pause
j tinygo_task_exit
nop

.section .text.tinygo_swapTask
Expand Down
Loading
Loading