Description
Here is a snippet from the Semantics document:
Stack Overflow
Call stack space is limited by unspecified and dynamically varying constraints and is a source of nondeterminism. If program call stack usage exceeds the available call stack space at any time, the execution in the WebAssembly instance is terminated and abnormal termination is reported to the outside environment.
Implementations must have an internal maximum call stack size, and every call must take up some resources toward exhausting that size (of course, dynamic resources may be exhausted much earlier). This rule exists to avoid differences in observable behavior; if some implementations have this property and others don’t, the same program which runs successfully on some implementations may consume unbounded resources and fail on others. Also, in the future, it is expected that WebAssembly will add some form of stack-introspection functionality, in which case such optimizations would be directly observable.
Support for explicit tail calls is planned in the future 🦄, which would add an explicit tail-call operator with well-defined effects on stack introspection.
I am the creator of Zig, an LLVM-based programming language which supports WebAssembly as one of many architecture backends.
In Zig we take stack overflow very seriously. In fact, recursion is a compile error unless you use a builtin function @newStackCall
in order to make the recursive call on an explicitly allocated stack. Combining this with determining stack upper bound size at compile-time, Zig eliminates the possibility of stack overflow.
For most architectures, this works with the following LLVM IR code:
%6 = call i8* @llvm.stacksave(), !dbg !45
call void @llvm.write_register.i64(metadata !47, i64 %5), !dbg !45
call fastcc void @foo(), !dbg !45
call void @llvm.stackrestore(i8* %6), !dbg !45
...
!47 = !{!"rsp\00"}
That is, it modifies the stack pointer register to point to the new stack memory, makes the function call, and then restores the stack pointer register.
This proposal is to support these semantics, in one of 2 ways:
- Allow modification of the stack pointer so that the LLVM IR above can lower to WebAssembly.
- Support a builtin function directly which performs a function call using an explicitly provided memory buffer as the stack.