diff --git a/builder/build.go b/builder/build.go index 325f6b4906..88ce6ed485 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1352,6 +1352,14 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri } baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize() + // Account for the bytes that tinygo_swapTask pushes onto the goroutine stack + // on every context switch. The static analysis correctly traces Go calls, + // but it cannot see into the assembly-level register push. + var contextSwitchOverhead uint64 + if swapFuncs, ok := functions["tinygo_swapTask"]; ok && len(swapFuncs) == 1 { + contextSwitchOverhead = swapFuncs[0].FrameSize + } + sizes := make(map[string]functionStackSize) // Add the reset handler function, for convenience. The reset handler runs @@ -1400,6 +1408,12 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri // overflow will occur even before the goroutine is started. stackSize = baseStackSize } + if stackSizeType == stacksize.Bounded { + // Add the overhead of context switching. This is needed because the + // context switch (tinygo_swapTask) pushes callee-saved registers + // onto the current stack, which is not seen by the static analysis. + stackSize += contextSwitchOverhead + } sizes[name] = functionStackSize{ stackSize: stackSize, stackSizeType: stackSizeType,