Skip to content

Commit

Permalink
JIT: Allow helper calls that always throw to be marked as no-return (d…
Browse files Browse the repository at this point in the history
…otnet#100900)

Fixes dotnet#100458 by addressing two issues:

When flagging a call node as no-return with GTF_CALL_M_DOES_NOT_RETURN, we should always increment Compiler::optNoReturnCallCount to avoid asserts in Compiler::fgTailMergeThrows. Previously, we weren't doing this in a unified place, which seemed error-prone.
When incrementing the optNoReturnCallCount member of an inlinee compiler, ensure this information is propagated to the inlinee's parent compiler. In a similar vein, if we try to inline a call, and the inlinee compiler determines it does not return, make sure we increment optNoReturnCallCount in the parent compiler object if the inline fails -- we've kept the call, and we now know it doesn't return.
With these changes, I can now mark helper calls that always throw as no-return; this unblocks morph to convert BBJ_ALWAYS blocks with helper calls that throw into BBJ_THROW blocks, and has the nice side effect of improving the efficacy of throw merging. Since I was touching relevant code, I decided to improve our usage of GenTreeCall::IsHelperCall, too.
  • Loading branch information
amanasifkhalid authored and matouskozak committed Apr 30, 2024
1 parent 4bb0bc7 commit ae442e5
Show file tree
Hide file tree
Showing 24 changed files with 91 additions and 78 deletions.
4 changes: 2 additions & 2 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2327,8 +2327,8 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
std::swap(op1, op2);
}
// Validate op1 and op2
if ((op1->gtOper != GT_CALL) || (op1->AsCall()->gtCallType != CT_HELPER) || (op1->TypeGet() != TYP_REF) || // op1
(op2->gtOper != GT_CNS_INT) || (op2->AsIntCon()->gtIconVal != 0)) // op2
if (!op1->OperIs(GT_CALL) || !op1->AsCall()->IsHelperCall() || !op1->TypeIs(TYP_REF) || // op1
!op2->OperIs(GT_CNS_INT) || (op2->AsIntCon()->gtIconVal != 0)) // op2
{
return NO_ASSERTION_INDEX;
}
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3530,7 +3530,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
#ifdef DEBUG
// Pass the call signature information down into the emitter so the emitter can associate
// native call sites with the signatures they were generated from.
if (call->gtCallType != CT_HELPER)
if (!call->IsHelperCall())
{
sigInfo = call->callSig;
}
Expand Down Expand Up @@ -3693,7 +3693,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
else
{
// Generate a direct call to a non-virtual user defined or helper method
assert(call->gtCallType == CT_HELPER || call->gtCallType == CT_USER_FUNC);
assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC));

