Compare commits

...

1 Commits

Author SHA1 Message Date
Ayke van Laethem 907d90105a 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.
2022-07-30 01:57:45 +02:00
33 changed files with 221 additions and 177 deletions
+13 -3
View File
@@ -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
View File
@@ -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()
+2 -8
View File
@@ -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
}
+3 -3
View File
@@ -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
}
+23 -12
View File
@@ -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
}
+2 -5
View File
@@ -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
+3 -11
View File
@@ -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, &current.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
}
+3 -3
View File
@@ -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
+3 -11
View File
@@ -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, &current.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
}
+2 -2
View File
@@ -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"
+3 -11
View File
@@ -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, &current.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
}
+3 -11
View File
@@ -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, &current.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
}
+3 -11
View File
@@ -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, &current.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
}
-6
View File
@@ -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
+3 -12
View File
@@ -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, &current.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
}
+12
View File
@@ -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
}
+75 -18
View File
@@ -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__)
+12 -8
View File
@@ -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(&current.state.sp)
} else if current == &mainTask {
switchToTask(s.sp)
} else {
swapTask(s.sp, &current.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.
+3 -11
View File
@@ -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, &current.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
}
+3 -11
View File
@@ -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, &current.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
}
+3 -11
View File
@@ -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, &current.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
}
+2 -2
View File
@@ -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)
+6 -4
View File
@@ -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
+24
View File
@@ -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
+1
View File
@@ -27,6 +27,7 @@ SECTIONS
.stack (NOLOAD) :
{
. = ALIGN(4);
_stack_bottom = .;
. += _stack_size;
_stack_top = .;
} >RAM
+1
View File
@@ -19,6 +19,7 @@ SECTIONS
.stack (NOLOAD) :
{
_stack_bottom = .;
. += _stack_size;
_stack_top = .;
} >RAM
+1
View File
@@ -41,6 +41,7 @@ SECTIONS
.stack (NOLOAD) :
{
. = ALIGN(16);
_stack_bottom = .;
. += _stack_size;
_stack_top = .;
} >DRAM
+1
View File
@@ -70,6 +70,7 @@ SECTIONS
.stack (NOLOAD) :
{
. = ALIGN(16);
_stack_bottom = .;
. += _stack_size;
_stack_top = .;
} >DRAM
+5
View File
@@ -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
+1
View File
@@ -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;
+1
View File
@@ -59,6 +59,7 @@ SECTIONS
.stack (NOLOAD) : {
. = ALIGN(8);
_stack_bottom = .;
. += _stack_size;
_stack_top = .;
+1
View File
@@ -19,6 +19,7 @@ SECTIONS
.stack (NOLOAD) :
{
. = ALIGN(16);
_stack_bottom = .;
. += _stack_size;
_stack_top = .;
} >RAM