[llvm] [ValueTracking] a u>= b and a = b + NonZero implies a != 0 (PR #219103)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 26 21:21:35 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: ThePo1es

<details>
<summary>Changes</summary>

For an equality against zero, isImpliedCondition also looks through
umin(x, y) with y known non-zero, which is zero iff x is. Together this
folds the zero check of the libstdc++ vector growth length, which is
multi-use and so never simplified away.

Codegen for the _M_check_len + _M_allocate shape: https://godbolt.org/z/7ed3qc16z

Alive2: https://alive2.llvm.org/ce/z/4hqZxM

Fixes https://github.com/llvm/llvm-project/issues/200661.


---

Patch is 27.52 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/219103.diff


3 Files Affected:

- (modified) llvm/lib/Analysis/ValueTracking.cpp (+26-1) 
- (added) llvm/test/Transforms/InstCombine/icmp-select-implied-nonzero.ll (+265) 
- (added) llvm/test/Transforms/SimplifyCFG/implied-nonzero-check.ll (+418) 


``````````diff
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index f4ed5e07038da..41477b97e9cbe 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -9801,6 +9801,8 @@ isImpliedCondICmps(CmpPredicate LPred, const Value *L0, const Value *L1,
   if (!LHSIsTrue)
     LPred = ICmpInst::getInverseCmpPredicate(LPred);
 
+  SimplifyQuery SQ(DL);
+
   // We can have non-canonical operands, so try to normalize any common operand
   // to L0/R0.
   if (L0 == R1) {
@@ -9829,7 +9831,6 @@ isImpliedCondICmps(CmpPredicate LPred, const Value *L0, const Value *L1,
     // further constraint the constant ranges. At the moment this leads to
     // several regressions related to not transforming `multi_use(A + C0) eq/ne
     // C1` (see discussion: D58633).
-    SimplifyQuery SQ(DL);
     ConstantRange LCR = computeConstantRange(L1, ICmpInst::isSigned(LPred), SQ,
                                              MaxAnalysisRecursionDepth - 1);
     ConstantRange RCR = computeConstantRange(R1, ICmpInst::isSigned(RPred), SQ,
@@ -9887,6 +9888,15 @@ isImpliedCondICmps(CmpPredicate LPred, const Value *L0, const Value *L1,
     return RPred.dropSameSign() == ICmpInst::ICMP_NE;
   }
 
+  // L0 u>= L1 with L0 = L1 + Addend and Addend != 0 implies L0 != 0:
+  // If L0 == 0 then L1 u<= 0, i.e. L1 == 0, and then L0 == Addend != 0.
+  const Value *Addend;
+  if (LPred == ICmpInst::ICMP_UGE && L0 == R0 && ICmpInst::isEquality(RPred) &&
+      match(R1, m_Zero()) &&
+      match(L0, m_c_Add(m_Specific(L1), m_Value(Addend))) &&
+      isKnownNonZero(Addend, SQ))
+    return RPred.dropSameSign() == ICmpInst::ICMP_NE;
+
   // L0 = R0 = L1 + R1, L0 >=u L1 implies R0 >=u R1, L0 <u L1 implies R0 <u R1
   if (L0 == R0 &&
       (LPred == ICmpInst::ICMP_ULT || LPred == ICmpInst::ICMP_UGE) &&
@@ -10017,6 +10027,21 @@ llvm::isImpliedCondition(const Value *LHS, CmpPredicate RHSPred,
   if (match(LHS, m_Not(m_Value(LHS))))
     LHSIsTrue = !LHSIsTrue;
 
+  // umin(X, Y) with Y != 0 is zero iff X is zero, so for an equality against
+  // zero it is enough to look at X. Fall through with the original operands
+  // otherwise, e.g. when LHS is about the umin itself.
+  const Value *X, *Y;
+  if (ICmpInst::isEquality(RHSPred) && match(RHSOp1, m_Zero()) &&
+      match(RHSOp0, m_UMin(m_Value(X), m_Value(Y)))) {
+    SimplifyQuery SQ(DL);
+    if (isKnownNonZero(X, SQ))
+      std::swap(X, Y);
+    if (isKnownNonZero(Y, SQ))
+      if (std::optional<bool> Res = isImpliedCondition(
+              LHS, RHSPred, X, RHSOp1, DL, LHSIsTrue, Depth + 1))
+        return Res;
+  }
+
   // Both LHS and RHS are icmps.
   if (RHSOp0->getType()->getScalarType()->isIntOrPtrTy()) {
     CmpPredicate LHSPred;
diff --git a/llvm/test/Transforms/InstCombine/icmp-select-implied-nonzero.ll b/llvm/test/Transforms/InstCombine/icmp-select-implied-nonzero.ll
new file mode 100644
index 0000000000000..3cc7b370520ff
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-select-implied-nonzero.ll
@@ -0,0 +1,265 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; The zero check of the libstdc++ vector growth length (issue #200661):
+; %add u>= %size with %add = %size + umax(%size, 1) means %add != 0.
+
+declare void @use(i64)
+declare void @llvm.assume(i1)
+declare void @use32(i32)
+declare void @usev(<2 x i64>)
+
+; %len is multi-use: also passed to @use (models operator new in push_back).
+define i1 @check_len_multiuse_ne(i64 %size) {
+; CHECK-LABEL: @check_len_multiuse_ne(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i64 9223372036854775807, i64 [[MIN]]
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    ret i1 true
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ov = icmp ult i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp ne i64 %len, 0
+  ret i1 %r
+}
+
+define i1 @check_len_multiuse_eq(i64 %size) {
+; CHECK-LABEL: @check_len_multiuse_eq(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i64 9223372036854775807, i64 [[MIN]]
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    ret i1 false
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ov = icmp ult i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp eq i64 %len, 0
+  ret i1 %r
+}
+
+; Commuted add operands.
+define i1 @check_len_multiuse_commuted(i64 %size) {
+; CHECK-LABEL: @check_len_multiuse_commuted(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[SIZE]], [[MAX]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i64 9223372036854775807, i64 [[MIN]]
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    ret i1 true
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %size, %max
+  %ov = icmp ult i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp ne i64 %len, 0
+  ret i1 %r
+}
+
+; Different bit width and clamp constant.
+define i1 @check_len_multiuse_i32(i32 %size) {
+; CHECK-LABEL: @check_len_multiuse_i32(
+; CHECK-NEXT:    [[MAX:%.*]] = call i32 @llvm.umax.i32(i32 [[SIZE:%.*]], i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i32 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[ADD]], i32 2147483647)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i32 2147483647, i32 [[MIN]]
+; CHECK-NEXT:    call void @use32(i32 [[LEN]])
+; CHECK-NEXT:    ret i1 true
+;
+  %max = call i32 @llvm.umax.i32(i32 %size, i32 1)
+  %add = add i32 %max, %size
+  %ov = icmp ult i32 %add, %size
+  %min = call i32 @llvm.umin.i32(i32 %add, i32 2147483647)
+  %len = select i1 %ov, i32 2147483647, i32 %min
+  call void @use32(i32 %len)
+  %r = icmp ne i32 %len, 0
+  ret i1 %r
+}
+
+; Negative test: with a dynamic growth amount %n, %len == 0 is possible
+; (%size == 0, %n == 0), so the compare must NOT fold to a constant.
+define i1 @check_len_dynamic_n_negative(i64 %size, i64 %n) {
+; CHECK-LABEL: @check_len_dynamic_n_negative(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 [[N:%.*]])
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i64 9223372036854775807, i64 [[MIN]]
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i64 [[LEN]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 %n)
+  %add = add i64 %max, %size
+  %ov = icmp ult i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp ne i64 %len, 0
+  ret i1 %r
+}
+
+; umin(%add, 0) folds to 0 before isImpliedCondition sees it; sanity check
+; that nothing miscompiles. The C != 0 guard itself is exercised by
+; SimplifyCFG/implied-nonzero-check.ll.
+define i1 @check_len_zero_clamp_folds_early(i64 %size) {
+; CHECK-LABEL: @check_len_zero_clamp_folds_early(
+; CHECK-NEXT:    [[OV:%.*]] = icmp slt i64 [[SIZE:%.*]], 0
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i64 9223372036854775807, i64 0
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    ret i1 [[OV]]
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ov = icmp ult i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 0)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp ne i64 %len, 0
+  ret i1 %r
+}
+
+; Vector splat variant.
+define <2 x i1> @check_len_multiuse_vec(<2 x i64> %size) {
+; CHECK-LABEL: @check_len_multiuse_vec(
+; CHECK-NEXT:    [[MAX:%.*]] = call <2 x i64> @llvm.umax.v2i64(<2 x i64> [[SIZE:%.*]], <2 x i64> splat (i64 1))
+; CHECK-NEXT:    [[ADD:%.*]] = add <2 x i64> [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult <2 x i64> [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call <2 x i64> @llvm.umin.v2i64(<2 x i64> [[ADD]], <2 x i64> splat (i64 9223372036854775807))
+; CHECK-NEXT:    [[LEN:%.*]] = select <2 x i1> [[OV]], <2 x i64> splat (i64 9223372036854775807), <2 x i64> [[MIN]]
+; CHECK-NEXT:    call void @usev(<2 x i64> [[LEN]])
+; CHECK-NEXT:    ret <2 x i1> splat (i1 true)
+;
+  %max = call <2 x i64> @llvm.umax.v2i64(<2 x i64> %size, <2 x i64> splat (i64 1))
+  %add = add <2 x i64> %max, %size
+  %ov = icmp ult <2 x i64> %add, %size
+  %min = call <2 x i64> @llvm.umin.v2i64(<2 x i64> %add, <2 x i64> splat (i64 9223372036854775807))
+  %len = select <2 x i1> %ov, <2 x i64> splat (i64 9223372036854775807), <2 x i64> %min
+  call void @usev(<2 x i64> %len)
+  %r = icmp ne <2 x i64> %len, zeroinitializer
+  ret <2 x i1> %r
+}
+
+; The premise can also come from an assume rather than a select or a branch.
+define i1 @check_len_assume(i64 %size) {
+; CHECK-LABEL: @check_len_assume(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OK:%.*]] = icmp uge i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[OK]])
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    call void @use(i64 [[MIN]])
+; CHECK-NEXT:    ret i1 false
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ok = icmp uge i64 %add, %size
+  call void @llvm.assume(i1 %ok)
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  call void @use(i64 %min)
+  %r = icmp eq i64 %min, 0
+  ret i1 %r
+}
+
+; Strict form of the overflow check.
+define i1 @check_len_multiuse_strict(i64 %size) {
+; CHECK-LABEL: @check_len_multiuse_strict(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV_NOT:%.*]] = icmp ugt i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV_NOT]], i64 [[MIN]], i64 9223372036854775807
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    ret i1 true
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ov = icmp ule i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp ne i64 %len, 0
+  ret i1 %r
+}
+
+; Negative test: a signed compare does not say anything about wrapping.
+define i1 @check_len_signed_premise_negative(i64 %size) {
+; CHECK-LABEL: @check_len_signed_premise_negative(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp slt i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i64 9223372036854775807, i64 [[MIN]]
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i64 [[LEN]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ov = icmp slt i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp ne i64 %len, 0
+  ret i1 %r
+}
+
+; Negative test: one clamp lane is zero, so that lane of %min can be zero.
+define <2 x i1> @check_len_vec_zero_lane_negative(<2 x i64> %size) {
+; CHECK-LABEL: @check_len_vec_zero_lane_negative(
+; CHECK-NEXT:    [[MAX:%.*]] = call <2 x i64> @llvm.umax.v2i64(<2 x i64> [[SIZE:%.*]], <2 x i64> splat (i64 1))
+; CHECK-NEXT:    [[ADD:%.*]] = add <2 x i64> [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult <2 x i64> [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call <2 x i64> @llvm.umin.v2i64(<2 x i64> [[ADD]], <2 x i64> <i64 9223372036854775807, i64 0>)
+; CHECK-NEXT:    [[LEN:%.*]] = select <2 x i1> [[OV]], <2 x i64> splat (i64 9223372036854775807), <2 x i64> [[MIN]]
+; CHECK-NEXT:    call void @usev(<2 x i64> [[LEN]])
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <2 x i64> [[LEN]], zeroinitializer
+; CHECK-NEXT:    ret <2 x i1> [[R]]
+;
+  %max = call <2 x i64> @llvm.umax.v2i64(<2 x i64> %size, <2 x i64> splat (i64 1))
+  %add = add <2 x i64> %max, %size
+  %ov = icmp ult <2 x i64> %add, %size
+  %min = call <2 x i64> @llvm.umin.v2i64(<2 x i64> %add, <2 x i64> <i64 9223372036854775807, i64 0>)
+  %len = select <2 x i1> %ov, <2 x i64> splat (i64 9223372036854775807), <2 x i64> %min
+  call void @usev(<2 x i64> %len)
+  %r = icmp ne <2 x i64> %len, zeroinitializer
+  ret <2 x i1> %r
+}
+
+; Real clang -O2 codegen flags this add nsw (verified against actual
+; std::vector::push_back output); the fold must not depend on that.
+define i1 @check_len_nsw_flag(i64 %size) {
+; CHECK-LABEL: @check_len_nsw_flag(
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[OV]], i64 9223372036854775807, i64 [[MIN]]
+; CHECK-NEXT:    call void @use(i64 [[LEN]])
+; CHECK-NEXT:    ret i1 true
+;
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add nsw i64 %max, %size
+  %ov = icmp ult i64 %add, %size
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  %len = select i1 %ov, i64 9223372036854775807, i64 %min
+  call void @use(i64 %len)
+  %r = icmp ne i64 %len, 0
+  ret i1 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/implied-nonzero-check.ll b/llvm/test/Transforms/SimplifyCFG/implied-nonzero-check.ll
new file mode 100644
index 0000000000000..3da16449c72bc
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/implied-nonzero-check.ll
@@ -0,0 +1,418 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
+
+; Branch form of issue #200661: on the non-overflow path, the zero check of
+; the clamped length is dominated by !(add u< size) and must fold away.
+
+declare void @use(i64)
+declare void @sink_zero()
+declare void @sink_nonzero()
+
+define void @zero_check_dominated(i64 %size) {
+; CHECK-LABEL: @zero_check_dominated(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    br i1 [[OV]], label [[COMMON_RET:%.*]], label [[NORMAL:%.*]]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       normal:
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 9223372036854775807)
+; CHECK-NEXT:    call void @use(i64 [[MIN]])
+; CHECK-NEXT:    call void @sink_nonzero()
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+entry:
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ov = icmp ult i64 %add, %size
+  br i1 %ov, label %overflow, label %normal
+
+overflow:
+  ret void
+
+normal:
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 9223372036854775807)
+  call void @use(i64 %min)
+  %z = icmp eq i64 %min, 0
+  br i1 %z, label %iszero, label %nonzero
+
+iszero:
+  call void @sink_zero()
+  ret void
+
+nonzero:
+  call void @sink_nonzero()
+  ret void
+}
+
+declare void @use8(i8)
+declare i8 @llvm.umin.i8(i8, i8)
+
+; The dominating condition is about the umin itself, not about its operand
+; %x. Looking through the umin gives nothing here, so the implication must
+; still be found on the original operands and %c2 must fold.
+define void @umin_premise_still_folds(i8 %x) {
+; CHECK-LABEL: @umin_premise_still_folds(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[U:%.*]] = call i8 @llvm.umin.i8(i8 [[X:%.*]], i8 5)
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[U]], 2
+; CHECK-NEXT:    br i1 [[C1]], label [[T:%.*]], label [[COMMON_RET:%.*]]
+; CHECK:       t:
+; CHECK-NEXT:    call void @use8(i8 [[U]])
+; CHECK-NEXT:    call void @sink_nonzero()
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %u = call i8 @llvm.umin.i8(i8 %x, i8 5)
+  %c1 = icmp ugt i8 %u, 2
+  br i1 %c1, label %t, label %f
+
+t:
+  call void @use8(i8 %u)
+  %c2 = icmp ne i8 %u, 0
+  br i1 %c2, label %nz, label %z
+
+nz:
+  call void @sink_nonzero()
+  ret void
+
+z:
+  call void @sink_zero()
+  ret void
+
+f:
+  ret void
+}
+
+; Negative: umin with a ZERO clamp constant. umin(%add, 0) is always 0, so
+; the zero check is always TRUE; looking through the umin and applying the
+; nonzero-add implication would derive FALSE, a miscompile. The requirement
+; must reject this, leaving the branch alone.
+define void @zero_clamp_guard(i64 %size) {
+; CHECK-LABEL: @zero_clamp_guard(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[MAX:%.*]] = call i64 @llvm.umax.i64(i64 [[SIZE:%.*]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[MAX]], [[SIZE]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[SIZE]]
+; CHECK-NEXT:    br i1 [[OV]], label [[COMMON_RET:%.*]], label [[NORMAL:%.*]]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       normal:
+; CHECK-NEXT:    [[MIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 0)
+; CHECK-NEXT:    call void @use(i64 [[MIN]])
+; CHECK-NEXT:    [[Z:%.*]] = icmp eq i64 [[MIN]], 0
+; CHECK-NEXT:    br i1 [[Z]], label [[ISZERO:%.*]], label [[NONZERO:%.*]]
+; CHECK:       iszero:
+; CHECK-NEXT:    call void @sink_zero()
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       nonzero:
+; CHECK-NEXT:    call void @sink_nonzero()
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+entry:
+  %max = call i64 @llvm.umax.i64(i64 %size, i64 1)
+  %add = add i64 %max, %size
+  %ov = icmp ult i64 %add, %size
+  br i1 %ov, label %overflow, label %normal
+
+overflow:
+  ret void
+
+normal:
+  %min = call i64 @llvm.umin.i64(i64 %add, i64 0)
+  call void @use(i64 %min)
+  %z = icmp eq i64 %min, 0
+  br i1 %z, label %iszero, label %nonzero
+
+iszero:
+  call void @sink_zero()
+  ret void
+
+nonzero:
+  call void @sink_nonzero()
+  ret void
+}
+
+; No umin involved: %add u>= %x with %add = %x + %nz, %nz != 0 implies
+; %add != 0, so the zero check folds.
+define void @add_nonzero_only(i64 %x, i64 %y) {
+; CHECK-LABEL: @add_nonzero_only(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[NZ:%.*]] = or i64 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[X:%.*]], [[NZ]]
+; CHECK-NEXT:    [[OV:%.*]] = icmp ult i64 [[ADD]], [[X]]
+; CHECK-NEXT:    br i1 [[OV]], label [[COMMON_RET:%.*]], label [[NORMAL:%.*]]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       normal:
+; CHECK-NEXT:    call void @u...
[truncated]

``````````

</details>


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


More information about the llvm-commits mailing list