未验证 提交 93645752 编写于 作者: W Will Smith 提交者: GitHub

[JIT] ARM64 - Overflow check optimizations for division (#82924)

* Do not emit overflow check for div if we know first operand is a constant that is a not a min value

* Remove redundant branch

* Update codegenarm64.cpp

* Try to use subs, zr, dividendReg, 1 to determine overflow in division

* Cleanup

* Use IsNeverNegative to help not emit overflow checks

* Fix check

* Added helper functions

* Use main node instead of op1 for type check

* Add 64-bit check

* Use cmp instead of subs since it is an alias

* Handle 32-bit arch division for LONG.

* Additional checks

* More cleanup

* More cleanup

* Do not find exception blocks

* Try using flags

* Try using flags

* More checks, more comments

* Update src/coreclr/jit/gentree.cpp
Co-authored-by: NSingleAccretion <62474226+SingleAccretion@users.noreply.github.com>

* Flip meaning of check flags. Rename OperExceptions to GetExceptionSetFlags. Use GetExceptionSetFlags to determine to add div-by-zero or overflow blocks for arm64.

* Use GetExceptionSetFlags in arm64 codegen

* Revert name

* Use GTF_DIV_MOD_NO_OVERFLOW_CHK in CanDivOrModPossiblyOverflow

* Fix build

* Rename flags by removing '_CHK'

* Remove break

---------
Co-authored-by: NSingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
上级 3941a481
......@@ -3459,103 +3459,68 @@ void CodeGen::genCodeForDivMod(GenTreeOp* tree)
}
else // an integer divide operation
{
// Generate the require runtime checks for GT_DIV or GT_UDIV.
GenTree* divisorOp = tree->gtGetOp2();
emitAttr size = EA_ATTR(genTypeSize(genActualType(tree->TypeGet())));
if (divisorOp->IsIntegralConst(0))
{
// We unconditionally throw a divide by zero exception
genJumpToThrowHlpBlk(EJ_jmp, SCK_DIV_BY_ZERO);
regNumber divisorReg = divisorOp->GetRegNum();
// We still need to call genProduceReg
genProduceReg(tree);
}
else // the divisor is not the constant zero
{
regNumber divisorReg = divisorOp->GetRegNum();
ExceptionSetFlags exSetFlags = tree->OperExceptions(compiler);
// Generate the require runtime checks for GT_DIV or GT_UDIV
if (tree->gtOper == GT_DIV)
// (AnyVal / 0) => DivideByZeroException
if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) != ExceptionSetFlags::None)
{
if (divisorOp->IsIntegralConst(0))
{
BasicBlock* sdivLabel = genCreateTempLabel();
// Two possible exceptions:
// (AnyVal / 0) => DivideByZeroException
// (MinInt / -1) => ArithmeticException
//
bool checkDividend = true;
// Do we have an immediate for the 'divisorOp' or 'dividendOp'?
//
GenTree* dividendOp = tree->gtGetOp1();
if (dividendOp->IsCnsIntOrI())
{
GenTreeIntConCommon* intConstTree = dividendOp->AsIntConCommon();
ssize_t intConstValue = intConstTree->IconValue();
if ((targetType == TYP_INT && intConstValue != INT_MIN) ||
(targetType == TYP_LONG && intConstValue != INT64_MIN))
{
checkDividend = false; // We statically know that the dividend is not the minimum int
}
}
if (divisorOp->IsCnsIntOrI())
{
GenTreeIntConCommon* intConstTree = divisorOp->AsIntConCommon();
ssize_t intConstValue = intConstTree->IconValue();
assert(intConstValue != 0); // already checked above by IsIntegralConst(0)
if (intConstValue != -1)
{
checkDividend = false; // We statically know that the dividend is not -1
}
}
else // insert check for division by zero
{
// Check if the divisor is zero throw a DivideByZeroException
emit->emitIns_R_I(INS_cmp, size, divisorReg, 0);
genJumpToThrowHlpBlk(EJ_eq, SCK_DIV_BY_ZERO);
}
// We unconditionally throw a divide by zero exception
genJumpToThrowHlpBlk(EJ_jmp, SCK_DIV_BY_ZERO);
if (checkDividend)
{
// Check if the divisor is not -1 branch to 'sdivLabel'
emit->emitIns_R_I(INS_cmp, size, divisorReg, -1);
inst_JMP(EJ_ne, sdivLabel);
// If control flow continues past here the 'divisorReg' is known to be -1
regNumber dividendReg = dividendOp->GetRegNum();
// At this point the divisor is known to be -1
//
// Issue the 'adds zr, dividendReg, dividendReg' instruction
// this will set both the Z and V flags only when dividendReg is MinInt
//
emit->emitIns_R_R_R(INS_adds, size, REG_ZR, dividendReg, dividendReg);
inst_JMP(EJ_ne, sdivLabel); // goto sdiv if the Z flag is clear
genJumpToThrowHlpBlk(EJ_vs, SCK_ARITH_EXCPN); // if the V flags is set throw
// ArithmeticException
genDefineTempLabel(sdivLabel);
}
genCodeForBinary(tree); // Generate the sdiv instruction
// We still need to call genProduceReg
genProduceReg(tree);
return;
}
else // (tree->gtOper == GT_UDIV)
else
{
// Only one possible exception
// (AnyVal / 0) => DivideByZeroException
//
// Note that division by the constant 0 was already checked for above by the
// op2->IsIntegralConst(0) check
//
if (!divisorOp->IsCnsIntOrI())
{
// divisorOp is not a constant, so it could be zero
//
emit->emitIns_R_I(INS_cmp, size, divisorReg, 0);
genJumpToThrowHlpBlk(EJ_eq, SCK_DIV_BY_ZERO);
}
genCodeForBinary(tree);
// Check if the divisor is zero throw a DivideByZeroException
emit->emitIns_R_I(INS_cmp, size, divisorReg, 0);
genJumpToThrowHlpBlk(EJ_eq, SCK_DIV_BY_ZERO);
}
}
// (MinInt / -1) => ArithmeticException
if ((exSetFlags & ExceptionSetFlags::ArithmeticException) != ExceptionSetFlags::None)
{
// Signed-division might overflow.
assert(tree->OperIs(GT_DIV));
assert(!divisorOp->IsIntegralConst(0));
BasicBlock* sdivLabel = genCreateTempLabel();
GenTree* dividendOp = tree->gtGetOp1();
// Check if the divisor is not -1 branch to 'sdivLabel'
emit->emitIns_R_I(INS_cmp, size, divisorReg, -1);
inst_JMP(EJ_ne, sdivLabel);
// If control flow continues past here the 'divisorReg' is known to be -1
regNumber dividendReg = dividendOp->GetRegNum();
// At this point the divisor is known to be -1
//
// Issue the 'cmp dividendReg, 1' instruction.
// This is an alias to 'subs zr, dividendReg, 1' on ARM64 itself.
// This will set the V (overflow) flags only when dividendReg is MinInt
//
emit->emitIns_R_I(INS_cmp, size, dividendReg, 1);
genJumpToThrowHlpBlk(EJ_vs, SCK_ARITH_EXCPN); // if the V flags is set throw
// ArithmeticException
genDefineTempLabel(sdivLabel);
}
genCodeForBinary(tree); // Generate the sdiv instruction
}
}
......
......@@ -6620,7 +6620,7 @@ bool GenTree::OperIsImplicitIndir() const
}
//------------------------------------------------------------------------------
// OperExceptions : Get exception set this tree may throw.
// OperExceptions: Get exception set this tree may throw.
//
//
// Arguments:
......@@ -6635,40 +6635,34 @@ bool GenTree::OperIsImplicitIndir() const
//
ExceptionSetFlags GenTree::OperExceptions(Compiler* comp)
{
GenTree* op;
switch (gtOper)
{
case GT_MOD:
case GT_DIV:
case GT_UMOD:
case GT_UDIV:
{
if (varTypeIsFloating(this->TypeGet()))
{
return ExceptionSetFlags::None;
}
/* Division with a non-zero, non-minus-one constant does not throw an exception */
ExceptionSetFlags exSetFlags = ExceptionSetFlags::None;
op = AsOp()->gtOp2;
GenTree* op2 = this->gtGetOp2();
if (varTypeIsFloating(op->TypeGet()))
if (!(this->gtFlags & GTF_DIV_MOD_NO_BY_ZERO) && !op2->IsNeverZero())
{
return ExceptionSetFlags::None;
exSetFlags = ExceptionSetFlags::DivideByZeroException;
}
// For integers only division by 0 or by -1 can throw
if (op->IsIntegralConst())
if (this->OperIs(GT_DIV, GT_MOD) && this->CanDivOrModPossiblyOverflow(comp))
{
if (op->IsIntegralConst(0))
{
return ExceptionSetFlags::DivideByZeroException;
}
if (op->IsIntegralConst(-1))
{
return ExceptionSetFlags::ArithmeticException;
}
return ExceptionSetFlags::None;
exSetFlags |= ExceptionSetFlags::ArithmeticException;
}
return ExceptionSetFlags::DivideByZeroException | ExceptionSetFlags::ArithmeticException;
return exSetFlags;
}
case GT_INTRINSIC:
// If this is an intrinsic that represents the object.GetType(), it can throw an NullReferenceException.
......@@ -24755,3 +24749,95 @@ bool GenTree::IsNeverNegative(Compiler* comp) const
// TODO-Casts: extend IntegralRange to handle constants
return IntegralRange::ForNode(const_cast<GenTree*>(this), comp).IsNonNegative();
}
//------------------------------------------------------------------------
// IsNeverNegativeOne: returns true if the given tree is known to never be
// be negative one. Only valid for integral types.
//
// Arguments:
// comp - Compiler object, needed for IsNeverNegative
//
// Return Value:
// true if the given tree is known to never be negative one
//
bool GenTree::IsNeverNegativeOne(Compiler* comp) const
{
assert(varTypeIsIntegral(this));
if (this->IsNeverNegative(comp))
return true;
if (this->IsIntegralConst())
{
return !this->IsIntegralConst(-1);
}
return false;
}
//------------------------------------------------------------------------
// IsNeverZero: returns true if the given tree is known to never be zero.
// Only valid for integral types.
//
// Return Value:
// true if the given tree is known to never be zero
//
bool GenTree::IsNeverZero() const
{
assert(varTypeIsIntegral(this));
if (this->IsIntegralConst())
{
return !this->IsIntegralConst(0);
}
return false;
}
//------------------------------------------------------------------------
// CanDivOrModPossiblyOverflow: returns true if the given tree is known
// to possibly overflow on a division.
// Only valid for integral types.
// Only valid for signed-div/signed-mod.
//
// Arguments:
// comp - Compiler object, needed for IsNeverNegativeOne
//
// Return Value:
// true if the given tree is known to possibly overflow on a division
//
bool GenTree::CanDivOrModPossiblyOverflow(Compiler* comp) const
{
assert(this->OperIs(GT_DIV, GT_MOD));
assert(varTypeIsIntegral(this));
if (this->gtFlags & GTF_DIV_MOD_NO_OVERFLOW)
return false;
GenTree* op1 = this->gtGetOp1();
GenTree* op2 = this->gtGetOp2();
// If the divisor is known to never be '-1', we cannot overflow.
if (op2->IsNeverNegativeOne(comp))
return false;
// If the dividend is a constant with a minimum value with respect to the division's type, then we might overflow
// as we do not know if the divisor will be '-1' or not at this point.
if (op1->IsIntegralConst())
{
if (this->TypeIs(TYP_INT) && op1->IsIntegralConst(INT32_MIN))
{
return true;
}
else if (this->TypeIs(TYP_LONG) && (op1->AsIntConCommon()->IntegralValue() == INT64_MIN))
{
return true;
}
// Dividend is not a minimum value; therefore we cannot overflow.
return false;
}
// Not enough known information; therefore we might overflow.
return true;
}
......@@ -562,6 +562,10 @@ enum GenTreeFlags : unsigned int
GTF_OVERFLOW = 0x10000000, // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.
GTF_DIV_MOD_NO_BY_ZERO = 0x20000000, // GT_DIV, GT_MOD -- Div or mod definitely does not divide-by-zero.
GTF_DIV_MOD_NO_OVERFLOW = 0x40000000, // GT_DIV, GT_MOD -- Div or mod definitely does not overflow.
GTF_DIV_BY_CNS_OPT = 0x80000000, // GT_DIV -- Uses the division by constant optimization to compute this division
GTF_CHK_INDEX_INBND = 0x80000000, // GT_BOUNDS_CHECK -- have proven this check is always in-bounds
......@@ -2300,6 +2304,9 @@ public:
bool IsInvariant() const;
bool IsNeverNegative(Compiler* comp) const;
bool IsNeverNegativeOne(Compiler* comp) const;
bool IsNeverZero() const;
bool CanDivOrModPossiblyOverflow(Compiler* comp) const;
bool IsReuseRegVal() const
{
......
......@@ -9662,32 +9662,47 @@ DONE_MORPHING_CHILDREN:
#ifdef TARGET_LOONGARCH64
case GT_MOD:
#endif
{
if (!varTypeIsFloating(tree->gtType))
{
// We do not need to throw if the second operand is a non-(negative one) constant.
if (!op2->IsIntegralConst() || op2->IsIntegralConst(-1))
ExceptionSetFlags exSetFlags = tree->OperExceptions(this);
if ((exSetFlags & ExceptionSetFlags::ArithmeticException) != ExceptionSetFlags::None)
{
fgAddCodeRef(compCurBB, bbThrowIndex(compCurBB), SCK_OVERFLOW);
}
else
{
tree->gtFlags |= GTF_DIV_MOD_NO_OVERFLOW;
}
// We do not need to throw if the second operand is a non-zero constant.
if (!op2->IsIntegralConst() || op2->IsIntegralConst(0))
if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) != ExceptionSetFlags::None)
{
fgAddCodeRef(compCurBB, bbThrowIndex(compCurBB), SCK_DIV_BY_ZERO);
}
else
{
tree->gtFlags |= GTF_DIV_MOD_NO_BY_ZERO;
}
}
break;
}
break;
case GT_UDIV:
#ifdef TARGET_LOONGARCH64
case GT_UMOD:
#endif
// We do not need to throw if the second operand is a non-zero constant.
if (!op2->IsIntegralConst() || op2->IsIntegralConst(0))
{
ExceptionSetFlags exSetFlags = tree->OperExceptions(this);
if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) != ExceptionSetFlags::None)
{
fgAddCodeRef(compCurBB, bbThrowIndex(compCurBB), SCK_DIV_BY_ZERO);
}
break;
else
{
tree->gtFlags |= GTF_DIV_MOD_NO_BY_ZERO;
}
}
break;
#endif // defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
case GT_ADD:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册