[llvm] [InstCombine] Recognize strcmp/strncmp/memcmp/bcmp == 0 as commutative (PR #213545)
Aayush Shrivastava via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 2 06:33:50 PDT 2026
https://github.com/iamaayushrivastava created https://github.com/llvm/llvm-project/pull/213545
Fixes #211058.
`strcmp(x, y) == 0`, `strncmp(x, y, n) == 0`, `memcmp(x, y, n) == 0`, and `bcmp(x, y, n) == 0` are all commutative. They hold precisely when the ranges being compared are equal, regardless of the order of the arguments. The concrete nonzero return value is implementation-defined and not guaranteed to simply negate when arguments are swapped, but "is the result zero" is order-independent, so it's only safe to reuse a call's result across argument order when every use is a zero-equality comparison.
This PR adds an InstCombine fold that reuses a dominating call's result for a dominated call with swapped pointer arguments (same size, for the 3-arg variants), when both results are only ever compared against zero, and no intervening instruction could have written to the compared memory.
Added `llvm/test/Transforms/InstCombine/strcmp-memcmp-commutative-cmp-zero.ll` covering all four functions plus negative cases: raw (non-comparison) use of the result, mismatched size arguments, an intervening store, non-dominating branches, and self-comparison.
>From b9834fd292ead40b87d892d1e7c6f32e954f3f6c Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sun, 2 Aug 2026 18:50:03 +0530
Subject: [PATCH] [InstCombine] Recognize strcmp/strncmp/memcmp/bcmp == 0 as
commutative
---
.../InstCombine/InstCombineCalls.cpp | 131 ++++++++
.../InstCombine/InstCombineInternal.h | 1 +
.../strcmp-memcmp-commutative-cmp-zero.ll | 287 ++++++++++++++++++
3 files changed, 419 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/strcmp-memcmp-commutative-cmp-zero.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 5ee5009bd0262..614410c5ed85d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -4955,6 +4955,134 @@ bool InstCombinerImpl::annotateAnyAllocSite(CallBase &Call,
return Changed;
}
+/// Returns true if every use of \p CI is an equality (eq/ne) comparison
+/// against the constant zero. strcmp(x, y) == 0, strncmp(x, y, n) == 0,
+/// memcmp(x, y, n) == 0, and bcmp(x, y, n) == 0 all hold under exactly the
+/// same condition as their argument-swapped counterparts (the compared
+/// ranges are equal), even though the concrete nonzero return value of
+/// these functions is implementation-defined and may differ when the
+/// pointer arguments are swapped. So this property is what makes it safe to
+/// treat such a call as interchangeable with its argument-swapped sibling.
+static bool isOnlyUsedInZeroEqualityComparisons(CallInst *CI) {
+ if (CI->use_empty())
+ return false;
+ return all_of(CI->users(), [CI](User *U) {
+ auto *Cmp = dyn_cast<ICmpInst>(U);
+ if (!Cmp || !Cmp->isEquality())
+ return false;
+ Value *Other =
+ Cmp->getOperand(0) == CI ? Cmp->getOperand(1) : Cmp->getOperand(0);
+ return match(Other, PatternMatch::m_Zero());
+ });
+}
+
+/// Returns true if no instruction strictly between \p Dominator and
+/// \p Dominated may write to memory, so the two calls are guaranteed to see
+/// the same memory contents. Only the common shapes that matter in practice
+/// are handled: the two calls share a basic block, or \p Dominated's block
+/// is reached directly (and only) from \p Dominator's block, as happens for
+/// short-circuiting `&&`/`||`.
+static bool isFreeOfIntermediateWrites(CallInst *Dominator,
+ CallInst *Dominated) {
+ BasicBlock *DominatorBB = Dominator->getParent();
+ BasicBlock *DominatedBB = Dominated->getParent();
+
+ if (DominatorBB == DominatedBB) {
+ for (Instruction &I : make_range(std::next(Dominator->getIterator()),
+ Dominated->getIterator()))
+ if (I.mayWriteToMemory())
+ return false;
+ return true;
+ }
+
+ if (DominatedBB->getUniquePredecessor() != DominatorBB)
+ return false;
+
+ for (Instruction &I :
+ make_range(std::next(Dominator->getIterator()), DominatorBB->end()))
+ if (I.mayWriteToMemory())
+ return false;
+ for (Instruction &I :
+ make_range(DominatedBB->begin(), Dominated->getIterator()))
+ if (I.mayWriteToMemory())
+ return false;
+ return true;
+}
+
+/// strcmp(x, y) == 0, strncmp(x, y, n) == 0, memcmp(x, y, n) == 0, and
+/// bcmp(x, y, n) == 0 are all commutative. They hold precisely when the
+/// compared ranges are equal, regardless of which pointer is passed first.
+/// When we find two such calls in the same function whose pointer
+/// arguments are swapped, and whose results are only ever compared for
+/// equality with zero, fold away the one that is dominated by the other
+/// (memory permitting), exposing the redundancy that ordinary CSE cannot
+/// see because the two calls are not literally identical instructions.
+Instruction *InstCombinerImpl::foldCommutativeCmpLibCall(CallInst &CI) {
+ LibFunc Func;
+ if (!TLI.getLibFunc(CI, Func))
+ return nullptr;
+
+ switch (Func) {
+ case LibFunc_strcmp:
+ case LibFunc_strncmp:
+ case LibFunc_memcmp:
+ case LibFunc_bcmp:
+ break;
+ default:
+ return nullptr;
+ }
+
+ if (!isOnlyUsedInZeroEqualityComparisons(&CI))
+ return nullptr;
+
+ Function *Callee = CI.getCalledFunction();
+ Value *A = CI.getArgOperand(0);
+ Value *B = CI.getArgOperand(1);
+ if (A == B)
+ return nullptr;
+
+ // ConstantData (null, poison, ...) does not maintain a use list, and may
+ // in any case be shared by an unbounded number of unrelated instructions,
+ // so it isn't useful to search its users for a matching call.
+ if (!A->hasUseList())
+ return nullptr;
+
+ for (User *U : A->users()) {
+ auto *Other = dyn_cast<CallInst>(U);
+ if (!Other || Other == &CI || Other->getCalledFunction() != Callee)
+ continue;
+ if (Other->getArgOperand(0) != B || Other->getArgOperand(1) != A)
+ continue;
+ if (CI.arg_size() == 3 && Other->getArgOperand(2) != CI.getArgOperand(2))
+ continue;
+ if (!isOnlyUsedInZeroEqualityComparisons(Other))
+ continue;
+
+ CallInst *Dominator, *Dominated;
+ if (DT.dominates(&CI, Other)) {
+ Dominator = &CI;
+ Dominated = Other;
+ } else if (DT.dominates(Other, &CI)) {
+ Dominator = Other;
+ Dominated = &CI;
+ } else {
+ continue;
+ }
+
+ if (!isFreeOfIntermediateWrites(Dominator, Dominated))
+ continue;
+
+ if (Dominated == &CI)
+ return replaceInstUsesWith(CI, Dominator);
+
+ replaceInstUsesWith(*Dominated, Dominator);
+ eraseInstFromFunction(*Dominated);
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
/// Improvements for call, callbr and invoke instructions.
Instruction *InstCombinerImpl::visitCallBase(CallBase &Call) {
bool Changed = annotateAnyAllocSite(Call, &TLI);
@@ -5090,6 +5218,9 @@ Instruction *InstCombinerImpl::visitCallBase(CallBase &Call) {
// this. None of these calls are seen as possibly dead so go ahead and
// delete the instruction now.
if (CallInst *CI = dyn_cast<CallInst>(&Call)) {
+ if (Instruction *I = foldCommutativeCmpLibCall(*CI))
+ return I;
+
Instruction *I = tryOptimizeCall(CI);
// If we changed something return the result, etc. Otherwise let
// the fallthrough check.
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 8b759e701da60..fcd32b8d4e4cb 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -257,6 +257,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
Instruction *visitCallBase(CallBase &Call);
Instruction *tryOptimizeCall(CallInst *CI);
+ Instruction *foldCommutativeCmpLibCall(CallInst &CI);
bool transformConstExprCastCall(CallBase &Call);
Instruction *transformCallThroughTrampoline(CallBase &Call,
IntrinsicInst &Tramp);
diff --git a/llvm/test/Transforms/InstCombine/strcmp-memcmp-commutative-cmp-zero.ll b/llvm/test/Transforms/InstCombine/strcmp-memcmp-commutative-cmp-zero.ll
new file mode 100644
index 0000000000000..758b46394751c
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/strcmp-memcmp-commutative-cmp-zero.ll
@@ -0,0 +1,287 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; strcmp(x, y) == 0, strncmp(x, y, n) == 0, memcmp(x, y, n) == 0, and
+; bcmp(x, y, n) == 0 are all commutative: they hold precisely when the
+; compared ranges are equal, regardless of which pointer comes first. When
+; two such calls in a function have their pointer arguments swapped and are
+; only ever compared for equality with zero, the dominated call should be
+; folded away in favor of the dominating one.
+
+declare i32 @strcmp(ptr, ptr)
+declare i32 @strncmp(ptr, ptr, i64)
+declare i32 @memcmp(ptr, ptr, i64)
+declare i32 @bcmp(ptr, ptr, i64)
+
+; (strcmp(x, y) == 0) && (strcmp(y, x) == 0)
+define i1 @strcmp_and(ptr %x, ptr %y, i1 %c) {
+; CHECK-LABEL: define i1 @strcmp_and(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[X]], ptr noundef nonnull dereferenceable(1) [[Y]])
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[CALL1]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RHS:.*]], label %[[END:.*]]
+; CHECK: [[RHS]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: ret i1 [[CMP1]]
+;
+ %call1 = call i32 @strcmp(ptr %x, ptr %y)
+ %cmp1 = icmp eq i32 %call1, 0
+ br i1 %cmp1, label %rhs, label %end
+
+rhs:
+ %call2 = call i32 @strcmp(ptr %y, ptr %x)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ false, %0 ], [ %cmp2, %rhs ]
+ ret i1 %r
+}
+
+; (strcmp(y, x) == 0) && (strcmp(x, y) == 0) (swapped order of the two calls)
+define i1 @strcmp_and_commuted(ptr %x, ptr %y) {
+; CHECK-LABEL: define i1 @strcmp_and_commuted(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[Y]], ptr noundef nonnull dereferenceable(1) [[X]])
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[CALL1]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RHS:.*]], label %[[END:.*]]
+; CHECK: [[RHS]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: ret i1 [[CMP1]]
+;
+ %call1 = call i32 @strcmp(ptr %y, ptr %x)
+ %cmp1 = icmp eq i32 %call1, 0
+ br i1 %cmp1, label %rhs, label %end
+
+rhs:
+ %call2 = call i32 @strcmp(ptr %x, ptr %y)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ false, %0 ], [ %cmp2, %rhs ]
+ ret i1 %r
+}
+
+; (strcmp(x, y) == 0) == (strcmp(y, x) == 0) -> true
+define i1 @strcmp_eq_same_block(ptr %x, ptr %y) {
+; CHECK-LABEL: define i1 @strcmp_eq_same_block(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[X]], ptr noundef nonnull dereferenceable(1) [[Y]])
+; CHECK-NEXT: ret i1 true
+;
+ %call1 = call i32 @strcmp(ptr %x, ptr %y)
+ %cmp1 = icmp eq i32 %call1, 0
+ %call2 = call i32 @strcmp(ptr %y, ptr %x)
+ %cmp2 = icmp eq i32 %call2, 0
+ %r = icmp eq i1 %cmp1, %cmp2
+ ret i1 %r
+}
+
+; strncmp(x, y, n) == 0 && strncmp(y, x, n) == 0
+define i1 @strncmp_and(ptr %x, ptr %y, i64 %n) {
+; CHECK-LABEL: define i1 @strncmp_and(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @strncmp(ptr [[X]], ptr [[Y]], i64 [[N]])
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[CALL1]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RHS:.*]], label %[[END:.*]]
+; CHECK: [[RHS]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: ret i1 [[CMP1]]
+;
+ %call1 = call i32 @strncmp(ptr %x, ptr %y, i64 %n)
+ %cmp1 = icmp eq i32 %call1, 0
+ br i1 %cmp1, label %rhs, label %end
+
+rhs:
+ %call2 = call i32 @strncmp(ptr %y, ptr %x, i64 %n)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ false, %0 ], [ %cmp2, %rhs ]
+ ret i1 %r
+}
+
+; memcmp(x, y, n) == 0 && memcmp(y, x, n) == 0
+define i1 @memcmp_and(ptr %x, ptr %y, i64 %n) {
+; CHECK-LABEL: define i1 @memcmp_and(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @memcmp(ptr [[X]], ptr [[Y]], i64 [[N]])
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[CALL1]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RHS:.*]], label %[[END:.*]]
+; CHECK: [[RHS]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: ret i1 [[CMP1]]
+;
+ %call1 = call i32 @memcmp(ptr %x, ptr %y, i64 %n)
+ %cmp1 = icmp eq i32 %call1, 0
+ br i1 %cmp1, label %rhs, label %end
+
+rhs:
+ %call2 = call i32 @memcmp(ptr %y, ptr %x, i64 %n)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ false, %0 ], [ %cmp2, %rhs ]
+ ret i1 %r
+}
+
+; bcmp(x, y, n) == 0 && bcmp(y, x, n) == 0
+define i1 @bcmp_and(ptr %x, ptr %y, i64 %n) {
+; CHECK-LABEL: define i1 @bcmp_and(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @bcmp(ptr [[X]], ptr [[Y]], i64 [[N]])
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[CALL1]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RHS:.*]], label %[[END:.*]]
+; CHECK: [[RHS]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: ret i1 [[CMP1]]
+;
+ %call1 = call i32 @bcmp(ptr %x, ptr %y, i64 %n)
+ %cmp1 = icmp eq i32 %call1, 0
+ br i1 %cmp1, label %rhs, label %end
+
+rhs:
+ %call2 = call i32 @bcmp(ptr %y, ptr %x, i64 %n)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ false, %0 ], [ %cmp2, %rhs ]
+ ret i1 %r
+}
+
+; Negative test: the raw (nonzero) return value of the second call is used
+; for something other than a zero-equality comparison, so the two calls must
+; not be merged -- their nonzero magnitudes are not guaranteed to be related.
+define i32 @strcmp_raw_value_not_folded(ptr %x, ptr %y) {
+; CHECK-LABEL: define i32 @strcmp_raw_value_not_folded(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[X]], ptr noundef nonnull dereferenceable(1) [[Y]])
+; CHECK-NEXT: [[CALL2:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[Y]], ptr noundef nonnull dereferenceable(1) [[X]])
+; CHECK-NEXT: [[SUM:%.*]] = add nsw i32 [[CALL1]], [[CALL2]]
+; CHECK-NEXT: ret i32 [[SUM]]
+;
+ %call1 = call i32 @strcmp(ptr %x, ptr %y)
+ %call2 = call i32 @strcmp(ptr %y, ptr %x)
+ %sum = add nsw i32 %call1, %call2
+ ret i32 %sum
+}
+
+; Negative test: the two memcmp calls use different length arguments, so
+; they are not guaranteed to compare the same ranges and must not be merged.
+define i1 @memcmp_different_size_not_folded(ptr %x, ptr %y) {
+; CHECK-LABEL: define i1 @memcmp_different_size_not_folded(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]]) {
+; CHECK-NEXT: [[LHSC:%.*]] = load i8, ptr [[X]], align 1
+; CHECK-NEXT: [[RHSC:%.*]] = load i8, ptr [[Y]], align 1
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i8 [[LHSC]], [[RHSC]]
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RHS:.*]], label %[[END:.*]]
+; CHECK: [[RHS]]:
+; CHECK-NEXT: [[CALL2:%.*]] = call i32 @memcmp(ptr noundef nonnull dereferenceable(2) [[Y]], ptr noundef nonnull dereferenceable(2) [[X]], i64 2)
+; CHECK-NEXT: [[CMP2:%.*]] = icmp eq i32 [[CALL2]], 0
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: [[R:%.*]] = phi i1 [ false, [[TMP0:%.*]] ], [ [[CMP2]], %[[RHS]] ]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %call1 = call i32 @memcmp(ptr %x, ptr %y, i64 1)
+ %cmp1 = icmp eq i32 %call1, 0
+ br i1 %cmp1, label %rhs, label %end
+
+rhs:
+ %call2 = call i32 @memcmp(ptr %y, ptr %x, i64 2)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ false, %0 ], [ %cmp2, %rhs ]
+ ret i1 %r
+}
+
+; Negative test: a store that may alias either buffer occurs between the two
+; calls, so the second call cannot be assumed to observe the same memory
+; contents as the first, and the calls must not be merged.
+define i1 @strcmp_clobbered_between_calls_not_folded(ptr %x, ptr %y) {
+; CHECK-LABEL: define i1 @strcmp_clobbered_between_calls_not_folded(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]]) {
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[X]], ptr noundef nonnull dereferenceable(1) [[Y]])
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[CALL1]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RHS:.*]], label %[[END:.*]]
+; CHECK: [[RHS]]:
+; CHECK-NEXT: store i8 97, ptr [[X]], align 1
+; CHECK-NEXT: [[CALL2:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[Y]], ptr noundef nonnull dereferenceable(1) [[X]])
+; CHECK-NEXT: [[CMP2:%.*]] = icmp eq i32 [[CALL2]], 0
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: [[R:%.*]] = phi i1 [ false, [[TMP0:%.*]] ], [ [[CMP2]], %[[RHS]] ]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %call1 = call i32 @strcmp(ptr %x, ptr %y)
+ %cmp1 = icmp eq i32 %call1, 0
+ br i1 %cmp1, label %rhs, label %end
+
+rhs:
+ store i8 97, ptr %x
+ %call2 = call i32 @strcmp(ptr %y, ptr %x)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ false, %0 ], [ %cmp2, %rhs ]
+ ret i1 %r
+}
+
+; Negative test: the two calls are in unrelated, non-dominating branches, so
+; neither call is guaranteed to have executed before the other.
+define i1 @strcmp_non_dominating_branches_not_folded(ptr %x, ptr %y, i1 %c) {
+; CHECK-LABEL: define i1 @strcmp_non_dominating_branches_not_folded(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT: br i1 [[C]], label %[[BB1:.*]], label %[[BB2:.*]]
+; CHECK: [[BB1]]:
+; CHECK-NEXT: [[CALL1:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[X]], ptr noundef nonnull dereferenceable(1) [[Y]])
+; CHECK-NEXT: br label %[[END:.*]]
+; CHECK: [[BB2]]:
+; CHECK-NEXT: [[CALL3:%.*]] = call i32 @strcmp(ptr noundef nonnull dereferenceable(1) [[Y]], ptr noundef nonnull dereferenceable(1) [[X]])
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: [[CALL2:%.*]] = phi i32 [ [[CALL1]], %[[BB1]] ], [ [[CALL3]], %[[BB2]] ]
+; CHECK-NEXT: [[CMP2:%.*]] = icmp eq i32 [[CALL2]], 0
+; CHECK-NEXT: ret i1 [[CMP2]]
+;
+ br i1 %c, label %bb1, label %bb2
+
+bb1:
+ %call1 = call i32 @strcmp(ptr %x, ptr %y)
+ %cmp1 = icmp eq i32 %call1, 0
+ br label %end
+
+bb2:
+ %call2 = call i32 @strcmp(ptr %y, ptr %x)
+ %cmp2 = icmp eq i32 %call2, 0
+ br label %end
+
+end:
+ %r = phi i1 [ %cmp1, %bb1 ], [ %cmp2, %bb2 ]
+ ret i1 %r
+}
+
+; Negative test: strcmp(x, x) has both "arguments" be the same value, this
+; is already folded by the library call simplifier and should not interact
+; with the new fold.
+define i1 @strcmp_same_pointer_not_ambiguous(ptr %x) {
+; CHECK-LABEL: define i1 @strcmp_same_pointer_not_ambiguous(
+; CHECK-SAME: ptr [[X:%.*]]) {
+; CHECK-NEXT: ret i1 true
+;
+ %call1 = call i32 @strcmp(ptr %x, ptr %x)
+ %cmp1 = icmp eq i32 %call1, 0
+ ret i1 %cmp1
+}
More information about the llvm-commits
mailing list