Skip to content

Commit 2411805

Browse files
authored
JIT: improve codegen for inline candidates called only for effect (#116123)
Keep track of unused GT_RET_EXPRs, and when we inline, substitute just the return value side effects for unused GT_RET_EXPRs. Reduces address exposure in some GDV cases, which can encourage physical promotion and other good things. In particular one case from #84872.
1 parent b0d68f7 commit 2411805

File tree

4 files changed

+110
-53
lines changed

4 files changed

+110
-53
lines changed

src/coreclr/jit/fginline.cpp

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -357,37 +357,60 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitor<Substi
357357
// constant bools will have CASTS. This folding may uncover more
358358
// GT_RET_EXPRs, so we loop around until we've got something distinct.
359359
//
360-
inlineCandidate = m_compiler->gtFoldExpr(inlineCandidate);
361-
var_types retType = tree->TypeGet();
360+
inlineCandidate = m_compiler->gtFoldExpr(inlineCandidate);
362361

363-
#ifdef DEBUG
364-
if (m_compiler->verbose)
362+
// If this use is an unused ret expr, is the first child of a comma, the return value is ignored.
363+
// Extract any side effects.
364+
//
365+
if ((parent != nullptr) && parent->OperIs(GT_COMMA) && (parent->gtGetOp1() == *use))
365366
{
366-
printf("\nReplacing the return expression placeholder ");
367-
Compiler::printTreeID(tree);
368-
printf(" with ");
369-
Compiler::printTreeID(inlineCandidate);
370-
printf("\n");
371-
// Dump out the old return expression placeholder it will be overwritten by the ReplaceWith below
372-
m_compiler->gtDispTree(tree);
373-
}
374-
#endif // DEBUG
367+
JITDUMP("\nReturn expression placeholder [%06u] value [%06u] unused\n", m_compiler->dspTreeID(tree),
368+
m_compiler->dspTreeID(inlineCandidate));
375369

376-
var_types newType = inlineCandidate->TypeGet();
370+
GenTree* sideEffects = nullptr;
371+
m_compiler->gtExtractSideEffList(inlineCandidate, &sideEffects);
377372

378-
// If we end up swapping type we may need to retype the tree:
379-
if (retType != newType)
373+
if (sideEffects == nullptr)
374+
{
375+
JITDUMP("\nInline return expression had no side effects\n");
376+
(*use)->gtBashToNOP();
377+
}
378+
else
379+
{
380+
JITDUMP("\nInserting the inline return expression side effects\n");
381+
JITDUMPEXEC(m_compiler->gtDispTree(sideEffects));
382+
JITDUMP("\n");
383+
*use = sideEffects;
384+
}
385+
}
386+
else
380387
{
381-
if ((retType == TYP_BYREF) && (tree->OperGet() == GT_IND))
388+
JITDUMP("\nReplacing the return expression placeholder [%06u] with [%06u]\n",
389+
m_compiler->dspTreeID(tree), m_compiler->dspTreeID(inlineCandidate));
390+
JITDUMPEXEC(m_compiler->gtDispTree(tree));
391+
392+
var_types retType = tree->TypeGet();
393+
var_types newType = inlineCandidate->TypeGet();
394+
395+
// If we end up swapping type we may need to retype the tree:
396+
if (retType != newType)
382397
{
383-
// - in an RVA static if we've reinterpreted it as a byref;
384-
assert(newType == TYP_I_IMPL);
385-
JITDUMP("Updating type of the return GT_IND expression to TYP_BYREF\n");
386-
inlineCandidate->gtType = TYP_BYREF;
398+
if ((retType == TYP_BYREF) && (tree->OperGet() == GT_IND))
399+
{
400+
// - in an RVA static if we've reinterpreted it as a byref;
401+
assert(newType == TYP_I_IMPL);
402+
JITDUMP("Updating type of the return GT_IND expression to TYP_BYREF\n");
403+
inlineCandidate->gtType = TYP_BYREF;
404+
}
387405
}
406+
407+
JITDUMP("\nInserting the inline return expression\n");
408+
JITDUMPEXEC(m_compiler->gtDispTree(inlineCandidate));
409+
JITDUMP("\n");
410+
411+
*use = inlineCandidate;
388412
}
389413

