Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: Allow helper calls that always throw to be marked as no-return #100900

Merged
merged 4 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
Copy link
Member

@VSadov VSadov Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this change, but I was wondering - why do we do (call->gtOper == GT_CALL)?
Is it possible at this point for a BBJ_THROW to end with anything other than a call?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am mostly concerned about a possibility that the throwing call is somewhere inside the block and followed by some unreachable junk. Then we either emit the junk unnecessarily, or, worse, emitter may skip/optimize it and we end up with the same state we are trying to prevent here.
So I wonder - is there GT_SOMETHING that is not a call, and yet it throws and can conclude a BBJ_THROW?

Copy link
Member Author

@amanasifkhalid amanasifkhalid Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this out by asserting the last node is a call if the block has any IR, and the assert did hit. Here's some example IR it hit for:

------------ BB04 [0003] [024..02A) (throw), preds={BB03} succs={}
N069 (???,???) [000043] -----------                            IL_OFFSET void   INLRT @ 0x024[E-] REG NA
N071 (  3, 10) [000046] Hc---------                   t46 =    CNS_INT(h) long   0x7ffc04235008 ftn REG NA
                                                            /--*  t46    long   
N073 (  5, 12) [000047] nc--G------                   t47 = *  IND       long   REG NA
                                                            /--*  t47    long   control expr
N075 ( 14,  5) [000020] --CXG------                   t20 = *  CALL      int    Microsoft.FSharp.Core.Operators+OperatorIntrinsics:alreadyFinished[int]():int REG rax $c4
                                                            /--*  t20    int    
N077 ( 15,  6) [000022] --CXG------                         *  RETURN    int    REG NA $VN.Void

That call is marked as not returning, but there's still a GT_RETURN after it; this seems to confirm your concerns. Perhaps we need a dead code elimination check somewhere that deletes any IR after no-return calls, though a quick-and-dirty fix might be to always emit the breakpoint after no-return calls (regardless of whether they're the last node or not) in CodeGen::genCall, or somewhere similar.

This method in particular expects an int to be return, so removing the GT_RETURN might trip up the JIT if we do it too early...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Morph has an exemption for tail calls that are no return, and will not prune the IR afterwards. So likely the method above is a tail call? Also there is this note:

if (call->IsNoReturn())
{
//
// If we know that the call does not return then we can set fgRemoveRestOfBlock
// to remove all subsequent statements and change the call's basic block to BBJ_THROW.
// As a result the compiler won't need to preserve live registers across the call.
//
// This isn't need for tail calls as there shouldn't be any code after the call anyway.
// Besides, the tail call code is part of the epilog and converting the block to
// BBJ_THROW would result in the tail call being dropped as the epilog is generated
// only for BBJ_RETURN blocks.
//
if (!call->IsTailCall())
{
fgRemoveRestOfBlock = true;
}
}
return call;

which the IR dump above seems to contradict, since it looks from the dump that the block kind is BBJ_THROW. I wonder if the comment is now stale?

Earlier on morph will reject implicit tail calls to no return methods, I wonder if we should just do the same for explicit tail calls, even though technically we'd be violating the intent of the .tail prefix. Then we would not have these special cases.

a quick-and-dirty fix might be to always emit the breakpoint after no-return calls

There's also a concern that with profiler-driven rejitting a method that might always throw when the JIT looked at it could be updated to not always throw. I don't know if there is any good reason to support this sort of rejitting, but we should make sure there's an obvious failure if someone does it since the jitted code may depend on the old behavior. So I think in general we will want to put a breakpoint afterwards.

Copy link
Member Author

@amanasifkhalid amanasifkhalid Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Morph has an exemption for tail calls that are no return, and will not prune the IR afterwards. So likely the method above is a tail call?

In the above case, the call is an implicit tail call to a no-return method, so fgMorphPotentialTailCall rejected it, and fgMorphCall set fgRemoveRestOfBlock = true. The GT_CALL node is an operand of a GT_RETURN node, which is in the last statement of the block, so fgRemoveRestOfBlock didn't actually remove any IR, but it still converted the block into a BBJ_THROW. Perhaps we could add a check that prevents converting the block into a BBJ_THROW if it ends with a GT_RETURN node, so that we can always expect the last node in a BBJ_THROW to be a GT_CALL? I'm not sure if we lose any benefits by doing this -- maybe we could still mark the block as rarely run?

If we decide to make any changes to this, I'll open a follow-up PR.

{
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
11 changes: 11 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7412,6 +7412,17 @@ class Compiler
optNoReturnCallCount++;
}

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

// Always increment the root compiler's optNoReturnCallCount
// so fgTailMergeThrows has the correct number of candidates.
impInlineRoot()->setMethodHasNoReturnCalls();
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}

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
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
20 changes: 10 additions & 10 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 @@ -9923,7 +9923,7 @@ GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree)
if (tree->IsNoReturn())
{
assert(copy->IsNoReturn());
setMethodHasNoReturnCalls();
impInlineRoot()->setMethodHasNoReturnCalls();
}

return copy;
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