[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