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

Reply via email to