void* addr = nullptr;
#ifdef FEATURE_READYTORUN
Expand All @@ -3704,7 +3704,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
}
else
#endif // FEATURE_READYTORUN
if (call->gtCallType == CT_HELPER)
if (call->IsHelperCall())
{
CorInfoHelpFunc helperNum = compiler->eeGetHelperNum(methHnd);
noway_assert(helperNum != CORINFO_HELP_UNDEF);
Expand Down
17 changes: 5 additions & 12 deletions src/coreclr/jit/codegenlinear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -721,9 +721,7 @@ void CodeGen::genCodeForBBlist()

if ((call != nullptr) && (call->gtOper == GT_CALL))
{
if ((call->AsCall()->gtCallMoreFlags & GTF_CALL_M_DOES_NOT_RETURN) != 0 ||
((call->AsCall()->gtCallType == CT_HELPER) &&
Compiler::s_helperCallProperties.AlwaysThrow(call->AsCall()->GetHelperNum())))
if (call->AsCall()->IsNoReturn())
{
instGen(INS_BREAKPOINT); // This should never get executed
}
Expand Down Expand Up @@ -761,19 +759,14 @@ void CodeGen::genCodeForBBlist()

case BBJ_ALWAYS:
{
#ifdef DEBUG
GenTree* call = block->lastNode();
if ((call != nullptr) && (call->gtOper == GT_CALL))
{
if ((call->AsCall()->gtCallMoreFlags & GTF_CALL_M_DOES_NOT_RETURN) != 0 ||
((call->AsCall()->gtCallType == CT_HELPER) &&
Compiler::s_helperCallProperties.AlwaysThrow(call->AsCall()->GetHelperNum())))
{
// NOTE: We should probably never see a BBJ_ALWAYS block ending with a throw in a first place.
// If that is fixed, this condition can be just an assert.
// For the reasons why we insert a BP, see the similar code in "case BBJ_THROW:" above.
instGen(INS_BREAKPOINT); // This should never get executed
}
// At this point, BBJ_ALWAYS should never end with a call that doesn't return.
assert(!call->AsCall()->IsNoReturn());
}
#endif // DEBUG

// If this block jumps to the next one, we might be able to skip emitting the jump
if (block->CanRemoveJumpToNext(compiler))
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/jit/codegenloongarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6518,7 +6518,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
#ifdef DEBUG
// Pass the call signature information down into the emitter so the emitter can associate
// native call sites with the signatures they were generated from.
if (call->gtCallType != CT_HELPER)
if (!call->IsHelperCall())
{
sigInfo = call->callSig;
}
Expand Down Expand Up @@ -6635,7 +6635,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
else
{
// Generate a direct call to a non-virtual user defined or helper method
assert(call->gtCallType == CT_HELPER || call->gtCallType == CT_USER_FUNC);
assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC));

void* addr = nullptr;
#ifdef FEATURE_READYTORUN
Expand All @@ -6646,7 +6646,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
}
else
#endif // FEATURE_READYTORUN
if (call->gtCallType == CT_HELPER)
if (call->IsHelperCall())
{
CorInfoHelpFunc helperNum = compiler->eeGetHelperNum(methHnd);
noway_assert(helperNum != CORINFO_HELP_UNDEF);
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/jit/codegenriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6594,7 +6594,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
#ifdef DEBUG
// Pass the call signature information down into the emitter so the emitter can associate
// native call sites with the signatures they were generated from.
if (call->gtCallType != CT_HELPER)
if (!call->IsHelperCall())
{
sigInfo = call->callSig;
}
Expand Down Expand Up @@ -6711,7 +6711,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
else
{
// Generate a direct call to a non-virtual user defined or helper method
assert(call->gtCallType == CT_HELPER || call->gtCallType == CT_USER_FUNC);
assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC));

void* addr = nullptr;
#ifdef FEATURE_READYTORUN
Expand All @@ -6722,7 +6722,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
}
else
#endif // FEATURE_READYTORUN
if (call->gtCallType == CT_HELPER)
if (call->IsHelperCall())
{
CorInfoHelpFunc helperNum = compiler->eeGetHelperNum(methHnd);
noway_assert(helperNum != CORINFO_HELP_UNDEF);
Expand Down
8 changes: 4 additions & 4 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6132,7 +6132,7 @@ void CodeGen::genCall(GenTreeCall* call)
// This needs to be here, rather than above where fPossibleSyncHelperCall is set,
// so the GC state vars have been updated before creating the label.

if ((call->gtCallType == CT_HELPER) && (compiler->info.compFlags & CORINFO_FLG_SYNCH))
if (call->IsHelperCall() && (compiler->info.compFlags & CORINFO_FLG_SYNCH))
{
CorInfoHelpFunc helperNum = compiler->eeGetHelperNum(call->gtCallMethHnd);
noway_assert(helperNum != CORINFO_HELP_UNDEF);
Expand Down Expand Up @@ -6239,7 +6239,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA
#ifdef DEBUG
// Pass the call signature information down into the emitter so the emitter can associate
// native call sites with the signatures they were generated from.
if (call->gtCallType != CT_HELPER)
if (!call->IsHelperCall())
{
sigInfo = call->callSig;
}
Expand Down Expand Up @@ -6439,10 +6439,10 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA
else
{
// Generate a direct call to a non-virtual user defined or helper method
assert(call->gtCallType == CT_HELPER || call->gtCallType == CT_USER_FUNC);
assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC));

void* addr = nullptr;
if (call->gtCallType == CT_HELPER)
if (call->IsHelperCall())
{
// Direct call to a helper method.
CorInfoHelpFunc helperNum = compiler->eeGetHelperNum(methHnd);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8407,7 +8407,7 @@ void Compiler::compCallArgStats()

if (call->AsCall()->gtCallThisArg == nullptr)
{
if (call->AsCall()->gtCallType == CT_HELPER)
if (call->AsCall()->IsHelperCall())
{
argHelperCalls++;
}
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7412,6 +7412,14 @@ class Compiler
optNoReturnCallCount++;
}

void setCallDoesNotReturn(GenTreeCall* const call)
{
assert(call != nullptr);
assert(!call->IsNoReturn());
call->gtCallMoreFlags |= GTF_CALL_M_DOES_NOT_RETURN;
setMethodHasNoReturnCalls();
}

unsigned optNoReturnCallCount;

// Recursion bound controls how far we can go backwards tracking for a SSA value.
Expand Down
15 changes: 11 additions & 4 deletions src/coreclr/jit/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1384,10 +1384,17 @@ inline GenTree* Compiler::gtNewIconEmbFldHndNode(CORINFO_FIELD_HANDLE fldHnd)
inline GenTreeCall* Compiler::gtNewHelperCallNode(
unsigned helper, var_types type, GenTree* arg1, GenTree* arg2, GenTree* arg3)
{
GenTreeFlags flags = s_helperCallProperties.NoThrow((CorInfoHelpFunc)helper) ? GTF_EMPTY : GTF_EXCEPT;
GenTreeCall* result = gtNewCallNode(CT_HELPER, eeFindHelper(helper), type);
result->gtFlags |= flags;
GenTreeCall* const result = gtNewCallNode(CT_HELPER, eeFindHelper(helper), type);

if (!s_helperCallProperties.NoThrow((CorInfoHelpFunc)helper))
{
result->gtFlags |= GTF_EXCEPT;

if (s_helperCallProperties.AlwaysThrow((CorInfoHelpFunc)helper))
{
setCallDoesNotReturn(result);
}
}
#if DEBUG
// Helper calls are never candidates.

Expand Down Expand Up @@ -3747,7 +3754,7 @@ inline bool Compiler::IsStaticHelperEligibleForExpansion(GenTree* tree, bool* is

inline bool Compiler::IsSharedStaticHelper(GenTree* tree)
{
if (tree->gtOper != GT_CALL || tree->AsCall()->gtCallType != CT_HELPER)
if (!tree->OperIs(GT_CALL) || !tree->AsCall()->IsHelperCall())
{
return false;
}
Expand Down
4 changes: 1 addition & 3 deletions src/coreclr/jit/fgbasic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2645,9 +2645,7 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed
info.compCompHnd->notifyMethodInfoUsage(impInlineInfo->iciCall->gtCallMethHnd))
{
// Mark the call node as "no return" as it can impact caller's code quality.
impInlineInfo->iciCall->gtCallMoreFlags |= GTF_CALL_M_DOES_NOT_RETURN;
// Mark root method as containing a noreturn call.
impInlineRoot()->setMethodHasNoReturnCalls();
setCallDoesNotReturn(impInlineInfo->iciCall);

// NOTE: we also ask VM whether we're allowed to do so - we don't want to mark a call
// as "no-return" if its IL may change.
Expand Down
7 changes: 3 additions & 4 deletions src/coreclr/jit/fgehopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1910,7 +1910,7 @@ PhaseStatus Compiler::fgTailMergeThrows()
// The second pass modifies flow so that predecessors of
// non-canonical throw blocks now transfer control to the
// appropriate canonical block.
int numCandidates = 0;
unsigned numCandidates = 0;

// First pass
//
Expand Down Expand Up @@ -1960,9 +1960,6 @@ PhaseStatus Compiler::fgTailMergeThrows()
continue;
}

// Sanity check -- only user funcs should be marked do not return
assert(call->gtCallType == CT_USER_FUNC);

// Ok, we've found a suitable call. See if this is one we know
// about already, or something new.
BasicBlock* canonicalBlock = nullptr;
Expand All @@ -1987,6 +1984,8 @@ PhaseStatus Compiler::fgTailMergeThrows()
}
}

assert(numCandidates <= optNoReturnCallCount);

// Bail if no candidates were found
if (numCandidates == 0)
{
Expand Down
11 changes: 10 additions & 1 deletion src/coreclr/jit/fginline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,12 @@ void Compiler::fgMorphCallInline(GenTreeCall* call, InlineResult* inlineResult)
noway_assert(fgMorphStmt->GetRootNode() == call);
fgMorphStmt->SetRootNode(gtNewNothingNode());
}

// Inlinee compiler may have determined call does not return; if so, update this compiler's state.
if (call->IsNoReturn())
{
setMethodHasNoReturnCalls();
}
}
}

