@@ -1352,6 +1352,14 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
13521352 }
13531353 baseStackSize , baseStackSizeType , baseStackSizeFailedAt := functions ["tinygo_startTask" ][0 ].StackSize ()
13541354
1355+ // Account for the bytes that tinygo_swapTask pushes onto the goroutine stack
1356+ // on every context switch. The static analysis correctly traces Go calls,
1357+ // but it cannot see into the assembly-level register push.
1358+ var contextSwitchOverhead uint64
1359+ if swapFuncs , ok := functions ["tinygo_swapTask" ]; ok && len (swapFuncs ) == 1 {
1360+ contextSwitchOverhead = swapFuncs [0 ].FrameSize
1361+ }
1362+
13551363 sizes := make (map [string ]functionStackSize )
13561364
13571365 // 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
14001408 // overflow will occur even before the goroutine is started.
14011409 stackSize = baseStackSize
14021410 }
1411+ if stackSizeType == stacksize .Bounded {
1412+ // Add the overhead of context switching. This is needed because the
1413+ // context switch (tinygo_swapTask) pushes callee-saved registers
1414+ // onto the current stack, which is not seen by the static analysis.
1415+ stackSize += contextSwitchOverhead
1416+ }
14031417 sizes [name ] = functionStackSize {
14041418 stackSize : stackSize ,
14051419 stackSizeType : stackSizeType ,
0 commit comments