Repository navigation
[wasm] Regalloc: Allow Non-exposed GC Ref Locals to Live in Wasm Locals - #135543
Conversation
|
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 |
There was a problem hiding this comment.
🟡 Changes recommended
The tests do not automatically verify that eligible references are actually assigned Wasm-local homes.
3 open findings
What changed in this PR
Adds Wasm GC-reference liveness analysis so safe reference locals can use Wasm-local homes.
Changes:
- Identifies tracked GC references not live across safepoints.
- Enables their register allocation and preserves call arguments via spills.
- Adds GC-liveness correctness scenarios.
| File | Description |
|---|---|
src/coreclr/jit/fgwasm.cpp |
Implements candidate analysis and spill handling. |
src/coreclr/jit/regallocwasm.cpp |
Allows identified references into Wasm locals. |
src/coreclr/jit/compiler.cpp |
Runs the new analysis phase. |
src/coreclr/jit/compiler.h |
Declares candidate state and phase method. |
src/coreclr/jit/compphases.h |
Registers the new phase. |
src/coreclr/jit/compmemkind.h |
Adds candidate-set allocation tracking. |
src/tests/JIT/Directed/WasmGCLiveness/WasmGCLiveness.cs |
Adds GC-liveness scenarios. |
src/tests/JIT/Directed/WasmGCLiveness/WasmGCLiveness.csproj |
Configures the isolated priority-one test. |
🧠 Review effort: Balanced
The current `fgWasmSpillRefs` pass spills all outstanding GC-ref typed temporary values, even if those temps are consumed by the most immediate call. However, for calls it should be the callee's responsibility to maintain GC safety of any incoming GC-ref typed args (either by spilling to a pinned stack home, spilling and reloading, or potentially avoiding spilling at all, see #135543). In the R2R fixup case, where a thunk will fix up the call target on first invocation, we should also be GC-protecting all incoming args by default (See the spilling logic in crossgen2 `WasmImportThunk.cs`). This PR removes the need for spilling in cases like: ``` MyClass x = Allocate(); Use(x); ``` Note that the spilling STILL needs to happen for nested calls, where a ref has an outstanding use which is NOT its most immediate parent. ```csharp MyClass x = Allocate(); M(x, F()) // <-- temporary value of x is an operand of the call to M, and may be live across the other sub-tree call, F where a GC might occur. ``` ## Results ### SuperPMI Total bytes of base: 8771651 Total bytes of diff: 8555629 Total bytes of delta: **-216,022 (-2.46% of base)** ### Local Benchmarks | Metric | CoreCLR baseline | CoreCLR after | Change | |---|---:|---:|---:| | Mobile cold startup | 7,474 ms | 6,901 ms | 7.7% faster | | Desktop walkthrough | 5.60 s | 6.05 s | 8.1% slower | | Desktop cold download | 10.324 MB | 10.215 MB | 1.1% smaller | | Havit publish | 150.81 s | 149.21 s | 1.1% faster | Cold download and startup improve as expected. I didn't put too much weight on the walkthrough benchmark because it is only run once and is pretty variable.
6ee0537 to
f702924
Compare
|
The regressions have mostly been mitigated by #135577, there were many more before. The ones that remain are expected given the current state; In these cases we have a local which is not considered live over a safepoint using local liveness, but its value is read into a temporary which is live across a safepoint. In that case, it is a bit more expensive to spill the temporary value and reload it than it would have been to assign the local a stack home to begin with. We could potentially try and do some more analysis to avoid even these cases, but that doesn't seem worth it to me due to the comparatively very small size of the regressions. |
|
cc @dotnet/wasm-contrib; @EgorBo PTAL. |
EgorBo
left a comment
There was a problem hiding this comment.
Approving to unblock the work, but this might need a 2nd look by @AndyAyersMS I think
74537f1 to
c25a9fa
Compare
c25a9fa to
e3d52f9
Compare
|
would this make the values/references invisible to managed debugger ? Is that OK for R2R code ? @radekdoulik |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
…r a method and checks the state of local liveness at each potential GC safepoint to determine which locals are live across a safepoint. It distinguishes between normal GC safepoints triggered by a call or and exception-only GC safepoints, where an operation can only collect on its throwing path. This allows us to effectively discount locals as gc-exposed if they are only exposed to GC on a throwing path but are NOT live into a handler. The set of gc-safe locals is the set of gc exposed locals subracted from the set of all locals. This reduced set of gc-safe locals can be used by Wasm regalloc to make gc refs into wasm-local candidates.
…asses to allow safely placing select GC references in locals
e3d52f9 to
4e7a64c
Compare
|
/ba-g unrelated failures |


Background
This PR introduces a pass which does some liveness analysis to determine GC-ref tracked locals which are meaningfully live at GC safepoints. Locals which are live at safepoints are treated as before and we continue to assign them pinned stack homes. However, the pass uses this list to then determine candidate gc ref locals which are NOT live at any safepoint, which RA can assign
localhomes since the GC does not need to see them.The one exception here is temporary reads of locals. A tree temporary from a local var which is live across a call still needs a pinned stack slot, and `fgWasmSpillRefs has been updated to handle this case. This is also why this analysis was structured as a separate phase; it is a self-contained analysis and provides information that two subsequent phases need to consume.
Notes
For throwing code paths, we must account for any LIR node which could possibly throw (this includes all null checks, div-by-zero checks, range checks, etc.) and also track which live locals are actually live on the throwing path. A reference stored in a local is not actually exposed to GC if the only collecting path is the throwing path, unless it is also live into a handler for that site.
Performance
SPMI
Diff: -549,758/+1,245 bytes
16,424 size improvements, 205 size regressions
Total bytes of delta: -548,513 (-6.41 % of base)
Local benchmarks
Validation
The new WasmGCLiveness tests pass which is a good start, but it is hard to force the GC to move objects to detect if something is wrong. If we had GC Stress for Wasm that might help. For several of the test cases (including live across finally/EH, mixed stack/reg homes, and others), I inspected JIT dumps manually to ensure that the pass was working as expected.