https://github.com/nikic updated 
https://github.com/llvm/llvm-project/pull/204548

>From 15cc10f0523b9c222d03a049ce3ca85e1daf50b2 Mon Sep 17 00:00:00 2001
From: Nikita Popov <[email protected]>
Date: Wed, 17 Jun 2026 15:15:56 +0200
Subject: [PATCH 1/4] [IR] Explicitly specify target feature for module asm

Support specifying additional properties on module-level inline
assembly. In particular, the target features and target CPU can
now be specified as follows:

    module asm(target_features="+foo", target_cpu="bar")
        "asm line 1"
        "asm line 2"

There may be multiple module inline assembly blocks with different
properties.

This is intended to fix the long standing issue where in LTO
scenarios we don't know what target features to use for parsing
the module-level inline assembly. Now they can be faithfully
preserved, even when merging inline assembly from different
modules with different features.
---
 clang/lib/CodeGen/CGObjCMac.cpp               |  6 +-
 clang/lib/CodeGen/CodeGenModule.cpp           |  6 +-
 clang/test/CodeGen/2006-01-23-FileScopeAsm.c  | 11 +--
 clang/test/CodeGen/asm.c                      |  7 +-
 clang/test/CodeGen/asm_incbin.c               |  3 +-
 clang/test/CodeGen/cfi-salt.c                 | 11 +--
 clang/test/CodeGen/kcfi.c                     |  7 +-
 clang/test/CodeGenCUDA/filter-decl.cu         |  5 +-
 clang/test/CodeGenCXX/gnu-asm-constexpr.cpp   |  3 +-
 clang/test/CodeGenObjC/category-class.m       |  7 +-
 clang/test/CodeGenObjC/objc-runtime-name.m    |  7 +-
 clang/test/Frontend/ast-codegen.c             |  3 +-
 clang/test/Modules/codegen.test               |  3 +-
 llvm/include/llvm/AsmParser/LLToken.h         |  2 +
 llvm/include/llvm/Bitcode/LLVMBitCodes.h      |  3 +
 llvm/include/llvm/IR/Module.h                 | 79 +++++++++++++---
 llvm/lib/AsmParser/LLLexer.cpp                |  2 +
 llvm/lib/AsmParser/LLParser.cpp               | 37 +++++++-
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp     | 18 +++-
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     | 13 ++-
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp    | 25 ++++--
 llvm/lib/IR/AsmWriter.cpp                     | 47 +++++++---
 llvm/lib/IR/Core.cpp                          | 13 ++-
 llvm/lib/LTO/LTO.cpp                          |  4 +-
 llvm/lib/Linker/IRMover.cpp                   |  9 +-
 llvm/lib/Object/ModuleSymbolTable.cpp         | 90 ++++++++++---------
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |  2 +-
 llvm/lib/Transforms/IPO/ExtractGV.cpp         |  2 +-
 .../Transforms/IPO/ThinLTOBitcodeWriter.cpp   |  2 +-
 .../Instrumentation/DataFlowSanitizer.cpp     | 26 +++---
 llvm/lib/Transforms/Utils/SplitModule.cpp     |  2 +-
 llvm/test/Assembler/module-asm-invalid.ll     | 25 ++++++
 llvm/test/Bitcode/compatibility-3.6.ll        |  3 +-
 llvm/test/Bitcode/compatibility-3.7.ll        |  3 +-
 llvm/test/Bitcode/compatibility-3.8.ll        |  3 +-
 llvm/test/Bitcode/compatibility-3.9.ll        |  3 +-
 llvm/test/Bitcode/compatibility-4.0.ll        |  3 +-
 llvm/test/Bitcode/compatibility-5.0.ll        |  3 +-
 llvm/test/Bitcode/compatibility-6.0.ll        |  3 +-
 llvm/test/Bitcode/compatibility.ll            |  3 +-
 llvm/test/Bitcode/highLevelStructure.3.2.ll   |  3 +-
 llvm/test/Bitcode/module-asm.ll               | 29 ++++++
 .../test/CodeGen/RISCV/module-asm-features.ll | 12 +++
 .../DataFlowSanitizer/prefix-rename.ll        |  3 +-
 llvm/test/LTO/RISCV/module-asm.ll             | 18 ++++
 llvm/test/LTO/X86/inline-asm-lto-discard.ll   | 24 ++---
 llvm/test/Linker/Inputs/module-asm.ll         |  2 +
 .../link-arm-and-thumb-module-inline-asm.ll   | 17 ++--
 llvm/test/Linker/module-asm.ll                |  9 ++
 llvm/test/ThinLTO/X86/import-symver.ll        |  3 +-
 .../LowerTypeTests/export-symver.ll           |  3 +-
 .../cfi-icall-static-inline-asm.ll            |  3 +-
 .../ThinLTOBitcodeWriter/x86/module-asm.ll    |  3 +-
 .../llvm-reduce/deltas/ReduceModuleData.cpp   |  2 +-
 mlir/lib/Target/LLVMIR/ModuleImport.cpp       | 11 ++-
 mlir/test/Target/LLVMIR/module-asm.mlir       |  5 +-
 56 files changed, 474 insertions(+), 177 deletions(-)
 create mode 100644 llvm/test/Assembler/module-asm-invalid.ll
 create mode 100644 llvm/test/Bitcode/module-asm.ll
 create mode 100644 llvm/test/CodeGen/RISCV/module-asm-features.ll
 create mode 100644 llvm/test/LTO/RISCV/module-asm.ll
 create mode 100644 llvm/test/Linker/Inputs/module-asm.ll
 create mode 100644 llvm/test/Linker/module-asm.ll

diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp
index 69c5e88f3c768..e518b875f44bb 100644
--- a/clang/lib/CodeGen/CGObjCMac.cpp
+++ b/clang/lib/CodeGen/CGObjCMac.cpp
@@ -6610,10 +6610,6 @@ void CGObjCMac::FinishModule() {
   if ((!LazySymbols.empty() || !DefinedSymbols.empty()) &&
       CGM.getTriple().isOSBinFormatMachO()) {
     SmallString<256> Asm;
-    Asm += CGM.getModule().getModuleInlineAsm();
-    if (!Asm.empty() && Asm.back() != '\n')
-      Asm += '\n';
-
     llvm::raw_svector_ostream OS(Asm);
     for (const auto *Sym : DefinedSymbols)
       OS << "\t.objc_class_name_" << Sym->getName() << "=0\n"
@@ -6624,7 +6620,7 @@ void CGObjCMac::FinishModule() {
       OS << "\t.objc_category_name_" << Category << "=0\n"
          << "\t.globl .objc_category_name_" << Category << "\n";
 
-    CGM.getModule().setModuleInlineAsm(OS.str());
+    CGM.getModule().appendModuleInlineAsm(OS.str());
   }
 }
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp 
b/clang/lib/CodeGen/CodeGenModule.cpp
index fec18acd46998..3616846b0fb01 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -8054,7 +8054,11 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
     if (LangOpts.SYCLIsDevice)
       break;
     auto *AD = cast<FileScopeAsmDecl>(D);
-    getModule().appendModuleInlineAsm(AD->getAsmString());
+
+    const TargetOptions &TargetOpts = getTarget().getTargetOpts();
+    std::string Features = llvm::join(TargetOpts.Features, ",");
+    getModule().appendModuleInlineAsm(llvm::Module::GlobalAsmFragment(
+        AD->getAsmString(), Features, TargetOpts.CPU));
     break;
   }
 
diff --git a/clang/test/CodeGen/2006-01-23-FileScopeAsm.c 
b/clang/test/CodeGen/2006-01-23-FileScopeAsm.c
index 877da4fc669e3..eee40654240f7 100644
--- a/clang/test/CodeGen/2006-01-23-FileScopeAsm.c
+++ b/clang/test/CodeGen/2006-01-23-FileScopeAsm.c
@@ -1,13 +1,14 @@
 // UNSUPPORTED: target={{.*}}-zos{{.*}}
 // RUN: %clang_cc1 %s -emit-llvm -o - | FileCheck %s
 
-// CHECK: module asm "foo1"
+// CHECK: module asm(target_features="{{.*}}")
+// CHECK-NEXT: "foo1"
 __asm__ ("foo1");
-// CHECK: module asm "foo2"
+// CHECK-NEXT: "foo2"
 __asm__ ("foo2");
-// CHECK: module asm "foo3"
+// CHECK-NEXT: "foo3"
 __asm__ ("foo3");
-// CHECK: module asm "foo4"
+// CHECK-NEXT: "foo4"
 __asm__ ("foo4");
-// CHECK: module asm "foo5"
+// CHECK-NEXT: "foo5"
 __asm__ ("foo5");
diff --git a/clang/test/CodeGen/asm.c b/clang/test/CodeGen/asm.c
index d7465b22fbbf6..bc54eb017ad12 100644
--- a/clang/test/CodeGen/asm.c
+++ b/clang/test/CodeGen/asm.c
@@ -2,9 +2,10 @@
 
 // PR10415:
 //
-// CHECK:      module asm "foo1"
-// CHECK-NEXT: module asm "foo2"
-// CHECK-NEXT: module asm "foo3"
+// CHECK: module asm(target_features="{{.*}}")
+// CHECK-NEXT: "foo1"
+// CHECK-NEXT: "foo2"
+// CHECK-NEXT: "foo3"
 __asm__ ("foo1");
 __asm__ ("foo2");
 __asm__ ("foo3");
diff --git a/clang/test/CodeGen/asm_incbin.c b/clang/test/CodeGen/asm_incbin.c
index 6234eea1d6738..bbc5d3af55ea2 100644
--- a/clang/test/CodeGen/asm_incbin.c
+++ b/clang/test/CodeGen/asm_incbin.c
@@ -6,4 +6,5 @@ asm(".incbin \"foo.h\"");
 // RUN: cd %t
 // RUN: %clang -c -emit-llvm %t/tu.c -o %t/tu.ll
 // RUN: llvm-dis %t/tu.ll -o - | FileCheck %s
-// CHECK: module asm ".incbin \22foo.h\22"
+// CHECK: module asm
+// CHECK-NEXT: ".incbin \22foo.h\22"
diff --git a/clang/test/CodeGen/cfi-salt.c b/clang/test/CodeGen/cfi-salt.c
index 2a90dc31d939b..18156dc014280 100644
--- a/clang/test/CodeGen/cfi-salt.c
+++ b/clang/test/CodeGen/cfi-salt.c
@@ -26,13 +26,14 @@ typedef unsigned int (*ufn_t)(void);
 typedef unsigned int (* __cfi_salt ufn_salt_t)(void);
 
 /// Must emit __kcfi_typeid symbols for address-taken function declarations