390-
*use = inlineCandidate;
391414
m_madeChanges = true;
392415

393416
if (inlineeBB != nullptr)
@@ -396,15 +419,6 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitor<Substi
396419
// Propagate those flags from the containing BB.
397420
m_compiler->compCurBB->CopyFlags(inlineeBB, BBF_COPY_PROPAGATE);
398421
}
399-
400-
#ifdef DEBUG
401-
if (m_compiler->verbose)
402-
{
403-
printf("\nInserting the inline return expression\n");
404-
m_compiler->gtDispTree(inlineCandidate);
405-
printf("\n");
406-
}
407-
#endif // DEBUG
408422
}
409423

410424
// If the inline was rejected and returns a retbuffer, then mark that

src/coreclr/jit/gentree.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16868,7 +16868,7 @@ bool Compiler::gtNodeHasSideEffects(GenTree* tree, GenTreeFlags flags, bool igno
1686816868
{
1686916869
GenTree* potentialCall = tree;
1687016870

16871-
if (potentialCall->OperIs(GT_RET_EXPR))
16871+
while (potentialCall->OperIs(GT_RET_EXPR))
1687216872
{
1687316873
// We need to preserve return expressions where the underlying call
1687416874
// has side effects. Otherwise early folding can result in us dropping

src/coreclr/jit/indirectcalltransformer.cpp

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ class IndirectCallTransformer
483483
GuardedDevirtualizationTransformer(Compiler* compiler, BasicBlock* block, Statement* stmt)
484484
: Transformer(compiler, block, stmt)
485485
, returnTemp(BAD_VAR_NUM)
486+
, returnValueUnused(false)
486487
{
487488
}
488489

@@ -786,9 +787,52 @@ class IndirectCallTransformer
786787
//
787788
// Note implicit by-ref returns should have already been converted
788789
// so any struct copy we induce here should be cheap.
789-
InlineCandidateInfo* const inlineInfo = origCall->GetGDVCandidateInfo(0);
790+
InlineCandidateInfo* const inlineInfo = origCall->GetGDVCandidateInfo(0);
791+
GenTree* const retExprNode = inlineInfo->retExpr;
790792

791-
if (!origCall->TypeIs(TYP_VOID))
793+
if (retExprNode == nullptr)
794+
{
795+
// We do not produce GT_RET_EXPRs for CTOR calls, so there is nothing to patch.
796+
return;
797+
}
798+
799+
GenTreeRetExpr* const retExpr = retExprNode->AsRetExpr();
800+
bool const noReturnValue = origCall->TypeIs(TYP_VOID);
801+
802+
// If there is a return value, search the next statement to see if we can find
803+
// retExprNode's parent. If we find it, see if retExprNode's value is unused.
804+
//
805+
// If we fail to find it, we will assume the return value is used.
806+
//
807+
if (!noReturnValue)
808+
{
809+
Statement* const nextStmt = stmt->GetNextStmt();
810+
if (nextStmt != nullptr)
811+
{
812+
Compiler::FindLinkData fld = compiler->gtFindLink(nextStmt, retExprNode);
813+
GenTree* const parent = fld.parent;
814+
815+
if ((parent != nullptr) && parent->OperIs(GT_COMMA) && (parent->AsOp()->gtGetOp1() == retExprNode))
816+
{
817+
returnValueUnused = true;
818+
JITDUMP("GT_RET_EXPR [%06u] value is unused\n", compiler->dspTreeID(retExprNode));
819+
}
820+
}
821+
}
822+
823+
if (noReturnValue)
824+
{
825+
JITDUMP("Linking GT_RET_EXPR [%06u] for VOID return to NOP\n",
826+
compiler->dspTreeID(inlineInfo->retExpr));
827+
inlineInfo->retExpr->gtSubstExpr = compiler->gtNewNothingNode();
828+
}
829+
else if (returnValueUnused)
830+
{
831+
JITDUMP("Linking GT_RET_EXPR [%06u] for UNUSED return to NOP\n",
832+
compiler->dspTreeID(inlineInfo->retExpr));
833+
inlineInfo->retExpr->gtSubstExpr = compiler->gtNewNothingNode();
834+
}
835+
else
792836
{
793837
// If there's a spill temp already associated with this inline candidate,
794838
// use that instead of allocating a new temp.
@@ -851,23 +895,6 @@ class IndirectCallTransformer
851895

852896
inlineInfo->retExpr->gtSubstExpr = tempTree;
853897
}
854-
else if (inlineInfo->retExpr != nullptr)
855-
{
856-
// We still oddly produce GT_RET_EXPRs for some void
857-
// returning calls. Just bash the ret expr to a NOP.
858-
//
859-
// Todo: consider bagging creation of these RET_EXPRs. The only possible
860-
// benefit they provide is stitching back larger trees for failed inlines
861-
// of void-returning methods. But then the calls likely sit in commas and
862-
// the benefit of a larger tree is unclear.
863-
JITDUMP("Linking GT_RET_EXPR [%06u] for VOID return to NOP\n",
864-
compiler->dspTreeID(inlineInfo->retExpr));
865-
inlineInfo->retExpr->gtSubstExpr = compiler->gtNewNothingNode();
866-
}
867-
else
868-
{
869-
// We do not produce GT_RET_EXPRs for CTOR calls, so there is nothing to patch.
870-
}
871898
}
872899

873900
//------------------------------------------------------------------------
@@ -1055,7 +1082,6 @@ class IndirectCallTransformer
10551082
if (oldRetExpr != nullptr)
10561083
{
10571084
inlineInfo->retExpr = compiler->gtNewInlineCandidateReturnExpr(call, call->TypeGet());
1058-
10591085
GenTree* newRetExpr = inlineInfo->retExpr;
10601086

10611087
if (returnTemp != BAD_VAR_NUM)
@@ -1065,7 +1091,9 @@ class IndirectCallTransformer
10651091
else
10661092
{
10671093
// We should always have a return temp if we return results by value
1068-
assert(origCall->TypeGet() == TYP_VOID);
1094+
// and that value is used.
1095+
assert((origCall->TypeGet() == TYP_VOID) || returnValueUnused);
1096+
newRetExpr = compiler->gtUnusedValNode(newRetExpr);
10691097
}
10701098
compiler->fgNewStmtAtEnd(block, newRetExpr);
10711099
}
@@ -1470,6 +1498,7 @@ class IndirectCallTransformer
14701498
unsigned returnTemp;
14711499
Statement* lastStmt;
14721500
bool checkFallsThrough;
1501+
bool returnValueUnused;
14731502

14741503
//------------------------------------------------------------------------
14751504
// CreateTreeForLookup: Create a tree representing a lookup of a method address.

src/coreclr/jit/morph.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5110,6 +5110,10 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
51105110
{
51115111
assert(ValidateUse(tree) && "Expected use of local to be tailcall value");
51125112
}
5113+
else if (IsCommaNop(tree))
5114+
{
5115+
// COMMA(NOP,NOP)
5116+
}
51135117
else
51145118
{
51155119
DISPTREE(tree);
@@ -5119,6 +5123,16 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
51195123
return WALK_CONTINUE;
51205124
}
51215125

5126+
bool IsCommaNop(GenTree* node)
5127+
{
5128+
if (!node->OperIs(GT_COMMA))
5129+
{
5130+
return false;
5131+
}
5132+
5133+
return node->AsOp()->gtGetOp1()->OperIs(GT_NOP) && node->AsOp()->gtGetOp2()->OperIs(GT_NOP);
5134+
}
5135+
51225136
bool ValidateUse(GenTree* node)
51235137
{
51245138
if (m_lclNum != BAD_VAR_NUM)

0 commit comments

Comments
 (0)