Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class CodeGen final : public CodeGenInterface
// Wraparound if necessary, REG_V0 comes next after REG_V31.
return (nextReg > REG_V31) ? REG_V0 : nextReg;
}

void genRuntimeCheck(GenTree* node);
#endif // defined(TARGET_ARM64)

static GenTreeIndir indirForm(var_types type, GenTree* base);
Expand Down
71 changes: 71 additions & 0 deletions src/coreclr/jit/codegenarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3664,6 +3664,77 @@ void CodeGen::genCodeForDivMod(GenTreeOp* tree)
}
}

//------------------------------------------------------------------------
// genRuntimeCheck: generate code for GT_RTCHECK node.
//
void CodeGen::genRuntimeCheck(GenTree* oper)
{
assert(oper->OperIs(GT_RTCHECK));
GenTreeRTCheck* node = oper->AsRTCheck();

GenTree* check = node->GetCheck();
GenTree* value = node->GetValue();
assert(!value->isContained());
assert(!check->IsIntegralConst(0)); // Runtime check that never passes should have been removed during
// rationalization.

genConsumeOperands(oper->AsOp());

if (check->isContained())
{
if (check->IsIntegralConst(1))
{
genJumpToThrowHlpBlk(node->gtThrowKind, [&](BasicBlock* target, bool isInline) {
if (!isInline)
{
inst_JMP(EJ_jmp, target);
}
});
}
else if (check->OperIsCompare())
{
GenCondition cond = GenCondition::FromRelop(check);

genJumpToThrowHlpBlk(node->gtThrowKind, [&](BasicBlock* target, bool isInline) {
cond = isInline ? GenCondition::Reverse(cond)
: cond; // Jump over the throw block if it is generated inline.
const GenConditionDesc& desc = GenConditionDesc::Get(cond);
genCodeForCompare(check->AsOp());
inst_JMP(desc.jumpKind1, target);
});
}
else if (check->OperIs(GT_SETCC))
{
GenCondition cond = check->AsCC()->gtCondition;

genJumpToThrowHlpBlk(node->gtThrowKind, [&](BasicBlock* target, bool isInline) {
cond = isInline ? GenCondition::Reverse(cond)
: cond; // Jump over the throw block if it is generated inline.
const GenConditionDesc& desc = GenConditionDesc::Get(cond);
inst_JMP(desc.jumpKind1, target);
});
}
else
{
noway_assert(!"unsupported contained check");
}
}
else
{
// It is possible for the check to have been CSE'd and propagated as a LCL_VAR in this node.
// It is set up such that this node is easy to CSE as well so this shouldn't happen often.
noway_assert(check->GetReg() != REG_NA);

genJumpToThrowHlpBlk(node->gtThrowKind, [&](BasicBlock* target, bool isInline) {
genCompareImmAndJump(isInline ? GenCondition::Code::NE : GenCondition::Code::EQ, check->GetReg(), 1,
emitActualTypeSize(check), target);
});
}

inst_Mov(genActualType(oper), oper->GetReg(), value->GetReg(), true);
genProduceReg(oper);
}

// Generate code for CpObj nodes which copy structs that have interleaved
// GC pointers.
// For this case we'll generate a sequence of loads/stores in the case of struct
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
genRangeCheck(treeNode);
break;

#ifdef TARGET_ARM64
case GT_RTCHECK:
genRuntimeCheck(treeNode);
break;
#endif

case GT_PHYSREG:
genCodeForPhysReg(treeNode->AsPhysReg());
break;
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2967,6 +2967,9 @@ class Compiler

GenTreeColon* gtNewColonNode(var_types type, GenTree* thenNode, GenTree* elseNode);
GenTreeQmark* gtNewQmarkNode(var_types type, GenTree* cond, GenTreeColon* colon);
GenTreeRTCheck* gtNewRTCheckNode(GenTree* value, GenTree* check, SpecialCodeKind kind);
GenTreeRTCheck* gtNewDivideByZeroCheck(GenTree* divisor);
GenTreeRTCheck* gtNewDivideOverflowCheck(GenTree* dividend, GenTree* divisor);

