cfallin opened PR #11500 from cfallin:exception-sp-from-fp-with-offset to bytecodealliance:main:
Currently Wasmtime unwinds stack frames to look for exception handlers by walking frames one-by-one, following the FP chain as usual, and assuming that these frames are contiguous: that is, that the SP in any given frame (bottom of that frame) is immediately above the FP of the next lower frame, plus the FP/return address pair (e.g. 16 bytes). This allows us to get the SP for any given frame in addition to FP. We need SP for two reasons:
- To look up dynamic context, to match Wasm tag instances for handlers against the thrown tag;
- To actually set SP when we resume, if we do resume to a handler in this frame.
This logic almost but not quite worked: I had forgotten that in our tail-call ABI, we need to clean up incoming stack args in the callee (because only the final callee in a parade of tail-calling functions that reuse the same stack frame location knows how many args it has, not the original caller). This implies that there is an "incoming args area" above the FP/return address pair. Thus, frames are not necessarily contiguous by the above definition.
In #11489 we see a case where a function of signature
(func)tail-calls one of(func (param i32 i32 i32 i32 i32)), which on x86-64 (with four arg registers left for Wasm) is sufficient to create incoming stack args, which then trips up the unwinder, reading a bogus vmctx and segfaulting.The most reasonable solution seems to be to embed the SP-to-FP offset in the exception metadata itself, so from only the FP (which is totally robust -- we rely on the FP chain for multiple kinds of stack-walking) we can get the SP, allowing us to read dynamic context and to reset SP during resume.
This PR does just that. Technically, in our ABI, the SP-to-FP offset is constant for an entire function, but it was simpler in the exception metadata to encode this per callsite instead (there is no other notion of "per-function" data, only "per-callsite", so it would be a separate binary search).
Fixes #11489.
prtest:full
<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
cfallin requested alexcrichton for a review on PR #11500.
cfallin requested wasmtime-compiler-reviewers for a review on PR #11500.
cfallin requested wasmtime-core-reviewers for a review on PR #11500.
alexcrichton submitted PR review.
alexcrichton created PR review comment:
Could this (and below) expand documentation to explain what
Nonemeans?
alexcrichton created PR review comment:
Would it be worth it to add a disas test for
$throwto ensure that it's handling the offset correctly?
alexcrichton created PR review comment:
Could this have a comment as to why
unwrap_oris ok here? Or otherwise, ifNoneis the same as 0 should this be represented as a u32 instead of aOption<u32>?
cfallin updated PR #11500.
cfallin updated PR #11500.
cfallin updated PR #11500.
cfallin submitted PR review.
cfallin created PR review comment:
The interesting new bit is actually the offset at the
try_tablesite (callsite in$start), which is the same as the case inexceptions.wat, so I think we're covered already!
cfallin submitted PR review.
cfallin created PR review comment:
Updated to explicitly
.expect()only when there are handlers. The subtlety is thatframe_offsetwill beNoneif there are no handlers because we don't encode any metadata in that case.
cfallin submitted PR review.
cfallin created PR review comment:
Updated!
alexcrichton has enabled auto merge for PR #11500.
alexcrichton merged PR #11500.
Last updated: Dec 06 2025 at 06:05 UTC