Skip to content

Commit f8e2f0a

Browse files
committed
JIT: rework tail call IR validity checks
Adapt `fgValidateIRForTailCall` to use as a utility to verify that the JIT has not added IR that would make a tail call invalid. Currently all our cases pass this check so no tail calls are invalidated. Remove `fgCheckStmtAfterTailCall` as it did less thorough and less correct checking. Contributes to dotnet#93246.
1 parent c2608bd commit f8e2f0a

File tree

2 files changed

+65
-122
lines changed

2 files changed

+65
-122
lines changed

src/coreclr/jit/compiler.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -5979,7 +5979,6 @@ class Compiler
59795979
bool fgCallArgWillPointIntoLocalFrame(GenTreeCall* call, CallArg& arg);
59805980

59815981
#endif
5982-
bool fgCheckStmtAfterTailCall();
59835982
GenTree* fgMorphTailCallViaHelpers(GenTreeCall* call, CORINFO_TAILCALL_HELPERS& help);
59845983
bool fgCanTailCallViaJitHelper(GenTreeCall* call);
59855984
void fgMorphTailCallViaJitHelper(GenTreeCall* call);
@@ -5999,7 +5998,7 @@ class Compiler
59995998
GenTree* getTokenHandleTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, bool parent);
60005999

60016000
GenTree* fgMorphPotentialTailCall(GenTreeCall* call);
6002-
void fgValidateIRForTailCall(GenTreeCall* call);
6001+
bool fgValidateIRForTailCall(GenTreeCall* call);
60036002
GenTree* fgGetStubAddrArg(GenTreeCall* call);
60046003
unsigned fgGetArgParameterLclNum(GenTreeCall* call, CallArg* arg);
60056004
void fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCall* recursiveTailCall);

src/coreclr/jit/morph.cpp

+64-120
Original file line numberDiff line numberDiff line change
@@ -5894,7 +5894,7 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call)
58945894
}
58955895
}
58965896

5897-
if (!fgCheckStmtAfterTailCall())
5897+
if (!fgValidateIRForTailCall(call))
58985898
{
58995899
failTailCall("Unexpected statements after the tail call");
59005900
return nullptr;
@@ -6088,8 +6088,6 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call)
60886088
#endif
60896089
}
60906090

6091-
fgValidateIRForTailCall(call);
6092-
60936091
// If this block has a flow successor, make suitable updates.
60946092
//
60956093
BasicBlock* nextBlock = compCurBB->GetUniqueSucc();
@@ -6340,21 +6338,25 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call)
63406338
// fgValidateIRForTailCall:
63416339
// Validate that the IR looks ok to perform a tailcall.
63426340
//
6341+
// Returns:
6342+
// false if IR after the tail call has non-negligble effects,
6343+
// in which case the tail call should be abandoned.
6344+
//
63436345
// Arguments:
63446346
// call - The call that we are dispatching as a tailcall.
63456347
//
63466348
// Notes:
63476349
// This function needs to handle somewhat complex IR that appears after
63486350
// tailcall candidates due to inlining.
63496351
//
6350-
void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
6352+
bool Compiler::fgValidateIRForTailCall(GenTreeCall* call)
63516353
{
6352-
#ifdef DEBUG
63536354
class TailCallIRValidatorVisitor final : public GenTreeVisitor<TailCallIRValidatorVisitor>
63546355
{
63556356
GenTreeCall* m_tailcall;
63566357
unsigned m_lclNum;
63576358
bool m_active;
6359+
bool m_isValid;
63586360

63596361
public:
63606362
enum
@@ -6363,14 +6365,23 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
63636365
UseExecutionOrder = true,
63646366
};
63656367

6368+
bool IsValid() const
6369+
{
6370+
return m_isValid;
6371+
}
6372+
void SetIsNotValid()
6373+
{
6374+
m_isValid = false;
6375+
}
6376+
63666377
TailCallIRValidatorVisitor(Compiler* comp, GenTreeCall* tailcall)
6367-
: GenTreeVisitor(comp), m_tailcall(tailcall), m_lclNum(BAD_VAR_NUM), m_active(false)
6378+
: GenTreeVisitor(comp), m_tailcall(tailcall), m_lclNum(BAD_VAR_NUM), m_active(false), m_isValid(true)
63686379
{
63696380
}
63706381

63716382
fgWalkResult PostOrderVisit(GenTree** use, GenTree* user)
63726383
{
6373-
GenTree* tree = *use;
6384+
GenTree* const tree = *use;
63746385

63756386
// Wait until we get to the actual call...
63766387
if (!m_active)
@@ -6385,8 +6396,13 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
63856396

63866397
if (tree->OperIs(GT_RETURN))
63876398
{
6388-
assert((tree->TypeIs(TYP_VOID) || ValidateUse(tree->gtGetOp1())) &&
6389-
"Expected return to be result of tailcall");
6399+
if (!tree->TypeIs(TYP_VOID) && !ValidateUse(tree->gtGetOp1()))
6400+
{
6401+
JITDUMP("Tail call validation failed: expected return to be result of tailcall\n");
6402+
DISPTREE(tree);
6403+
m_isValid = false;
6404+
}
6405+
63906406
return WALK_ABORT;
63916407
}
63926408

@@ -6409,17 +6425,32 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
64096425
//
64106426
else if (tree->OperIs(GT_STORE_LCL_VAR))
64116427
{
6412-
assert(ValidateUse(tree->AsLclVar()->Data()) && "Expected value of store to be result of tailcall");
6428+
if (!ValidateUse(tree->AsLclVar()->Data()))
6429+
{
6430+
JITDUMP("Tail call validation failed: [%06u] is not use of V%02u\n", m_compiler->dspTreeID(tree),
6431+
m_lclNum);
6432+
m_isValid = false;
6433+
return WALK_ABORT;
6434+
}
6435+
64136436
m_lclNum = tree->AsLclVar()->GetLclNum();
64146437
}
64156438
else if (tree->OperIs(GT_LCL_VAR))
64166439
{
6417-
assert(ValidateUse(tree) && "Expected use of local to be tailcall value");
6440+
if (!ValidateUse(tree))
6441+
{
6442+
JITDUMP("Tail call validation failed [%06u] is not use of V%02u\n", m_compiler->dspTreeID(tree),
6443+
m_lclNum);
6444+
m_isValid = false;
6445+
return WALK_ABORT;
6446+
}
64186447
}
64196448
else
64206449
{
6450+
JITDUMP("Tail call validation failed: unexpected tree\n");
64216451
DISPTREE(tree);
6422-
assert(!"Unexpected tree op after call marked as tailcall");
6452+
m_isValid = false;
6453+
return WALK_ABORT;
64236454
}
64246455

64256456
return WALK_CONTINUE;
@@ -6452,24 +6483,39 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
64526483
}
64536484
};
64546485

