Skip to content

Conversation

@AndyAyersMS
Copy link
Member

Conditional escape analysis was not happening when an object's GetEnumerator returns the result of another GetEnumerator call. These calls must be specially flagged and the JIT was not handling this case.

Fix the flagging logic so that when processing a return from a flagged inlinee, if the return value is a call, also flag that call.

Also relax a constraint on the location of enumerator temp appearances, as there can now be another layer of temp assignments involved in returning the enumerator instance.

Closes #122856.

Conditional escape analysis was not happening when an object's `GetEnumerator`
returns the result of another `GetEnumerator` call. These calls must
be specially flagged and the JIT was not handling this case.

Fix the flagging logic so that when processing a return from a flagged inlinee,
if the return value is a call, also flag that call.

Also relax a constraint on the location of enumerator temp appearances, as there can
now be another layer of temp assignments involved in returning the enumerator instance.

Closes dotnet#122856.
Copilot AI review requested due to automatic review settings January 6, 2026 23:15
@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 Jan 6, 2026
@dotnet-policy-service
Copy link
Contributor

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances conditional escape analysis (CEA) in the JIT compiler to properly handle scenarios where a GetEnumerator method returns the result of another GetEnumerator call. The fix ensures that enumerator allocation tracking propagates correctly through nested GetEnumerator calls and adjusts domination constraints for the empty static pattern.

Key changes:

  • Added logic to transfer enumerator GDV flags from an inlined call to its returned call when the return value is also a call
  • Relaxed domination constraints for empty static cases to allow temp appearances dominated by the allocation block's unique predecessor

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/coreclr/jit/importer.cpp Added logic in return instruction handling to propagate enumerator GDV flags to nested GetEnumerator calls during inlining
src/coreclr/jit/objectalloc.cpp Modified domination checking to allow temp appearances to be dominated by the allocation block's unique predecessor in empty static patterns

The changes look reasonable and follow established patterns in the codebase. The logic is clear and well-commented. The implementation correctly handles the case where GetEnumerator is implemented by calling another GetEnumerator method, which was preventing conditional escape analysis from working properly.

@AndyAyersMS
Copy link
Member Author

@dotnet/jit-contrib PTAL

Recall the flagging logic identifies a particular enumerable ALLOCOBJ that is inside of a GDV diamond with the variable that will ultimately be assigned the result of the entire GDV... that variable (or copies thereof) is then type tested in downstream GDVs during enumeration. This flagging triggers the conditional escape logic for accesses guarded by the downstream GDVs.

The flag starts out mapping the initial interface call site, eg

   V02 = obj.GetEnumerator()    // rhs flagged with V02

   if (V02.MoveNext()) // ... enumeration

and then is moved to the exact call when the GDV expands:

    if (obj.Type() == int[])
    {
        V05 = (obj as int[]).GetEnumerator()   // rhs flagged with V02
    }
    else 
    {
        V05 = obj.GetEnumerator();
    }
    V02 = V05;

and then is moved to the allocobj once this is devirtualized and inlined. In the cases up until now the inline of that first GetEnumerator exposed the allocation site.

   // control flow not shown
   V15 = ALLOCOBJ SzArrayEnumerator....   // rhs flagged with V02
   V05 = V15
   V02 = V05;

Now the JIT can handle cases where that first inline exposes a second (or further nested) GetEnumerator which must then be inlined before we see the allocation site.

Note if for some reason there were multiple GetEnumerator calls nested inside a GetEnumerator that all could provide return values, only one of them will inspire CEA. Which one "wins" is somewhat arbitrary (whichever we see first in our RPO).

Two diffs in SPMI where we do more stack allocation, plus 20 or so context misses likely indicating more diffs if we had proper context information.

@AndyAyersMS
Copy link
Member Author

/ba-g IOS dead letter

@AndyAyersMS AndyAyersMS merged commit c21ca88 into dotnet:main Jan 7, 2026
126 of 129 checks passed
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT de-abstraction for generated ReadOnlyArray works for static not for instance fields

3 participants