[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