call_host and the host branch of call_indirect got the trampoline's
Result<StackGuard, Error> back through a stack slot in the handler's own
frame. That slot's address escapes into the call, so LLVM could not turn
the handler's final call to the next instruction into a sibling call, and
every host call left a frame behind, roughly 150-200 bytes each (Apple
M5 Max). A module that calls a host function in a loop overflowed an
8 MiB thread after 40,000-60,000 calls and aborted the process; calls
between wasm functions were not affected.
The host call now happens in an out-of-line helper, so the slot lives in
the helper's frame. A million host calls run on a 1 MiB stack, and a host
call is faster: about 22 ns before, 10 ns after, in a release build.
tests/host_calls.rs loops a million direct and indirect host calls on a
1 MiB thread. Without the fix both abort with a stack overflow, in debug
and release builds.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>