Repository navigation
Allow CSE for address-exposed locals - #136832
NinoFloris wants to merge 2 commits into
Conversation
Treat address-exposed local reads as memory-load CSE candidates so repeated reads can share a captured value, enabling further bounds-check elimination. Existing memory dependence and availability checks govern reuse. Keep TYP_REF excluded for now. One known complication is that capturing a receiver can leave cached method-table VNs referring to an earlier identity, preventing exact-type assertions from matching. Leave investigation of object-reference candidates to a separate change. Cover guarded span slicing, intervening writes/calls, and volatile reads.
|
Azure Pipelines: Successfully started running 5 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 |
|
Decent diffs, some tp cost (+0.01% to +0.04%) which was more than made up for by #136738. Some regressions come from register allocation choosing an extra callee-saved register because an unrelated call lies between uses, even though the value isn’t live across that call, adding unnecessary saves and restores. If that’s a blocker, I can submit a small ~15 line RA patch that addresses it. In local ARM64 PMI, that patch improves 1,343 methods and regresses 146, saving about 14 KB overall. |
The x86 JIT still retains the unsigned Slice comparison after the signed guard. Keep functional and inlining coverage on all targets without requiring this separate signedness optimization on x86.
40aabfd to
fcf9a68
Compare
|
Both Windows x86 and x64 retain ThrowArgumentOutOfRangeException because the span is stack-passed on x86 and passed through an implicit byref on windows x64 ABIs. These memory-based length loads lack range annotations that promoted locals receive. Addressed in #136865 |
There was a problem hiding this comment.
🟡 Changes recommended
The change can allow direct volatile-field reads from address-exposed promoted structs to be collapsed into one cached value, violating volatile-read semantics; volatile indirections must be preserved and a regression test added.
1 open issue
What changed in this PR
Enables common subexpression elimination (CSE) for non-object-reference, address-exposed locals in RyuJIT, helping eliminate redundant bounds checks in guarded span slicing.
Changes:
- Allows eligible local reads to be reused through CSE temporaries while excluding
TYP_REF. - Adds tests for guarded slicing, intervening writes, and volatile reads.
- Configures isolated, optimized test execution with disassembly checks.
| File | Description |
|---|---|
src/tests/JIT/opt/CSE/AddressExposedLoads.csproj |
Configures optimized execution and disassembly validation. |
src/tests/JIT/opt/CSE/AddressExposedLoads.cs |
Tests span bounds checks, writes, and volatile reads. |
src/coreclr/jit/optcse.cpp |
Enables CSE for eligible address-exposed local reads. |
Review effort: Balanced
| // TODO-CQ: Investigate enabling object-reference loads. For example, | ||
| // capturing a receiver can leave cached method-table VNs referring to | ||
| // an earlier identity, preventing exact-type assertions from matching. | ||
| if (tree->TypeIs(TYP_REF) || !m_compiler->lvaGetDesc(tree->AsLclVar())->IsAddressExposed()) |
There was a problem hiding this comment.
I can make it even better, this already happens on main :) Issue incoming.

Blocked by #136865, and #136940
A trivial change, though unrestricted it exposed an exact-type assertion issue that would leave redundant type tests and unbox helpers with this PR. So I excluded TYP_REF locals for now. I could follow up with the exact-type assertion fix to enable them too. Including TYP_REF would double the number of diffs in this PR.
The immediate practical benefit for enabling CSE is redundant bounds-check elimination when a guard and an inlined Span.Slice read the same length from an address exposed span.
Contributes to unblocking: #119689
Extracted from: #136739
Example, simplified from DnsEncodedName.Equals: