[llvm] [InstCombine] Fold strlen/wcslen over select/phi of constants (PR #223246)
Haram Jeong via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 13 18:27:05 PDT 2026
https://github.com/haramj updated https://github.com/llvm/llvm-project/pull/223246
>From ae605b914df994b7cfb0b1b98c5469bdc9093389 Mon Sep 17 00:00:00 2001
From: haramjeong <04harams77 at gmail.com>
Date: Sun, 13 Sep 2026 22:37:22 +0900
Subject: [PATCH] [InstCombine] Fold strlen/wcslen over select/phi of constants
---
llvm/lib/Analysis/ConstantFolding.cpp | 21 +
.../InstCombine/InstCombineCalls.cpp | 129 +++++-
llvm/test/Transforms/InstCombine/strlen-4.ll | 5 +-
.../Transforms/InstCombine/strlen-select.ll | 412 ++++++++++++++++++
.../Transforms/InstCombine/wcslen-select.ll | 120 +++++
5 files changed, 680 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/Transforms/InstCombine/strlen-select.ll
create mode 100644 llvm/test/Transforms/InstCombine/wcslen-select.ll
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index aca2556cd48eb..aacef0837f7f2 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -2092,6 +2092,8 @@ static bool anyTypeContainsFP(Type *RetTy, ArrayRef<Value *> Ops) {
bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F,
const TargetLibraryInfo *TLI) {
+ if (Call->isMustTailCall())
+ return false;
if (Call->isNoBuiltin())
return false;
if (Call->getFunctionType() != F->getFunctionType())
@@ -2201,12 +2203,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 +2611,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..10133d9340f7d 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,50 @@ 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.isMustTailCall() || 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 (!CI.use_empty() && !CI.isMustTailCall()) {
+ 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 +2175,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..49cc6ff233d46
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/strlen-select.ll
@@ -0,0 +1,412 @@
+; 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-NEXT: [[S:%.*]] = select i1 [[C]], ptr @s0, ptr [[LOADED]]
+; CHECK-NEXT: [[L:%.*]] = call i64 @strlen(ptr noundef nonnull dereferenceable(1) [[S]])
+; CHECK-NEXT: ret i64 [[L]]
+;
+ %loaded = load ptr, ptr @g_ptr
+ %s = select i1 %c, ptr @s0, ptr %loaded
+ %l = call i64 @strlen(ptr %s)
+ ret i64 %l
+}
+
+; Do not fold a phi over a mutable (non-constant) global string.
+ at g_mut = global [3 x i8] c"ok\00", align 1
+
+define i64 @call_strlen_mutable_global(i32 %x) {
+; CHECK-LABEL: define i64 @call_strlen_mutable_global(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; 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 [ @g_mut, %[[CASE0]] ], [ @s1, %[[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 [ @g_mut, %case0 ], [ @s1, %case1 ]
+ %l = call i64 @strlen(ptr %p)
+ ret i64 %l
+}
+
+; strlen(...) != 0 still wins over the select fold (the char-load fold).
+define i1 @call_strlen_zero_cmp(i1 %c) {
+; CHECK-LABEL: define i1 @call_strlen_zero_cmp(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT: ret i1 true
+;
+ %s = select i1 %c, ptr @s0, ptr @s1
+ %l = call i64 @strlen(ptr %s)
+ %r = icmp ne i64 %l, 0
+ ret i1 %r
+}
+
+define i64 @call_strlen_musttail_select(ptr %p) {
+; CHECK-LABEL: define i64 @call_strlen_musttail_select(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[L:%.*]] = musttail call i64 @strlen(ptr nonnull @s1)
+; CHECK-NEXT: ret i64 [[L]]
+;
+ %s = select i1 false, ptr @s0, ptr @s1
+ %l = musttail call i64 @strlen(ptr %s)
+ ret i64 %l
+}
diff --git a/llvm/test/Transforms/InstCombine/wcslen-select.ll b/llvm/test/Transforms/InstCombine/wcslen-select.ll
new file mode 100644
index 0000000000000..ba52ca45e9baa
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/wcslen-select.ll
@@ -0,0 +1,120 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; Verify that wcslen 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 i32] [i32 111, i32 107, i32 0]
+ at s1 = private unnamed_addr constant [8 x i32] [i32 109, i32 105, i32 115, i32 115, i32 105, i32 110, i32 103, i32 0]
+ at s2 = private unnamed_addr constant [10 x i32] [i32 99, i32 97, i32 110, i32 99, i32 101, i32 108, i32 108, i32 101, i32 100, i32 0]
+ at sx = external global [0 x i32]
+
+declare i64 @wcslen(ptr)
+
+; wcslen(x ? @s0 : @s1) -> x ? 2 : 7
+define i64 @fold_wcslen_select(i1 %c) {
+; CHECK-LABEL: define i64 @fold_wcslen_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 @wcslen(ptr %s)
+ ret i64 %l
+}
+
+; wcslen(c1 ? @s0 : c2 ? @s1 : @s2) -> c1 ? 2 : c2 ? 7 : 9
+define i64 @fold_wcslen_nested_select(i1 %c1, i1 %c2) {
+; CHECK-LABEL: define i64 @fold_wcslen_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 @wcslen(ptr %sel)
+ ret i64 %l
+}
+
+; wcslen(phi over @s0.. at s2) -> phi over the constant lengths
+define i64 @fold_wcslen_phi(i32 %x) {
+; CHECK-LABEL: define i64 @fold_wcslen_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: ]
+; CHECK: [[CASE0]]:
+; CHECK-NEXT: br label %[[JOIN:.*]]
+; CHECK: [[CASE1]]:
+; CHECK-NEXT: br label %[[JOIN]]
+; CHECK: [[CASE2]]:
+; CHECK-NEXT: br label %[[JOIN]]
+; CHECK: [[JOIN]]:
+; CHECK-NEXT: [[L:%.*]] = phi i64 [ 2, %[[CASE0]] ], [ 7, %[[CASE1]] ], [ 9, %[[CASE2]] ]
+; CHECK-NEXT: ret i64 [[L]]
+;
+entry:
+ %idx = and i32 %x, 3
+ switch i32 %idx, label %case0 [
+ i32 1, label %case1
+ i32 2, label %case2
+ ]
+case0:
+ br label %join
+case1:
+ br label %join
+case2:
+ br label %join
+join:
+ %p = phi ptr [ @s0, %case0 ], [ @s1, %case1 ], [ @s2, %case2 ]
+ %l = call i64 @wcslen(ptr %p)
+ ret i64 %l
+}
+
+; Do not fold wcslen(x ? @s0 : %q).
+define i64 @call_wcslen_select_nonconst(i1 %c, ptr %q) {
+; CHECK-LABEL: define i64 @call_wcslen_select_nonconst(
+; CHECK-SAME: i1 [[C:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT: [[S:%.*]] = select i1 [[C]], ptr @s0, ptr [[Q]]
+; CHECK-NEXT: [[L:%.*]] = call i64 @wcslen(ptr [[S]])
+; CHECK-NEXT: ret i64 [[L]]
+;
+ %s = select i1 %c, ptr @s0, ptr %q
+ %l = call i64 @wcslen(ptr %s)
+ ret i64 %l
+}
+
+; Do not fold wcslen(phi over @s0 and %q).
+define i64 @call_wcslen_phi_nonconst(i32 %x, ptr %q) {
+; CHECK-LABEL: define i64 @call_wcslen_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 @wcslen(ptr [[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 @wcslen(ptr %p)
+ ret i64 %l
+}
More information about the llvm-commits
mailing list