Expand Down Expand Up @@ -1520,7 +1526,7 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
}

//
// At this point, we have successully inserted inlinee's code.
// At this point, we have successfully inserted inlinee's code.
//

//
Expand Down Expand Up @@ -1581,6 +1587,9 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
}
}

// Update no-return call count
optNoReturnCallCount += InlineeCompiler->optNoReturnCallCount;

// Update optMethodFlags

#ifdef DEBUG
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,8 +609,9 @@ bool Compiler::fgIsThrow(GenTree* tree)
return false;
}
GenTreeCall* call = tree->AsCall();
if ((call->gtCallType == CT_HELPER) && s_helperCallProperties.AlwaysThrow(eeGetHelperNum(call->gtCallMethHnd)))
if (call->IsHelperCall() && s_helperCallProperties.AlwaysThrow(eeGetHelperNum(call->gtCallMethHnd)))
{
assert(call->IsNoReturn());
noway_assert(call->gtFlags & GTF_EXCEPT);
return true;
}
Expand Down
18 changes: 9 additions & 9 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,7 @@ GenTree* Compiler::getArrayLengthFromAllocation(GenTree* tree DEBUGARG(BasicBloc
{
GenTreeCall* call = tree->AsCall();

if (call->gtCallType == CT_HELPER)
if (call->IsHelperCall())
{
CorInfoHelpFunc helper = eeGetHelperNum(call->gtCallMethHnd);
switch (helper)
Expand Down Expand Up @@ -2380,7 +2380,7 @@ bool GenTreeCall::HasSideEffects(Compiler* compiler, bool ignoreExceptions, bool
{
// Generally all GT_CALL nodes are considered to have side-effects, but we may have extra information about helper
// calls that can prove them side-effect-free.
if (gtCallType != CT_HELPER)
if (!IsHelperCall())
{
// If needed, we can annotate other special intrinsic methods as side effect free as well.
if (IsSpecialIntrinsic(compiler, NI_System_Type_GetTypeFromHandle))
Expand Down Expand Up @@ -10997,7 +10997,7 @@ void Compiler::gtDispNodeName(GenTree* tree)
callType = "CALLV";
}
}
else if (tree->AsCall()->gtCallType == CT_HELPER)
else if (tree->AsCall()->IsHelperCall())
{
ctType = " help";
}
Expand Down Expand Up @@ -16726,7 +16726,7 @@ bool Compiler::gtTreeHasSideEffects(GenTree* tree, GenTreeFlags flags /* = GTF_S
{
// Generally all trees that contain GT_CALL nodes are considered to have side-effects.
//
if (tree->AsCall()->gtCallType == CT_HELPER)
if (tree->AsCall()->IsHelperCall())
{
// If this node is a helper call we may not care about the side-effects.
// Note that gtNodeHasSideEffects checks the side effects of the helper itself
Expand Down Expand Up @@ -17188,7 +17188,7 @@ void Compiler::gtExtractSideEffList(GenTree* expr,
// Generally all GT_CALL nodes are considered to have side-effects.
// So if we get here it must be a helper call that we decided it does
// not have side effects that we needed to keep.
assert(!node->OperIs(GT_CALL) || (node->AsCall()->gtCallType == CT_HELPER));
assert(!node->OperIs(GT_CALL) || node->AsCall()->IsHelperCall());
}

if ((m_flags & GTF_IS_IN_CSE) != 0)
Expand Down Expand Up @@ -17486,7 +17486,7 @@ Compiler::TypeProducerKind Compiler::gtGetTypeProducerKind(GenTree* tree)
{
if (tree->gtOper == GT_CALL)
{
if (tree->AsCall()->gtCallType == CT_HELPER)
if (tree->AsCall()->IsHelperCall())
{
if (gtIsTypeHandleToRuntimeTypeHelper(tree->AsCall()))
{
Expand Down Expand Up @@ -18568,7 +18568,7 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* pIsExact, b
objClass = sig.retTypeClass;
}
}
else if (call->gtCallType == CT_HELPER)
else if (call->IsHelperCall())
{
objClass = gtGetHelperCallClassHandle(call, pIsExact, pIsNonNull);
}
Expand Down Expand Up @@ -18735,7 +18735,7 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* pIsExact, b
//
CORINFO_CLASS_HANDLE Compiler::gtGetHelperCallClassHandle(GenTreeCall* call, bool* pIsExact, bool* pIsNonNull)
{
assert(call->gtCallType == CT_HELPER);
assert(call->IsHelperCall());

*pIsNonNull = false;
*pIsExact = false;
Expand Down Expand Up @@ -27082,7 +27082,7 @@ genTreeOps GenTreeHWIntrinsic::HWOperGet() const
GenTree* Compiler::gtNewMustThrowException(unsigned helper, var_types type, CORINFO_CLASS_HANDLE clsHnd)
{
GenTreeCall* node = gtNewHelperCallNode(helper, TYP_VOID);
node->gtCallMoreFlags |= GTF_CALL_M_DOES_NOT_RETURN;
assert(node->IsNoReturn());
if (type != TYP_VOID)
{
unsigned dummyTemp = lvaGrabTemp(true DEBUGARG("dummy temp of must thrown exception"));
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -9987,7 +9987,7 @@ inline bool GenTree::IsCnsVec() const

inline bool GenTree::IsHelperCall()
{
return OperGet() == GT_CALL && AsCall()->gtCallType == CT_HELPER;
return OperGet() == GT_CALL && AsCall()->IsHelperCall();
}

inline var_types GenTree::CastFromType()
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/helperexpansion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2440,7 +2440,7 @@ bool Compiler::fgLateCastExpansionForCall(BasicBlock** pBlock, Statement* stmt,
if (typeCheckNotNeeded || (typeCheckFailedAction == TypeCheckFailedAction::CallHelper_AlwaysThrows))
{
// fallback call is used only to throw InvalidCastException
call->gtCallMoreFlags |= GTF_CALL_M_DOES_NOT_RETURN;
setCallDoesNotReturn(call);
fallbackBb = fgNewBBFromTreeAfter(BBJ_THROW, lastTypeCheckBb, call, debugInfo, true);
}
else if (typeCheckFailedAction == TypeCheckFailedAction::ReturnNull)
Expand Down
Loading

0 comments on commit ae442e5

Please sign in to comment.