6486+
JITDUMP("Validating IR for tail call candidate [%06u] in " FMT_STMT "\n", dspTreeID(call), compCurStmt->GetID());
6487+
64556488
TailCallIRValidatorVisitor visitor(this, call);
6456-
for (Statement* stmt = compCurStmt; stmt != nullptr; stmt = stmt->GetNextStmt())
6489+
for (Statement* stmt = compCurStmt; visitor.IsValid() && (stmt != nullptr); stmt = stmt->GetNextStmt())
64576490
{
64586491
visitor.WalkTree(stmt->GetRootNodePointer(), nullptr);
64596492
}
64606493

64616494
BasicBlock* bb = compCurBB;
6462-
while (!bb->KindIs(BBJ_RETURN))
6495+
while (!bb->KindIs(BBJ_RETURN) && visitor.IsValid())
64636496
{
6464-
bb = bb->GetUniqueSucc();
6465-
assert((bb != nullptr) && "Expected straight flow after tailcall");
6497+
BasicBlock* const succBB = bb->GetUniqueSucc();
64666498

6467-
for (Statement* stmt : bb->Statements())
6499+
if (succBB == nullptr)
6500+
{
6501+
JITDUMP("Tail call validation failed: " FMT_BB " does not have linear flow\n", bb->bbNum);
6502+
visitor.SetIsNotValid();
6503+
break;
6504+
}
6505+
6506+
for (Statement* stmt : succBB->Statements())
64686507
{
64696508
visitor.WalkTree(stmt->GetRootNodePointer(), nullptr);
6509+
if (!visitor.IsValid())
6510+
{
6511+
break;
6512+
}
64706513
}
6514+
6515+
bb = succBB;
64716516
}
6472-
#endif
6517+
6518+
return visitor.IsValid();
64736519
}
64746520

64756521
//------------------------------------------------------------------------
@@ -15367,108 +15413,6 @@ void Compiler::fgMarkDemotedImplicitByRefArgs()
1536715413
#endif // FEATURE_IMPLICIT_BYREFS
1536815414
}
1536915415

