Skip to content

Allow CSE for address-exposed locals - #136832

Open
NinoFloris wants to merge 2 commits into
dotnet:mainfrom
NinoFloris:bounds-elimination/cse-address-exposed-loads
Open

NinoFloris wants to merge 2 commits into
dotnet:mainfrom
NinoFloris:bounds-elimination/cse-address-exposed-loads

Conversation

@NinoFloris

@NinoFloris NinoFloris commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

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:

bool Equals(ReadOnlySpan<char> name)
{
    if (!Ascii.IsValid(name))
    {
        // ToString remains uninlined and receives 'name' as its ref this.
        // 'name' is now address-exposed, including its length field.
        name = s_idnMapping.GetAscii(name.ToString());
    }

    foreach (ReadOnlySpan<byte> label in EnumerateLabels())
    {
        // Reads the address-exposed 'name.Length'.
        if (label.Length > name.Length)
            return false;

        // The widened Slice check is eliminated here today. The split form
        // retains a redundant check because CSE rejects the repeated reads
        // of address-exposed 'name.Length'.
        if (!Ascii.EqualsIgnoreCase(label, name.Slice(0, label.Length)))
            return false;

        name = name.Slice(label.Length);
    }

    return name.IsEmpty;
}

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.
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Nov 2, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Nov 2, 2026
@azure-pipelines

Copy link
Copy Markdown
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.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@NinoFloris

Copy link
Copy Markdown
Contributor Author

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.
@NinoFloris

Copy link
Copy Markdown
Contributor Author

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make it even better, this already happens on main :) Issue incoming.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants