Skip to content

Commit 6bc04bf

Browse files
authored
JIT: empty array enumerator opt (#109237)
For empty arrays, `GetEnumerator` returns a static instance rather than a new instance. This hinders the JIT's ability to optimize when it is able to stack allocate the new instance. Detect when a stack allocation comes from an array `GetEnumerator` and fold the branch in the inlined enumerator method to always take the new instance path (since it is now cheap, allocation free, and is functionally equivalent). Contributes to #108913.
1 parent e4fe27d commit 6bc04bf

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

src/coreclr/jit/gentree.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,8 @@ enum GenTreeFlags : unsigned int
548548

549549
GTF_MDARRLOWERBOUND_NONFAULTING = 0x20000000, // GT_MDARR_LOWER_BOUND -- An MD array lower bound operation that cannot fault. Same as GT_IND_NONFAULTING.
550550

551+
GTF_ALLOCOBJ_EMPTY_STATIC = 0x80000000, // GT_ALLOCOBJ -- allocation site is part of an empty static pattern
552+
551553
#ifdef FEATURE_HW_INTRINSICS
552554
GTF_HW_EM_OP = 0x10000000, // GT_HWINTRINSIC -- node is used as an operand to an embedded mask
553555
GTF_HW_USER_CALL = 0x20000000, // GT_HWINTRINSIC -- node is implemented via a user call

src/coreclr/jit/importer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8966,6 +8966,15 @@ void Compiler::impImportBlockCode(BasicBlock* block)
89668966
return;
89678967
}
89688968

8969+
// Flag if this allocation happens within a method that uses the static empty
8970+
// pattern (if we stack allocate this object, we can optimize the empty side away)
8971+
//
8972+
if (lookupNamedIntrinsic(info.compMethodHnd) == NI_System_SZArrayHelper_GetEnumerator)
8973+
{
8974+
JITDUMP("Allocation is part of empty static pattern\n");
8975+
op1->gtFlags |= GTF_ALLOCOBJ_EMPTY_STATIC;
8976+
}
8977+
89698978
// Remember that this basic block contains 'new' of an object
89708979
block->SetFlags(BBF_HAS_NEWOBJ);
89718980
optMethodFlags |= OMF_HAS_NEWOBJ;

src/coreclr/jit/objectalloc.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,58 @@ unsigned int ObjectAllocator::MorphAllocObjNodeIntoStackAlloc(
623623

624624
comp->fgInsertStmtBefore(block, stmt, initStmt);
625625

626+
// If this allocation is part the special empty static pattern, find the controlling
627+
// branch and force control to always flow to the new instance side.
628+
//
629+
if ((allocObj->gtFlags & GTF_ALLOCOBJ_EMPTY_STATIC) != 0)
630+
{
631+
BasicBlock* const predBlock = block->GetUniquePred(comp);
632+
assert(predBlock != nullptr);
633+
assert(predBlock->KindIs(BBJ_COND));
634+
635+
JITDUMP("Empty static pattern controlled by " FMT_BB ", optimizing to always use stack allocated instance\n",
636+
predBlock->bbNum);
637+
Statement* const controllingStmt = predBlock->lastStmt();
638+
GenTree* const controllingNode = controllingStmt->GetRootNode();
639+
assert(controllingNode->OperIs(GT_JTRUE));
640+
641+
FlowEdge* const trueEdge = predBlock->GetTrueEdge();
642+
FlowEdge* const falseEdge = predBlock->GetFalseEdge();
643+
FlowEdge* keptEdge = nullptr;
644+
FlowEdge* removedEdge = nullptr;
645+
646+
if (trueEdge->getDestinationBlock() == block)
647+
{
648+
keptEdge = trueEdge;
649+
removedEdge = falseEdge;
650+
}
651+
else
652+
{
653+
assert(falseEdge->getDestinationBlock() == block);
654+
keptEdge = falseEdge;
655+
removedEdge = trueEdge;
656+
}
657+
658+
BasicBlock* removedBlock = removedEdge->getDestinationBlock();
659+
comp->fgRemoveRefPred(removedEdge);
660+
predBlock->SetKindAndTargetEdge(BBJ_ALWAYS, keptEdge);
661+
662+
if (predBlock->hasProfileWeight())
663+
{
664+
block->setBBProfileWeight(predBlock->bbWeight);
665+
}
666+
667+
// Just lop off the JTRUE, the rest can clean up later
668+
// (eg may have side effects)
669+
//
670+
controllingStmt->SetRootNode(controllingNode->AsOp()->gtOp1);
671+
672+
// We must remove the empty static block now too.
673+
assert(removedBlock->bbRefs == 0);
674+
assert(removedBlock->KindIs(BBJ_ALWAYS));
675+
comp->fgRemoveBlock(removedBlock, /* unreachable */ true);
676+
}
677+
626678
return lclNum;
627679
}
628680

0 commit comments

Comments
 (0)