[llvm] [ValueTracking] Extend computeConstantRange for add/sub, sext/zext/trunc (PR #181110)

via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 12 01:59:25 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Guy David (guy-david)

<details>
<summary>Changes</summary>

Recursively compute operand ranges for add/sub and propagate ranges through sext/zext/trunc.
For add/sub, the computed range is intersected with any existing range from setLimitsForBinOp, and NSW/NUW flags are used via addWithNoWrap/subWithNoWrap to tighten bounds.

The motivation is to enable further folding of reduce.add expressions in comparisons, where the result range can be bounded by the input element ranges.

Compile-time impact on llvm-test-suite is <0.1% mean (5 rounds).

---
Full diff: https://github.com/llvm/llvm-project/pull/181110.diff


3 Files Affected:

- (modified) llvm/lib/Analysis/ValueTracking.cpp (+28) 
- (modified) llvm/test/Analysis/BasicAA/range.ll (+66) 
- (modified) llvm/unittests/Analysis/ValueTrackingTest.cpp (+92) 


``````````diff
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 8761b7bcb51a2..acbb50b8cae53 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -10242,6 +10242,34 @@ ConstantRange llvm::computeConstantRange(const Value *V, bool ForSigned,
     // TODO: Return ConstantRange.
     setLimitsForBinOp(*BO, Lower, Upper, IIQ, ForSigned);
     CR = ConstantRange::getNonEmpty(Lower, Upper);
+    if (BO->getOpcode() == Instruction::Add ||
+        BO->getOpcode() == Instruction::Sub) {
+      ConstantRange LHS = computeConstantRange(
+          BO->getOperand(0), ForSigned, UseInstrInfo, AC, CtxI, DT, Depth + 1);
+      ConstantRange RHS = computeConstantRange(
+          BO->getOperand(1), ForSigned, UseInstrInfo, AC, CtxI, DT, Depth + 1);
+      unsigned NoWrapKind = 0;
+      if (IIQ.hasNoUnsignedWrap(BO))
+        NoWrapKind |= OverflowingBinaryOperator::NoUnsignedWrap;
+      if (IIQ.hasNoSignedWrap(BO))
+        NoWrapKind |= OverflowingBinaryOperator::NoSignedWrap;
+      ConstantRange OpCR = BO->getOpcode() == Instruction::Add
+                               ? LHS.addWithNoWrap(RHS, NoWrapKind)
+                               : LHS.subWithNoWrap(RHS, NoWrapKind);
+      CR = CR.intersectWith(OpCR);
+    }
+  } else if (auto *SExt = dyn_cast<SExtInst>(V)) {
+    CR = computeConstantRange(SExt->getOperand(0), ForSigned, UseInstrInfo, AC,
+                              CtxI, DT, Depth + 1)
+             .signExtend(BitWidth);
+  } else if (auto *ZExt = dyn_cast<ZExtInst>(V)) {
+    CR = computeConstantRange(ZExt->getOperand(0), ForSigned, UseInstrInfo, AC,
+                              CtxI, DT, Depth + 1)
+             .zeroExtend(BitWidth);
+  } else if (auto *Trunc = dyn_cast<TruncInst>(V)) {
+    CR = computeConstantRange(Trunc->getOperand(0), ForSigned, UseInstrInfo, AC,
+                              CtxI, DT, Depth + 1)
+             .truncate(BitWidth);
   } else if (auto *II = dyn_cast<IntrinsicInst>(V))
     CR = getRangeForIntrinsic(*II, UseInstrInfo);
   else if (auto *SI = dyn_cast<SelectInst>(V)) {
diff --git a/llvm/test/Analysis/BasicAA/range.ll b/llvm/test/Analysis/BasicAA/range.ll
index e5dfb60c8b878..a41fd63ee52f6 100644
--- a/llvm/test/Analysis/BasicAA/range.ll
+++ b/llvm/test/Analysis/BasicAA/range.ll
@@ -271,6 +271,72 @@ entry:
   ret i32 %load_
 }
 
+; CHECK-LABEL: Function: zext_propagate_range
+; CHECK: NoAlias: i32* %gep, i32* %gep128
+define void @zext_propagate_range(ptr %p, i8 %idx) {
+  %narrow = and i8 %idx, 127
+  %wide = zext i8 %narrow to i64
+  %gep = getelementptr i32, ptr %p, i64 %wide
+  %gep128 = getelementptr i32, ptr %p, i64 128
+  load i32, ptr %gep
+  load i32, ptr %gep128
+  ret void
+}
+
+; CHECK-LABEL: Function: sext_propagate_range
+; CHECK: NoAlias: i32* %gep, i32* %gep128
+define void @sext_propagate_range(ptr %p, i8 %idx) {
+  %clamped = and i8 %idx, 100
+  %wide = sext i8 %clamped to i64
+  %gep = getelementptr i32, ptr %p, i64 %wide
+  %gep128 = getelementptr i32, ptr %p, i64 128
+  load i32, ptr %gep
+  load i32, ptr %gep128
+  ret void
+}
+
+; CHECK-LABEL: Function: zext_add_range
+; CHECK: NoAlias: i32* %gep, i32* %gep512
+define void @zext_add_range(ptr %p, i8 %x, i8 %y) {
+  %ext.x = zext i8 %x to i64
+  %ext.y = zext i8 %y to i64
+  %sum = add i64 %ext.x, %ext.y
+  %gep = getelementptr i32, ptr %p, i64 %sum
+  %gep512 = getelementptr i32, ptr %p, i64 512
+  load i32, ptr %gep
+  load i32, ptr %gep512
+  ret void
+}
+
+; CHECK-LABEL: Function: zext_sub_range
+; CHECK: NoAlias: i32* %gep, i32* %gep256
+; CHECK: NoAlias: i32* %gep, i32* %gepneg256
+define void @zext_sub_range(ptr %p, i8 %x, i8 %y) {
+  %ext.x = zext i8 %x to i64
+  %ext.y = zext i8 %y to i64
+  %diff = sub i64 %ext.x, %ext.y
+  %gep = getelementptr i32, ptr %p, i64 %diff
+  %gep256 = getelementptr i32, ptr %p, i64 256
+  %gepneg256 = getelementptr i32, ptr %p, i64 -256
+  load i32, ptr %gep
+  load i32, ptr %gep256
+  load i32, ptr %gepneg256
+  ret void
+}
+
+; CHECK-LABEL: Function: trunc_propagate_range
+; CHECK: NoAlias: i32* %gep, i32* %gep64
+define void @trunc_propagate_range(ptr %p, i64 %idx) {
+  %clamped = and i64 %idx, 63
+  %narrow = trunc i64 %clamped to i8
+  %wide = zext i8 %narrow to i64
+  %gep = getelementptr i32, ptr %p, i64 %wide
+  %gep64 = getelementptr i32, ptr %p, i64 64
+  load i32, ptr %gep
+  load i32, ptr %gep64
+  ret void
+}
+
 declare void @llvm.assume(i1)
 
 !0 = !{ i32 0, i32 2 }
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 6229d408de2a8..2ee45dccc6595 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -3394,6 +3394,98 @@ TEST_F(ValueTrackingTest, ComputeConstantRange) {
     // If we don't know the value of x.2, we don't know the value of x.1.
     EXPECT_TRUE(CR1.isFullSet());
   }
+  {
+    auto M = parseModule(R"(
+  define void @test(i8 %x) {
+    %sext = sext i8 %x to i32
+    %zext = zext i8 %x to i32
+    ret void
+  })");
+    Function *F = M->getFunction("test");
+    AssumptionCache AC(*F);
+    Instruction *SExt = &findInstructionByName(F, "sext");
+    Instruction *ZExt = &findInstructionByName(F, "zext");
+    ConstantRange SExtCR = computeConstantRange(SExt, true, true, &AC, SExt);
+    EXPECT_EQ(SExtCR.getSignedMin().getSExtValue(), -128);
+    EXPECT_EQ(SExtCR.getSignedMax().getSExtValue(), 127);
+    ConstantRange ZExtCR = computeConstantRange(ZExt, false, true, &AC, ZExt);
+    EXPECT_EQ(ZExtCR.getUnsignedMin().getZExtValue(), 0u);
+    EXPECT_EQ(ZExtCR.getUnsignedMax().getZExtValue(), 255u);
+  }
+  {
+    auto M = parseModule(R"(
+  define i32 @test(i8 %x) {
+    %ext = sext i8 %x to i32
+    %add = add nsw i32 %ext, 10
+    ret i32 %add
+  })");
+    Function *F = M->getFunction("test");
+    AssumptionCache AC(*F);
+    Instruction *Add = &findInstructionByName(F, "add");
+    ConstantRange CR = computeConstantRange(Add, true, true, &AC, Add);
+    EXPECT_EQ(CR.getSignedMin().getSExtValue(), -118);
+    EXPECT_EQ(CR.getSignedMax().getSExtValue(), 137);
+  }
+  {
+    auto M = parseModule(R"(
+  define i32 @test(i8 %x, i8 %y) {
+    %ext.x = zext i8 %x to i32
+    %ext.y = zext i8 %y to i32
+    %sub = sub i32 %ext.x, %ext.y
+    ret i32 %sub
+  })");
+    Function *F = M->getFunction("test");
+    AssumptionCache AC(*F);
+    Instruction *Sub = &findInstructionByName(F, "sub");
+    ConstantRange CR = computeConstantRange(Sub, true, true, &AC, Sub);
+    EXPECT_EQ(CR.getSignedMin().getSExtValue(), -255);
+    EXPECT_EQ(CR.getSignedMax().getSExtValue(), 255);
+  }
+  {
+    // trunc
+    auto M = parseModule(R"(
+  define void @test(i32 %x) {
+    %narrow = trunc i32 %x to i8
+    ret void
+  })");
+    Function *F = M->getFunction("test");
+    AssumptionCache AC(*F);
+    Instruction *Trunc = &findInstructionByName(F, "narrow");
+    ConstantRange CR = computeConstantRange(Trunc, false, true, &AC, Trunc);
+    EXPECT_TRUE(CR.isFullSet());
+    EXPECT_EQ(CR.getBitWidth(), 8u);
+  }
+  {
+    // trunc with restricted input range
+    auto M = parseModule(R"(
+  define i8 @test(i32 %x) {
+    %clamped = and i32 %x, 127
+    %narrow = trunc i32 %clamped to i8
+    ret i8 %narrow
+  })");
+    Function *F = M->getFunction("test");
+    AssumptionCache AC(*F);
+    Instruction *Trunc = &findInstructionByName(F, "narrow");
+    ConstantRange CR = computeConstantRange(Trunc, false, true, &AC, Trunc);
+    EXPECT_EQ(CR.getUnsignedMin().getZExtValue(), 0u);
+    EXPECT_EQ(CR.getUnsignedMax().getZExtValue(), 127u);
+  }
+  {
+    // Chained adds from i1
+    auto M = parseModule(R"(
+  define i32 @test(i1 %x) {
+    %ext = sext i1 %x to i32
+    %add1 = add nsw i32 %ext, %ext
+    %add2 = add nsw i32 %add1, %ext
+    ret i32 %add2
+  })");
+    Function *F = M->getFunction("test");
+    AssumptionCache AC(*F);
+    Instruction *Add2 = &findInstructionByName(F, "add2");
+    ConstantRange CR = computeConstantRange(Add2, true, true, &AC, Add2);
+    EXPECT_EQ(CR.getSignedMin().getSExtValue(), -3);
+    EXPECT_EQ(CR.getSignedMax().getSExtValue(), 0);
+  }
 }
 
 struct FindAllocaForValueTestParams {

``````````

</details>


https://github.com/llvm/llvm-project/pull/181110


More information about the llvm-commits mailing list