-
Notifications
You must be signed in to change notification settings - Fork 11.5k
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
[ConstantRange][LVI] Add initial support for multiplyWithNoWrap
#92356
[ConstantRange][LVI] Add initial support for multiplyWithNoWrap
#92356
Conversation
@llvm/pr-subscribers-llvm-transforms @llvm/pr-subscribers-llvm-ir Author: Antonio Frighetto (antoniofrighetto) ChangesIntroduce preliminary support for computing multiplication ranges when nowrap flags are known. This is achieved by intersecting the multiplication range with the saturating one. Note that we may still conservatively return overdefined when handling non-wrapped/ non-sign-wrapped ranges; this is suboptimal and to be refined in the future. Full diff: https://github.com/llvm/llvm-project/pull/92356.diff 5 Files Affected:
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..3f5d3092396eb 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,28 @@ 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();
+
+ ConstantRange Result = multiply(Other);
+
+ // TODO: Bounds are not yet precise if the input sets are non-wrapped /
+ // non-sign-wrapped, as we may still conservatively return full-set in such
+ // cases. To be extended.
+
+ 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..a59b8b2762a61
--- /dev/null
+++ b/llvm/test/Transforms/SCCP/range-mul-nuw-nsw-flags.ll
@@ -0,0 +1,30 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=ipsccp -S %s | FileCheck %s
+
+declare void @opaque()
+
+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: call void @opaque()
+; 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:
+ call void @opaque()
+ ret i1 0
+}
diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp
index 8ec120d70e99f..39f2f615ff8e6 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -1019,6 +1019,99 @@ 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(4, true));
+ 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(8, false));
+
+ 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), APInt(4, 4)),
+ OBO::NoSignedWrap),
+ ConstantRange(4, true));
+ 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)));
+ EXPECT_EQ(
+ ConstantRange(APInt(8, 3), APInt(8, -11))
+ .multiplyWithNoWrap(ConstantRange(APInt(8, -1)), OBO::NoSignedWrap),
+ ConstantRange(APInt(8, 12), APInt(8, -2)));
+ EXPECT_EQ(ConstantRange(APInt(8, 254), APInt(8, 255))
+ .multiplyWithNoWrap(ConstantRange(APInt(8, 100), APInt(8, 121)),
+ OBO::NoSignedWrap),
+ ConstantRange(8, false));
+
+ auto CheckCorrectnessOnly = [](const ConstantRange &, const ConstantRange &) {
+ return false;
+ };
+
+ TestBinaryOpExhaustive(
+ [](const ConstantRange &CR1, const ConstantRange &CR2) {
+ return CR1.multiplyWithNoWrap(CR2, OBO::NoUnsignedWrap);
+ },
+ [](const APInt &N1, const APInt &N2) -> std::optional<APInt> {
+ 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<APInt> {
+ 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<APInt> {
+ 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) {
|
TBH I have been having a hard time understanding some of the failures that arise when testing exhaustively mul nuw w/ optimality
Isn't the union of the two sets full-set? What am I missing? |
@antoniofrighetto 0,1,2,3,4 *nuw 6,7,8,9,10,11,12,13,14 = 0,6,7,8,9,10,11,12,13,14 I don't think having accurate mul ranges is feasible without a brute force evaluation. You can drop the TODO. |
EXPECT_EQ(ConstantRange(APInt(4, 0), APInt(4, 2)) | ||
.multiplyWithNoWrap(ConstantRange(APInt(4, 2), APInt(4, 0)), | ||
OBO::NoUnsignedWrap), | ||
ConstantRange(4, true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConstantRange(4, true)); | |
ConstantRange(4)); |
Not signed.
722d274
to
f336ed0
Compare
Argh, for some reason, I kept on considering them as if they were two different ranges :( Addressed reviews, thanks! (The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
LGTM. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Introduce support for computing multiplication ranges when nowrap flags are known. This is achieved by intersecting the multiplication range with the saturating one. Note that we may still conservatively return overdefined when handling non-wrapped/non-sign-wrapped ranges.
f336ed0
to
e897b0b
Compare
Introduce support for computing multiplication ranges when nowrap flags are known. This is achieved by intersecting the multiplication range with the saturating one. Note that we may still conservatively return overdefined when handling non-wrapped/non-sign-wrapped ranges.