-// CHECK: module asm ".weak __kcfi_typeid_[[F4:[a-zA-Z0-9_]+]]"
-// CHECK: module asm ".set __kcfi_typeid_[[F4]], {{[0-9]+}} /* 
[[#%d,LOW_SODIUM_HASH:]] */"
-// CHECK: module asm ".weak __kcfi_typeid_[[F4_SALT:[a-zA-Z0-9_]+]]"
-// CHECK: module asm ".set __kcfi_typeid_[[F4_SALT]], {{[0-9]+}} /* 
[[#%d,ASM_SALTY_HASH:]] */"
+// CHECK: module asm
+// CHECK-NEXT: ".weak __kcfi_typeid_[[F4:[a-zA-Z0-9_]+]]"
+// CHECK-NEXT: ".set __kcfi_typeid_[[F4]], {{[0-9]+}} /* 
[[#%d,LOW_SODIUM_HASH:]] */"
+// CHECK-NEXT: ".weak __kcfi_typeid_[[F4_SALT:[a-zA-Z0-9_]+]]"
+// CHECK-NEXT: ".set __kcfi_typeid_[[F4_SALT]], {{[0-9]+}} /* 
[[#%d,ASM_SALTY_HASH:]] */"
 
 /// Must not __kcfi_typeid symbols for non-address-taken declarations
-// CHECK-NOT: module asm ".weak __kcfi_typeid_f6"
+// CHECK-NOT: ".weak __kcfi_typeid_f6"
 
 int f1(void);
 int f1_salt(void) __cfi_salt;
diff --git a/clang/test/CodeGen/kcfi.c b/clang/test/CodeGen/kcfi.c
index da32859dd3d88..f08cf7635fb5a 100644
--- a/clang/test/CodeGen/kcfi.c
+++ b/clang/test/CodeGen/kcfi.c
@@ -6,10 +6,11 @@
 #endif
 
 /// Must emit __kcfi_typeid symbols for address-taken function declarations
-// CHECK: module asm ".weak __kcfi_typeid_[[F4:[a-zA-Z0-9_]+]]"
-// CHECK: module asm ".set __kcfi_typeid_[[F4]], {{[0-9]+}} /* [[#%d,HASH:]] 
*/"
+// CHECK: module asm
+// CHECK-NEXT: ".weak __kcfi_typeid_[[F4:[a-zA-Z0-9_]+]]"
+// CHECK-NEXT: ".set __kcfi_typeid_[[F4]], {{[0-9]+}} /* [[#%d,HASH:]] */"
 /// Must not __kcfi_typeid symbols for non-address-taken declarations
-// CHECK-NOT: module asm ".weak __kcfi_typeid_{{f6|_Z2f6v}}"
+// CHECK-NOT: ".weak __kcfi_typeid_{{f6|_Z2f6v}}"
 
 // C: @ifunc1 = ifunc i32 (i32), ptr @resolver1
 // C: @ifunc2 = ifunc i64 (i64), ptr @resolver2
diff --git a/clang/test/CodeGenCUDA/filter-decl.cu 
b/clang/test/CodeGenCUDA/filter-decl.cu
index 02dacd0ad8ef4..f87d62a56631e 100644
--- a/clang/test/CodeGenCUDA/filter-decl.cu
+++ b/clang/test/CodeGenCUDA/filter-decl.cu
@@ -5,8 +5,9 @@
 
 // This has to be at the top of the file as that's where file-scope
 // asm ends up.
-// CHECK-HOST: module asm "file scope asm is host only"
-// CHECK-DEVICE-NOT: module asm "file scope asm is host only"
+// CHECK-HOST: module asm
+// CHECK-HOST-NEXT: "file scope asm is host only"
+// CHECK-DEVICE-NOT: "file scope asm is host only"
 __asm__("file scope asm is host only");
 
 // CHECK-HOST: constantdata = internal global
diff --git a/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp 
b/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp
index 175a3b7bc588c..b7e0ba389c7dd 100644
--- a/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp
+++ b/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp
@@ -19,7 +19,8 @@ struct string_view {
 namespace GH143242 {
     constexpr string_view code2 = R"(nop; nop; nop; nop)";
     asm((code2));
-    // CHECK: module asm "nop; nop; nop; nop"
+    // CHECK: module asm(target_features="{{.*}}")
+    // CHECK-NEXT: "nop; nop; nop; nop"
 }
 
 int func() {return 0;};
diff --git a/clang/test/CodeGenObjC/category-class.m 
b/clang/test/CodeGenObjC/category-class.m
index 92fd36cbe4066..febe94ce5b9f5 100644
--- a/clang/test/CodeGenObjC/category-class.m
+++ b/clang/test/CodeGenObjC/category-class.m
@@ -1,9 +1,10 @@
 // RUN: %clang_cc1 -triple i386-apple-darwin9 
-fobjc-runtime=macosx-fragile-10.5  -emit-llvm -o - %s | FileCheck %s
 // PR7431
 
-// CHECK: module asm "\09.lazy_reference .objc_class_name_A"
-// CHECK: module asm "\09.objc_category_name_A_foo=0"
-// CHECK: module asm "\09.globl .objc_category_name_A_foo"
+// CHECK: module asm
+// CHECK-NEXT: "\09.lazy_reference .objc_class_name_A"
+// CHECK-NEXT: "\09.objc_category_name_A_foo=0"
+// CHECK-NEXT: "\09.globl .objc_category_name_A_foo"
 
 @interface A
 @end
diff --git a/clang/test/CodeGenObjC/objc-runtime-name.m 
b/clang/test/CodeGenObjC/objc-runtime-name.m
index c838b8c2c6f1a..a7f59caced390 100644
--- a/clang/test/CodeGenObjC/objc-runtime-name.m
+++ b/clang/test/CodeGenObjC/objc-runtime-name.m
@@ -3,9 +3,10 @@
 // Check that the runtime name is emitted and used instead of the class
 // identifier.
 
-// CHECK: module asm {{.*}}objc_class_name_XYZ=0
-// CHECK: module asm {{.*}}globl .objc_class_name_XYZ
-// CHECK: module asm {{.*}}lazy_reference .objc_class_name_XYZ
+// CHECK: module asm
+// CHECK-NEXT: {{.*}}objc_class_name_XYZ=0
+// CHECK-NEXT: {{.*}}globl .objc_class_name_XYZ
+// CHECK-NEXT: {{.*}}lazy_reference .objc_class_name_XYZ
 
 // CHECK: @[[OBJC_CLASS_NAME:.*]] = private unnamed_addr constant [4 x i8] 
c"XYZ{{.*}}, section "__TEXT,__cstring,cstring_literals",
 // CHECK: = private global {{.*}} @[[OBJC_CLASS_NAME]], section 
"__OBJC,__cls_refs,literal_pointers,no_dead_strip"
diff --git a/clang/test/Frontend/ast-codegen.c 
b/clang/test/Frontend/ast-codegen.c
index 2e2e3d360dd17..bf35da069772d 100644
--- a/clang/test/Frontend/ast-codegen.c
+++ b/clang/test/Frontend/ast-codegen.c
@@ -2,7 +2,8 @@
 // RUN: %clang -target i386-unknown-unknown -emit-ast -o %t.ast %s
 // RUN: %clang -target i386-unknown-unknown -emit-llvm -S -o - %t.ast | 
FileCheck %s
 
-// CHECK: module asm "foo"
+// CHECK: module asm(target_features="{{.*}}")
+// CHECK-NEXT: "foo"
 __asm__("foo");
 
 // CHECK: @g0 = dso_local global i32 0, align 4
diff --git a/clang/test/Modules/codegen.test b/clang/test/Modules/codegen.test
index 0af630a754805..5cc5b3c1cda2a 100644
--- a/clang/test/Modules/codegen.test
+++ b/clang/test/Modules/codegen.test
@@ -18,7 +18,8 @@ was implemented to workaround/support the initialization of 
iostreams
 (implemented as a namespace scope static in the header - only to be provided
 when that specific header is included in the program).
 
-BOTH: module asm "narf"
+BOTH: module asm
+BOTH-NEXT: "narf"
 
 FOO: $_Z2f1PKcz = comdat any
 FOO: $_ZN13implicit_dtorD1Ev = comdat any
diff --git a/llvm/include/llvm/AsmParser/LLToken.h 
b/llvm/include/llvm/AsmParser/LLToken.h
index d2766a05ce9ba..95d3e67ff3849 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -86,6 +86,8 @@ enum Kind {
   kw_musttail,
   kw_notail,
   kw_target,
+  kw_target_cpu,
+  kw_target_features,
   kw_triple,
   kw_source_filename,
   kw_unwind,
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h 
b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 95787c595dff7..7b05d2c161188 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -120,6 +120,9 @@ enum ModuleCodes {
 
   // IFUNC: [ifunc value type, addrspace, resolver val#, linkage, visibility]
   MODULE_CODE_IFUNC = 18,
+
+  MODULE_CODE_ASM_TARGET_FEATURES = 19, // [strchr x N]
+  MODULE_CODE_ASM_TARGET_CPU = 20,      // [strchr x N]
 };
 
 /// PARAMATTR blocks have code for defining a parameter attribute set.
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index 4f7e33969f16f..8a873a361ff06 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -169,6 +169,28 @@ class LLVM_ABI Module {
         : Behavior(B), Key(K), Val(V) {}
   };
 
+  struct GlobalAsmFragment {
+    std::string Asm;
+    std::string TargetFeatures;
+    std::string TargetCPU;
+
+    GlobalAsmFragment(StringRef Asm) : GlobalAsmFragment(Asm.str()) {}
+    GlobalAsmFragment(std::string AsmArg, std::string TargetFeatures = "",
+                      std::string TargetCPU = "")
+        : Asm(std::move(AsmArg)), TargetFeatures(std::move(TargetFeatures)),
+          TargetCPU(std::move(TargetCPU)) {
+      if (!Asm.empty() && Asm.back() != '\n')
+        Asm += '\n';
+    }
+
+    bool empty() const { return Asm.empty(); }
+
+    bool hasSameProperties(const GlobalAsmFragment &Other) const {
+      return TargetFeatures == Other.TargetFeatures &&
+             TargetCPU == Other.TargetCPU;
+    }
+  };
+
 /// @}
 /// @name Member Variables
 /// @{
@@ -180,7 +202,8 @@ class LLVM_ABI Module {
   AliasListType AliasList;        ///< The Aliases in the module
   IFuncListType IFuncList;        ///< The IFuncs in the module
   NamedMDListType NamedMDList;    ///< The named metadata in the module
-  std::string GlobalScopeAsm;     ///< Inline Asm at global scope.
+  /// Inline Asm at the global scope.
+  SmallVector<GlobalAsmFragment, 0> GlobalScopeAsm;
   std::unique_ptr<ValueSymbolTable> ValSymTab; ///< Symbol table for values
   ComdatSymTabType ComdatSymTab;  ///< Symbol table for COMDATs
   std::unique_ptr<MemoryBuffer>
@@ -287,8 +310,17 @@ class LLVM_ABI Module {
   LLVMContext &getContext() const { return Context; }
 
   /// Get any module-scope inline assembly blocks.
-  /// @returns a string containing the module-scope inline assembly blocks.
-  const std::string &getModuleInlineAsm() const { return GlobalScopeAsm; }
+  ArrayRef<GlobalAsmFragment> getModuleInlineAsm() const {
+    return GlobalScopeAsm;
+  }
+
+  /// Get any module-scope inline assembly blocks.
+  MutableArrayRef<GlobalAsmFragment> getModuleInlineAsm() {
+    return GlobalScopeAsm;
+  }
+
+  /// Return whether there is any module-scope inline assembly.
+  bool hasModuleInlineAsm() const { return !GlobalScopeAsm.empty(); }
 
   /// Get a RandomNumberGenerator salted for use with this module. The
   /// RNG can be seeded via -rng-seed=<uint64> and is salted with the
@@ -325,20 +357,45 @@ class LLVM_ABI Module {
   /// Set the target triple.
   void setTargetTriple(Triple T) { TargetTriple = std::move(T); }
 
+  void removeModuleInlineAsm() { GlobalScopeAsm.clear(); }
+
   /// Set the module-scope inline assembly blocks.
   /// A trailing newline is added if the input doesn't have one.
-  void setModuleInlineAsm(StringRef Asm) {
-    GlobalScopeAsm = std::string(Asm);
-    if (!GlobalScopeAsm.empty() && GlobalScopeAsm.back() != '\n')
-      GlobalScopeAsm += '\n';
+  void setModuleInlineAsm(GlobalAsmFragment Fragment) {
+    GlobalScopeAsm.clear();
+    appendModuleInlineAsm(std::move(Fragment));
+  }
+
+  void setModuleInlineAsm(ArrayRef<GlobalAsmFragment> Fragments) {
+    GlobalScopeAsm.clear();
+    append_range(GlobalScopeAsm, Fragments);
   }
 
   /// Append to the module-scope inline assembly blocks.
   /// A trailing newline is added if the input doesn't have one.
-  void appendModuleInlineAsm(StringRef Asm) {
-    GlobalScopeAsm += Asm;
-    if (!GlobalScopeAsm.empty() && GlobalScopeAsm.back() != '\n')
-      GlobalScopeAsm += '\n';
+  void appendModuleInlineAsm(GlobalAsmFragment Fragment) {
+    if (Fragment.empty())
+      return;
+
+    if (!GlobalScopeAsm.empty() &&
+        GlobalScopeAsm.back().hasSameProperties(Fragment)) {
+      GlobalScopeAsm.back().Asm += Fragment.Asm;
+    } else {
+      GlobalScopeAsm.emplace_back(std::move(Fragment));
+    }
+  }
+
+  /// Prepend to the module-scope inline assembly blocks.
+  void prependModuleInlineAsm(GlobalAsmFragment Fragment) {
+    if (Fragment.empty())
+      return;
+
+    if (!GlobalScopeAsm.empty() &&
+        GlobalScopeAsm.front().hasSameProperties(Fragment)) {
+      GlobalScopeAsm.front().Asm.insert(0, Fragment.Asm);
+    } else {
+      GlobalScopeAsm.insert(GlobalScopeAsm.begin(), std::move(Fragment));
+    }
   }
 
 /// @}
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index 069a180056488..a978867e1ad14 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -595,6 +595,8 @@ lltok::Kind LLLexer::LexIdentifier() {
   KEYWORD(tail);
   KEYWORD(musttail);
   KEYWORD(notail);
+  KEYWORD(target_cpu);
+  KEYWORD(target_features);
   KEYWORD(target);
   KEYWORD(triple);
   KEYWORD(source_filename);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 75e6add0ec76f..cfd52c98d5b88 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -641,16 +641,47 @@ bool LLParser::parseTopLevelEntities() {
 
 /// toplevelentity
 ///   ::= 'module' 'asm' STRINGCONSTANT
+///   ::= 'module' 'asm' '(' 'target_features' '=' STRINGCONSTANT ','
+///                          'target_cpu' '=' STRINGCONSTANT ')'
+///                      STRINGCONSTANT
 bool LLParser::parseModuleAsm() {
   assert(Lex.getKind() == lltok::kw_module);
   Lex.Lex();
 
   std::string AsmStr;
-  if (parseToken(lltok::kw_asm, "expected 'module asm'") ||
-      parseStringConstant(AsmStr))
+  if (parseToken(lltok::kw_asm, "expected 'module asm'"))
     return true;
 
-  M->appendModuleInlineAsm(AsmStr);
+  std::string TargetFeatures, TargetCPU;
+  if (EatIfPresent(lltok::lparen)) {
+    while (true) {
+      if (EatIfPresent(lltok::kw_target_features)) {
+        if (parseToken(lltok::equal, "expected '='") ||
+            parseStringConstant(TargetFeatures))
+          return true;
+      } else if (EatIfPresent(lltok::kw_target_cpu)) {
+        if (parseToken(lltok::equal, "expected '='") ||
+            parseStringConstant(TargetCPU))
+          return true;
+      } else {
+        return error(Lex.getLoc(),
+                     "expected one of 'target_features' or 'target_cpu'");
+      }
+      if (EatIfPresent(lltok::rparen))
+        break;
+      if (parseToken(lltok::comma, "expected ',' or ')'"))
+        return true;
+    }
+  }
+
+  do {
+    std::string AsmStrPart;
+    if (parseStringConstant(AsmStrPart))
+      return true;
+    AsmStr += AsmStrPart + "\n";
+  } while (Lex.getKind() == lltok::StringConstant);
+
+  M->appendModuleInlineAsm({AsmStr, TargetFeatures, TargetCPU});
   return false;
 }
 
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp 
b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index c913dd811a2e0..99a76379feb51 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -4579,6 +4579,9 @@ Error BitcodeReader::parseModule(uint64_t ResumeBit,
   // Initialize to the current module's layout string in case none is 
specified.
   std::string TentativeDataLayoutStr = TheModule->getDataLayoutStr();
 
+  // Apply to the following module asm.
+  std::string ModuleAsmTargetFeatures, ModuleAsmTargetCPU;
+
   auto ResolveDataLayout = [&]() -> Error {
     if (ResolvedDataLayout)
       return Error::success();
@@ -4787,11 +4790,24 @@ Error BitcodeReader::parseModule(uint64_t ResumeBit,
         return error("Invalid data layout record");
       break;
     }
+    case bitc::MODULE_CODE_ASM_TARGET_FEATURES:
+      ModuleAsmTargetFeatures.clear();
+      if (convertToString(Record, 0, ModuleAsmTargetFeatures))
+        return error("Invalid asm target features record");
+      break;
+    case bitc::MODULE_CODE_ASM_TARGET_CPU:
+      ModuleAsmTargetCPU.clear();
+      if (convertToString(Record, 0, ModuleAsmTargetCPU))
+        return error("Invalid asm target cpu record");
+      break;
     case bitc::MODULE_CODE_ASM: {  // ASM: [strchr x N]
       std::string S;
       if (convertToString(Record, 0, S))
         return error("Invalid asm record");
-      TheModule->setModuleInlineAsm(S);
+      TheModule->appendModuleInlineAsm(Module::GlobalAsmFragment(
+          S, ModuleAsmTargetFeatures, ModuleAsmTargetCPU));
+      ModuleAsmTargetFeatures.clear();
+      ModuleAsmTargetCPU.clear();
       break;
     }
     case bitc::MODULE_CODE_DEPLIB: {  // DEPLIB: [strchr x N]
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp 
b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index f4857461ca58e..e9620763820f7 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -1546,9 +1546,16 @@ void ModuleBitcodeWriter::writeModuleInfo() {
   const std::string &DL = M.getDataLayoutStr();
   if (!DL.empty())
     writeStringRecord(Stream, bitc::MODULE_CODE_DATALAYOUT, DL, 0 /*TODO*/);
-  if (!M.getModuleInlineAsm().empty())
-    writeStringRecord(Stream, bitc::MODULE_CODE_ASM, M.getModuleInlineAsm(),
-                      0 /*TODO*/);
+
+  for (const Module::GlobalAsmFragment &Frag : M.getModuleInlineAsm()) {
+    if (!Frag.TargetFeatures.empty())
+      writeStringRecord(Stream, bitc::MODULE_CODE_ASM_TARGET_FEATURES,
+                        Frag.TargetFeatures, 0);
+    if (!Frag.TargetCPU.empty())
+      writeStringRecord(Stream, bitc::MODULE_CODE_ASM_TARGET_CPU,
+                        Frag.TargetCPU, 0);
+    writeStringRecord(Stream, bitc::MODULE_CODE_ASM, Frag.Asm, 0 /*TODO*/);
+  }
 
   // Emit information about sections and GC, computing how many there are. Also
   // compute the maximum alignment value.
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp 
b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index aedc4956f1bd8..8049675701c7f 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -110,6 +110,7 @@
 #include "llvm/MC/MCTargetOptions.h"
 #include "llvm/MC/MCValue.h"
 #include "llvm/MC/SectionKind.h"
+#include "llvm/MC/TargetRegistry.h"
 #include "llvm/Object/ELFTypes.h"
 #include "llvm/Pass.h"
 #include "llvm/Remarks/RemarkStreamer.h"
@@ -127,7 +128,6 @@
 #include "llvm/Target/TargetLoweringObjectFile.h"
 #include "llvm/Target/TargetMachine.h"
 #include "llvm/Target/TargetOptions.h"
-#include "llvm/TargetParser/Triple.h"
 #include <algorithm>
 #include <cassert>
 #include <cinttypes>
@@ -610,13 +610,26 @@ bool AsmPrinter::doInitialization(Module &M) {
   BeginGCAssembly(M);
 
   // Emit module-level inline asm if it exists.
-  if (!M.getModuleInlineAsm().empty()) {
+  if (M.hasModuleInlineAsm()) {
     OutStreamer->AddComment("Start of file scope inline assembly");
     OutStreamer->addBlankLine();
-    emitInlineAsm(
-        M.getModuleInlineAsm() + "\n", TM.getMCSubtargetInfo(),
-        TM.Options.MCOptions, nullptr,
-        InlineAsm::AsmDialect(TM.getMCAsmInfo().getAssemblerDialect()));
+    for (const Module::GlobalAsmFragment &Frag : M.getModuleInlineAsm()) {
+      if (!Frag.TargetFeatures.empty() || !Frag.TargetCPU.empty()) {
+        std::unique_ptr<MCSubtargetInfo> AsmSTI(
+            TM.getTarget().createMCSubtargetInfo(
+                TM.getTargetTriple(), Frag.TargetCPU, Frag.TargetFeatures));
+        emitInlineAsm(
+            Frag.Asm + "\n", *AsmSTI, TM.Options.MCOptions, nullptr,
+            InlineAsm::AsmDialect(TM.getMCAsmInfo().getAssemblerDialect()));
+      } else {
+        // If the module asm does not explicitly specify target features,
+        // fall back to default subtargetinfo.
+        emitInlineAsm(
+            Frag.Asm + "\n", TM.getMCSubtargetInfo(), TM.Options.MCOptions,
+            nullptr,
+            InlineAsm::AsmDialect(TM.getMCAsmInfo().getAssemblerDialect()));
+      }
+    }
     OutStreamer->AddComment("End of file scope inline assembly");
     OutStreamer->addBlankLine();
   }
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 52ed28f71f615..9457043f48520 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -3125,21 +3125,42 @@ void AssemblyWriter::printModule(const Module *M) {
   if (!M->getTargetTriple().empty())
     Out << "target triple = \"" << M->getTargetTriple().str() << "\"\n";
 
-  if (!M->getModuleInlineAsm().empty()) {
+  if (M->hasModuleInlineAsm()) {
     Out << '\n';
 
-    // Split the string into lines, to make it easier to read the .ll file.
-    StringRef Asm = M->getModuleInlineAsm();
-    do {
-      StringRef Front;
-      std::tie(Front, Asm) = Asm.split('\n');
-
-      // We found a newline, print the portion of the asm string from the
-      // last newline up to this newline.
-      Out << "module asm \"";
-      printEscapedString(Front, Out);
-      Out << "\"\n";
-    } while (!Asm.empty());
+    for (const Module::GlobalAsmFragment &Frag : M->getModuleInlineAsm()) {
+      Out << "module asm";
+      if (!Frag.TargetFeatures.empty() || !Frag.TargetCPU.empty()) {
+        ListSeparator LS;
+        Out << "(";
+        if (!Frag.TargetFeatures.empty()) {
+          Out << LS;
+          Out << "target_features=\"";
+          printEscapedString(Frag.TargetFeatures, Out);
+          Out << "\"";
+        }
+        if (!Frag.TargetCPU.empty()) {
+          Out << LS;
+          Out << "target_cpu=\"";
+          printEscapedString(Frag.TargetCPU, Out);
+          Out << "\"";
+        }
+        Out << ")";
+      }
+      Out << "\n";
+      // Split the string into lines, to make it easier to read the .ll file.
+      StringRef Asm = Frag.Asm;
+      do {
+        StringRef Front;
+        std::tie(Front, Asm) = Asm.split('\n');
+
+        // We found a newline, print the portion of the asm string from the
+        // last newline up to this newline.
+        Out << "    \"";
+        printEscapedString(Front, Out);
+        Out << "\"\n";
+      } while (!Asm.empty());
+    }
   }
 
   printTypeIdentities();
diff --git a/llvm/lib/IR/Core.cpp b/llvm/lib/IR/Core.cpp
index 6c6c3b5c84ede..a7abd3eed31c3 100644
--- a/llvm/lib/IR/Core.cpp
+++ b/llvm/lib/IR/Core.cpp
@@ -510,7 +510,18 @@ void LLVMAppendModuleInlineAsm(LLVMModuleRef M, const char 
*Asm, size_t Len) {
 }
 
 const char *LLVMGetModuleInlineAsm(LLVMModuleRef M, size_t *Len) {
-  auto &Str = unwrap(M)->getModuleInlineAsm();
+  Module *Mod = unwrap(M);
+  ArrayRef<Module::GlobalAsmFragment> Frags = Mod->getModuleInlineAsm();
+  if (Frags.empty()) {
+    *Len = 0;
+    return nullptr;
+  }
+
+  if (Frags.size() != 1)
+    reportFatalUsageError("LLVMGetModuleInlineAsm is not supported if there is 
"
+                          "more than one module inline assembly fragment");
+
+  auto &Str = Frags.begin()->Asm;
   *Len = Str.length();
   return Str.c_str();
 }
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 6a754f95092bb..b672f4e5fac14 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1112,7 +1112,7 @@ LTO::addRegularLTO(InputFile &Input, 
ArrayRef<SymbolResolution> InputRes,
 
   // Prepend ".lto_discard <sym>, <sym>*" directive to each module inline asm
   // block.
-  if (!M.getModuleInlineAsm().empty()) {
+  if (M.hasModuleInlineAsm()) {
     std::string NewIA = ".lto_discard";
     if (!NonPrevailingAsmSymbols.empty()) {
       // Don't dicard a symbol if there is a live .symver for it.
@@ -1124,7 +1124,7 @@ LTO::addRegularLTO(InputFile &Input, 
ArrayRef<SymbolResolution> InputRes,
       NewIA += " " + llvm::join(NonPrevailingAsmSymbols, ", ");
     }
     NewIA += "\n";
-    M.setModuleInlineAsm(NewIA + M.getModuleInlineAsm());
+    M.prependModuleInlineAsm(NewIA);
   }
 
   assert(MsymI == MsymE);
diff --git a/llvm/lib/Linker/IRMover.cpp b/llvm/lib/Linker/IRMover.cpp
index 239201bde1cec..3b72b412d0b2e 100644
--- a/llvm/lib/Linker/IRMover.cpp
+++ b/llvm/lib/Linker/IRMover.cpp
@@ -1562,8 +1562,11 @@ Error IRLinker::run() {
 
   if (!IsPerformingImport && !SrcM->getModuleInlineAsm().empty()) {
     // Append the module inline asm string.
-    DstM.appendModuleInlineAsm(adjustInlineAsm(SrcM->getModuleInlineAsm(),
-                                               SrcTriple));
+    for (const Module::GlobalAsmFragment &Frag : SrcM->getModuleInlineAsm()) {
+      Module::GlobalAsmFragment NewFrag(Frag);
+      NewFrag.Asm = adjustInlineAsm(NewFrag.Asm, SrcTriple);
+      DstM.appendModuleInlineAsm(std::move(NewFrag));
+    }
   } else if (IsPerformingImport) {
     // Import any symver directives for symbols in DstM.
     ModuleSymbolTable::CollectAsmSymvers(*SrcM,
@@ -1573,7 +1576,7 @@ Error IRLinker::run() {
         S += Name;
         S += ", ";
         S += Alias;
-        DstM.appendModuleInlineAsm(S);
+        DstM.appendModuleInlineAsm(std::string(S));
       }
     });
   }
diff --git a/llvm/lib/Object/ModuleSymbolTable.cpp 
b/llvm/lib/Object/ModuleSymbolTable.cpp
index 1da5fa9c10a0b..b60e35fba1507 100644
--- a/llvm/lib/Object/ModuleSymbolTable.cpp
+++ b/llvm/lib/Object/ModuleSymbolTable.cpp
@@ -72,8 +72,7 @@ initializeRecordStreamer(const Module &M,
   // caused errors in the first run, suppress the second run.
   if (M.getContext().getDiagHandlerPtr()->HasErrors)
     return;
-  StringRef InlineAsm = M.getModuleInlineAsm();
-  if (InlineAsm.empty())
+  if (!M.hasModuleInlineAsm())
     return;
 
   std::string Err;
@@ -90,52 +89,55 @@ initializeRecordStreamer(const Module &M,
   if (!MAI)
     return;
 
-  std::unique_ptr<MCSubtargetInfo> STI(T->createMCSubtargetInfo(TT, "", ""));
-  if (!STI)
-    return;
-
   std::unique_ptr<MCInstrInfo> MCII(T->createMCInstrInfo());
   if (!MCII)
     return;
 
-  std::unique_ptr<MemoryBuffer> Buffer(
-      MemoryBuffer::getMemBuffer(InlineAsm, "<inline asm>"));
-  SourceMgr SrcMgr;
-  SrcMgr.AddNewSourceBuffer(std::move(Buffer), SMLoc());
-
-  MCContext MCCtx(TT, *MAI, *MRI, *STI, &SrcMgr);
-  std::unique_ptr<MCObjectFileInfo> MOFI(
-      T->createMCObjectFileInfo(MCCtx, /*PIC=*/false));
-  MCCtx.setObjectFileInfo(MOFI.get());
-  RecordStreamer Streamer(MCCtx, M);
-  T->createNullTargetStreamer(Streamer);
-
-  std::unique_ptr<MCAsmParser> Parser(
-      createMCAsmParser(SrcMgr, MCCtx, Streamer, *MAI));
-
-  std::unique_ptr<MCTargetAsmParser> TAP(
-      T->createMCAsmParser(*STI, *Parser, *MCII));
-  if (!TAP)
-    return;
-
-  MCCtx.setDiagnosticHandler([&](const SMDiagnostic &SMD, bool IsInlineAsm,
-                                 const SourceMgr &SrcMgr,
-                                 std::vector<const MDNode *> &LocInfos) {
-    M.getContext().diagnose(
-        DiagnosticInfoSrcMgr(SMD, M.getName(), IsInlineAsm, /*LocCookie=*/0));
-  });
-
-  // Module-level inline asm is assumed to use At&t syntax (see
-  // AsmPrinter::doInitialization()).
-  Parser->setAssemblerDialect(InlineAsm::AD_ATT);
-
-  Parser->setSymbolScanningMode(true);
-
-  Parser->setTargetParser(*TAP);
-  if (Parser->Run(false))
-    return;
-
-  Init(Streamer);
+  for (const Module::GlobalAsmFragment &Frag : M.getModuleInlineAsm()) {
+    std::unique_ptr<MCSubtargetInfo> STI(
+        T->createMCSubtargetInfo(TT, Frag.TargetCPU, Frag.TargetFeatures));
+    if (!STI)
+      return;
+
+    std::unique_ptr<MemoryBuffer> Buffer(
+        MemoryBuffer::getMemBuffer(Frag.Asm, "<inline asm>"));
+    SourceMgr SrcMgr;
+    SrcMgr.AddNewSourceBuffer(std::move(Buffer), SMLoc());
+
+    MCContext MCCtx(TT, *MAI, *MRI, *STI, &SrcMgr);
+    std::unique_ptr<MCObjectFileInfo> MOFI(
+        T->createMCObjectFileInfo(MCCtx, /*PIC=*/false));
+    MCCtx.setObjectFileInfo(MOFI.get());
+    RecordStreamer Streamer(MCCtx, M);
+    T->createNullTargetStreamer(Streamer);
+
+    std::unique_ptr<MCAsmParser> Parser(
+        createMCAsmParser(SrcMgr, MCCtx, Streamer, *MAI));
+
+    std::unique_ptr<MCTargetAsmParser> TAP(
+        T->createMCAsmParser(*STI, *Parser, *MCII));
+    if (!TAP)
+      return;
+
+    MCCtx.setDiagnosticHandler([&](const SMDiagnostic &SMD, bool IsInlineAsm,
+                                   const SourceMgr &SrcMgr,
+                                   std::vector<const MDNode *> &LocInfos) {
+      M.getContext().diagnose(
+          DiagnosticInfoSrcMgr(SMD, M.getName(), IsInlineAsm, 
/*LocCookie=*/0));
+    });
+
+    // Module-level inline asm is assumed to use At&t syntax (see
+    // AsmPrinter::doInitialization()).
+    Parser->setAssemblerDialect(InlineAsm::AD_ATT);
+
+    Parser->setSymbolScanningMode(true);
+
+    Parser->setTargetParser(*TAP);
+    if (Parser->Run(false))
+      return;
+
+    Init(Streamer);
+  }
 }
 
 void ModuleSymbolTable::CollectAsmSymbols(
diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp 
b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index f0296184dde49..8ad486a51ee69 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -934,7 +934,7 @@ bool SPIRVAsmPrinter::doInitialization(Module &M) {
   if (!M.getModuleInlineAsm().empty()) {
     M.getContext().emitError(
         "SPIR-V does not support module-level inline assembly");
-    M.setModuleInlineAsm("");
+    M.removeModuleInlineAsm();
   }
 
   // Register the NSDI handler before calling the base class so that
diff --git a/llvm/lib/Transforms/IPO/ExtractGV.cpp 
b/llvm/lib/Transforms/IPO/ExtractGV.cpp
index 16fdb93e2b21b..4b76b02c32fe4 100644
--- a/llvm/lib/Transforms/IPO/ExtractGV.cpp
+++ b/llvm/lib/Transforms/IPO/ExtractGV.cpp
@@ -58,7 +58,7 @@ ExtractGVPass::ExtractGVPass(std::vector<GlobalValue *> &GVs, 
bool deleteS,
 PreservedAnalyses ExtractGVPass::run(Module &M, ModuleAnalysisManager &) {
   // Visit the global inline asm.
   if (!deleteStuff)
-    M.setModuleInlineAsm("");
+    M.removeModuleInlineAsm();
 
   // For simplicity, just give all GlobalValues ExternalLinkage. A trickier
   // implementation could figure out which GlobalValues are actually
diff --git a/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp 
b/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
index c14c9b869525d..ae6c31925a120 100644
--- a/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
+++ b/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
@@ -372,7 +372,7 @@ void splitAndWriteThinLTOBitcode(
         return false;
       }));
   StripDebugInfo(*MergedM);
-  MergedM->setModuleInlineAsm("");
+  MergedM->removeModuleInlineAsm();
 
   // Clone any llvm.*used globals to ensure the included values are
   // not deleted.
diff --git a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp 
b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
index c349fe33dd237..375ac069272eb 100644
--- a/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/DataFlowSanitizer.cpp
@@ -1283,18 +1283,20 @@ void DataFlowSanitizer::addGlobalNameSuffix(GlobalValue 
*GV) {
   // corrupting asm which happens to contain the symbol name as a substring.
   // Note that the substitution for .symver assumes that the versioned symbol
   // also has an instrumented name.
-  std::string Asm = GV->getParent()->getModuleInlineAsm();
-  std::string SearchStr = ".symver " + GVName + ",";
-  size_t Pos = Asm.find(SearchStr);
-  if (Pos != std::string::npos) {
-    Asm.replace(Pos, SearchStr.size(), ".symver " + GVName + Suffix + ",");
-    Pos = Asm.find('@');
-
-    if (Pos == std::string::npos)
-      report_fatal_error(Twine("unsupported .symver: ", Asm));
-
-    Asm.replace(Pos, 1, Suffix + "@");
-    GV->getParent()->setModuleInlineAsm(Asm);
+  for (Module::GlobalAsmFragment &Frag :
+       GV->getParent()->getModuleInlineAsm()) {
+    std::string SearchStr = ".symver " + GVName + ",";
+    size_t Pos = Frag.Asm.find(SearchStr);
+    if (Pos != std::string::npos) {
+      Frag.Asm.replace(Pos, SearchStr.size(),
+                       ".symver " + GVName + Suffix + ",");
+      Pos = Frag.Asm.find('@');
+
+      if (Pos == std::string::npos)
+        report_fatal_error(Twine("unsupported .symver: ", Frag.Asm));
+
+      Frag.Asm.replace(Pos, 1, Suffix + "@");
+    }
   }
 }
 
diff --git a/llvm/lib/Transforms/Utils/SplitModule.cpp 
b/llvm/lib/Transforms/Utils/SplitModule.cpp
index 64910b4d1ce99..8e70902c4dac2 100644
--- a/llvm/lib/Transforms/Utils/SplitModule.cpp
+++ b/llvm/lib/Transforms/Utils/SplitModule.cpp
@@ -304,7 +304,7 @@ void llvm::SplitModule(
             return isInPartition(GV, I, N);
         }));
     if (I != 0)
-      MPart->setModuleInlineAsm("");
+      MPart->removeModuleInlineAsm();
     ModuleCallback(std::move(MPart));
   }
 }
diff --git a/llvm/test/Assembler/module-asm-invalid.ll 
b/llvm/test/Assembler/module-asm-invalid.ll
new file mode 100644
index 0000000000000..de2a1c32ca6b5
--- /dev/null
+++ b/llvm/test/Assembler/module-asm-invalid.ll
@@ -0,0 +1,25 @@
+; RUN: split-file %s %t
+; RUN: not llvm-as -disable-output < %t/invalid_prop.ll 2>&1 | FileCheck 
%t/invalid_prop.ll
+; RUN: not llvm-as -disable-output < %t/missing_value1.ll 2>&1 | FileCheck 
%t/missing_value1.ll
+; RUN: not llvm-as -disable-output < %t/missing_value2.ll 2>&1 | FileCheck 
%t/missing_value2.ll
+; RUN: not llvm-as -disable-output < %t/missing_paren.ll 2>&1 | FileCheck 
%t/missing_paren.ll
+
+;--- invalid_prop.ll
+; CHECK: expected one of 'target_features' or 'target_cpu'
+module asm(foo="bar")
+    "asm"
+
+;--- missing_value1.ll
+; CHECK: expected '='
+module asm(target_features)
+    "asm"
+
+;--- missing_value2.ll
+; CHECK: expected string constant
+module asm(target_features=)
+    "asm"
+
+;--- missing_paren.ll
+; CHECK: expected ',' or ')'
+module asm(target_features="bar"
+    "asm"
diff --git a/llvm/test/Bitcode/compatibility-3.6.ll 
b/llvm/test/Bitcode/compatibility-3.6.ll
index 288e0fc50a9ba..c79086bad1ba5 100644
--- a/llvm/test/Bitcode/compatibility-3.6.ll
+++ b/llvm/test/Bitcode/compatibility-3.6.ll
@@ -14,7 +14,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/compatibility-3.7.ll 
b/llvm/test/Bitcode/compatibility-3.7.ll
index ef9490310fe5d..00b2bb9b30b62 100644
--- a/llvm/test/Bitcode/compatibility-3.7.ll
+++ b/llvm/test/Bitcode/compatibility-3.7.ll
@@ -14,7 +14,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/compatibility-3.8.ll 
b/llvm/test/Bitcode/compatibility-3.8.ll
index 538d6101b3994..5b86d5ffa1fee 100644
--- a/llvm/test/Bitcode/compatibility-3.8.ll
+++ b/llvm/test/Bitcode/compatibility-3.8.ll
@@ -13,7 +13,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/compatibility-3.9.ll 
b/llvm/test/Bitcode/compatibility-3.9.ll
index 3bfef73e485dd..eca4bbdf03766 100644
--- a/llvm/test/Bitcode/compatibility-3.9.ll
+++ b/llvm/test/Bitcode/compatibility-3.9.ll
@@ -13,7 +13,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/compatibility-4.0.ll 
b/llvm/test/Bitcode/compatibility-4.0.ll
index aaf9fe5492171..8551b98815b1a 100644
--- a/llvm/test/Bitcode/compatibility-4.0.ll
+++ b/llvm/test/Bitcode/compatibility-4.0.ll
@@ -13,7 +13,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/compatibility-5.0.ll 
b/llvm/test/Bitcode/compatibility-5.0.ll
index b820417107e8b..883ac3eaaa293 100644
--- a/llvm/test/Bitcode/compatibility-5.0.ll
+++ b/llvm/test/Bitcode/compatibility-5.0.ll
@@ -13,7 +13,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/compatibility-6.0.ll 
b/llvm/test/Bitcode/compatibility-6.0.ll
index 67e96c7277cc3..624d5bd7703c6 100644
--- a/llvm/test/Bitcode/compatibility-6.0.ll
+++ b/llvm/test/Bitcode/compatibility-6.0.ll
@@ -13,7 +13,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/compatibility.ll 
b/llvm/test/Bitcode/compatibility.ll
index d55aa1dc496df..97889d4687516 100644
--- a/llvm/test/Bitcode/compatibility.ll
+++ b/llvm/test/Bitcode/compatibility.ll
@@ -16,7 +16,8 @@ target triple = "x86_64-apple-macosx10.10.0"
 
 ;; Module-level assembly
 module asm "beep boop"
-; CHECK: module asm "beep boop"
+; CHECK: module asm
+; CHECK-NEXT: "beep boop"
 
 ;; Comdats
 $comdat.any = comdat any
diff --git a/llvm/test/Bitcode/highLevelStructure.3.2.ll 
b/llvm/test/Bitcode/highLevelStructure.3.2.ll
index f2770ea4e4d34..cd9a34c810d57 100644
--- a/llvm/test/Bitcode/highLevelStructure.3.2.ll
+++ b/llvm/test/Bitcode/highLevelStructure.3.2.ll
@@ -9,7 +9,8 @@
 target datalayout = 
"e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v24:32:32-v32:32:32-v48:64:64-v64:64:64-v96:128:128-v128:128:128-v192:256:256-v256:256:256-v512:512:512-v1024:1024:1024-a0:0:64-f80:32:32-n8:16:32-S32"
 
 ; Module-Level Inline Assembly Test
-; CHECK: module asm "some assembly"
+; CHECK: module asm
+; CHECK-NEXT: "some assembly"
 module asm "some assembly"
 
 ; Named Types Test
diff --git a/llvm/test/Bitcode/module-asm.ll b/llvm/test/Bitcode/module-asm.ll
new file mode 100644
index 0000000000000..4d2144c0cba33
--- /dev/null
+++ b/llvm/test/Bitcode/module-asm.ll
@@ -0,0 +1,29 @@
+; RUN: llvm-as < %s | llvm-dis | FileCheck %s
+
+; CHECK: module asm
+; CHECK:     "asm line 0"
+; CHECK: module asm(target_features="+foo", target_cpu="foo_cpu")
+; CHECK:     "asm line 1"
+; CHECK:     "asm line 2"
+; CHECK: module asm(target_features="+bar")
+; CHECK:     "asm line 3"
+; CHECK: module asm(target_cpu="bar_cpu")
+; CHECK:     "asm line 4"
+; CHECK: module asm
+; CHECK:     "asm line 5"
+
+module asm
+    "asm line 0"
+
+module asm(target_features="+foo", target_cpu="foo_cpu")
+    "asm line 1"
+    "asm line 2"
+
+module asm(target_features="+bar")
+    "asm line 3"
+
+module asm(target_cpu="bar_cpu")
+    "asm line 4"
+
+module asm
+    "asm line 5"
diff --git a/llvm/test/CodeGen/RISCV/module-asm-features.ll 
b/llvm/test/CodeGen/RISCV/module-asm-features.ll
new file mode 100644
index 0000000000000..eecc967734ad0
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/module-asm-features.ll
@@ -0,0 +1,12 @@
+; RUN: llc -mtriple=riscv64-unknown-linux-gnu < %s | FileCheck %s
+
+; This should work fine, because the module asm specifies the necessary
+; target features
+
+; CHECK: fld ft0, 0(sp)
+
+module asm(target_features="+d")
+    ".globl func"
+    "func:"
+    "fld f0, 0(sp)"
+    "ret"
diff --git a/llvm/test/Instrumentation/DataFlowSanitizer/prefix-rename.ll 
b/llvm/test/Instrumentation/DataFlowSanitizer/prefix-rename.ll
index 454c496d1d4bb..829dd829d51f9 100644
--- a/llvm/test/Instrumentation/DataFlowSanitizer/prefix-rename.ll
+++ b/llvm/test/Instrumentation/DataFlowSanitizer/prefix-rename.ll
@@ -2,7 +2,8 @@
 target datalayout = 
"e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"
 target triple = "x86_64-unknown-linux-gnu"
 
-; CHECK: module asm ".symver f1.dfsan,f.dfsan@@version1"
+; CHECK: module asm
+; CHECK-NEXT: ".symver f1.dfsan,f.dfsan@@version1"
 module asm ".symver f1,f@@version1"
 
 ; CHECK: @f2.dfsan = alias {{.*}} @f1.dfsan
diff --git a/llvm/test/LTO/RISCV/module-asm.ll 
b/llvm/test/LTO/RISCV/module-asm.ll
new file mode 100644
index 0000000000000..c92343b457ff7
--- /dev/null
+++ b/llvm/test/LTO/RISCV/module-asm.ll
@@ -0,0 +1,18 @@
+; RUN: llvm-as %s -o %t.o
+; RUN: llvm-lto2 run -save-temps -filetype=asm -o %t.s %t.o -r=%t.o,func,p
+; RUN: llvm-nm %t.o | FileCheck %s --check-prefix NM
+; RUN: llvm-nm %t.s.0.5.precodegen.bc | FileCheck %s --check-prefix NM
+; RUN: FileCheck %s --input-file %t.s.0
+
+; NM: T func
+
+; CHECK: fld ft0, 0(sp)
+
+target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
+target triple = "riscv64-unknown-linux-gnu"
+
+module asm(target_features="+d")
+    ".globl func"
+    "func:"
+    "fld f0, 0(sp)"
+    "ret"
diff --git a/llvm/test/LTO/X86/inline-asm-lto-discard.ll 
b/llvm/test/LTO/X86/inline-asm-lto-discard.ll
index 88492e4366489..6c5173c3f9553 100644
--- a/llvm/test/LTO/X86/inline-asm-lto-discard.ll
+++ b/llvm/test/LTO/X86/inline-asm-lto-discard.ll
@@ -32,17 +32,19 @@
 ; RUN:  -r %t4,foo@@VER1,px
 ; RUN: llvm-dis < %to3.0.0.preopt.bc | FileCheck %s --check-prefix=ASM3
 
-; ASM1:      module asm ".lto_discard foo"
-; ASM1-NEXT: module asm ".weak foo"
-; ASM1-NEXT: module asm ".equ foo,bar"
-
-; ASM2:      module asm ".lto_discard foo"
-; ASM2-NEXT: module asm ".weak foo"
-; ASM2-NEXT: module asm ".equ foo,bar"
-; ASM2-NEXT: module asm ".lto_discard"
-; ASM2-NEXT: module asm " .global foo ; foo: leal    2(%rdi), %eax"
-
-; ASM3-NOT:  module asm ".lto_discard foo"
+; ASM1: module asm
+; ASM1-NEXT: ".lto_discard foo"
+; ASM1-NEXT: ".weak foo"
+; ASM1-NEXT: ".equ foo,bar"
+
+; ASM2: module asm
+; ASM2-NEXT: ".lto_discard foo"
+; ASM2-NEXT: ".weak foo"
+; ASM2-NEXT: ".equ foo,bar"
+; ASM2-NEXT: ".lto_discard"
+; ASM2-NEXT: " .global foo ; foo: leal    2(%rdi), %eax"
+
+; ASM3-NOT:  ".lto_discard foo"
 
 ; SYM: T foo
 
diff --git a/llvm/test/Linker/Inputs/module-asm.ll 
b/llvm/test/Linker/Inputs/module-asm.ll
new file mode 100644
index 0000000000000..01c55a875af4f
--- /dev/null
+++ b/llvm/test/Linker/Inputs/module-asm.ll
@@ -0,0 +1,2 @@
+module asm(target_features="+bar")
+    "asm 2"
diff --git a/llvm/test/Linker/link-arm-and-thumb-module-inline-asm.ll 
b/llvm/test/Linker/link-arm-and-thumb-module-inline-asm.ll
index 5e12a8c88d90a..612bce5be18f1 100644
--- a/llvm/test/Linker/link-arm-and-thumb-module-inline-asm.ll
+++ b/llvm/test/Linker/link-arm-and-thumb-module-inline-asm.ll
@@ -9,11 +9,12 @@ target triple = "armv7-linux-gnueabihf"
 
 module asm "add r1, r2, r2"
 
-; CHECK:      module asm ".text"
-; CHECK-NEXT: module asm ".balign 4"
-; CHECK-NEXT: module asm ".arm"
-; CHECK-NEXT: module asm "add r1, r2, r2"
-; CHECK-NEXT: module asm ".text"
-; CHECK-NEXT: module asm ".balign 2"
-; CHECK-NEXT: module asm ".thumb"
-; CHECK-NEXT: module asm "orn r1, r2, r2"
+; CHECK: module asm
+; CHECK-NEXT: ".text"
+; CHECK-NEXT: ".balign 4"
+; CHECK-NEXT: ".arm"
+; CHECK-NEXT: "add r1, r2, r2"
+; CHECK-NEXT: ".text"
+; CHECK-NEXT: ".balign 2"
+; CHECK-NEXT: ".thumb"
+; CHECK-NEXT: "orn r1, r2, r2"
diff --git a/llvm/test/Linker/module-asm.ll b/llvm/test/Linker/module-asm.ll
new file mode 100644
index 0000000000000..9f43c7a22514d
--- /dev/null
+++ b/llvm/test/Linker/module-asm.ll
@@ -0,0 +1,9 @@
+; RUN: llvm-link %s %p/Inputs/module-asm.ll -S | FileCheck %s
+
+; CHECK: module asm(target_features="+foo")
+; CHECK-NEXT: "asm 1"
+; CHECK: module asm(target_features="+bar")
+; CHECK-NEXT: "asm 2"
+
+module asm(target_features="+foo")
+    "asm 1"
diff --git a/llvm/test/ThinLTO/X86/import-symver.ll 
b/llvm/test/ThinLTO/X86/import-symver.ll
index 556c4fd992f0d..9525527b4531c 100644
--- a/llvm/test/ThinLTO/X86/import-symver.ll
+++ b/llvm/test/ThinLTO/X86/import-symver.ll
@@ -9,7 +9,8 @@
 ; RUN: llvm-dis %t1.bc.thinlto.imported.bc -o - | FileCheck 
--check-prefix=NOIMPORT %s
 
 ; When @bar gets imported, the symver must be imported too.
-; IMPORT: module asm ".symver bar, bar@BAR_1.2.3"
+; IMPORT: module asm
+; IMPORT-NEXT: ".symver bar, bar@BAR_1.2.3"
 ; IMPORT: declare dso_local i32 @bar()
 
 ; When @bar isn't imported, the symver is also not imported.
diff --git a/llvm/test/Transforms/LowerTypeTests/export-symver.ll 
b/llvm/test/Transforms/LowerTypeTests/export-symver.ll
index ea4594a359cc0..7c3ed3da3537e 100644
--- a/llvm/test/Transforms/LowerTypeTests/export-symver.ll
+++ b/llvm/test/Transforms/LowerTypeTests/export-symver.ll
@@ -1,6 +1,7 @@
 ; RUN: opt -S %s -passes=lowertypetests -lowertypetests-summary-action=export 
-lowertypetests-read-summary=%S/Inputs/exported-funcs.yaml | FileCheck %s
 ;
-; CHECK: module asm ".symver external_addrtaken, alias1"
+; CHECK: module asm
+; CHECK-NEXT: ".symver external_addrtaken, alias1"
 ; CHECK-NOT: .symver external_addrtaken2
 ; CHECK-NOT: .symver not_exported
 
diff --git 
a/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll 
b/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll
index d8ebae17d4693..61141c71335bf 100644
--- a/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll
@@ -3,7 +3,8 @@
 
 target triple = "x86_64-unknown-linux-gnu"
 
-; CHECK: module asm ".lto_set_conditional a,a.[[HASH:[0-9a-f]+]]"
+; CHECK: module asm
+; CHECK-NEXT: ".lto_set_conditional a,a.[[HASH:[0-9a-f]+]]"
 
 define void @b() {
   %f = alloca ptr, align 8
diff --git a/llvm/test/Transforms/ThinLTOBitcodeWriter/x86/module-asm.ll 
b/llvm/test/Transforms/ThinLTOBitcodeWriter/x86/module-asm.ll
index 587ab3fbb5020..5796537661e60 100644
--- a/llvm/test/Transforms/ThinLTOBitcodeWriter/x86/module-asm.ll
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/x86/module-asm.ll
@@ -7,6 +7,7 @@ target triple = "x86_64-unknown-linux-gnu"
 @g = constant i32 0, !type !0
 !0 = !{i32 0, !"typeid"}
 
-; M0: module asm "ret"
+; M0: module asm
+; M0-NEXT: "ret"
 ; M1-NOT: module asm
 module asm "ret"
diff --git a/llvm/tools/llvm-reduce/deltas/ReduceModuleData.cpp 
b/llvm/tools/llvm-reduce/deltas/ReduceModuleData.cpp
index 4aeaef6d8d676..b5b68404e8690 100644
--- a/llvm/tools/llvm-reduce/deltas/ReduceModuleData.cpp
+++ b/llvm/tools/llvm-reduce/deltas/ReduceModuleData.cpp
@@ -23,5 +23,5 @@ void llvm::reduceModuleDataDeltaPass(Oracle &O, 
ReducerWorkItem &WorkItem) {
     Program.setSourceFileName("");
   // TODO: clear line by line rather than all at once
   if (!Program.getModuleInlineAsm().empty() && !O.shouldKeep())
-    Program.setModuleInlineAsm("");
+    Program.removeModuleInlineAsm();
 }
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp 
b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index 3038a6a2ed986..bb55678f91005 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1150,12 +1150,15 @@ void ModuleImport::convertTargetTriple() {
 }
 
 void ModuleImport::convertModuleLevelAsm() {
-  llvm::StringRef asmStr = llvmModule->getModuleInlineAsm();
   llvm::SmallVector<mlir::Attribute> asmArrayAttr;
 
-  for (llvm::StringRef line : llvm::split(asmStr, '\n'))
-    if (!line.empty())
-      asmArrayAttr.push_back(builder.getStringAttr(line));
+  for (const llvm::Module::GlobalAsmFragment &Frag :
+       llvmModule->getModuleInlineAsm()) {
+    // TODO: Preserve module asm properties.
+    for (llvm::StringRef line : llvm::split(Frag.Asm, '\n'))
+      if (!line.empty())
+        asmArrayAttr.push_back(builder.getStringAttr(line));
+  }
 
   mlirModule->setAttr(LLVM::LLVMDialect::getModuleLevelAsmAttrName(),
                       builder.getArrayAttr(asmArrayAttr));
diff --git a/mlir/test/Target/LLVMIR/module-asm.mlir 
b/mlir/test/Target/LLVMIR/module-asm.mlir
index 2afb37cf005bf..813eeeafdeb44 100644
--- a/mlir/test/Target/LLVMIR/module-asm.mlir
+++ b/mlir/test/Target/LLVMIR/module-asm.mlir
@@ -2,5 +2,6 @@
 
 module attributes {llvm.module_asm = ["foo", "bar"]} {}
 
-// CHECK: module asm "foo"
-// CHECK: module asm "bar"
+// CHECK: module asm
+// CHECK-NEXT: "foo"
+// CHECK-NEXT: "bar"

>From 36c6e8ca5a1876396a4b38b754ecd401942d7f65 Mon Sep 17 00:00:00 2001
From: Nikita Popov <[email protected]>
Date: Mon, 22 Jun 2026 08:48:23 +0200
Subject: [PATCH 2/4] Add LangRef

---
 llvm/docs/LangRef.rst | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 287341d787b64..a395f9fb61d44 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -3339,19 +3339,34 @@ Module-Level Inline Assembly
 ----------------------------
 
 Modules may contain "module-level inline asm" blocks, which corresponds
-to the GCC "file scope inline asm" blocks. These blocks are internally
-concatenated by LLVM and treated as a single unit, but may be separated
-in the ``.ll`` file if desired. The syntax is very simple:
+to the GCC "file scope inline asm" blocks:
 
 .. code-block:: llvm
 
-    module asm "inline asm code goes here"
-    module asm "more can go here"
+    module asm
+        "inline asm code goes here"
+        "more can go here"
 
-The strings can contain any character by escaping non-printable
+The sequence of string literals is internally concatenated using newlines as
+separators. The strings can contain any character by escaping non-printable
 characters. The escape sequence used is simply "\\xx" where "xx" is the
 two digit hex code for the number.
 
+Additionally, module-level inline assembly can specify an optional list of
+properties:
+
+.. code-block:: llvm
+
+    module asm(target_features="+foo", target_cpu="bar")
+        "inline asm code goes here"
+        "more can go here"
+
+Currently, the only supported properties are ``target_features`` and
+``target_cpu``, with the same meaning as the ``"target-features"`` and
+``"target-cpu"`` function attributes.
+
+Consecutive blocks with identical properties will be concatenated into one.
+
 Note that the assembly string *must* be parseable by LLVM's integrated 
assembler
 (unless it is disabled), even when emitting a ``.s`` file.
 

>From 2cee9bcf34fc706b6bf229cf2a02053e8b2aff0c Mon Sep 17 00:00:00 2001
From: Nikita Popov <[email protected]>
Date: Mon, 22 Jun 2026 12:35:53 +0200
Subject: [PATCH 3/4] Emit option push/pop on RISCV

---
 llvm/include/llvm/CodeGen/AsmPrinter.h        | 13 +++++++++++
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp    |  4 +++-
 llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp     | 23 +++++++++++--------
 .../test/CodeGen/RISCV/module-asm-features.ll |  7 +++++-
 4 files changed, 36 insertions(+), 11 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h 
b/llvm/include/llvm/CodeGen/AsmPrinter.h
index f6f0f7348836e..ae33439f7d7e6 100644
--- a/llvm/include/llvm/CodeGen/AsmPrinter.h
+++ b/llvm/include/llvm/CodeGen/AsmPrinter.h
@@ -930,6 +930,19 @@ class LLVM_ABI AsmPrinter : public MachineFunctionPass {
                                 const MCSubtargetInfo *EndInfo,
                                 const MachineInstr *MI);
 
+  /// Emit necessary directives to allow use of instructions that are permitted
+  /// by target features enabled by STI, but are not permitted by target
+  /// features enabled by the global subtarget (TM.getSubTargetInfo()).
+  /// Returns whether anything was emitted.
+  virtual bool emitTargetFeaturePush(const MCSubtargetInfo &STI) {
+    return false;
+  }
+
+  /// Emit necessary directives to restore target feature state.
+  /// The \p DidPush argument is the result of the prior 
emitTargetFeaturePush()
+  /// call.
+  virtual void emitTargetFeaturePop(const MCSubtargetInfo &STI, bool DidPush) 
{}
+
   /// This emits visibility information about symbol, if this is supported by
   /// the target.
   void emitVisibility(MCSymbol *Sym, unsigned Visibility,
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp 
b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index 8049675701c7f..bc097d4ee14f2 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -618,9 +618,11 @@ bool AsmPrinter::doInitialization(Module &M) {
         std::unique_ptr<MCSubtargetInfo> AsmSTI(
             TM.getTarget().createMCSubtargetInfo(
                 TM.getTargetTriple(), Frag.TargetCPU, Frag.TargetFeatures));
+        bool DidPush = emitTargetFeaturePush(*AsmSTI);
         emitInlineAsm(
-            Frag.Asm + "\n", *AsmSTI, TM.Options.MCOptions, nullptr,
+            Frag.Asm, *AsmSTI, TM.Options.MCOptions, nullptr,
             InlineAsm::AsmDialect(TM.getMCAsmInfo().getAssemblerDialect()));
+        emitTargetFeaturePop(*AsmSTI, DidPush);
       } else {
         // If the module asm does not explicitly specify target features,
         // fall back to default subtargetinfo.
diff --git a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp 
b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
index 7b4e2e7390058..9e3f5dcc89c04 100644
--- a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
+++ b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
@@ -117,7 +117,8 @@ class RISCVAsmPrinter : public AsmPrinter {
   void emitEndOfAsmFile(Module &M) override;
 
   void emitFunctionEntryLabel() override;
-  bool emitDirectiveOptionArch();
+  bool emitTargetFeaturePush(const MCSubtargetInfo &STI) override;
+  void emitTargetFeaturePop(const MCSubtargetInfo &STI, bool DidPush) override;
 
   void emitNoteGnuProperty(const Module &M);
 
@@ -527,19 +528,19 @@ bool RISCVAsmPrinter::PrintAsmMemoryOperand(const 
MachineInstr *MI,
   return false;
 }
 
-bool RISCVAsmPrinter::emitDirectiveOptionArch() {
+bool RISCVAsmPrinter::emitTargetFeaturePush(const MCSubtargetInfo &STI) {
   RISCVTargetStreamer &RTS = getTargetStreamer();
   SmallVector<RISCVOptionArchArg> NeedEmitStdOptionArgs;
   const MCSubtargetInfo &MCSTI = TM.getMCSubtargetInfo();
   for (const auto &Feature : RISCVFeatureKV) {
-    if (STI->hasFeature(Feature.Value) == MCSTI.hasFeature(Feature.Value))
+    if (STI.hasFeature(Feature.Value) == MCSTI.hasFeature(Feature.Value))
       continue;
 
     if (!llvm::RISCVISAInfo::isSupportedExtensionFeature(Feature.Key))
       continue;
 
-    auto Delta = STI->hasFeature(Feature.Value) ? RISCVOptionArchArgType::Plus
-                                                : 
RISCVOptionArchArgType::Minus;
+    auto Delta = STI.hasFeature(Feature.Value) ? RISCVOptionArchArgType::Plus
+                                               : RISCVOptionArchArgType::Minus;
     StringRef ExtName = Feature.Key;
     ExtName.consume_front("experimental-");
     NeedEmitStdOptionArgs.emplace_back(Delta, ExtName.str());
@@ -553,11 +554,16 @@ bool RISCVAsmPrinter::emitDirectiveOptionArch() {
   return false;
 }
 
+void RISCVAsmPrinter::emitTargetFeaturePop(const MCSubtargetInfo &STI,
+                                           bool DidPush) {
+  if (DidPush)
+    getTargetStreamer().emitDirectiveOptionPop();
+}
+
 bool RISCVAsmPrinter::runOnMachineFunction(MachineFunction &MF) {
   STI = &MF.getSubtarget<RISCVSubtarget>();
-  RISCVTargetStreamer &RTS = getTargetStreamer();
 
-  bool EmittedOptionArch = emitDirectiveOptionArch();
+  bool EmittedOptionArch = emitTargetFeaturePush(*STI);
 
   SetupMachineFunction(MF);
   emitFunctionBody();
@@ -565,8 +571,7 @@ bool RISCVAsmPrinter::runOnMachineFunction(MachineFunction 
&MF) {
   // Emit the XRay table
   emitXRayTable();
 
-  if (EmittedOptionArch)
-    RTS.emitDirectiveOptionPop();
+  emitTargetFeaturePop(*STI, EmittedOptionArch);
   return false;
 }
 
diff --git a/llvm/test/CodeGen/RISCV/module-asm-features.ll 
b/llvm/test/CodeGen/RISCV/module-asm-features.ll
index eecc967734ad0..3e9483cbf4461 100644
--- a/llvm/test/CodeGen/RISCV/module-asm-features.ll
+++ b/llvm/test/CodeGen/RISCV/module-asm-features.ll
@@ -1,9 +1,14 @@
-; RUN: llc -mtriple=riscv64-unknown-linux-gnu < %s | FileCheck %s
+; RUN: llc -mtriple=riscv64-unknown-linux-gnu < %s | FileCheck %s 
--check-prefixes=CHECK,EXTRA-FEATURES
+; RUN: llc -mtriple=riscv64-unknown-linux-gnu -mattr=+d < %s | FileCheck %s 
--check-prefixes=CHECK,SAME-FEATURES
 
 ; This should work fine, because the module asm specifies the necessary
 ; target features
 
+; SAME-FEATURES-NOT: .option arch
+; EXTRA-FEATURES: .option push
+; EXTRA-FEATURES: .option arch, +d
 ; CHECK: fld ft0, 0(sp)
+; EXTRA-FEATURES: .option pop
 
 module asm(target_features="+d")
     ".globl func"

>From 53a716147d50d905f34a113e76d1f9100399060e Mon Sep 17 00:00:00 2001
From: Nikita Popov <[email protected]>
Date: Thu, 25 Jun 2026 14:37:12 +0200
Subject: [PATCH 4/4] Make the implementation easier to extend

---
 clang/lib/CodeGen/CodeGenModule.cpp           |  8 +++--
 clang/test/CodeGen/2006-01-23-FileScopeAsm.c  |  2 +-
 clang/test/CodeGen/asm.c                      |  2 +-
 clang/test/CodeGenCXX/gnu-asm-constexpr.cpp   |  2 +-
 clang/test/Frontend/ast-codegen.c             |  2 +-
 llvm/docs/LangRef.rst                         |  2 +-
 llvm/docs/ReleaseNotes.md                     |  4 +++
 llvm/include/llvm/AsmParser/LLToken.h         |  2 --
 llvm/include/llvm/Bitcode/LLVMBitCodes.h      |  3 +-
 llvm/include/llvm/IR/Module.h                 | 33 +++++++++++++-----
 llvm/lib/AsmParser/LLLexer.cpp                |  2 --
 llvm/lib/AsmParser/LLParser.cpp               | 34 ++++++++++---------
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp     | 27 +++++++--------
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     | 15 ++++----
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp    |  7 ++--
 llvm/lib/IR/AsmWriter.cpp                     | 16 ++++-----
 llvm/lib/IR/Module.cpp                        | 20 +++++++++++
 llvm/lib/Object/ModuleSymbolTable.cpp         |  4 +--
 llvm/test/Assembler/module-asm-invalid.ll     | 10 +++---
 llvm/test/Bitcode/module-asm.ll               | 12 +++----
 .../test/CodeGen/RISCV/module-asm-features.ll |  2 +-
 llvm/test/LTO/RISCV/module-asm.ll             |  2 +-
 llvm/test/Linker/Inputs/module-asm.ll         |  2 +-
 llvm/test/Linker/module-asm.ll                |  6 ++--
 24 files changed, 129 insertions(+), 90 deletions(-)

diff --git a/clang/lib/CodeGen/CodeGenModule.cpp 
b/clang/lib/CodeGen/CodeGenModule.cpp
index 3616846b0fb01..e07f18a5aca46 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -8056,9 +8056,11 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
     auto *AD = cast<FileScopeAsmDecl>(D);
 
     const TargetOptions &TargetOpts = getTarget().getTargetOpts();
-    std::string Features = llvm::join(TargetOpts.Features, ",");
-    getModule().appendModuleInlineAsm(llvm::Module::GlobalAsmFragment(
-        AD->getAsmString(), Features, TargetOpts.CPU));
+    llvm::Module::GlobalAsmProperties Props;
+    Props.TargetFeatures = llvm::join(TargetOpts.Features, ",");
+    Props.TargetCPU = TargetOpts.CPU;
+    getModule().appendModuleInlineAsm(
+        llvm::Module::GlobalAsmFragment(AD->getAsmString(), Props));
     break;
   }
 
diff --git a/clang/test/CodeGen/2006-01-23-FileScopeAsm.c 
b/clang/test/CodeGen/2006-01-23-FileScopeAsm.c
index eee40654240f7..cc1f4b585f209 100644
--- a/clang/test/CodeGen/2006-01-23-FileScopeAsm.c
+++ b/clang/test/CodeGen/2006-01-23-FileScopeAsm.c
@@ -1,7 +1,7 @@
 // UNSUPPORTED: target={{.*}}-zos{{.*}}
 // RUN: %clang_cc1 %s -emit-llvm -o - | FileCheck %s
 
-// CHECK: module asm(target_features="{{.*}}")
+// CHECK: module asm(target_features: "{{.*}}")
 // CHECK-NEXT: "foo1"
 __asm__ ("foo1");
 // CHECK-NEXT: "foo2"
diff --git a/clang/test/CodeGen/asm.c b/clang/test/CodeGen/asm.c
index bc54eb017ad12..ea69c7a17d1cb 100644
--- a/clang/test/CodeGen/asm.c
+++ b/clang/test/CodeGen/asm.c
@@ -2,7 +2,7 @@
 
 // PR10415:
 //
-// CHECK: module asm(target_features="{{.*}}")
+// CHECK: module asm(target_features: "{{.*}}")
 // CHECK-NEXT: "foo1"
 // CHECK-NEXT: "foo2"
 // CHECK-NEXT: "foo3"
diff --git a/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp 
b/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp
index b7e0ba389c7dd..2b4abee5dc05f 100644
--- a/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp
+++ b/clang/test/CodeGenCXX/gnu-asm-constexpr.cpp
@@ -19,7 +19,7 @@ struct string_view {
 namespace GH143242 {
     constexpr string_view code2 = R"(nop; nop; nop; nop)";
     asm((code2));
-    // CHECK: module asm(target_features="{{.*}}")
+    // CHECK: module asm(target_features: "{{.*}}")
     // CHECK-NEXT: "nop; nop; nop; nop"
 }
 
diff --git a/clang/test/Frontend/ast-codegen.c 
b/clang/test/Frontend/ast-codegen.c
index bf35da069772d..c26b0a1908379 100644
--- a/clang/test/Frontend/ast-codegen.c
+++ b/clang/test/Frontend/ast-codegen.c
@@ -2,7 +2,7 @@
 // RUN: %clang -target i386-unknown-unknown -emit-ast -o %t.ast %s
 // RUN: %clang -target i386-unknown-unknown -emit-llvm -S -o - %t.ast | 
FileCheck %s
 
-// CHECK: module asm(target_features="{{.*}}")
+// CHECK: module asm(target_features: "{{.*}}")
 // CHECK-NEXT: "foo"
 __asm__("foo");
 
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index a395f9fb61d44..4a069fbba452c 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -3357,7 +3357,7 @@ properties:
 
 .. code-block:: llvm
 
-    module asm(target_features="+foo", target_cpu="bar")
+    module asm(target_features: "+foo", target_cpu: "bar")
         "inline asm code goes here"
         "more can go here"
 
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index a365e30792b4c..f5e9169a4afde 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -97,6 +97,10 @@ Makes programs 10x faster by doing Special New Thing.
 * The ``modular-format`` attribute now supports the ``fixed`` aspect for C
   ISO 18037 fixed-point ``printf`` specifiers.
 
+* Module-level inline assembly now accepts optional `target_features` and
+  `target_cpu` properties. This resolves errors during LTO on some
+  architectures.
+
 ### Changes to LLVM infrastructure
 
 * Removed ``Constant::isZeroValue``. It was functionally identical to
diff --git a/llvm/include/llvm/AsmParser/LLToken.h 
b/llvm/include/llvm/AsmParser/LLToken.h
index 95d3e67ff3849..d2766a05ce9ba 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -86,8 +86,6 @@ enum Kind {
   kw_musttail,
   kw_notail,
   kw_target,
-  kw_target_cpu,
-  kw_target_features,
   kw_triple,
   kw_source_filename,
   kw_unwind,
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h 
b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 7b05d2c161188..b186a05dc2a43 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -121,8 +121,7 @@ enum ModuleCodes {
   // IFUNC: [ifunc value type, addrspace, resolver val#, linkage, visibility]
   MODULE_CODE_IFUNC = 18,
 
-  MODULE_CODE_ASM_TARGET_FEATURES = 19, // [strchr x N]
-  MODULE_CODE_ASM_TARGET_CPU = 20,      // [strchr x N]
+  MODULE_CODE_ASM_PROPERTY = 19, // [strchr x N]
 };
 
 /// PARAMATTR blocks have code for defining a parameter attribute set.
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index 8a873a361ff06..a82ac363dee65 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -169,16 +169,34 @@ class LLVM_ABI Module {
         : Behavior(B), Key(K), Val(V) {}
   };
 
-  struct GlobalAsmFragment {
-    std::string Asm;
+  struct GlobalAsmProperties {
     std::string TargetFeatures;
     std::string TargetCPU;
 
+    /// Set a property using a string name.
+    /// Returns whether the property name was valid.
+    bool set(StringRef Name, std::string Value);
+
+    /// Get a list of set properties as pairs of key and value.
+    SmallVector<std::pair<StringRef, StringRef>> getAsStrings() const;
+
+    bool operator==(const GlobalAsmProperties &Other) const {
+      return TargetFeatures == Other.TargetFeatures &&
+             TargetCPU == Other.TargetCPU;
+    }
+
+    bool operator!=(const GlobalAsmProperties &Other) const {
+      return !(*this == Other);
+    }
+  };
+
+  struct GlobalAsmFragment {
+    std::string Asm;
+    GlobalAsmProperties Props;
+
     GlobalAsmFragment(StringRef Asm) : GlobalAsmFragment(Asm.str()) {}
-    GlobalAsmFragment(std::string AsmArg, std::string TargetFeatures = "",
-                      std::string TargetCPU = "")
-        : Asm(std::move(AsmArg)), TargetFeatures(std::move(TargetFeatures)),
-          TargetCPU(std::move(TargetCPU)) {
+    GlobalAsmFragment(std::string AsmArg, GlobalAsmProperties Props = {})
+        : Asm(std::move(AsmArg)), Props(std::move(Props)) {
       if (!Asm.empty() && Asm.back() != '\n')
         Asm += '\n';
     }
@@ -186,8 +204,7 @@ class LLVM_ABI Module {
     bool empty() const { return Asm.empty(); }
 
     bool hasSameProperties(const GlobalAsmFragment &Other) const {
-      return TargetFeatures == Other.TargetFeatures &&
-             TargetCPU == Other.TargetCPU;
+      return Props == Other.Props;
     }
   };
 
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index a978867e1ad14..069a180056488 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -595,8 +595,6 @@ lltok::Kind LLLexer::LexIdentifier() {
   KEYWORD(tail);
   KEYWORD(musttail);
   KEYWORD(notail);
-  KEYWORD(target_cpu);
-  KEYWORD(target_features);
   KEYWORD(target);
   KEYWORD(triple);
   KEYWORD(source_filename);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index cfd52c98d5b88..0a5b4c6c91879 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -641,8 +641,8 @@ bool LLParser::parseTopLevelEntities() {
 
 /// toplevelentity
 ///   ::= 'module' 'asm' STRINGCONSTANT
-///   ::= 'module' 'asm' '(' 'target_features' '=' STRINGCONSTANT ','
-///                          'target_cpu' '=' STRINGCONSTANT ')'
+///   ::= 'module' 'asm' '(' 'property_name1:' STRINGCONSTANT ','
+///                          'property_name2:' STRINGCONSTANT ')'
 ///                      STRINGCONSTANT
 bool LLParser::parseModuleAsm() {
   assert(Lex.getKind() == lltok::kw_module);
@@ -652,21 +652,23 @@ bool LLParser::parseModuleAsm() {
   if (parseToken(lltok::kw_asm, "expected 'module asm'"))
     return true;
 
-  std::string TargetFeatures, TargetCPU;
+  Module::GlobalAsmProperties Props;
   if (EatIfPresent(lltok::lparen)) {
     while (true) {
-      if (EatIfPresent(lltok::kw_target_features)) {
-        if (parseToken(lltok::equal, "expected '='") ||
-            parseStringConstant(TargetFeatures))
-          return true;
-      } else if (EatIfPresent(lltok::kw_target_cpu)) {
-        if (parseToken(lltok::equal, "expected '='") ||
-            parseStringConstant(TargetCPU))
-          return true;
-      } else {
-        return error(Lex.getLoc(),
-                     "expected one of 'target_features' or 'target_cpu'");
-      }
+      std::string Key, Value;
+      SMLoc Loc = Lex.getLoc();
+      if (Lex.getKind() != lltok::LabelStr)
+        return error(Loc, "expected property name followed by ':'");
+
+      Key = Lex.getStrVal();
+      Lex.Lex();
+
+      if (parseStringConstant(Value))
+        return true;
+
+      if (!Props.set(Key, Value))
+        return error(Loc, "unknown property name");
+
       if (EatIfPresent(lltok::rparen))
         break;
       if (parseToken(lltok::comma, "expected ',' or ')'"))
@@ -681,7 +683,7 @@ bool LLParser::parseModuleAsm() {
     AsmStr += AsmStrPart + "\n";
   } while (Lex.getKind() == lltok::StringConstant);
 
-  M->appendModuleInlineAsm({AsmStr, TargetFeatures, TargetCPU});
+  M->appendModuleInlineAsm({AsmStr, Props});
   return false;
 }
 
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp 
b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 99a76379feb51..75c3e769b085d 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -4580,7 +4580,7 @@ Error BitcodeReader::parseModule(uint64_t ResumeBit,
   std::string TentativeDataLayoutStr = TheModule->getDataLayoutStr();
 
   // Apply to the following module asm.
-  std::string ModuleAsmTargetFeatures, ModuleAsmTargetCPU;
+  Module::GlobalAsmProperties Props;
 
   auto ResolveDataLayout = [&]() -> Error {
     if (ResolvedDataLayout)
@@ -4790,24 +4790,23 @@ Error BitcodeReader::parseModule(uint64_t ResumeBit,
         return error("Invalid data layout record");
       break;
     }
-    case bitc::MODULE_CODE_ASM_TARGET_FEATURES:
-      ModuleAsmTargetFeatures.clear();
-      if (convertToString(Record, 0, ModuleAsmTargetFeatures))
-        return error("Invalid asm target features record");
-      break;
-    case bitc::MODULE_CODE_ASM_TARGET_CPU:
-      ModuleAsmTargetCPU.clear();
-      if (convertToString(Record, 0, ModuleAsmTargetCPU))
-        return error("Invalid asm target cpu record");
+    case bitc::MODULE_CODE_ASM_PROPERTY: {
+      std::string Str;
+      if (convertToString(Record, 0, Str))
+        return error("Invalid module asm record");
+      size_t SepPos = Str.find('\0');
+      if (SepPos == std::string::npos)
+        return error("Invalid module asm record");
+      if (!Props.set(StringRef(Str.data(), SepPos), Str.substr(SepPos + 1)))
+        return error("Unknown module asm property");
       break;
+    }
     case bitc::MODULE_CODE_ASM: {  // ASM: [strchr x N]
       std::string S;
       if (convertToString(Record, 0, S))
         return error("Invalid asm record");
-      TheModule->appendModuleInlineAsm(Module::GlobalAsmFragment(
-          S, ModuleAsmTargetFeatures, ModuleAsmTargetCPU));
-      ModuleAsmTargetFeatures.clear();
-      ModuleAsmTargetCPU.clear();
+      TheModule->appendModuleInlineAsm(Module::GlobalAsmFragment(S, Props));
+      Props = {};
       break;
     }
     case bitc::MODULE_CODE_DEPLIB: {  // DEPLIB: [strchr x N]
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp 
b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index e9620763820f7..d31ba4b1f338b 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -1548,12 +1548,15 @@ void ModuleBitcodeWriter::writeModuleInfo() {
     writeStringRecord(Stream, bitc::MODULE_CODE_DATALAYOUT, DL, 0 /*TODO*/);
 
   for (const Module::GlobalAsmFragment &Frag : M.getModuleInlineAsm()) {
-    if (!Frag.TargetFeatures.empty())
-      writeStringRecord(Stream, bitc::MODULE_CODE_ASM_TARGET_FEATURES,
-                        Frag.TargetFeatures, 0);
-    if (!Frag.TargetCPU.empty())
-      writeStringRecord(Stream, bitc::MODULE_CODE_ASM_TARGET_CPU,
-                        Frag.TargetCPU, 0);
+    SmallVector<std::pair<StringRef, StringRef>> Props =
+        Frag.Props.getAsStrings();
+    for (auto [Key, Value] : Props) {
+      SmallVector<unsigned, 64> Record;
+      Record.append(Key.begin(), Key.end());
+      Record.push_back(0);
+      Record.append(Value.begin(), Value.end());
+      Stream.EmitRecord(bitc::MODULE_CODE_ASM_PROPERTY, Record);
+    }
     writeStringRecord(Stream, bitc::MODULE_CODE_ASM, Frag.Asm, 0 /*TODO*/);
   }
 
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp 
b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index bc097d4ee14f2..08b3011703cd1 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -614,10 +614,11 @@ bool AsmPrinter::doInitialization(Module &M) {
     OutStreamer->AddComment("Start of file scope inline assembly");
     OutStreamer->addBlankLine();
     for (const Module::GlobalAsmFragment &Frag : M.getModuleInlineAsm()) {
-      if (!Frag.TargetFeatures.empty() || !Frag.TargetCPU.empty()) {
+      if (!Frag.Props.TargetFeatures.empty() || !Frag.Props.TargetCPU.empty()) 
{
         std::unique_ptr<MCSubtargetInfo> AsmSTI(
-            TM.getTarget().createMCSubtargetInfo(
-                TM.getTargetTriple(), Frag.TargetCPU, Frag.TargetFeatures));
+            TM.getTarget().createMCSubtargetInfo(TM.getTargetTriple(),
+                                                 Frag.Props.TargetCPU,
+                                                 Frag.Props.TargetFeatures));
         bool DidPush = emitTargetFeaturePush(*AsmSTI);
         emitInlineAsm(
             Frag.Asm, *AsmSTI, TM.Options.MCOptions, nullptr,
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 9457043f48520..05bc175257074 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -3130,19 +3130,15 @@ void AssemblyWriter::printModule(const Module *M) {
 
     for (const Module::GlobalAsmFragment &Frag : M->getModuleInlineAsm()) {
       Out << "module asm";
-      if (!Frag.TargetFeatures.empty() || !Frag.TargetCPU.empty()) {
+      SmallVector<std::pair<StringRef, StringRef>> Props =
+          Frag.Props.getAsStrings();
+      if (!Props.empty()) {
         ListSeparator LS;
         Out << "(";
-        if (!Frag.TargetFeatures.empty()) {
+        for (auto [Key, Value] : Props) {
           Out << LS;
-          Out << "target_features=\"";
-          printEscapedString(Frag.TargetFeatures, Out);
-          Out << "\"";
-        }
-        if (!Frag.TargetCPU.empty()) {
-          Out << LS;
-          Out << "target_cpu=\"";
-          printEscapedString(Frag.TargetCPU, Out);
+          Out << Key << ": \"";
+          printEscapedString(Value, Out);
           Out << "\"";
         }
         Out << ")";
diff --git a/llvm/lib/IR/Module.cpp b/llvm/lib/IR/Module.cpp
index 40ef7e354da72..34e44e26d5db5 100644
--- a/llvm/lib/IR/Module.cpp
+++ b/llvm/lib/IR/Module.cpp
@@ -977,3 +977,23 @@ ControlFlowGuardMode Module::getControlFlowGuardMode() 
const {
     return static_cast<ControlFlowGuardMode>(CI->getZExtValue());
   return ControlFlowGuardMode::Disabled;
 }
+
+bool Module::GlobalAsmProperties::set(StringRef Name, std::string Value) {
+  if (Name == "target_features")
+    TargetFeatures = std::move(Value);
+  else if (Name == "target_cpu")
+    TargetCPU = std::move(Value);
+  else
+    return false;
+  return true;
+}
+
+SmallVector<std::pair<StringRef, StringRef>>
+Module::GlobalAsmProperties::getAsStrings() const {
+  SmallVector<std::pair<StringRef, StringRef>> Props;
+  if (!TargetFeatures.empty())
+    Props.emplace_back("target_features", TargetFeatures);
+  if (!TargetCPU.empty())
+    Props.emplace_back("target_cpu", TargetCPU);
+  return Props;
+}
diff --git a/llvm/lib/Object/ModuleSymbolTable.cpp 
b/llvm/lib/Object/ModuleSymbolTable.cpp
index b60e35fba1507..44f717ec12323 100644
--- a/llvm/lib/Object/ModuleSymbolTable.cpp
+++ b/llvm/lib/Object/ModuleSymbolTable.cpp
@@ -94,8 +94,8 @@ initializeRecordStreamer(const Module &M,
     return;
 
   for (const Module::GlobalAsmFragment &Frag : M.getModuleInlineAsm()) {
-    std::unique_ptr<MCSubtargetInfo> STI(
-        T->createMCSubtargetInfo(TT, Frag.TargetCPU, Frag.TargetFeatures));
+    std::unique_ptr<MCSubtargetInfo> STI(T->createMCSubtargetInfo(
+        TT, Frag.Props.TargetCPU, Frag.Props.TargetFeatures));
     if (!STI)
       return;
 
diff --git a/llvm/test/Assembler/module-asm-invalid.ll 
b/llvm/test/Assembler/module-asm-invalid.ll
index de2a1c32ca6b5..34e5b71e683a9 100644
--- a/llvm/test/Assembler/module-asm-invalid.ll
+++ b/llvm/test/Assembler/module-asm-invalid.ll
@@ -5,21 +5,21 @@
 ; RUN: not llvm-as -disable-output < %t/missing_paren.ll 2>&1 | FileCheck 
%t/missing_paren.ll
 
 ;--- invalid_prop.ll
-; CHECK: expected one of 'target_features' or 'target_cpu'
-module asm(foo="bar")
+; CHECK: unknown property name
+module asm(foo: "bar")
     "asm"
 
 ;--- missing_value1.ll
-; CHECK: expected '='
+; CHECK: expected property name followed by ':'
 module asm(target_features)
     "asm"
 
 ;--- missing_value2.ll
 ; CHECK: expected string constant
-module asm(target_features=)
+module asm(target_features:)
     "asm"
 
 ;--- missing_paren.ll
 ; CHECK: expected ',' or ')'
-module asm(target_features="bar"
+module asm(target_features: "bar"
     "asm"
diff --git a/llvm/test/Bitcode/module-asm.ll b/llvm/test/Bitcode/module-asm.ll
index 4d2144c0cba33..406f25c5dd28c 100644
--- a/llvm/test/Bitcode/module-asm.ll
+++ b/llvm/test/Bitcode/module-asm.ll
@@ -2,12 +2,12 @@
 
 ; CHECK: module asm
 ; CHECK:     "asm line 0"
-; CHECK: module asm(target_features="+foo", target_cpu="foo_cpu")
+; CHECK: module asm(target_features: "+foo", target_cpu: "foo_cpu")
 ; CHECK:     "asm line 1"
 ; CHECK:     "asm line 2"
-; CHECK: module asm(target_features="+bar")
+; CHECK: module asm(target_features: "+bar")
 ; CHECK:     "asm line 3"
-; CHECK: module asm(target_cpu="bar_cpu")
+; CHECK: module asm(target_cpu: "bar_cpu")
 ; CHECK:     "asm line 4"
 ; CHECK: module asm
 ; CHECK:     "asm line 5"
@@ -15,14 +15,14 @@
 module asm
     "asm line 0"
 
-module asm(target_features="+foo", target_cpu="foo_cpu")
+module asm(target_features: "+foo", target_cpu: "foo_cpu")
     "asm line 1"
     "asm line 2"
 
-module asm(target_features="+bar")
+module asm(target_features: "+bar")
     "asm line 3"
 
-module asm(target_cpu="bar_cpu")
+module asm(target_cpu: "bar_cpu")
     "asm line 4"
 
 module asm
diff --git a/llvm/test/CodeGen/RISCV/module-asm-features.ll 
b/llvm/test/CodeGen/RISCV/module-asm-features.ll
index 3e9483cbf4461..ab16acab48688 100644
--- a/llvm/test/CodeGen/RISCV/module-asm-features.ll
+++ b/llvm/test/CodeGen/RISCV/module-asm-features.ll
@@ -10,7 +10,7 @@
 ; CHECK: fld ft0, 0(sp)
 ; EXTRA-FEATURES: .option pop
 
-module asm(target_features="+d")
+module asm(target_features: "+d")
     ".globl func"
     "func:"
     "fld f0, 0(sp)"
diff --git a/llvm/test/LTO/RISCV/module-asm.ll 
b/llvm/test/LTO/RISCV/module-asm.ll
index c92343b457ff7..73320185e778a 100644
--- a/llvm/test/LTO/RISCV/module-asm.ll
+++ b/llvm/test/LTO/RISCV/module-asm.ll
@@ -11,7 +11,7 @@
 target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
 target triple = "riscv64-unknown-linux-gnu"
 
-module asm(target_features="+d")
+module asm(target_features: "+d")
     ".globl func"
     "func:"
     "fld f0, 0(sp)"
diff --git a/llvm/test/Linker/Inputs/module-asm.ll 
b/llvm/test/Linker/Inputs/module-asm.ll
index 01c55a875af4f..b097c7067ccc1 100644
--- a/llvm/test/Linker/Inputs/module-asm.ll
+++ b/llvm/test/Linker/Inputs/module-asm.ll
@@ -1,2 +1,2 @@
-module asm(target_features="+bar")
+module asm(target_features: "+bar")
     "asm 2"
diff --git a/llvm/test/Linker/module-asm.ll b/llvm/test/Linker/module-asm.ll
index 9f43c7a22514d..6c600981d72a5 100644
--- a/llvm/test/Linker/module-asm.ll
+++ b/llvm/test/Linker/module-asm.ll
@@ -1,9 +1,9 @@
 ; RUN: llvm-link %s %p/Inputs/module-asm.ll -S | FileCheck %s
 
-; CHECK: module asm(target_features="+foo")
+; CHECK: module asm(target_features: "+foo")
 ; CHECK-NEXT: "asm 1"
-; CHECK: module asm(target_features="+bar")
+; CHECK: module asm(target_features: "+bar")
 ; CHECK-NEXT: "asm 2"
 
-module asm(target_features="+foo")
+module asm(target_features: "+foo")
     "asm 1"

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to