https://github.com/asavonic created
https://github.com/llvm/llvm-project/pull/174995
`ModuleSymbolTable` used to get symbols from global inline assembly by running
the target asm parser with a generic CPU and no target flags.
This caused problems for top-level inline assembly where instructions require a
target feature. For example, in `global-inline-asm-flags.c` we have PACIB and
RETAB instructions that need a `+pauth` target flag. This test used to fail
with a diagnostic:
```
<inline asm>:4:1: error: instruction requires: pauth
4 | pacib x30, x27
<inline asm>:5:1: error: instruction requires: pauth
5 | retab
```
The patch resolves this problem by moving assembly parsing to clang, where we
have correct CPU and Features and can initialize asm parser correctly. Clang
now records all symbols and symvers as module flags with
`ModFlagBehavior::Append`. This ensures that when modules are linked, these
flags are consistent with (merged) inline asm.
This issue was previously discussed in
https://discourse.llvm.org/t/rfc-target-cpu-and-features-for-module-level-inline-assembly/74713,
and in the issue #67698 "LTO scan of module-level inline assembly does not
respect CPU".
>From 130ddbabb21e71eaf1cec3be99993fec0de6cd69 Mon Sep 17 00:00:00 2001
From: Andrew Savonichev <[email protected]>
Date: Fri, 19 Dec 2025 16:12:39 +0900
Subject: [PATCH] [clang][LTO] Emit symbols for global inline assembly as
module flags
ModuleSymbolTable used to get symbols from global inline assembly by
running the target asm parser with a generic CPU and no target flags.
This caused problems for top-level inline assembly where instructions
require a target feature. For example, in global-inline-asm-flags.c we
have PACIB and RETAB instructions that need a +pauth target flag. This
test used to fail with a diagnostic:
<inline asm>:4:1: error: instruction requires: pauth
4 | pacib x30, x27
<inline asm>:5:1: error: instruction requires: pauth
5 | retab
The patch resolves this problem by moving assembly parsing to clang,
where we have correct CPU and Features and can initialize asm parser
correctly. Clang now records all symbols and symvers as module flags
with ModFlagBehavior::Append. This ensures that when modules are
linked, these flags are consistent with (merged) inline asm.
This issue was previously discussed in
https://discourse.llvm.org/t/rfc-target-cpu-and-features-for-module-level-inline-assembly/74713,
and in the issue #67698 "LTO scan of module-level inline assembly does not
respect CPU".
---
clang/lib/CodeGen/CodeGenModule.cpp | 79 +++++++++
.../CodeGen/AArch64/global-inline-asm-flags.c | 42 +++++
llvm/include/llvm/Object/ModuleSymbolTable.h | 32 +++-
llvm/lib/Object/ModuleSymbolTable.cpp | 150 ++++++++++++------
.../AArch64/Inputs/global-inline-asm-flags.ll | 30 ++++
.../LTO/AArch64/global-inline-asm-flags.ll | 68 ++++++++
6 files changed, 347 insertions(+), 54 deletions(-)
create mode 100644 clang/test/CodeGen/AArch64/global-inline-asm-flags.c
create mode 100644 llvm/test/LTO/AArch64/Inputs/global-inline-asm-flags.ll
create mode 100644 llvm/test/LTO/AArch64/global-inline-asm-flags.ll
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp
b/clang/lib/CodeGen/CodeGenModule.cpp
index 85ed38f144627..99955fd2058d0 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -55,10 +55,13 @@
#include "llvm/IR/AttributeMask.h"
#include "llvm/IR/CallingConv.h"
#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ProfileSummary.h"
+#include "llvm/Object/ModuleSymbolTable.h"
+#include "llvm/Object/SymbolicFile.h"
#include "llvm/ProfileData/InstrProfReader.h"
#include "llvm/ProfileData/SampleProf.h"
#include "llvm/Support/CRC.h"
@@ -950,6 +953,76 @@ static bool isStackProtectorOn(const LangOptions &LangOpts,
return LangOpts.getStackProtector() == Mode;
}
+// Emit module flags for symbols and symvers defined in global inline
+// assembly. This allows LLVM IR tools to build a symbol table for an
+// IR module without knowing exact CPU and Features required to parse
+// its global inline assembly.
+static void emitGlobalAsmSymbols(llvm::Module &M, StringRef CPU,
+ StringRef Features) {
+ llvm::LLVMContext &Ctx = M.getContext();
+ bool HaveErrors = false;
+
+ auto DiagHandler = [&](const llvm::DiagnosticInfo &DI) {
+ // Ignore diagnostics from the assembly parser.
+ //
+ // Errors in assembly mean that we cannot build a symbol table
+ // from it. However, we do not diagnose them here in Clang,
+ // because we don't know if the Module is ever going to actually
+ // reach CodeGen where this would matter.
+ if (DI.getSeverity() == llvm::DS_Error) {
+ HaveErrors = true;
+ }
+ };
+
+ // Build global-asm-symbols as a list of pairs (name, flags bitmask).
+ SmallVector<llvm::Metadata *, 16> Symbols;
+ llvm::ModuleSymbolTable::CollectAsmSymbols(
+ M,
+ [&](StringRef Name, llvm::object::BasicSymbolRef::Flags Flags) {
+ Symbols.push_back(llvm::MDNode::get(
+ Ctx, {llvm::MDString::get(Ctx, Name),
+ llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(
+ llvm::Type::getInt32Ty(Ctx), Flags))}));
+ },
+ DiagHandler, CPU, Features);
+
+ if (Symbols.empty() || HaveErrors) {
+ return;
+ }
+
+ M.addModuleFlag(llvm::Module::Append, "global-asm-symbols",
+ llvm::MDNode::get(Ctx, Symbols));
+
+ // Build global-asm-symvers as a list of lists (name, followed by all
+ // aliases).
+ llvm::MapVector<StringRef, SmallVector<llvm::Metadata *, 2>> SymversMap;
+ llvm::ModuleSymbolTable::CollectAsmSymvers(
+ M,
+ [&](StringRef Name, StringRef Alias) {
+ auto ItNew = SymversMap.try_emplace(Name);
+ SmallVector<llvm::Metadata *, 2> &Aliases = ItNew.first->second;
+ if (ItNew.second) {
+ // If it is a new list, insert the primary name at the
+ // front.
+ Aliases.push_back(llvm::MDString::get(Ctx, Name));
+ }
+ Aliases.push_back(llvm::MDString::get(Ctx, Alias));
+ },
+ DiagHandler, CPU, Features);
+
+ if (SymversMap.empty() || HaveErrors) {
+ return;
+ }
+
+ SmallVector<llvm::Metadata *, 16> Symvers;
+ for (const auto &KV : SymversMap) {
+ Symvers.push_back(llvm::MDNode::get(Ctx, KV.second));
+ }
+
+ M.addModuleFlag(llvm::Module::Append, "global-asm-symvers",
+ llvm::MDNode::get(Ctx, Symvers));
+}
+
void CodeGenModule::Release() {
Module *Primary = getContext().getCurrentNamedModule();
if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
@@ -1562,6 +1635,12 @@ void CodeGenModule::Release() {
getModule().addModuleFlag(llvm::Module::Error, "MaxTLSAlign",
getContext().getTargetInfo().getMaxTLSAlign());
+ // Emit module flags for global inline assembly symbols.
+ if (!TheModule.getModuleInlineAsm().empty()) {
+ emitGlobalAsmSymbols(TheModule, getTarget().getTargetOpts().CPU,
+ llvm::join(getTarget().getTargetOpts().Features,
","));
+ }
+
getTargetCodeGenInfo().emitTargetGlobals(*this);
getTargetCodeGenInfo().emitTargetMetadata(*this, MangledDeclNames);
diff --git a/clang/test/CodeGen/AArch64/global-inline-asm-flags.c
b/clang/test/CodeGen/AArch64/global-inline-asm-flags.c
new file mode 100644
index 0000000000000..7d4e82d95e09a
--- /dev/null
+++ b/clang/test/CodeGen/AArch64/global-inline-asm-flags.c
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -target-feature +pauth -flto=thin
-emit-llvm -o - %s | FileCheck %s
+// REQUIRES: aarch64-registered-target
+
+asm (
+ ".text" "\n"
+ ".balign 16" "\n"
+ ".globl foo\n"
+ "pacib x30, x27" "\n"
+ "retab" "\n"
+ ".symver foo, foo@VER" "\n"
+ ".symver foo, foo@ANOTHERVER" "\n"
+ ".globl bar\n"
+ "pacib x30, x27" "\n"
+ "retab" "\n"
+ ".symver bar, bar@VER" "\n"
+ ".previous" "\n"
+);
+
+// CHECK: module asm ".text"
+// CHECK: module asm ".balign 16"
+// CHECK: module asm ".globl foo"
+// CHECK: module asm "pacib x30, x27"
+// CHECK: module asm "retab"
+// CHECK: module asm ".symver foo, foo@VER"
+// CHECK: module asm ".symver foo, foo@ANOTHERVER"
+// CHECK: module asm ".globl bar"
+// CHECK: module asm "pacib x30, x27"
+// CHECK: module asm "retab"
+// CHECK: module asm ".symver bar, bar@VER"
+// CHECK: module asm ".previous"
+
+// CHECK: !{{.*}} = !{i32 5, !"global-asm-symbols", ![[SYM:[0-9]+]]}
+// CHECK: ![[SYM]] = !{![[SBAR1:[0-9]+]], ![[SBAR2:[0-9]+]],
![[SBAR3:[0-9]+]], ![[SFOO1:[0-9]+]], ![[SFOO2:[0-9]+]]}
+// CHECK: ![[SBAR1]] = !{!"bar", i32 2051}
+// CHECK: ![[SBAR2]] = !{!"bar@VER", i32 2051}
+// CHECK: ![[SBAR3]] = !{!"foo@ANOTHERVER", i32 2051}
+// CHECK: ![[SFOO1]] = !{!"foo", i32 2051}
+// CHECK: ![[SFOO2]] = !{!"foo@VER", i32 2051}
+// CHECK: !{{.*}} = !{i32 5, !"global-asm-symvers", ![[SYMVER:[0-9]+]]}
+// CHECK: ![[SYMVER]] = !{![[VFOO:[0-9]+]], ![[VBAR:[0-9]+]]}
+// CHECK: ![[VFOO:[0-9]+]] = !{!"foo", !"foo@VER", !"foo@ANOTHERVER"}
+// CHECK: ![[VBAR:[0-9]+]] = !{!"bar", !"bar@VER"}
diff --git a/llvm/include/llvm/Object/ModuleSymbolTable.h
b/llvm/include/llvm/Object/ModuleSymbolTable.h
index 564ce76b3feb1..23604e1f9c26b 100644
--- a/llvm/include/llvm/Object/ModuleSymbolTable.h
+++ b/llvm/include/llvm/Object/ModuleSymbolTable.h
@@ -30,6 +30,7 @@ namespace llvm {
class GlobalValue;
class Module;
+class DiagnosticInfo;
class ModuleSymbolTable {
public:
@@ -45,7 +46,8 @@ class ModuleSymbolTable {
public:
ArrayRef<Symbol> symbols() const { return SymTab; }
- LLVM_ABI void addModule(Module *M);
+ LLVM_ABI void addModule(Module *M, StringRef CPU = "",
+ StringRef Features = "");
LLVM_ABI void printSymbolName(raw_ostream &OS, Symbol S) const;
LLVM_ABI uint32_t getSymbolFlags(Symbol S) const;
@@ -55,18 +57,38 @@ class ModuleSymbolTable {
///
/// For each found symbol, call \p AsmSymbol with the name of the symbol
found
/// and the associated flags.
+ ///
+ /// The function attempts to use global-asm-symbols module flag if
+ /// it is present. Otherwise it parses assembly with the provided \p
+ /// CPU and \p Features and calls \p DiagHandler for any
+ /// diagnostics.
+ ///
+ /// If \p DiagHandler is not provided, the function calls
+ /// LLVMContext::diagnose() instead.
LLVM_ABI static void CollectAsmSymbols(
const Module &M,
- function_ref<void(StringRef, object::BasicSymbolRef::Flags)> AsmSymbol);
+ function_ref<void(StringRef, object::BasicSymbolRef::Flags)> AsmSymbol,
+ function_ref<void(const DiagnosticInfo &DI)> DiagHandler = nullptr,
+ StringRef CPU = "", StringRef Features = "");
/// Parse inline ASM and collect the symvers directives that are defined in
/// the current module.
///
/// For each found symbol, call \p AsmSymver with the name of the symbol and
/// its alias.
- LLVM_ABI static void
- CollectAsmSymvers(const Module &M,
- function_ref<void(StringRef, StringRef)> AsmSymver);
+ ///
+ /// The function attempts to use global-asm-symvers module flag if
+ /// it is present. Otherwise it parses assembly with the provided \p
+ /// CPU and \p Features and calls \p DiagHandler for any
+ /// diagnostics.
+ ///
+ /// If \p DiagHandler is not provided, the function calls
+ /// LLVMContext::diagnose() instead.
+
+ LLVM_ABI static void CollectAsmSymvers(
+ const Module &M, function_ref<void(StringRef, StringRef)> AsmSymver,
+ function_ref<void(const DiagnosticInfo &DI)> DiagHandler = nullptr,
+ StringRef CPU = "", StringRef Features = "");
};
} // end namespace llvm
diff --git a/llvm/lib/Object/ModuleSymbolTable.cpp
b/llvm/lib/Object/ModuleSymbolTable.cpp
index 9442becdb7d33..7bb6e22a7ca8a 100644
--- a/llvm/lib/Object/ModuleSymbolTable.cpp
+++ b/llvm/lib/Object/ModuleSymbolTable.cpp
@@ -15,6 +15,7 @@
#include "llvm/Object/ModuleSymbolTable.h"
#include "RecordStreamer.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/IR/Constants.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalAlias.h"
@@ -49,7 +50,8 @@
using namespace llvm;
using namespace object;
-void ModuleSymbolTable::addModule(Module *M) {
+void ModuleSymbolTable::addModule(Module *M, StringRef CPU,
+ StringRef Features) {
if (FirstMod)
assert(FirstMod->getTargetTriple() == M->getTargetTriple());
else
@@ -58,15 +60,19 @@ void ModuleSymbolTable::addModule(Module *M) {
for (GlobalValue &GV : M->global_values())
SymTab.push_back(&GV);
- CollectAsmSymbols(*M, [this](StringRef Name, BasicSymbolRef::Flags Flags) {
- SymTab.push_back(new (AsmSymbols.Allocate())
- AsmSymbol(std::string(Name), Flags));
- });
+ CollectAsmSymbols(
+ *M,
+ [this](StringRef Name, BasicSymbolRef::Flags Flags) {
+ SymTab.push_back(new (AsmSymbols.Allocate())
+ AsmSymbol(std::string(Name), Flags));
+ },
+ /*DiagHandler=*/nullptr, CPU, Features);
}
-static void
-initializeRecordStreamer(const Module &M,
- function_ref<void(RecordStreamer &)> Init) {
+static void initializeRecordStreamer(
+ const Module &M, StringRef CPU, StringRef Features,
+ function_ref<void(RecordStreamer &)> Init,
+ function_ref<void(const DiagnosticInfo &DI)> DiagHandler) {
// This function may be called twice, once for ModuleSummaryIndexAnalysis and
// the other when writing the IR symbol table. If parsing inline assembly has
// caused errors in the first run, suppress the second run.
@@ -90,7 +96,8 @@ initializeRecordStreamer(const Module &M,
if (!MAI)
return;
- std::unique_ptr<MCSubtargetInfo> STI(T->createMCSubtargetInfo(TT, "", ""));
+ std::unique_ptr<MCSubtargetInfo> STI(
+ T->createMCSubtargetInfo(TT, CPU, Features));
if (!STI)
return;
@@ -121,8 +128,12 @@ initializeRecordStreamer(const Module &M,
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));
+ DiagnosticInfoSrcMgr Diag(SMD, M.getName(), IsInlineAsm, /*LocCookie=*/0);
+ if (DiagHandler) {
+ DiagHandler(Diag);
+ return;
+ }
+ M.getContext().diagnose(Diag);
});
// Module-level inline asm is assumed to use At&t syntax (see
@@ -138,39 +149,60 @@ initializeRecordStreamer(const Module &M,
void ModuleSymbolTable::CollectAsmSymbols(
const Module &M,
- function_ref<void(StringRef, BasicSymbolRef::Flags)> AsmSymbol) {
- initializeRecordStreamer(M, [&](RecordStreamer &Streamer) {
- Streamer.flushSymverDirectives();
-
- for (auto &KV : Streamer) {
- StringRef Key = KV.first();
- RecordStreamer::State Value = KV.second;
- // FIXME: For now we just assume that all asm symbols are executable.
- uint32_t Res = BasicSymbolRef::SF_Executable;
- switch (Value) {
- case RecordStreamer::NeverSeen:
- llvm_unreachable("NeverSeen should have been replaced earlier");
- case RecordStreamer::DefinedGlobal:
- Res |= BasicSymbolRef::SF_Global;
- break;
- case RecordStreamer::Defined:
- break;
- case RecordStreamer::Global:
- case RecordStreamer::Used:
- Res |= BasicSymbolRef::SF_Undefined;
- Res |= BasicSymbolRef::SF_Global;
- break;
- case RecordStreamer::DefinedWeak:
- Res |= BasicSymbolRef::SF_Weak;
- Res |= BasicSymbolRef::SF_Global;
- break;
- case RecordStreamer::UndefinedWeak:
- Res |= BasicSymbolRef::SF_Weak;
- Res |= BasicSymbolRef::SF_Undefined;
- }
- AsmSymbol(Key, BasicSymbolRef::Flags(Res));
+ function_ref<void(StringRef, BasicSymbolRef::Flags)> AsmSymbol,
+ function_ref<void(const DiagnosticInfo &DI)> DiagHandler, StringRef CPU,
+ StringRef Features) {
+
+ MDTuple *SymbolsMD =
+ dyn_cast_if_present<MDTuple>(M.getModuleFlag("global-asm-symbols"));
+
+ if (SymbolsMD) {
+ for (const Metadata *MD : SymbolsMD->operands()) {
+ const MDTuple *SymMD = cast<MDTuple>(MD);
+ const MDString *Name = cast<MDString>(SymMD->getOperand(0));
+ const ConstantInt *Flags =
+ mdconst::extract<ConstantInt>(SymMD->getOperand(1));
+ AsmSymbol(Name->getString(),
+ static_cast<BasicSymbolRef::Flags>(Flags->getZExtValue()));
}
- });
+ return;
+ }
+
+ initializeRecordStreamer(
+ M, CPU, Features,
+ [&](RecordStreamer &Streamer) {
+ Streamer.flushSymverDirectives();
+
+ for (auto &KV : Streamer) {
+ StringRef Key = KV.first();
+ RecordStreamer::State Value = KV.second;
+ // FIXME: For now we just assume that all asm symbols are executable.
+ uint32_t Res = BasicSymbolRef::SF_Executable;
+ switch (Value) {
+ case RecordStreamer::NeverSeen:
+ llvm_unreachable("NeverSeen should have been replaced earlier");
+ case RecordStreamer::DefinedGlobal:
+ Res |= BasicSymbolRef::SF_Global;
+ break;
+ case RecordStreamer::Defined:
+ break;
+ case RecordStreamer::Global:
+ case RecordStreamer::Used:
+ Res |= BasicSymbolRef::SF_Undefined;
+ Res |= BasicSymbolRef::SF_Global;
+ break;
+ case RecordStreamer::DefinedWeak:
+ Res |= BasicSymbolRef::SF_Weak;
+ Res |= BasicSymbolRef::SF_Global;
+ break;
+ case RecordStreamer::UndefinedWeak:
+ Res |= BasicSymbolRef::SF_Weak;
+ Res |= BasicSymbolRef::SF_Undefined;
+ }
+ AsmSymbol(Key, BasicSymbolRef::Flags(Res));
+ }
+ },
+ DiagHandler);
// In ELF, object code generated for x86-32 and some code models of x86-64
may
// reference the special symbol _GLOBAL_OFFSET_TABLE_ that is not used in the
@@ -188,12 +220,32 @@ void ModuleSymbolTable::CollectAsmSymbols(
}
void ModuleSymbolTable::CollectAsmSymvers(
- const Module &M, function_ref<void(StringRef, StringRef)> AsmSymver) {
- initializeRecordStreamer(M, [&](RecordStreamer &Streamer) {
- for (auto &KV : Streamer.symverAliases())
- for (auto &Alias : KV.second)
- AsmSymver(KV.first->getName(), Alias);
- });
+ const Module &M, function_ref<void(StringRef, StringRef)> AsmSymver,
+ function_ref<void(const DiagnosticInfo &DI)> DiagHandler, StringRef CPU,
+ StringRef Features) {
+
+ MDTuple *SymversMD =
+ dyn_cast_if_present<MDTuple>(M.getModuleFlag("global-asm-symvers"));
+
+ if (SymversMD) {
+ for (const Metadata *MD : SymversMD->operands()) {
+ const MDTuple *SymverMD = cast<MDTuple>(MD);
+ StringRef Name = cast<MDString>(SymverMD->getOperand(0))->getString();
+ for (unsigned i = 1; i < SymverMD->getNumOperands(); ++i) {
+ AsmSymver(Name, cast<MDString>(SymverMD->getOperand(i))->getString());
+ }
+ }
+ return;
+ }
+
+ initializeRecordStreamer(
+ M, CPU, Features,
+ [&](RecordStreamer &Streamer) {
+ for (auto &KV : Streamer.symverAliases())
+ for (auto &Alias : KV.second)
+ AsmSymver(KV.first->getName(), Alias);
+ },
+ DiagHandler);
}
void ModuleSymbolTable::printSymbolName(raw_ostream &OS, Symbol S) const {
diff --git a/llvm/test/LTO/AArch64/Inputs/global-inline-asm-flags.ll
b/llvm/test/LTO/AArch64/Inputs/global-inline-asm-flags.ll
new file mode 100644
index 0000000000000..349d8e37fc488
--- /dev/null
+++ b/llvm/test/LTO/AArch64/Inputs/global-inline-asm-flags.ll
@@ -0,0 +1,30 @@
+target datalayout =
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-none-linux-gnu"
+
+module asm ".text"
+module asm ".balign 16"
+module asm ".globl foo"
+module asm "pacib x30, x27"
+module asm "retab"
+module asm ".symver foo, foo@VER"
+module asm ".symver foo, foo@ANOTHERVER"
+module asm ".globl bar"
+module asm "pacib x30, x27"
+module asm "retab"
+module asm ".symver bar, bar@VER"
+module asm ".previous"
+
+!llvm.module.flags = !{!1, !8}
+
+!1 = !{i32 5, !"global-asm-symbols", !2}
+!2 = !{!3, !4, !5, !6, !7}
+!3 = !{!"bar", i32 2051}
+!4 = !{!"bar@VER", i32 2051}
+!5 = !{!"foo@ANOTHERVER", i32 2051}
+!6 = !{!"foo", i32 2051}
+!7 = !{!"foo@VER", i32 2051}
+!8 = !{i32 5, !"global-asm-symvers", !9}
+!9 = !{!10, !11}
+!10 = !{!"foo", !"foo@VER", !"foo@ANOTHERVER"}
+!11 = !{!"bar", !"bar@VER"}
+
diff --git a/llvm/test/LTO/AArch64/global-inline-asm-flags.ll
b/llvm/test/LTO/AArch64/global-inline-asm-flags.ll
new file mode 100644
index 0000000000000..173cc014c8852
--- /dev/null
+++ b/llvm/test/LTO/AArch64/global-inline-asm-flags.ll
@@ -0,0 +1,68 @@
+; RUN: llvm-as %s -o %t1.bc
+; RUN: llvm-as %p/Inputs/global-inline-asm-flags.ll -o %t2.bc
+; RUN: llvm-lto -save-merged-module -filetype=asm -mattr=+pauth %t1.bc %t2.bc
-o %t3
+; RUN: llvm-dis %t3.merged.bc -o - | FileCheck %s
+
+; Note that -mattr=+pauth for llvm-lto is still required, because it
+; runs full CodeGen at the end. Symbols and Symvers are still
+; extracted from metadata.
+
+; CHECK: module asm ".text"
+; CHECK: module asm ".balign 16"
+; CHECK: module asm ".globl baz"
+; CHECK: module asm "pacib x30, x27"
+; CHECK: module asm "retab"
+; CHECK: module asm ".symver baz, baz@VER"
+; CHECK: module asm ".symver foo, foo@LINKEDVER"
+; CHECK: module asm ".previous"
+; CHECK: module asm ".text"
+; CHECK: module asm ".balign 16"
+; CHECK: module asm ".globl foo"
+; CHECK: module asm "pacib x30, x27"
+; CHECK: module asm "retab"
+; CHECK: module asm ".symver foo, foo@VER"
+; CHECK: module asm ".symver foo, foo@ANOTHERVER"
+; CHECK: module asm ".globl bar"
+; CHECK: module asm "pacib x30, x27"
+; CHECK: module asm "retab"
+; CHECK: module asm ".symver bar, bar@VER"
+; CHECK: module asm ".previous"
+
+; CHECK: !{{[0-9]+}} = distinct !{i32 5, !"global-asm-symbols",
![[SYM:[0-9]+]]}
+; CHECK: ![[SYM]] = distinct !{![[SBAZ1:[0-9]+]], ![[SBAZ2:[0-9]+]],
![[SFOO1:[0-9]+]], ![[SBAR1:[0-9]+]], ![[SBAR2:[0-9]+]], ![[SFOO2:[0-9]+]],
![[SFOO3:[0-9]+]], ![[SFOO4:[0-9]+]]}
+; CHECK: ![[SBAZ1]] = !{!"baz", i32 2051}
+; CHECK: ![[SBAZ2]] = !{!"baz@VER", i32 2051}
+; CHECK: ![[SFOO1]] = !{!"foo@LINKEDVER", i32 2051}
+; CHECK: ![[SBAR1]] = !{!"bar", i32 2051}
+; CHECK: ![[SBAR2]] = !{!"bar@VER", i32 2051}
+; CHECK: ![[SFOO2]] = !{!"foo@ANOTHERVER", i32 2051}
+; CHECK: ![[SFOO3]] = !{!"foo", i32 2051}
+; CHECK: ![[SFOO4]] = !{!"foo@VER", i32 2051}
+
+; CHECK: !{{[0-9]+}} = distinct !{i32 5, !"global-asm-symvers",
![[SYMVER:[0-9]+]]}
+; CHECK: ![[SYMVER]] = distinct !{![[VBAZ:[0-9]+]], ![[VFOO1:[0-9]+]],
![[VFOO2:[0-9]+]], ![[VBAR:[0-9]+]]}
+; CHECK: ![[VBAZ]] = !{!"baz", !"baz@VER"}
+; CHECK: ![[VFOO1]] = !{!"foo", !"foo@LINKEDVER"}
+; CHECK: ![[VFOO2]] = !{!"foo", !"foo@VER", !"foo@ANOTHERVER"}
+; CHECK: ![[VBAR]] = !{!"bar", !"bar@VER"}
+
+module asm ".text"
+module asm ".balign 16"
+module asm ".globl baz"
+module asm "pacib x30, x27"
+module asm "retab"
+module asm ".symver baz, baz@VER"
+module asm ".symver foo, foo@LINKEDVER"
+module asm ".previous"
+
+!llvm.module.flags = !{!0, !5}
+
+!0 = !{i32 5, !"global-asm-symbols", !1}
+!1 = !{!2, !3, !4}
+!2 = !{!"baz", i32 2051}
+!3 = !{!"baz@VER", i32 2051}
+!4 = !{!"foo@LINKEDVER", i32 2051}
+!5 = !{i32 5, !"global-asm-symvers", !6}
+!6 = !{!7, !8}
+!7 = !{!"baz", !"baz@VER"}
+!8 = !{!"foo", !"foo@LINKEDVER"}
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits