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