[llvm] [SPIR-V] Add NonSemantic.AuxData emission (-spirv-preserve-auxdata) (PR #200002)

Arseniy Obolenskiy via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 8 01:30:41 PDT 2026


https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/200002

>From 80e3a2be07648c5bf7d9a58acedb95a7459b3c34 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Wed, 27 May 2026 17:14:14 +0200
Subject: [PATCH 1/9] [SPIR-V] Skip available_externally functions in module
 analysis

These definitions have no MachineFunction so the module analysis and AsmPrinter would assert on a null MF or an invalid global object register. Filter them out using isDeclarationForLinker
---
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |  2 +-
 llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp |  4 +--
 .../linkage/available-externally-function.ll  | 28 +++++++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 3f10d52a61b0e..803f2a012f8f4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -569,7 +569,7 @@ void SPIRVAsmPrinter::outputExecutionMode(const Module &M) {
     const Function &F = *FI;
     // Only operands of OpEntryPoint instructions are allowed to be
     // <Entry Point> operands of OpExecutionMode
-    if (F.isDeclaration() || !isEntryPoint(F))
+    if (F.isDeclarationForLinker() || !isEntryPoint(F))
       continue;
     MCRegister FReg = MAI->getGlobalObjReg(&F);
     assert(FReg.isValid());
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index 769945db154bc..4a26745725451 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -666,7 +666,7 @@ static void collectOtherInstr(MachineInstr &MI, SPIRV::ModuleAnalysisInfo &MAI,
 void SPIRVModuleAnalysis::processOtherInstrs(const Module &M) {
   InstrTraces IS;
   for (const Function &F : M) {
-    if (F.isDeclaration())
+    if (F.isDeclarationForLinker())
       continue;
     MachineFunction *MF = MMI->getMachineFunction(F);
     assert(MF);
@@ -725,7 +725,7 @@ void SPIRVModuleAnalysis::processOtherInstrs(const Module &M) {
 // numbered.
 void SPIRVModuleAnalysis::numberRegistersGlobally(const Module &M) {
   for (const Function &F : M) {
-    if (F.isDeclaration())
+    if (F.isDeclarationForLinker())
       continue;
     MachineFunction *MF = MMI->getMachineFunction(F);
     assert(MF);
diff --git a/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll b/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
new file mode 100644
index 0000000000000..335118ef3e46a
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
@@ -0,0 +1,28 @@
+; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --implicit-check-not=ae_kernel --implicit-check-not=ae_func
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --implicit-check-not=ae_kernel --implicit-check-not=ae_func
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; available_externally definitions must not be emitted: they have no
+; MachineFunction, so the module analysis and AsmPrinter would otherwise
+; assert.
+
+; CHECK: OpName %[[#used:]] "used_kernel"
+; CHECK: %[[#used]] = OpFunction
+
+define available_externally spir_kernel void @ae_kernel(ptr addrspace(1) %out) {
+entry:
+  store i32 1, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+define available_externally spir_func i32 @ae_func(i32 %x) {
+entry:
+  %r = add i32 %x, 1
+  ret i32 %r
+}
+
+define spir_kernel void @used_kernel(ptr addrspace(1) %out, i32 %n) {
+entry:
+  store i32 %n, ptr addrspace(1) %out, align 4
+  ret void
+}

>From 5cc7250a28a722679a8e751199311dee3d8d458c Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 28 May 2026 17:20:43 +0200
Subject: [PATCH 2/9] Rewrok the patch

---
 llvm/lib/Target/SPIRV/CMakeLists.txt          |   1 +
 .../SPIRV/MCTargetDesc/SPIRVBaseInfo.cpp      |   5 +-
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |  15 ++-
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp | 123 ++++++++++++++++++
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h   |  56 ++++++++
 llvm/lib/Target/SPIRV/SPIRVBuiltins.td        |   1 +
 llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp |   4 +-
 .../Target/SPIRV/SPIRVPrepareFunctions.cpp    |   7 +
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          |   5 +
 llvm/lib/Target/SPIRV/SPIRVUtils.h            |   2 +
 .../linkage/available-externally-function.ll  |  51 ++++++--
 11 files changed, 252 insertions(+), 18 deletions(-)
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h

diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 36ad7f976d6e9..cf77264a95d63 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -52,6 +52,7 @@ add_llvm_target(SPIRVCodeGen
   SPIRVTypeInst.cpp
   SPIRVUtils.cpp
   SPIRVNonSemanticDebugHandler.cpp
+  SPIRVAuxDataHandler.cpp
   SPIRVCBufferAccess.cpp
   SPIRVPushConstantAccess.cpp
 
diff --git a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVBaseInfo.cpp b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVBaseInfo.cpp
index d6b6079810471..4daac32d84215 100644
--- a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVBaseInfo.cpp
+++ b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVBaseInfo.cpp
@@ -235,6 +235,8 @@ std::string getExtInstSetName(SPIRV::InstructionSet::InstructionSet Set) {
     return "GLSL.std.450";
   case SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100:
     return "NonSemantic.Shader.DebugInfo.100";
+  case SPIRV::InstructionSet::NonSemantic_AuxData:
+    return "NonSemantic.AuxData";
   case SPIRV::InstructionSet::SPV_AMD_shader_trinary_minmax:
     return "SPV_AMD_shader_trinary_minmax";
   }
@@ -245,7 +247,8 @@ SPIRV::InstructionSet::InstructionSet
 getExtInstSetFromString(std::string SetName) {
   for (auto Set :
        {SPIRV::InstructionSet::GLSL_std_450, SPIRV::InstructionSet::OpenCL_std,
-        SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100}) {
+        SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100,
+        SPIRV::InstructionSet::NonSemantic_AuxData}) {
     if (SetName == getExtInstSetName(Set))
       return Set;
   }
diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 803f2a012f8f4..9f2c3edcad47e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -13,6 +13,7 @@
 
 #include "MCTargetDesc/SPIRVInstPrinter.h"
 #include "SPIRV.h"
+#include "SPIRVAuxDataHandler.h"
 #include "SPIRVInstrInfo.h"
 #include "SPIRVMCInstLower.h"
 #include "SPIRVModuleAnalysis.h"
@@ -111,6 +112,8 @@ class SPIRVAsmPrinter : public AsmPrinter {
   // object), so this pointer cannot dangle.
   SPIRVNonSemanticDebugHandler *NSDebugHandler = nullptr;
 
+  std::unique_ptr<SPIRVAuxDataHandler> AuxDataHandler;
+
 protected:
   void cleanUp(Module &M);
 };
@@ -569,7 +572,7 @@ void SPIRVAsmPrinter::outputExecutionMode(const Module &M) {
     const Function &F = *FI;
     // Only operands of OpEntryPoint instructions are allowed to be
     // <Entry Point> operands of OpExecutionMode
-    if (F.isDeclarationForLinker() || !isEntryPoint(F))
+    if (F.isDeclaration() || !isEntryPoint(F))
       continue;
     MCRegister FReg = MAI->getGlobalObjReg(&F);
     assert(FReg.isValid());
@@ -850,10 +853,18 @@ void SPIRVAsmPrinter::outputModuleSections() {
   MAI = &getAnalysis<SPIRVModuleAnalysis>().MAI;
   assert(ST && TII && MAI && M && "Module analysis is required");
 
+  if (!AuxDataHandler) {
+    auto Handler = std::make_unique<SPIRVAuxDataHandler>(*this, *M);
+    if (Handler->hasWork())
+      AuxDataHandler = std::move(Handler);
+  }
+
   // Let the NSDI handler add its extension and ext inst import entry to MAI
   // before the module header sections are emitted.
   if (NSDebugHandler)
     NSDebugHandler->prepareModuleOutput(*ST, *MAI);
+  if (AuxDataHandler)
+    AuxDataHandler->prepareModuleOutput(*ST, *MAI);
 
   // Output instructions according to the Logical Layout of a Module:
   // 1,2. All OpCapability instructions, then optional OpExtension
@@ -890,6 +901,8 @@ void SPIRVAsmPrinter::outputModuleSections() {
   // MB_NonSemanticGlobalDI section in MAI is intentionally left empty.
   if (NSDebugHandler)
     NSDebugHandler->emitNonSemanticGlobalDebugInfo(*MAI);
+  if (AuxDataHandler)
+    AuxDataHandler->emitAuxData(*MAI);
   // 11. All function declarations (functions without a body).
   outputExtFuncDecls();
   // 12. All function definitions (functions with a body).
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
new file mode 100644
index 0000000000000..4d810c55bc5a0
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
@@ -0,0 +1,123 @@
+//===-- SPIRVAuxDataHandler.cpp - NonSemantic.AuxData emitter -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRVAuxDataHandler.h"
+#include "MCTargetDesc/SPIRVMCTargetDesc.h"
+#include "SPIRVSubtarget.h"
+#include "SPIRVUtils.h"
+#include "llvm/CodeGen/AsmPrinter.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Module.h"
+#include "llvm/MC/MCInst.h"
+#include "llvm/MC/MCStreamer.h"
+
+using namespace llvm;
+
+namespace {
+constexpr int64_t AuxDataLinkage = 4;
+constexpr uint32_t LinkageAvailableExternally = 0;
+} // namespace
+
+SPIRVAuxDataHandler::SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M)
+    : Asm(AP) {
+  for (const Function &F : M)
+    if (F.hasFnAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR))
+      LinkagePreservedFns.push_back(&F);
+}
+
+void SPIRVAuxDataHandler::prepareModuleOutput(const SPIRVSubtarget &ST,
+                                              SPIRV::ModuleAnalysisInfo &MAI) {
+  if (LinkagePreservedFns.empty())
+    return;
+  if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_non_semantic_info))
+    return;
+  MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info);
+  constexpr unsigned AuxSet =
+      static_cast<unsigned>(SPIRV::InstructionSet::NonSemantic_AuxData);
+  if (!MAI.ExtInstSetMap.count(AuxSet))
+    MAI.ExtInstSetMap[AuxSet] = MAI.getNextIDRegister();
+}
+
+void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) {
+  if (LinkagePreservedFns.empty())
+    return;
+  constexpr unsigned AuxSet =
+      static_cast<unsigned>(SPIRV::InstructionSet::NonSemantic_AuxData);
+  MCRegister ExtSetReg = MAI.getExtInstSetReg(AuxSet);
+  if (!ExtSetReg.isValid())
+    return;
+
+  MCRegister VoidTypeReg = findOrEmitOpTypeVoid(MAI);
+  MCRegister I32TypeReg = findOrEmitOpTypeInt32(MAI);
+  // Share one OpConstant across all AE functions.
+  MCRegister ZeroReg;
+
+  for (const Function *F : LinkagePreservedFns) {
+    MCRegister FnReg = MAI.getGlobalObjReg(F);
+    if (!FnReg.isValid())
+      continue;
+    if (!ZeroReg.isValid())
+      ZeroReg = emitOpConstantI32(LinkageAvailableExternally, I32TypeReg, MAI);
+    MCRegister ValReg = ZeroReg;
+    MCInst Inst;
+    Inst.setOpcode(SPIRV::OpExtInst);
+    Inst.addOperand(MCOperand::createReg(MAI.getNextIDRegister()));
+    Inst.addOperand(MCOperand::createReg(VoidTypeReg));
+    Inst.addOperand(MCOperand::createReg(ExtSetReg));
+    Inst.addOperand(MCOperand::createImm(AuxDataLinkage));
+    Inst.addOperand(MCOperand::createReg(FnReg));
+    Inst.addOperand(MCOperand::createReg(ValReg));
+    emitMCInst(Inst);
+  }
+}
+
+void SPIRVAuxDataHandler::emitMCInst(MCInst &Inst) {
+  Asm.OutStreamer->emitInstruction(Inst, Asm.getSubtargetInfo());
+}
+
+MCRegister
+SPIRVAuxDataHandler::findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI) {
+  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars))
+    if (MI->getOpcode() == SPIRV::OpTypeVoid)
+      return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+  MCRegister Reg = MAI.getNextIDRegister();
+  MCInst Inst;
+  Inst.setOpcode(SPIRV::OpTypeVoid);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  emitMCInst(Inst);
+  return Reg;
+}
+
+MCRegister
+SPIRVAuxDataHandler::findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI) {
+  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars))
+    if (MI->getOpcode() == SPIRV::OpTypeInt &&
+        MI->getOperand(1).getImm() == 32 && MI->getOperand(2).getImm() == 0)
+      return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+  MCRegister Reg = MAI.getNextIDRegister();
+  MCInst Inst;
+  Inst.setOpcode(SPIRV::OpTypeInt);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  Inst.addOperand(MCOperand::createImm(32));
+  Inst.addOperand(MCOperand::createImm(0));
+  emitMCInst(Inst);
+  return Reg;
+}
+
+MCRegister
+SPIRVAuxDataHandler::emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
+                                       SPIRV::ModuleAnalysisInfo &MAI) {
+  MCRegister Reg = MAI.getNextIDRegister();
+  MCInst Inst;
+  Inst.setOpcode(SPIRV::OpConstantI);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  Inst.addOperand(MCOperand::createReg(I32TypeReg));
+  Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Value)));
+  emitMCInst(Inst);
+  return Reg;
+}
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
new file mode 100644
index 0000000000000..fde451f7c9670
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
@@ -0,0 +1,56 @@
+//===-- SPIRVAuxDataHandler.h - NonSemantic.AuxData emitter -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Emits NonSemantic.AuxData ExtInst annotations preserving LLVM-level info
+// with no native SPIR-V representation (currently: available_externally
+// linkage). Matches SPIRV-LLVM-Translator's --spirv-preserve-auxdata.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVAUXDATAHANDLER_H
+#define LLVM_LIB_TARGET_SPIRV_SPIRVAUXDATAHANDLER_H
+
+#include "SPIRVModuleAnalysis.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/MC/MCRegister.h"
+
+namespace llvm {
+
+class AsmPrinter;
+class Function;
+class Module;
+class SPIRVSubtarget;
+
+class SPIRVAuxDataHandler {
+public:
+  SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M);
+
+  bool hasWork() const { return !LinkagePreservedFns.empty(); }
+
+  /// Register the extension and ext-inst-set in MAI. Must run before
+  /// outputGlobalRequirements() / outputOpExtInstImports().
+  void prepareModuleOutput(const SPIRVSubtarget &ST,
+                           SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Emit AuxData annotations in module section 10.
+  void emitAuxData(SPIRV::ModuleAnalysisInfo &MAI);
+
+private:
+  AsmPrinter &Asm;
+  SmallVector<const Function *> LinkagePreservedFns;
+
+  void emitMCInst(MCInst &Inst);
+  MCRegister findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
+                               SPIRV::ModuleAnalysisInfo &MAI);
+};
+
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVAUXDATAHANDLER_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
index 806d283ff715f..64d5695ef2167 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
@@ -27,6 +27,7 @@ def OpenCL_std : InstructionSet<0>;
 def GLSL_std_450 : InstructionSet<1>;
 def SPV_AMD_shader_trinary_minmax : InstructionSet<2>;
 def NonSemantic_Shader_DebugInfo_100 : InstructionSet<3>;
+def NonSemantic_AuxData : InstructionSet<4>;
 
 // Define various builtin groups
 def BuiltinGroup : GenericEnum {
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index 4a26745725451..769945db154bc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -666,7 +666,7 @@ static void collectOtherInstr(MachineInstr &MI, SPIRV::ModuleAnalysisInfo &MAI,
 void SPIRVModuleAnalysis::processOtherInstrs(const Module &M) {
   InstrTraces IS;
   for (const Function &F : M) {
-    if (F.isDeclarationForLinker())
+    if (F.isDeclaration())
       continue;
     MachineFunction *MF = MMI->getMachineFunction(F);
     assert(MF);
@@ -725,7 +725,7 @@ void SPIRVModuleAnalysis::processOtherInstrs(const Module &M) {
 // numbered.
 void SPIRVModuleAnalysis::numberRegistersGlobally(const Module &M) {
   for (const Function &F : M) {
-    if (F.isDeclarationForLinker())
+    if (F.isDeclaration())
       continue;
     MachineFunction *MF = MMI->getMachineFunction(F);
     assert(MF);
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 916353ffdf381..4211197fc97d2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -830,6 +830,13 @@ bool SPIRVPrepareFunctionsImpl::runOnModule(Module &M) {
   Changed |= terminateBlocksAfterTrap(M, Intrinsic::ubsantrap);
 
   for (Function &F : M) {
+    // MachineFunctionPass skips available_externally; strip + tag so AuxData
+    // can re-emit the original linkage as NonSemantic.AuxData::Linkage.
+    if (F.hasAvailableExternallyLinkage() && !F.isDeclaration()) {
+      F.addFnAttr(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR);
+      F.setLinkage(GlobalValue::ExternalLinkage);
+      Changed = true;
+    }
     Changed |= substituteAbortKHRCalls(&F);
     Changed |= substituteIntrinsicCalls(&F);
     Changed |= sortBlocks(F);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 0ef31f4182b4e..e56d2e47e9045 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -1217,6 +1217,11 @@ getSpirvLinkageTypeFor(const SPIRVSubtarget &ST, const GlobalValue &GV) {
   if (GV.hasLocalLinkage())
     return std::nullopt;
 
+  // Preserved via NonSemantic.AuxData; skip LinkageAttributes.
+  if (const auto *F = dyn_cast<Function>(&GV))
+    if (F->hasFnAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR))
+      return std::nullopt;
+
   if (GV.isDeclarationForLinker()) {
     // Interface variables must not get Import linkage.
     if (const auto *GVar = dyn_cast<GlobalVariable>(&GV)) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 27b196cb8dad3..c53e4154d35c1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -526,6 +526,8 @@ CallInst *buildIntrWithMD(Intrinsic::ID IntrID, ArrayRef<Type *> Types,
 MachineInstr *getVRegDef(MachineRegisterInfo &MRI, Register Reg);
 
 #define SPIRV_BACKEND_SERVICE_FUN_NAME "__spirv_backend_service_fun"
+#define SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR "spv.was-available-externally"
+
 bool getVacantFunctionName(Module &M, std::string &Name);
 
 void setRegClassType(Register Reg, const Type *Ty, SPIRVGlobalRegistry *GR,
diff --git a/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll b/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
index 335118ef3e46a..35f800d1ea1cd 100644
--- a/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
+++ b/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
@@ -1,19 +1,41 @@
-; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --implicit-check-not=ae_kernel --implicit-check-not=ae_func
-; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --implicit-check-not=ae_kernel --implicit-check-not=ae_func
+; Without SPV_KHR_non_semantic_info: emitted as plain external definitions
+; (original linkage has no native SPIR-V representation).
+
+; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --check-prefix=NOAUX
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=NOAUX
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
-; available_externally definitions must not be emitted: they have no
-; MachineFunction, so the module analysis and AsmPrinter would otherwise
-; assert.
+; NOAUX-NOT: OpExtension "SPV_KHR_non_semantic_info"
+; NOAUX-NOT: NonSemantic.AuxData
+; NOAUX-DAG: OpName %[[#ae_func:]] "ae_func"
+; NOAUX-DAG: OpName %[[#caller:]] "caller"
+; NOAUX-NOT: OpDecorate %[[#ae_func]] LinkageAttributes
+; NOAUX:     %[[#ae_func]] = OpFunction
+; NOAUX:     OpFunctionEnd
+; NOAUX:     %[[#caller]] = OpFunction
+; NOAUX:     OpFunctionCall %[[#]] %[[#ae_func]]
 
-; CHECK: OpName %[[#used:]] "used_kernel"
-; CHECK: %[[#used]] = OpFunction
+; With SPV_KHR_non_semantic_info: linkage preserved via a
+; NonSemantic.AuxData::Linkage annotation for round-trip recovery.
 
-define available_externally spir_kernel void @ae_kernel(ptr addrspace(1) %out) {
-entry:
-  store i32 1, ptr addrspace(1) %out, align 4
-  ret void
-}
+; RUN: llc -O0 -mtriple=spirv32-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=AUX
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=AUX
+;; spirv-val rejects the AuxData forward-ref without
+;; SPV_KHR_relaxed_extended_instruction; matches Translator behavior.
+
+; AUX-DAG: OpExtension "SPV_KHR_non_semantic_info"
+; AUX-DAG: %[[#auxset:]] = OpExtInstImport "NonSemantic.AuxData"
+; AUX-DAG: OpName %[[#ae_func:]] "ae_func"
+; AUX-DAG: OpName %[[#caller:]] "caller"
+; AUX-NOT: OpDecorate %[[#ae_func]] LinkageAttributes
+; AUX-DAG: %[[#void:]] = OpTypeVoid
+; AUX-DAG: %[[#i32:]] = OpTypeInt 32 0
+; AUX-DAG: %[[#zero:]] = OpConstant %[[#i32]] 0
+; AUX:     %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#ae_func]] %[[#zero]]
+; AUX:     %[[#ae_func]] = OpFunction
+; AUX:     OpFunctionEnd
+; AUX:     %[[#caller]] = OpFunction
+; AUX:     OpFunctionCall %[[#]] %[[#ae_func]]
 
 define available_externally spir_func i32 @ae_func(i32 %x) {
 entry:
@@ -21,8 +43,9 @@ entry:
   ret i32 %r
 }
 
-define spir_kernel void @used_kernel(ptr addrspace(1) %out, i32 %n) {
+define spir_kernel void @caller(ptr addrspace(1) %out, i32 %n) {
 entry:
-  store i32 %n, ptr addrspace(1) %out, align 4
+  %v = call spir_func i32 @ae_func(i32 %n)
+  store i32 %v, ptr addrspace(1) %out, align 4
   ret void
 }

>From c85885b249b7c018623706bc96475e61cfbb1af9 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Fri, 29 May 2026 10:15:10 +0200
Subject: [PATCH 3/9] Address comments

---
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |   2 +
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp | 210 +++++++++++++++---
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h   |  50 ++++-
 .../preserve-auxdata.ll                       |  50 +++++
 4 files changed, 274 insertions(+), 38 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 9f2c3edcad47e..8e9913a326ba2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -347,6 +347,8 @@ void SPIRVAsmPrinter::outputDebugSourceAndStrings(const Module &M) {
   // emitNonSemanticGlobalDebugInfo().
   if (NSDebugHandler)
     NSDebugHandler->emitNonSemanticDebugStrings(*MAI);
+  if (AuxDataHandler)
+    AuxDataHandler->emitAuxDataStrings(*MAI);
 }
 
 void SPIRVAsmPrinter::outputOpExtInstImports(const Module &M) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
index 4d810c55bc5a0..08d3675868693 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
@@ -11,71 +11,216 @@
 #include "SPIRVSubtarget.h"
 #include "SPIRVUtils.h"
 #include "llvm/CodeGen/AsmPrinter.h"
+#include "llvm/IR/Attributes.h"
 #include "llvm/IR/Function.h"
+#include "llvm/IR/GlobalObject.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Metadata.h"
 #include "llvm/IR/Module.h"
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCStreamer.h"
+#include "llvm/Support/CommandLine.h"
 
 using namespace llvm;
 
+static cl::opt<bool> SPVPreserveAuxData(
+    "spirv-preserve-auxdata",
+    cl::desc("Preserve LLVM attributes and metadata as "
+             "NonSemantic.AuxData ExtInst annotations (requires "
+             "SPV_KHR_non_semantic_info)"),
+    cl::Optional, cl::Hidden, cl::init(false));
+
 namespace {
-constexpr int64_t AuxDataLinkage = 4;
-constexpr uint32_t LinkageAvailableExternally = 0;
+// Khronos NonSemantic.AuxData opcodes.
+enum AuxDataOpcode : uint32_t {
+  FunctionMetadata = 0,
+  FunctionAttribute = 1,
+  GlobalVariableMetadata = 2,
+  GlobalVariableAttribute = 3,
+  Linkage = 4,
+};
+
+enum AuxDataLinkageType : uint32_t {
+  AvailableExternally = 0,
+};
+
+constexpr unsigned NonSemanticAuxDataSet =
+    static_cast<unsigned>(SPIRV::InstructionSet::NonSemantic_AuxData);
+
+AttributeSet getGOAttrs(const GlobalObject *GO) {
+  if (const auto *F = dyn_cast<Function>(GO))
+    return F->getAttributes().getFnAttrs();
+  return cast<GlobalVariable>(GO)->getAttributes();
+}
 } // namespace
 
 SPIRVAuxDataHandler::SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M)
-    : Asm(AP) {
+    : Asm(AP), Mod(M) {
   for (const Function &F : M)
     if (F.hasFnAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR))
       LinkagePreservedFns.push_back(&F);
 }
 
+bool SPIRVAuxDataHandler::hasWork() const {
+  return !LinkagePreservedFns.empty() || SPVPreserveAuxData;
+}
+
 void SPIRVAuxDataHandler::prepareModuleOutput(const SPIRVSubtarget &ST,
                                               SPIRV::ModuleAnalysisInfo &MAI) {
-  if (LinkagePreservedFns.empty())
+  if (!hasWork())
     return;
   if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_non_semantic_info))
     return;
   MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info);
-  constexpr unsigned AuxSet =
-      static_cast<unsigned>(SPIRV::InstructionSet::NonSemantic_AuxData);
-  if (!MAI.ExtInstSetMap.count(AuxSet))
-    MAI.ExtInstSetMap[AuxSet] = MAI.getNextIDRegister();
+  if (!MAI.ExtInstSetMap.count(NonSemanticAuxDataSet))
+    MAI.ExtInstSetMap[NonSemanticAuxDataSet] = MAI.getNextIDRegister();
 }
 
-void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) {
-  if (LinkagePreservedFns.empty())
+MCRegister
+SPIRVAuxDataHandler::getOrEmitString(StringRef S,
+                                     SPIRV::ModuleAnalysisInfo &MAI) {
+  auto [It, Inserted] = StringRegs.try_emplace(S);
+  if (!Inserted)
+    return It->second;
+  MCRegister Reg = MAI.getNextIDRegister();
+  It->second = Reg;
+  MCInst Inst;
+  Inst.setOpcode(SPIRV::OpString);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  addStringImm(S, Inst);
+  emitMCInst(Inst);
+  return Reg;
+}
+
+void SPIRVAuxDataHandler::collectAttributesFor(
+    const GlobalObject *GO, function_ref<MCRegister()> GetNameReg,
+    SPIRV::ModuleAnalysisInfo &MAI) {
+  uint32_t Opcode =
+      isa<Function>(GO) ? FunctionAttribute : GlobalVariableAttribute;
+  for (const Attribute &A : getGOAttrs(GO)) {
+    if (A.isStringAttribute() &&
+        A.getKindAsString() == SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR)
+      continue;
+    ExtInstRecord Rec;
+    Rec.Opcode = Opcode;
+    Rec.Operands.push_back(GetNameReg());
+    if (A.isStringAttribute()) {
+      Rec.Operands.push_back(getOrEmitString(A.getKindAsString(), MAI));
+      StringRef Val = A.getValueAsString();
+      if (!Val.empty())
+        Rec.Operands.push_back(getOrEmitString(Val, MAI));
+    } else {
+      Rec.Operands.push_back(
+          getOrEmitString(StringPool.save(A.getAsString()), MAI));
+    }
+    PendingRecords.push_back(std::move(Rec));
+  }
+}
+
+void SPIRVAuxDataHandler::collectMetadataFor(
+    const GlobalObject *GO, function_ref<MCRegister()> GetNameReg,
+    ArrayRef<StringRef> MDNames, SPIRV::ModuleAnalysisInfo &MAI) {
+  SmallVector<std::pair<unsigned, MDNode *>> AllMD;
+  GO->getAllMetadata(AllMD);
+  if (AllMD.empty())
     return;
-  constexpr unsigned AuxSet =
-      static_cast<unsigned>(SPIRV::InstructionSet::NonSemantic_AuxData);
-  MCRegister ExtSetReg = MAI.getExtInstSetReg(AuxSet);
+  uint32_t Opcode =
+      isa<Function>(GO) ? FunctionMetadata : GlobalVariableMetadata;
+  // Skip non-MDString operands: emitting them would require a full value
+  // translation we can't safely drive from here.
+  auto CollectStrings = [&](MDNode *MD) -> std::optional<SmallVector<MCRegister, 4>> {
+    SmallVector<MCRegister, 4> Out;
+    for (const MDOperand &MdOp : MD->operands()) {
+      auto *MDStr = dyn_cast_or_null<MDString>(MdOp.get());
+      if (!MDStr)
+        return std::nullopt;
+      Out.push_back(getOrEmitString(MDStr->getString(), MAI));
+    }
+    return Out;
+  };
+  for (const auto &MD : AllMD) {
+    if (MD.first == LLVMContext::MD_dbg)
+      continue;
+    StringRef MDName = MDNames[MD.first];
+    if (MDName == "spirv.Decorations" ||
+        MDName == "spirv.ParameterDecorations")
+      continue;
+    auto Operands = CollectStrings(MD.second);
+    if (!Operands)
+      continue;
+    ExtInstRecord Rec;
+    Rec.Opcode = Opcode;
+    Rec.Operands.push_back(GetNameReg());
+    Rec.Operands.push_back(getOrEmitString(MDName, MAI));
+    Rec.Operands.append(Operands->begin(), Operands->end());
+    PendingRecords.push_back(std::move(Rec));
+  }
+}
+
+void SPIRVAuxDataHandler::emitAuxDataStrings(SPIRV::ModuleAnalysisInfo &MAI) {
+  if (!SPVPreserveAuxData)
+    return;
+  if (!MAI.getExtInstSetReg(NonSemanticAuxDataSet).isValid())
+    return;
+  SmallVector<StringRef> MDNames;
+  Mod.getContext().getMDKindNames(MDNames);
+  for (const GlobalObject &GO : Mod.global_objects()) {
+    if (GO.isDeclaration())
+      continue;
+    // Defer the name OpString until the first record actually fires.
+    MCRegister NameReg;
+    auto GetNameReg = [&]() {
+      if (!NameReg.isValid())
+        NameReg = getOrEmitString(GO.getName(), MAI);
+      return NameReg;
+    };
+    collectAttributesFor(&GO, GetNameReg, MAI);
+    collectMetadataFor(&GO, GetNameReg, MDNames, MAI);
+  }
+}
+
+void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) {
+  MCRegister ExtSetReg = MAI.getExtInstSetReg(NonSemanticAuxDataSet);
   if (!ExtSetReg.isValid())
     return;
 
   MCRegister VoidTypeReg = findOrEmitOpTypeVoid(MAI);
-  MCRegister I32TypeReg = findOrEmitOpTypeInt32(MAI);
-  // Share one OpConstant across all AE functions.
-  MCRegister ZeroReg;
 
+  for (const ExtInstRecord &Rec : PendingRecords)
+    emitAuxDataExtInst(Rec.Opcode, VoidTypeReg, ExtSetReg, Rec.Operands, MAI);
+
+  if (LinkagePreservedFns.empty())
+    return;
+
+  MCRegister I32TypeReg = findOrEmitOpTypeInt32(MAI);
+  MCRegister AEConstReg;
   for (const Function *F : LinkagePreservedFns) {
     MCRegister FnReg = MAI.getGlobalObjReg(F);
     if (!FnReg.isValid())
       continue;
-    if (!ZeroReg.isValid())
-      ZeroReg = emitOpConstantI32(LinkageAvailableExternally, I32TypeReg, MAI);
-    MCRegister ValReg = ZeroReg;
-    MCInst Inst;
-    Inst.setOpcode(SPIRV::OpExtInst);
-    Inst.addOperand(MCOperand::createReg(MAI.getNextIDRegister()));
-    Inst.addOperand(MCOperand::createReg(VoidTypeReg));
-    Inst.addOperand(MCOperand::createReg(ExtSetReg));
-    Inst.addOperand(MCOperand::createImm(AuxDataLinkage));
-    Inst.addOperand(MCOperand::createReg(FnReg));
-    Inst.addOperand(MCOperand::createReg(ValReg));
-    emitMCInst(Inst);
+    if (!AEConstReg.isValid())
+      AEConstReg =
+          emitOpConstantI32(AvailableExternally, I32TypeReg, MAI);
+    emitAuxDataExtInst(Linkage, VoidTypeReg, ExtSetReg, {FnReg, AEConstReg},
+                       MAI);
   }
 }
 
+void SPIRVAuxDataHandler::emitAuxDataExtInst(
+    uint32_t Opcode, MCRegister VoidTypeReg, MCRegister ExtSetReg,
+    ArrayRef<MCRegister> Operands, SPIRV::ModuleAnalysisInfo &MAI) {
+  MCInst Inst;
+  Inst.setOpcode(SPIRV::OpExtInst);
+  Inst.addOperand(MCOperand::createReg(MAI.getNextIDRegister()));
+  Inst.addOperand(MCOperand::createReg(VoidTypeReg));
+  Inst.addOperand(MCOperand::createReg(ExtSetReg));
+  Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Opcode)));
+  for (MCRegister R : Operands)
+    Inst.addOperand(MCOperand::createReg(R));
+  emitMCInst(Inst);
+}
+
 void SPIRVAuxDataHandler::emitMCInst(MCInst &Inst) {
   Asm.OutStreamer->emitInstruction(Inst, Asm.getSubtargetInfo());
 }
@@ -95,16 +240,19 @@ SPIRVAuxDataHandler::findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI) {
 
 MCRegister
 SPIRVAuxDataHandler::findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI) {
+  constexpr int64_t Int32BitWidth = 32;
+  constexpr int64_t UnsignedSignedness = 0;
   for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars))
     if (MI->getOpcode() == SPIRV::OpTypeInt &&
-        MI->getOperand(1).getImm() == 32 && MI->getOperand(2).getImm() == 0)
+        MI->getOperand(1).getImm() == Int32BitWidth &&
+        MI->getOperand(2).getImm() == UnsignedSignedness)
       return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
   MCRegister Reg = MAI.getNextIDRegister();
   MCInst Inst;
   Inst.setOpcode(SPIRV::OpTypeInt);
   Inst.addOperand(MCOperand::createReg(Reg));
-  Inst.addOperand(MCOperand::createImm(32));
-  Inst.addOperand(MCOperand::createImm(0));
+  Inst.addOperand(MCOperand::createImm(Int32BitWidth));
+  Inst.addOperand(MCOperand::createImm(UnsignedSignedness));
   emitMCInst(Inst);
   return Reg;
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
index fde451f7c9670..b60b77e8de1e4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
@@ -6,9 +6,9 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// Emits NonSemantic.AuxData ExtInst annotations preserving LLVM-level info
-// with no native SPIR-V representation (currently: available_externally
-// linkage). Matches SPIRV-LLVM-Translator's --spirv-preserve-auxdata.
+// Emits NonSemantic.AuxData ExtInst annotations (mirrors SPIRV-LLVM-Translator
+// --spirv-preserve-auxdata). Linkage records emit unconditionally for AE-tagged
+// functions; attribute/metadata records are gated by -spirv-preserve-auxdata.
 //
 //===----------------------------------------------------------------------===//
 
@@ -16,13 +16,20 @@
 #define LLVM_LIB_TARGET_SPIRV_SPIRVAUXDATAHANDLER_H
 
 #include "SPIRVModuleAnalysis.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/MC/MCRegister.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/StringSaver.h"
 
 namespace llvm {
 
 class AsmPrinter;
 class Function;
+class GlobalObject;
 class Module;
 class SPIRVSubtarget;
 
@@ -30,25 +37,54 @@ class SPIRVAuxDataHandler {
 public:
   SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M);
 
-  bool hasWork() const { return !LinkagePreservedFns.empty(); }
+  bool hasWork() const;
 
-  /// Register the extension and ext-inst-set in MAI. Must run before
-  /// outputGlobalRequirements() / outputOpExtInstImports().
+  /// Register extension + ext-inst-set; call before output of section 1.
   void prepareModuleOutput(const SPIRVSubtarget &ST,
                            SPIRV::ModuleAnalysisInfo &MAI);
 
-  /// Emit AuxData annotations in module section 10.
+  /// Emit OpStrings and stage ExtInst records; call in module section 7.
+  void emitAuxDataStrings(SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Emit the staged ExtInst records; call in module section 10.
   void emitAuxData(SPIRV::ModuleAnalysisInfo &MAI);
 
 private:
+  struct ExtInstRecord {
+    uint32_t Opcode;
+    SmallVector<MCRegister, 4> Operands;
+  };
+
   AsmPrinter &Asm;
+  const Module &Mod;
+
   SmallVector<const Function *> LinkagePreservedFns;
 
+  // Backing storage for non-string-attribute strings; StringRegs keys are
+  // StringRefs into it.
+  BumpPtrAllocator StringAlloc;
+  UniqueStringSaver StringPool{StringAlloc};
+
+  DenseMap<StringRef, MCRegister> StringRegs;
+  SmallVector<ExtInstRecord> PendingRecords;
+
+  MCRegister getOrEmitString(StringRef S, SPIRV::ModuleAnalysisInfo &MAI);
+  void collectAttributesFor(const GlobalObject *GO,
+                            function_ref<MCRegister()> GetNameReg,
+                            SPIRV::ModuleAnalysisInfo &MAI);
+  void collectMetadataFor(const GlobalObject *GO,
+                          function_ref<MCRegister()> GetNameReg,
+                          ArrayRef<StringRef> MDNames,
+                          SPIRV::ModuleAnalysisInfo &MAI);
+
   void emitMCInst(MCInst &Inst);
   MCRegister findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI);
   MCRegister findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI);
   MCRegister emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
                                SPIRV::ModuleAnalysisInfo &MAI);
+  void emitAuxDataExtInst(uint32_t Opcode, MCRegister VoidTypeReg,
+                          MCRegister ExtSetReg, ArrayRef<MCRegister> Operands,
+                          SPIRV::ModuleAnalysisInfo &MAI);
 };
 
 } // namespace llvm
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
new file mode 100644
index 0000000000000..cc6c1f8637937
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
@@ -0,0 +1,50 @@
+; -spirv-preserve-auxdata: emit NonSemantic.AuxData for attrs/metadata.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
+; RUN:   %s -o - | FileCheck %s
+
+; Off by default: only the Linkage record fires (for the AE function).
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info %s -o - \
+; RUN:   | FileCheck %s --check-prefix=OFF
+
+; OFF-NOT: OpString "my-attr"
+; OFF-NOT: OpString "nounwind"
+; OFF-NOT: OpString "some.md"
+; OFF-NOT: OpString "spv.was-available-externally"
+
+; CHECK-DAG: %[[#auxset:]] = OpExtInstImport "NonSemantic.AuxData"
+; CHECK-DAG: %[[#fname:]] = OpString "fn"
+; CHECK-DAG: %[[#akind:]] = OpString "my-attr"
+; CHECK-DAG: %[[#aval:]]  = OpString "val"
+; CHECK-DAG: %[[#nounwind:]] = OpString "nounwind"
+; CHECK-DAG: %[[#gname:]] = OpString "gv"
+; CHECK-DAG: %[[#mdname:]] = OpString "some.md"
+; CHECK-DAG: %[[#mdval:]] = OpString "hello"
+; CHECK-DAG: %[[#void:]] = OpTypeVoid
+; CHECK-DAG: %[[#i32:]] = OpTypeInt 32 0
+
+; Internal marker must not leak as a string.
+; CHECK-NOT: OpString "spv.was-available-externally"
+; @ae has no payload, so no name OpString.
+; CHECK-NOT: OpString "ae"
+
+; Records emit in module order; Linkage's 2nd operand is the shared AE const.
+; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#akind]] %[[#aval]]
+; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#nounwind]]
+; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#gname]] %[[#mdname]] %[[#mdval]]
+; CHECK-DAG: %[[#aelink:]] = OpConstant %[[#i32]] 0
+; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %{{[0-9]+}} %[[#aelink]]
+
+ at gv = global i32 0, align 4, !some.md !0
+
+define spir_func i32 @fn(i32 %x) "my-attr"="val" nounwind {
+  ret i32 %x
+}
+
+define available_externally spir_func i32 @ae(i32 %x) {
+  ret i32 %x
+}
+
+!0 = !{!"hello"}

>From b734fc25197262098a5ad331135d5a6177117d44 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Fri, 29 May 2026 10:41:42 +0200
Subject: [PATCH 4/9] fmt

---
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
index 08d3675868693..516bad6f83b0f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
@@ -129,7 +129,8 @@ void SPIRVAuxDataHandler::collectMetadataFor(
       isa<Function>(GO) ? FunctionMetadata : GlobalVariableMetadata;
   // Skip non-MDString operands: emitting them would require a full value
   // translation we can't safely drive from here.
-  auto CollectStrings = [&](MDNode *MD) -> std::optional<SmallVector<MCRegister, 4>> {
+  auto CollectStrings =
+      [&](MDNode *MD) -> std::optional<SmallVector<MCRegister, 4>> {
     SmallVector<MCRegister, 4> Out;
     for (const MDOperand &MdOp : MD->operands()) {
       auto *MDStr = dyn_cast_or_null<MDString>(MdOp.get());
@@ -143,8 +144,7 @@ void SPIRVAuxDataHandler::collectMetadataFor(
     if (MD.first == LLVMContext::MD_dbg)
       continue;
     StringRef MDName = MDNames[MD.first];
-    if (MDName == "spirv.Decorations" ||
-        MDName == "spirv.ParameterDecorations")
+    if (MDName == "spirv.Decorations" || MDName == "spirv.ParameterDecorations")
       continue;
     auto Operands = CollectStrings(MD.second);
     if (!Operands)
@@ -200,16 +200,17 @@ void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) {
     if (!FnReg.isValid())
       continue;
     if (!AEConstReg.isValid())
-      AEConstReg =
-          emitOpConstantI32(AvailableExternally, I32TypeReg, MAI);
+      AEConstReg = emitOpConstantI32(AvailableExternally, I32TypeReg, MAI);
     emitAuxDataExtInst(Linkage, VoidTypeReg, ExtSetReg, {FnReg, AEConstReg},
                        MAI);
   }
 }
 
-void SPIRVAuxDataHandler::emitAuxDataExtInst(
-    uint32_t Opcode, MCRegister VoidTypeReg, MCRegister ExtSetReg,
-    ArrayRef<MCRegister> Operands, SPIRV::ModuleAnalysisInfo &MAI) {
+void SPIRVAuxDataHandler::emitAuxDataExtInst(uint32_t Opcode,
+                                             MCRegister VoidTypeReg,
+                                             MCRegister ExtSetReg,
+                                             ArrayRef<MCRegister> Operands,
+                                             SPIRV::ModuleAnalysisInfo &MAI) {
   MCInst Inst;
   Inst.setOpcode(SPIRV::OpExtInst);
   Inst.addOperand(MCOperand::createReg(MAI.getNextIDRegister()));

>From 7da17b426ca4eabe81e78046050c0e6aa52c6e98 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Fri, 29 May 2026 18:45:40 +0200
Subject: [PATCH 5/9] Address comments

---
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp | 34 +++++++++----------
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h   | 14 ++++++--
 2 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
index 516bad6f83b0f..8eaf15e4caa8d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
@@ -21,6 +21,7 @@
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCStreamer.h"
 #include "llvm/Support/CommandLine.h"
+#include "llvm/Support/ErrorHandling.h"
 
 using namespace llvm;
 
@@ -32,15 +33,6 @@ static cl::opt<bool> SPVPreserveAuxData(
     cl::Optional, cl::Hidden, cl::init(false));
 
 namespace {
-// Khronos NonSemantic.AuxData opcodes.
-enum AuxDataOpcode : uint32_t {
-  FunctionMetadata = 0,
-  FunctionAttribute = 1,
-  GlobalVariableMetadata = 2,
-  GlobalVariableAttribute = 3,
-  Linkage = 4,
-};
-
 enum AuxDataLinkageType : uint32_t {
   AvailableExternally = 0,
 };
@@ -70,8 +62,12 @@ void SPIRVAuxDataHandler::prepareModuleOutput(const SPIRVSubtarget &ST,
                                               SPIRV::ModuleAnalysisInfo &MAI) {
   if (!hasWork())
     return;
-  if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_non_semantic_info))
+  if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_non_semantic_info)) {
+    if (SPVPreserveAuxData)
+      report_fatal_error("-spirv-preserve-auxdata requires the "
+                         "SPV_KHR_non_semantic_info extension to be enabled.");
     return;
+  }
   MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info);
   if (!MAI.ExtInstSetMap.count(NonSemanticAuxDataSet))
     MAI.ExtInstSetMap[NonSemanticAuxDataSet] = MAI.getNextIDRegister();
@@ -96,8 +92,8 @@ SPIRVAuxDataHandler::getOrEmitString(StringRef S,
 void SPIRVAuxDataHandler::collectAttributesFor(
     const GlobalObject *GO, function_ref<MCRegister()> GetNameReg,
     SPIRV::ModuleAnalysisInfo &MAI) {
-  uint32_t Opcode =
-      isa<Function>(GO) ? FunctionAttribute : GlobalVariableAttribute;
+  AuxDataOpcode Opcode = isa<Function>(GO) ? FunctionAttributeOpcode
+                                           : GlobalVariableAttributeOpcode;
   for (const Attribute &A : getGOAttrs(GO)) {
     if (A.isStringAttribute() &&
         A.getKindAsString() == SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR)
@@ -125,8 +121,8 @@ void SPIRVAuxDataHandler::collectMetadataFor(
   GO->getAllMetadata(AllMD);
   if (AllMD.empty())
     return;
-  uint32_t Opcode =
-      isa<Function>(GO) ? FunctionMetadata : GlobalVariableMetadata;
+  AuxDataOpcode Opcode =
+      isa<Function>(GO) ? FunctionMetadataOpcode : GlobalVariableMetadataOpcode;
   // Skip non-MDString operands: emitting them would require a full value
   // translation we can't safely drive from here.
   auto CollectStrings =
@@ -201,12 +197,12 @@ void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) {
       continue;
     if (!AEConstReg.isValid())
       AEConstReg = emitOpConstantI32(AvailableExternally, I32TypeReg, MAI);
-    emitAuxDataExtInst(Linkage, VoidTypeReg, ExtSetReg, {FnReg, AEConstReg},
-                       MAI);
+    emitAuxDataExtInst(LinkageOpcode, VoidTypeReg, ExtSetReg,
+                       {FnReg, AEConstReg}, MAI);
   }
 }
 
-void SPIRVAuxDataHandler::emitAuxDataExtInst(uint32_t Opcode,
+void SPIRVAuxDataHandler::emitAuxDataExtInst(AuxDataOpcode Opcode,
                                              MCRegister VoidTypeReg,
                                              MCRegister ExtSetReg,
                                              ArrayRef<MCRegister> Operands,
@@ -216,7 +212,7 @@ void SPIRVAuxDataHandler::emitAuxDataExtInst(uint32_t Opcode,
   Inst.addOperand(MCOperand::createReg(MAI.getNextIDRegister()));
   Inst.addOperand(MCOperand::createReg(VoidTypeReg));
   Inst.addOperand(MCOperand::createReg(ExtSetReg));
-  Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Opcode)));
+  Inst.addOperand(MCOperand::createImm(Opcode));
   for (MCRegister R : Operands)
     Inst.addOperand(MCOperand::createReg(R));
   emitMCInst(Inst);
@@ -241,6 +237,8 @@ SPIRVAuxDataHandler::findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI) {
 
 MCRegister
 SPIRVAuxDataHandler::findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI) {
+  // SPIR-V OpTypeInt: <width>, <signedness>. Signedness 0 = unsigned, 1 =
+  // signed (we want unsigned i32).
   constexpr int64_t Int32BitWidth = 32;
   constexpr int64_t UnsignedSignedness = 0;
   for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars))
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
index b60b77e8de1e4..cfd12785b64da 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
@@ -33,6 +33,16 @@ class GlobalObject;
 class Module;
 class SPIRVSubtarget;
 
+// Khronos NonSemantic.AuxData opcodes (int64_t to drop casts at MCOperand
+// boundaries).
+enum AuxDataOpcode : int64_t {
+  FunctionMetadataOpcode = 0,
+  FunctionAttributeOpcode = 1,
+  GlobalVariableMetadataOpcode = 2,
+  GlobalVariableAttributeOpcode = 3,
+  LinkageOpcode = 4,
+};
+
 class SPIRVAuxDataHandler {
 public:
   SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M);
@@ -51,7 +61,7 @@ class SPIRVAuxDataHandler {
 
 private:
   struct ExtInstRecord {
-    uint32_t Opcode;
+    AuxDataOpcode Opcode;
     SmallVector<MCRegister, 4> Operands;
   };
 
@@ -82,7 +92,7 @@ class SPIRVAuxDataHandler {
   MCRegister findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI);
   MCRegister emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
                                SPIRV::ModuleAnalysisInfo &MAI);
-  void emitAuxDataExtInst(uint32_t Opcode, MCRegister VoidTypeReg,
+  void emitAuxDataExtInst(AuxDataOpcode Opcode, MCRegister VoidTypeReg,
                           MCRegister ExtSetReg, ArrayRef<MCRegister> Operands,
                           SPIRV::ModuleAnalysisInfo &MAI);
 };

>From e12eedb3af106e24fdead5f10be1f529c7dd1975 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Fri, 29 May 2026 19:25:20 +0200
Subject: [PATCH 6/9] Rename funcs

---
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp | 14 +++++++-------
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h   |  6 +++---
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
index 8eaf15e4caa8d..31c8e55d3da58 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
@@ -189,14 +189,15 @@ void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) {
   if (LinkagePreservedFns.empty())
     return;
 
-  MCRegister I32TypeReg = findOrEmitOpTypeInt32(MAI);
+  MCRegister UInt32TypeReg = findOrEmitOpTypeUInt32(MAI);
   MCRegister AEConstReg;
   for (const Function *F : LinkagePreservedFns) {
     MCRegister FnReg = MAI.getGlobalObjReg(F);
     if (!FnReg.isValid())
       continue;
     if (!AEConstReg.isValid())
-      AEConstReg = emitOpConstantI32(AvailableExternally, I32TypeReg, MAI);
+      AEConstReg =
+          emitOpConstantUInt32(AvailableExternally, UInt32TypeReg, MAI);
     emitAuxDataExtInst(LinkageOpcode, VoidTypeReg, ExtSetReg,
                        {FnReg, AEConstReg}, MAI);
   }
@@ -236,7 +237,7 @@ SPIRVAuxDataHandler::findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI) {
 }
 
 MCRegister
-SPIRVAuxDataHandler::findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI) {
+SPIRVAuxDataHandler::findOrEmitOpTypeUInt32(SPIRV::ModuleAnalysisInfo &MAI) {
   // SPIR-V OpTypeInt: <width>, <signedness>. Signedness 0 = unsigned, 1 =
   // signed (we want unsigned i32).
   constexpr int64_t Int32BitWidth = 32;
@@ -256,14 +257,13 @@ SPIRVAuxDataHandler::findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI) {
   return Reg;
 }
 
-MCRegister
-SPIRVAuxDataHandler::emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
-                                       SPIRV::ModuleAnalysisInfo &MAI) {
+MCRegister SPIRVAuxDataHandler::emitOpConstantUInt32(
+    uint32_t Value, MCRegister UInt32TypeReg, SPIRV::ModuleAnalysisInfo &MAI) {
   MCRegister Reg = MAI.getNextIDRegister();
   MCInst Inst;
   Inst.setOpcode(SPIRV::OpConstantI);
   Inst.addOperand(MCOperand::createReg(Reg));
-  Inst.addOperand(MCOperand::createReg(I32TypeReg));
+  Inst.addOperand(MCOperand::createReg(UInt32TypeReg));
   Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Value)));
   emitMCInst(Inst);
   return Reg;
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
index cfd12785b64da..5a672b1abcde2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
@@ -89,9 +89,9 @@ class SPIRVAuxDataHandler {
 
   void emitMCInst(MCInst &Inst);
   MCRegister findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI);
-  MCRegister findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI);
-  MCRegister emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
-                               SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister findOrEmitOpTypeUInt32(SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister emitOpConstantUInt32(uint32_t Value, MCRegister UInt32TypeReg,
+                                  SPIRV::ModuleAnalysisInfo &MAI);
   void emitAuxDataExtInst(AuxDataOpcode Opcode, MCRegister VoidTypeReg,
                           MCRegister ExtSetReg, ArrayRef<MCRegister> Operands,
                           SPIRV::ModuleAnalysisInfo &MAI);

>From 4f9b2a4a60d2cf0cf40015cbd4845869217b56c1 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 1 Jun 2026 14:23:31 +0200
Subject: [PATCH 7/9] Add NonSemantic.AuxData forward-path tests

---
 .../preserve-all-function-attributes.ll       | 32 +++++++++++++++++
 .../preserve-all-function-metadata-debug.ll   | 35 +++++++++++++++++++
 .../preserve-all-function-metadata.ll         | 34 ++++++++++++++++++
 .../preserve-auxdata-requires-extension.ll    | 12 +++++++
 .../preserve-auxdata.ll                       |  8 ++---
 .../preserve-gv-attributes.ll                 | 28 +++++++++++++++
 .../preserve-gv-metadata.ll                   | 28 +++++++++++++++
 7 files changed, 173 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata-requires-extension.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll

diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll
new file mode 100644
index 0000000000000..e034a51a9ae10
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll
@@ -0,0 +1,32 @@
+; Adapted from the SPIRV-LLVM-Translator --spirv-preserve-auxdata forward-path
+; tests. The backend only emits, so the reverse-translation checks are dropped.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
+; RUN:   %s -o - | FileCheck %s
+
+; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
+
+; CHECK-DAG: %[[#Fcn0:]] = OpString "mul_add"
+; CHECK-DAG: %[[#Attr0:]] = OpString "foo"
+; CHECK-DAG: %[[#Fcn1:]] = OpString "test"
+; CHECK-DAG: %[[#Attr1LHS:]] = OpString "bar"
+; CHECK-DAG: %[[#Attr1RHS:]] = OpString "baz"
+
+; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
+
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn0]] %[[#Attr0]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn1]] %[[#Attr1LHS]] %[[#Attr1RHS]]
+
+target triple = "spir64-unknown-unknown"
+
+define spir_func void @mul_add() #0 {
+  ret void
+}
+
+define spir_func void @test() #1 {
+  ret void
+}
+
+attributes #0 = { "foo" }
+attributes #1 = { "bar"="baz" }
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll
new file mode 100644
index 0000000000000..069068b754366
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll
@@ -0,0 +1,35 @@
+; Adapted from the SPIRV-LLVM-Translator --spirv-preserve-auxdata forward-path
+; tests. Debug info (!dbg) must never be emitted as NonSemantic.AuxData. The
+; "keep-me" attribute is a positive control: it confirms the feature is active,
+; so the absence of a debug record is meaningful rather than the feature being
+; off.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
+; RUN:   %s -o - | FileCheck %s
+
+; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
+; CHECK-DAG: %[[#Fcn:]] = OpString "foo"
+; CHECK-DAG: %[[#Attr:]] = OpString "keep-me"
+; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
+
+; CHECK: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn]] %[[#Attr]]
+; CHECK-NOT: OpExtInst %[[#VoidT]] %[[#Import]]
+
+target triple = "spir64-unknown-unknown"
+
+define spir_func void @foo() #0 !dbg !4 {
+  ret void
+}
+
+attributes #0 = { "keep-me" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, emissionKind: FullDebug)
+!1 = !DIFile(filename: "foo.c", directory: "./")
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !6, unit: !0)
+!6 = !DISubroutineType(types: !7)
+!7 = !{null}
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll
new file mode 100644
index 0000000000000..77b323a838440
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll
@@ -0,0 +1,34 @@
+; Adapted from the SPIRV-LLVM-Translator --spirv-preserve-auxdata forward-path
+; tests. The backend only records metadata whose operands are all MDStrings, so
+; the numeric !{i32 5} metadata on @test_val is dropped; only @test_string is
+; emitted.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
+; RUN:   %s -o - | FileCheck %s
+
+; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
+
+; CHECK-DAG: %[[#MDName:]] = OpString "bar"
+; CHECK-DAG: %[[#MDValue:]] = OpString "baz"
+; CHECK-DAG: %[[#Fcn1:]] = OpString "test_string"
+
+; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
+
+; Numeric function metadata is not representable as OpStrings and is skipped.
+; CHECK-NOT: OpString "test_val"
+
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn1]] %[[#MDName]] %[[#MDValue]]
+
+target triple = "spir64-unknown-unknown"
+
+define spir_func void @test_val() !foo !1 {
+  ret void
+}
+
+define spir_func void @test_string() !bar !2 {
+  ret void
+}
+
+!1 = !{i32 5}
+!2 = !{!"baz"}
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata-requires-extension.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata-requires-extension.ll
new file mode 100644
index 0000000000000..3927d55754bd1
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata-requires-extension.ll
@@ -0,0 +1,12 @@
+; -spirv-preserve-auxdata is an error unless SPV_KHR_non_semantic_info is on.
+
+; RUN: not --crash llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   -spirv-preserve-auxdata %s -o - 2>&1 | FileCheck %s
+
+; CHECK: -spirv-preserve-auxdata requires the SPV_KHR_non_semantic_info extension
+
+target triple = "spir64-unknown-unknown"
+
+define spir_func void @fn() "my-attr"="val" {
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
index cc6c1f8637937..9c6bd3b7558d0 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
@@ -31,11 +31,11 @@
 ; CHECK-NOT: OpString "ae"
 
 ; Records emit in module order; Linkage's 2nd operand is the shared AE const.
-; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#akind]] %[[#aval]]
-; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#nounwind]]
-; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#gname]] %[[#mdname]] %[[#mdval]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#akind]] %[[#aval]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#nounwind]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#gname]] %[[#mdname]] %[[#mdval]]
 ; CHECK-DAG: %[[#aelink:]] = OpConstant %[[#i32]] 0
-; CHECK-DAG: %{{[0-9]+}} = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %{{[0-9]+}} %[[#aelink]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#]] %[[#aelink]]
 
 @gv = global i32 0, align 4, !some.md !0
 
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll
new file mode 100644
index 0000000000000..20a700b2a773c
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll
@@ -0,0 +1,28 @@
+; Adapted from the SPIRV-LLVM-Translator --spirv-preserve-auxdata forward-path
+; tests. Global-variable attributes are emitted as NonSemantic.AuxData records.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
+; RUN:   %s -o - | FileCheck %s
+
+; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
+
+; CHECK-DAG: %[[#GVName:]] = OpString "g"
+; CHECK-DAG: %[[#Attr0:]] = OpString "flag"
+; CHECK-DAG: %[[#Attr1LHS:]] = OpString "my-gv-attr"
+; CHECK-DAG: %[[#Attr1RHS:]] = OpString "7"
+
+; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
+
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GVName]] %[[#Attr0]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GVName]] %[[#Attr1LHS]] %[[#Attr1RHS]]
+
+target triple = "spir64-unknown-unknown"
+
+ at g = addrspace(1) global i32 0 #0
+
+define spir_func void @use() {
+  ret void
+}
+
+attributes #0 = { "my-gv-attr"="7" "flag" }
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll
new file mode 100644
index 0000000000000..191f510a04c06
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll
@@ -0,0 +1,28 @@
+; Adapted from the SPIRV-LLVM-Translator --spirv-preserve-auxdata forward-path
+; tests. Global-variable metadata with all-MDString operands is emitted as a
+; NonSemantic.AuxData record.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
+; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
+; RUN:   %s -o - | FileCheck %s
+
+; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
+
+; CHECK-DAG: %[[#MD0:]] = OpString "hi"
+; CHECK-DAG: %[[#MD1:]] = OpString "there"
+; CHECK-DAG: %[[#GVName:]] = OpString "a"
+; CHECK-DAG: %[[#MDName:]] = OpString "some.gv.md"
+
+; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
+
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GVName]] %[[#MDName]] %[[#MD0]] %[[#MD1]]
+
+target triple = "spir64-unknown-unknown"
+
+ at a = addrspace(1) global i8 0, !some.gv.md !0
+
+define spir_func void @use() {
+  ret void
+}
+
+!0 = !{!"hi", !"there"}

>From 825a730b8fd0678290c77b5ac8d31500a3111d43 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Tue, 2 Jun 2026 13:38:49 +0200
Subject: [PATCH 8/9] Address comment

---
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp |  4 +---
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h   |  5 ++--
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          |  5 ----
 .../preserve-auxdata.ll                       |  3 ++-
 .../linkage/available-externally-function.ll  | 23 ++++++++++++-------
 5 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
index 31c8e55d3da58..63e46a5776fc7 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
@@ -54,9 +54,7 @@ SPIRVAuxDataHandler::SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M)
       LinkagePreservedFns.push_back(&F);
 }
 
-bool SPIRVAuxDataHandler::hasWork() const {
-  return !LinkagePreservedFns.empty() || SPVPreserveAuxData;
-}
+bool SPIRVAuxDataHandler::hasWork() const { return SPVPreserveAuxData; }
 
 void SPIRVAuxDataHandler::prepareModuleOutput(const SPIRVSubtarget &ST,
                                               SPIRV::ModuleAnalysisInfo &MAI) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
index 5a672b1abcde2..68ef62c170f5f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
@@ -7,8 +7,9 @@
 //===----------------------------------------------------------------------===//
 //
 // Emits NonSemantic.AuxData ExtInst annotations (mirrors SPIRV-LLVM-Translator
-// --spirv-preserve-auxdata). Linkage records emit unconditionally for AE-tagged
-// functions; attribute/metadata records are gated by -spirv-preserve-auxdata.
+// --spirv-preserve-auxdata).
+// Otherwise, AE-tagged functions emit plain Export linkage via
+// LinkageAttributes.
 //
 //===----------------------------------------------------------------------===//
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index e56d2e47e9045..0ef31f4182b4e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -1217,11 +1217,6 @@ getSpirvLinkageTypeFor(const SPIRVSubtarget &ST, const GlobalValue &GV) {
   if (GV.hasLocalLinkage())
     return std::nullopt;
 
-  // Preserved via NonSemantic.AuxData; skip LinkageAttributes.
-  if (const auto *F = dyn_cast<Function>(&GV))
-    if (F->hasFnAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR))
-      return std::nullopt;
-
   if (GV.isDeclarationForLinker()) {
     // Interface variables must not get Import linkage.
     if (const auto *GVar = dyn_cast<GlobalVariable>(&GV)) {
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
index 9c6bd3b7558d0..89b8526282e47 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
@@ -4,11 +4,12 @@
 ; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
 ; RUN:   %s -o - | FileCheck %s
 
-; Off by default: only the Linkage record fires (for the AE function).
+; Off by default: no AuxData records at all, even with the extension enabled.
 ; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
 ; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info %s -o - \
 ; RUN:   | FileCheck %s --check-prefix=OFF
 
+; OFF-NOT: OpExtInstImport "NonSemantic.AuxData"
 ; OFF-NOT: OpString "my-attr"
 ; OFF-NOT: OpString "nounwind"
 ; OFF-NOT: OpString "some.md"
diff --git a/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll b/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
index 35f800d1ea1cd..ed7e7e146cf42 100644
--- a/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
+++ b/llvm/test/CodeGen/SPIRV/linkage/available-externally-function.ll
@@ -1,25 +1,32 @@
-; Without SPV_KHR_non_semantic_info: emitted as plain external definitions
-; (original linkage has no native SPIR-V representation).
+; available_externally functions are emitted as definitions with Export
+; linkage. The original linkage is only preserved as a NonSemantic.AuxData
+; annotation when -spirv-preserve-auxdata is passed.
 
 ; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --check-prefix=NOAUX
 ; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=NOAUX
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=NOAUX
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
+; Passing the flag without the extension is a fatal error.
+
+; RUN: not --crash llc -O0 -mtriple=spirv64-unknown-unknown --spirv-preserve-auxdata %s -o - 2>&1 | FileCheck %s --check-prefix=ERR
+; ERR: -spirv-preserve-auxdata requires the SPV_KHR_non_semantic_info extension to be enabled.
+
 ; NOAUX-NOT: OpExtension "SPV_KHR_non_semantic_info"
 ; NOAUX-NOT: NonSemantic.AuxData
 ; NOAUX-DAG: OpName %[[#ae_func:]] "ae_func"
 ; NOAUX-DAG: OpName %[[#caller:]] "caller"
-; NOAUX-NOT: OpDecorate %[[#ae_func]] LinkageAttributes
+; NOAUX-DAG: OpDecorate %[[#ae_func]] LinkageAttributes "ae_func" Export
 ; NOAUX:     %[[#ae_func]] = OpFunction
 ; NOAUX:     OpFunctionEnd
 ; NOAUX:     %[[#caller]] = OpFunction
 ; NOAUX:     OpFunctionCall %[[#]] %[[#ae_func]]
 
-; With SPV_KHR_non_semantic_info: linkage preserved via a
-; NonSemantic.AuxData::Linkage annotation for round-trip recovery.
+; With -spirv-preserve-auxdata: Export linkage plus a NonSemantic.AuxData
+; Linkage annotation recording the original available_externally linkage.
 
-; RUN: llc -O0 -mtriple=spirv32-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=AUX
-; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=AUX
+; RUN: llc -O0 -mtriple=spirv32-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info --spirv-preserve-auxdata %s -o - | FileCheck %s --check-prefix=AUX
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info --spirv-preserve-auxdata %s -o - | FileCheck %s --check-prefix=AUX
 ;; spirv-val rejects the AuxData forward-ref without
 ;; SPV_KHR_relaxed_extended_instruction; matches Translator behavior.
 
@@ -27,7 +34,7 @@
 ; AUX-DAG: %[[#auxset:]] = OpExtInstImport "NonSemantic.AuxData"
 ; AUX-DAG: OpName %[[#ae_func:]] "ae_func"
 ; AUX-DAG: OpName %[[#caller:]] "caller"
-; AUX-NOT: OpDecorate %[[#ae_func]] LinkageAttributes
+; AUX-DAG: OpDecorate %[[#ae_func]] LinkageAttributes "ae_func" Export
 ; AUX-DAG: %[[#void:]] = OpTypeVoid
 ; AUX-DAG: %[[#i32:]] = OpTypeInt 32 0
 ; AUX-DAG: %[[#zero:]] = OpConstant %[[#i32]] 0

>From b6d51cf2d121b72a0e9ab5abc7c27505c6a558f6 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 8 Jun 2026 10:30:26 +0200
Subject: [PATCH 9/9] Address review comments

---
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp | 165 +++++++++++++-----
 llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h   |  27 ++-
 .../Target/SPIRV/SPIRVPrepareFunctions.cpp    |  10 ++
 .../preserve-all-function-attributes.ll       |   6 +-
 .../preserve-all-function-metadata-debug.ll   |   2 +-
 .../preserve-all-function-metadata.ll         |  18 +-
 .../preserve-auxdata.ll                       |  28 +--
 .../preserve-gv-attributes.ll                 |   8 +-
 .../preserve-gv-metadata.ll                   |   6 +-
 .../linkage/available-externally-global.ll    |  33 ++++
 .../CodeGen/SPIRV/linkage/linkage-types.ll    |   3 +-
 11 files changed, 227 insertions(+), 79 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/linkage/available-externally-global.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
index 63e46a5776fc7..d60b204620720 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.cpp
@@ -12,6 +12,7 @@
 #include "SPIRVUtils.h"
 #include "llvm/CodeGen/AsmPrinter.h"
 #include "llvm/IR/Attributes.h"
+#include "llvm/IR/Constants.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GlobalObject.h"
 #include "llvm/IR/GlobalVariable.h"
@@ -47,11 +48,18 @@ AttributeSet getGOAttrs(const GlobalObject *GO) {
 }
 } // namespace
 
+static bool wasAvailableExternally(const GlobalObject *GO) {
+  if (const auto *F = dyn_cast<Function>(GO))
+    return F->hasFnAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR);
+  return cast<GlobalVariable>(GO)->getAttributes().hasAttribute(
+      SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR);
+}
+
 SPIRVAuxDataHandler::SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M)
     : Asm(AP), Mod(M) {
-  for (const Function &F : M)
-    if (F.hasFnAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR))
-      LinkagePreservedFns.push_back(&F);
+  for (const GlobalObject &GO : M.global_objects())
+    if (wasAvailableExternally(&GO))
+      LinkagePreservedGOs.push_back(&GO);
 }
 
 bool SPIRVAuxDataHandler::hasWork() const { return SPVPreserveAuxData; }
@@ -87,9 +95,8 @@ SPIRVAuxDataHandler::getOrEmitString(StringRef S,
   return Reg;
 }
 
-void SPIRVAuxDataHandler::collectAttributesFor(
-    const GlobalObject *GO, function_ref<MCRegister()> GetNameReg,
-    SPIRV::ModuleAnalysisInfo &MAI) {
+void SPIRVAuxDataHandler::collectAttributesFor(const GlobalObject *GO,
+                                               SPIRV::ModuleAnalysisInfo &MAI) {
   AuxDataOpcode Opcode = isa<Function>(GO) ? FunctionAttributeOpcode
                                            : GlobalVariableAttributeOpcode;
   for (const Attribute &A : getGOAttrs(GO)) {
@@ -98,39 +105,47 @@ void SPIRVAuxDataHandler::collectAttributesFor(
       continue;
     ExtInstRecord Rec;
     Rec.Opcode = Opcode;
-    Rec.Operands.push_back(GetNameReg());
+    Rec.Target = GO;
     if (A.isStringAttribute()) {
-      Rec.Operands.push_back(getOrEmitString(A.getKindAsString(), MAI));
+      Rec.Operands.push_back({getOrEmitString(A.getKindAsString(), MAI)});
       StringRef Val = A.getValueAsString();
       if (!Val.empty())
-        Rec.Operands.push_back(getOrEmitString(Val, MAI));
+        Rec.Operands.push_back({getOrEmitString(Val, MAI)});
     } else {
       Rec.Operands.push_back(
-          getOrEmitString(StringPool.save(A.getAsString()), MAI));
+          {getOrEmitString(StringPool.save(A.getAsString()), MAI)});
     }
     PendingRecords.push_back(std::move(Rec));
   }
 }
 
-void SPIRVAuxDataHandler::collectMetadataFor(
-    const GlobalObject *GO, function_ref<MCRegister()> GetNameReg,
-    ArrayRef<StringRef> MDNames, SPIRV::ModuleAnalysisInfo &MAI) {
+void SPIRVAuxDataHandler::collectMetadataFor(const GlobalObject *GO,
+                                             ArrayRef<StringRef> MDNames,
+                                             SPIRV::ModuleAnalysisInfo &MAI) {
   SmallVector<std::pair<unsigned, MDNode *>> AllMD;
   GO->getAllMetadata(AllMD);
   if (AllMD.empty())
     return;
   AuxDataOpcode Opcode =
       isa<Function>(GO) ? FunctionMetadataOpcode : GlobalVariableMetadataOpcode;
-  // Skip non-MDString operands: emitting them would require a full value
-  // translation we can't safely drive from here.
-  auto CollectStrings =
-      [&](MDNode *MD) -> std::optional<SmallVector<MCRegister, 4>> {
-    SmallVector<MCRegister, 4> Out;
+  // MDString operands become OpStrings; ValueAsMetadata constants (e.g.
+  // !{i32 5}) become OpConstants emitted at section 10. Any other operand
+  // kind would need full value translation, so skip the whole node.
+  auto CollectOperands =
+      [&](MDNode *MD) -> std::optional<SmallVector<Operand, 4>> {
+    SmallVector<Operand, 4> Out;
     for (const MDOperand &MdOp : MD->operands()) {
-      auto *MDStr = dyn_cast_or_null<MDString>(MdOp.get());
-      if (!MDStr)
+      Metadata *Md = MdOp.get();
+      if (auto *MDStr = dyn_cast_or_null<MDString>(Md)) {
+        Out.push_back({getOrEmitString(MDStr->getString(), MAI)});
+      } else if (auto *VAM = dyn_cast_or_null<ValueAsMetadata>(Md)) {
+        auto *C = dyn_cast<Constant>(VAM->getValue());
+        if (!C || !(isa<ConstantInt>(C) || isa<ConstantFP>(C)))
+          return std::nullopt;
+        Out.push_back({MCRegister(), C});
+      } else {
         return std::nullopt;
-      Out.push_back(getOrEmitString(MDStr->getString(), MAI));
+      }
     }
     return Out;
   };
@@ -140,13 +155,13 @@ void SPIRVAuxDataHandler::collectMetadataFor(
     StringRef MDName = MDNames[MD.first];
     if (MDName == "spirv.Decorations" || MDName == "spirv.ParameterDecorations")
       continue;
-    auto Operands = CollectStrings(MD.second);
+    auto Operands = CollectOperands(MD.second);
     if (!Operands)
       continue;
     ExtInstRecord Rec;
     Rec.Opcode = Opcode;
-    Rec.Operands.push_back(GetNameReg());
-    Rec.Operands.push_back(getOrEmitString(MDName, MAI));
+    Rec.Target = GO;
+    Rec.Operands.push_back({getOrEmitString(MDName, MAI)});
     Rec.Operands.append(Operands->begin(), Operands->end());
     PendingRecords.push_back(std::move(Rec));
   }
@@ -162,15 +177,8 @@ void SPIRVAuxDataHandler::emitAuxDataStrings(SPIRV::ModuleAnalysisInfo &MAI) {
   for (const GlobalObject &GO : Mod.global_objects()) {
     if (GO.isDeclaration())
       continue;
-    // Defer the name OpString until the first record actually fires.
-    MCRegister NameReg;
-    auto GetNameReg = [&]() {
-      if (!NameReg.isValid())
-        NameReg = getOrEmitString(GO.getName(), MAI);
-      return NameReg;
-    };
-    collectAttributesFor(&GO, GetNameReg, MAI);
-    collectMetadataFor(&GO, GetNameReg, MDNames, MAI);
+    collectAttributesFor(&GO, MAI);
+    collectMetadataFor(&GO, MDNames, MAI);
   }
 }
 
@@ -181,23 +189,31 @@ void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) {
 
   MCRegister VoidTypeReg = findOrEmitOpTypeVoid(MAI);
 
-  for (const ExtInstRecord &Rec : PendingRecords)
-    emitAuxDataExtInst(Rec.Opcode, VoidTypeReg, ExtSetReg, Rec.Operands, MAI);
+  for (const ExtInstRecord &Rec : PendingRecords) {
+    MCRegister TargetReg = MAI.getGlobalObjReg(Rec.Target);
+    if (!TargetReg.isValid())
+      continue;
+    SmallVector<MCRegister, 5> Operands;
+    Operands.push_back(TargetReg);
+    for (const Operand &Op : Rec.Operands)
+      Operands.push_back(Op.Const ? emitConstant(Op.Const, MAI) : Op.Reg);
+    emitAuxDataExtInst(Rec.Opcode, VoidTypeReg, ExtSetReg, Operands, MAI);
+  }
 
-  if (LinkagePreservedFns.empty())
+  if (LinkagePreservedGOs.empty())
     return;
 
   MCRegister UInt32TypeReg = findOrEmitOpTypeUInt32(MAI);
   MCRegister AEConstReg;
-  for (const Function *F : LinkagePreservedFns) {
-    MCRegister FnReg = MAI.getGlobalObjReg(F);
-    if (!FnReg.isValid())
+  for (const GlobalObject *GO : LinkagePreservedGOs) {
+    MCRegister TargetReg = MAI.getGlobalObjReg(GO);
+    if (!TargetReg.isValid())
       continue;
     if (!AEConstReg.isValid())
       AEConstReg =
           emitOpConstantUInt32(AvailableExternally, UInt32TypeReg, MAI);
     emitAuxDataExtInst(LinkageOpcode, VoidTypeReg, ExtSetReg,
-                       {FnReg, AEConstReg}, MAI);
+                       {TargetReg, AEConstReg}, MAI);
   }
 }
 
@@ -235,26 +251,85 @@ SPIRVAuxDataHandler::findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI) {
 }
 
 MCRegister
-SPIRVAuxDataHandler::findOrEmitOpTypeUInt32(SPIRV::ModuleAnalysisInfo &MAI) {
+SPIRVAuxDataHandler::findOrEmitOpTypeInt(unsigned BitWidth,
+                                         SPIRV::ModuleAnalysisInfo &MAI) {
   // SPIR-V OpTypeInt: <width>, <signedness>. Signedness 0 = unsigned, 1 =
-  // signed (we want unsigned i32).
-  constexpr int64_t Int32BitWidth = 32;
+  // signed; we always emit unsigned.
   constexpr int64_t UnsignedSignedness = 0;
   for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars))
     if (MI->getOpcode() == SPIRV::OpTypeInt &&
-        MI->getOperand(1).getImm() == Int32BitWidth &&
+        MI->getOperand(1).getImm() == static_cast<int64_t>(BitWidth) &&
         MI->getOperand(2).getImm() == UnsignedSignedness)
       return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
   MCRegister Reg = MAI.getNextIDRegister();
   MCInst Inst;
   Inst.setOpcode(SPIRV::OpTypeInt);
   Inst.addOperand(MCOperand::createReg(Reg));
-  Inst.addOperand(MCOperand::createImm(Int32BitWidth));
+  Inst.addOperand(MCOperand::createImm(BitWidth));
   Inst.addOperand(MCOperand::createImm(UnsignedSignedness));
   emitMCInst(Inst);
   return Reg;
 }
 
+MCRegister
+SPIRVAuxDataHandler::findOrEmitOpTypeUInt32(SPIRV::ModuleAnalysisInfo &MAI) {
+  return findOrEmitOpTypeInt(32, MAI);
+}
+
+MCRegister
+SPIRVAuxDataHandler::findOrEmitOpTypeFloat(unsigned BitWidth,
+                                           SPIRV::ModuleAnalysisInfo &MAI) {
+  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars))
+    if (MI->getOpcode() == SPIRV::OpTypeFloat &&
+        MI->getOperand(1).getImm() == static_cast<int64_t>(BitWidth))
+      return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+  MCRegister Reg = MAI.getNextIDRegister();
+  MCInst Inst;
+  Inst.setOpcode(SPIRV::OpTypeFloat);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  Inst.addOperand(MCOperand::createImm(BitWidth));
+  emitMCInst(Inst);
+  return Reg;
+}
+
+MCRegister SPIRVAuxDataHandler::emitConstant(const Constant *C,
+                                             SPIRV::ModuleAnalysisInfo &MAI) {
+  auto [It, Inserted] = ConstantRegs.try_emplace(C);
+  if (!Inserted)
+    return It->second;
+
+  APInt Bits;
+  unsigned Opcode;
+  MCRegister TypeReg;
+  if (const auto *CI = dyn_cast<ConstantInt>(C)) {
+    Bits = CI->getValue();
+    Opcode = SPIRV::OpConstantI;
+    TypeReg = findOrEmitOpTypeInt(Bits.getBitWidth(), MAI);
+  } else {
+    const auto *CF = cast<ConstantFP>(C);
+    Bits = CF->getValueAPF().bitcastToAPInt();
+    Opcode = SPIRV::OpConstantF;
+    TypeReg = findOrEmitOpTypeFloat(Bits.getBitWidth(), MAI);
+  }
+
+  MCRegister Reg = MAI.getNextIDRegister();
+  It->second = Reg;
+  MCInst Inst;
+  Inst.setOpcode(Opcode);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  Inst.addOperand(MCOperand::createReg(TypeReg));
+  // SPIR-V encodes the literal as ceil(width/32) little-endian 32-bit words.
+  unsigned NumWords = std::max(1u, (Bits.getBitWidth() + 31) / 32);
+  for (unsigned I = 0; I < NumWords; ++I)
+    Inst.addOperand(MCOperand::createImm(Bits.extractBitsAsZExtValue(
+        std::min(32u, Bits.getBitWidth() - I * 32), I * 32)));
+  // The asm printer needs this hint to render an f16 literal correctly.
+  if (Opcode == SPIRV::OpConstantF && Bits.getBitWidth() == 16)
+    Inst.setFlags(SPIRV::INST_PRINTER_WIDTH16);
+  emitMCInst(Inst);
+  return Reg;
+}
+
 MCRegister SPIRVAuxDataHandler::emitOpConstantUInt32(
     uint32_t Value, MCRegister UInt32TypeReg, SPIRV::ModuleAnalysisInfo &MAI) {
   MCRegister Reg = MAI.getNextIDRegister();
diff --git a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
index 68ef62c170f5f..1d6ba998e6c2e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVAuxDataHandler.h
@@ -19,7 +19,6 @@
 #include "SPIRVModuleAnalysis.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/MC/MCRegister.h"
@@ -29,6 +28,7 @@
 namespace llvm {
 
 class AsmPrinter;
+class Constant;
 class Function;
 class GlobalObject;
 class Module;
@@ -61,15 +61,25 @@ class SPIRVAuxDataHandler {
   void emitAuxData(SPIRV::ModuleAnalysisInfo &MAI);
 
 private:
+  // An ExtInst operand is either an already-emitted OpString (Reg valid) or a
+  // ValueAsMetadata constant whose OpConstant is emitted lazily at section 10
+  // (Const set). Exactly one is populated.
+  struct Operand {
+    MCRegister Reg;
+    const Constant *Const = nullptr;
+  };
+  // The Target operand (the OpFunction / OpVariable <id>) is resolved from
+  // GlobalObjMap at emit time, so records keep the GlobalObject, not a reg.
   struct ExtInstRecord {
     AuxDataOpcode Opcode;
-    SmallVector<MCRegister, 4> Operands;
+    const GlobalObject *Target;
+    SmallVector<Operand, 4> Operands;
   };
 
   AsmPrinter &Asm;
   const Module &Mod;
 
-  SmallVector<const Function *> LinkagePreservedFns;
+  SmallVector<const GlobalObject *> LinkagePreservedGOs;
 
   // Backing storage for non-string-attribute strings; StringRegs keys are
   // StringRefs into it.
@@ -77,22 +87,25 @@ class SPIRVAuxDataHandler {
   UniqueStringSaver StringPool{StringAlloc};
 
   DenseMap<StringRef, MCRegister> StringRegs;
+  DenseMap<const Constant *, MCRegister> ConstantRegs;
   SmallVector<ExtInstRecord> PendingRecords;
 
   MCRegister getOrEmitString(StringRef S, SPIRV::ModuleAnalysisInfo &MAI);
   void collectAttributesFor(const GlobalObject *GO,
-                            function_ref<MCRegister()> GetNameReg,
                             SPIRV::ModuleAnalysisInfo &MAI);
-  void collectMetadataFor(const GlobalObject *GO,
-                          function_ref<MCRegister()> GetNameReg,
-                          ArrayRef<StringRef> MDNames,
+  void collectMetadataFor(const GlobalObject *GO, ArrayRef<StringRef> MDNames,
                           SPIRV::ModuleAnalysisInfo &MAI);
 
   void emitMCInst(MCInst &Inst);
   MCRegister findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister findOrEmitOpTypeInt(unsigned BitWidth,
+                                 SPIRV::ModuleAnalysisInfo &MAI);
   MCRegister findOrEmitOpTypeUInt32(SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister findOrEmitOpTypeFloat(unsigned BitWidth,
+                                   SPIRV::ModuleAnalysisInfo &MAI);
   MCRegister emitOpConstantUInt32(uint32_t Value, MCRegister UInt32TypeReg,
                                   SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister emitConstant(const Constant *C, SPIRV::ModuleAnalysisInfo &MAI);
   void emitAuxDataExtInst(AuxDataOpcode Opcode, MCRegister VoidTypeReg,
                           MCRegister ExtSetReg, ArrayRef<MCRegister> Operands,
                           SPIRV::ModuleAnalysisInfo &MAI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 4211197fc97d2..10772409f96a5 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -829,6 +829,16 @@ bool SPIRVPrepareFunctionsImpl::runOnModule(Module &M) {
   Changed |= terminateBlocksAfterTrap(M, Intrinsic::trap);
   Changed |= terminateBlocksAfterTrap(M, Intrinsic::ubsantrap);
 
+  for (GlobalVariable &GV : M.globals()) {
+    // Strip + tag available_externally globals so AuxData can re-emit the
+    // original linkage as NonSemantic.AuxData::Linkage.
+    if (GV.hasAvailableExternallyLinkage() && !GV.isDeclaration()) {
+      GV.addAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR);
+      GV.setLinkage(GlobalValue::ExternalLinkage);
+      Changed = true;
+    }
+  }
+
   for (Function &F : M) {
     // MachineFunctionPass skips available_externally; strip + tag so AuxData
     // can re-emit the original linkage as NonSemantic.AuxData::Linkage.
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll
index e034a51a9ae10..ec15eb6c1e080 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-attributes.ll
@@ -7,12 +7,14 @@
 
 ; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
 
-; CHECK-DAG: %[[#Fcn0:]] = OpString "mul_add"
 ; CHECK-DAG: %[[#Attr0:]] = OpString "foo"
-; CHECK-DAG: %[[#Fcn1:]] = OpString "test"
 ; CHECK-DAG: %[[#Attr1LHS:]] = OpString "bar"
 ; CHECK-DAG: %[[#Attr1RHS:]] = OpString "baz"
 
+; The Target operand is the real OpFunction <id>, not an OpString name.
+; CHECK-DAG: OpName %[[#Fcn0:]] "mul_add"
+; CHECK-DAG: OpName %[[#Fcn1:]] "test"
+
 ; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
 
 ; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn0]] %[[#Attr0]]
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll
index 069068b754366..13465a374c652 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata-debug.ll
@@ -9,8 +9,8 @@
 ; RUN:   %s -o - | FileCheck %s
 
 ; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
-; CHECK-DAG: %[[#Fcn:]] = OpString "foo"
 ; CHECK-DAG: %[[#Attr:]] = OpString "keep-me"
+; CHECK-DAG: OpName %[[#Fcn:]] "foo"
 ; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
 
 ; CHECK: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn]] %[[#Attr]]
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll
index 77b323a838440..43ed97b48a20d 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-all-function-metadata.ll
@@ -1,7 +1,6 @@
 ; Adapted from the SPIRV-LLVM-Translator --spirv-preserve-auxdata forward-path
-; tests. The backend only records metadata whose operands are all MDStrings, so
-; the numeric !{i32 5} metadata on @test_val is dropped; only @test_string is
-; emitted.
+; tests. Numeric metadata operands become OpConstants; string metadata operands
+; become OpStrings.
 
 ; RUN: llc -O0 -mtriple=spirv64-unknown-unknown \
 ; RUN:   --spirv-ext=+SPV_KHR_non_semantic_info -spirv-preserve-auxdata \
@@ -9,15 +8,20 @@
 
 ; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
 
+; CHECK-DAG: %[[#MDNameVal:]] = OpString "foo"
 ; CHECK-DAG: %[[#MDName:]] = OpString "bar"
 ; CHECK-DAG: %[[#MDValue:]] = OpString "baz"
-; CHECK-DAG: %[[#Fcn1:]] = OpString "test_string"
 
-; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
+; The Target operand is the real OpFunction <id>, not an OpString name.
+; CHECK-DAG: OpName %[[#Fcn0:]] "test_val"
+; CHECK-DAG: OpName %[[#Fcn1:]] "test_string"
 
-; Numeric function metadata is not representable as OpStrings and is skipped.
-; CHECK-NOT: OpString "test_val"
+; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
+; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#Const:]] = OpConstant %[[#I32]] 5
 
+; Numeric metadata operand is emitted as an OpConstant id.
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn0]] %[[#MDNameVal]] %[[#Const]]
 ; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#Fcn1]] %[[#MDName]] %[[#MDValue]]
 
 target triple = "spir64-unknown-unknown"
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
index 89b8526282e47..d84f95bf7c70c 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-auxdata.ll
@@ -16,36 +16,42 @@
 ; OFF-NOT: OpString "spv.was-available-externally"
 
 ; CHECK-DAG: %[[#auxset:]] = OpExtInstImport "NonSemantic.AuxData"
-; CHECK-DAG: %[[#fname:]] = OpString "fn"
 ; CHECK-DAG: %[[#akind:]] = OpString "my-attr"
 ; CHECK-DAG: %[[#aval:]]  = OpString "val"
 ; CHECK-DAG: %[[#nounwind:]] = OpString "nounwind"
-; CHECK-DAG: %[[#gname:]] = OpString "gv"
 ; CHECK-DAG: %[[#mdname:]] = OpString "some.md"
 ; CHECK-DAG: %[[#mdval:]] = OpString "hello"
+; CHECK-DAG: %[[#numname:]] = OpString "num.md"
 ; CHECK-DAG: %[[#void:]] = OpTypeVoid
 ; CHECK-DAG: %[[#i32:]] = OpTypeInt 32 0
 
+; The Target operand of every record is the real OpFunction / OpVariable <id>,
+; not an OpString of the name. Names are only emitted via OpName.
+; CHECK-DAG: OpName %[[#fn:]] "fn"
+; CHECK-DAG: OpName %[[#gv:]] "gv"
+
 ; Internal marker must not leak as a string.
 ; CHECK-NOT: OpString "spv.was-available-externally"
-; @ae has no payload, so no name OpString.
-; CHECK-NOT: OpString "ae"
 
-; Records emit in module order; Linkage's 2nd operand is the shared AE const.
-; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#akind]] %[[#aval]]
-; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fname]] %[[#nounwind]]
-; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#gname]] %[[#mdname]] %[[#mdval]]
-; CHECK-DAG: %[[#aelink:]] = OpConstant %[[#i32]] 0
-; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#]] %[[#aelink]]
+; Records emit in module order; numeric metadata becomes OpConstant operands.
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fn]] %[[#akind]] %[[#aval]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#fn]] %[[#nounwind]]
+; CHECK-DAG: %[[#mdconst:]] = OpConstant %[[#i32]] 5
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#gv]] %[[#mdname]] %[[#mdval]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#gv]] %[[#numname]] %[[#mdconst]]
 
- at gv = global i32 0, align 4, !some.md !0
+ at gv = addrspace(1) global i32 0, align 4, !some.md !0, !num.md !1
 
 define spir_func i32 @fn(i32 %x) "my-attr"="val" nounwind {
   ret i32 %x
 }
 
+; @ae has no payload, so no attribute/metadata records are emitted for it.
+; CHECK-NOT: OpString "ae"
+
 define available_externally spir_func i32 @ae(i32 %x) {
   ret i32 %x
 }
 
 !0 = !{!"hello"}
+!1 = !{i32 5}
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll
index 20a700b2a773c..ab7e6f7eaa0e8 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-attributes.ll
@@ -7,15 +7,17 @@
 
 ; CHECK: %[[#Import:]] = OpExtInstImport "NonSemantic.AuxData"
 
-; CHECK-DAG: %[[#GVName:]] = OpString "g"
 ; CHECK-DAG: %[[#Attr0:]] = OpString "flag"
 ; CHECK-DAG: %[[#Attr1LHS:]] = OpString "my-gv-attr"
 ; CHECK-DAG: %[[#Attr1RHS:]] = OpString "7"
 
+; The Target operand is the real OpVariable <id>, not an OpString name.
+; CHECK-DAG: OpName %[[#GV:]] "g"
+
 ; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
 
-; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GVName]] %[[#Attr0]]
-; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GVName]] %[[#Attr1LHS]] %[[#Attr1RHS]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GV]] %[[#Attr0]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GV]] %[[#Attr1LHS]] %[[#Attr1RHS]]
 
 target triple = "spir64-unknown-unknown"
 
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll
index 191f510a04c06..3759ca362af67 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_KHR_non_semantic_info/preserve-gv-metadata.ll
@@ -10,12 +10,14 @@
 
 ; CHECK-DAG: %[[#MD0:]] = OpString "hi"
 ; CHECK-DAG: %[[#MD1:]] = OpString "there"
-; CHECK-DAG: %[[#GVName:]] = OpString "a"
 ; CHECK-DAG: %[[#MDName:]] = OpString "some.gv.md"
 
+; The Target operand is the real OpVariable <id>, not an OpString name.
+; CHECK-DAG: OpName %[[#GV:]] "a"
+
 ; CHECK-DAG: %[[#VoidT:]] = OpTypeVoid
 
-; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GVName]] %[[#MDName]] %[[#MD0]] %[[#MD1]]
+; CHECK-DAG: %[[#]] = OpExtInst %[[#VoidT]] %[[#Import]] {{.+}} %[[#GV]] %[[#MDName]] %[[#MD0]] %[[#MD1]]
 
 target triple = "spir64-unknown-unknown"
 
diff --git a/llvm/test/CodeGen/SPIRV/linkage/available-externally-global.ll b/llvm/test/CodeGen/SPIRV/linkage/available-externally-global.ll
new file mode 100644
index 0000000000000..93672265cbdd2
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/linkage/available-externally-global.ll
@@ -0,0 +1,33 @@
+; available_externally globals are emitted as definitions with Export linkage.
+; The original linkage is only preserved as a NonSemantic.AuxData annotation
+; when -spirv-preserve-auxdata is passed.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=NOAUX
+
+; NOAUX-NOT: NonSemantic.AuxData
+; NOAUX-DAG: OpName %[[#ae_gv:]] "ae_gv"
+; NOAUX-DAG: OpDecorate %[[#ae_gv]] LinkageAttributes "ae_gv" Export
+; NOAUX:     %[[#ae_gv]] = OpVariable
+
+; With -spirv-preserve-auxdata: Export linkage plus a NonSemantic.AuxData
+; Linkage annotation recording the original available_externally linkage.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info --spirv-preserve-auxdata %s -o - | FileCheck %s --check-prefix=AUX
+
+; AUX-DAG: %[[#auxset:]] = OpExtInstImport "NonSemantic.AuxData"
+; AUX-DAG: OpName %[[#ae_gv:]] "ae_gv"
+; AUX-DAG: OpDecorate %[[#ae_gv]] LinkageAttributes "ae_gv" Export
+; AUX-DAG: %[[#void:]] = OpTypeVoid
+; AUX-DAG: %[[#i32:]] = OpTypeInt 32 0
+; AUX-DAG: %[[#zero:]] = OpConstant %[[#i32]] 0
+; AUX-DAG: %[[#ae_gv]] = OpVariable
+; AUX:     %[[#]] = OpExtInst %[[#void]] %[[#auxset]] {{.+}} %[[#ae_gv]] %[[#zero]]
+
+ at ae_gv = available_externally addrspace(1) global i32 42, align 4
+
+define spir_kernel void @caller(ptr addrspace(1) %out) {
+entry:
+  %v = load i32, ptr addrspace(1) @ae_gv, align 4
+  store i32 %v, ptr addrspace(1) %out, align 4
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/linkage/linkage-types.ll b/llvm/test/CodeGen/SPIRV/linkage/linkage-types.ll
index dbe86ecac9c4b..1fd432cc3b37c 100644
--- a/llvm/test/CodeGen/SPIRV/linkage/linkage-types.ll
+++ b/llvm/test/CodeGen/SPIRV/linkage/linkage-types.ll
@@ -54,7 +54,8 @@
 ; SPIRV-DAG: OpName %[[#g:]] "g"
 ; SPIRV-DAG: OpName %[[#inline_fun:]] "inline_fun"
 
-; SPIRV-DAG: OpDecorate %[[#ae]] LinkageAttributes "ae" Import
+; available_externally is emitted as a definition with Export linkage.
+; SPIRV-DAG: OpDecorate %[[#ae]] LinkageAttributes "ae" Export
 ; SPIRV-DAG: OpDecorate %[[#e]] LinkageAttributes "e" Import
 ; SPIRV-DAG: OpDecorate %[[#f]] LinkageAttributes "f" Export
 ; SPIRV-DAG: OpDecorate %[[#w]] LinkageAttributes "w" Export



More information about the llvm-commits mailing list