GenTree* gtNewLargeOperNode(genTreeOps oper,
var_types type = TYP_I_IMPL,
Expand Down Expand Up @@ -5847,6 +5850,9 @@ class Compiler
// Adds the exception set for the current tree node which is performing a bounds check operation
void fgValueNumberAddExceptionSetForBoundsCheck(GenTree* tree);

// Adds the exception set for the current tree node which is performing a bounds check operation
void fgValueNumberAddExceptionSetForRuntimeCheck(GenTree* tree);

// Adds the exception set for the current tree node which is performing a ckfinite operation
void fgValueNumberAddExceptionSetForCkFinite(GenTree* tree);

Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/jit/fgdiagnostic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3302,7 +3302,8 @@ void Compiler::fgDebugCheckTypes(GenTree* tree)
return WALK_CONTINUE;
}

if (node->OperIsIndir() || node->OperIs(GT_NULLCHECK) || node->IsPhiNode() || node->IsAnyLocal())
if (node->OperIsIndir() || node->OperIs(GT_NULLCHECK, GT_RTCHECK) || node->IsPhiNode() ||
node->IsAnyLocal())
{
return WALK_CONTINUE;
}
Expand Down
61 changes: 61 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2827,6 +2827,13 @@ bool GenTree::Compare(GenTree* op1, GenTree* op2, bool swapOK)
case GT_QMARK:
break;

case GT_RTCHECK:
if (op1->AsRTCheck()->gtThrowKind != op2->AsRTCheck()->gtThrowKind)
{
return false;
}
break;

default:
assert(!"unexpected binary ExOp operator");
}
Expand Down Expand Up @@ -3378,6 +3385,10 @@ unsigned Compiler::gtHashValue(GenTree* tree)
hash = genTreeHashAdd(hash, tree->AsBoundsChk()->gtThrowKind);
break;

case GT_RTCHECK:
hash = genTreeHashAdd(hash, tree->AsRTCheck()->gtThrowKind);
break;

// For the ones below no extra argument matters for comparison.
case GT_QMARK:
case GT_INDEX_ADDR:
Expand Down Expand Up @@ -7152,6 +7163,17 @@ ExceptionSetFlags GenTree::OperExceptions(Compiler* comp)
}
#endif // FEATURE_HW_INTRINSICS

case GT_RTCHECK:
switch (this->AsRTCheck()->gtThrowKind)
{
case SCK_DIV_BY_ZERO:
return ExceptionSetFlags::DivideByZeroException;
case SCK_OVERFLOW:
return ExceptionSetFlags::OverflowException;
default:
unreached();
}

default:
assert(!OperMayOverflow() && !OperIsIndirOrArrMetaData());
return ExceptionSetFlags::None;
Expand Down Expand Up @@ -7553,6 +7575,38 @@ GenTreeQmark* Compiler::gtNewQmarkNode(var_types type, GenTree* cond, GenTreeCol
return result;
}

GenTreeRTCheck* Compiler::gtNewRTCheckNode(GenTree* value, GenTree* check, SpecialCodeKind kind)
{
GenTreeRTCheck* node = new (this, GT_RTCHECK) GenTreeRTCheck(value, check, kind);
return node;
}

