[llvm] [ConstantRange][LVI] Add initial support for `multiplyWithNoWrap` (PR #92356)
Antonio Frighetto via llvm-commits
llvm-commits at lists.llvm.org
Fri May 24 10:25:23 PDT 2024
https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/92356
>From e897b0bbe908ea17188f0b1ed96e6b6c8f8954d8 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Thu, 16 May 2024 08:50:50 +0200
Subject: [PATCH] [ConstantRange][LVI] Add support for `multiplyWithNoWrap`
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.
---
llvm/include/llvm/IR/ConstantRange.h | 9 ++
llvm/lib/IR/ConstantRange.cpp | 22 ++++
.../CorrelatedValuePropagation/mul.ll | 6 +-
.../SCCP/range-mul-nuw-nsw-flags.ll | 26 +++++
llvm/unittests/IR/ConstantRangeTest.cpp | 102 ++++++++++++++++--
5 files changed, 154 insertions(+), 11 deletions(-)
create mode 100644 llvm/test/Transforms/SCCP/range-mul-nuw-nsw-flags.ll
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<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) {
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) {
More information about the llvm-commits
mailing list