[llvm] Reland "[Transforms] LoopIdiomRecognize recognize strlen and wcslen #108985" (PR #132572)
Henry Jiang via llvm-commits
llvm-commits at lists.llvm.org
Sat Mar 22 16:25:29 PDT 2025
https://github.com/mustartt created https://github.com/llvm/llvm-project/pull/132572
Reland https://github.com/llvm/llvm-project/pull/108985
In this patch, also add `strlen` and `wcslen` to the common idiom names list so that functions named `strlen` or `wcslen` does not get transformed.
>From facd7dfc80d655fe49baf4bf27e144a4c890a149 Mon Sep 17 00:00:00 2001
From: Henry Jiang <h243jian at uwaterloo.ca>
Date: Fri, 21 Mar 2025 09:49:10 -0400
Subject: [PATCH 1/3] Reland "[Transforms] LoopIdiomRecognize recognize strlen
and wcslen (#108985)" (#131412)
Relands https://github.com/llvm/llvm-project/pull/108985
This PR continues the effort made in
https://discourse.llvm.org/t/rfc-strlen-loop-idiom-recognition-folding/55848
and https://reviews.llvm.org/D83392 and https://reviews.llvm.org/D88460
to extend `LoopIdiomRecognize` to find and replace loops of the form
```c
base = str;
while (*str)
++str;
```
and transforming the `strlen` loop idiom into the appropriate `strlen`
and `wcslen` library call which will give a small performance boost if
replaced.
```c
str = base + strlen(base)
len = str - base
```
---
.../Transforms/Scalar/LoopIdiomRecognize.h | 6 +
.../llvm/Transforms/Utils/BuildLibCalls.h | 6 +
.../Transforms/Scalar/LoopIdiomRecognize.cpp | 310 ++++++++-
llvm/lib/Transforms/Utils/BuildLibCalls.cpp | 9 +
.../LoopIdiom/strlen-not-emittable.ll | 66 ++
llvm/test/Transforms/LoopIdiom/strlen.ll | 614 ++++++++++++++++++
llvm/test/Transforms/LoopIdiom/wcslen16.ll | 126 ++++
llvm/test/Transforms/LoopIdiom/wcslen32.ll | 134 ++++
8 files changed, 1266 insertions(+), 5 deletions(-)
create mode 100644 llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll
create mode 100644 llvm/test/Transforms/LoopIdiom/strlen.ll
create mode 100644 llvm/test/Transforms/LoopIdiom/wcslen16.ll
create mode 100644 llvm/test/Transforms/LoopIdiom/wcslen32.ll
diff --git a/llvm/include/llvm/Transforms/Scalar/LoopIdiomRecognize.h b/llvm/include/llvm/Transforms/Scalar/LoopIdiomRecognize.h
index 0c6406d861851..241a3fc109360 100644
--- a/llvm/include/llvm/Transforms/Scalar/LoopIdiomRecognize.h
+++ b/llvm/include/llvm/Transforms/Scalar/LoopIdiomRecognize.h
@@ -34,6 +34,12 @@ struct DisableLIRP {
/// When true, Memcpy is disabled.
static bool Memcpy;
+
+ /// When true, Strlen is disabled.
+ static bool Strlen;
+
+ /// When true, Wcslen is disabled.
+ static bool Wcslen;
};
/// Performs Loop Idiom Recognize Pass.
diff --git a/llvm/include/llvm/Transforms/Utils/BuildLibCalls.h b/llvm/include/llvm/Transforms/Utils/BuildLibCalls.h
index a8fb38e726004..50f695dbe6c07 100644
--- a/llvm/include/llvm/Transforms/Utils/BuildLibCalls.h
+++ b/llvm/include/llvm/Transforms/Utils/BuildLibCalls.h
@@ -93,6 +93,12 @@ namespace llvm {
Value *emitStrLen(Value *Ptr, IRBuilderBase &B, const DataLayout &DL,
const TargetLibraryInfo *TLI);
+ /// Emit a call to the wcslen function to the builder, for the specified
+ /// pointer. Ptr is required to be some pointer type, and the return value has
+ /// 'size_t' type.
+ Value *emitWcsLen(Value *Ptr, IRBuilderBase &B, const DataLayout &DL,
+ const TargetLibraryInfo *TLI);
+
/// Emit a call to the strdup function to the builder, for the specified
/// pointer. Ptr is required to be some pointer type, and the return value has
/// 'i8*' type.
diff --git a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
index 8dc7f325f6262..b91cdc0748581 100644
--- a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
@@ -20,8 +20,7 @@
//
// TODO List:
//
-// Future loop memory idioms to recognize:
-// memcmp, strlen, etc.
+// Future loop memory idioms to recognize: memcmp, etc.
//
// This could recognize common matrix multiplies and dot product idioms and
// replace them with calls to BLAS (if linked in??).
@@ -33,6 +32,7 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
@@ -97,6 +97,7 @@ using namespace llvm;
STATISTIC(NumMemSet, "Number of memset's formed from loop stores");
STATISTIC(NumMemCpy, "Number of memcpy's formed from loop load+stores");
STATISTIC(NumMemMove, "Number of memmove's formed from loop load+stores");
+STATISTIC(NumStrLen, "Number of strlen's and wcslen's formed from loop loads");
STATISTIC(
NumShiftUntilBitTest,
"Number of uncountable loops recognized as 'shift until bitttest' idiom");
@@ -126,6 +127,22 @@ static cl::opt<bool, true>
cl::location(DisableLIRP::Memcpy), cl::init(false),
cl::ReallyHidden);
+bool DisableLIRP::Strlen;
+static cl::opt<bool, true>
+ DisableLIRPStrlen("disable-loop-idiom-strlen",
+ cl::desc("Proceed with loop idiom recognize pass, but do "
+ "not convert loop(s) to strlen."),
+ cl::location(DisableLIRP::Strlen), cl::init(false),
+ cl::ReallyHidden);
+
+bool DisableLIRP::Wcslen;
+static cl::opt<bool, true>
+ EnableLIRPWcslen("disable-loop-idiom-wcslen",
+ cl::desc("Proceed with loop idiom recognize pass, "
+ "enable conversion of loop(s) to wcslen."),
+ cl::location(DisableLIRP::Wcslen), cl::init(false),
+ cl::ReallyHidden);
+
static cl::opt<bool> UseLIRCodeSizeHeurs(
"use-lir-code-size-heurs",
cl::desc("Use loop idiom recognition code size heuristics when compiling "
@@ -246,6 +263,7 @@ class LoopIdiomRecognize {
bool recognizeShiftUntilBitTest();
bool recognizeShiftUntilZero();
+ bool recognizeAndInsertStrLen();
/// @}
};
@@ -1494,7 +1512,17 @@ bool LoopIdiomRecognize::runOnNoncountableLoop() {
return recognizePopcount() || recognizeAndInsertFFS() ||
recognizeShiftUntilBitTest() || recognizeShiftUntilZero() ||
- recognizeShiftUntilLessThan();
+ recognizeShiftUntilLessThan() || recognizeAndInsertStrLen();
+}
+
+/// Check if a Value is either a nullptr or a constant int zero
+static bool isZeroConstant(const Value *Val) {
+ if (isa<ConstantPointerNull>(Val))
+ return true;
+ const ConstantInt *CmpZero = dyn_cast<ConstantInt>(Val);
+ if (!CmpZero || !CmpZero->isZero())
+ return false;
+ return true;
}
/// Check if the given conditional branch is based on the comparison between
@@ -1512,8 +1540,7 @@ static Value *matchCondition(BranchInst *BI, BasicBlock *LoopEntry,
if (!Cond)
return nullptr;
- ConstantInt *CmpZero = dyn_cast<ConstantInt>(Cond->getOperand(1));
- if (!CmpZero || !CmpZero->isZero())
+ if (!isZeroConstant(Cond->getOperand(1)))
return nullptr;
BasicBlock *TrueSucc = BI->getSuccessor(0);
@@ -1529,6 +1556,279 @@ static Value *matchCondition(BranchInst *BI, BasicBlock *LoopEntry,
return nullptr;
}
+namespace {
+
+class StrlenVerifier {
+public:
+ explicit StrlenVerifier(const Loop *CurLoop, ScalarEvolution *SE,
+ const TargetLibraryInfo *TLI)
+ : CurLoop(CurLoop), SE(SE), TLI(TLI) {}
+
+ bool isValidStrlenIdiom() {
+ // Give up if the loop has multiple blocks, multiple backedges, or
+ // multiple exit blocks
+ if (CurLoop->getNumBackEdges() != 1 || CurLoop->getNumBlocks() != 1 ||
+ !CurLoop->getUniqueExitBlock())
+ return false;
+
+ // It should have a preheader and a branch instruction.
+ BasicBlock *Preheader = CurLoop->getLoopPreheader();
+ if (!Preheader)
+ return false;
+
+ BranchInst *EntryBI = dyn_cast<BranchInst>(Preheader->getTerminator());
+ if (!EntryBI)
+ return false;
+
+ // The loop exit must be conditioned on an icmp with 0 the null terminator.
+ // The icmp operand has to be a load on some SSA reg that increments
+ // by 1 in the loop.
+ BasicBlock *LoopBody = *CurLoop->block_begin();
+
+ // Skip if the body is too big as it most likely is not a strlen idiom.
+ if (!LoopBody || LoopBody->size() >= 15)
+ return false;
+
+ BranchInst *LoopTerm = dyn_cast<BranchInst>(LoopBody->getTerminator());
+ Value *LoopCond = matchCondition(LoopTerm, LoopBody);
+ if (!LoopCond)
+ return false;
+
+ LoadInst *LoopLoad = dyn_cast<LoadInst>(LoopCond);
+ if (!LoopLoad || LoopLoad->getPointerAddressSpace() != 0)
+ return false;
+
+ OperandType = LoopLoad->getType();
+ if (!OperandType || !OperandType->isIntegerTy())
+ return false;
+
+ // See if the pointer expression is an AddRec with constant step a of form
+ // ({n,+,a}) where a is the width of the char type.
+ Value *IncPtr = LoopLoad->getPointerOperand();
+ const SCEVAddRecExpr *LoadEv =
+ dyn_cast<SCEVAddRecExpr>(SE->getSCEV(IncPtr));
+ if (!LoadEv || LoadEv->getLoop() != CurLoop || !LoadEv->isAffine())
+ return false;
+ LoadBaseEv = LoadEv->getStart();
+
+ LLVM_DEBUG({
+ dbgs() << "pointer load scev: ";
+ LoadEv->print(outs());
+ dbgs() << "\n";
+ });
+
+ const SCEVConstant *Step =
+ dyn_cast<SCEVConstant>(LoadEv->getStepRecurrence(*SE));
+ if (!Step)
+ return false;
+
+ unsigned StepSize = 0;
+ StepSizeCI = dyn_cast<ConstantInt>(Step->getValue());
+ if (!StepSizeCI)
+ return false;
+ StepSize = StepSizeCI->getZExtValue();
+
+ // Verify that StepSize is consistent with platform char width.
+ OpWidth = OperandType->getIntegerBitWidth();
+ unsigned WcharSize = TLI->getWCharSize(*LoopLoad->getModule());
+ if (OpWidth != StepSize * 8)
+ return false;
+ if (OpWidth != 8 && OpWidth != 16 && OpWidth != 32)
+ return false;
+ if (OpWidth >= 16)
+ if (OpWidth != WcharSize * 8)
+ return false;
+
+ // Scan every instruction in the loop to ensure there are no side effects.
+ for (Instruction &I : *LoopBody)
+ if (I.mayHaveSideEffects())
+ return false;
+
+ BasicBlock *LoopExitBB = CurLoop->getExitBlock();
+ if (!LoopExitBB)
+ return false;
+
+ for (PHINode &PN : LoopExitBB->phis()) {
+ if (!SE->isSCEVable(PN.getType()))
+ return false;
+
+ const SCEV *Ev = SE->getSCEV(&PN);
+ if (!Ev)
+ return false;
+
+ LLVM_DEBUG({
+ dbgs() << "loop exit phi scev: ";
+ Ev->print(dbgs());
+ dbgs() << "\n";
+ });
+
+ // Since we verified that the loop trip count will be a valid strlen
+ // idiom, we can expand all lcssa phi with {n,+,1} as (n + strlen) and use
+ // SCEVExpander materialize the loop output.
+ const SCEVAddRecExpr *AddRecEv = dyn_cast<SCEVAddRecExpr>(Ev);
+ if (!AddRecEv || !AddRecEv->isAffine())
+ return false;
+
+ // We only want RecAddExpr with recurrence step that is constant. This
+ // is good enough for all the idioms we want to recognize. Later we expand
+ // and materialize the recurrence as {base,+,a} -> (base + a * strlen)
+ if (!dyn_cast<SCEVConstant>(AddRecEv->getStepRecurrence(*SE)))
+ return false;
+ }
+
+ return true;
+ }
+
+public:
+ const Loop *CurLoop;
+ ScalarEvolution *SE;
+ const TargetLibraryInfo *TLI;
+
+ unsigned OpWidth;
+ ConstantInt *StepSizeCI;
+ const SCEV *LoadBaseEv;
+ Type *OperandType;
+};
+
+} // namespace
+
+/// The Strlen Idiom we are trying to detect has the following structure
+///
+/// preheader:
+/// ...
+/// br label %body, ...
+///
+/// body:
+/// ... ; %0 is incremented by a gep
+/// %1 = load i8, ptr %0, align 1
+/// %2 = icmp eq i8 %1, 0
+/// br i1 %2, label %exit, label %body
+///
+/// exit:
+/// %lcssa = phi [%0, %body], ...
+///
+/// We expect the strlen idiom to have a load of a character type that
+/// is compared against '\0', and such load pointer operand must have scev
+/// expression of the form {%str,+,c} where c is a ConstantInt of the
+/// appropiate character width for the idiom, and %str is the base of the string
+/// And, that all lcssa phis have the form {...,+,n} where n is a constant,
+///
+/// When transforming the output of the strlen idiom, the lccsa phi are
+/// expanded using SCEVExpander as {base scev,+,a} -> (base scev + a * strlen)
+/// and all subsequent uses are replaced. For example,
+///
+/// \code{.c}
+/// const char* base = str;
+/// while (*str != '\0')
+/// ++str;
+/// size_t result = str - base;
+/// \endcode
+///
+/// will be transformed as follows: The idiom will be replaced by a strlen
+/// computation to compute the address of the null terminator of the string.
+///
+/// \code{.c}
+/// const char* base = str;
+/// const char* end = base + strlen(str);
+/// size_t result = end - base;
+/// \endcode
+///
+/// In the case we index by an induction variable, as long as the induction
+/// variable has a constant int increment, we can replace all such indvars
+/// with the closed form computation of strlen
+///
+/// \code{.c}
+/// size_t i = 0;
+/// while (str[i] != '\0')
+/// ++i;
+/// size_t result = i;
+/// \endcode
+///
+/// Will be replaced by
+///
+/// \code{.c}
+/// size_t i = 0 + strlen(str);
+/// size_t result = i;
+/// \endcode
+///
+bool LoopIdiomRecognize::recognizeAndInsertStrLen() {
+ if (DisableLIRP::All)
+ return false;
+
+ StrlenVerifier Verifier(CurLoop, SE, TLI);
+
+ if (!Verifier.isValidStrlenIdiom())
+ return false;
+
+ BasicBlock *Preheader = CurLoop->getLoopPreheader();
+ BasicBlock *LoopExitBB = CurLoop->getExitBlock();
+
+ IRBuilder<> Builder(Preheader->getTerminator());
+ SCEVExpander Expander(*SE, Preheader->getModule()->getDataLayout(),
+ "strlen_idiom");
+ Value *MaterialzedBase = Expander.expandCodeFor(
+ Verifier.LoadBaseEv, Verifier.LoadBaseEv->getType(),
+ Builder.GetInsertPoint());
+
+ Value *StrLenFunc = nullptr;
+ if (Verifier.OpWidth == 8) {
+ if (DisableLIRP::Strlen)
+ return false;
+ if (!isLibFuncEmittable(Preheader->getModule(), TLI, LibFunc_strlen))
+ return false;
+ StrLenFunc = emitStrLen(MaterialzedBase, Builder, *DL, TLI);
+ } else {
+ if (DisableLIRP::Wcslen)
+ return false;
+ if (!isLibFuncEmittable(Preheader->getModule(), TLI, LibFunc_wcslen))
+ return false;
+ StrLenFunc = emitWcsLen(MaterialzedBase, Builder, *DL, TLI);
+ }
+ assert(StrLenFunc && "Failed to emit strlen function.");
+
+ const SCEV *StrlenEv = SE->getSCEV(StrLenFunc);
+ SmallVector<PHINode *, 4> Cleanup;
+ for (PHINode &PN : LoopExitBB->phis()) {
+ // We can now materialize the loop output as all phi have scev {base,+,a}.
+ // We expand the phi as:
+ // %strlen = call i64 @strlen(%str)
+ // %phi.new = base expression + step * %strlen
+ const SCEV *Ev = SE->getSCEV(&PN);
+ const SCEVAddRecExpr *AddRecEv = dyn_cast<SCEVAddRecExpr>(Ev);
+ const SCEVConstant *Step =
+ dyn_cast<SCEVConstant>(AddRecEv->getStepRecurrence(*SE));
+ const SCEV *Base = AddRecEv->getStart();
+
+ // It is safe to truncate to base since if base is narrower than size_t
+ // the equivalent user code will have to truncate anyways.
+ const SCEV *NewEv = SE->getAddExpr(
+ Base, SE->getMulExpr(Step, SE->getTruncateOrSignExtend(
+ StrlenEv, Base->getType())));
+
+ Value *MaterializedPHI = Expander.expandCodeFor(NewEv, NewEv->getType(),
+ Builder.GetInsertPoint());
+ Expander.clear();
+ PN.replaceAllUsesWith(MaterializedPHI);
+ Cleanup.push_back(&PN);
+ }
+
+ // All LCSSA Loop Phi are dead, the left over dead loop body can be cleaned
+ // up by later passes
+ for (PHINode *PN : Cleanup)
+ RecursivelyDeleteDeadPHINode(PN);
+ SE->forgetLoop(CurLoop);
+
+ ++NumStrLen;
+ LLVM_DEBUG(dbgs() << " Formed strlen idiom: " << *StrLenFunc << "\n");
+ ORE.emit([&]() {
+ return OptimizationRemark(DEBUG_TYPE, "recognizeAndInsertStrLen",
+ CurLoop->getStartLoc(), Preheader)
+ << "Transformed " << StrLenFunc->getName() << " loop idiom";
+ });
+
+ return true;
+}
+
/// Check if the given conditional branch is based on an unsigned less-than
/// comparison between a variable and a constant, and if the comparison is false
/// the control yields to the loop entry. If the branch matches the behaviour,
diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
index 2301be6977cef..24eefc91117b4 100644
--- a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
@@ -1582,6 +1582,15 @@ Value *llvm::emitStrLen(Value *Ptr, IRBuilderBase &B, const DataLayout &DL,
return emitLibCall(LibFunc_strlen, SizeTTy, CharPtrTy, Ptr, B, TLI);
}
+Value *llvm::emitWcsLen(Value *Ptr, IRBuilderBase &B, const DataLayout &DL,
+ const TargetLibraryInfo *TLI) {
+ assert(Ptr && Ptr->getType()->isPointerTy() &&
+ "Argument to wcslen intrinsic must be a pointer.");
+ Type *PtrTy = B.getPtrTy();
+ Type *SizeTTy = getSizeTTy(B, TLI);
+ return emitLibCall(LibFunc_wcslen, SizeTTy, PtrTy, Ptr, B, TLI);
+}
+
Value *llvm::emitStrDup(Value *Ptr, IRBuilderBase &B,
const TargetLibraryInfo *TLI) {
Type *CharPtrTy = B.getPtrTy();
diff --git a/llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll b/llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll
new file mode 100644
index 0000000000000..0be76cbd42a72
--- /dev/null
+++ b/llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll
@@ -0,0 +1,66 @@
+; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; invalid libcall prototype
+declare void @strlen(i32)
+declare void @wcslen(i32)
+
+define i64 @valid_wcslen32(ptr %src) {
+; CHECK-LABEL: valid_wcslen32
+; CHECK-NOT: call {{.*}} @wcslen
+entry:
+ %cmp = icmp eq ptr %src, null
+ br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false: ; preds = %entry
+ %0 = load i32, ptr %src, align 4
+ %cmp1 = icmp eq i32 %0, 0
+ br i1 %cmp1, label %return, label %while.cond.preheader
+
+while.cond.preheader: ; preds = %lor.lhs.false
+ br label %while.cond
+
+while.cond: ; preds = %while.cond.preheader, %while.cond
+ %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %while.cond.preheader ]
+ %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 4
+ %1 = load i32, ptr %curr.0, align 4
+ %tobool.not = icmp eq i32 %1, 0
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end: ; preds = %while.cond
+ %curr.0.lcssa = phi ptr [ %curr.0, %while.cond ]
+ %sub.ptr.lhs.cast = ptrtoint ptr %curr.0.lcssa to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 2
+ br label %return
+
+return: ; preds = %entry, %lor.lhs.false, %while.end
+ %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+ ret i64 %retval.0
+}
+
+define i64 @valid_strlen(ptr %str) {
+; CHECK-LABEL: valid_strlen
+; CHECK-NOT: call {{.*}} @strlen
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %0 = load i8, ptr %str.addr.0, align 1
+ %cmp.not = icmp eq i8 %0, 0
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 1
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ ret i64 %sub.ptr.sub
+}
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"wchar_size", i32 4}
diff --git a/llvm/test/Transforms/LoopIdiom/strlen.ll b/llvm/test/Transforms/LoopIdiom/strlen.ll
new file mode 100644
index 0000000000000..137a17f541cd4
--- /dev/null
+++ b/llvm/test/Transforms/LoopIdiom/strlen.ll
@@ -0,0 +1,614 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+
+declare void @other()
+declare void @use(ptr)
+declare void @usei(i32)
+declare void @usel(i64)
+
+; size_t basic_strlen(const char* str) {
+; while (*str != '\0') {
+; ++str;
+; }
+; return str - base;
+; }
+define i64 @valid_basic_strlen(ptr %str) {
+; CHECK-LABEL: define i64 @valid_basic_strlen(
+; CHECK-SAME: ptr [[STR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[STRLEN:%.*]] = call i64 @strlen(ptr [[STR]])
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[STR]], i64 [[STRLEN]]
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[STR]], %[[ENTRY]] ], [ [[INCDEC_PTR:%.*]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[STR_ADDR_0]], align 1
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr i8, ptr [[STR_ADDR_0]], i64 1
+; CHECK-NEXT: br i1 [[CMP_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[SCEVGEP]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[STR]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: ret i64 [[SUB_PTR_SUB]]
+;
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %0 = load i8, ptr %str.addr.0, align 1
+ %cmp.not = icmp eq i8 %0, 0
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 1
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ ret i64 %sub.ptr.sub
+}
+
+; int valid_basic_strlen_rotated(const char* str) {
+; const char* base = str;
+; if (!*str) return 0;
+; do {
+; ++str;
+; } while (*str);
+; return str - base;
+; }
+define i32 @valid_basic_strlen_rotated(ptr %str) {
+; CHECK-LABEL: define i32 @valid_basic_strlen_rotated(
+; CHECK-SAME: ptr [[STR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[STR]], align 1
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[CLEANUP:.*]], label %[[DO_BODY_PREHEADER:.*]]
+; CHECK: [[DO_BODY_PREHEADER]]:
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[STR]], i64 1
+; CHECK-NEXT: [[STRLEN:%.*]] = call i64 @strlen(ptr [[SCEVGEP]])
+; CHECK-NEXT: [[TMP1:%.*]] = add i64 [[STRLEN]], 1
+; CHECK-NEXT: [[SCEVGEP1:%.*]] = getelementptr i8, ptr [[STR]], i64 [[TMP1]]
+; CHECK-NEXT: br label %[[DO_BODY:.*]]
+; CHECK: [[DO_BODY]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[INCDEC_PTR:%.*]], %[[DO_BODY]] ], [ [[STR]], %[[DO_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr inbounds nuw i8, ptr [[STR_ADDR_0]], i64 1
+; CHECK-NEXT: [[TMP2:%.*]] = load i8, ptr [[INCDEC_PTR]], align 1
+; CHECK-NEXT: [[TOBOOL1_NOT:%.*]] = icmp eq i8 [[TMP2]], 0
+; CHECK-NEXT: br i1 [[TOBOOL1_NOT]], label %[[DO_END:.*]], label %[[DO_BODY]]
+; CHECK: [[DO_END]]:
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[SCEVGEP1]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[STR]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[SUB_PTR_SUB]] to i32
+; CHECK-NEXT: br label %[[CLEANUP]]
+; CHECK: [[CLEANUP]]:
+; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i32 [ [[CONV]], %[[DO_END]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: ret i32 [[RETVAL_0]]
+;
+entry:
+ %0 = load i8, ptr %str, align 1
+ %tobool.not = icmp eq i8 %0, 0
+ br i1 %tobool.not, label %cleanup, label %do.body
+
+do.body:
+ %str.addr.0 = phi ptr [ %incdec.ptr, %do.body ], [ %str, %entry ]
+ %incdec.ptr = getelementptr inbounds nuw i8, ptr %str.addr.0, i64 1
+ %1 = load i8, ptr %incdec.ptr, align 1
+ %tobool1.not = icmp eq i8 %1, 0
+ br i1 %tobool1.not, label %do.end, label %do.body
+
+do.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %incdec.ptr to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ %conv = trunc i64 %sub.ptr.sub to i32
+ br label %cleanup
+
+cleanup:
+ %retval.0 = phi i32 [ %conv, %do.end ], [ 0, %entry ]
+ ret i32 %retval.0
+}
+
+; int valid_strlen_with_aux_indvar(const char* str) {
+; int count = 0;
+; int count_offset = -10;
+; int count_multiple = 0;
+;
+; while (*str) {
+; ++str;
+; ++count;
+; ++count_offset;
+; count_multiple += 2;
+; ++foo;
+; }
+;
+; usei(count);
+; usei(count_offset);
+; usei(count_multiple);
+; use(str);
+; use(foo);
+; }
+define dso_local void @valid_strlen_with_aux_indvar(ptr noundef %str, ptr noundef %foo) local_unnamed_addr {
+; CHECK-LABEL: define dso_local void @valid_strlen_with_aux_indvar(
+; CHECK-SAME: ptr noundef [[STR:%.*]], ptr noundef [[FOO:%.*]]) local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[STR]], align 1
+; CHECK-NEXT: [[TOBOOL_NOT9:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT9]], label %[[WHILE_END:.*]], label %[[WHILE_BODY_PREHEADER:.*]]
+; CHECK: [[WHILE_BODY_PREHEADER]]:
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[STR]], i64 1
+; CHECK-NEXT: [[STRLEN:%.*]] = call i64 @strlen(ptr [[SCEVGEP]])
+; CHECK-NEXT: [[TMP1:%.*]] = add i64 [[STRLEN]], 1
+; CHECK-NEXT: [[SCEVGEP1:%.*]] = getelementptr i8, ptr [[STR]], i64 [[TMP1]]
+; CHECK-NEXT: [[TMP2:%.*]] = trunc i64 [[STRLEN]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = add i32 [[TMP2]], 1
+; CHECK-NEXT: [[TMP4:%.*]] = trunc i64 [[STRLEN]] to i32
+; CHECK-NEXT: [[TMP5:%.*]] = add i32 [[TMP4]], -9
+; CHECK-NEXT: [[TMP6:%.*]] = trunc i64 [[STRLEN]] to i32
+; CHECK-NEXT: [[TMP7:%.*]] = shl i32 [[TMP6]], 1
+; CHECK-NEXT: [[TMP8:%.*]] = add i32 [[TMP7]], 2
+; CHECK-NEXT: [[TMP9:%.*]] = add i64 [[STRLEN]], 1
+; CHECK-NEXT: [[SCEVGEP2:%.*]] = getelementptr i8, ptr [[FOO]], i64 [[TMP9]]
+; CHECK-NEXT: br label %[[WHILE_BODY:.*]]
+; CHECK: [[WHILE_BODY]]:
+; CHECK-NEXT: [[COUNT_MULTIPLE_014:%.*]] = phi i32 [ [[ADD:%.*]], %[[WHILE_BODY]] ], [ 0, %[[WHILE_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[COUNT_OFFSET_013:%.*]] = phi i32 [ [[INC1:%.*]], %[[WHILE_BODY]] ], [ -10, %[[WHILE_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[COUNT_012:%.*]] = phi i32 [ [[INC:%.*]], %[[WHILE_BODY]] ], [ 0, %[[WHILE_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[FOO_ADDR_011:%.*]] = phi ptr [ [[INCDEC_PTR2:%.*]], %[[WHILE_BODY]] ], [ [[FOO]], %[[WHILE_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[STR_ADDR_010:%.*]] = phi ptr [ [[INCDEC_PTR:%.*]], %[[WHILE_BODY]] ], [ [[STR]], %[[WHILE_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr inbounds nuw i8, ptr [[STR_ADDR_010]], i64 1
+; CHECK-NEXT: [[INC]] = add nuw nsw i32 [[COUNT_012]], 1
+; CHECK-NEXT: [[INC1]] = add nsw i32 [[COUNT_OFFSET_013]], 1
+; CHECK-NEXT: [[ADD]] = add nuw nsw i32 [[COUNT_MULTIPLE_014]], 2
+; CHECK-NEXT: [[INCDEC_PTR2]] = getelementptr inbounds nuw i8, ptr [[FOO_ADDR_011]], i64 1
+; CHECK-NEXT: [[TMP10:%.*]] = load i8, ptr [[INCDEC_PTR]], align 1
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i8 [[TMP10]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END_LOOPEXIT:.*]], label %[[WHILE_BODY]]
+; CHECK: [[WHILE_END_LOOPEXIT]]:
+; CHECK-NEXT: br label %[[WHILE_END]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[STR_ADDR_0_LCSSA:%.*]] = phi ptr [ [[STR]], %[[ENTRY]] ], [ [[SCEVGEP1]], %[[WHILE_END_LOOPEXIT]] ]
+; CHECK-NEXT: [[FOO_ADDR_0_LCSSA:%.*]] = phi ptr [ [[FOO]], %[[ENTRY]] ], [ [[SCEVGEP2]], %[[WHILE_END_LOOPEXIT]] ]
+; CHECK-NEXT: [[COUNT_0_LCSSA:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[TMP3]], %[[WHILE_END_LOOPEXIT]] ]
+; CHECK-NEXT: [[COUNT_OFFSET_0_LCSSA:%.*]] = phi i32 [ -10, %[[ENTRY]] ], [ [[TMP5]], %[[WHILE_END_LOOPEXIT]] ]
+; CHECK-NEXT: [[COUNT_MULTIPLE_0_LCSSA:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[TMP8]], %[[WHILE_END_LOOPEXIT]] ]
+; CHECK-NEXT: tail call void @usei(i32 noundef [[COUNT_0_LCSSA]])
+; CHECK-NEXT: tail call void @usei(i32 noundef [[COUNT_OFFSET_0_LCSSA]])
+; CHECK-NEXT: tail call void @usei(i32 noundef [[COUNT_MULTIPLE_0_LCSSA]])
+; CHECK-NEXT: tail call void @use(ptr noundef nonnull [[STR_ADDR_0_LCSSA]])
+; CHECK-NEXT: tail call void @use(ptr noundef [[FOO_ADDR_0_LCSSA]])
+; CHECK-NEXT: ret void
+;
+entry:
+ %0 = load i8, ptr %str, align 1
+ %tobool.not9 = icmp eq i8 %0, 0
+ br i1 %tobool.not9, label %while.end, label %while.body
+
+while.body:
+ %count_multiple.014 = phi i32 [ %add, %while.body ], [ 0, %entry ]
+ %count_offset.013 = phi i32 [ %inc1, %while.body ], [ -10, %entry ]
+ %count.012 = phi i32 [ %inc, %while.body ], [ 0, %entry ]
+ %foo.addr.011 = phi ptr [ %incdec.ptr2, %while.body ], [ %foo, %entry ]
+ %str.addr.010 = phi ptr [ %incdec.ptr, %while.body ], [ %str, %entry ]
+ %incdec.ptr = getelementptr inbounds nuw i8, ptr %str.addr.010, i64 1
+ %inc = add nuw nsw i32 %count.012, 1
+ %inc1 = add nsw i32 %count_offset.013, 1
+ %add = add nuw nsw i32 %count_multiple.014, 2
+ %incdec.ptr2 = getelementptr inbounds nuw i8, ptr %foo.addr.011, i64 1
+ %1 = load i8, ptr %incdec.ptr, align 1
+ %tobool.not = icmp eq i8 %1, 0
+ br i1 %tobool.not, label %while.end, label %while.body
+
+while.end:
+ %str.addr.0.lcssa = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.body ]
+ %foo.addr.0.lcssa = phi ptr [ %foo, %entry ], [ %incdec.ptr2, %while.body ]
+ %count.0.lcssa = phi i32 [ 0, %entry ], [ %inc, %while.body ]
+ %count_offset.0.lcssa = phi i32 [ -10, %entry ], [ %inc1, %while.body ]
+ %count_multiple.0.lcssa = phi i32 [ 0, %entry ], [ %add, %while.body ]
+ tail call void @usei(i32 noundef %count.0.lcssa) #3
+ tail call void @usei(i32 noundef %count_offset.0.lcssa) #3
+ tail call void @usei(i32 noundef %count_multiple.0.lcssa) #3
+ tail call void @use(ptr noundef nonnull %str.addr.0.lcssa) #3
+ tail call void @use(ptr noundef %foo.addr.0.lcssa) #3
+ ret void
+}
+
+; int valid_strlen_index(const char* str) {
+; int i = 0;
+; while (str[i]) {
+; ++i;
+; }
+; return i;
+; }
+define i32 @valid_strlen_index(ptr %str) {
+; CHECK-LABEL: define i32 @valid_strlen_index(
+; CHECK-SAME: ptr [[STR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[STRLEN:%.*]] = call i64 @strlen(ptr [[STR]])
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[WHILE_COND]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i8, ptr [[STR]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[ARRAYIDX]], align 1
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[TMP1:%.*]] = trunc nuw nsw i64 [[STRLEN]] to i32
+; CHECK-NEXT: ret i32 [[TMP1]]
+;
+entry:
+ br label %while.cond
+
+while.cond:
+ %indvars.iv = phi i64 [ %indvars.iv.next, %while.cond ], [ 0, %entry ]
+ %arrayidx = getelementptr inbounds i8, ptr %str, i64 %indvars.iv
+ %0 = load i8, ptr %arrayidx, align 1
+ %tobool.not = icmp eq i8 %0, 0
+ %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end:
+ %1 = trunc nuw nsw i64 %indvars.iv to i32
+ ret i32 %1
+}
+
+; void valid_strlen_offset(const my_char* str) {
+; if (*(str++) == '\0') return;
+; if (*(str++) == '\0') return;
+; if (*(str++) == '\0') return;
+; while (*str) {
+; ++str;
+; }
+; use(str);
+; }
+define dso_local void @valid_strlen_offset(ptr noundef %str) local_unnamed_addr {
+; CHECK-LABEL: define dso_local void @valid_strlen_offset(
+; CHECK-SAME: ptr noundef [[STR:%.*]]) local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[STR]], align 1
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[CMP]], label %[[RETURN:.*]], label %[[IF_END:.*]]
+; CHECK: [[IF_END]]:
+; CHECK-NEXT: [[INCDEC_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[STR]], i64 1
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[INCDEC_PTR]], align 1
+; CHECK-NEXT: [[CMP4:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[CMP4]], label %[[RETURN]], label %[[IF_END7:.*]]
+; CHECK: [[IF_END7]]:
+; CHECK-NEXT: [[INCDEC_PTR2:%.*]] = getelementptr inbounds nuw i8, ptr [[STR]], i64 2
+; CHECK-NEXT: [[TMP2:%.*]] = load i8, ptr [[INCDEC_PTR2]], align 1
+; CHECK-NEXT: [[CMP10:%.*]] = icmp eq i8 [[TMP2]], 0
+; CHECK-NEXT: br i1 [[CMP10]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
+; CHECK: [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT: [[INCDEC_PTR8:%.*]] = getelementptr i8, ptr [[STR]], i64 3
+; CHECK-NEXT: [[STRLEN:%.*]] = call i64 @strlen(ptr [[INCDEC_PTR8]])
+; CHECK-NEXT: [[TMP3:%.*]] = add i64 [[STRLEN]], 3
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[STR]], i64 [[TMP3]]
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[INCDEC_PTR14:%.*]], %[[WHILE_COND]] ], [ [[INCDEC_PTR8]], %[[WHILE_COND_PREHEADER]] ]
+; CHECK-NEXT: [[TMP4:%.*]] = load i8, ptr [[STR_ADDR_0]], align 1
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i8 [[TMP4]], 0
+; CHECK-NEXT: [[INCDEC_PTR14]] = getelementptr inbounds nuw i8, ptr [[STR_ADDR_0]], i64 1
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: tail call void @use(ptr noundef nonnull [[SCEVGEP]])
+; CHECK-NEXT: br label %[[RETURN]]
+; CHECK: [[RETURN]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %0 = load i8, ptr %str, align 1
+ %cmp = icmp eq i8 %0, 0
+ br i1 %cmp, label %return, label %if.end
+
+if.end:
+ %incdec.ptr = getelementptr inbounds nuw i8, ptr %str, i64 1
+ %1 = load i8, ptr %incdec.ptr, align 1
+ %cmp4 = icmp eq i8 %1, 0
+ br i1 %cmp4, label %return, label %if.end7
+
+if.end7:
+ %incdec.ptr2 = getelementptr inbounds nuw i8, ptr %str, i64 2
+ %2 = load i8, ptr %incdec.ptr2, align 1
+ %cmp10 = icmp eq i8 %2, 0
+ br i1 %cmp10, label %return, label %while.cond.preheader
+
+while.cond.preheader:
+ %incdec.ptr8 = getelementptr inbounds nuw i8, ptr %str, i64 3
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %incdec.ptr14, %while.cond ], [ %incdec.ptr8, %while.cond.preheader ]
+ %3 = load i8, ptr %str.addr.0, align 1
+ %tobool.not = icmp eq i8 %3, 0
+ %incdec.ptr14 = getelementptr inbounds nuw i8, ptr %str.addr.0, i64 1
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end:
+ tail call void @use(ptr noundef nonnull %str.addr.0) #3
+ br label %return
+
+return:
+ ret void
+}
+
+; void valid_nested_idiom(const char** strs, int n) {
+; for (int i = 0; i < n; ++i) {
+; const char* s = strs[i];
+; int count = 0;
+; while (*s) {
+; ++s;
+; ++count;
+; }
+; usei(count);
+; }
+; }
+define void @valid_nested_idiom(ptr %strs, i32 %n) {
+; CHECK-LABEL: define void @valid_nested_idiom(
+; CHECK-SAME: ptr [[STRS:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[CMP9:%.*]] = icmp sgt i32 [[N]], 0
+; CHECK-NEXT: br i1 [[CMP9]], label %[[FOR_BODY_PREHEADER:.*]], label %[[FOR_COND_CLEANUP:.*]]
+; CHECK: [[FOR_BODY_PREHEADER]]:
+; CHECK-NEXT: [[WIDE_TRIP_COUNT:%.*]] = zext nneg i32 [[N]] to i64
+; CHECK-NEXT: br label %[[FOR_BODY:.*]]
+; CHECK: [[FOR_COND_CLEANUP_LOOPEXIT:.*]]:
+; CHECK-NEXT: br label %[[FOR_COND_CLEANUP]]
+; CHECK: [[FOR_COND_CLEANUP]]:
+; CHECK-NEXT: ret void
+; CHECK: [[FOR_BODY]]:
+; CHECK-NEXT: [[INDVARS_IV:%.*]] = phi i64 [ 0, %[[FOR_BODY_PREHEADER]] ], [ [[INDVARS_IV_NEXT:%.*]], %[[WHILE_END:.*]] ]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds ptr, ptr [[STRS]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 1
+; CHECK-NEXT: [[TOBOOL_NOT6:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT6]], label %[[WHILE_END]], label %[[WHILE_BODY_PREHEADER:.*]]
+; CHECK: [[WHILE_BODY_PREHEADER]]:
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[TMP0]], i64 1
+; CHECK-NEXT: [[STRLEN:%.*]] = call i64 @strlen(ptr [[SCEVGEP]])
+; CHECK-NEXT: [[TMP2:%.*]] = trunc i64 [[STRLEN]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = add i32 [[TMP2]], 1
+; CHECK-NEXT: br label %[[WHILE_BODY:.*]]
+; CHECK: [[WHILE_BODY]]:
+; CHECK-NEXT: [[COUNT_08:%.*]] = phi i32 [ [[INC:%.*]], %[[WHILE_BODY]] ], [ 0, %[[WHILE_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[S_07:%.*]] = phi ptr [ [[INCDEC_PTR:%.*]], %[[WHILE_BODY]] ], [ [[TMP0]], %[[WHILE_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr inbounds nuw i8, ptr [[S_07]], i64 1
+; CHECK-NEXT: [[INC]] = add nuw nsw i32 [[COUNT_08]], 1
+; CHECK-NEXT: [[TMP4:%.*]] = load i8, ptr [[INCDEC_PTR]], align 1
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i8 [[TMP4]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END_LOOPEXIT:.*]], label %[[WHILE_BODY]]
+; CHECK: [[WHILE_END_LOOPEXIT]]:
+; CHECK-NEXT: br label %[[WHILE_END]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[COUNT_0_LCSSA:%.*]] = phi i32 [ 0, %[[FOR_BODY]] ], [ [[TMP3]], %[[WHILE_END_LOOPEXIT]] ]
+; CHECK-NEXT: tail call void @usei(i32 [[COUNT_0_LCSSA]])
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[EXITCOND_NOT:%.*]] = icmp eq i64 [[INDVARS_IV_NEXT]], [[WIDE_TRIP_COUNT]]
+; CHECK-NEXT: br i1 [[EXITCOND_NOT]], label %[[FOR_COND_CLEANUP_LOOPEXIT]], label %[[FOR_BODY]]
+;
+entry:
+ %cmp9 = icmp sgt i32 %n, 0
+ br i1 %cmp9, label %for.body.preheader, label %for.cond.cleanup
+
+for.body.preheader:
+ %wide.trip.count = zext nneg i32 %n to i64
+ br label %for.body
+
+for.cond.cleanup:
+ ret void
+
+for.body:
+ %indvars.iv = phi i64 [ 0, %for.body.preheader ], [ %indvars.iv.next, %while.end ]
+ %arrayidx = getelementptr inbounds ptr, ptr %strs, i64 %indvars.iv
+ %0 = load ptr, ptr %arrayidx, align 8
+ %1 = load i8, ptr %0, align 1
+ %tobool.not6 = icmp eq i8 %1, 0
+ br i1 %tobool.not6, label %while.end, label %while.body
+
+while.body:
+ %count.08 = phi i32 [ %inc, %while.body ], [ 0, %for.body ]
+ %s.07 = phi ptr [ %incdec.ptr, %while.body ], [ %0, %for.body ]
+ %incdec.ptr = getelementptr inbounds nuw i8, ptr %s.07, i64 1
+ %inc = add nuw nsw i32 %count.08, 1
+ %2 = load i8, ptr %incdec.ptr, align 1
+ %tobool.not = icmp eq i8 %2, 0
+ br i1 %tobool.not, label %while.end, label %while.body
+
+while.end:
+ %count.0.lcssa = phi i32 [ 0, %for.body ], [ %inc, %while.body ]
+ tail call void @usei(i32 %count.0.lcssa) #2
+ %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+ %exitcond.not = icmp eq i64 %indvars.iv.next, %wide.trip.count
+ br i1 %exitcond.not, label %for.cond.cleanup, label %for.body
+}
+
+define i64 @invalid_strlen_has_side_effects(ptr %str) {
+; CHECK-LABEL: define i64 @invalid_strlen_has_side_effects(
+; CHECK-SAME: ptr [[STR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[STR]], %[[ENTRY]] ], [ [[INCDEC_PTR:%.*]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = load volatile i8, ptr [[STR_ADDR_0]], align 1
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr i8, ptr [[STR_ADDR_0]], i64 1
+; CHECK-NEXT: br i1 [[CMP_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[STR_ADDR_0_LCSSA:%.*]] = phi ptr [ [[STR_ADDR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[STR_ADDR_0_LCSSA]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[STR]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: ret i64 [[SUB_PTR_SUB]]
+;
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %0 = load volatile i8, ptr %str.addr.0, align 1
+ %cmp.not = icmp eq i8 %0, 0
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 1
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ ret i64 %sub.ptr.sub
+}
+
+
+define i8 @invalid_exit_phi_scev(ptr %str) {
+; CHECK-LABEL: define i8 @invalid_exit_phi_scev(
+; CHECK-SAME: ptr [[STR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[STR]], %[[ENTRY]] ], [ [[INCDEC_PTR:%.*]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[STR_ADDR_0]], align 1
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr i8, ptr [[STR_ADDR_0]], i64 1
+; CHECK-NEXT: br i1 [[CMP_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[STR_ADDR_0_LCSSA:%.*]] = phi ptr [ [[STR_ADDR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[DOTLCSSA:%.*]] = phi i8 [ [[TMP0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[STR_ADDR_0_LCSSA]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[STR]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: ret i8 [[DOTLCSSA]]
+;
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %0 = load i8, ptr %str.addr.0, align 1
+ %cmp.not = icmp eq i8 %0, 0
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 1
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+
+ ; %0.lcssa has invalid scev rec {%0} expected to be {%str,+,constant}
+ ret i8 %0
+}
+
+
+
+define i64 @invalid_branch_cond(ptr %str) {
+; CHECK-LABEL: define i64 @invalid_branch_cond(
+; CHECK-SAME: ptr [[STR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[STR]], %[[ENTRY]] ], [ [[INCDEC_PTR:%.*]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[STR_ADDR_0]], align 1
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i8 [[TMP0]], 10
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr i8, ptr [[STR_ADDR_0]], i64 1
+; CHECK-NEXT: br i1 [[CMP_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[STR_ADDR_0_LCSSA:%.*]] = phi ptr [ [[STR_ADDR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[STR_ADDR_0_LCSSA]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[STR]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: ret i64 [[SUB_PTR_SUB]]
+;
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %0 = load i8, ptr %str.addr.0, align 1
+
+ ; We compare against '\n' instead of '\0'
+ %cmp.not = icmp eq i8 %0, 10
+
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 1
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ ret i64 %sub.ptr.sub
+}
+
+define i64 @invalid_unknown_step_size(ptr %str, i64 %step) {
+; CHECK-LABEL: define i64 @invalid_unknown_step_size(
+; CHECK-SAME: ptr [[STR:%.*]], i64 [[STEP:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[STR]], %[[ENTRY]] ], [ [[INCDEC_PTR:%.*]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[STR_ADDR_0]], align 1
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr i8, ptr [[STR_ADDR_0]], i64 [[STEP]]
+; CHECK-NEXT: br i1 [[CMP_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[STR_ADDR_0_LCSSA:%.*]] = phi ptr [ [[STR_ADDR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[STR_ADDR_0_LCSSA]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[STR]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: ret i64 [[SUB_PTR_SUB]]
+;
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %0 = load i8, ptr %str.addr.0, align 1
+ %cmp.not = icmp eq i8 %0, 0
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 %step
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ ret i64 %sub.ptr.sub
+}
+
+declare ptr @pure(ptr) #0;
+attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
+
+define i64 @invalid_add_rec(ptr %str) {
+; CHECK-LABEL: define i64 @invalid_add_rec(
+; CHECK-SAME: ptr [[STR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[STR_ADDR_0:%.*]] = phi ptr [ [[STR]], %[[ENTRY]] ], [ [[INCDEC_PTR:%.*]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[INDIRECT:%.*]] = tail call ptr @pure(ptr [[STR_ADDR_0]])
+; CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[INDIRECT]], align 1
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i8 [[TMP0]], 0
+; CHECK-NEXT: [[INCDEC_PTR]] = getelementptr i8, ptr [[STR_ADDR_0]], i64 1
+; CHECK-NEXT: br i1 [[CMP_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[STR_ADDR_0_LCSSA:%.*]] = phi ptr [ [[STR_ADDR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[STR_ADDR_0_LCSSA]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[STR]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: ret i64 [[SUB_PTR_SUB]]
+;
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %indirect = tail call ptr @pure(ptr %str.addr.0)
+ %0 = load i8, ptr %indirect, align 1
+ %cmp.not = icmp eq i8 %0, 0
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 1
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ ret i64 %sub.ptr.sub
+}
+
diff --git a/llvm/test/Transforms/LoopIdiom/wcslen16.ll b/llvm/test/Transforms/LoopIdiom/wcslen16.ll
new file mode 100644
index 0000000000000..d3b0b8d208cd8
--- /dev/null
+++ b/llvm/test/Transforms/LoopIdiom/wcslen16.ll
@@ -0,0 +1,126 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i64 @valid_strlen16(ptr %src) {
+; CHECK-LABEL: define i64 @valid_strlen16(
+; CHECK-SAME: ptr [[SRC:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[SRC]], null
+; CHECK-NEXT: br i1 [[CMP]], label %[[RETURN:.*]], label %[[LOR_LHS_FALSE:.*]]
+; CHECK: [[LOR_LHS_FALSE]]:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[SRC]], align 2
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i16 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
+; CHECK: [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT: [[NEWGEP:%.*]] = getelementptr i8, ptr [[SRC]], i64 2
+; CHECK-NEXT: [[WCSLEN:%.*]] = call i64 @wcslen(ptr [[NEWGEP]])
+; CHECK-NEXT: [[TMP1:%.*]] = shl i64 [[WCSLEN]], 1
+; CHECK-NEXT: [[TMP2:%.*]] = add i64 [[TMP1]], 2
+; CHECK-NEXT: [[END:%.*]] = getelementptr i8, ptr [[SRC]], i64 [[TMP2]]
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
+; CHECK-NEXT: [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 2
+; CHECK-NEXT: [[TMP3:%.*]] = load i16, ptr [[CURR_0]], align 2
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i16 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[END]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: [[SUB_PTR_DIV:%.*]] = ashr exact i64 [[SUB_PTR_SUB]], 1
+; CHECK-NEXT: br label %[[RETURN]]
+; CHECK: [[RETURN]]:
+; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i64 [ [[SUB_PTR_DIV]], %[[WHILE_END]] ], [ 0, %[[LOR_LHS_FALSE]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: ret i64 [[RETVAL_0]]
+;
+entry:
+ %cmp = icmp eq ptr %src, null
+ br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false: ; preds = %entry
+ %0 = load i16, ptr %src, align 2
+ %cmp1 = icmp eq i16 %0, 0
+ br i1 %cmp1, label %return, label %while.cond
+
+while.cond: ; preds = %lor.lhs.false, %while.cond
+ %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %lor.lhs.false ]
+ %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 2
+ %1 = load i16, ptr %curr.0, align 2
+ %tobool.not = icmp eq i16 %1, 0
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end: ; preds = %while.cond
+ %sub.ptr.lhs.cast = ptrtoint ptr %curr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 1
+ br label %return
+
+return: ; preds = %entry, %lor.lhs.false, %while.end
+ %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+ ret i64 %retval.0
+}
+
+define i64 @invalid_char_size(ptr %src) {
+; CHECK-LABEL: define i64 @invalid_char_size(
+; CHECK-SAME: ptr [[SRC:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[SRC]], null
+; CHECK-NEXT: br i1 [[CMP]], label %[[RETURN:.*]], label %[[LOR_LHS_FALSE:.*]]
+; CHECK: [[LOR_LHS_FALSE]]:
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[SRC]], align 2
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
+; CHECK: [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
+; CHECK-NEXT: [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 4
+; CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[CURR_0]], align 4
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[CURR_0_LCSSA:%.*]] = phi ptr [ [[CURR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[CURR_0_LCSSA]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: [[SUB_PTR_DIV:%.*]] = ashr exact i64 [[SUB_PTR_SUB]], 2
+; CHECK-NEXT: br label %[[RETURN]]
+; CHECK: [[RETURN]]:
+; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i64 [ [[SUB_PTR_DIV]], %[[WHILE_END]] ], [ 0, %[[LOR_LHS_FALSE]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: ret i64 [[RETVAL_0]]
+;
+entry:
+ %cmp = icmp eq ptr %src, null
+ br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false: ; preds = %entry
+ %0 = load i32, ptr %src, align 2
+ %cmp1 = icmp eq i32 %0, 0
+ br i1 %cmp1, label %return, label %while.cond
+
+while.cond: ; preds = %lor.lhs.false, %while.cond
+ %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %lor.lhs.false ]
+ %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 4
+ %1 = load i32, ptr %curr.0, align 4
+ %tobool.not = icmp eq i32 %1, 0
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end: ; preds = %while.cond
+ %sub.ptr.lhs.cast = ptrtoint ptr %curr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 2
+ br label %return
+
+return: ; preds = %entry, %lor.lhs.false, %while.end
+ %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+ ret i64 %retval.0
+}
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"wchar_size", i32 2}
+
diff --git a/llvm/test/Transforms/LoopIdiom/wcslen32.ll b/llvm/test/Transforms/LoopIdiom/wcslen32.ll
new file mode 100644
index 0000000000000..488afff86c245
--- /dev/null
+++ b/llvm/test/Transforms/LoopIdiom/wcslen32.ll
@@ -0,0 +1,134 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i64 @valid_wcslen32(ptr %src) {
+; CHECK-LABEL: define i64 @valid_wcslen32(
+; CHECK-SAME: ptr [[SRC:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[SRC]], null
+; CHECK-NEXT: br i1 [[CMP]], label %[[RETURN:.*]], label %[[LOR_LHS_FALSE:.*]]
+; CHECK: [[LOR_LHS_FALSE]]:
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[SRC]], align 4
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i32 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
+; CHECK: [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT: [[NEWGEP:%.*]] = getelementptr i8, ptr [[SRC]], i64 4
+; CHECK-NEXT: [[WCSLEN:%.*]] = call i64 @wcslen(ptr [[NEWGEP]])
+; CHECK-NEXT: [[TMP1:%.*]] = shl i64 [[WCSLEN]], 2
+; CHECK-NEXT: [[TMP2:%.*]] = add i64 [[TMP1]], 4
+; CHECK-NEXT: [[END:%.*]] = getelementptr i8, ptr [[SRC]], i64 [[TMP2]]
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
+; CHECK-NEXT: [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 4
+; CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[CURR_0]], align 4
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[END]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: [[SUB_PTR_DIV:%.*]] = ashr exact i64 [[SUB_PTR_SUB]], 2
+; CHECK-NEXT: br label %[[RETURN]]
+; CHECK: [[RETURN]]:
+; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i64 [ [[SUB_PTR_DIV]], %[[WHILE_END]] ], [ 0, %[[LOR_LHS_FALSE]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: ret i64 [[RETVAL_0]]
+;
+entry:
+ %cmp = icmp eq ptr %src, null
+ br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false: ; preds = %entry
+ %0 = load i32, ptr %src, align 4
+ %cmp1 = icmp eq i32 %0, 0
+ br i1 %cmp1, label %return, label %while.cond.preheader
+
+while.cond.preheader: ; preds = %lor.lhs.false
+ br label %while.cond
+
+while.cond: ; preds = %while.cond.preheader, %while.cond
+ %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %while.cond.preheader ]
+ %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 4
+ %1 = load i32, ptr %curr.0, align 4
+ %tobool.not = icmp eq i32 %1, 0
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end: ; preds = %while.cond
+ %curr.0.lcssa = phi ptr [ %curr.0, %while.cond ]
+ %sub.ptr.lhs.cast = ptrtoint ptr %curr.0.lcssa to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 2
+ br label %return
+
+return: ; preds = %entry, %lor.lhs.false, %while.end
+ %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+ ret i64 %retval.0
+}
+
+define i64 @invalid_char_size(ptr %src) {
+; CHECK-LABEL: define i64 @invalid_char_size(
+; CHECK-SAME: ptr [[SRC:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[SRC]], null
+; CHECK-NEXT: br i1 [[CMP]], label %[[RETURN:.*]], label %[[LOR_LHS_FALSE:.*]]
+; CHECK: [[LOR_LHS_FALSE]]:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[SRC]], align 2
+; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i16 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
+; CHECK: [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT: br label %[[WHILE_COND:.*]]
+; CHECK: [[WHILE_COND]]:
+; CHECK-NEXT: [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
+; CHECK-NEXT: [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 2
+; CHECK-NEXT: [[TMP1:%.*]] = load i16, ptr [[CURR_0]], align 2
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK: [[WHILE_END]]:
+; CHECK-NEXT: [[CURR_0_LCSSA:%.*]] = phi ptr [ [[CURR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT: [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[CURR_0_LCSSA]] to i64
+; CHECK-NEXT: [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
+; CHECK-NEXT: [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT: [[SUB_PTR_DIV:%.*]] = ashr exact i64 [[SUB_PTR_SUB]], 1
+; CHECK-NEXT: br label %[[RETURN]]
+; CHECK: [[RETURN]]:
+; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i64 [ [[SUB_PTR_DIV]], %[[WHILE_END]] ], [ 0, %[[LOR_LHS_FALSE]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: ret i64 [[RETVAL_0]]
+;
+entry:
+ %cmp = icmp eq ptr %src, null
+ br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false: ; preds = %entry
+ %0 = load i16, ptr %src, align 2
+ %cmp1 = icmp eq i16 %0, 0
+ br i1 %cmp1, label %return, label %while.cond.preheader
+
+while.cond.preheader: ; preds = %lor.lhs.false
+ br label %while.cond
+
+while.cond: ; preds = %while.cond.preheader, %while.cond
+ %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %while.cond.preheader ]
+ %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 2
+ %1 = load i16, ptr %curr.0, align 2
+ %tobool.not = icmp eq i16 %1, 0
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end: ; preds = %while.cond
+ %curr.0.lcssa = phi ptr [ %curr.0, %while.cond ]
+ %sub.ptr.lhs.cast = ptrtoint ptr %curr.0.lcssa to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 1
+ br label %return
+
+return: ; preds = %entry, %lor.lhs.false, %while.end
+ %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+ ret i64 %retval.0
+}
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"wchar_size", i32 4}
+
>From 93c425e1b545e3584f3e421bacb96339887ec3c3 Mon Sep 17 00:00:00 2001
From: Henry Jiang <h243jian at uwaterloo.ca>
Date: Fri, 21 Mar 2025 19:10:25 -0400
Subject: [PATCH 2/3] [Transform] Clean up strlen loop idiom (#132421)
We should bailout of modifying the CFG if the library functions are not
emittable or disabled.
---
.../Transforms/Scalar/LoopIdiomRecognize.cpp | 45 +++++++------------
.../LoopIdiom/strlen-not-emittable.ll | 2 +-
llvm/test/Transforms/LoopIdiom/strlen.ll | 2 +-
llvm/test/Transforms/LoopIdiom/wcslen16.ll | 2 +-
llvm/test/Transforms/LoopIdiom/wcslen32.ll | 2 +-
5 files changed, 20 insertions(+), 33 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
index b91cdc0748581..a0b257b7d3ea3 100644
--- a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
@@ -1515,16 +1515,6 @@ bool LoopIdiomRecognize::runOnNoncountableLoop() {
recognizeShiftUntilLessThan() || recognizeAndInsertStrLen();
}
-/// Check if a Value is either a nullptr or a constant int zero
-static bool isZeroConstant(const Value *Val) {
- if (isa<ConstantPointerNull>(Val))
- return true;
- const ConstantInt *CmpZero = dyn_cast<ConstantInt>(Val);
- if (!CmpZero || !CmpZero->isZero())
- return false;
- return true;
-}
-
/// Check if the given conditional branch is based on the comparison between
/// a variable and zero, and if the variable is non-zero or zero (JmpOnZero is
/// true), the control yields to the loop entry. If the branch matches the
@@ -1540,7 +1530,8 @@ static Value *matchCondition(BranchInst *BI, BasicBlock *LoopEntry,
if (!Cond)
return nullptr;
- if (!isZeroConstant(Cond->getOperand(1)))
+ auto *CmpZero = dyn_cast<ConstantInt>(Cond->getOperand(1));
+ if (!CmpZero || !CmpZero->isZero())
return nullptr;
BasicBlock *TrueSucc = BI->getSuccessor(0);
@@ -1611,11 +1602,7 @@ class StrlenVerifier {
return false;
LoadBaseEv = LoadEv->getStart();
- LLVM_DEBUG({
- dbgs() << "pointer load scev: ";
- LoadEv->print(outs());
- dbgs() << "\n";
- });
+ LLVM_DEBUG(dbgs() << "pointer load scev: " << *LoadEv << "\n");
const SCEVConstant *Step =
dyn_cast<SCEVConstant>(LoadEv->getStepRecurrence(*SE));
@@ -1656,11 +1643,7 @@ class StrlenVerifier {
if (!Ev)
return false;
- LLVM_DEBUG({
- dbgs() << "loop exit phi scev: ";
- Ev->print(dbgs());
- dbgs() << "\n";
- });
+ LLVM_DEBUG(dbgs() << "loop exit phi scev: " << *Ev << "\n");
// Since we verified that the loop trip count will be a valid strlen
// idiom, we can expand all lcssa phi with {n,+,1} as (n + strlen) and use
@@ -1763,6 +1746,18 @@ bool LoopIdiomRecognize::recognizeAndInsertStrLen() {
BasicBlock *Preheader = CurLoop->getLoopPreheader();
BasicBlock *LoopExitBB = CurLoop->getExitBlock();
+ if (Verifier.OpWidth == 8) {
+ if (DisableLIRP::Strlen)
+ return false;
+ if (!isLibFuncEmittable(Preheader->getModule(), TLI, LibFunc_strlen))
+ return false;
+ } else {
+ if (DisableLIRP::Wcslen)
+ return false;
+ if (!isLibFuncEmittable(Preheader->getModule(), TLI, LibFunc_wcslen))
+ return false;
+ }
+
IRBuilder<> Builder(Preheader->getTerminator());
SCEVExpander Expander(*SE, Preheader->getModule()->getDataLayout(),
"strlen_idiom");
@@ -1772,16 +1767,8 @@ bool LoopIdiomRecognize::recognizeAndInsertStrLen() {
Value *StrLenFunc = nullptr;
if (Verifier.OpWidth == 8) {
- if (DisableLIRP::Strlen)
- return false;
- if (!isLibFuncEmittable(Preheader->getModule(), TLI, LibFunc_strlen))
- return false;
StrLenFunc = emitStrLen(MaterialzedBase, Builder, *DL, TLI);
} else {
- if (DisableLIRP::Wcslen)
- return false;
- if (!isLibFuncEmittable(Preheader->getModule(), TLI, LibFunc_wcslen))
- return false;
StrLenFunc = emitWcsLen(MaterialzedBase, Builder, *DL, TLI);
}
assert(StrLenFunc && "Failed to emit strlen function.");
diff --git a/llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll b/llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll
index 0be76cbd42a72..00fbe89846a30 100644
--- a/llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll
+++ b/llvm/test/Transforms/LoopIdiom/strlen-not-emittable.ll
@@ -1,4 +1,4 @@
-; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+; RUN: opt -passes='loop(loop-idiom)' < %s -S | FileCheck %s
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
diff --git a/llvm/test/Transforms/LoopIdiom/strlen.ll b/llvm/test/Transforms/LoopIdiom/strlen.ll
index 137a17f541cd4..c1141177c659f 100644
--- a/llvm/test/Transforms/LoopIdiom/strlen.ll
+++ b/llvm/test/Transforms/LoopIdiom/strlen.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+; RUN: opt -passes='loop(loop-idiom)' < %s -S | FileCheck %s
declare void @other()
declare void @use(ptr)
diff --git a/llvm/test/Transforms/LoopIdiom/wcslen16.ll b/llvm/test/Transforms/LoopIdiom/wcslen16.ll
index d3b0b8d208cd8..8be51869f1f2e 100644
--- a/llvm/test/Transforms/LoopIdiom/wcslen16.ll
+++ b/llvm/test/Transforms/LoopIdiom/wcslen16.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+; RUN: opt -passes='loop(loop-idiom)' < %s -S | FileCheck %s
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
diff --git a/llvm/test/Transforms/LoopIdiom/wcslen32.ll b/llvm/test/Transforms/LoopIdiom/wcslen32.ll
index 488afff86c245..2c7ceb4d187e6 100644
--- a/llvm/test/Transforms/LoopIdiom/wcslen32.ll
+++ b/llvm/test/Transforms/LoopIdiom/wcslen32.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -passes='loop(loop-idiom),verify' < %s -S | FileCheck %s
+; RUN: opt -passes='loop(loop-idiom)' < %s -S | FileCheck %s
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
>From 083fc6caa311279ebf12e9da6842ae69a1bfb9de Mon Sep 17 00:00:00 2001
From: Henry Jiang <henry.jiang1 at ibm.com>
Date: Sat, 22 Mar 2025 19:01:13 -0400
Subject: [PATCH 3/3] dont transform idiom named strlen
---
.../Transforms/Scalar/LoopIdiomRecognize.cpp | 3 +-
.../Transforms/LoopIdiom/strlen-noidiom.ll | 64 +++++++++++++++++++
2 files changed, 66 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/Transforms/LoopIdiom/strlen-noidiom.ll
diff --git a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
index a0b257b7d3ea3..4940dd8c47dde 100644
--- a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
@@ -313,7 +313,8 @@ bool LoopIdiomRecognize::runOnLoop(Loop *L) {
// Disable loop idiom recognition if the function's name is a common idiom.
StringRef Name = L->getHeader()->getParent()->getName();
- if (Name == "memset" || Name == "memcpy")
+ if (Name == "memset" || Name == "memcpy" || Name == "strlen" ||
+ Name == "wcslen")
return false;
// Determine if code size heuristics need to be applied.
diff --git a/llvm/test/Transforms/LoopIdiom/strlen-noidiom.ll b/llvm/test/Transforms/LoopIdiom/strlen-noidiom.ll
new file mode 100644
index 0000000000000..498ec52ed26ee
--- /dev/null
+++ b/llvm/test/Transforms/LoopIdiom/strlen-noidiom.ll
@@ -0,0 +1,64 @@
+; RUN: opt -passes='loop(loop-idiom)' < %s -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; CHECK-LABEL: @strlen(
+; CHECK-NOT: call{{.*}} strlen
+; CHECK-LABEL: @wcslen(
+; CHECK-NOT: call{{.*}} wcslen
+
+define i64 @strlen(ptr %str) {
+entry:
+ br label %while.cond
+
+while.cond:
+ %str.addr.0 = phi ptr [ %str, %entry ], [ %incdec.ptr, %while.cond ]
+ %0 = load i8, ptr %str.addr.0, align 1
+ %cmp.not = icmp eq i8 %0, 0
+ %incdec.ptr = getelementptr i8, ptr %str.addr.0, i64 1
+ br i1 %cmp.not, label %while.end, label %while.cond
+
+while.end:
+ %sub.ptr.lhs.cast = ptrtoint ptr %str.addr.0 to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %str to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ ret i64 %sub.ptr.sub
+}
+
+define i64 @wcslen(ptr %src) {
+entry:
+ %cmp = icmp eq ptr %src, null
+ br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false: ; preds = %entry
+ %0 = load i32, ptr %src, align 4
+ %cmp1 = icmp eq i32 %0, 0
+ br i1 %cmp1, label %return, label %while.cond.preheader
+
+while.cond.preheader: ; preds = %lor.lhs.false
+ br label %while.cond
+
+while.cond: ; preds = %while.cond.preheader, %while.cond
+ %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %while.cond.preheader ]
+ %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 4
+ %1 = load i32, ptr %curr.0, align 4
+ %tobool.not = icmp eq i32 %1, 0
+ br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end: ; preds = %while.cond
+ %curr.0.lcssa = phi ptr [ %curr.0, %while.cond ]
+ %sub.ptr.lhs.cast = ptrtoint ptr %curr.0.lcssa to i64
+ %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+ %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+ %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 2
+ br label %return
+
+return: ; preds = %entry, %lor.lhs.false, %while.end
+ %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+ ret i64 %retval.0
+}
+
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"wchar_size", i32 4}
More information about the llvm-commits
mailing list