Skip to content

Commit 8115429

Browse files
authored
JIT: Disallow forward substitution of async calls (#115936)
This can be forwarded into an overlapping byref temp, which is invalid IR. We could look for that condition specifically, but that's more complex. Do the simple thing for now and skip forward substituting trees containing async calls. In the future when we have async contexts in SPMI collections we can try the more precise check and see if it makes a difference. The opposite might also be possible: forward substituting a TYP_BYREF into a tree with an async call. However, I am not 100% sure that can create overlapping lifetimes, so let's wait and see if Fuzzlyn comes up with an example.
1 parent d896e85 commit 8115429

File tree

3 files changed

+77
-15
lines changed

3 files changed

+77
-15
lines changed

src/coreclr/jit/compiler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6160,7 +6160,10 @@ class Compiler
61606160
PhaseStatus fgHeadTailMerge(bool early);
61616161
bool fgHeadMerge(BasicBlock* block, bool early);
61626162
bool fgTryOneHeadMerge(BasicBlock* block, bool early);
6163+
template<typename Predicate>
6164+
bool gtTreeContainsCall(GenTree* tree, Predicate pred);
61636165
bool gtTreeContainsTailCall(GenTree* tree);
6166+
bool gtTreeContainsAsyncCall(GenTree* tree);
61646167
bool fgCanMoveFirstStatementIntoPred(bool early, Statement* firstStmt, BasicBlock* pred);
61656168

61666169
enum FG_RELOCATE_TYPE

src/coreclr/jit/fgopt.cpp

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5605,29 +5605,33 @@ bool Compiler::fgHeadMerge(BasicBlock* block, bool early)
56055605
}
56065606

56075607
//------------------------------------------------------------------------
5608-
// gtTreeContainsTailCall: Check if a tree contains any tail call or tail call
5609-
// candidate.
5608+
// gtTreeContainsCall:
5609+
// Check if a tree contains a call node matching the given predicate.
56105610
//
56115611
// Parameters:
56125612
// tree - The tree
5613+
// pred - Predicate that the call must match
56135614
//
5614-
// Remarks:
5615-
// While tail calls are generally expected to be top level nodes we do allow
5616-
// some other shapes of calls to be tail calls, including some cascading
5617-
// trivial assignments and casts. This function does a tree walk to check if
5618-
// any sub tree is a tail call.
5615+
// Returns:
5616+
// True if a call node matching the predicate was found, false otherwise.
56195617
//
5620-
bool Compiler::gtTreeContainsTailCall(GenTree* tree)
5618+
template <typename Predicate>
5619+
bool Compiler::gtTreeContainsCall(GenTree* tree, Predicate pred)
56215620
{
5622-
struct HasTailCallCandidateVisitor : GenTreeVisitor<HasTailCallCandidateVisitor>
5621+
struct HasCallVisitor : GenTreeVisitor<HasCallVisitor>
56235622
{
5623+
private:
5624+
Predicate& m_pred;
5625+
5626+
public:
56245627
enum
56255628
{
56265629
DoPreOrder = true
56275630
};
56285631

5629-
HasTailCallCandidateVisitor(Compiler* comp)
5630-
: GenTreeVisitor(comp)
5632+
HasCallVisitor(Compiler* comp, Predicate& pred)
5633+
: GenTreeVisitor<HasCallVisitor>(comp)
5634+
, m_pred(pred)
56315635
{
56325636
}
56335637

@@ -5639,7 +5643,7 @@ bool Compiler::gtTreeContainsTailCall(GenTree* tree)
56395643
return WALK_SKIP_SUBTREES;
56405644
}
56415645

5642-
if (node->IsCall() && (node->AsCall()->CanTailCall() || node->AsCall()->IsTailCall()))
5646+
if (node->IsCall() && m_pred(node->AsCall()))
56435647
{
56445648
return WALK_ABORT;
56455649
}
@@ -5648,8 +5652,54 @@ bool Compiler::gtTreeContainsTailCall(GenTree* tree)
56485652
}
56495653
};
56505654

5651-
HasTailCallCandidateVisitor visitor(this);
5652-
return visitor.WalkTree(&tree, nullptr) == WALK_ABORT;
5655+
if ((tree->gtFlags & GTF_CALL) == 0)
5656+
{
5657+
return false;
5658+
}
5659+
5660+
HasCallVisitor hasCall(this, pred);
5661+
return hasCall.WalkTree(&tree, nullptr) == WALK_ABORT;
5662+
}
5663+
5664+
//------------------------------------------------------------------------
5665+
// gtTreeContainsTailCall: Check if a tree contains any tail call or tail call
5666+
// candidate.
5667+
//
5668+
// Parameters:
5669+
// tree - The tree
5670+
//
5671+
// Remarks:
5672+
// While tail calls are generally expected to be top level nodes we do allow
5673+
// some other shapes of calls to be tail calls, including some cascading
5674+
// trivial assignments and casts. This function does a tree walk to check if
5675+
// any sub tree is a tail call.
5676+
//
5677+
bool Compiler::gtTreeContainsTailCall(GenTree* tree)
5678+
{
5679+
return gtTreeContainsCall(tree, [](GenTreeCall* call) {
5680+
return call->CanTailCall() || call->IsTailCall();
5681+
});
5682+
}
5683+
5684+
//------------------------------------------------------------------------
5685+
// gtTreeContainsAsyncCall: Check if a tree contains any async call.
5686+
//
5687+
// Parameters:
5688+
// tree - The tree to check
5689+
//
5690+
// Returns:
5691+
// True if any node in the tree is an async call, false otherwise.
5692+
//
5693+
bool Compiler::gtTreeContainsAsyncCall(GenTree* tree)
5694+
{
5695+
if (!compIsAsync())
5696+
{
5697+
return false;
5698+
}
5699+
5700+
return gtTreeContainsCall(tree, [](GenTreeCall* call) {
5701+
return call->IsAsync();
5702+
});
56535703
}
56545704

56555705
//------------------------------------------------------------------------

src/coreclr/jit/forwardsub.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,19 +501,28 @@ bool Compiler::fgForwardSubStatement(Statement* stmt)
501501
// Can't substitute GT_CATCH_ARG.
502502
// Can't substitute GT_LCLHEAP.
503503
//
504-
// Don't substitute a no return call (trips up morph in some cases).
505504
if (fwdSubNode->OperIs(GT_CATCH_ARG, GT_LCLHEAP, GT_ASYNC_CONTINUATION))
506505
{
507506
JITDUMP(" tree to sub is catch arg, or lcl heap\n");
508507
return false;
509508
}
510509

510+
// Don't substitute a no return call (trips up morph in some cases).
511+
//
511512
if (fwdSubNode->IsCall() && fwdSubNode->AsCall()->IsNoReturn())
512513
{
513514
JITDUMP(" tree to sub is a 'no return' call\n");
514515
return false;
515516
}
516517

518+
// Do not substitute async calls; if the target node has a temp BYREF node,
519+
// that creates illegal IR.
520+
if (gtTreeContainsAsyncCall(fwdSubNode))
521+
{
522+
JITDUMP(" tree has an async call\n");
523+
return false;
524+
}
525+
517526
// Bail if sub node has embedded stores.
518527
//
519528
if ((fwdSubNode->gtFlags & GTF_ASG) != 0)

0 commit comments

Comments
 (0)