[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