macovedj opened PR #14180 from macovedj:winch-catch-exceptions to bytecodealliance:main:
Adds support for catching exceptions in Winch.
Winch tracks exception handlers in scope, records them in call-site metadata, and emits landing pads when atry_tableends. When Wasmtime’s unwinder selects a handler, its landing pad restores the expected stack state andVMContext.
Support forcatch_refandcatch_all_refis not yet implemented and will be added in follow-up work
macovedj requested cfallin for a review on PR #14180.
macovedj requested wasmtime-compiler-reviewers for a review on PR #14180.
macovedj requested wasmtime-core-reviewers for a review on PR #14180.
github-actions[bot] added the label cranelift on PR #14180.
github-actions[bot] added the label winch on PR #14180.
github-actions[bot] commented on PR #14180:
Subscribe to Label Action
cc @saulecabrera
<details>
This issue or pull request has been labeled: "cranelift", "winch"Thus the following users have been cc'd because of the following labels:
- saulecabrera: winch
To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.
Learn more.
</details>
:memo: cfallin submitted PR review:
Thanks -- this looks pretty much OK to me (and the handler stack with checkpoints appears to be using the same approach that we have in the Wasm-to-CLIF translator, which is good). A few questions around the libcalls/barriers and register management below. It might also be good to get a second pair of eyes (@saulecabrera maybe?) to ensure that all of that is done properly.
:speech_balloon: cfallin created PR review comment:
It seems that we have logic to re-compute
object_addr(field_base) every iteration if invalidated. A few thoughts:
- Do we need to initialize
object_addrabove (line 178) if we're going to lazily recompute it ifNonein each iteration? In other words wouldNoneas an initializer work?- Zooming out, why are we recomputing it? It seems that below we set it to
Nonewhen a call (DRC barrier) clobbers registers, but we save the other registers; why do we need to re-deriveobject_addrfrom scratch? Does the read barrier have the option to relocate the heap or similar?- All of these manual register save/restore sequences make me a little squeamish. Do we not have an abstraction to save active registers and restore them around calls?
saulecabrera commented on PR #14180:
It might also be good to get a second pair of eyes (@saulecabrera maybe?) to ensure that all of that is done properly.
I will take a look; thanks!
macovedj updated PR #14180.
macovedj updated PR #14180.
:memo: macovedj submitted PR review.
:speech_balloon: macovedj created PR review comment:
Thanks for pointing this out. On revisiting it, I realized the recomputation was there because the barrier was responsible for freeing the
storage_baseregister. I changed the barrier to operate on an already-loaded GC reference, leaving responsibility for freeing thestorage_basewith the caller. This lets the exception path preserve the already-computedobject_addrrather than recomputing it.I'm not aware of a mechanism available other than the value stack for managing the registers, but these changes simplify the sequence by preserving the already-computed
object_addrinstead of preservingexception_regand later reloading the heap metadata to reconstruct the address. If @saulecabrera has any thoughts about some potential helpers that could assist, I'm open to suggestions. Otherwise, the simplified barrier contract may be sufficient.
:memo: saulecabrera submitted PR review:
With this branch, this test is failing for me:
(module (tag $e (param i32)) (func $callee (result i32) (i32.const 9)) (func (export "f") (result i32) (block $h (result i32) (try_table (result i32 i32 i32 i32 i32 i32) (catch $e $h) (i32.const 1) (i32.const 2) (i32.const 3) (i32.const 4) (i32.const 5) (call $callee)) drop drop drop drop drop)))$ wasmtime compile -W exceptions -C compiler=winch -C collector=copying -o out.cwasm repro.wat Error: failed to compile: wasm[0]::function[1] Caused by: Compilation error: Winch internal error: Invalid stack pointer offsetSorry it took me a bit to review, I was trying to see if I could pinpoint exactly where the failure is, but it is probably faster for you.
The symptom is probably related to how the fall-through case is handled though; we need to be very careful when manipulating the sp in the Masm, as it _always_ needs to be in sync with the machine's
:speech_balloon: saulecabrera created PR review comment:
Can we allocate this register inside this function? It's generally easier to reason about register allocation when scoped to a particular function and avoids unwanted clobbering.
saulecabrera commented on PR #14180:
Pressed enter too fast, could we also:
- Add tests for landing pads in aarch64
- Add tests with the null collector for caught payloads
- Add tests for blocks with multi-value
macovedj updated PR #14180.
macovedj commented on PR #14180:
Thanks for the review! So the previous condition missed that emitting the landing pads changed the tracked stack pointer. We should just ensure stack state regardless of fallthrough reachability for reachable ends. I also added a stack pointer check after popping ABIresults for good measure. The existing disas for x64 is also covered by aarch64, but I added some additional landing pad disas for both architectures. If there are any other cases you had in mind that I missed, let me know!
:thumbs_up: saulecabrera submitted PR review:
LGTM, thanks.
:thumbs_up: saulecabrera submitted PR review.
saulecabrera added PR #14180 winch: catch exceptions to the merge queue.
github-merge-queue[bot] removed PR #14180 winch: catch exceptions from the merge queue.
saulecabrera added PR #14180 winch: catch exceptions to the merge queue.
:check: saulecabrera merged PR #14180.
saulecabrera removed PR #14180 winch: catch exceptions from the merge queue.
Last updated: Aug 30 2026 at 09:07 UTC