[llvm] [ValueTracking] Use dominating conditions in computeConstantRange (PR #210412)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 17 14:03:22 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-analysis
Author: Joel Walker (Joel-Wwalker)
<details>
<summary>Changes</summary>
`computeConstantRange` already restricts a value's range using `llvm.assume`, but not using dominating branch conditions, even though `computeKnownBits`, `computeKnownFPClass`, and `isKnownNonEqual` all do. As a result a range guard like `if (a <= 15 && b <= 15)` does not tighten the range of `a` or `b`, so a derived compare such as `smin(a, b) > 15` does not fold, while the equivalent bitmask guard (which goes through knownbits) does.
This adds a dominating-condition walk mirroring the `llvm.assume` handling directly below it: for a condition `icmp <pred> V, C` whose true (or false) edge dominates the context, intersect the range with `makeAllowedICmpRegion(<pred>, range(C))` (or the inverse predicate on the false edge). It is gated on a populated `DomConditionCache`/`DominatorTree`, so only passes that already build one pay any cost, and those already run the same walk for knownbits.
Motivated by #<!-- -->207131. This resolves the missing analysis the issue identifies (the dead branch is eliminated when the guards are present as dominating conditions); the fully flattened `-O3` form in that report, where SimplifyCFG has merged the guards into a single `and` before the compare, needs separate work and is not addressed here.
Assisted by Claude (Anthropic).
---
Full diff: https://github.com/llvm/llvm-project/pull/210412.diff
7 Files Affected:
- (modified) llvm/lib/Analysis/ValueTracking.cpp (+26)
- (added) llvm/test/Transforms/InstCombine/icmp-dominating-condition-range.ll (+167)
- (modified) llvm/test/Transforms/LoopVectorize/induction.ll (+1-1)
- (modified) llvm/test/Transforms/LoopVectorize/interleaved-accesses-2.ll (+1-1)
- (modified) llvm/test/Transforms/LoopVectorize/interleaved-accesses.ll (+4-4)
- (modified) llvm/test/Transforms/PhaseOrdering/AArch64/hoist-load-from-vector-loop.ll (+1-1)
- (modified) llvm/unittests/Analysis/ValueTrackingTest.cpp (+38)
``````````diff
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index efc14f9a639da..83e6848122c78 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -10588,6 +10588,32 @@ ConstantRange llvm::computeConstantRange(const Value *V, bool ForSigned,
}
}
+ if (SQ.CxtI && SQ.DC && SQ.DT) {
+ // Try to restrict the range based on dominating conditions. This mirrors
+ // the dominating-condition handling in computeKnownBitsFromContext, which
+ // computeConstantRange previously lacked.
+ for (CondBrInst *BI : SQ.DC->conditionsFor(V)) {
+ auto *Cmp = dyn_cast<ICmpInst>(BI->getCondition());
+ // Currently we just use information from comparisons directly on V.
+ if (!Cmp || Cmp->getOperand(0) != V)
+ continue;
+ // TODO: Set "ForSigned" parameter via Cmp->isSigned()?
+ ConstantRange RHS =
+ computeConstantRange(Cmp->getOperand(1), /*ForSigned=*/false,
+ SQ.getWithInstruction(BI), Depth + 1);
+ // The true edge implies the condition; the false edge implies its
+ // inverse. At most one of them can dominate the context.
+ BasicBlockEdge TrueEdge(BI->getParent(), BI->getSuccessor(0));
+ if (SQ.DT->dominates(TrueEdge, SQ.CxtI->getParent()))
+ CR = CR.intersectWith(
+ ConstantRange::makeAllowedICmpRegion(Cmp->getCmpPredicate(), RHS));
+ BasicBlockEdge FalseEdge(BI->getParent(), BI->getSuccessor(1));
+ if (SQ.DT->dominates(FalseEdge, SQ.CxtI->getParent()))
+ CR = CR.intersectWith(ConstantRange::makeAllowedICmpRegion(
+ Cmp->getInverseCmpPredicate(), RHS));
+ }
+ }
+
if (SQ.CxtI && SQ.AC) {
// Try to restrict the range based on information from assumptions.
for (auto &AssumeVH : SQ.AC->assumptionsFor(V)) {
diff --git a/llvm/test/Transforms/InstCombine/icmp-dominating-condition-range.ll b/llvm/test/Transforms/InstCombine/icmp-dominating-condition-range.ll
new file mode 100644
index 0000000000000..ee9df0c3f14f1
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-dominating-condition-range.ll
@@ -0,0 +1,167 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Tests that computeConstantRange picks up dominating conditions, not just
+; llvm.assume. Each query is on a value derived from operands that are only
+; range-constrained by a dominating branch, through a diamond so that a
+; single-predecessor walk cannot see the guard. See issue #207131.
+
+declare void @use()
+declare i32 @llvm.smin.i32(i32, i32)
+declare i32 @llvm.umax.i32(i32, i32)
+
+; a < 16 and b < 16 dominate %join, so smin(a, b) <= 15 and "smin > 15" is false.
+define i1 @smin_sgt_dominating_range(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @smin_sgt_dominating_range(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[GA:%.*]] = icmp ult i32 [[A:%.*]], 16
+; CHECK-NEXT: br i1 [[GA]], label [[G2:%.*]], label [[ELSE:%.*]]
+; CHECK: g2:
+; CHECK-NEXT: [[GB:%.*]] = icmp ult i32 [[B:%.*]], 16
+; CHECK-NEXT: br i1 [[GB]], label [[MID:%.*]], label [[ELSE]]
+; CHECK: mid:
+; CHECK-NEXT: [[SPLIT:%.*]] = icmp ult i32 [[C:%.*]], 5
+; CHECK-NEXT: br i1 [[SPLIT]], label [[LEFT:%.*]], label [[RIGHT:%.*]]
+; CHECK: left:
+; CHECK-NEXT: br label [[JOIN:%.*]]
+; CHECK: right:
+; CHECK-NEXT: br label [[JOIN]]
+; CHECK: join:
+; CHECK-NEXT: ret i1 false
+; CHECK: else:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %ga = icmp ult i32 %a, 16
+ br i1 %ga, label %g2, label %else
+g2:
+ %gb = icmp ult i32 %b, 16
+ br i1 %gb, label %mid, label %else
+mid:
+ %split = icmp ult i32 %c, 5
+ br i1 %split, label %left, label %right
+left:
+ br label %join
+right:
+ br label %join
+join:
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp sgt i32 %m, 15
+ ret i1 %r
+else:
+ ret i1 false
+}
+
+; a < 11 and b < 11 dominate %join, so umax(a, b) <= 10 and "umax < 20" is true.
+define i1 @umax_ult_dominating_range(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @umax_ult_dominating_range(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[GA:%.*]] = icmp ult i32 [[A:%.*]], 11
+; CHECK-NEXT: br i1 [[GA]], label [[G2:%.*]], label [[ELSE:%.*]]
+; CHECK: g2:
+; CHECK-NEXT: [[GB:%.*]] = icmp ult i32 [[B:%.*]], 11
+; CHECK-NEXT: br i1 [[GB]], label [[MID:%.*]], label [[ELSE]]
+; CHECK: mid:
+; CHECK-NEXT: [[SPLIT:%.*]] = icmp ult i32 [[C:%.*]], 5
+; CHECK-NEXT: br i1 [[SPLIT]], label [[LEFT:%.*]], label [[RIGHT:%.*]]
+; CHECK: left:
+; CHECK-NEXT: br label [[JOIN:%.*]]
+; CHECK: right:
+; CHECK-NEXT: br label [[JOIN]]
+; CHECK: join:
+; CHECK-NEXT: ret i1 true
+; CHECK: else:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %ga = icmp ult i32 %a, 11
+ br i1 %ga, label %g2, label %else
+g2:
+ %gb = icmp ult i32 %b, 11
+ br i1 %gb, label %mid, label %else
+mid:
+ %split = icmp ult i32 %c, 5
+ br i1 %split, label %left, label %right
+left:
+ br label %join
+right:
+ br label %join
+join:
+ %m = call i32 @llvm.umax.i32(i32 %a, i32 %b)
+ %r = icmp ult i32 %m, 20
+ ret i1 %r
+else:
+ ret i1 false
+}
+
+; The false edge of "a >= 16" dominates %join, implying a < 16. Exercises the
+; inverse-predicate path.
+define i1 @smin_inverted_edge(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @smin_inverted_edge(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[GA:%.*]] = icmp ugt i32 [[A:%.*]], 15
+; CHECK-NEXT: br i1 [[GA]], label [[ELSE:%.*]], label [[G2:%.*]]
+; CHECK: g2:
+; CHECK-NEXT: [[GB:%.*]] = icmp ult i32 [[B:%.*]], 16
+; CHECK-NEXT: br i1 [[GB]], label [[MID:%.*]], label [[ELSE]]
+; CHECK: mid:
+; CHECK-NEXT: [[SPLIT:%.*]] = icmp ult i32 [[C:%.*]], 5
+; CHECK-NEXT: br i1 [[SPLIT]], label [[LEFT:%.*]], label [[RIGHT:%.*]]
+; CHECK: left:
+; CHECK-NEXT: br label [[JOIN:%.*]]
+; CHECK: right:
+; CHECK-NEXT: br label [[JOIN]]
+; CHECK: join:
+; CHECK-NEXT: ret i1 false
+; CHECK: else:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %ga = icmp uge i32 %a, 16
+ br i1 %ga, label %else, label %g2
+g2:
+ %gb = icmp ult i32 %b, 16
+ br i1 %gb, label %mid, label %else
+mid:
+ %split = icmp ult i32 %c, 5
+ br i1 %split, label %left, label %right
+left:
+ br label %join
+right:
+ br label %join
+join:
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp sgt i32 %m, 15
+ ret i1 %r
+else:
+ ret i1 false
+}
+
+; Negative test: the guard does not dominate %join (sibling edge), so the range
+; stays unknown and the compare must not fold.
+define i1 @not_dominating(i32 %a, i32 %b, i1 %cc) {
+; CHECK-LABEL: @not_dominating(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 [[CC:%.*]], label [[GUARD:%.*]], label [[JOIN:%.*]]
+; CHECK: guard:
+; CHECK-NEXT: [[GA:%.*]] = icmp ult i32 [[A:%.*]], 16
+; CHECK-NEXT: br i1 [[GA]], label [[JOIN]], label [[ELSE:%.*]]
+; CHECK: join:
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B:%.*]])
+; CHECK-NEXT: [[R:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: ret i1 [[R]]
+; CHECK: else:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ br i1 %cc, label %guard, label %join
+guard:
+ %ga = icmp ult i32 %a, 16
+ br i1 %ga, label %join, label %else
+join:
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp sgt i32 %m, 15
+ ret i1 %r
+else:
+ ret i1 false
+}
diff --git a/llvm/test/Transforms/LoopVectorize/induction.ll b/llvm/test/Transforms/LoopVectorize/induction.ll
index f2ede41effb54..adfad0f65610a 100644
--- a/llvm/test/Transforms/LoopVectorize/induction.ll
+++ b/llvm/test/Transforms/LoopVectorize/induction.ll
@@ -1442,7 +1442,7 @@ define void @scalarize_induction_variable_03(ptr %p, i32 %y, i64 %n) {
; INTERLEAVE-NEXT: [[N_MOD_VF:%.*]] = and i64 [[N]], 7
; INTERLEAVE-NEXT: [[TMP0:%.*]] = icmp eq i64 [[N_MOD_VF]], 0
; INTERLEAVE-NEXT: [[TMP1:%.*]] = select i1 [[TMP0]], i64 8, i64 [[N_MOD_VF]]
-; INTERLEAVE-NEXT: [[N_VEC:%.*]] = sub nsw i64 [[N]], [[TMP1]]
+; INTERLEAVE-NEXT: [[N_VEC:%.*]] = sub nuw nsw i64 [[N]], [[TMP1]]
; INTERLEAVE-NEXT: [[BROADCAST_SPLATINSERT:%.*]] = insertelement <4 x i32> poison, i32 [[Y:%.*]], i64 0
; INTERLEAVE-NEXT: [[BROADCAST_SPLAT:%.*]] = shufflevector <4 x i32> [[BROADCAST_SPLATINSERT]], <4 x i32> poison, <4 x i32> zeroinitializer
; INTERLEAVE-NEXT: br label [[VECTOR_BODY:%.*]]
diff --git a/llvm/test/Transforms/LoopVectorize/interleaved-accesses-2.ll b/llvm/test/Transforms/LoopVectorize/interleaved-accesses-2.ll
index 04d07cfdecd1b..9f22302c2347b 100644
--- a/llvm/test/Transforms/LoopVectorize/interleaved-accesses-2.ll
+++ b/llvm/test/Transforms/LoopVectorize/interleaved-accesses-2.ll
@@ -35,7 +35,7 @@ define void @_Z4testPfS_m(ptr noalias nocapture %out, ptr noalias nocapture read
; CHECK-NEXT: [[N_MOD_VF:%.*]] = and i64 [[SIZE]], 3
; CHECK-NEXT: [[TMP0:%.*]] = icmp eq i64 [[N_MOD_VF]], 0
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[TMP0]], i64 4, i64 [[N_MOD_VF]]
-; CHECK-NEXT: [[N_VEC:%.*]] = sub i64 [[SIZE]], [[TMP1]]
+; CHECK-NEXT: [[N_VEC:%.*]] = sub nuw i64 [[SIZE]], [[TMP1]]
; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
; CHECK: [[VECTOR_BODY]]:
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
diff --git a/llvm/test/Transforms/LoopVectorize/interleaved-accesses.ll b/llvm/test/Transforms/LoopVectorize/interleaved-accesses.ll
index 1a97a87d2edfa..047688e5420c8 100644
--- a/llvm/test/Transforms/LoopVectorize/interleaved-accesses.ll
+++ b/llvm/test/Transforms/LoopVectorize/interleaved-accesses.ll
@@ -863,7 +863,7 @@ define void @PR27626_0(ptr %p, i32 %z, i64 %n) {
; CHECK-NEXT: [[N_MOD_VF:%.*]] = and i64 [[N]], 3
; CHECK-NEXT: [[TMP0:%.*]] = icmp eq i64 [[N_MOD_VF]], 0
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[TMP0]], i64 4, i64 [[N_MOD_VF]]
-; CHECK-NEXT: [[N_VEC:%.*]] = sub nsw i64 [[N]], [[TMP1]]
+; CHECK-NEXT: [[N_VEC:%.*]] = sub nuw nsw i64 [[N]], [[TMP1]]
; CHECK-NEXT: br label [[VECTOR_BODY:%.*]]
; CHECK: vector.body:
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, [[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], [[VECTOR_BODY]] ]
@@ -955,7 +955,7 @@ define i32 @PR27626_1(ptr %p, i64 %n) {
; CHECK-NEXT: [[N_MOD_VF:%.*]] = and i64 [[N]], 3
; CHECK-NEXT: [[TMP0:%.*]] = icmp eq i64 [[N_MOD_VF]], 0
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[TMP0]], i64 4, i64 [[N_MOD_VF]]
-; CHECK-NEXT: [[N_VEC:%.*]] = sub nsw i64 [[N]], [[TMP1]]
+; CHECK-NEXT: [[N_VEC:%.*]] = sub nuw nsw i64 [[N]], [[TMP1]]
; CHECK-NEXT: br label [[VECTOR_BODY:%.*]]
; CHECK: vector.body:
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, [[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], [[VECTOR_BODY]] ]
@@ -1047,7 +1047,7 @@ define void @PR27626_2(ptr %p, i64 %n, i32 %z) {
; CHECK-NEXT: [[N_MOD_VF:%.*]] = and i64 [[N]], 3
; CHECK-NEXT: [[TMP0:%.*]] = icmp eq i64 [[N_MOD_VF]], 0
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[TMP0]], i64 4, i64 [[N_MOD_VF]]
-; CHECK-NEXT: [[N_VEC:%.*]] = sub nsw i64 [[N]], [[TMP1]]
+; CHECK-NEXT: [[N_VEC:%.*]] = sub nuw nsw i64 [[N]], [[TMP1]]
; CHECK-NEXT: br label [[VECTOR_BODY:%.*]]
; CHECK: vector.body:
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, [[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], [[VECTOR_BODY]] ]
@@ -1143,7 +1143,7 @@ define i32 @PR27626_3(ptr %p, i64 %n, i32 %z) {
; CHECK-NEXT: [[N_MOD_VF:%.*]] = and i64 [[N]], 3
; CHECK-NEXT: [[TMP0:%.*]] = icmp eq i64 [[N_MOD_VF]], 0
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[TMP0]], i64 4, i64 [[N_MOD_VF]]
-; CHECK-NEXT: [[N_VEC:%.*]] = sub nsw i64 [[N]], [[TMP1]]
+; CHECK-NEXT: [[N_VEC:%.*]] = sub nuw nsw i64 [[N]], [[TMP1]]
; CHECK-NEXT: br label [[VECTOR_BODY:%.*]]
; CHECK: vector.body:
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, [[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], [[VECTOR_BODY]] ]
diff --git a/llvm/test/Transforms/PhaseOrdering/AArch64/hoist-load-from-vector-loop.ll b/llvm/test/Transforms/PhaseOrdering/AArch64/hoist-load-from-vector-loop.ll
index b0c163737c206..5372dcef2b69e 100644
--- a/llvm/test/Transforms/PhaseOrdering/AArch64/hoist-load-from-vector-loop.ll
+++ b/llvm/test/Transforms/PhaseOrdering/AArch64/hoist-load-from-vector-loop.ll
@@ -27,7 +27,7 @@ define void @hoist_invariant_load(ptr %invariant_ptr, i64 %num_elements, ptr %ar
; CHECK-NEXT: [[N_MOD_VF:%.*]] = and i64 [[NUM_ELEMENTS]], 3
; CHECK-NEXT: [[TMP2:%.*]] = icmp eq i64 [[N_MOD_VF]], 0
; CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 4, i64 [[N_MOD_VF]]
-; CHECK-NEXT: [[N_VEC:%.*]] = sub i64 [[NUM_ELEMENTS]], [[TMP3]]
+; CHECK-NEXT: [[N_VEC:%.*]] = sub nuw i64 [[NUM_ELEMENTS]], [[TMP3]]
; CHECK-NEXT: [[TMP4:%.*]] = load double, ptr [[INVARIANT_PTR]], align 8, !alias.scope [[META0:![0-9]+]]
; CHECK-NEXT: [[BROADCAST_SPLATINSERT:%.*]] = insertelement <2 x double> poison, double [[TMP4]], i64 0
; CHECK-NEXT: [[BROADCAST_SPLAT:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT]], <2 x double> poison, <2 x i32> zeroinitializer
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 382cf0f2b57ed..0acb3287a762f 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -9,6 +9,7 @@
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/ADT/FloatingPointMode.h"
#include "llvm/Analysis/AssumptionCache.h"
+#include "llvm/Analysis/DomConditionCache.h"
#include "llvm/Analysis/FloatingPointPredicateUtils.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/ConstantRange.h"
@@ -3856,6 +3857,43 @@ 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());
}
+ {
+ // Dominating condition:
+ // * x < 42 on the taken edge
+ //
+ // x = [0, 42)
+ auto M = parseModule(R"(
+ define i32 @test(i32 %x) {
+ entry:
+ %c = icmp ult i32 %x, 42
+ br i1 %c, label %taken, label %exit
+ taken:
+ %x.plus.one = add nsw nuw i32 %x, 1
+ ret i32 %x.plus.one
+ exit:
+ ret i32 0
+ })");
+ Function *F = M->getFunction("test");
+
+ DominatorTree DT(*F);
+ DomConditionCache DC;
+ DC.registerBranch(cast<CondBrInst>(F->getEntryBlock().getTerminator()));
+ Value *X = &*F->arg_begin();
+
+ // Without a context instruction the branch does not apply.
+ SimplifyQuery SQ(M->getDataLayout(), /*TLI=*/nullptr, &DT, /*AC=*/nullptr,
+ /*CXTI=*/nullptr, /*UseInstrInfo=*/true,
+ /*CanUseUndef=*/true, &DC);
+ ConstantRange CR1 = computeConstantRange(X, /*ForSigned=*/false, SQ);
+ EXPECT_TRUE(CR1.isFullSet());
+
+ // At an instruction dominated by the taken edge, x is known to be [0, 42).
+ Instruction *I = &findInstructionByName(F, "x.plus.one");
+ ConstantRange CR2 =
+ computeConstantRange(X, /*ForSigned=*/false, SQ.getWithInstruction(I));
+ EXPECT_EQ(0, CR2.getLower());
+ EXPECT_EQ(42, CR2.getUpper());
+ }
}
struct FindAllocaForValueTestParams {
``````````
</details>
https://github.com/llvm/llvm-project/pull/210412
More information about the llvm-commits
mailing list