diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 602f6e5b1b7b1..31182129355d6 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -3440,103 +3440,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 } } diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 5a84e860fbefc..8af003b5bbdd7 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -6608,7 +6608,7 @@ bool GenTree::OperIsImplicitIndir() const } //------------------------------------------------------------------------------ -// OperExceptions : Get exception set this tree may throw. +// OperExceptions: Get exception set this tree may throw. // // // Arguments: @@ -6623,40 +6623,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. @@ -24768,3 +24762,95 @@ bool GenTree::IsNeverNegative(Compiler* comp) const // TODO-Casts: extend IntegralRange to handle constants return IntegralRange::ForNode((GenTree*)this, comp).IsPositive(); } + +//------------------------------------------------------------------------ +// 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; +} diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 9972b71d8b8c5..2a8fa75bafa47 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -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 @@ struct GenTree bool IsInvariant() const; bool IsNeverNegative(Compiler* comp) const; + bool IsNeverNegativeOne(Compiler* comp) const; + bool IsNeverZero() const; + bool CanDivOrModPossiblyOverflow(Compiler* comp) const; bool IsReuseRegVal() const { diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 3de6ce1d41191..9e094782ff457 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -9670,32 +9670,47 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA #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: