Skip to content

Commit 3126d58

Browse files
authored
JIT: fix interaction of GDV and boxing (#60355)
In #56126 we disabled GDV if the object being boxed came from a call with a hidden return buffer pointer. Fix this and enable GDV for these cases by inserting the box allocation statements before the call.
1 parent 838fed9 commit 3126d58

File tree

2 files changed

+91
-33
lines changed

2 files changed

+91
-33
lines changed

src/coreclr/jit/gentree.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7428,7 +7428,11 @@ GenTree* Compiler::gtNewBitCastNode(var_types type, GenTree* arg)
74287428
// Return Value:
74297429
// Returns GT_ALLOCOBJ node that will be later morphed into an
74307430
// allocation helper call or local variable allocation on the stack.
7431-
7431+
//
7432+
// Node creation can fail for inlinees when the type described by pResolvedToken
7433+
// can't be represented in jitted code. If this happens, this method will return
7434+
// nullptr.
7435+
//
74327436
GenTreeAllocObj* Compiler::gtNewAllocObjNode(CORINFO_RESOLVED_TOKEN* pResolvedToken, bool useParent)
74337437
{
74347438
const bool mustRestoreHandle = true;

src/coreclr/jit/importer.cpp

Lines changed: 86 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6865,59 +6865,113 @@ void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken)
68656865
// the opcode stack becomes empty
68666866
impBoxTempInUse = true;
68676867

6868+
// Remember the current last statement in case we need to move
6869+
// a range of statements to ensure the box temp is initialized
6870+
// before it's used.
6871+
//
6872+
Statement* const cursor = impLastStmt;
6873+
68686874
const bool useParent = false;
68696875
op1 = gtNewAllocObjNode(pResolvedToken, useParent);
68706876
if (op1 == nullptr)
68716877
{
6878+
// If we fail to create the newobj node, we must be inlining
6879+
// and have run across a type we can't describe.
6880+
//
6881+
assert(compDonotInline());
68726882
return;
68736883
}
68746884

6875-
/* Remember that this basic block contains 'new' of an object, and so does this method */
6885+
// Remember that this basic block contains 'new' of an object,
6886+
// and so does this method
6887+
//
68766888
compCurBB->bbFlags |= BBF_HAS_NEWOBJ;
68776889
optMethodFlags |= OMF_HAS_NEWOBJ;
68786890

6879-
GenTree* asg = gtNewTempAssign(impBoxTemp, op1);
6880-
6891+
// Assign the boxed object to the box temp.
6892+
//
6893+
GenTree* asg = gtNewTempAssign(impBoxTemp, op1);
68816894
Statement* asgStmt = impAppendTree(asg, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs);
68826895

6883-
op1 = gtNewLclvNode(impBoxTemp, TYP_REF);
6884-
op2 = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL);
6885-
op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, op2);
6886-
6887-
if (varTypeIsStruct(exprToBox))
6896+
// If the exprToBox is a call that returns its value via a ret buf arg,
6897+
// move the assignment statement(s) before the call (which must be a top level tree).
6898+
//
6899+
// We do this because impAssignStructPtr (invoked below) will
6900+
// back-substitute into a call when it sees a GT_RET_EXPR and the call
6901+
// has a hidden buffer pointer, So we need to reorder things to avoid
6902+
// creating out-of-sequence IR.
6903+
//
6904+
if (varTypeIsStruct(exprToBox) && exprToBox->OperIs(GT_RET_EXPR))
68886905
{
6889-
// Workaround for GitHub issue 53549.
6890-
//
6891-
// If the struct being boxed is returned via hidden buffer and comes from an inline/gdv candidate,
6892-
// the IR we produce after importation is out of order:
6893-
//
6894-
// call (&(box-temp + 8), ....)
6895-
// box-temp = newobj
6896-
// ret-val from call (void)
6897-
// ... box-temp (on stack)
6898-
//
6899-
// For inline candidates this bad ordering gets fixed up during inlining, but for GDV candidates
6900-
// the GDV expansion is such that the newobj follows the call as in the above.
6901-
//
6902-
// This is nontrivial to fix in GDV, so in these (rare) cases we simply disable GDV.
6903-
//
6904-
if (exprToBox->OperIs(GT_RET_EXPR))
6906+
GenTreeCall* const call = exprToBox->AsRetExpr()->gtInlineCandidate->AsCall();
6907+
6908+
if (call->HasRetBufArg())
69056909
{
6906-
GenTreeCall* const call = exprToBox->AsRetExpr()->gtInlineCandidate->AsCall();
6910+
JITDUMP("Must insert newobj stmts for box before call [%06u]\n", dspTreeID(call));
6911+
6912+
// Walk back through the statements in this block, looking for the one
6913+
// that has this call as the root node.
6914+
//
6915+
// Because gtNewTempAssign (above) may have added statements that
6916+
// feed into the actual assignment we need to move this set of added
6917+
// statements as a group.
6918+
//
6919+
// Note boxed allocations are side-effect free (no com or finalizer) so
6920+
// our only worries here are (correctness) not overlapping the box temp
6921+
// lifetime and (perf) stretching the temp lifetime across the inlinee
6922+
// body.
6923+
//
6924+
// Since this is an inline candidate, we must be optimizing, and so we have
6925+
// a unique box temp per call. So no worries about overlap.
6926+
//
6927+
assert(!opts.OptimizationDisabled());
69076928

6908-
if (call->IsGuardedDevirtualizationCandidate() && call->HasRetBufArg())
6929+
// Lifetime stretching could addressed with some extra cleverness--sinking
6930+
// the allocation back down to just before the copy, once we figure out
6931+
// where the copy is. We defer for now.
6932+
//
6933+
Statement* insertBeforeStmt = cursor;
6934+
noway_assert(insertBeforeStmt != nullptr);
6935+
6936+
while (true)
69096937
{
6910-
JITDUMP("Disabling GDV for [%06u] because of in-box struct return\n");
6911-
call->ClearGuardedDevirtualizationCandidate();
6912-
if (call->IsVirtualStub())
6938+
if (insertBeforeStmt->GetRootNode() == call)
69136939
{
6914-
JITDUMP("Restoring stub addr %p from guarded devirt candidate info\n",
6915-
dspPtr(call->gtGuardedDevirtualizationCandidateInfo->stubAddr));
6916-
call->gtStubCallStubAddr = call->gtGuardedDevirtualizationCandidateInfo->stubAddr;
6940+
break;
69176941
}
6942+
6943+
// If we've searched all the statements in the block and failed to
6944+
// find the call, then something's wrong.
6945+
//
6946+
noway_assert(insertBeforeStmt != impStmtList);
6947+
6948+
insertBeforeStmt = insertBeforeStmt->GetPrevStmt();
69186949
}
6950+
6951+
// Found the call. Move the statements comprising the assignment.
6952+
//
6953+
JITDUMP("Moving " FMT_STMT "..." FMT_STMT " before " FMT_STMT "\n", cursor->GetNextStmt()->GetID(),
6954+
asgStmt->GetID(), insertBeforeStmt->GetID());
6955+
assert(asgStmt == impLastStmt);
6956+
do
6957+
{
6958+
Statement* movingStmt = impExtractLastStmt();
6959+
impInsertStmtBefore(movingStmt, insertBeforeStmt);
6960+
insertBeforeStmt = movingStmt;
6961+
} while (impLastStmt != cursor);
69196962
}
6963+
}
69206964

6965+
// Create a pointer to the box payload in op1.
6966+
//
6967+
op1 = gtNewLclvNode(impBoxTemp, TYP_REF);
6968+
op2 = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL);
6969+
op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, op2);
6970+
6971+
// Copy from the exprToBox to the box payload.
6972+
//
6973+
if (varTypeIsStruct(exprToBox))
6974+
{
69216975
assert(info.compCompHnd->getClassSize(pResolvedToken->hClass) == info.compCompHnd->getClassSize(operCls));
69226976
op1 = impAssignStructPtr(op1, exprToBox, operCls, (unsigned)CHECK_SPILL_ALL);
69236977
}

0 commit comments

Comments
 (0)