@@ -5894,7 +5894,7 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call)
5894
5894
}
5895
5895
}
5896
5896
5897
- if (!fgCheckStmtAfterTailCall( ))
5897
+ if (!fgValidateIRForTailCall(call ))
5898
5898
{
5899
5899
failTailCall("Unexpected statements after the tail call");
5900
5900
return nullptr;
@@ -6088,8 +6088,6 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call)
6088
6088
#endif
6089
6089
}
6090
6090
6091
- fgValidateIRForTailCall(call);
6092
-
6093
6091
// If this block has a flow successor, make suitable updates.
6094
6092
//
6095
6093
BasicBlock* nextBlock = compCurBB->GetUniqueSucc();
@@ -6340,21 +6338,25 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call)
6340
6338
// fgValidateIRForTailCall:
6341
6339
// Validate that the IR looks ok to perform a tailcall.
6342
6340
//
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
+ //
6343
6345
// Arguments:
6344
6346
// call - The call that we are dispatching as a tailcall.
6345
6347
//
6346
6348
// Notes:
6347
6349
// This function needs to handle somewhat complex IR that appears after
6348
6350
// tailcall candidates due to inlining.
6349
6351
//
6350
- void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
6352
+ bool Compiler::fgValidateIRForTailCall(GenTreeCall* call)
6351
6353
{
6352
- #ifdef DEBUG
6353
6354
class TailCallIRValidatorVisitor final : public GenTreeVisitor<TailCallIRValidatorVisitor>
6354
6355
{
6355
6356
GenTreeCall* m_tailcall;
6356
6357
unsigned m_lclNum;
6357
6358
bool m_active;
6359
+ bool m_isValid;
6358
6360
6359
6361
public:
6360
6362
enum
@@ -6363,14 +6365,23 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
6363
6365
UseExecutionOrder = true,
6364
6366
};
6365
6367
6368
+ bool IsValid() const
6369
+ {
6370
+ return m_isValid;
6371
+ }
6372
+ void SetIsNotValid()
6373
+ {
6374
+ m_isValid = false;
6375
+ }
6376
+
6366
6377
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)
6368
6379
{
6369
6380
}
6370
6381
6371
6382
fgWalkResult PostOrderVisit(GenTree** use, GenTree* user)
6372
6383
{
6373
- GenTree* tree = *use;
6384
+ GenTree* const tree = *use;
6374
6385
6375
6386
// Wait until we get to the actual call...
6376
6387
if (!m_active)
@@ -6385,8 +6396,13 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
6385
6396
6386
6397
if (tree->OperIs(GT_RETURN))
6387
6398
{
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
+
6390
6406
return WALK_ABORT;
6391
6407
}
6392
6408
@@ -6409,17 +6425,32 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
6409
6425
//
6410
6426
else if (tree->OperIs(GT_STORE_LCL_VAR))
6411
6427
{
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
+
6413
6436
m_lclNum = tree->AsLclVar()->GetLclNum();
6414
6437
}
6415
6438
else if (tree->OperIs(GT_LCL_VAR))
6416
6439
{
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
+ }
6418
6447
}
6419
6448
else
6420
6449
{
6450
+ JITDUMP("Tail call validation failed: unexpected tree\n");
6421
6451
DISPTREE(tree);
6422
- assert(!"Unexpected tree op after call marked as tailcall");
6452
+ m_isValid = false;
6453
+ return WALK_ABORT;
6423
6454
}
6424
6455
6425
6456
return WALK_CONTINUE;
@@ -6452,24 +6483,39 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call)
6452
6483
}
6453
6484
};
6454
6485
6486
+ JITDUMP("Validating IR for tail call candidate [%06u] in " FMT_STMT "\n", dspTreeID(call), compCurStmt->GetID());
6487
+
6455
6488
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())
6457
6490
{
6458
6491
visitor.WalkTree(stmt->GetRootNodePointer(), nullptr);
6459
6492
}
6460
6493
6461
6494
BasicBlock* bb = compCurBB;
6462
- while (!bb->KindIs(BBJ_RETURN))
6495
+ while (!bb->KindIs(BBJ_RETURN) && visitor.IsValid() )
6463
6496
{
6464
- bb = bb->GetUniqueSucc();
6465
- assert((bb != nullptr) && "Expected straight flow after tailcall");
6497
+ BasicBlock* const succBB = bb->GetUniqueSucc();
6466
6498
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())
6468
6507
{
6469
6508
visitor.WalkTree(stmt->GetRootNodePointer(), nullptr);
6509
+ if (!visitor.IsValid())
6510
+ {
6511
+ break;
6512
+ }
6470
6513
}
6514
+
6515
+ bb = succBB;
6471
6516
}
6472
- #endif
6517
+
6518
+ return visitor.IsValid();
6473
6519
}
6474
6520
6475
6521
//------------------------------------------------------------------------
@@ -15367,108 +15413,6 @@ void Compiler::fgMarkDemotedImplicitByRefArgs()
15367
15413
#endif // FEATURE_IMPLICIT_BYREFS
15368
15414
}
15369
15415
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
-
15472
15416
//------------------------------------------------------------------------
15473
15417
// fgCanTailCallViaJitHelper: check whether we can use the faster tailcall
15474
15418
// JIT helper on x86.
0 commit comments