GenTreeRTCheck* Compiler::gtNewDivideByZeroCheck(GenTree* divisor)
{
assert(varTypeIsIntegral(divisor));
GenTree* check = gtNewOperNode(GT_EQ, TYP_INT, gtCloneExpr(divisor), gtNewIconNode(0, genActualType(divisor)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's not legal to clone the divisor tree like this since it can have side effects. I would just remove this and call gtNewRTCheckNode directly from the importer, where we can ensure the proper side effect handling.

GenTreeRTCheck* node = new (this, GT_RTCHECK) GenTreeRTCheck(divisor, check, SCK_DIV_BY_ZERO);
return node;
}

GenTreeRTCheck* Compiler::gtNewDivideOverflowCheck(GenTree* dividend, GenTree* divisor)
{
assert(varTypeIsIntegral(dividend) && varTypeIsIntegral(divisor));

// (dividend == MinValue && divisor == -1)
const ssize_t minValue = genActualType(dividend) == TYP_LONG ? INT64_MIN : INT32_MIN;
GenTreeOp* const divisorIsMinusOne =
gtNewOperNode(GT_EQ, TYP_INT, gtCloneExpr(divisor), gtNewIconNode(-1, genActualType(divisor)));
GenTreeOp* const dividendIsMinValue =
gtNewOperNode(GT_EQ, TYP_INT, gtCloneExpr(dividend), gtNewIconNode(minValue, genActualType(dividend)));

GenTreeOp* const combinedTest = gtNewOperNode(GT_AND, TYP_INT, divisorIsMinusOne, dividendIsMinValue);
GenTree* check = gtNewOperNode(GT_EQ, TYP_INT, combinedTest, gtNewTrue());

GenTreeRTCheck* node = new (this, GT_RTCHECK) GenTreeRTCheck(dividend, check, SCK_OVERFLOW);
return node;
}

GenTreeIntCon* Compiler::gtNewIconNode(ssize_t value, var_types type)
{
assert(genActualType(type) == type);
Expand Down Expand Up @@ -9615,6 +9669,13 @@ GenTree* Compiler::gtCloneExpr(GenTree* tree)
copy->AsBoundsChk()->gtInxType = tree->AsBoundsChk()->gtInxType;
break;

case GT_RTCHECK:
{
GenTreeRTCheck* node = tree->AsRTCheck();
copy = gtNewRTCheckNode(node->GetValue(), node->GetCheck(), node->gtThrowKind);
break;
}

case GT_LEA:
{
GenTreeAddrMode* addrModeOp = tree->AsAddrMode();
Expand Down
38 changes: 38 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -7632,6 +7632,44 @@ struct GenTreeBoundsChk : public GenTreeOp
}
};

// GenTreeRTCheck - wraps an expression with a runtime check. The runtime check is a
// function of the value being wrapped. For example:
// value => GT_LCL_VAR
// check => GT_EQ(value, GT_CNS_INT(0))
// kind => SCK_DIV_BY_ZERO
// would be the parameter set required to implement a divide-by-zero check on some
// local value.
// CodeGen for this node should produce a compare-and-branch to the throw helper in
// gtThrowKind when the check evaluates to true.
//
struct GenTreeRTCheck : public GenTreeOp
{
SpecialCodeKind gtThrowKind; // Kind of throw block to branch to on failure
Copy link
Copy Markdown
Member

@jakobbotsch jakobbotsch Apr 8, 2025

Choose a reason for hiding this comment

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

Do you think using this pattern for the overflow check is going to come with any benefits? Optimizing multiple of those checks will generally require both the dividend and divisor to be equal, but in that case the whole division operation is also equal and can be CSE'd on its own.
I think the main interest is for the zero check, so I would just make it GT_ZEROCHECK and make it a normal GenTreeOp.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wrote it this way partly thinking about making it easier to add new checks in future. Having a common framework for if (expr) throw() could be helpful generally. I don't expect any performance benefit from doing it this way, except that it does allow the JIT to generate CMP+CCMP for the overflow check at the moment, which is good for lowering conditional branch density. I could just change the emitter to generate this pattern though, if it shows a benefit. I did something similar with the divide-by-zero checks recently (#111797).


GenTreeRTCheck(GenTree* value, GenTree* check, SpecialCodeKind kind)
: GenTreeOp(GT_RTCHECK, value->TypeGet(), value, check)
, gtThrowKind(kind)
{
gtFlags |= GTF_EXCEPT;
}
#if DEBUGGABLE_GENTREE
GenTreeRTCheck()
: GenTreeOp()
{
}
#endif

GenTree* GetValue() const
{
return gtOp1;
}

GenTree* GetCheck() const
{
return gtOp2;
}
};

// GenTreeArrElem - bounds checked address (byref) of a general array element,
// for multidimensional arrays, or 1-d arrays with non-zero lower bounds.
//
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/gtlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ GTNODE(CKFINITE , GenTreeOp ,0,1,GTK_UNOP|DBK_NOCONTAIN) // Che
GTNODE(LCLHEAP , GenTreeOp ,0,1,GTK_UNOP|DBK_NOCONTAIN) // alloca()

GTNODE(BOUNDS_CHECK , GenTreeBoundsChk ,0,1,GTK_BINOP|GTK_EXOP|GTK_NOVALUE) // a bounds check - for arrays/spans/SIMDs/HWINTRINSICs
GTNODE(RTCHECK , GenTreeRTCheck ,0,1,GTK_BINOP|GTK_EXOP|DBK_NOCONTAIN) // runtime check

GTNODE(MEMORYBARRIER , GenTree ,0,0,GTK_LEAF|GTK_NOVALUE)
GTNODE(LOCKADD , GenTreeOp ,0,1,GTK_BINOP|GTK_NOVALUE|DBK_NOTHIR)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/gtstructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ GTSTRUCT_1(IndexAddr , GT_INDEX_ADDR)
GTSTRUCT_N(MultiOp , GT_HWINTRINSIC)
#endif // FEATURE_HW_INTRINSICS
GTSTRUCT_1(BoundsChk , GT_BOUNDS_CHECK)
GTSTRUCT_1(RTCheck , GT_RTCHECK)
GTSTRUCT_3_SPECIAL(ArrCommon , GT_ARR_LENGTH, GT_MDARR_LENGTH, GT_MDARR_LOWER_BOUND)
GTSTRUCT_1(ArrLen , GT_ARR_LENGTH)
GTSTRUCT_2(MDArr , GT_MDARR_LENGTH, GT_MDARR_LOWER_BOUND)
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,18 @@ GenTree* Lowering::LowerNode(GenTree* node)
break;
#endif // defined(TARGET_XARCH) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)

#if defined(TARGET_ARM64)
case GT_RTCHECK:
{
GenTree* check = node->AsRTCheck()->GetCheck();
if (check->OperIsCompare() || check->OperIs(GT_SETCC) || check->IsIntegralConst())
{
MakeSrcContained(node, check);
}
}
break;
#endif

case GT_ROL:
case GT_ROR:
{
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/jit/lsraarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,16 @@ int LinearScan::BuildNode(GenTree* tree)
buildInternalRegisterUses();
break;

case GT_RTCHECK:
{
GenTreeRTCheck* node = tree->AsRTCheck();
assert(dstCount == 1);
srcCount = BuildOperandUses(node->GetValue());
srcCount += BuildOperandUses(node->GetCheck());
BuildDef(tree);
}
break;

case GT_CMPXCHG:
{
GenTreeCmpXchg* cmpXchgNode = tree->AsCmpXchg();
Expand Down
30 changes: 30 additions & 0 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8023,6 +8023,16 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
if ((exSetFlags & ExceptionSetFlags::ArithmeticException) != ExceptionSetFlags::None)
{
fgAddCodeRef(compCurBB, SCK_OVERFLOW);

#ifdef TARGET_ARM64
if (opts.OptimizationEnabled())
{
JITDUMP("Adding division overflow check.\n");
tree->AsOp()->gtOp1 = gtNewDivideOverflowCheck(op1, op2);
tree->gtFlags |= GTF_DIV_MOD_NO_OVERFLOW;
tree->SetMorphed(this, true);
}
#endif
}
else
{
Expand All @@ -8032,6 +8042,16 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) != ExceptionSetFlags::None)
{
fgAddCodeRef(compCurBB, SCK_DIV_BY_ZERO);

#ifdef TARGET_ARM64
if (opts.OptimizationEnabled())
{
JITDUMP("Adding divide-by-zero check.\n");
tree->AsOp()->gtOp2 = gtNewDivideByZeroCheck(op2);
tree->gtFlags |= GTF_DIV_MOD_NO_BY_ZERO;
tree->SetMorphed(this, true);
}
#endif
}
else
{
Expand All @@ -8049,6 +8069,16 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) != ExceptionSetFlags::None)
{
fgAddCodeRef(compCurBB, SCK_DIV_BY_ZERO);

#ifdef TARGET_ARM64
if (opts.OptimizationEnabled())
{
JITDUMP("Adding divide-by-zero check.\n");
tree->AsOp()->gtOp2 = gtNewDivideByZeroCheck(op2);
tree->gtFlags |= GTF_DIV_MOD_NO_BY_ZERO;
tree->SetMorphed(this, true);
}
#endif
}
else
{
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/optcse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1913,6 +1913,9 @@ bool CSE_HeuristicCommon::CanConsiderTree(GenTree* tree, bool isReturn)
return false; // Currently the only special nodes that we hit
// that we know that we don't want to CSE

case GT_RTCHECK:
break;

default:
return false;
}
Expand Down
Loading
Loading