[Wasm RyuJit] EH, Unwind, and unwindable frames#127043
[Wasm RyuJit] EH, Unwind, and unwindable frames#127043AndyAyersMS wants to merge 3 commits intodotnet:mainfrom
Conversation
Implement the codegen changes necessary for EH and unwind reporting and for making frames unwindable by the runtime: * Add a new phase that assigns Virtual IP ranges to each try, handler, and filter, and a distinct Virtual IP for each call. Call Virtual IPs lie within the range of their enclosing EH region. Region ranges lie within the range of their enclosing region. * Add codegen to the method to ensure the Virtual IP slot is at the bottom of the fixed part of the stack frame, and is updated before each call. * Adjust the localloc codegen so that for variable-sized frames there is a special marker at the bottom of the stack, and a pointer to the bottom of the fixed portion of the stack just above this. * Report unwind data to the host. For Wasm this is just the size of the fixed portion of the frame. * Report EH info to the host. For Wasm these are the Virtual IP ranges for the EH regions as noted above. * Change throw helpers so there is one per EH region (instead of one per EH handler region), so the throw helper calls have a well defined Virtual IP. * Catch funclets now return a value to the runtime, though not the actual resume IP (resume IPs are in a different "IP space" than the virtual IP, and are not meaningful except to codegen). We are currently somewhat generous in handing out Virtual IPs; likely we could compress their range a bit more. Still to do: * Use the Virtual IPs for GC reporting * Host side handling (in particular funclet extraction) * Runtime stack walking and EH processing.
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
FYI @dotnet/wasm-contrib See the revised unwind proposal here: #121651 (comment) |
There was a problem hiding this comment.
Pull request overview
This PR extends the CoreCLR JIT’s Wasm backend to support unwind reporting and EH region mapping using a “Virtual IP” scheme, laying groundwork for runtime stack walking/EH processing on Wasm.
Changes:
- Add a new Wasm JIT phase to assign Virtual IP ranges to EH regions and a distinct Virtual IP at each call site, and inject code to update the Virtual IP prior to calls.
- Track and report per-func (root + funclets) Wasm unwind info (currently: fixed frame size) and report EH clause data to the VM for Wasm.
- Update Wasm frame layout/codegen to place new unwind/EH-related slots at stable positions and adjust localloc handling to keep variable-sized frames unwindable.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/jit/unwindwasm.cpp | Implement Wasm unwind reservation/emission and track per-func frame size. |
| src/coreclr/jit/lclvars.cpp | Ensure Wasm VirtualIP/ResumeIP/FunctionIndex locals are allocated last in the frame layout; add frame diagrams. |
| src/coreclr/jit/jit.h | Include win64unwind.h on TARGET_WASM to get UNWIND_INFO definition. |
| src/coreclr/jit/flowgraph.cpp | Adjust throw helper placement logic for Wasm to allow try-region association. |
| src/coreclr/jit/fgwasm.h | Update Wasm successor enumeration to treat try-beg blocks as region entry points. |
| src/coreclr/jit/fgwasm.cpp | Add fgWasmVirtualIP phase: compute EH Virtual IP ranges, inject Virtual IP stores before calls, mark funcs needing unwindable frames. |
| src/coreclr/jit/compphases.h | Add PHASE_WASM_VIRTUAL_IP. |
| src/coreclr/jit/compmemkind.h | Add WasmEH allocator kind used for EH clause info storage. |
| src/coreclr/jit/compiler.h | Add Wasm-specific locals + per-func fields (frame size, unwindable flag) and store fgWasmEHInfo. |
| src/coreclr/jit/compiler.cpp | Schedule PHASE_WASM_VIRTUAL_IP after lowering and before FinalizeEH/RA. |
| src/coreclr/jit/codegenwasm.cpp | Record unwind stack allocation, create unwindable funclet prolog slots, adjust localloc unwind markers, return a value from catch funclets, and report EH clauses from precomputed data. |
| src/coreclr/jit/codegeninterface.h | Promote EHClauseInfo to a shared type. |
| src/coreclr/jit/codegencommon.cpp | Refactor EH reporting to a shared genReportEHClauses helper callable by Wasm codegen. |
| src/coreclr/jit/codegen.h | Declare genReportEHClauses. |
| src/coreclr/inc/win64unwind.h | Define a Wasm-specific UNWIND_INFO payload (FrameSize). |
| src/coreclr/inc/clrnt.h | Include win64unwind.h for Wasm unwind type definitions. |
| #else // TARGET_X86 | ||
| #elif defined(TARGET_WASM) | ||
|
|
||
| typedef struct _UNWIND_INFO { |
There was a problem hiding this comment.
It looks odd for wasm stuff to live in a file called win64unwind.h.
Can this live in clrnt.h or not exist at all (we can use compressed integer to save a few bytes like most of the modern platforms)?
There was a problem hiding this comment.
Compressing it now with ULEB128.
| #endif | ||
|
|
||
| #if defined(TARGET_WASM) | ||
| // These Wasm locals must be allocated last. |
There was a problem hiding this comment.
These frame slots must be placed at $sp +8, $sp + 4, and $sp + 0. This method allocates frame slots top-down, so by allocating them last they end up in the desired locations.
kg
left a comment
There was a problem hiding this comment.
LGTM, but a second pair of eyes would be good
| // If the sp is changed, the value at sp[0] must be the frame pointer | ||
| // so that the runtime unwinder can locate the base of the fixed area | ||
| // in the shadow stack. | ||
| // If the sp is changed, the value at sp[0] must be set to zero and |
There was a problem hiding this comment.
I forgot to ask about this in the issue, but what's the motivation for (essentially) replacing an unwinder check of WithingStack with == 0? It is more code for codegen.
There was a problem hiding this comment.
@davidwrighton do you have a strong opinion here? This is for stackalloc frames.
| LIR::ReadOnlyRange blockRange(firstNode, lastNode); | ||
| m_pLowering->LowerRange(block, blockRange); | ||
|
|
||
| virtualIP++; |
There was a problem hiding this comment.
I would like to understand whether the choice we're making here w.r.t. one-IP-per-call is something that is, yes, more or less set in stone, and what we want from that is.
Do we want it because we want line numbers in stack traces?
Do we want it because we want (fully) precise GC with fine-grained GCInfo?
As-is, we'll need to figure out what to do with calls that we only insert in codegen.
There was a problem hiding this comment.
Nothing is set in stone yet. If we can find ways to generate less IP updates if/when we have per-call GC info, we can change this.
For late added calls, seems like we'll need to anticipate them and leave room in the IP space, and add IP updates as needed in codegen. Or maybe we'll be lucky and those calls will always be ones that can't trigger stack walks.
There was a problem hiding this comment.
If we can find ways to generate less IP updates if/when we have per-call GC info, we can change this.
Well, my frame challenge here is why do we need them in the first place? I. e. why not start the other way around, with per-region IPs only (it can be per-block updates for a simple impl)?
Or maybe we'll be lucky and those calls will always be ones that can't trigger stack walks.
I think most of these cases will be throw helpers.
| } | ||
|
|
||
| LIR::ReadOnlyRange& range = LIR::AsRange(block); | ||
| for (auto i = range.rbegin(); i != range.rend(); ++i) |
There was a problem hiding this comment.
Nit: to be honest, I like our usual loops of node = gtNext more for this kind of thing. It's trickier to reason what's going on here with whether the range enumerator gets invalidated or not when we mutate the range.
There was a problem hiding this comment.
Good suggestion, let me change this over.
| * | Async contexts | | ||
| * |-----------------------| | ||
| * | GS cookie | | ||
| * |-----------------------| |
There was a problem hiding this comment.
Nit: I know you're following the style of the other diagrams here, but I think it would help legibility if we removed all detail here that is immaterial to WASM stack layout and left only what's 'interesting' in terms of the fixed-offset positions, labeling the rest as e. g. just "variables".
E. g. currently this is referring to the GS Cookie (which we don't have), and "temps", which I think refer to codegen temps, which we also don't have.
There was a problem hiding this comment.
Fair enough. I'll trim out the unrelated bits.
Implement the codegen changes necessary for EH and unwind reporting and for making frames unwindable by the runtime:
We are currently somewhat generous in handing out Virtual IPs; likely we could compress their range a bit more.
Still to do: