[llvm] [ConstantRange] Add binaryOp supporting 'or disjoint'. (PR #222573)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 10 03:01:58 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Florian Hahn (fhahn)
<details>
<summary>Changes</summary>
Promote logic from https://github.com/llvm/llvm-project/pull/221832 to
ConstantRange::binaryOp so it can also be re-used from SCCP.
This enables a few additional folds & flag inference end-to-end:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1227
---
Full diff: https://github.com/llvm/llvm-project/pull/222573.diff
7 Files Affected:
- (modified) llvm/include/llvm/IR/ConstantRange.h (+7)
- (modified) llvm/lib/Analysis/LazyValueInfo.cpp (+1-9)
- (modified) llvm/lib/IR/ConstantRange.cpp (+17)
- (modified) llvm/lib/Transforms/Utils/SCCPSolver.cpp (+1-5)
- (added) llvm/test/Transforms/CorrelatedValuePropagation/or-disjoint.ll (+63)
- (added) llvm/test/Transforms/SCCP/or-disjoint.ll (+94)
- (modified) llvm/unittests/IR/ConstantRangeTest.cpp (+27)
``````````diff
diff --git a/llvm/include/llvm/IR/ConstantRange.h b/llvm/include/llvm/IR/ConstantRange.h
index e553aab2b93ee..fc6e64d8dbff8 100644
--- a/llvm/include/llvm/IR/ConstantRange.h
+++ b/llvm/include/llvm/IR/ConstantRange.h
@@ -407,6 +407,13 @@ class [[nodiscard]] ConstantRange {
LLVM_ABI ConstantRange binaryOp(Instruction::BinaryOps BinOp,
const ConstantRange &Other) const;
+ /// Return a new range representing the possible values resulting from an
+ /// application of the specified binary operation \p BO (including
+ /// poison-generating flags) to a left hand side of this range and a right
+ /// hand side of \p Other.
+ LLVM_ABI ConstantRange binaryOp(const BinaryOperator &BO,
+ const ConstantRange &Other) const;
+
/// Return a new range representing the possible values resulting
/// from an application of the specified overflowing binary operator to a
/// left hand side of this range and a right hand side of \p Other given
diff --git a/llvm/lib/Analysis/LazyValueInfo.cpp b/llvm/lib/Analysis/LazyValueInfo.cpp
index a62d195036258..833d337fac875 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -1167,18 +1167,10 @@ std::optional<ValueLatticeElement>
LazyValueInfoImpl::solveBlockValueBinaryOp(BinaryOperator *BO, BasicBlock *BB) {
assert(BO->getOperand(0)->getType()->isSized() &&
"all operands to binary operators are sized");
- if (auto *OBO = dyn_cast<OverflowingBinaryOperator>(BO)) {
- unsigned NoWrapKind = OBO->getNoWrapKind();
- return solveBlockValueBinaryOpImpl(
- BO, BB,
- [BO, NoWrapKind](const ConstantRange &CR1, const ConstantRange &CR2) {
- return CR1.overflowingBinaryOp(BO->getOpcode(), CR2, NoWrapKind);
- });
- }
return solveBlockValueBinaryOpImpl(
BO, BB, [BO](const ConstantRange &CR1, const ConstantRange &CR2) {
- return CR1.binaryOp(BO->getOpcode(), CR2);
+ return CR1.binaryOp(*BO, CR2);
});
}
diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index ffe51cdcbe2ba..0daa8010e6d1a 100644
--- a/llvm/lib/IR/ConstantRange.cpp
+++ b/llvm/lib/IR/ConstantRange.cpp
@@ -1044,6 +1044,23 @@ ConstantRange ConstantRange::overflowingBinaryOp(Instruction::BinaryOps BinOp,
}
}
+ConstantRange ConstantRange::binaryOp(const BinaryOperator &BO,
+ const ConstantRange &Other) const {
+ if (const auto *OBO = dyn_cast<OverflowingBinaryOperator>(&BO))
+ return overflowingBinaryOp(BO.getOpcode(), Other, OBO->getNoWrapKind());
+
+ // Treat 'or disjoint' as both 'add nuw nsw' and binary or, picking the best
+ // from both.
+ const auto *PDI = dyn_cast<PossiblyDisjointInst>(&BO);
+ if (PDI && PDI->isDisjoint()) {
+ using OBO = OverflowingBinaryOperator;
+ return addWithNoWrap(Other, OBO::NoUnsignedWrap | OBO::NoSignedWrap)
+ .intersectWith(binaryOr(Other));
+ }
+
+ return binaryOp(BO.getOpcode(), Other);
+}
+
bool ConstantRange::isIntrinsicSupported(Intrinsic::ID IntrinsicID) {
switch (IntrinsicID) {
case Intrinsic::uadd_sat:
diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index c24e59aac14a5..0101a82bdbbcb 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -1806,11 +1806,7 @@ void SCCPInstVisitor::visitBinaryOperator(Instruction &I) {
V2State.asConstantRange(I.getType(), /*UndefAllowed=*/false);
auto *BO = cast<BinaryOperator>(&I);
- ConstantRange R = ConstantRange::getEmpty(I.getType()->getScalarSizeInBits());
- if (auto *OBO = dyn_cast<OverflowingBinaryOperator>(BO))
- R = A.overflowingBinaryOp(BO->getOpcode(), B, OBO->getNoWrapKind());
- else
- R = A.binaryOp(BO->getOpcode(), B);
+ ConstantRange R = A.binaryOp(*BO, B);
mergeInValue(ValueState[&I], &I, ValueLatticeElement::getRange(R));
// TODO: Currently we do not exploit special values that produce something
diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/or-disjoint.ll b/llvm/test/Transforms/CorrelatedValuePropagation/or-disjoint.ll
new file mode 100644
index 0000000000000..eccadb190381a
--- /dev/null
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/or-disjoint.ll
@@ -0,0 +1,63 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=correlated-propagation -S %s | FileCheck %s
+
+define i1 @or_disjoint(i8 %x) {
+; CHECK-LABEL: define i1 @or_disjoint(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[C:%.*]] = icmp uge i8 [[X]], 8
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[O:%.*]] = or disjoint i8 [[X]], 32
+; CHECK-NEXT: ret i1 false
+;
+ %c = icmp uge i8 %x, 8
+ call void @llvm.assume(i1 %c)
+ %o = or disjoint i8 %x, 32
+ %cmp = icmp ult i8 %o, 40
+ ret i1 %cmp
+}
+
+define i1 @or_not_disjoint(i8 %x) {
+; CHECK-LABEL: define i1 @or_not_disjoint(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[C:%.*]] = icmp uge i8 [[X]], 8
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[O:%.*]] = or i8 [[X]], 32
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[O]], 40
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %c = icmp uge i8 %x, 8
+ call void @llvm.assume(i1 %c)
+ %o = or i8 %x, 32
+ %cmp = icmp ult i8 %o, 40
+ ret i1 %cmp
+}
+
+define i1 @or_disjoint_upper_bound(i8 %x) {
+; CHECK-LABEL: define i1 @or_disjoint_upper_bound(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[C:%.*]] = icmp ult i8 [[X]], 24
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[O:%.*]] = or disjoint i8 [[X]], 32
+; CHECK-NEXT: ret i1 false
+;
+ %c = icmp ult i8 %x, 24
+ call void @llvm.assume(i1 %c)
+ %o = or disjoint i8 %x, 32
+ %cmp = icmp ugt i8 %o, 55
+ ret i1 %cmp
+}
+
+define i1 @or_disjoint_known_bits_more_precise(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @or_disjoint_known_bits_more_precise(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT: [[A:%.*]] = and i8 [[X]], 3
+; CHECK-NEXT: [[B:%.*]] = and i8 [[Y]], 3
+; CHECK-NEXT: [[O:%.*]] = or disjoint i8 [[A]], [[B]]
+; CHECK-NEXT: ret i1 false
+;
+ %a = and i8 %x, 3
+ %b = and i8 %y, 3
+ %o = or disjoint i8 %a, %b
+ %cmp = icmp ugt i8 %o, 3
+ ret i1 %cmp
+}
diff --git a/llvm/test/Transforms/SCCP/or-disjoint.ll b/llvm/test/Transforms/SCCP/or-disjoint.ll
new file mode 100644
index 0000000000000..6e61569120019
--- /dev/null
+++ b/llvm/test/Transforms/SCCP/or-disjoint.ll
@@ -0,0 +1,94 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=sccp -S %s | FileCheck %s
+
+declare void @use(i1)
+
+define void @or_disjoint(i8 %p) {
+; CHECK-LABEL: define void @or_disjoint(
+; CHECK-SAME: i8 [[P:%.*]]) {
+; CHECK-NEXT: [[C:%.*]] = icmp ult i8 [[P]], 13
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[O:%.*]] = or disjoint i8 [[P]], 32
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: call void @use(i1 false)
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i8 [[P]], 12
+; CHECK-NEXT: call void @use(i1 [[C1]])
+; CHECK-NEXT: ret void
+;
+ %pre = icmp ult i8 %p, 13
+ call void @llvm.assume(i1 %pre)
+ %o = or disjoint i8 %p, 32
+ %t = icmp ult i8 %o, 45
+ call void @use(i1 %t)
+ %f = icmp ugt i8 %o, 45
+ call void @use(i1 %f)
+ %c = icmp ult i8 %o, 44
+ call void @use(i1 %c)
+ ret void
+}
+
+define void @or_not_disjoint(i8 %p) {
+; CHECK-LABEL: define void @or_not_disjoint(
+; CHECK-SAME: i8 [[P:%.*]]) {
+; CHECK-NEXT: [[C:%.*]] = icmp ult i8 [[P]], 13
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[O:%.*]] = or i8 [[P]], 32
+; CHECK-NEXT: [[R:%.*]] = icmp ult i8 [[O]], 45
+; CHECK-NEXT: call void @use(i1 [[R]])
+; CHECK-NEXT: [[F:%.*]] = icmp ugt i8 [[O]], 45
+; CHECK-NEXT: call void @use(i1 [[F]])
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i8 [[O]], 44
+; CHECK-NEXT: call void @use(i1 [[C1]])
+; CHECK-NEXT: ret void
+;
+ %pre = icmp ult i8 %p, 13
+ call void @llvm.assume(i1 %pre)
+ %o = or i8 %p, 32
+ %t = icmp ult i8 %o, 45
+ call void @use(i1 %t)
+ %f = icmp ugt i8 %o, 45
+ call void @use(i1 %f)
+ %c = icmp ult i8 %o, 44
+ call void @use(i1 %c)
+ ret void
+}
+
+define void @or_disjoint_known_bits_better(i8 %p, i8 %q) {
+; CHECK-LABEL: define void @or_disjoint_known_bits_better(
+; CHECK-SAME: i8 [[P:%.*]], i8 [[Q:%.*]]) {
+; CHECK-NEXT: [[C_P:%.*]] = icmp ult i8 [[P]], 4
+; CHECK-NEXT: call void @llvm.assume(i1 [[C_P]])
+; CHECK-NEXT: [[C_Q:%.*]] = icmp ult i8 [[Q]], 4
+; CHECK-NEXT: call void @llvm.assume(i1 [[C_Q]])
+; CHECK-NEXT: [[O:%.*]] = or disjoint i8 [[P]], [[Q]]
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: ret void
+;
+ %pre.p = icmp ult i8 %p, 4
+ call void @llvm.assume(i1 %pre.p)
+ %pre.q = icmp ult i8 %q, 4
+ call void @llvm.assume(i1 %pre.q)
+ %o = or disjoint i8 %p, %q
+ %t = icmp ult i8 %o, 4
+ call void @use(i1 %t)
+ ret void
+}
+
+; %p always has bit 7 set, so the operands are never disjoint, so %o is always poison.
+define void @or_disjoint_never_disjoint(i8 %p) {
+; CHECK-LABEL: define void @or_disjoint_never_disjoint(
+; CHECK-SAME: i8 [[P:%.*]]) {
+; CHECK-NEXT: [[C:%.*]] = icmp uge i8 [[P]], -128
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[O:%.*]] = or disjoint i8 [[P]], -128
+; CHECK-NEXT: [[TMP1:%.*]] = icmp slt i8 [[P]], -83
+; CHECK-NEXT: call void @use(i1 [[TMP1]])
+; CHECK-NEXT: ret void
+;
+ %pre = icmp uge i8 %p, 128
+ call void @llvm.assume(i1 %pre)
+ %o = or disjoint i8 %p, 128
+ %r = icmp ult i8 %o, 45
+ call void @use(i1 %r)
+ ret void
+}
diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp
index 099a43bba34c4..2cd33d87ab4ba 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -2969,6 +2969,33 @@ TEST_F(ConstantRangeTest, binaryOr) {
CheckSingleElementsOnly);
}
+TEST_F(ConstantRangeTest, binaryOpDisjointOr) {
+ // TestBinaryOpExhaustive enumerates 1- and 4-bit ranges, create or disjoint
+ // binary ops up front for the bitwidths.
+ LLVMContext Ctx;
+ std::unique_ptr<BinaryOperator> DisjointOr[2];
+ for (auto [Idx, Bits] : enumerate(ArrayRef<unsigned>({1, 4}))) {
+ Value *Poison = PoisonValue::get(Type::getIntNTy(Ctx, Bits));
+ DisjointOr[Idx].reset(
+ BinaryOperator::CreateDisjoint(Instruction::Or, Poison, Poison));
+ }
+
+ TestBinaryOpExhaustive(
+ [&](const ConstantRange &CR1, const ConstantRange &CR2) {
+ const BinaryOperator &BO = *DisjointOr[CR1.getBitWidth() == 1 ? 0 : 1];
+ return CR1.binaryOp(BO, CR2);
+ },
+ [](const APInt &N1, const APInt &N2) -> std::optional<APInt> {
+ if (N1.intersects(N2))
+ return std::nullopt;
+ return N1 | N2;
+ },
+ PreferSmallest,
+ [](const ConstantRange &, const ConstantRange &) {
+ return false; // Check correctness only.
+ });
+}
+
TEST_F(ConstantRangeTest, binaryXor) {
// Single element ranges.
ConstantRange R16(APInt(8, 16));
``````````
</details>
https://github.com/llvm/llvm-project/pull/222573
More information about the llvm-commits
mailing list