15370-
//------------------------------------------------------------------------
15371-
// fgCheckStmtAfterTailCall: check that statements after the tail call stmt
15372-
// candidate are in one of expected forms, that are desctibed below.
15373-
//
15374-
// Return Value:
15375-
// 'true' if stmts are in the expected form, else 'false'.
15376-
//
15377-
bool Compiler::fgCheckStmtAfterTailCall()
15378-
{
15379-
15380-
// For void calls, we would have created a GT_CALL in the stmt list.
15381-
// For non-void calls, we would have created a GT_RETURN(GT_CAST(GT_CALL)).
15382-
// For calls returning structs, we would have a void call, followed by a void return.
15383-
// For debuggable code, it would be an assignment of the call to a temp
15384-
// We want to get rid of any of this extra trees, and just leave
15385-
// the call.
15386-
Statement* callStmt = fgMorphStmt;
15387-
15388-
Statement* nextMorphStmt = callStmt->GetNextStmt();
15389-
15390-
// Check that the rest stmts in the block are in one of the following pattern:
15391-
// 1) ret(void)
15392-
// 2) ret(cast*(callResultLclVar))
15393-
// 3) lclVar = callResultLclVar, the actual ret(lclVar) in another block
15394-
// 4) nop
15395-
if (nextMorphStmt != nullptr)
15396-
{
15397-
GenTree* callExpr = callStmt->GetRootNode();
15398-
if (!callExpr->OperIs(GT_STORE_LCL_VAR))
15399-
{
15400-
// The next stmt can be GT_RETURN(TYP_VOID) or GT_RETURN(lclVar),
15401-
// where lclVar was return buffer in the call for structs or simd.
15402-
Statement* retStmt = nextMorphStmt;
15403-
GenTree* retExpr = retStmt->GetRootNode();
15404-
noway_assert(retExpr->gtOper == GT_RETURN);
15405-
15406-
nextMorphStmt = retStmt->GetNextStmt();
15407-
}
15408-
else
15409-
{
15410-
noway_assert(callExpr->OperIs(GT_STORE_LCL_VAR));
15411-
unsigned callResultLclNumber = callExpr->AsLclVar()->GetLclNum();
15412-
15413-
#if FEATURE_TAILCALL_OPT_SHARED_RETURN
15414-
15415-
// We can have a chain of assignments from the call result to
15416-
// various inline return spill temps. These are ok as long
15417-
// as the last one ultimately provides the return value or is ignored.
15418-
//
15419-
// And if we're returning a small type we may see a cast
15420-
// on the source side.
15421-
while ((nextMorphStmt != nullptr) && (nextMorphStmt->GetRootNode()->OperIs(GT_STORE_LCL_VAR, GT_NOP)))
15422-
{
15423-
if (nextMorphStmt->GetRootNode()->OperIs(GT_NOP))
15424-
{
15425-
nextMorphStmt = nextMorphStmt->GetNextStmt();
15426-
continue;
15427-
}
15428-
Statement* moveStmt = nextMorphStmt;
15429-
GenTree* moveExpr = nextMorphStmt->GetRootNode();
15430-
15431-
// Tunnel through any casts on the source side.
15432-
GenTree* moveSource = moveExpr->AsLclVar()->Data();
15433-
while (moveSource->OperIs(GT_CAST))
15434-
{
15435-
noway_assert(!moveSource->gtOverflow());
15436-
moveSource = moveSource->gtGetOp1();
15437-
}
15438-
noway_assert(moveSource->OperIsLocal());
15439-
15440-
// Verify we're just passing the value from one local to another
15441-
// along the chain.
15442-
const unsigned srcLclNum = moveSource->AsLclVarCommon()->GetLclNum();
15443-
noway_assert(srcLclNum == callResultLclNumber);
15444-
const unsigned dstLclNum = moveExpr->AsLclVar()->GetLclNum();
15445-
callResultLclNumber = dstLclNum;
15446-
15447-
nextMorphStmt = moveStmt->GetNextStmt();
15448-
}
15449-
if (nextMorphStmt != nullptr)
15450-
#endif
15451-
{
15452-
Statement* retStmt = nextMorphStmt;
15453-
GenTree* retExpr = nextMorphStmt->GetRootNode();
15454-
noway_assert(retExpr->gtOper == GT_RETURN);
15455-
15456-
GenTree* treeWithLcl = retExpr->gtGetOp1();
15457-
while (treeWithLcl->gtOper == GT_CAST)
15458-
{
15459-
noway_assert(!treeWithLcl->gtOverflow());
15460-
treeWithLcl = treeWithLcl->gtGetOp1();
15461-
}
15462-
15463-
noway_assert(callResultLclNumber == treeWithLcl->AsLclVarCommon()->GetLclNum());
15464-
15465-
nextMorphStmt = retStmt->GetNextStmt();
15466-
}
15467-
}
15468-
}
15469-
return nextMorphStmt == nullptr;
15470-
}
15471-
1547215416
//------------------------------------------------------------------------
1547315417
// fgCanTailCallViaJitHelper: check whether we can use the faster tailcall
1547415418
// JIT helper on x86.

0 commit comments

Comments
 (0)