mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 11:07:46 +00:00
runtime: use the main (startup) stack for the main goroutine
Instead of always starting a new goroutine for the main goroutine, run
the main goroutine on the system stack.
The system stack is not occupied with scheduling, instead each goroutine
that wants to pause itself calls into the scheduler which will switch to
the next task (goroutine) to run, or sleeps.
There are various advantages of this over the previous system:
* When the program doesn't start a goroutine, the code size and RAM
consumption is close to what you'd get with `-scheduler=none`.
* When the program does start a goroutine, there is still a reduction
in RAM consumption because only one extra stack is needed.
* Because tasks directly switch to the next task to run, only a single
task switch is needed instead of two (goroutine -> scheduler ->
goroutine). This should improve task switching performance.
I kept the current behavior for WebAssembly/Asyncify. I looked into how
the same benefits can be realized for WebAssembly but couldn't easily
find how to do that. Maybe this can be done separately, or maybe we'll
just wait for the stack switching proposal to finish.
The code for Cortex-M is currently more complicated than I'd like, and
therefore can sometimes result in a slight increase in code size. I'd
like to fix this eventually but am still looking into good ways to do
this. I still think this change is generally beneficial because many
programs see big reductions in code size when compiling for Cortex-M.
This commit is contained in:
+13
-3
@@ -1228,10 +1228,15 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
|
||||
// Goroutines need to be started and finished and take up some stack space
|
||||
// that way. This can be measured by measuing the stack size of
|
||||
// tinygo_startTask.
|
||||
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
|
||||
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
|
||||
var baseStackSize uint64
|
||||
var baseStackSizeType stacksize.SizeType
|
||||
var baseStackSizeFailedAt *stacksize.CallNode
|
||||
if len(gowrappers) != 0 {
|
||||
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
|
||||
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
|
||||
}
|
||||
baseStackSize, baseStackSizeType, baseStackSizeFailedAt = functions["tinygo_startTask"][0].StackSize()
|
||||
}
|
||||
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
|
||||
|
||||
sizes := make(map[string]functionStackSize)
|
||||
|
||||
@@ -1303,6 +1308,11 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if data == nil {
|
||||
// The .tinygo_stacksizes section doesn't exist, so assume this
|
||||
// modification isn't needed.
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(stackSizeLoads)*4 != len(data) {
|
||||
// Note: while AVR should use 2 byte stack sizes, even 64-bit platforms
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileH
|
||||
|
||||
section := elfFile.Section(sectionName)
|
||||
if section == nil {
|
||||
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
|
||||
return nil, elf.FileHeader{}, nil
|
||||
}
|
||||
|
||||
data, err := section.Data()
|
||||
|
||||
@@ -99,9 +99,9 @@ func Pause() {
|
||||
//export tinygo_unwind
|
||||
func (*stackState) unwind()
|
||||
|
||||
// Resume the task until it pauses or completes.
|
||||
// Switch to this task until it pauses or completes.
|
||||
// This may only be called from the scheduler.
|
||||
func (t *Task) Resume() {
|
||||
func (t *Task) Switch() {
|
||||
// The current task must be saved and restored because this can nest on WASM with JS.
|
||||
prevTask := currentTask
|
||||
t.gcData.swap()
|
||||
@@ -121,9 +121,3 @@ func (t *Task) Resume() {
|
||||
|
||||
//export tinygo_rewind
|
||||
func (*state) rewind()
|
||||
|
||||
// OnSystemStack returns whether the caller is running on the system stack.
|
||||
func OnSystemStack() bool {
|
||||
// If there is not an active goroutine, then this must be running on the system stack.
|
||||
return Current() == nil
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
||||
|
||||
type state struct{}
|
||||
|
||||
func (t *Task) Resume() {
|
||||
func (t *Task) Switch() {
|
||||
runtimePanic("scheduler is disabled")
|
||||
}
|
||||
|
||||
// OnSystemStack returns whether the caller is running on the system stack.
|
||||
func OnSystemStack() bool {
|
||||
// MainTask returns whether the caller is running in the main goroutine.
|
||||
func MainTask() bool {
|
||||
// This scheduler does not do any stack switching.
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -27,8 +27,12 @@ type state struct {
|
||||
canaryPtr *uintptr
|
||||
}
|
||||
|
||||
// currentTask is the current running task, or nil if currently in the scheduler.
|
||||
var currentTask *Task
|
||||
// The task struct for the main goroutine.
|
||||
var mainTask Task
|
||||
|
||||
// currentTask is the current running task. The default value is the main
|
||||
// goroutine.
|
||||
var currentTask = &mainTask
|
||||
|
||||
// Current returns the current active task.
|
||||
func Current() *Task {
|
||||
@@ -36,29 +40,36 @@ func Current() *Task {
|
||||
}
|
||||
|
||||
// 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 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 occured.
|
||||
if *currentTask.state.canaryPtr != stackCanary {
|
||||
if currentTask.state.canaryPtr != nil && *currentTask.state.canaryPtr != stackCanary {
|
||||
runtimePanic("goroutine stack overflow")
|
||||
}
|
||||
currentTask.state.pause()
|
||||
scheduler()
|
||||
}
|
||||
|
||||
//go:linkname scheduler runtime.scheduler
|
||||
func scheduler()
|
||||
|
||||
//export tinygo_pause
|
||||
func pause() {
|
||||
Pause()
|
||||
}
|
||||
|
||||
// Resume the task until it pauses or completes.
|
||||
// Switch to the given task until it pauses or completes.
|
||||
// This may only be called from the scheduler.
|
||||
func (t *Task) Resume() {
|
||||
func (t *Task) Switch() {
|
||||
current := currentTask
|
||||
if current == t {
|
||||
// Nothing to switch to: we're already in this task.
|
||||
return
|
||||
}
|
||||
currentTask = t
|
||||
t.gcData.swap()
|
||||
t.state.resume()
|
||||
t.state.switchTo(current)
|
||||
t.gcData.swap()
|
||||
currentTask = nil
|
||||
}
|
||||
|
||||
// initialize the state and prepare to call the specified function with the specified argument bundle.
|
||||
@@ -103,8 +114,8 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
|
||||
// OnSystemStack returns whether the caller is running on the system stack.
|
||||
func OnSystemStack() bool {
|
||||
// MainTask returns whether the caller is running in the main goroutine.
|
||||
func MainTask() bool {
|
||||
// If there is not an active goroutine, then this must be running on the system stack.
|
||||
return Current() == nil
|
||||
return currentTask == &mainTask
|
||||
}
|
||||
|
||||
@@ -20,11 +20,8 @@ tinygo_startTask:
|
||||
// Branch to the "goroutine start" function.
|
||||
calll *%ebx
|
||||
|
||||
// Rebalance the stack (to undo the above push).
|
||||
addl $4, %esp
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
jmp tinygo_pause
|
||||
// After return, exit this goroutine.
|
||||
calll tinygo_pause
|
||||
.cfi_endproc
|
||||
|
||||
.global tinygo_swapTask
|
||||
|
||||
@@ -5,8 +5,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_386.S that relies on the exact
|
||||
// layout of this struct.
|
||||
@@ -46,18 +44,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.esi = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ tinygo_startTask:
|
||||
// Branch to the "goroutine start" function.
|
||||
callq *%r12
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
// After return, exit this goroutine.
|
||||
#ifdef __MACH__
|
||||
jmp _tinygo_pause
|
||||
callq _tinygo_pause
|
||||
#else
|
||||
jmp tinygo_pause
|
||||
callq tinygo_pause
|
||||
#endif
|
||||
.cfi_endproc
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_amd64.S that relies on the exact
|
||||
// layout of this struct.
|
||||
@@ -45,18 +43,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.r13 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ tinygo_startTask:
|
||||
// Branch to the "goroutine start" function.
|
||||
callq *%r12
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
jmp tinygo_pause
|
||||
// After return, exit this goroutine.
|
||||
callq tinygo_pause
|
||||
|
||||
.global tinygo_swapTask
|
||||
.section .text.tinygo_swapTask,"ax"
|
||||
|
||||
@@ -8,8 +8,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_amd64_windows.S that relies on
|
||||
// the exact layout of this struct.
|
||||
@@ -50,18 +48,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.r13 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_arm.S that relies on the exact
|
||||
// layout of this struct.
|
||||
@@ -45,18 +43,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.r5 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_arm64.S that relies on the exact
|
||||
// layout of this struct.
|
||||
@@ -48,18 +46,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.x20 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
.section .bss.tinygo_systemStack
|
||||
.global tinygo_systemStack
|
||||
.type tinygo_systemStack, %object
|
||||
tinygo_systemStack:
|
||||
.short 0
|
||||
|
||||
.section .text.tinygo_startTask
|
||||
.global tinygo_startTask
|
||||
.type tinygo_startTask, %function
|
||||
|
||||
@@ -5,9 +5,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
//go:extern tinygo_systemStack
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_avr.S that relies on the exact
|
||||
// layout of this struct.
|
||||
@@ -51,18 +48,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.r4r5 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
//go:build scheduler.tasks && baremetal
|
||||
// +build scheduler.tasks,baremetal
|
||||
|
||||
package task
|
||||
|
||||
//go:extern _stack_bottom
|
||||
var _stack_bottom uintptr
|
||||
|
||||
func init() {
|
||||
mainTask.state.canaryPtr = &_stack_bottom
|
||||
*mainTask.state.canaryPtr = stackCanary
|
||||
}
|
||||
@@ -30,10 +30,10 @@ tinygo_startTask:
|
||||
.cfi_endproc
|
||||
.size tinygo_startTask, .-tinygo_startTask
|
||||
|
||||
.section .text.tinygo_switchToScheduler
|
||||
.global tinygo_switchToScheduler
|
||||
.type tinygo_switchToScheduler, %function
|
||||
tinygo_switchToScheduler:
|
||||
.section .text.tinygo_switchToMain
|
||||
.global tinygo_switchToMain
|
||||
.type tinygo_switchToMain, %function
|
||||
tinygo_switchToMain:
|
||||
.cfi_startproc
|
||||
// r0 = sp *uintptr
|
||||
|
||||
@@ -43,9 +43,9 @@ tinygo_switchToScheduler:
|
||||
subs r1, #36
|
||||
str r1, [r0]
|
||||
|
||||
b tinygo_swapTask
|
||||
b tinygo_swapTaskInternal
|
||||
.cfi_endproc
|
||||
.size tinygo_switchToScheduler, .-tinygo_switchToScheduler
|
||||
.size tinygo_switchToMain, .-tinygo_switchToMain
|
||||
|
||||
.section .text.tinygo_switchToTask
|
||||
.global tinygo_switchToTask
|
||||
@@ -55,17 +55,17 @@ tinygo_switchToTask:
|
||||
// r0 = sp uintptr
|
||||
|
||||
// Currently on the scheduler stack (SP=MSP). We'll have to update the PSP,
|
||||
// and then we can invoke swapTask.
|
||||
// and then we can invoke swapTaskInternal.
|
||||
msr PSP, r0
|
||||
|
||||
b.n tinygo_swapTask
|
||||
b.n tinygo_swapTaskInternal
|
||||
.cfi_endproc
|
||||
.size tinygo_switchToTask, .-tinygo_switchToTask
|
||||
|
||||
.section .text.tinygo_swapTask
|
||||
.global tinygo_swapTask
|
||||
.type tinygo_swapTask, %function
|
||||
tinygo_swapTask:
|
||||
.section .text.tinygo_swapTaskInternal
|
||||
.global tinygo_swapTaskInternal
|
||||
.type tinygo_swapTaskInternal, %function
|
||||
tinygo_swapTaskInternal:
|
||||
.cfi_startproc
|
||||
// This function stores the current register state to the stack, switches to
|
||||
// the other stack (MSP/PSP), and loads the register state from the other
|
||||
@@ -77,15 +77,15 @@ tinygo_swapTask:
|
||||
// used directly. Only very few operations work on them, such as mov. That's
|
||||
// why the higher register values are first stored in the temporary register
|
||||
// r3 when loading/storing them.
|
||||
// It is possible to reduce the swapTask by two instructions (~2 cycles) on
|
||||
// Cortex-M0 by reordering the layout of the pushed registers from {r4-r11,
|
||||
// lr} to {r8-r11, r4-r8, lr}. However, that also requires a change on the
|
||||
// Go side (depending on thumb1/thumb2!) and so is not really worth the
|
||||
// complexity.
|
||||
// It is possible to reduce the swapTaskInternal by two instructions (~2
|
||||
// cycles) on Cortex-M0 by reordering the layout of the pushed registers
|
||||
// from {r4-r11, lr} to {r8-r11, r4-r8, lr}. However, that also requires a
|
||||
// change on the Go side (depending on thumb1/thumb2!) and so is not really
|
||||
// worth the complexity.
|
||||
|
||||
// Store state to old task. It saves the lr instead of the pc, because that
|
||||
// will be the pc after returning back to the old task (in a different
|
||||
// invocation of swapTask).
|
||||
// invocation of swapTaskInternal).
|
||||
#if defined(__thumb2__)
|
||||
push {r4-r11, lr}
|
||||
.cfi_def_cfa_offset 9*4
|
||||
@@ -108,6 +108,63 @@ tinygo_swapTask:
|
||||
msr CONTROL, r0 // store CONTROL register
|
||||
isb // required to flush the pipeline
|
||||
|
||||
// Load state from new task and branch to the previous position in the
|
||||
// program.
|
||||
#if defined(__thumb2__)
|
||||
pop {r4-r11, pc}
|
||||
#else
|
||||
pop {r4-r7}
|
||||
.cfi_def_cfa_offset 5*9
|
||||
pop {r0-r3}
|
||||
.cfi_def_cfa_offset 1*9
|
||||
mov r8, r0
|
||||
mov r9, r1
|
||||
mov r10, r2
|
||||
mov r11, r3
|
||||
pop {pc}
|
||||
#endif
|
||||
.cfi_endproc
|
||||
.size tinygo_swapTaskInternal, .-tinygo_swapTaskInternal
|
||||
|
||||
|
||||
.section .text.tinygo_swapTask
|
||||
.global tinygo_swapTask
|
||||
.type tinygo_swapTask, %function
|
||||
tinygo_swapTask:
|
||||
.cfi_startproc
|
||||
// This function is like tinygo_swapTaskInternal but only switches the stack
|
||||
// pointer without touching the SPSEL bit (to switch between MSP and PSP).
|
||||
// Therefore, it is used to switch between two tasks that are not the main
|
||||
// goroutine.
|
||||
|
||||
// Store state to old task. It saves the lr instead of the pc, because that
|
||||
// will be the pc after returning back to the old task (in a different
|
||||
// invocation of swapTask).
|
||||
#if defined(__thumb2__)
|
||||
push {r4-r11, lr}
|
||||
.cfi_def_cfa_offset 9*4
|
||||
#else
|
||||
mov r0, r8
|
||||
mov r1, r9
|
||||
mov r2, r10
|
||||
mov r3, r11
|
||||
push {r0-r3, lr}
|
||||
.cfi_def_cfa_offset 5*4
|
||||
push {r4-r7}
|
||||
.cfi_def_cfa_offset 9*4
|
||||
#endif
|
||||
|
||||
// Save the current stack pointer in oldStack.
|
||||
#if defined(__thumb2__)
|
||||
str sp, [r1]
|
||||
#else
|
||||
mov r2, sp
|
||||
str r2, [r1]
|
||||
#endif
|
||||
|
||||
// Switch to the new stack pointer.
|
||||
mov sp, r0
|
||||
|
||||
// Load state from new task and branch to the previous position in the
|
||||
// program.
|
||||
#if defined(__thumb2__)
|
||||
|
||||
@@ -52,19 +52,23 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.r5 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
switchToTask(s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
// This code is probably more complex than it needs to be.
|
||||
// TODO: simplify this, ideally to a single function call.
|
||||
if s == &mainTask.state {
|
||||
switchToMain(¤t.state.sp)
|
||||
} else if current == &mainTask {
|
||||
switchToTask(s.sp)
|
||||
} else {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
}
|
||||
|
||||
//export tinygo_switchToTask
|
||||
func switchToTask(uintptr)
|
||||
|
||||
//export tinygo_switchToScheduler
|
||||
func switchToScheduler(*uintptr)
|
||||
|
||||
func (s *state) pause() {
|
||||
switchToScheduler(&s.sp)
|
||||
}
|
||||
//export tinygo_switchToMain
|
||||
func switchToMain(*uintptr)
|
||||
|
||||
// SystemStack returns the system stack pointer. On Cortex-M, it is always
|
||||
// available.
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_esp8266.S that relies on the
|
||||
// exact layout of this struct.
|
||||
@@ -60,18 +58,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.a2 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see task_stack_esp8266.S that relies on the
|
||||
// exact layout of this struct.
|
||||
@@ -54,18 +52,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.a13 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ package task
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var systemStack uintptr
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see scheduler_riscv.S that relies on the
|
||||
// exact layout of this struct.
|
||||
@@ -50,18 +48,12 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||
r.s1 = uintptr(args)
|
||||
}
|
||||
|
||||
func (s *state) resume() {
|
||||
swapTask(s.sp, &systemStack)
|
||||
}
|
||||
|
||||
func (s *state) pause() {
|
||||
newStack := systemStack
|
||||
systemStack = 0
|
||||
swapTask(newStack, &s.sp)
|
||||
func (s *state) switchTo(current *Task) {
|
||||
swapTask(s.sp, ¤t.state.sp)
|
||||
}
|
||||
|
||||
// SystemStack returns the system stack pointer when called from a task stack.
|
||||
// When called from the system stack, it returns 0.
|
||||
func SystemStack() uintptr {
|
||||
return systemStack
|
||||
return mainTask.state.sp
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func markStack() {
|
||||
// Scan the current stack, and all current registers.
|
||||
scanCurrentStack()
|
||||
|
||||
if !task.OnSystemStack() {
|
||||
if !task.MainTask() {
|
||||
// Mark system stack.
|
||||
markRoots(getSystemStackPointer(), stackTop)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func scanstack(sp uintptr) {
|
||||
// Mark current stack.
|
||||
// This function is called by scanCurrentStack, after pushing all registers onto the stack.
|
||||
// Callee-saved registers have been pushed onto stack by tinygo_localscan, so this will scan them too.
|
||||
if task.OnSystemStack() {
|
||||
if task.MainTask() {
|
||||
// This is the system stack.
|
||||
// Scan all words on the stack.
|
||||
markRoots(sp, stackTop)
|
||||
|
||||
@@ -112,9 +112,7 @@ func addSleepTask(t *task.Task, duration timeUnit) {
|
||||
*q = t
|
||||
}
|
||||
|
||||
// Run the scheduler until all tasks have finished.
|
||||
func scheduler() {
|
||||
// Main scheduler loop.
|
||||
var now timeUnit
|
||||
for !schedulerDone {
|
||||
scheduleLog("")
|
||||
@@ -164,7 +162,11 @@ func scheduler() {
|
||||
|
||||
// Run the given task.
|
||||
scheduleLogTask(" run:", t)
|
||||
t.Resume()
|
||||
t.Switch()
|
||||
if GOARCH != "wasm" {
|
||||
// Don't return when using Asyncify.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +183,7 @@ func minSched() {
|
||||
}
|
||||
|
||||
scheduleLogTask(" run:", t)
|
||||
t.Resume()
|
||||
t.Switch()
|
||||
}
|
||||
scheduleLog("stop nested scheduler")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build !scheduler.none
|
||||
// +build !scheduler.none
|
||||
//go:build scheduler.asyncify
|
||||
// +build scheduler.asyncify
|
||||
|
||||
package runtime
|
||||
|
||||
@@ -16,3 +16,27 @@ func getSystemStackPointer() uintptr {
|
||||
}
|
||||
return sp
|
||||
}
|
||||
|
||||
// Pause the current task for a given time.
|
||||
//
|
||||
//go:linkname sleep time.Sleep
|
||||
func sleep(duration int64) {
|
||||
if duration <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
addSleepTask(task.Current(), nanosecondsToTicks(duration))
|
||||
task.Pause()
|
||||
}
|
||||
|
||||
// run is called by the program entry point to execute the go program.
|
||||
// The main goroutine runs on the system stack, so initAll and callMain can be
|
||||
// called directly.
|
||||
func run() {
|
||||
initHeap()
|
||||
initAll()
|
||||
callMain()
|
||||
schedulerDone = true
|
||||
}
|
||||
|
||||
const hasScheduler = true
|
||||
|
||||
@@ -27,6 +27,7 @@ SECTIONS
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_stack_bottom = .;
|
||||
. += _stack_size;
|
||||
_stack_top = .;
|
||||
} >RAM
|
||||
|
||||
@@ -19,6 +19,7 @@ SECTIONS
|
||||
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
_stack_bottom = .;
|
||||
. += _stack_size;
|
||||
_stack_top = .;
|
||||
} >RAM
|
||||
|
||||
@@ -41,6 +41,7 @@ SECTIONS
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
. = ALIGN(16);
|
||||
_stack_bottom = .;
|
||||
. += _stack_size;
|
||||
_stack_top = .;
|
||||
} >DRAM
|
||||
|
||||
@@ -70,6 +70,7 @@ SECTIONS
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
. = ALIGN(16);
|
||||
_stack_bottom = .;
|
||||
. += _stack_size;
|
||||
_stack_top = .;
|
||||
} >DRAM
|
||||
|
||||
@@ -67,6 +67,11 @@ _heap_end = ORIGIN(DRAM) + LENGTH(DRAM);
|
||||
*/
|
||||
_stack_top = 0x40000000;
|
||||
|
||||
/* This is the stack bottom according to this forum post:
|
||||
* https://bbs.espressif.com/viewtopic.php?t=8879#p19038
|
||||
*/
|
||||
_stack_bottom = 0x3fffeb2c;
|
||||
|
||||
/* Functions normally provided by a libc.
|
||||
* Source:
|
||||
* https://github.com/espressif/ESP8266_NONOS_SDK/blob/master/ld/eagle.rom.addr.v6.ld
|
||||
|
||||
@@ -57,6 +57,7 @@ SECTIONS
|
||||
}
|
||||
|
||||
PROVIDE(_stack_top = ORIGIN(RAM) + LENGTH(RAM));
|
||||
_stack_bottom = _stack_top - _stack_size;
|
||||
|
||||
/* For the memory allocator. */
|
||||
_heap_start = _ebss;
|
||||
|
||||
@@ -59,6 +59,7 @@ SECTIONS
|
||||
.stack (NOLOAD) : {
|
||||
|
||||
. = ALIGN(8);
|
||||
_stack_bottom = .;
|
||||
. += _stack_size;
|
||||
_stack_top = .;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ SECTIONS
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
. = ALIGN(16);
|
||||
_stack_bottom = .;
|
||||
. += _stack_size;
|
||||
_stack_top = .;
|
||||
} >RAM
|
||||
|
||||
Reference in New Issue
Block a user