[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