JIT: Improve Wasm PEP calls - #134490
JIT: Improve Wasm PEP calls#134490
Conversation
Reuse each managed call target's Wasm local for the PEP argument and indirect target. This avoids explicit PEP locals that can become shadow-stack traffic. Addresses part of dotnet#134450; other inefficiencies reported there remain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
|
Azure Pipelines: Successfully started running 6 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
@adamperlin PTAL Code size delta is from SPMI, actual browser corelib size should be similar, but I'll verify. (looks like it is more savings in bytes, lower percent overall given the other stuff in the file)
|
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Wasm JIT lowering, register allocation, and code generation changes require final human review.
Review effort: Lite
Findings: None
What changed in this PR
Improves WebAssembly PEP call generation by reusing the target local, reducing shadow-stack traffic and code size.
Changes:
- Updates Wasm register allocation for PEP target temporaries.
- Uses a contained ABI placeholder during lowering.
- Reuses the target local for indirect-call emission.
| File | Description |
|---|---|
src/coreclr/jit/regallocwasm.cpp |
Manages PEP target temporary lifetime. |
src/coreclr/jit/lowerwasm.cpp |
Lowers PEP calls using the original control expression. |
src/coreclr/jit/codegenwasm.cpp |
Emits the optimized indirect-call sequence. |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
Explain how the control expression supplies both the PEP argument and the indirect call target. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
jakobbotsch
left a comment
There was a problem hiding this comment.
What is the IR here? I'm curious why the straightforward IR representation with a local for the PEP argument and GT_IND on top of that local for the call target doesn't get good codegen.
|
This PR is possibly obsolete given that #134555 is changing over to making direct calls to helpers.
Yeah it is trying to be clever and use wasm locals for this instead. Let me look at a more conventional solution. |
This should still have positive diffs for all non-helper managed calls though I would imagine? |
|
@AndyAyersMS, this PR isn't obsolete. It still improves the case when the module we compile ISN'T also containing System.Private.CoreLib, or when the helper is written in C. I'm going to probably try to address the C helper situation, since those should be able to use a function import, but the modular R2R file istuation would be good to fix. |
|
Actual problem is that locals introduced post-lower aren't tracked, so aren't considered as register candidates, so for helper calls (added in stack level setter) the PEP local is put on the frame. Seems fixable. [edit] Though not perhaps fixable without some other similar hackiness. Either I rebuild liveness, adopt a temporary side-car liveness bypass like the one physical promotion uses, teach Wasm RA via some other means that this local is special, or do something like this PR that leverages Wasm multi-use nodes to avoid introducing a local. |
Can we move the existing liveness to run after the stack level setter? I don't think we depend on the current ordering of those. |
Almost, yes. We introduced a dependence recently; I suppose I can just bring back that bit of code. runtime/src/coreclr/jit/liveness.cpp Lines 1305 to 1308 in f3fc53d |
That doesn't quite work since we introduce a new local but not new var death flags so liveness and codegen live vars now seemingly diverge. Looking into fixing that now. |
Replace the specialized lowering and code generation approach with the late-liveness implementation in the following commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
|
Ok, got the deferred liveness to work. Had to recompute live in/last use for throw helpers, verified that it does not lead to new global liveness. Similar size impact as before (a bit less as the pep locals each get their own wasm local, which can increase encoding size). We could fix this with a "real RA" that assigns different lcl vars to the same wasm local. Some small diffs on native targets, as zero weight blocks may be in slightly different orders now. @adamperlin @jakobbotsch ptal |
jakobbotsch
left a comment
There was a problem hiding this comment.
LGTM.
I wonder where the TP regressions are coming from... Is it just the throw helper liveness?
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
Two things:
Both fixed, TP impact should now be minimal |
|
@jakobbotsch re-approve? |
|
From the updated diffs
|



Summary
Example
For
System.UInt32.GetHashCode, the null-throw helper path previously stored the PEP to the shadow stack and loaded it twice:It now tees the PEP into the existing Wasm local and reuses it for the indirect target:
This method decreases from 116 to 105 bytes.
Code size
Browser CoreLib SuperPMI, 28,985 successful contexts:
16,849 methods improve, 9 regress, and 12,127 are unchanged.
Testing
Core_Root:deadoponerrorinfunclet_ddeadoponerrorinfunclet_rsimpledeadehregion_rNote
This PR description was generated with GitHub Copilot.