Skip to content

Commit 60e9340

Browse files
authored
JIT: generalize escape analysis delegate invoke expansion slightly (#116099)
Expand the invoke when the delegate object address has been substituted directly. Addresses example in #84872.
1 parent 1713d65 commit 60e9340

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

src/coreclr/jit/objectalloc.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,25 +2456,28 @@ void ObjectAllocator::RewriteUses()
24562456
CallArg* const thisArg = call->gtArgs.GetThisArg();
24572457
GenTree* const delegateThis = thisArg->GetNode();
24582458

2459-
if (delegateThis->OperIs(GT_LCL_VAR))
2459+
if (delegateThis->OperIs(GT_LCL_VAR, GT_LCL_ADDR))
24602460
{
24612461
GenTreeLclVarCommon* const lcl = delegateThis->AsLclVarCommon();
2462+
bool const isStackAllocatedDelegate =
2463+
delegateThis->OperIs(GT_LCL_ADDR) || m_allocator->DoesLclVarPointToStack(lcl->GetLclNum());
24622464

2463-
if (m_allocator->DoesLclVarPointToStack(lcl->GetLclNum()))
2465+
if (isStackAllocatedDelegate)
24642466
{
24652467
JITDUMP("Expanding delegate invoke [%06u]\n", m_compiler->dspTreeID(call));
2468+
24662469
// Expand the delgate invoke early, so that physical promotion has
24672470
// a chance to promote the delegate fields.
24682471
//
24692472
// Note the instance field may also be stack allocatable (someday)
24702473
//
2471-
GenTree* const cloneThis = m_compiler->gtClone(lcl);
2474+
GenTree* const cloneThis = m_compiler->gtClone(lcl, /* complexOk */ true);
24722475
unsigned const instanceOffset = m_compiler->eeGetEEInfo()->offsetOfDelegateInstance;
24732476
GenTree* const newThisAddr =
24742477
m_compiler->gtNewOperNode(GT_ADD, TYP_I_IMPL, cloneThis,
24752478
m_compiler->gtNewIconNode(instanceOffset, TYP_I_IMPL));
24762479

2477-
// For now assume the instance is heap...
2480+
// For now assume the instance field is on the heap...
24782481
//
24792482
GenTree* const newThis = m_compiler->gtNewIndir(TYP_REF, newThisAddr);
24802483
thisArg->SetEarlyNode(newThis);

src/tests/JIT/opt/ObjectStackAllocation/Delegates.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ static AllocationKind StackAllocation()
3434
return expectedAllocationKind;
3535
}
3636

37+
static AllocationKind HeapAllocation()
38+
{
39+
AllocationKind expectedAllocationKind = AllocationKind.Heap;
40+
if (GCStressEnabled())
41+
{
42+
Console.WriteLine("GCStress is enabled");
43+
expectedAllocationKind = AllocationKind.Undefined;
44+
}
45+
return expectedAllocationKind;
46+
}
47+
3748
static int CallTestAndVerifyAllocation(Test test, int expectedResult, AllocationKind expectedAllocationsKind, bool throws = false)
3849
{
3950
string methodName = test.Method.Name;
@@ -122,4 +133,21 @@ public static int Test1()
122133
RunTest1();
123134
return CallTestAndVerifyAllocation(RunTest1, 100, StackAllocation());
124135
}
136+
137+
// Here the delegate gets stack allocated, but not the closure.
138+
// With PGO the delegate is also inlined.
139+
//
140+
[MethodImpl(MethodImplOptions.NoInlining)]
141+
static int RunTest2Inner(int a) => InvokeFunc(x => x + a);
142+
143+
static int InvokeFunc(Func<int, int> func) => func(101);
144+
145+
[MethodImpl(MethodImplOptions.NoInlining)]
146+
static int RunTest2() => RunTest2Inner(-1);
147+
148+
[Fact]
149+
public static int Test2()
150+
{
151+
return CallTestAndVerifyAllocation(RunTest2, 100, HeapAllocation());
152+
}
125153
}

0 commit comments

Comments
 (0)