https://github.com/Blazearth updated https://github.com/llvm/llvm-project/pull/231163
>From 386617c429cec75b75bbc3a54a205932cfb688e2 Mon Sep 17 00:00:00 2001 From: Arth Srivastava <[email protected]> Date: Mon, 12 Oct 2026 18:51:46 +0530 Subject: [PATCH 1/2] [Clang][LLVM] Add -finline-stringops=memset and "inline-stringops" function attribute Add -finline-stringops[=memset] and -fno-inline-stringops to prevent compiler-generated out-of-line memset calls in freestanding and kernel environments (such as Linux kernel vDSO). 1. Clang: Attach "inline-stringops"="memset" attribute to function definitions. 2. Inliner: Add adjustInlineStringops merge rule so caller inherits the attribute when an opted-in callee is inlined. 3. PreISelIntrinsicLowering: Expand variable/large memset into loops and rewrite eligible constant-size memset into llvm.memset.inline. Fixes #228242. --- clang/docs/ReleaseNotes.md | 5 + clang/docs/UsersManual.md | 3 + clang/include/clang/Basic/CodeGenOptions.h | 3 + clang/include/clang/Options/Options.td | 11 ++ clang/lib/CodeGen/CodeGenModule.cpp | 5 + clang/lib/Driver/ToolChains/Clang.cpp | 13 ++ clang/test/CodeGen/inline-stringops.c | 16 +++ clang/test/Driver/inline-stringops.c | 12 ++ llvm/include/llvm/IR/Attributes.td | 1 + llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp | 26 ++++ llvm/lib/IR/Attributes.cpp | 30 +++++ .../Inline/X86/inline-stringops-asm.ll | 53 ++++++++ .../Inline/X86/inline-stringops-lto.ll | 66 ++++++++++ .../Transforms/Inline/inline-stringops.ll | 80 ++++++++++++ .../AArch64/inline-stringops-memset.ll | 118 +++++++++++++++++ .../PowerPC/inline-stringops-memset.ll | 90 +++++++++++++ .../RISCV/inline-stringops-memset.ll | 90 +++++++++++++ .../X86/inline-stringops-memset.ll | 119 ++++++++++++++++++ .../inline-stringops-memset.ll | 90 +++++++++++++ 19 files changed, 831 insertions(+) create mode 100644 clang/test/CodeGen/inline-stringops.c create mode 100644 clang/test/Driver/inline-stringops.c create mode 100644 llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll create mode 100644 llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll create mode 100644 llvm/test/Transforms/Inline/inline-stringops.ll create mode 100644 llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll create mode 100644 llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll create mode 100644 llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll create mode 100644 llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll create mode 100644 llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 77a1e73bc8af60..86ab2d3930234b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -275,6 +275,11 @@ features cannot lower the translation-unit ABI level; ### New Compiler Flags +- Added `-finline-stringops[=memset]` and `-fno-inline-stringops` to prevent + compiler-generated out-of-line `memset` calls for applicable memory operations + in opted-in code, expanding eligible constant sizes to `llvm.memset.inline` and + large or runtime-variable sizes into loops. + - New option `-fmodules-validate-directory-dependencies` makes an implicitly built module out of date when a header is added to a directory it enumerated, such as an umbrella directory or the directory of an umbrella header, after it diff --git a/clang/docs/UsersManual.md b/clang/docs/UsersManual.md index 96d1c4e009c71a..346d7774418ad1 100644 --- a/clang/docs/UsersManual.md +++ b/clang/docs/UsersManual.md @@ -5604,6 +5604,9 @@ Execute `clang-cl /?` to see a list of supported options: > -fimplicit-module-maps Implicitly search the file system for module map > files. > -finline-max-stacksize=<value> > Suppress inlining of functions whose stack size > exceeds the given value +> -finline-stringops[=<value>] +> Prevent compiler-generated out-of-line calls for specified memory operations (supported: memset) in opted-in code +> -fno-inline-stringops Do not inline string operations > -fintegrated-cc1 Run cc1 in-process > -fintegrated-objemitter Use internal machine object code emitter. > -flto=<value> Set LTO mode diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index c3a23415c922ae..9aa068a9ab2beb 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -549,6 +549,9 @@ class CodeGenOptions : public CodeGenOptionsBase { std::vector<std::string> DefaultFunctionAttrs; + /// List of string operations that should be inlined. + std::vector<std::string> InlineStringops; + /// List of dynamic shared object files to be loaded as pass plugins. std::vector<std::string> PassPlugins; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 160c647286aac9..17d2522f96b2bd 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3306,6 +3306,17 @@ def finline_max_stacksize_EQ Group<f_Group>, Visibility<[ClangOption, CLOption, CC1Option]>, HelpText<"Suppress inlining of functions whose stack size exceeds the given value">, MarshallingInfoInt<CodeGenOpts<"InlineMaxStackSize">, "UINT_MAX">; +def finline_stringops_EQ : CommaJoined<["-"], "finline-stringops=">, + Group<f_Group>, Visibility<[ClangOption, CC1Option]>, + HelpText<"Prevent compiler-generated out-of-line calls for specified memory operations (supported: memset)">, + MarshallingInfoStringVector<CodeGenOpts<"InlineStringops">>; +def finline_stringops : Flag<["-"], "finline-stringops">, + Group<f_Group>, Visibility<[ClangOption, CC1Option]>, + Alias<finline_stringops_EQ>, AliasArgs<["memset"]>, + HelpText<"Inline string operations (equivalent to -finline-stringops=memset)">; +def fno_inline_stringops : Flag<["-"], "fno-inline-stringops">, + Group<f_Group>, Visibility<[ClangOption]>, + HelpText<"Do not inline string operations">; defm jmc : BoolFOption<"jmc", CodeGenOpts<"JMCInstrument">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption, CC1Option], diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 62ef11a697b1c2..5884b18750db0c 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3172,6 +3172,11 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, B.addAttribute("stack-probe-size", std::to_string(CodeGenOpts.StackProbeSize)); + if (!CodeGenOpts.InlineStringops.empty()) { + std::string Ops = llvm::join(CodeGenOpts.InlineStringops, ","); + B.addAttribute("inline-stringops", Ops); + } + if (!CodeGenUtils::hasUnwindExceptions(LangOpts)) B.addAttribute(llvm::Attribute::NoUnwind); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 4c8530f01fd9a8..7325758a1ecbcd 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7840,6 +7840,19 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Args.AddLastArg(CmdArgs, options::OPT_finline_max_stacksize_EQ); + if (const Arg *A = Args.getLastArg(options::OPT_finline_stringops_EQ, + options::OPT_fno_inline_stringops)) { + if (A->getOption().matches(options::OPT_finline_stringops_EQ)) { + for (StringRef Val : A->getValues()) { + if (Val != "memset") { + D.Diag(diag::err_drv_invalid_value) << A->getAsString(Args) << Val; + } + } + CmdArgs.push_back(Args.MakeArgString(Twine("-finline-stringops=") + + llvm::join(A->getValues(), ","))); + } + } + // FIXME: Find a better way to determine whether we are in C++20. bool HaveCxx20 = Std && diff --git a/clang/test/CodeGen/inline-stringops.c b/clang/test/CodeGen/inline-stringops.c new file mode 100644 index 00000000000000..78d853dd6efdd2 --- /dev/null +++ b/clang/test/CodeGen/inline-stringops.c @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - -finline-stringops=memset %s | FileCheck --check-prefix=CHECK-DEF-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - -finline-stringops %s | FileCheck --check-prefix=CHECK-DEF-ATTR %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck --check-prefix=CHECK-NO-ATTR %s + +extern void bar(void); + +void foo(void) { + bar(); +} + +// CHECK-DEF-ATTR: define dso_local void @foo() #[[ATTR:[0-9]+]] +// CHECK-DEF-ATTR: declare void @bar() #[[DECL_ATTR:[0-9]+]] +// CHECK-DEF-ATTR: attributes #[[ATTR]] = { {{.*}}"inline-stringops"="memset"{{.*}} } +// CHECK-DEF-ATTR-NOT: attributes #[[DECL_ATTR]] = { {{.*}}"inline-stringops" + +// CHECK-NO-ATTR-NOT: "inline-stringops" diff --git a/clang/test/Driver/inline-stringops.c b/clang/test/Driver/inline-stringops.c new file mode 100644 index 00000000000000..bfbcef492a6b47 --- /dev/null +++ b/clang/test/Driver/inline-stringops.c @@ -0,0 +1,12 @@ +// RUN: %clang -### -c -finline-stringops=memset %s 2>&1 | FileCheck --check-prefix=CHECK-MEMSET %s +// RUN: %clang -### -c -finline-stringops %s 2>&1 | FileCheck --check-prefix=CHECK-MEMSET %s +// RUN: %clang -### -c -fno-inline-stringops -finline-stringops=memset %s 2>&1 | FileCheck --check-prefix=CHECK-MEMSET %s +// RUN: %clang -### -c -fno-inline-stringops -finline-stringops %s 2>&1 | FileCheck --check-prefix=CHECK-MEMSET %s +// CHECK-MEMSET: "-finline-stringops=memset" + +// RUN: %clang -### -c -finline-stringops=memset -fno-inline-stringops %s 2>&1 | FileCheck --check-prefix=CHECK-NO-INLINE %s +// RUN: %clang -### -c -finline-stringops -fno-inline-stringops %s 2>&1 | FileCheck --check-prefix=CHECK-NO-INLINE %s +// CHECK-NO-INLINE-NOT: "-finline-stringops=" + +// RUN: not %clang -### -c -finline-stringops=invalid %s 2>&1 | FileCheck --check-prefix=CHECK-INVALID %s +// CHECK-INVALID: error: invalid value 'invalid' in '-finline-stringops=invalid' diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index b3a80cf8f6b3c5..2b7ef4a34caf25 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -503,4 +503,5 @@ def : MergeRule<"adjustCallerStackProbes">; def : MergeRule<"adjustCallerStackProbeSize">; def : MergeRule<"adjustMinLegalVectorWidth">; def : MergeRule<"adjustNullPointerValidAttr">; +def : MergeRule<"adjustInlineStringops">; def : MergeRule<"setAND<MustProgressAttr>">; diff --git a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp index 7a4a6778bc6c69..49f7c1e26e42f5 100644 --- a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp +++ b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp @@ -288,6 +288,15 @@ static bool canEmitMemcpy(const ModuleLibcallLoweringInfo &ModuleLowering, return Lowering.getMemcpyImpl() != RTLIB::Unsupported; } +static bool shouldInlineStringOp(const Function *F, StringRef Op) { + if (!F->hasFnAttribute("inline-stringops")) + return false; + StringRef Val = F->getFnAttribute("inline-stringops").getValueAsString(); + SmallVector<StringRef, 4> Ops; + Val.split(Ops, ',', -1, false); + return llvm::is_contained(Ops, Op); +} + // Return a value appropriate for use with the memset_pattern16 libcall, if // possible and if we know how. (Adapted from equivalent helper in // LoopIdiomRecognize). @@ -406,6 +415,23 @@ bool PreISelIntrinsicLowering::expandMemIntrinsicUses( auto *Memset = cast<MemSetInst>(Inst); Function *ParentFunc = Memset->getFunction(); const TargetTransformInfo &TTI = LookupTTI(*ParentFunc); + if (shouldInlineStringOp(ParentFunc, "memset")) { + if (shouldExpandMemIntrinsicWithSize(Memset->getLength(), TTI)) { + expandMemSetAsLoop(Memset, TTI); + Changed = true; + Memset->eraseFromParent(); + } else { + IRBuilder<> Builder(Memset); + CallInst *NewCI = Builder.CreateMemSetInline( + Memset->getRawDest(), Memset->getDestAlign(), Memset->getValue(), + Memset->getLength(), Memset->isVolatile()); + NewCI->copyMetadata(*Memset); + NewCI->setAttributes(Memset->getAttributes()); + Memset->eraseFromParent(); + Changed = true; + } + break; + } if (shouldExpandMemIntrinsicWithSize(Memset->getLength(), TTI)) { if (UseMemIntrinsicLibFunc && canEmitLibcall(ModuleLibcalls, TM, ParentFunc, RTLIB::MEMSET)) diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp index 7f5f5f34af3899..f3ad8af7010c86 100644 --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -2714,6 +2714,36 @@ adjustNullPointerValidAttr(Function &Caller, const Function &Callee) { } } +/// If the inlined function has inline-stringops attribute, ensure the caller +/// inherits it so string operation guarantees survive inlining. +static void adjustInlineStringops(Function &Caller, const Function &Callee) { + Attribute CalleeAttr = Callee.getFnAttribute("inline-stringops"); + if (!CalleeAttr.isValid()) + return; + + Attribute CallerAttr = Caller.getFnAttribute("inline-stringops"); + if (!CallerAttr.isValid()) { + Caller.addFnAttr(CalleeAttr); + return; + } + + StringRef CallerVal = CallerAttr.getValueAsString(); + StringRef CalleeVal = CalleeAttr.getValueAsString(); + if (CallerVal == CalleeVal) + return; + + SmallVector<StringRef, 4> Ops; + CallerVal.split(Ops, ',', -1, false); + SmallVector<StringRef, 4> CalleeOps; + CalleeVal.split(CalleeOps, ',', -1, false); + for (StringRef Op : CalleeOps) { + if (!llvm::is_contained(Ops, Op)) + Ops.push_back(Op); + } + std::string Merged = llvm::join(Ops, ","); + Caller.addFnAttr("inline-stringops", Merged); +} + struct EnumAttr { static bool isSet(const Function &Fn, Attribute::AttrKind Kind) { diff --git a/llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll b/llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll new file mode 100644 index 00000000000000..87c02c8374584c --- /dev/null +++ b/llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll @@ -0,0 +1,53 @@ +; RUN: opt -passes=inline %s | llc -mtriple=x86_64-unknown-linux-gnu -o - | FileCheck %s + +; Opted-in callee inlined into unmarked caller: must generate NO call memset. +define internal void @callee_optin(ptr %p) #0 { +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +define void @caller_inlined(ptr %p) { +; CHECK-LABEL: caller_inlined: +; CHECK-NOT: callq memset +; CHECK: retq +entry: + call void @callee_optin(ptr %p) + ret void +} + +; Unmarked caller with its own memset that inlines an opted-in callee: +; Both operations expand inline (loops), generating NO call memset. +define void @caller_with_own_memset(ptr %p1, ptr %p2) { +; CHECK-LABEL: caller_with_own_memset: +; CHECK-NOT: callq memset +; CHECK: retq +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p1, i8 0, i64 1024, i1 false) + call void @callee_optin(ptr %p2) + ret void +} + +; Function opted-in directly: must generate NO call memset. +define void @optin_direct(ptr %p) #0 { +; CHECK-LABEL: optin_direct: +; CHECK-NOT: callq memset +; CHECK: retq +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Unmarked function: retains normal lowering and calls memset. +define void @unmarked_func(ptr %p) { +; CHECK-LABEL: unmarked_func: +; CHECK: callq memset +; CHECK: retq +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } diff --git a/llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll b/llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll new file mode 100644 index 00000000000000..6d27b2184c2720 --- /dev/null +++ b/llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll @@ -0,0 +1,66 @@ +; REQUIRES: x86-registered-target +; RUN: split-file %s %t +; RUN: llvm-link %t/mod_a.ll %t/mod_b.ll -S -o %t/linked.ll +; RUN: opt -passes='default<O2>' %t/linked.ll -S -o %t/opt.ll +; RUN: llc -mtriple=x86_64-unknown-linux-gnu %t/opt.ll -o - | FileCheck %s + +; Inlined opted-in code across modules must not produce a memset call. +; CHECK-LABEL: caller_inlines_optin: +; CHECK-NOT: callq memset +; CHECK-NOT: jmp memset +; CHECK: retq + +; Opted-in callee non-inlined call. +; CHECK-LABEL: caller_calls_optin_noinline: +; CHECK: jmp optin_noinline + +; Unrelated unmarked function retains standard lowering to memset. +; CHECK-LABEL: unmarked_function: +; CHECK: jmp memset@PLT + +;--- mod_a.ll +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 void @optin_callee(ptr %p) #0 { +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +define void @optin_noinline(ptr %p) noinline #0 { +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } + +;--- mod_b.ll +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" + +declare void @optin_callee(ptr) +declare void @optin_noinline(ptr) + +define void @caller_inlines_optin(ptr %p) { +entry: + call void @optin_callee(ptr %p) + ret void +} + +define void @caller_calls_optin_noinline(ptr %p) { +entry: + call void @optin_noinline(ptr %p) + ret void +} + +define void @unmarked_function(ptr %p) { +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) diff --git a/llvm/test/Transforms/Inline/inline-stringops.ll b/llvm/test/Transforms/Inline/inline-stringops.ll new file mode 100644 index 00000000000000..980af7f7f91807 --- /dev/null +++ b/llvm/test/Transforms/Inline/inline-stringops.ll @@ -0,0 +1,80 @@ +; RUN: opt -passes=inline -S %s | FileCheck %s + +; Opted-in callee inlined into unmarked caller: caller must inherit the attribute. +define internal void @callee_memset(ptr %p) #0 { +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +define void @caller_unmarked(ptr %p) { +; CHECK-LABEL: define void @caller_unmarked( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @callee_memset(ptr %p) + ret void +} + +; Unmarked caller with its own memset inlining an opted-in callee: +; Caller inherits the attribute, so both the inlined memset and caller's own memset +; become subject to the inline-stringops policy. +define void @caller_with_own_memset(ptr %p1, ptr %p2) { +; CHECK-LABEL: define void @caller_with_own_memset( +; CHECK-SAME: ptr [[P1:%.*]], ptr [[P2:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P1]], i8 0, i64 1024, i1 false) +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P2]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p1, i8 0, i64 1024, i1 false) + call void @callee_memset(ptr %p2) + ret void +} + +; Non-inlined opted-in callee: unmarked caller must NOT inherit the attribute. +define void @callee_noinline(ptr %p) noinline #0 { +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +define void @caller_noinline(ptr %p) { +; CHECK-LABEL: define void @caller_noinline( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @callee_noinline(ptr [[P]]) +; CHECK-NEXT: ret void +entry: + call void @callee_noinline(ptr %p) + ret void +} + +; Inlining callee with "memcpy" into caller with "memset" should union attributes. +define internal void @callee_memcpy(ptr %dst, ptr %src) #1 { +entry: + call void @llvm.memcpy.p0.p0.i64(ptr align 1 %dst, ptr align 1 %src, i64 64, i1 false) + ret void +} + +define void @caller_union(ptr %dst, ptr %src) #0 { +; CHECK-LABEL: define void @caller_union( +; CHECK-SAME: ptr [[DST:%.*]], ptr [[SRC:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[DST]], ptr align 1 [[SRC]], i64 64, i1 false) +; CHECK-NEXT: ret void +entry: + call void @callee_memcpy(ptr %dst, ptr %src) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) +declare void @llvm.memcpy.p0.p0.i64(ptr nocapture writeonly, ptr nocapture readonly, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } +attributes #1 = { "inline-stringops"="memcpy" } + +; CHECK-DAG: attributes #[[ATTR0]] = { "inline-stringops"="memset" } +; CHECK-DAG: attributes #[[ATTR1]] = { "inline-stringops"="memset,memcpy" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll new file mode 100644 index 00000000000000..f4beae0091e9a4 --- /dev/null +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll @@ -0,0 +1,118 @@ +; RUN: opt -mtriple=aarch64-unknown-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s + +; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +define void @optin_small_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_small_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +define void @optin_large_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_large_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: br label %[[LOOP:.*]] +; CHECK: [[LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, 512 +; CHECK: br i1 {{.*}}, label %[[LOOP]], label %[[EXIT:.*]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length with "inline-stringops"="memset" should expand to loop +define void @optin_var(ptr %p, i64 %len) #0 { +; CHECK-LABEL: define void @optin_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[LEN]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[VAR_LOOP:.*]], label %[[VAR_EXIT:.*]] +; CHECK: [[VAR_LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, [[LEN]] +; CHECK: br i1 {{.*}}, label %[[VAR_LOOP]], label %[[VAR_EXIT]] +; CHECK: [[VAR_EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +; Small constant in unmarked function should remain untouched +define void @unmarked_small_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_small_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant in unmarked function should remain untouched +define void @unmarked_large_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_large_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length in unmarked function should remain untouched +define void @unmarked_var(ptr %p, i64 %len) { +; CHECK-LABEL: define void @unmarked_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 [[LEN]], i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +; Function with "inline-stringops"="memcpy" should NOT transform memset +define void @unrelated_optin(ptr %p) #1 { +; CHECK-LABEL: define void @unrelated_optin( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Function with multiple stringops "memset,memcpy" should transform memset +define void @multi_optin(ptr %p) #2 { +; CHECK-LABEL: define void @multi_optin( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR2:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } +attributes #1 = { "inline-stringops"="memcpy" } +attributes #2 = { "inline-stringops"="memset,memcpy" } + +; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } +; CHECK: attributes #[[ATTR1]] = { "inline-stringops"="memcpy" } +; CHECK: attributes #[[ATTR2]] = { "inline-stringops"="memset,memcpy" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll new file mode 100644 index 00000000000000..f2f8857240b341 --- /dev/null +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll @@ -0,0 +1,90 @@ +; RUN: opt -mtriple=powerpc64le-unknown-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s + +; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +define void @optin_small_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_small_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +define void @optin_large_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_large_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: br label %[[LOOP:.*]] +; CHECK: [[LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, 512 +; CHECK: br i1 {{.*}}, label %[[LOOP]], label %[[EXIT:.*]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length with "inline-stringops"="memset" should expand to loop +define void @optin_var(ptr %p, i64 %len) #0 { +; CHECK-LABEL: define void @optin_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[LEN]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[VAR_LOOP:.*]], label %[[VAR_EXIT:.*]] +; CHECK: [[VAR_LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, [[LEN]] +; CHECK: br i1 {{.*}}, label %[[VAR_LOOP]], label %[[VAR_EXIT]] +; CHECK: [[VAR_EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +; Small constant in unmarked function should remain untouched +define void @unmarked_small_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_small_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant in unmarked function should remain untouched +define void @unmarked_large_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_large_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length in unmarked function should remain untouched +define void @unmarked_var(ptr %p, i64 %len) { +; CHECK-LABEL: define void @unmarked_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 [[LEN]], i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } + +; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll new file mode 100644 index 00000000000000..603387f27030c5 --- /dev/null +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll @@ -0,0 +1,90 @@ +; RUN: opt -mtriple=riscv64-unknown-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s + +; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +define void @optin_small_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_small_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +define void @optin_large_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_large_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: br label %[[LOOP:.*]] +; CHECK: [[LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, 512 +; CHECK: br i1 {{.*}}, label %[[LOOP]], label %[[EXIT:.*]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length with "inline-stringops"="memset" should expand to loop +define void @optin_var(ptr %p, i64 %len) #0 { +; CHECK-LABEL: define void @optin_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[LEN]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[VAR_LOOP:.*]], label %[[VAR_EXIT:.*]] +; CHECK: [[VAR_LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, [[LEN]] +; CHECK: br i1 {{.*}}, label %[[VAR_LOOP]], label %[[VAR_EXIT]] +; CHECK: [[VAR_EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +; Small constant in unmarked function should remain untouched +define void @unmarked_small_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_small_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant in unmarked function should remain untouched +define void @unmarked_large_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_large_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length in unmarked function should remain untouched +define void @unmarked_var(ptr %p, i64 %len) { +; CHECK-LABEL: define void @unmarked_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 [[LEN]], i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } + +; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll new file mode 100644 index 00000000000000..0486257ff3b9d7 --- /dev/null +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll @@ -0,0 +1,119 @@ +; RUN: opt -mtriple=x86_64-pc-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s +; RUN: opt -mtriple=i686-pc-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s + +; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +define void @optin_small_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_small_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +define void @optin_large_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_large_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: br label %[[LOOP:.*]] +; CHECK: [[LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, 512 +; CHECK: br i1 {{.*}}, label %[[LOOP]], label %[[EXIT:.*]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length with "inline-stringops"="memset" should expand to loop +define void @optin_var(ptr %p, i64 %len) #0 { +; CHECK-LABEL: define void @optin_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[LEN]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[VAR_LOOP:.*]], label %[[VAR_EXIT:.*]] +; CHECK: [[VAR_LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, [[LEN]] +; CHECK: br i1 {{.*}}, label %[[VAR_LOOP]], label %[[VAR_EXIT]] +; CHECK: [[VAR_EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +; Small constant in unmarked function should remain untouched +define void @unmarked_small_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_small_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant in unmarked function should remain untouched +define void @unmarked_large_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_large_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length in unmarked function should remain untouched +define void @unmarked_var(ptr %p, i64 %len) { +; CHECK-LABEL: define void @unmarked_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 [[LEN]], i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +; Function with "inline-stringops"="memcpy" should NOT transform memset +define void @unrelated_optin(ptr %p) #1 { +; CHECK-LABEL: define void @unrelated_optin( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Function with multiple stringops "memset,memcpy" should transform memset +define void @multi_optin(ptr %p) #2 { +; CHECK-LABEL: define void @multi_optin( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR2:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } +attributes #1 = { "inline-stringops"="memcpy" } +attributes #2 = { "inline-stringops"="memset,memcpy" } + +; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } +; CHECK: attributes #[[ATTR1]] = { "inline-stringops"="memcpy" } +; CHECK: attributes #[[ATTR2]] = { "inline-stringops"="memset,memcpy" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll new file mode 100644 index 00000000000000..b5f672499320b9 --- /dev/null +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll @@ -0,0 +1,90 @@ +; RUN: opt -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s + +; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +define void @optin_small_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_small_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +define void @optin_large_const(ptr %p) #0 { +; CHECK-LABEL: define void @optin_large_const( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: br label %[[LOOP:.*]] +; CHECK: [[LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, 512 +; CHECK: br i1 {{.*}}, label %[[LOOP]], label %[[EXIT:.*]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length with "inline-stringops"="memset" should expand to loop +define void @optin_var(ptr %p, i64 %len) #0 { +; CHECK-LABEL: define void @optin_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[LEN]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[VAR_LOOP:.*]], label %[[VAR_EXIT:.*]] +; CHECK: [[VAR_LOOP]]: +; CHECK: store i8 0, ptr +; CHECK: icmp ult i64 {{.*}}, [[LEN]] +; CHECK: br i1 {{.*}}, label %[[VAR_LOOP]], label %[[VAR_EXIT]] +; CHECK: [[VAR_EXIT]]: +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +; Small constant in unmarked function should remain untouched +define void @unmarked_small_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_small_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) + ret void +} + +; Large constant in unmarked function should remain untouched +define void @unmarked_large_const(ptr %p) { +; CHECK-LABEL: define void @unmarked_large_const( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + +; Variable length in unmarked function should remain untouched +define void @unmarked_var(ptr %p, i64 %len) { +; CHECK-LABEL: define void @unmarked_var( +; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 [[LEN]], i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 %len, i1 false) + ret void +} + +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) + +attributes #0 = { "inline-stringops"="memset" } + +; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } >From 0a9b6be867c57b7865b967b59558527385817548 Mon Sep 17 00:00:00 2001 From: Arth Srivastava <[email protected]> Date: Thu, 22 Oct 2026 16:44:01 +0530 Subject: [PATCH 2/2] [Clang][LLVM] Simplify inline-memset attribute Replace the multi-valued inline-stringops attribute with a dedicated inline-memset attribute and standard setOR propagation. Document the attribute in LangRef and update lowering and regression tests for inlining, LTO, and target-specific behavior. Assisted-by: Google Antigravity (Gemini 3.8) --- clang/lib/CodeGen/CodeGenModule.cpp | 6 +-- clang/test/CodeGen/inline-stringops.c | 6 +-- llvm/docs/LangRef.md | 6 +++ llvm/include/llvm/IR/Attributes.td | 3 +- llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp | 12 +---- llvm/lib/IR/Attributes.cpp | 30 ------------ .../Inline/X86/inline-stringops-asm.ll | 2 +- .../Inline/X86/inline-stringops-lto.ll | 2 +- .../Transforms/Inline/inline-stringops.ll | 46 +++++++++++++------ .../AArch64/inline-stringops-memset.ll | 30 ++++-------- .../PowerPC/inline-stringops-memset.ll | 10 ++-- .../RISCV/inline-stringops-memset.ll | 10 ++-- .../X86/inline-stringops-memset.ll | 30 ++++-------- .../inline-stringops-memset.ll | 24 ++++++++-- 14 files changed, 94 insertions(+), 123 deletions(-) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 5884b18750db0c..b1eafb962ae13e 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3172,9 +3172,9 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, B.addAttribute("stack-probe-size", std::to_string(CodeGenOpts.StackProbeSize)); - if (!CodeGenOpts.InlineStringops.empty()) { - std::string Ops = llvm::join(CodeGenOpts.InlineStringops, ","); - B.addAttribute("inline-stringops", Ops); + for (StringRef Op : CodeGenOpts.InlineStringops) { + if (Op == "memset") + B.addAttribute("inline-memset", "true"); } if (!CodeGenUtils::hasUnwindExceptions(LangOpts)) diff --git a/clang/test/CodeGen/inline-stringops.c b/clang/test/CodeGen/inline-stringops.c index 78d853dd6efdd2..7f053ab23373ff 100644 --- a/clang/test/CodeGen/inline-stringops.c +++ b/clang/test/CodeGen/inline-stringops.c @@ -10,7 +10,7 @@ void foo(void) { // CHECK-DEF-ATTR: define dso_local void @foo() #[[ATTR:[0-9]+]] // CHECK-DEF-ATTR: declare void @bar() #[[DECL_ATTR:[0-9]+]] -// CHECK-DEF-ATTR: attributes #[[ATTR]] = { {{.*}}"inline-stringops"="memset"{{.*}} } -// CHECK-DEF-ATTR-NOT: attributes #[[DECL_ATTR]] = { {{.*}}"inline-stringops" +// CHECK-DEF-ATTR: attributes #[[ATTR]] = { {{.*}}"inline-memset"="true"{{.*}} } +// CHECK-DEF-ATTR-NOT: attributes #[[DECL_ATTR]] = { {{.*}}"inline-memset" -// CHECK-NO-ATTR-NOT: "inline-stringops" +// CHECK-NO-ATTR-NOT: "inline-memset" diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md index f8cf36be4a4c76..451767e2578949 100644 --- a/llvm/docs/LangRef.md +++ b/llvm/docs/LangRef.md @@ -2286,6 +2286,12 @@ define void @f() "no-sse" { ... } C/C++). It is just a hint; it imposes no requirements on the inliner. +`inline-memset` +: When this attribute is set to true, any calls to `llvm.memset` within this + function—including operations present after inlining or intermediate + transformations—must be expanded inline without generating external library + calls to `memset`. + `jumptable` : This attribute indicates that the function should be added to a jump-instruction table at code-generation time, and that all address-taken diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index 2b7ef4a34caf25..1e1e47dc37de8a 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -440,6 +440,7 @@ def NoInlineLineTables : StrBoolAttr<"no-inline-line-tables">; def ProfileSampleAccurate : StrBoolAttr<"profile-sample-accurate">; def UseSampleProfile : StrBoolAttr<"use-sample-profile">; def LoaderReplaceable : StrBoolAttr<"loader-replaceable">; +def InlineMemset : StrBoolAttr<"inline-memset">; // Attribute compatiblity rules are generated to check the attribute of the // caller and callee and decide whether inlining should be allowed. CompatRule @@ -503,5 +504,5 @@ def : MergeRule<"adjustCallerStackProbes">; def : MergeRule<"adjustCallerStackProbeSize">; def : MergeRule<"adjustMinLegalVectorWidth">; def : MergeRule<"adjustNullPointerValidAttr">; -def : MergeRule<"adjustInlineStringops">; +def : MergeRule<"setOR<InlineMemsetAttr>">; def : MergeRule<"setAND<MustProgressAttr>">; diff --git a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp index 49f7c1e26e42f5..b9a1063d6de93e 100644 --- a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp +++ b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp @@ -288,15 +288,6 @@ static bool canEmitMemcpy(const ModuleLibcallLoweringInfo &ModuleLowering, return Lowering.getMemcpyImpl() != RTLIB::Unsupported; } -static bool shouldInlineStringOp(const Function *F, StringRef Op) { - if (!F->hasFnAttribute("inline-stringops")) - return false; - StringRef Val = F->getFnAttribute("inline-stringops").getValueAsString(); - SmallVector<StringRef, 4> Ops; - Val.split(Ops, ',', -1, false); - return llvm::is_contained(Ops, Op); -} - // Return a value appropriate for use with the memset_pattern16 libcall, if // possible and if we know how. (Adapted from equivalent helper in // LoopIdiomRecognize). @@ -415,7 +406,8 @@ bool PreISelIntrinsicLowering::expandMemIntrinsicUses( auto *Memset = cast<MemSetInst>(Inst); Function *ParentFunc = Memset->getFunction(); const TargetTransformInfo &TTI = LookupTTI(*ParentFunc); - if (shouldInlineStringOp(ParentFunc, "memset")) { + Attribute InlineMemsetAttr = ParentFunc->getFnAttribute("inline-memset"); + if (InlineMemsetAttr.isValid() && InlineMemsetAttr.getValueAsBool()) { if (shouldExpandMemIntrinsicWithSize(Memset->getLength(), TTI)) { expandMemSetAsLoop(Memset, TTI); Changed = true; diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp index f3ad8af7010c86..7f5f5f34af3899 100644 --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -2714,36 +2714,6 @@ adjustNullPointerValidAttr(Function &Caller, const Function &Callee) { } } -/// If the inlined function has inline-stringops attribute, ensure the caller -/// inherits it so string operation guarantees survive inlining. -static void adjustInlineStringops(Function &Caller, const Function &Callee) { - Attribute CalleeAttr = Callee.getFnAttribute("inline-stringops"); - if (!CalleeAttr.isValid()) - return; - - Attribute CallerAttr = Caller.getFnAttribute("inline-stringops"); - if (!CallerAttr.isValid()) { - Caller.addFnAttr(CalleeAttr); - return; - } - - StringRef CallerVal = CallerAttr.getValueAsString(); - StringRef CalleeVal = CalleeAttr.getValueAsString(); - if (CallerVal == CalleeVal) - return; - - SmallVector<StringRef, 4> Ops; - CallerVal.split(Ops, ',', -1, false); - SmallVector<StringRef, 4> CalleeOps; - CalleeVal.split(CalleeOps, ',', -1, false); - for (StringRef Op : CalleeOps) { - if (!llvm::is_contained(Ops, Op)) - Ops.push_back(Op); - } - std::string Merged = llvm::join(Ops, ","); - Caller.addFnAttr("inline-stringops", Merged); -} - struct EnumAttr { static bool isSet(const Function &Fn, Attribute::AttrKind Kind) { diff --git a/llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll b/llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll index 87c02c8374584c..4b06f0267226bf 100644 --- a/llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll +++ b/llvm/test/Transforms/Inline/X86/inline-stringops-asm.ll @@ -50,4 +50,4 @@ entry: declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } +attributes #0 = { "inline-memset"="true" } diff --git a/llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll b/llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll index 6d27b2184c2720..75ce935655bfb8 100644 --- a/llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll +++ b/llvm/test/Transforms/Inline/X86/inline-stringops-lto.ll @@ -36,7 +36,7 @@ entry: declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } +attributes #0 = { "inline-memset"="true" } ;--- mod_b.ll 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" diff --git a/llvm/test/Transforms/Inline/inline-stringops.ll b/llvm/test/Transforms/Inline/inline-stringops.ll index 980af7f7f91807..d96750e3db32e4 100644 --- a/llvm/test/Transforms/Inline/inline-stringops.ll +++ b/llvm/test/Transforms/Inline/inline-stringops.ll @@ -19,8 +19,7 @@ entry: } ; Unmarked caller with its own memset inlining an opted-in callee: -; Caller inherits the attribute, so both the inlined memset and caller's own memset -; become subject to the inline-stringops policy. +; Caller inherits the attribute via setOR<InlineMemsetAttr>. define void @caller_with_own_memset(ptr %p1, ptr %p2) { ; CHECK-LABEL: define void @caller_with_own_memset( ; CHECK-SAME: ptr [[P1:%.*]], ptr [[P2:%.*]]) #[[ATTR0]] { @@ -52,29 +51,46 @@ entry: ret void } -; Inlining callee with "memcpy" into caller with "memset" should union attributes. -define internal void @callee_memcpy(ptr %dst, ptr %src) #1 { +; Unmarked callee inlined into unmarked caller: neither has attribute. +define internal void @callee_unmarked(ptr %p) { entry: - call void @llvm.memcpy.p0.p0.i64(ptr align 1 %dst, ptr align 1 %src, i64 64, i1 false) + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 64, i1 false) ret void } -define void @caller_union(ptr %dst, ptr %src) #0 { -; CHECK-LABEL: define void @caller_union( -; CHECK-SAME: ptr [[DST:%.*]], ptr [[SRC:%.*]]) #[[ATTR1:[0-9]+]] { +define void @caller_unmarked_callee(ptr %p) { +; CHECK-LABEL: define void @caller_unmarked_callee( +; CHECK-SAME: ptr [[P:%.*]]) { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 64, i1 false) +; CHECK-NEXT: ret void +entry: + call void @callee_unmarked(ptr %p) + ret void +} + +; Callee with "inline-memset"="false" inlined into unmarked caller: +; Caller must NOT inherit the attribute. +define internal void @callee_false_memset(ptr %p) #1 { +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 128, i1 false) + ret void +} + +define void @caller_false_callee(ptr %p) { +; CHECK-LABEL: define void @caller_false_callee( +; CHECK-SAME: ptr [[P:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[DST]], ptr align 1 [[SRC]], i64 64, i1 false) +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 128, i1 false) ; CHECK-NEXT: ret void entry: - call void @callee_memcpy(ptr %dst, ptr %src) + call void @callee_false_memset(ptr %p) ret void } declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -declare void @llvm.memcpy.p0.p0.i64(ptr nocapture writeonly, ptr nocapture readonly, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } -attributes #1 = { "inline-stringops"="memcpy" } +attributes #0 = { "inline-memset"="true" } +attributes #1 = { "inline-memset"="false" } -; CHECK-DAG: attributes #[[ATTR0]] = { "inline-stringops"="memset" } -; CHECK-DAG: attributes #[[ATTR1]] = { "inline-stringops"="memset,memcpy" } +; CHECK: attributes #[[ATTR0]] = { "inline-memset"="true" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll index f4beae0091e9a4..5b266c84a5dc7f 100644 --- a/llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/AArch64/inline-stringops-memset.ll @@ -1,6 +1,6 @@ ; RUN: opt -mtriple=aarch64-unknown-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s -; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +; Small constant (32 bytes) with "inline-memset" should convert to llvm.memset.inline define void @optin_small_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_small_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { @@ -12,7 +12,7 @@ entry: ret void } -; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +; Large constant (512 bytes) with "inline-memset" should expand to loop define void @optin_large_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_large_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { @@ -29,7 +29,7 @@ entry: ret void } -; Variable length with "inline-stringops"="memset" should expand to loop +; Variable length with "inline-memset" should expand to loop define void @optin_var(ptr %p, i64 %len) #0 { ; CHECK-LABEL: define void @optin_var( ; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { @@ -83,7 +83,7 @@ entry: ret void } -; Function with "inline-stringops"="memcpy" should NOT transform memset +; Function with unrelated attribute should NOT transform memset define void @unrelated_optin(ptr %p) #1 { ; CHECK-LABEL: define void @unrelated_optin( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR1:[0-9]+]] { @@ -95,24 +95,10 @@ entry: ret void } -; Function with multiple stringops "memset,memcpy" should transform memset -define void @multi_optin(ptr %p) #2 { -; CHECK-LABEL: define void @multi_optin( -; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR2:[0-9]+]] { -; CHECK-NEXT: entry: -; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) -; CHECK-NEXT: ret void -entry: - call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) - ret void -} - declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } -attributes #1 = { "inline-stringops"="memcpy" } -attributes #2 = { "inline-stringops"="memset,memcpy" } +attributes #0 = { "inline-memset"="true" } +attributes #1 = { "no-jump-tables"="true" } -; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } -; CHECK: attributes #[[ATTR1]] = { "inline-stringops"="memcpy" } -; CHECK: attributes #[[ATTR2]] = { "inline-stringops"="memset,memcpy" } +; CHECK: attributes #[[ATTR0]] = { "inline-memset"="true" } +; CHECK: attributes #[[ATTR1]] = { "no-jump-tables"="true" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll index f2f8857240b341..a6b42d1b0631ab 100644 --- a/llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/PowerPC/inline-stringops-memset.ll @@ -1,6 +1,6 @@ ; RUN: opt -mtriple=powerpc64le-unknown-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s -; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +; Small constant (32 bytes) with "inline-memset" should convert to llvm.memset.inline define void @optin_small_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_small_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { @@ -12,7 +12,7 @@ entry: ret void } -; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +; Large constant (512 bytes) with "inline-memset" should expand to loop define void @optin_large_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_large_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { @@ -29,7 +29,7 @@ entry: ret void } -; Variable length with "inline-stringops"="memset" should expand to loop +; Variable length with "inline-memset" should expand to loop define void @optin_var(ptr %p, i64 %len) #0 { ; CHECK-LABEL: define void @optin_var( ; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { @@ -85,6 +85,6 @@ entry: declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } +attributes #0 = { "inline-memset"="true" } -; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } +; CHECK: attributes #[[ATTR0]] = { "inline-memset"="true" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll index 603387f27030c5..6a7a3b032ce69a 100644 --- a/llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/RISCV/inline-stringops-memset.ll @@ -1,6 +1,6 @@ ; RUN: opt -mtriple=riscv64-unknown-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s -; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +; Small constant (32 bytes) with "inline-memset" should convert to llvm.memset.inline define void @optin_small_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_small_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { @@ -12,7 +12,7 @@ entry: ret void } -; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +; Large constant (512 bytes) with "inline-memset" should expand to loop define void @optin_large_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_large_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { @@ -29,7 +29,7 @@ entry: ret void } -; Variable length with "inline-stringops"="memset" should expand to loop +; Variable length with "inline-memset" should expand to loop define void @optin_var(ptr %p, i64 %len) #0 { ; CHECK-LABEL: define void @optin_var( ; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { @@ -85,6 +85,6 @@ entry: declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } +attributes #0 = { "inline-memset"="true" } -; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } +; CHECK: attributes #[[ATTR0]] = { "inline-memset"="true" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll index 0486257ff3b9d7..09bffbdd66a478 100644 --- a/llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/X86/inline-stringops-memset.ll @@ -1,7 +1,7 @@ ; RUN: opt -mtriple=x86_64-pc-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s ; RUN: opt -mtriple=i686-pc-linux-gnu -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s -; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +; Small constant (32 bytes) with "inline-memset" should convert to llvm.memset.inline define void @optin_small_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_small_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { @@ -13,7 +13,7 @@ entry: ret void } -; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +; Large constant (512 bytes) with "inline-memset" should expand to loop define void @optin_large_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_large_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { @@ -30,7 +30,7 @@ entry: ret void } -; Variable length with "inline-stringops"="memset" should expand to loop +; Variable length with "inline-memset" should expand to loop define void @optin_var(ptr %p, i64 %len) #0 { ; CHECK-LABEL: define void @optin_var( ; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { @@ -84,7 +84,7 @@ entry: ret void } -; Function with "inline-stringops"="memcpy" should NOT transform memset +; Function with unrelated attribute should NOT transform memset define void @unrelated_optin(ptr %p) #1 { ; CHECK-LABEL: define void @unrelated_optin( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR1:[0-9]+]] { @@ -96,24 +96,10 @@ entry: ret void } -; Function with multiple stringops "memset,memcpy" should transform memset -define void @multi_optin(ptr %p) #2 { -; CHECK-LABEL: define void @multi_optin( -; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR2:[0-9]+]] { -; CHECK-NEXT: entry: -; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 1 [[P]], i8 0, i64 32, i1 false) -; CHECK-NEXT: ret void -entry: - call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 32, i1 false) - ret void -} - declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } -attributes #1 = { "inline-stringops"="memcpy" } -attributes #2 = { "inline-stringops"="memset,memcpy" } +attributes #0 = { "inline-memset"="true" } +attributes #1 = { "no-jump-tables"="true" } -; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } -; CHECK: attributes #[[ATTR1]] = { "inline-stringops"="memcpy" } -; CHECK: attributes #[[ATTR2]] = { "inline-stringops"="memset,memcpy" } +; CHECK: attributes #[[ATTR0]] = { "inline-memset"="true" } +; CHECK: attributes #[[ATTR1]] = { "no-jump-tables"="true" } diff --git a/llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll b/llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll index b5f672499320b9..b3d21ff2d8f2f8 100644 --- a/llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll +++ b/llvm/test/Transforms/PreISelIntrinsicLowering/inline-stringops-memset.ll @@ -1,6 +1,6 @@ ; RUN: opt -passes=pre-isel-intrinsic-lowering -S -o - %s | FileCheck %s -; Small constant (32 bytes) with "inline-stringops"="memset" should convert to llvm.memset.inline +; Small constant (32 bytes) with "inline-memset" should convert to llvm.memset.inline define void @optin_small_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_small_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] { @@ -12,7 +12,7 @@ entry: ret void } -; Large constant (512 bytes) with "inline-stringops"="memset" should expand to loop +; Large constant (512 bytes) with "inline-memset" should expand to loop define void @optin_large_const(ptr %p) #0 { ; CHECK-LABEL: define void @optin_large_const( ; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] { @@ -29,7 +29,7 @@ entry: ret void } -; Variable length with "inline-stringops"="memset" should expand to loop +; Variable length with "inline-memset" should expand to loop define void @optin_var(ptr %p, i64 %len) #0 { ; CHECK-LABEL: define void @optin_var( ; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) #[[ATTR0]] { @@ -83,8 +83,22 @@ entry: ret void } +; Function with "inline-memset"="false" should NOT transform memset +define void @optout_explicit_false(ptr %p) #1 { +; CHECK-LABEL: define void @optout_explicit_false( +; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[P]], i8 0, i64 512, i1 false) +; CHECK-NEXT: ret void +entry: + call void @llvm.memset.p0.i64(ptr align 1 %p, i8 0, i64 512, i1 false) + ret void +} + declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) -attributes #0 = { "inline-stringops"="memset" } +attributes #0 = { "inline-memset"="true" } +attributes #1 = { "inline-memset"="false" } -; CHECK: attributes #[[ATTR0]] = { "inline-stringops"="memset" } +; CHECK: attributes #[[ATTR0]] = { "inline-memset"="true" } +; CHECK: attributes #[[ATTR1]] = { "inline-memset"="false" } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
