[llvm] [InstCombine] Fold strlen/wcslen over select/phi of constants (PR #223246)

via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 13 07:09:03 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Haram Jeong (haramj)

<details>
<summary>Changes</summary>

Fixes #<!-- -->217529

This teaches InstCombine to fold unary constant-foldable libcalls, specifically `strlen` and `wcslen`, through `select` and `phi` operands when every reachable incoming value can be folded.

For example, this allows cases like:

```llvm
%p = select i1 %c, ptr @<!-- -->str0, ptr @<!-- -->str1
%len = call i64 @<!-- -->strlen(ptr %p)
```

to become:

```llvm
%len = select i1 %c, i64 4, i64 7
````

The fold also handles nested select/phi trees, while rejecting cases with non-constant, mutable, or otherwise non-foldable incoming pointers.

The direct two-constant `select` case is left to the existing SimplifyLibCalls path so existing optimization remarks behavior is preserved.

Tests cover:
- `strlen` over select, nested select, phi, and mixed select/phi trees
- `wcslen` over select, nested select, and phi
- non-foldable pointer arms
- mutable globals
- existing optimization remark behavior

Verification:
- `ninja -C build opt FileCheck llvm-config`
- `ninja -C build opt`
- `git diff --check`
- `build/bin/llvm-lit -sv llvm/test/Transforms/InstCombine/strlen-select.ll llvm/test/Transforms/InstCombine/wcslen-select.ll llvm/test/Transforms/InstCombine/strlen-4.ll llvm/test/Transforms/Util/libcalls-opt-remarks.ll`

---

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


5 Files Affected:

- (modified) llvm/lib/Analysis/ConstantFolding.cpp (+19) 
- (modified) llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp (+123-3) 
- (modified) llvm/test/Transforms/InstCombine/strlen-4.ll (+2-3) 
- (added) llvm/test/Transforms/InstCombine/strlen-select.ll (+401) 
- (added) llvm/test/Transforms/InstCombine/wcslen-select.ll (+120) 


``````````diff
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index aca2556cd48eb..30f058a01b818 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -2201,12 +2201,14 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F,
   case LibFunc_sinhf_finite:
   case LibFunc_sqrt:
   case LibFunc_sqrtf:
+  case LibFunc_strlen:
   case LibFunc_tan:
   case LibFunc_tanf:
   case LibFunc_tanh:
   case LibFunc_tanhf:
   case LibFunc_trunc:
   case LibFunc_truncf:
+  case LibFunc_wcslen:
     return true;
   default:
     return false;
@@ -2607,6 +2609,23 @@ static Constant *ConstantFoldScalarCall1(StringRef Name,
     }
   }
 
+  if (Name == "strlen") {
+    if (uint64_t Len = GetStringLength(Operands[0]))
+      return ConstantInt::get(Ty, Len - 1);
+    return nullptr;
+  }
+
+  if (Name == "wcslen") {
+    if (!TLI || !Call || !Call->getModule())
+      return nullptr;
+    unsigned WCharSize = TLI->getWCharSize(*Call->getModule()) * 8;
+    if (WCharSize == 0)
+      return nullptr;
+    if (uint64_t Len = GetStringLength(Operands[0], WCharSize))
+      return ConstantInt::get(Ty, Len - 1);
+    return nullptr;
+  }
+
   if (auto *Op = dyn_cast<ConstantFP>(Operands[0])) {
     APFloat U = Op->getValueAPF();
 
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 86dd4397f22c9..0982189eabf5a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -16,14 +16,18 @@
 #include "llvm/ADT/APSInt.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/Bitset.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/Statistic.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Analysis/AliasAnalysis.h"
 #include "llvm/Analysis/AssumeBundleQueries.h"
 #include "llvm/Analysis/AssumptionCache.h"
+#include "llvm/Analysis/ConstantFolding.h"
 #include "llvm/Analysis/InstructionSimplify.h"
 #include "llvm/Analysis/Loads.h"
 #include "llvm/Analysis/MemoryBuiltins.h"
@@ -94,6 +98,79 @@ static cl::opt<unsigned> GuardWideningWindow(
     cl::desc("How wide an instruction window to bypass looking for "
              "another guard"));
 
+static Value *foldUnaryCallWithOperand(CallInst &CI, Value *Op,
+                                       const SimplifyQuery &SQ) {
+  assert(CI.arg_size() == 1 && "Expected a unary call");
+  SmallVector<Value *, 1> Args = {Op};
+  return simplifyCall(&CI, CI.getCalledOperand(), Args,
+                      SQ.getWithInstruction(&CI));
+}
+
+static Value *foldUnaryCallThroughSelectOrPhi(
+    CallInst &CI, Value *Op, const SimplifyQuery &SQ, IRBuilderBase &Builder,
+    unsigned Depth, SmallPtrSetImpl<Value *> &Visiting,
+    DenseMap<Value *, Value *> &Cache) {
+  if (Value *V = foldUnaryCallWithOperand(CI, Op, SQ))
+    return V;
+
+  if (Depth == 0)
+    return nullptr;
+  if (Value *V = Cache.lookup(Op))
+    return V;
+  if (!Visiting.insert(Op).second)
+    return nullptr;
+
+  scope_exit RemoveFromVisiting([&]() { Visiting.erase(Op); });
+
+  if (auto *Sel = dyn_cast<SelectInst>(Op)) {
+    Value *TrueV = foldUnaryCallThroughSelectOrPhi(
+        CI, Sel->getTrueValue(), SQ, Builder, Depth - 1, Visiting, Cache);
+    if (!TrueV)
+      return nullptr;
+    Value *FalseV = foldUnaryCallThroughSelectOrPhi(
+        CI, Sel->getFalseValue(), SQ, Builder, Depth - 1, Visiting, Cache);
+    if (!FalseV)
+      return nullptr;
+    if (TrueV == FalseV)
+      return Cache[Op] = TrueV;
+
+    IRBuilderBase::InsertPointGuard Guard(Builder);
+    Builder.SetInsertPoint(Sel->getNextNode());
+    return Cache[Op] = Builder.CreateSelect(Sel->getCondition(), TrueV, FalseV);
+  }
+
+  if (auto *Phi = dyn_cast<PHINode>(Op)) {
+    SmallVector<Value *, 8> IncomingValues;
+    Value *CommonValue = nullptr;
+    bool AllSame = true;
+    for (Value *Incoming : Phi->incoming_values()) {
+      Value *IncomingV = foldUnaryCallThroughSelectOrPhi(
+          CI, Incoming, SQ, Builder, Depth - 1, Visiting, Cache);
+      if (!IncomingV)
+        return nullptr;
+      IncomingValues.push_back(IncomingV);
+      if (!CommonValue)
+        CommonValue = IncomingV;
+      else if (IncomingV != CommonValue)
+        AllSame = false;
+    }
+
+    if (AllSame)
+      return Cache[Op] = CommonValue;
+
+    IRBuilderBase::InsertPointGuard Guard(Builder);
+    Builder.SetInsertPoint(Phi->getParent(),
+                           Phi->getParent()->getFirstInsertionPt());
+    PHINode *NewPhi =
+        Builder.CreatePHI(CI.getType(), Phi->getNumIncomingValues());
+    for (auto [IncomingV, IncomingBB] : zip(IncomingValues, Phi->blocks()))
+      NewPhi->addIncoming(IncomingV, IncomingBB);
+    return Cache[Op] = NewPhi;
+  }
+
+  return nullptr;
+}
+
 /// Return the specified type promoted as it would be to pass though a va_arg
 /// area.
 static Type *getPromotedType(Type *Ty) {
@@ -2041,13 +2118,49 @@ static Value *foldCmpIntrinsicOfExtended(IntrinsicInst *II,
 /// instructions. For normal calls, it allows visitCallBase to do the heavy
 /// lifting.
 Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
+  auto TryFoldUnaryCallThroughSelectOrPhi =
+      [&]() -> std::optional<Instruction *> {
+    if (CI.use_empty() || CI.arg_size() != 1 || isa<IntrinsicInst>(&CI))
+      return std::nullopt;
+    Function *F = CI.getCalledFunction();
+    if (!F || !canConstantFoldCallTo(&CI, F, SQ.TLI))
+      return std::nullopt;
+
+    Value *Op = CI.getArgOperand(0);
+    if (!isa<SelectInst>(Op) && !isa<PHINode>(Op))
+      return std::nullopt;
+    if (auto *Sel = dyn_cast<SelectInst>(Op))
+      if (foldUnaryCallWithOperand(CI, Sel->getTrueValue(), SQ) &&
+          foldUnaryCallWithOperand(CI, Sel->getFalseValue(), SQ))
+        return std::nullopt;
+
+    SmallPtrSet<Value *, 8> Visiting;
+    DenseMap<Value *, Value *> Cache;
+    if (Value *V = foldUnaryCallThroughSelectOrPhi(CI, Op, SQ, Builder, 16,
+                                                   Visiting, Cache)) {
+      replaceInstUsesWith(CI, V);
+      return eraseInstFromFunction(CI);
+    }
+    return std::nullopt;
+  };
+
   // Don't try to simplify calls without uses. It will not do anything useful,
   // but will result in the following folds being skipped.
   if (!CI.use_empty()) {
+    if (std::optional<Instruction *> I = TryFoldUnaryCallThroughSelectOrPhi())
+      return *I;
+
     SmallVector<Value *, 8> Args(CI.args());
     if (Value *V = simplifyCall(&CI, CI.getCalledOperand(), Args,
-                                SQ.getWithInstruction(&CI)))
+                                SQ.getWithInstruction(&CI))) {
+      if (Function *F = CI.getCalledFunction())
+        if (CI.arg_size() == 1 &&
+            (F->getName() == "strlen" || F->getName() == "wcslen")) {
+          replaceInstUsesWith(CI, V);
+          return eraseInstFromFunction(CI);
+        }
       return replaceInstUsesWith(CI, V);
+    }
   }
 
   if (Value *FreedOp = getFreedOperand(&CI, &TLI))
@@ -2061,8 +2174,15 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
   }
 
   IntrinsicInst *II = dyn_cast<IntrinsicInst>(&CI);
-  if (!II)
-    return visitCallBase(CI);
+  if (!II) {
+    Instruction *I = visitCallBase(CI);
+    if (I && I != &CI)
+      return I;
+
+    if (std::optional<Instruction *> R = TryFoldUnaryCallThroughSelectOrPhi())
+      return *R;
+    return I;
+  }
 
   // Intrinsics cannot occur in an invoke or a callbr, so handle them here
   // instead of in visitCallBase.
diff --git a/llvm/test/Transforms/InstCombine/strlen-4.ll b/llvm/test/Transforms/InstCombine/strlen-4.ll
index c8d2563b344a7..7aa86d5c6c3cc 100644
--- a/llvm/test/Transforms/InstCombine/strlen-4.ll
+++ b/llvm/test/Transforms/InstCombine/strlen-4.ll
@@ -155,9 +155,8 @@ define i64 @fold_strlen_s3_s5_s7(i32 %X) {
 ; CHECK-LABEL: @fold_strlen_s3_s5_s7(
 ; CHECK-NEXT:    [[X_EQ_3:%.*]] = icmp eq i32 [[X:%.*]], 3
 ; CHECK-NEXT:    [[X_EQ_5:%.*]] = icmp eq i32 [[X]], 5
-; CHECK-NEXT:    [[SEL_X_EQ_5:%.*]] = select i1 [[X_EQ_5]], ptr @s5, ptr @s7
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[X_EQ_3]], ptr @s3, ptr [[SEL_X_EQ_5]]
-; CHECK-NEXT:    [[LEN:%.*]] = tail call i64 @strlen(ptr noundef nonnull dereferenceable(1) [[SEL]])
+; CHECK-NEXT:    [[TMP1:%.*]] = select i1 [[X_EQ_5]], i64 5, i64 7
+; CHECK-NEXT:    [[LEN:%.*]] = select i1 [[X_EQ_3]], i64 3, i64 [[TMP1]]
 ; CHECK-NEXT:    ret i64 [[LEN]]
 ;
 
diff --git a/llvm/test/Transforms/InstCombine/strlen-select.ll b/llvm/test/Transforms/InstCombine/strlen-select.ll
new file mode 100644
index 0000000000000..0ab68ea77a100
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/strlen-select.ll
@@ -0,0 +1,401 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; Verify that strlen calls over selects and phis of constant string literals
+; are folded to selects/phis of the corresponding constant lengths.
+
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+ at s0 = private unnamed_addr constant [3 x i8] c"ok\00", align 1
+ at s1 = private unnamed_addr constant [8 x i8] c"missing\00", align 1
+ at s2 = private unnamed_addr constant [10 x i8] c"cancelled\00", align 1
+ at s3 = private unnamed_addr constant [11 x i8] c"timed out!\00", align 1
+ at sx = external global [0 x i8]
+
+declare i64 @strlen(ptr)
+
+; strlen(x ? @s0 : @s1) -> x ? 2 : 7
+define i64 @fold_strlen_select(i1 %c) {
+; CHECK-LABEL: define i64 @fold_strlen_select(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:    [[L:%.*]] = select i1 [[C]], i64 2, i64 7
+; CHECK-NEXT:    ret i64 [[L]]
+;
+  %s = select i1 %c, ptr @s0, ptr @s1
+  %l = call i64 @strlen(ptr %s)
+  ret i64 %l
+}
+
+; strlen(c1 ? @s0 : c2 ? @s1 : @s2) -> c1 ? 2 : c2 ? 7 : 9
+define i64 @fold_strlen_nested_select(i1 %c1, i1 %c2) {
+; CHECK-LABEL: define i64 @fold_strlen_nested_select(
+; CHECK-SAME: i1 [[C1:%.*]], i1 [[C2:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = select i1 [[C2]], i64 7, i64 9
+; CHECK-NEXT:    [[L:%.*]] = select i1 [[C1]], i64 2, i64 [[TMP1]]
+; CHECK-NEXT:    ret i64 [[L]]
+;
+  %inner = select i1 %c2, ptr @s1, ptr @s2
+  %sel = select i1 %c1, ptr @s0, ptr %inner
+  %l = call i64 @strlen(ptr %sel)
+  ret i64 %l
+}
+
+; strlen(phi over @s0.. at s3) -> phi over the constant lengths
+define i64 @fold_strlen_phi(i32 %x) {
+; CHECK-LABEL: define i64 @fold_strlen_phi(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[IDX:%.*]] = and i32 [[X]], 3
+; CHECK-NEXT:    switch i32 [[IDX]], label %[[CASE0:.*]] [
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:      i32 3, label %[[CASE3:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    br label %[[JOIN:.*]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[CASE3]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[JOIN]]:
+; CHECK-NEXT:    [[L:%.*]] = phi i64 [ 2, %[[CASE0]] ], [ 7, %[[CASE1]] ], [ 9, %[[CASE2]] ], [ 10, %[[CASE3]] ]
+; CHECK-NEXT:    ret i64 [[L]]
+;
+entry:
+  %idx = and i32 %x, 3
+  switch i32 %idx, label %case0 [
+  i32 1, label %case1
+  i32 2, label %case2
+  i32 3, label %case3
+  ]
+case0:
+  br label %join
+case1:
+  br label %join
+case2:
+  br label %join
+case3:
+  br label %join
+join:
+  %p = phi ptr [ @s0, %case0 ], [ @s1, %case1 ], [ @s2, %case2 ], [ @s3, %case3 ]
+  %l = call i64 @strlen(ptr %p)
+  ret i64 %l
+}
+
+; The length select for a phi incoming value is inserted in the incoming block.
+define i64 @fold_strlen_phi_select_incoming(i1 %c) {
+; CHECK-LABEL: define i64 @fold_strlen_phi_select_incoming(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[BB0:.*]]
+; CHECK:       [[BB0]]:
+; CHECK-NEXT:    [[TMP1:%.*]] = select i1 [[C]], i64 2, i64 7
+; CHECK-NEXT:    br label %[[JOIN:.*]]
+; CHECK:       [[BB1:.*]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[JOIN]]:
+; CHECK-NEXT:    [[L:%.*]] = phi i64 [ [[TMP1]], %[[BB0]] ], [ 9, %[[BB1]] ]
+; CHECK-NEXT:    ret i64 [[L]]
+;
+entry:
+  br label %in_true
+in_true:
+  %ins = select i1 %c, ptr @s0, ptr @s1
+  br label %join
+in_false:
+  br label %join
+join:
+  %p = phi ptr [ %ins, %in_true ], [ @s2, %in_false ]
+  %l = call i64 @strlen(ptr %p)
+  ret i64 %l
+}
+
+; strlen(c ? phi : @s2) -> c ? phi-of-lengths : 9
+define i64 @fold_strlen_select_of_phi(i1 %c, i32 %x) {
+; CHECK-LABEL: define i64 @fold_strlen_select_of_phi(
+; CHECK-SAME: i1 [[C:%.*]], i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i32 [[X]] to i1
+; CHECK-NEXT:    switch i1 [[TRUNC]], label %[[CASE0:.*]] [
+; CHECK-NEXT:      i1 true, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    br label %[[JOIN:.*]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[JOIN]]:
+; CHECK-NEXT:    [[TMP1:%.*]] = phi i64 [ 2, %[[CASE0]] ], [ 7, %[[CASE1]] ]
+; CHECK-NEXT:    [[L:%.*]] = select i1 [[C]], i64 [[TMP1]], i64 9
+; CHECK-NEXT:    ret i64 [[L]]
+;
+entry:
+  %idx = and i32 %x, 1
+  switch i32 %idx, label %case0 [
+  i32 1, label %case1
+  ]
+case0:
+  br label %join
+case1:
+  br label %join
+join:
+  %p = phi ptr [ @s0, %case0 ], [ @s1, %case1 ]
+  %sel = select i1 %c, ptr %p, ptr @s2
+  %l = call i64 @strlen(ptr %sel)
+  ret i64 %l
+}
+
+; The strlen call is in a different block than the phi; the length phi is
+; created in the block of the pointer phi and flows to the use.
+define i64 @fold_strlen_phi_other_block(i32 %x) {
+; CHECK-LABEL: define i64 @fold_strlen_phi_other_block(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[IDX:%.*]] = and i32 [[X]], 3
+; CHECK-NEXT:    switch i32 [[IDX]], label %[[CASE0:.*]] [
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:      i32 3, label %[[CASE3:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    br label %[[JOIN:.*]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[CASE3]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[JOIN]]:
+; CHECK-NEXT:    [[L:%.*]] = phi i64 [ 2, %[[CASE0]] ], [ 7, %[[CASE1]] ], [ 9, %[[CASE2]] ], [ 10, %[[CASE3]] ]
+; CHECK-NEXT:    br label %[[USE:.*]]
+; CHECK:       [[USE]]:
+; CHECK-NEXT:    ret i64 [[L]]
+;
+entry:
+  %idx = and i32 %x, 3
+  switch i32 %idx, label %case0 [
+  i32 1, label %case1
+  i32 2, label %case2
+  i32 3, label %case3
+  ]
+case0:
+  br label %join
+case1:
+  br label %join
+case2:
+  br label %join
+case3:
+  br label %join
+join:
+  %p = phi ptr [ @s0, %case0 ], [ @s1, %case1 ], [ @s2, %case2 ], [ @s3, %case3 ]
+  br label %use
+use:
+  %l = call i64 @strlen(ptr %p)
+  ret i64 %l
+}
+
+; Do not fold strlen(x ? @s0 : %q).
+define i64 @call_strlen_select_nonconst(i1 %c, ptr %q) {
+; CHECK-LABEL: define i64 @call_strlen_select_nonconst(
+; CHECK-SAME: i1 [[C:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C]], ptr @s0, ptr [[Q]]
+; CHECK-NEXT:    [[L:%.*]] = call i64 @strlen(ptr noundef nonnull dereferenceable(1) [[S]])
+; CHECK-NEXT:    ret i64 [[L]]
+;
+  %s = select i1 %c, ptr @s0, ptr %q
+  %l = call i64 @strlen(ptr %s)
+  ret i64 %l
+}
+
+; Do not fold strlen(phi over @s0 and %q).
+define i64 @call_strlen_phi_nonconst(i32 %x, ptr %q) {
+; CHECK-LABEL: define i64 @call_strlen_phi_nonconst(
+; CHECK-SAME: i32 [[X:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    switch i32 [[X]], label %[[CASE0:.*]] [
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    br label %[[JOIN:.*]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[JOIN]]:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ @s0, %[[CASE0]] ], [ [[Q]], %[[CASE1]] ]
+; CHECK-NEXT:    [[L:%.*]] = call i64 @strlen(ptr noundef nonnull dereferenceable(1) [[P]])
+; CHECK-NEXT:    ret i64 [[L]]
+;
+entry:
+  switch i32 %x, label %case0 [
+  i32 1, label %case1
+  ]
+case0:
+  br label %join
+case1:
+  br label %join
+join:
+  %p = phi ptr [ @s0, %case0 ], [ %q, %case1 ]
+  %l = call i64 @strlen(ptr %p)
+  ret i64 %l
+}
+
+; Do not fold a nested select with a non-constant leaf.
+define i64 @call_strlen_nested_nonconst(i1 %c1, i1 %c2, ptr %q) {
+; CHECK-LABEL: define i64 @call_strlen_nested_nonconst(
+; CHECK-SAME: i1 [[C1:%.*]], i1 [[C2:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT:    [[INNER:%.*]] = select i1 [[C2]], ptr @s1, ptr [[Q]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[C1]], ptr @s0, ptr [[INNER]]
+; CHECK-NEXT:    [[L:%.*]] = call i64 @strlen(ptr noundef nonnull dereferenceable(1) [[SEL]])
+; CHECK-NEXT:    ret i64 [[L]]
+;
+  %inner = select i1 %c2, ptr @s1, ptr %q
+  %sel = select i1 %c1, ptr @s0, ptr %inner
+  %l = call i64 @strlen(ptr %sel)
+  ret i64 %l
+}
+
+; strlen over a phi whose incoming value is a phi in another block: the length
+; phis are inserted in the same blocks as the pointer phis they replace.
+define i64 @fold_strlen_nested_phi(i32 %x) {
+; CHECK-LABEL: define i64 @fold_strlen_nested_phi(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    switch i32 [[X]], label %[[CASE2:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    br label %[[MID:.*]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[MID]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    br label %[[JOIN:.*]]
+; CHECK:       [[MID]]:
+; CHECK-NEXT:    [[TMP0:%.*]] = phi i64 [ 2, %[[CASE0]] ], [ 7, %[[CASE1]] ]
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[JOIN]]:
+; CHECK-NEXT:    [[L:%.*]] = phi i64 [ [[TMP0]], %[[MID]] ], [ 9, %[[CASE2]] ]
+; CHECK-NEXT:    ret i64 [[L]]
+;
+entry:
+  switch i32 %x, label %case2 [
+  i32 0, label %case0
+  i32 1, label %case1
+  ]
+case0:
+  br label %mid
+case1:
+  br label %mid
+case2:
+  br label %join
+mid:
+  %inner = phi ptr [ @s0, %case0 ], [ @s1, %case1 ]
+  br label %join
+join:
+  %outer = phi ptr [ %inner, %mid ], [ @s2, %case2 ]
+  %l = call i64 @strlen(ptr %outer)
+  ret i64 %l
+}
+
+; A constant in-bounds offset GEP is still a constant string.
+define i64 @fold_strlen_const_gep_arm(i1 %c) {
+; CHECK-LABEL: define i64 @fold_strlen_const_gep_arm(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:    [[L:%.*]] = select i1 [[C]], i64 2, i64 4
+; CHECK-NEXT:    ret i64 [[L]]
+;
+  %g = getelementptr inbounds [8 x i8], ptr @s1, i64 0, i64 3
+  %s = select i1 %c, ptr @s0, ptr %g
+  %l = call i64 @strlen(ptr %s)
+  ret i64 %l
+}
+
+; Do not fold a select with a variable-offset GEP arm.
+define i64 @call_strlen_var_gep_arm(i1 %c, i64 %i) {
+; CHECK-LABEL: define i64 @call_strlen_var_gep_arm(
+; CHECK-SAME: i1 [[C:%.*]], i64 [[I:%.*]]) {
+; CHECK-NEXT:    [[G:%.*]] = getelementptr inbounds i8, ptr @s1, i64 [[I]]
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C]], ptr @s0, ptr [[G]]
+; CHECK-NEXT:    [[L:%.*]] = call i64 @strlen(ptr noundef nonnull dereferenceable(1) [[S]])
+; CHECK-NEXT:    ret i64 [[L]]
+;
+  %g = getelementptr inbounds [8 x i8], ptr @s1, i64 0, i64 %i
+  %s = select i1 %c, ptr @s0, ptr %g
+  %l = call i64 @strlen(ptr %s)
+  ret i64 %l
+}
+
+; Two strlen calls on the same select are both folded; the select-of-lengths is
+; shared once InstCombine merges the duplicate selects.
+define i64 @fold_strlen_two_uses(i1 %c) {
+; CHECK-LABEL: define i64 @fold_strlen_two_uses(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = select i1 [[C]], i64 4, i64 14
+; CHECK-NEXT:    ret i64 [[R]]
+;
+  %s = select i1 %c, ptr @s0, ptr @s1
+  %l1 = call i64 @strlen(ptr %s)
+  %l2 = call i64 @strlen(ptr %s)
+  %r = add i64 %l1, %l2
+  ret i64 %r
+}
+
+; Do not fold a select whose arm is a loaded pointer.
+ at g_ptr = external global ptr
+
+define i64 @call_strlen_loaded_arm(i1 %c) {
+; CHECK-LABEL: define i64 @call_strlen_loaded_arm(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:    [[LOADED:%.*]] = load ptr, ptr @g_ptr, align 8
+; CHECK-NEX...
[truncated]

``````````

</details>


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


More information about the llvm-commits mailing list