[llvm] [PartiallyInlineLibCalls] Inline strcmp/strncmp calls like `strncmp(s, "ab")` (PR #89339)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 18 19:05:36 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Franklin Zhang (FLZ101)
<details>
<summary>Changes</summary>
Inline calls to `strcmp(s1, s2)` and `strncmp(s1, s2, N)`, where N and exactly one of s1 and s2 are constant.
For example:
```c
int res = strcmp(s, "ab");
```
is converted to
```c
int res = (int)s[0] - (int)'a';
if (res != 0)
goto END;
res = (int)s[1] - (int)'b';
if (res != 0)
goto END;
res = (int)s[2] - (int)'\0';
END:
```
---
Patch is 21.67 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/89339.diff
3 Files Affected:
- (modified) llvm/lib/Transforms/Scalar/PartiallyInlineLibCalls.cpp (+203)
- (added) llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-1.ll (+203)
- (added) llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-2.ll (+145)
``````````diff
diff --git a/llvm/lib/Transforms/Scalar/PartiallyInlineLibCalls.cpp b/llvm/lib/Transforms/Scalar/PartiallyInlineLibCalls.cpp
index 3a699df1cde4df..43dc7bc80b2f00 100644
--- a/llvm/lib/Transforms/Scalar/PartiallyInlineLibCalls.cpp
+++ b/llvm/lib/Transforms/Scalar/PartiallyInlineLibCalls.cpp
@@ -16,9 +16,11 @@
#include "llvm/Analysis/DomTreeUpdater.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/Analysis/TargetTransformInfo.h"
+#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/InitializePasses.h"
+#include "llvm/Support/CommandLine.h"
#include "llvm/Support/DebugCounter.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
@@ -101,6 +103,199 @@ static bool optimizeSQRT(CallInst *Call, Function *CalledFunc,
return true;
}
+static cl::opt<unsigned> StrNCmpInlineThreshold(
+ "strncmp-inline-threshold", cl::init(3), cl::Hidden,
+ cl::desc("The maximum length of a constant string for a builtin string cmp "
+ "call eligible for inlining. The default value is 3."));
+
+namespace {
+class StrNCmpInliner {
+public:
+ StrNCmpInliner(CallInst *CI, LibFunc Func, Function::iterator &BBNext,
+ DomTreeUpdater *DTU, const DataLayout &DL)
+ : CI(CI), Func(Func), BBNext(BBNext), DTU(DTU), DL(DL) {}
+
+ bool optimizeStrNCmp();
+
+private:
+ bool inlineCompare(Value *LHS, StringRef RHS, uint64_t N, bool Switched);
+
+ CallInst *CI;
+ LibFunc Func;
+ Function::iterator &BBNext;
+ DomTreeUpdater *DTU;
+ const DataLayout &DL;
+};
+
+} // namespace
+
+/// First we normalize calls to strncmp/strcmp to the form of
+/// compare(s1, s2, N), which means comparing first N bytes of s1 and s2
+/// (without considering '\0')
+///
+/// Examples:
+///
+/// \code
+/// strncmp(s, "a", 3) -> compare(s, "a", 2)
+/// strncmp(s, "abc", 3) -> compare(s, "abc", 3)
+/// strncmp(s, "a\0b", 3) -> compare(s, "a\0b", 2)
+/// strcmp(s, "a") -> compare(s, "a", 2)
+///
+/// char s2[] = {'a'}
+/// strncmp(s, s2, 3) -> compare(s, s2, 3)
+///
+/// char s2[] = {'a', 'b', 'c', 'd'}
+/// strncmp(s, s2, 3) -> compare(s, s2, 3)
+/// \endcode
+///
+/// We only handle cases that N and exactly one of s1 and s2 are constant. Cases
+/// that s1 and s2 are both constant are already handled by the instcombine
+/// pass.
+///
+/// We do not handle cases that N > StrNCmpInlineThreshold.
+///
+/// We also do not handles cases that N < 2, which are already
+/// handled by the instcombine pass.
+///
+bool StrNCmpInliner::optimizeStrNCmp() {
+ if (StrNCmpInlineThreshold < 2)
+ return false;
+
+ Value *Str1P = CI->getArgOperand(0);
+ Value *Str2P = CI->getArgOperand(1);
+ // should be handled elsewhere
+ if (Str1P == Str2P)
+ return false;
+
+ StringRef Str1, Str2;
+ bool HasStr1 = getConstantStringInfo(Str1P, Str1, false);
+ bool HasStr2 = getConstantStringInfo(Str2P, Str2, false);
+ if (!(HasStr1 ^ HasStr2))
+ return false;
+
+ // note that '\0' and characters after it are not trimmed
+ StringRef Str = HasStr1 ? Str1 : Str2;
+
+ size_t Idx = Str.find('\0');
+ uint64_t N = Idx == StringRef::npos ? UINT64_MAX : Idx + 1;
+ if (Func == LibFunc_strncmp) {
+ if (!isa<ConstantInt>(CI->getArgOperand(2)))
+ return false;
+ N = std::min(N, cast<ConstantInt>(CI->getArgOperand(2))->getZExtValue());
+ }
+ // now N means how many bytes we need to compare at most
+ if (N > Str.size() || N < 2 || N > StrNCmpInlineThreshold)
+ return false;
+
+ Value *StrP = HasStr1 ? Str2P : Str1P;
+
+ // cases that StrP has two or more dereferenceable bytes might be better
+ // optimized elsewhere
+ bool CanBeNull = false, CanBeFreed = false;
+ if (StrP->getPointerDereferenceableBytes(DL, CanBeNull, CanBeFreed) > 1)
+ return false;
+
+ return inlineCompare(StrP, Str, N, HasStr1);
+}
+
+/// Convert
+///
+/// \code
+/// ret = compare(s1, s2, N)
+/// \endcode
+///
+/// into
+///
+/// \code
+/// ret = (int)s1[0] - (int)s2[0]
+/// if (ret != 0)
+/// goto NE
+/// ...
+/// ret = (int)s1[N-2] - (int)s2[N-2]
+/// if (ret != 0)
+/// goto NE
+/// ret = (int)s1[N-1] - (int)s2[N-1]
+/// NE:
+/// \endcode
+///
+/// CFG before and after the transformation:
+///
+/// (before)
+/// BBCI
+///
+/// (after)
+/// BBBefore -> BBSubs[0] (sub,icmp) --NE-> BBNE -> BBCI
+/// | ^
+/// E |
+/// | |
+/// BBSubs[1] (sub,icmp) --NE-----+
+/// ... |
+/// BBSubs[N-1] (sub) ---------+
+///
+bool StrNCmpInliner::inlineCompare(Value *LHS, StringRef RHS, uint64_t N,
+ bool Switched) {
+ IRBuilder<> B(CI->getContext());
+
+ BasicBlock *BBCI = CI->getParent();
+ bool IsEntry = BBCI->isEntryBlock();
+ BasicBlock *BBBefore = splitBlockBefore(BBCI, CI, DTU, nullptr, nullptr,
+ BBCI->getName() + ".before");
+
+ SmallVector<BasicBlock *> BBSubs;
+ for (uint64_t i = 0; i < N + 1; ++i)
+ BBSubs.push_back(
+ BasicBlock::Create(CI->getContext(), "sub", BBCI->getParent(), BBCI));
+ BasicBlock *BBNE = BBSubs[N];
+
+ cast<BranchInst>(BBBefore->getTerminator())->setSuccessor(0, BBSubs[0]);
+
+ B.SetInsertPoint(BBNE);
+ PHINode *Phi = B.CreatePHI(CI->getType(), N);
+ B.CreateBr(BBCI);
+
+ Value *Base = LHS;
+ for (uint64_t i = 0; i < N; ++i) {
+ B.SetInsertPoint(BBSubs[i]);
+ Value *VL = B.CreateZExt(
+ B.CreateLoad(B.getInt8Ty(),
+ B.CreateInBoundsGEP(B.getInt8Ty(), Base, B.getInt64(i))),
+ CI->getType());
+ Value *VR = ConstantInt::get(CI->getType(), RHS[i]);
+ Value *Sub = Switched ? B.CreateSub(VR, VL) : B.CreateSub(VL, VR);
+ if (i < N - 1)
+ B.CreateCondBr(B.CreateICmpNE(Sub, ConstantInt::get(CI->getType(), 0)),
+ BBNE, BBSubs[i + 1]);
+ else
+ B.CreateBr(BBNE);
+
+ Phi->addIncoming(Sub, BBSubs[i]);
+ }
+
+ CI->replaceAllUsesWith(Phi);
+ CI->eraseFromParent();
+
+ BBNext = BBCI->getIterator();
+
+ // Update DomTree
+ if (DTU) {
+ if (IsEntry) {
+ DTU->recalculate(*BBCI->getParent());
+ } else {
+ SmallVector<DominatorTree::UpdateType, 8> Updates;
+ Updates.push_back({DominatorTree::Delete, BBBefore, BBCI});
+ Updates.push_back({DominatorTree::Insert, BBBefore, BBSubs[0]});
+ for (uint64_t i = 0; i < N; ++i) {
+ if (i < N - 1)
+ Updates.push_back({DominatorTree::Insert, BBSubs[i], BBSubs[i + 1]});
+ Updates.push_back({DominatorTree::Insert, BBSubs[i], BBNE});
+ }
+ Updates.push_back({DominatorTree::Insert, BBNE, BBCI});
+ DTU->applyUpdates(Updates);
+ }
+ }
+ return true;
+}
+
static bool runPartiallyInlineLibCalls(Function &F, TargetLibraryInfo *TLI,
const TargetTransformInfo *TTI,
DominatorTree *DT) {
@@ -143,6 +338,14 @@ static bool runPartiallyInlineLibCalls(Function &F, TargetLibraryInfo *TLI,
DTU ? &*DTU : nullptr))
break;
continue;
+ case LibFunc_strcmp:
+ case LibFunc_strncmp: {
+ auto DT = DTU ? &*DTU : nullptr;
+ auto &DL = F.getParent()->getDataLayout();
+ if (StrNCmpInliner(Call, LF, BB, DT, DL).optimizeStrNCmp())
+ break;
+ continue;
+ }
default:
continue;
}
diff --git a/llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-1.ll b/llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-1.ll
new file mode 100644
index 00000000000000..77bcbd7ab225b2
--- /dev/null
+++ b/llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-1.ll
@@ -0,0 +1,203 @@
+; RUN: opt -S -passes=partially-inline-libcalls -strncmp-inline-threshold=3 < %s | FileCheck %s
+
+declare i32 @strncmp(ptr nocapture, ptr nocapture, i64)
+declare i32 @strcmp(ptr nocapture, ptr nocapture)
+
+ at .str = private unnamed_addr constant [3 x i8] c"ab\00", align 1
+ at .str.1 = private unnamed_addr constant [2 x i8] c"a\00", align 1
+
+define i32 @test_strncmp_1(ptr nocapture readonly %s) {
+; CHECK-LABEL: @test_strncmp_1(
+; CHECK-NEXT: entry.before:
+; CHECK-NEXT: br label [[SUB:%.*]]
+; CHECK: sub:
+; CHECK-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr [[S:%.*]], i64 0
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 1
+; CHECK-NEXT: [[TMP2:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = sub i32 97, [[TMP2]]
+; CHECK-NEXT: [[TMP4:%.*]] = icmp ne i32 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TMP4]], label [[SUB2:%.*]], label [[SUB1:%.*]]
+; CHECK: sub1:
+; CHECK-NEXT: [[TMP5:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 1
+; CHECK-NEXT: [[TMP6:%.*]] = load i8, ptr [[TMP5]], align 1
+; CHECK-NEXT: [[TMP7:%.*]] = zext i8 [[TMP6]] to i32
+; CHECK-NEXT: [[TMP8:%.*]] = sub i32 98, [[TMP7]]
+; CHECK-NEXT: br label [[SUB2]]
+; CHECK: sub2:
+; CHECK-NEXT: [[TMP9:%.*]] = phi i32 [ [[TMP3]], [[SUB]] ], [ [[TMP8]], [[SUB1]] ]
+; CHECK-NEXT: br label [[ENTRY:%.*]]
+; CHECK: entry:
+; CHECK-NEXT: ret i32 [[TMP9]]
+;
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(3) @.str, ptr nonnull dereferenceable(1) %s, i64 2)
+ ret i32 %call
+}
+
+define i32 @test_strncmp_2(ptr nocapture readonly %s) {
+; CHECK-LABEL: @test_strncmp_2(
+; CHECK-NEXT: entry.before:
+; CHECK-NEXT: br label [[SUB:%.*]]
+; CHECK: sub:
+; CHECK-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr [[S:%.*]], i64 0
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 1
+; CHECK-NEXT: [[TMP2:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = sub i32 97, [[TMP2]]
+; CHECK-NEXT: [[TMP4:%.*]] = icmp ne i32 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TMP4]], label [[SUB3:%.*]], label [[SUB1:%.*]]
+; CHECK: sub1:
+; CHECK-NEXT: [[TMP5:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 1
+; CHECK-NEXT: [[TMP6:%.*]] = load i8, ptr [[TMP5]], align 1
+; CHECK-NEXT: [[TMP7:%.*]] = zext i8 [[TMP6]] to i32
+; CHECK-NEXT: [[TMP8:%.*]] = sub i32 98, [[TMP7]]
+; CHECK-NEXT: [[TMP9:%.*]] = icmp ne i32 [[TMP8]], 0
+; CHECK-NEXT: br i1 [[TMP9]], label [[SUB3]], label [[SUB2:%.*]]
+; CHECK: sub2:
+; CHECK-NEXT: [[TMP10:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 2
+; CHECK-NEXT: [[TMP11:%.*]] = load i8, ptr [[TMP10]], align 1
+; CHECK-NEXT: [[TMP12:%.*]] = zext i8 [[TMP11]] to i32
+; CHECK-NEXT: [[TMP13:%.*]] = sub i32 0, [[TMP12]]
+; CHECK-NEXT: br label [[SUB3]]
+; CHECK: sub3:
+; CHECK-NEXT: [[TMP14:%.*]] = phi i32 [ [[TMP3]], [[SUB]] ], [ [[TMP8]], [[SUB1]] ], [ [[TMP13]], [[SUB2]] ]
+; CHECK-NEXT: br label [[ENTRY:%.*]]
+; CHECK: entry:
+; CHECK-NEXT: ret i32 [[TMP14]]
+;
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(3) @.str, ptr nonnull dereferenceable(1) %s, i64 3)
+ ret i32 %call
+}
+
+define i32 @test_strncmp_3(ptr nocapture readonly %s) {
+; CHECK-LABEL: @test_strncmp_3(
+; CHECK-NEXT: entry.before:
+; CHECK-NEXT: br label [[SUB:%.*]]
+; CHECK: sub:
+; CHECK-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr [[S:%.*]], i64 0
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 1
+; CHECK-NEXT: [[TMP2:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = sub i32 97, [[TMP2]]
+; CHECK-NEXT: [[TMP4:%.*]] = icmp ne i32 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TMP4]], label [[SUB3:%.*]], label [[SUB1:%.*]]
+; CHECK: sub1:
+; CHECK-NEXT: [[TMP5:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 1
+; CHECK-NEXT: [[TMP6:%.*]] = load i8, ptr [[TMP5]], align 1
+; CHECK-NEXT: [[TMP7:%.*]] = zext i8 [[TMP6]] to i32
+; CHECK-NEXT: [[TMP8:%.*]] = sub i32 98, [[TMP7]]
+; CHECK-NEXT: [[TMP9:%.*]] = icmp ne i32 [[TMP8]], 0
+; CHECK-NEXT: br i1 [[TMP9]], label [[SUB3]], label [[SUB2:%.*]]
+; CHECK: sub2:
+; CHECK-NEXT: [[TMP10:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 2
+; CHECK-NEXT: [[TMP11:%.*]] = load i8, ptr [[TMP10]], align 1
+; CHECK-NEXT: [[TMP12:%.*]] = zext i8 [[TMP11]] to i32
+; CHECK-NEXT: [[TMP13:%.*]] = sub i32 0, [[TMP12]]
+; CHECK-NEXT: br label [[SUB3]]
+; CHECK: sub3:
+; CHECK-NEXT: [[TMP14:%.*]] = phi i32 [ [[TMP3]], [[SUB]] ], [ [[TMP8]], [[SUB1]] ], [ [[TMP13]], [[SUB2]] ]
+; CHECK-NEXT: br label [[ENTRY:%.*]]
+; CHECK: entry:
+; CHECK-NEXT: ret i32 [[TMP14]]
+;
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(3) @.str, ptr nonnull dereferenceable(1) %s, i64 4)
+ ret i32 %call
+}
+
+define i32 @test_strcmp_1(ptr nocapture readonly %s) {
+; CHECK-LABEL: @test_strcmp_1(
+; CHECK-NEXT: entry.before:
+; CHECK-NEXT: br label [[SUB:%.*]]
+; CHECK: sub:
+; CHECK-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr [[S:%.*]], i64 0
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 1
+; CHECK-NEXT: [[TMP2:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = sub i32 [[TMP2]], 97
+; CHECK-NEXT: [[TMP4:%.*]] = icmp ne i32 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TMP4]], label [[SUB2:%.*]], label [[SUB1:%.*]]
+; CHECK: sub1:
+; CHECK-NEXT: [[TMP5:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 1
+; CHECK-NEXT: [[TMP6:%.*]] = load i8, ptr [[TMP5]], align 1
+; CHECK-NEXT: [[TMP7:%.*]] = zext i8 [[TMP6]] to i32
+; CHECK-NEXT: [[TMP8:%.*]] = sub i32 [[TMP7]], 0
+; CHECK-NEXT: br label [[SUB2]]
+; CHECK: sub2:
+; CHECK-NEXT: [[TMP9:%.*]] = phi i32 [ [[TMP3]], [[SUB]] ], [ [[TMP8]], [[SUB1]] ]
+; CHECK-NEXT: br label [[ENTRY:%.*]]
+; CHECK: entry:
+; CHECK-NEXT: ret i32 [[TMP9]]
+;
+entry:
+ %call = tail call i32 @strcmp(ptr nonnull dereferenceable(1) %s, ptr nonnull dereferenceable(2) @.str.1)
+ ret i32 %call
+}
+
+define i32 @test_strcmp_2(ptr nocapture readonly %s) {
+; CHECK-LABEL: @test_strcmp_2(
+; CHECK-NEXT: entry.before:
+; CHECK-NEXT: br label [[SUB:%.*]]
+; CHECK: sub:
+; CHECK-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr [[S:%.*]], i64 0
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 1
+; CHECK-NEXT: [[TMP2:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = sub i32 [[TMP2]], 97
+; CHECK-NEXT: [[TMP4:%.*]] = icmp ne i32 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TMP4]], label [[SUB3:%.*]], label [[SUB1:%.*]]
+; CHECK: sub1:
+; CHECK-NEXT: [[TMP5:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 1
+; CHECK-NEXT: [[TMP6:%.*]] = load i8, ptr [[TMP5]], align 1
+; CHECK-NEXT: [[TMP7:%.*]] = zext i8 [[TMP6]] to i32
+; CHECK-NEXT: [[TMP8:%.*]] = sub i32 [[TMP7]], 98
+; CHECK-NEXT: [[TMP9:%.*]] = icmp ne i32 [[TMP8]], 0
+; CHECK-NEXT: br i1 [[TMP9]], label [[SUB3]], label [[SUB2:%.*]]
+; CHECK: sub2:
+; CHECK-NEXT: [[TMP10:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 2
+; CHECK-NEXT: [[TMP11:%.*]] = load i8, ptr [[TMP10]], align 1
+; CHECK-NEXT: [[TMP12:%.*]] = zext i8 [[TMP11]] to i32
+; CHECK-NEXT: [[TMP13:%.*]] = sub i32 [[TMP12]], 0
+; CHECK-NEXT: br label [[SUB3]]
+; CHECK: sub3:
+; CHECK-NEXT: [[TMP14:%.*]] = phi i32 [ [[TMP3]], [[SUB]] ], [ [[TMP8]], [[SUB1]] ], [ [[TMP13]], [[SUB2]] ]
+; CHECK-NEXT: br label [[ENTRY:%.*]]
+; CHECK: entry:
+; CHECK-NEXT: ret i32 [[TMP14]]
+;
+entry:
+ %call = tail call i32 @strcmp(ptr nonnull dereferenceable(1) %s, ptr nonnull dereferenceable(3) @.str)
+ ret i32 %call
+}
+
+define i32 @test_strcmp_3(ptr nocapture readonly %s) {
+; CHECK-LABEL: @test_strcmp_3(
+; CHECK-NEXT: entry.before:
+; CHECK-NEXT: br label [[SUB:%.*]]
+; CHECK: sub:
+; CHECK-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr [[S:%.*]], i64 0
+; CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[TMP0]], align 1
+; CHECK-NEXT: [[TMP2:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = sub i32 97, [[TMP2]]
+; CHECK-NEXT: [[TMP4:%.*]] = icmp ne i32 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[TMP4]], label [[SUB3:%.*]], label [[SUB1:%.*]]
+; CHECK: sub1:
+; CHECK-NEXT: [[TMP5:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 1
+; CHECK-NEXT: [[TMP6:%.*]] = load i8, ptr [[TMP5]], align 1
+; CHECK-NEXT: [[TMP7:%.*]] = zext i8 [[TMP6]] to i32
+; CHECK-NEXT: [[TMP8:%.*]] = sub i32 98, [[TMP7]]
+; CHECK-NEXT: [[TMP9:%.*]] = icmp ne i32 [[TMP8]], 0
+; CHECK-NEXT: br i1 [[TMP9]], label [[SUB3]], label [[SUB2:%.*]]
+; CHECK: sub2:
+; CHECK-NEXT: [[TMP10:%.*]] = getelementptr inbounds i8, ptr [[S]], i64 2
+; CHECK-NEXT: [[TMP11:%.*]] = load i8, ptr [[TMP10]], align 1
+; CHECK-NEXT: [[TMP12:%.*]] = zext i8 [[TMP11]] to i32
+; CHECK-NEXT: [[TMP13:%.*]] = sub i32 0, [[TMP12]]
+; CHECK-NEXT: br label [[SUB3]]
+; CHECK: sub3:
+; CHECK-NEXT: [[TMP14:%.*]] = phi i32 [ [[TMP3]], [[SUB]] ], [ [[TMP8]], [[SUB1]] ], [ [[TMP13]], [[SUB2]] ]
+; CHECK-NEXT: br label [[ENTRY:%.*]]
+; CHECK: entry:
+; CHECK-NEXT: ret i32 [[TMP14]]
+;
+entry:
+ %call = tail call i32 @strcmp(ptr nonnull dereferenceable(3) @.str, ptr nonnull dereferenceable(1) %s)
+ ret i32 %call
+}
diff --git a/llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-2.ll b/llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-2.ll
new file mode 100644
index 00000000000000..1c8bb3845b291b
--- /dev/null
+++ b/llvm/test/Transforms/PartiallyInlineLibCalls/strncmp-2.ll
@@ -0,0 +1,145 @@
+; RUN: opt -S -passes=partially-inline-libcalls -strncmp-inline-threshold=3 < %s | FileCheck %s
+
+declare i32 @strncmp(ptr nocapture, ptr nocapture, i64)
+declare i32 @strcmp(ptr nocapture, ptr nocapture)
+
+ at .str = private unnamed_addr constant [3 x i8] c"aa\00", align 1
+ at .str.1 = private unnamed_addr constant [4 x i8] c"aab\00", align 1
+ at __const.test_strncmp_8.s2 = private unnamed_addr constant [2 x i8] c"aa", align 1
+
+; int test_strncmp_1(const char *s) {
+; if (!strncmp(s, "aa", 2))
+; return 11;
+; return 41;
+; }
+define i32 @test_strncmp_1(i8* nocapture readonly %s) {
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(1) %s, ptr nonnull dereferenceable(3) @.str, i64 2)
+ %tobool.not = icmp eq i32 %call, 0
+ %retval.0 = select i1 %tobool.not, i32 11, i32 41
+ ret i32 %retval.0
+}
+; CHECK-LABEL: @test_strncmp_1(
+; CHECK-NOT: @strncmp
+
+; int test_strncmp_2(const char *s) {
+; if (!strncmp(s, "aa", 3))
+; return 11;
+; return 41;
+; }
+define i32 @test_strncmp_2(i8* nocapture readonly %s) {
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(1) %s, ptr nonnull dereferenceable(3) @.str, i64 3)
+ %tobool.not = icmp eq i32 %call, 0
+ %retval.0 = select i1 %tobool.not, i32 11, i32 41
+ ret i32 %retval.0
+}
+; CHECK-LABEL: @test_strncmp_2(
+; CHECK-NOT: @strncmp
+
+; int test_strncmp_3(const char *s) {
+; if (!strncmp(s, "aab", 3))
+; return 11;
+; return 41;
+; }
+define i32 @test_strncmp_3(i8* nocapture readonly %s) {
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(1) %s, ptr nonnull dereferenceable(4) @.str.1, i64 3)
+ %tobool.not = icmp eq i32 %call, 0
+ %retval.0 = select i1 %tobool.not, i32 11, i32 41
+ ret i32 %retval.0
+}
+; CHECK-LABEL: @test_strncmp_3(
+; CHECK-NOT: @strncmp
+
+; int test_strncmp_4(const char *s) {
+; if (!strncmp(s, "aab", 4))
+; return 11;
+; return 41;
+; }
+define i32 @test_strncmp_4(i8* nocapture readonly %s) {
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(1) %s, ptr nonnull dereferenceable(4) @.str.1, i64 4)
+ %tobool.not = icmp eq i32 %call, 0
+ %retval.0 = select i1 %tobool.not, i32 11, i32 41
+ ret i32 %retval.0
+}
+; CHECK-LABEL: @test_strncmp_4(
+; CHECK: @strncmp
+
+; int test_strncmp_5(const char *s) {
+; if (strncmp(s, "aa", 2) < 0)
+; return 11;
+; return 41;
+; }
+define i32 @test_strncmp_5(i8* nocapture readonly %s) {
+entry:
+ %call = tail call i32 @strncmp(ptr nonnull dereferenceable(1) %s, ptr nonnull dereferenceable(3) @.str, i64 2)
+ %cmp = icmp slt i32 %call, 0
+ %retval.0 = select i1 %cmp, i32 11, i32 41
+ ret i32 %retval.0
+}
+; CHECK-LABEL: @test_strncmp_5(
+; CHECK-NOT: @strncmp
+
+; int test_strncmp_6(const char *s1) {
+; char s2[] = {'a', 'a'};
+; if (strncmp(s1, s2, 2) < 0)
+; return 11;
+; return 41;
+; }
+define i32 @test_strncmp_6(i8* nocapture r...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/89339
More information about the llvm-commits
mailing list