diff --git a/llvm/include/llvm/IR/ConstantRange.h b/llvm/include/llvm/IR/ConstantRange.h index e718e6e7e3403..a5e2f809ab411 100644 --- a/llvm/include/llvm/IR/ConstantRange.h +++ b/llvm/include/llvm/IR/ConstantRange.h @@ -419,6 +419,15 @@ class [[nodiscard]] ConstantRange { /// treating both this and \p Other as unsigned ranges. ConstantRange multiply(const ConstantRange &Other) const; + /// Return a new range representing the possible values resulting + /// from a multiplication with wrap type \p NoWrapKind of a value in this + /// range and a value in \p Other. + /// If the result range is disjoint, the preferred range is determined by the + /// \p PreferredRangeType. + ConstantRange + multiplyWithNoWrap(const ConstantRange &Other, unsigned NoWrapKind, + PreferredRangeType RangeType = Smallest) const; + /// Return range of possible values for a signed multiplication of this and /// \p Other. However, if overflow is possible always return a full range /// rather than trying to determine a more precise result. diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp index 59e7a9f5eb111..c3bde48b982ce 100644 --- a/llvm/lib/IR/ConstantRange.cpp +++ b/llvm/lib/IR/ConstantRange.cpp @@ -930,6 +930,8 @@ ConstantRange ConstantRange::overflowingBinaryOp(Instruction::BinaryOps BinOp, return addWithNoWrap(Other, NoWrapKind); case Instruction::Sub: return subWithNoWrap(Other, NoWrapKind); + case Instruction::Mul: + return multiplyWithNoWrap(Other, NoWrapKind); default: // Don't know about this Overflowing Binary Operation. // Conservatively fallback to plain binop handling. @@ -1167,6 +1169,26 @@ ConstantRange::multiply(const ConstantRange &Other) const { return UR.isSizeStrictlySmallerThan(SR) ? UR : SR; } +ConstantRange +ConstantRange::multiplyWithNoWrap(const ConstantRange &Other, + unsigned NoWrapKind, + PreferredRangeType RangeType) const { + if (isEmptySet() || Other.isEmptySet()) + return getEmpty(); + if (isFullSet() && Other.isFullSet()) + return getFull(); + + ConstantRange Result = multiply(Other); + + if (NoWrapKind & OverflowingBinaryOperator::NoSignedWrap) + Result = Result.intersectWith(smul_sat(Other), RangeType); + + if (NoWrapKind & OverflowingBinaryOperator::NoUnsignedWrap) + Result = Result.intersectWith(umul_sat(Other), RangeType); + + return Result; +} + ConstantRange ConstantRange::smul_fast(const ConstantRange &Other) const { if (isEmptySet() || Other.isEmptySet()) return getEmpty(); diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/mul.ll b/llvm/test/Transforms/CorrelatedValuePropagation/mul.ll index b28107ef9d18d..086043d4b7c1b 100644 --- a/llvm/test/Transforms/CorrelatedValuePropagation/mul.ll +++ b/llvm/test/Transforms/CorrelatedValuePropagation/mul.ll @@ -179,8 +179,7 @@ define i1 @nuw_range1(i8 %b) { ; CHECK-NEXT: entry: ; CHECK-NEXT: [[C:%.*]] = add nuw nsw i8 [[B:%.*]], 1 ; CHECK-NEXT: [[MUL:%.*]] = mul nuw i8 [[C]], 4 -; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[MUL]], 0 -; CHECK-NEXT: ret i1 [[CMP]] +; CHECK-NEXT: ret i1 false ; entry: %c = add nuw nsw i8 %b, 1 @@ -194,8 +193,7 @@ define i1 @nuw_range2(i8 %b) { ; CHECK-NEXT: entry: ; CHECK-NEXT: [[C:%.*]] = add nuw nsw i8 [[B:%.*]], 3 ; CHECK-NEXT: [[MUL:%.*]] = mul nuw i8 [[C]], 4 -; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[MUL]], 2 -; CHECK-NEXT: ret i1 [[CMP]] +; CHECK-NEXT: ret i1 false ; entry: %c = add nuw nsw i8 %b, 3 diff --git a/llvm/test/Transforms/SCCP/range-mul-nuw-nsw-flags.ll b/llvm/test/Transforms/SCCP/range-mul-nuw-nsw-flags.ll new file mode 100644 index 0000000000000..8525264a00870 --- /dev/null +++ b/llvm/test/Transforms/SCCP/range-mul-nuw-nsw-flags.ll @@ -0,0 +1,26 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -passes=ipsccp -S %s | FileCheck %s + +define i1 @range_from_mul_nuw_nsw(i32 %a) { +; CHECK-LABEL: @range_from_mul_nuw_nsw( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp ne i32 [[A:%.*]], 0 +; CHECK-NEXT: br i1 [[CMP]], label [[THEN:%.*]], label [[ELSE:%.*]] +; CHECK: then: +; CHECK-NEXT: [[MUL:%.*]] = mul nuw nsw i32 [[A]], 10000 +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[MUL]], -5000 +; CHECK-NEXT: ret i1 false +; CHECK: else: +; CHECK-NEXT: ret i1 false +; +entry: + %cmp = icmp ne i32 %a, 0 + br i1 %cmp, label %then, label %else +then: + %mul = mul nuw nsw i32 %a, 10000 ; Refined range via mul_nuw: [10000, 0) + %add = add nsw i32 %mul, -5000 ; Range: [5000, UINT_MAX - 5000 + 1) + %cond = icmp ult i32 %add, 4999 + ret i1 %cond +else: + ret i1 0 +} diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp index 8ec120d70e99f..ac2075cb4af47 100644 --- a/llvm/unittests/IR/ConstantRangeTest.cpp +++ b/llvm/unittests/IR/ConstantRangeTest.cpp @@ -209,6 +209,10 @@ static bool CheckAll(const ConstantRange &, const ConstantRange &) { return true; } +static bool CheckCorrectnessOnly(const ConstantRange &, const ConstantRange &) { + return false; +} + static bool CheckSingleElementsOnly(const ConstantRange &CR1, const ConstantRange &CR2) { return CR1.isSingleElement() && CR2.isSingleElement(); @@ -1019,18 +1023,102 @@ TEST_F(ConstantRangeTest, Multiply) { }); } +TEST_F(ConstantRangeTest, MultiplyWithNoWrap) { + using OBO = OverflowingBinaryOperator; + + EXPECT_EQ(Empty.multiplyWithNoWrap(Some, OBO::NoUnsignedWrap), Empty); + EXPECT_EQ(Some.multiplyWithNoWrap(Empty, OBO::NoUnsignedWrap), Empty); + EXPECT_EQ(Full.multiplyWithNoWrap(Full, OBO::NoUnsignedWrap), Full); + EXPECT_EQ(Full.multiplyWithNoWrap(Some, OBO::NoUnsignedWrap), Full); + EXPECT_EQ(Some.multiplyWithNoWrap(Full, OBO::NoUnsignedWrap), Full); + EXPECT_EQ(ConstantRange(APInt(4, 0), APInt(4, 2)) + .multiplyWithNoWrap(ConstantRange(APInt(4, 2), APInt(4, 0)), + OBO::NoUnsignedWrap), + ConstantRange::getFull(4)); + EXPECT_EQ(ConstantRange(APInt(4, 1), APInt(4, 5)) + .multiplyWithNoWrap(ConstantRange(APInt(4, 1), APInt(4, 5)), + OBO::NoUnsignedWrap), + ConstantRange(APInt(4, 1), APInt(4, 0))); + EXPECT_EQ(ConstantRange(APInt(8, 254), APInt(8, 0)) + .multiplyWithNoWrap(ConstantRange(APInt(8, 252), APInt(8, 4)), + OBO::NoUnsignedWrap), + ConstantRange(APInt(8, 250), APInt(8, 9))); + EXPECT_EQ(ConstantRange(APInt(8, 254), APInt(8, 255)) + .multiplyWithNoWrap(ConstantRange(APInt(8, 2), APInt(8, 4)), + OBO::NoUnsignedWrap), + ConstantRange::getEmpty(8)); + + EXPECT_EQ(Empty.multiplyWithNoWrap(Some, OBO::NoSignedWrap), Empty); + EXPECT_EQ(Some.multiplyWithNoWrap(Empty, OBO::NoSignedWrap), Empty); + EXPECT_EQ(Full.multiplyWithNoWrap(Full, OBO::NoSignedWrap), Full); + EXPECT_EQ(Full.multiplyWithNoWrap(Some, OBO::NoSignedWrap), Full); + EXPECT_EQ(Some.multiplyWithNoWrap(Full, OBO::NoSignedWrap), Full); + EXPECT_EQ( + ConstantRange(APInt(4, 0), APInt(4, 4)) + .multiplyWithNoWrap(ConstantRange(APInt(4, -5, true), APInt(4, 4)), + OBO::NoSignedWrap), + ConstantRange::getFull(4)); + EXPECT_EQ(ConstantRange(APInt(4, 0), APInt(4, 3)) + .multiplyWithNoWrap(ConstantRange(APInt(4, 0), APInt(4, 5)), + OBO::NoSignedWrap), + ConstantRange(APInt(4, 0), APInt(4, -8, true))); + EXPECT_EQ(ConstantRange(APInt(8, 3), APInt(8, -11, true)) + .multiplyWithNoWrap(ConstantRange(APInt(8, -1, true)), + OBO::NoSignedWrap), + ConstantRange(APInt(8, 12), APInt(8, -2, true))); + EXPECT_EQ(ConstantRange(APInt(8, 254), APInt(8, 255)) + .multiplyWithNoWrap(ConstantRange(APInt(8, 100), APInt(8, 121)), + OBO::NoSignedWrap), + ConstantRange::getEmpty(8)); + + TestBinaryOpExhaustive( + [](const ConstantRange &CR1, const ConstantRange &CR2) { + return CR1.multiplyWithNoWrap(CR2, OBO::NoUnsignedWrap); + }, + [](const APInt &N1, const APInt &N2) -> std::optional { + bool IsOverflow; + APInt Res = N1.umul_ov(N2, IsOverflow); + if (IsOverflow) + return std::nullopt; + return Res; + }, + PreferSmallest, CheckCorrectnessOnly); + TestBinaryOpExhaustive( + [](const ConstantRange &CR1, const ConstantRange &CR2) { + return CR1.multiplyWithNoWrap(CR2, OBO::NoSignedWrap); + }, + [](const APInt &N1, const APInt &N2) -> std::optional { + bool IsOverflow; + APInt Res = N1.smul_ov(N2, IsOverflow); + if (IsOverflow) + return std::nullopt; + return Res; + }, + PreferSmallest, CheckCorrectnessOnly); + TestBinaryOpExhaustive( + [](const ConstantRange &CR1, const ConstantRange &CR2) { + return CR1.multiplyWithNoWrap(CR2, + OBO::NoUnsignedWrap | OBO::NoSignedWrap); + }, + [](const APInt &N1, const APInt &N2) -> std::optional { + bool IsOverflow1, IsOverflow2; + APInt Res1 = N1.umul_ov(N2, IsOverflow1); + APInt Res2 = N1.smul_ov(N2, IsOverflow2); + if (IsOverflow1 || IsOverflow2) + return std::nullopt; + assert(Res1 == Res2 && "Multiplication results differ?"); + return Res1; + }, + PreferSmallest, CheckCorrectnessOnly); +} + TEST_F(ConstantRangeTest, smul_fast) { TestBinaryOpExhaustive( [](const ConstantRange &CR1, const ConstantRange &CR2) { return CR1.smul_fast(CR2); }, - [](const APInt &N1, const APInt &N2) { - return N1 * N2; - }, - PreferSmallest, - [](const ConstantRange &, const ConstantRange &) { - return false; // Check correctness only. - }); + [](const APInt &N1, const APInt &N2) { return N1 * N2; }, PreferSmallest, + CheckCorrectnessOnly); } TEST_F(ConstantRangeTest, UMax) {