[llvm] Implement support for NSDI DebugFunctionDefinition. (PR #211853)
Manuel Carrasco via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 3 04:09:22 PDT 2026
https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/211853
>From 94a2f2906f132501f67eaf639a31fe54039d6da2 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 04:42:51 -0500
Subject: [PATCH 01/16] Implement support for NSDI DebugFunction opcode.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index d0b7f67866acb..903a424335111 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1038,6 +1038,12 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
}
+ for (const DISubprogram *SP : SubprogramDefinitions) {
+ emitOpStringIfNew(SP->getName(), MAI);
+ emitOpStringIfNew(SP->getLinkageName(), MAI);
+ emitAndCacheScopePathOpStringReg(SP, MAI);
+ }
+
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
>From d901d0c3256876b6b685589eaa8a342a77cb3599 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 08:23:12 -0500
Subject: [PATCH 02/16] Simplify loops.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ------
1 file changed, 6 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 903a424335111..d0b7f67866acb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1038,12 +1038,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
}
- for (const DISubprogram *SP : SubprogramDefinitions) {
- emitOpStringIfNew(SP->getName(), MAI);
- emitOpStringIfNew(SP->getLinkageName(), MAI);
- emitAndCacheScopePathOpStringReg(SP, MAI);
- }
-
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
>From 01462125bcf37169af086ccab3a0b5073e09617e Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 04:42:51 -0500
Subject: [PATCH 03/16] Implement support for NSDI DebugFunction opcode.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index d0b7f67866acb..679dc8f4d616e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1038,6 +1038,12 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
}
+ for (const DISubprogram *SP : SubprogramDefinitions) {
+ emitOpStringIfNew(SP->getName(), MAI);
+ emitOpStringIfNew(SP->getLinkageName(), MAI);
+ ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
+ }
+
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
>From b16f12fe9602a174af6fb4c92c8b4a39e376b37e Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 08:23:12 -0500
Subject: [PATCH 04/16] Simplify loops.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ------
1 file changed, 6 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 679dc8f4d616e..d0b7f67866acb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1038,12 +1038,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
}
- for (const DISubprogram *SP : SubprogramDefinitions) {
- emitOpStringIfNew(SP->getName(), MAI);
- emitOpStringIfNew(SP->getLinkageName(), MAI);
- ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
- }
-
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
>From ade3084022875c2f63839e8b0f2722efd6713884 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 04:42:51 -0500
Subject: [PATCH 05/16] Implement support for NSDI DebugFunction opcode.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index d0b7f67866acb..679dc8f4d616e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1038,6 +1038,12 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
}
+ for (const DISubprogram *SP : SubprogramDefinitions) {
+ emitOpStringIfNew(SP->getName(), MAI);
+ emitOpStringIfNew(SP->getLinkageName(), MAI);
+ ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
+ }
+
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
>From a543d586a928a812a889b2ac10400946a614e9bf Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 11:45:11 -0500
Subject: [PATCH 06/16] Implement support for NSDI DebugFunctionDefinition.
---
llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp | 17 +-
.../SPIRV/SPIRVNonSemanticDebugHandler.cpp | 166 +++++++++++++++++-
.../SPIRV/SPIRVNonSemanticDebugHandler.h | 71 +++++---
...ug-function-definition-after-opvariable.ll | 36 ++++
.../debug-info/debug-function-definition.ll | 40 +++++
5 files changed, 300 insertions(+), 30 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 31953bf811982..4c9c0255397cc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -322,16 +322,27 @@ void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) {
SPIRV_MC::verifyInstructionPredicates(MI->getOpcode(),
getSubtargetInfo().getFeatureBits());
- if (!MAI->getSkipEmission(MI))
+ bool InstructionEmitted = !MAI->getSkipEmission(MI);
+ if (InstructionEmitted) {
outputInstruction(MI);
+ if (NSDebugHandler && !isHidden() && MAI)
+ NSDebugHandler->notifyMachineInstructionEmitted(MI, *MF, *MAI);
+ }
// Output OpLabel after OpFunction and OpFunctionParameter in the first MBB.
const MachineInstr *NextMI = MI->getNextNode();
- if (!LabeledMBB.contains(MI->getParent()) && isFuncOrHeaderInstr(MI, TII) &&
- (!NextMI || !isFuncOrHeaderInstr(NextMI, TII))) {
+ bool BlockHasLabel = LabeledMBB.contains(MI->getParent());
+ bool IsFunctionPreambleInstruction = isFuncOrHeaderInstr(MI, TII);
+ bool IsNextInstructionFunctionPreamble =
+ NextMI && isFuncOrHeaderInstr(NextMI, TII);
+ bool ShouldEmitEntryLabel = !BlockHasLabel && IsFunctionPreambleInstruction &&
+ !IsNextInstructionFunctionPreamble;
+ if (ShouldEmitEntryLabel) {
assert(MI->getParent()->getNumber() == MF->front().getNumber() &&
"OpFunction is not in the front MBB of MF");
emitOpLabel(*MI->getParent());
+ if (NSDebugHandler && !isHidden() && MAI)
+ NSDebugHandler->notifyEntryLabelEmitted(*MF, *MAI);
}
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 679dc8f4d616e..23674755b2c17 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -15,6 +15,8 @@
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/CodeGen/AsmPrinter.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/GlobalVariable.h"
@@ -193,6 +195,35 @@ static uint32_t mapCompositeTypeTag(unsigned Tag) {
}
}
+static const MachineInstr *
+findLastEmittedFunctionOpVariable(const MachineFunction &MF,
+ SPIRV::ModuleAnalysisInfo &MAI) {
+
+ // We iterate over the instructions to find the last OpVariable instruction if
+ // any. The following SPIRV rule is used to terminate the traversal earlier:
+ // SPIR-V 2.16.1, Function Structure: "All OpVariable instructions in a
+ // function must be in the first block in the function. These instructions,
+ // together with any intermixed OpLine and OpNoLine instructions, must be the
+ // first instructions in that block."
+ const MachineInstr *LastOpVariable = nullptr;
+ bool SeenOpVariable = false;
+ for (const MachineInstr &MI : MF.front()) {
+ if (MI.getOpcode() == SPIRV::OpVariable) {
+ SeenOpVariable = true;
+ if (!MAI.getSkipEmission(&MI))
+ LastOpVariable = &MI;
+ continue;
+ }
+
+ bool CanInterleaveWithOpVariable =
+ MI.getOpcode() == SPIRV::OpLine || MI.getOpcode() == SPIRV::OpNoLine;
+ if (SeenOpVariable && !CanInterleaveWithOpVariable &&
+ !MAI.getSkipEmission(&MI))
+ break;
+ }
+ return LastOpVariable;
+}
+
} // namespace
SPIRVNonSemanticDebugHandler::SPIRVNonSemanticDebugHandler(AsmPrinter &AP)
@@ -242,6 +273,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
SubprogramDefinitions.clear();
GlobalVariableDebugInfoMap.clear();
DebugFunctionDeclarationRegs.clear();
+ DebugFunctionRegs.clear();
ScopeToPathOpStringReg.clear();
CUToCompilationUnitDbgReg.clear();
DebugSourceRegByFileStr.clear();
@@ -250,6 +282,9 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
I32ConstantCache.clear();
DebugTypeFunctionCache.clear();
GlobalDIEmitted = false;
+ GlobalNSDIEnabled = false;
+ CurrentMAI = nullptr;
+ CachedExtInstSetReg = MCRegister();
#ifndef NDEBUG
NonSemanticOpStringsSectionEmitted = false;
#endif
@@ -1057,18 +1092,128 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
#endif
}
-void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
+void SPIRVNonSemanticDebugHandler::emitDebugFunctionDefinition(
+ MCRegister DebugFunctionReg, MCRegister OpFunctionReg,
SPIRV::ModuleAnalysisInfo &MAI) {
- if (GlobalDIEmitted || CompileUnits.empty())
+ assert(DebugFunctionReg.isValid() && OpFunctionReg.isValid() &&
+ "DebugFunctionDefinition operands must be valid");
+ MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
+ emitExtInst(SPIRV::NonSemanticExtInst::DebugFunctionDefinition, VoidTypeReg,
+ CachedExtInstSetReg, {DebugFunctionReg, OpFunctionReg}, MAI);
+}
+
+void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
+ CurrentMF = nullptr;
+ LastFunctionOpVariable = nullptr;
+ DebugFunctionDefinitionEmitted = false;
+}
+
+void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
+ const MachineFunction *MF) {
+ resetPerFunctionDebugState();
+ if (!GlobalNSDIEnabled || !CurrentMAI)
return;
+
+ CurrentMF = MF;
+
+ if (MF->getFunction()
+ .getFnAttribute(SPIRV_BACKEND_SERVICE_FUN_NAME)
+ .isValid())
+ return;
+
+ const DISubprogram *SP = MF->getFunction().getSubprogram();
+ if (!SP || !SP->isDefinition())
+ return;
+
+ // DebugFunctionDefinition is emitted after the last function-level
+ // OpVariable. If there are none, it is emitted after the entry OpLabel.
+ LastFunctionOpVariable = findLastEmittedFunctionOpVariable(*MF, *CurrentMAI);
+}
+
+void SPIRVNonSemanticDebugHandler::tryEmitDebugFunctionDefinition(
+ SPIRV::ModuleAnalysisInfo &MAI) {
+ if (DebugFunctionDefinitionEmitted || !GlobalNSDIEnabled)
+ return;
+
+ assert(CurrentMF && "no current MachineFunction");
+ const Function &F = CurrentMF->getFunction();
+ const DISubprogram *SP = F.getSubprogram();
+ if (!SP || !SP->isDefinition())
+ return;
+
+ auto DFIt = DebugFunctionRegs.find(SP);
+ if (DFIt == DebugFunctionRegs.end())
+ return;
+
+ MCRegister OpFunctionReg = MAI.getGlobalObjReg(&F);
+ if (!OpFunctionReg.isValid())
+ return;
+
+ emitDebugFunctionDefinition(DFIt->second, OpFunctionReg, MAI);
+ DebugFunctionDefinitionEmitted = true;
+}
+
+void SPIRVNonSemanticDebugHandler::beginFunctionImpl(
+ const MachineFunction *MF) {
+ preparePerFunctionDebug(MF);
+}
+
+void SPIRVNonSemanticDebugHandler::endFunctionImpl(const MachineFunction *MF) {
+ (void)MF;
+ resetPerFunctionDebugState();
+}
+
+void SPIRVNonSemanticDebugHandler::notifyMachineInstructionEmitted(
+ const MachineInstr *MI, const MachineFunction &MF,
+ SPIRV::ModuleAnalysisInfo &MAI) {
+ if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+ return;
+ assert(CurrentMF == &MF &&
+ "notification does not match the current MachineFunction");
+ if (MI->getParent() != &MF.front())
+ return;
+
+ // If this is the last function-level OpVariable, emit the
+ // DebugFunctionDefinition. Otherwise, we had already done it before right
+ // after the OpLabel.
+ if (MI == LastFunctionOpVariable)
+ tryEmitDebugFunctionDefinition(MAI);
+}
+
+void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
+ const MachineFunction &MF, SPIRV::ModuleAnalysisInfo &MAI) {
+ if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+ return;
+ assert(CurrentMF == &MF &&
+ "notification does not match the current MachineFunction");
+
+ // If there are no function-level OpVariables, emit the
+ // DebugFunctionDefinition. Otherwise, DebugFunctionDefinition is emitted
+ // after the last OpVariable.
+ if (!LastFunctionOpVariable)
+ tryEmitDebugFunctionDefinition(MAI);
+}
+
+bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
+ SPIRV::ModuleAnalysisInfo &MAI) {
+ if (GlobalDIEmitted)
+ return GlobalNSDIEnabled;
+
GlobalDIEmitted = true;
+ if (CompileUnits.empty()) {
+ GlobalNSDIEnabled = false;
+ return false;
+ }
+
// Retrieve the ext inst set register allocated by prepareModuleOutput().
constexpr unsigned NSSet = static_cast<unsigned>(
SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100);
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
- if (!ExtInstSetReg.isValid())
- return; // Extension not available.
+ if (!ExtInstSetReg.isValid()) {
+ GlobalNSDIEnabled = false;
+ return false;
+ }
#ifndef NDEBUG
assert(NonSemanticOpStringsSectionEmitted &&
@@ -1076,6 +1221,9 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
"emitNonSemanticGlobalDebugInfo()");
#endif
+ CurrentMAI = &MAI;
+ CachedExtInstSetReg = ExtInstSetReg;
+
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
@@ -1229,13 +1377,19 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
}
// Emit DebugFunction for DISubprogram definitions.
- for (const DISubprogram *SP : SubprogramDefinitions)
- emitDebugFunction(SP, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
+ for (const DISubprogram *SP : SubprogramDefinitions) {
+ if (auto FnReg =
+ emitDebugFunction(SP, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI))
+ DebugFunctionRegs[SP] = *FnReg;
+ }
// Emit DebugGlobalVariable for each collected DIGlobalVariable.
for (const auto &[GV, Info] : GlobalVariableDebugInfoMap)
emitDebugGlobalVariable(GV, Info, VoidTypeReg, I32TypeReg, ExtInstSetReg,
MAI);
+
+ GlobalNSDIEnabled = true;
+ return true;
}
SmallString<128>
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 5da526587df91..b1793cc0db811 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -40,16 +40,15 @@ class SPIRVSubtarget;
/// the module contains debug info (llvm.dbg.cu).
///
/// Call sequence:
-/// beginModule() -- collect compile-unit metadata.
-/// prepareModuleOutput() -- add extension + ext inst set to MAI.
-/// emitNonSemanticDebugStrings() -- OpString for NSDI strings (sec. 7).
-/// emitNonSemanticGlobalDebugInfo() -- emit DebugSource,
-/// DebugCompilationUnit, DebugTypeBasic,
-/// DebugTypePointer, DebugTypeFunction,
-/// DebugFunctionDeclaration,
-/// DebugFunction.
-/// beginFunctionImpl() -- no-op (no per-function DI yet).
-/// endFunctionImpl() -- no-op.
+/// - beginModule() collects compile-unit metadata.
+/// - prepareModuleOutput() adds the extension and ext-inst set to MAI.
+/// - emitNonSemanticDebugStrings() emits NSDI OpStrings in section 7.
+/// - emitNonSemanticGlobalDebugInfo() emits module-scope NSDI and sets
+/// GlobalNSDIEnabled.
+/// - beginFunctionImpl() prepares per-function DebugFunctionDefinition state.
+/// - SPIRVAsmPrinter notifies the handler when it emits MachineInstrs and the
+/// synthesized entry OpLabel.
+/// - endFunctionImpl() resets per-function state.
class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
struct CompileUnitInfo {
const DICompileUnit *TheCU = nullptr;
@@ -101,6 +100,10 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// (only entries where emission succeeded).
DenseMap<const DISubprogram *, MCRegister> DebugFunctionDeclarationRegs;
+ // DebugFunction result id per emitted definition DISubprogram (only entries
+ // where emission succeeded).
+ DenseMap<const DISubprogram *, MCRegister> DebugFunctionRegs;
+
// Path \c OpString result id per \c DIScope (CU, \c DIFile, declaration
// \c DISubprogram, …). Filled during \c emitNonSemanticDebugStrings() using
// \c getDebugFullPath + \c emitOpStringIfNew; section 10 uses it for
@@ -148,6 +151,20 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// change.
bool GlobalDIEmitted = false;
+ // True when emitNonSemanticGlobalDebugInfo() completed module-scope NSDI
+ // emission for this module.
+ bool GlobalNSDIEnabled = false;
+
+ SPIRV::ModuleAnalysisInfo *CurrentMAI = nullptr;
+
+ MCRegister CachedExtInstSetReg;
+
+ const MachineFunction *CurrentMF = nullptr;
+
+ const MachineInstr *LastFunctionOpVariable = nullptr;
+
+ bool DebugFunctionDefinitionEmitted = false;
+
public:
explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);
@@ -177,7 +194,17 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
/// SPIRVAsmPrinter::outputModuleSections() at section 10 in place of
/// outputModuleSection(MB_NonSemanticGlobalDI). Requires
/// emitNonSemanticDebugStrings() to have run first when NSDI strings apply.
- void emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
+ /// \returns true when module-scope NSDI emission ran; false when skipped.
+ bool emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
+
+ /// Called after an MI has been emitted.
+ void notifyMachineInstructionEmitted(const MachineInstr *MI,
+ const MachineFunction &MF,
+ SPIRV::ModuleAnalysisInfo &MAI);
+
+ /// Called after the synthesized entry \c OpLabel has been emitted.
+ void notifyEntryLabelEmitted(const MachineFunction &MF,
+ SPIRV::ModuleAnalysisInfo &MAI);
protected:
// All module-level output is driven by emitNonSemanticGlobalDebugInfo(),
@@ -191,21 +218,23 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// point and MMI remains null for this handler's entire lifetime. The
// base-class beginInstruction/endInstruction dereference MMI to create temp
// symbols for label tracking and would crash. Override them as no-ops.
- // When per-function NSDI is implemented, use Asm->OutStreamer->getContext()
- // for MCContext access rather than MMI->getContext().
+ // Future local NSDI that needs MCContext must use
+ // Asm->OutStreamer->getContext() rather than MMI->getContext().
void beginInstruction(const MachineInstr *MI) override {}
void endInstruction() override {}
- // TODO: Emit DebugFunctionDefinition here once per-function NSDI emission is
- // implemented. DebugHandlerBase::beginFunction() populates LScopes and
- // DbgValues, which are needed for DebugLine emission. Do not override
- // beginFunction() until that work is in place.
- void beginFunctionImpl(const MachineFunction *MF) override {}
- // TODO: Add per-function cleanup when DebugFunctionDefinition emission is in
- // place.
- void endFunctionImpl(const MachineFunction *MF) override {}
+ void beginFunctionImpl(const MachineFunction *MF) override;
+ void endFunctionImpl(const MachineFunction *MF) override;
private:
+ void emitDebugFunctionDefinition(MCRegister DebugFunctionReg,
+ MCRegister OpFunctionReg,
+ SPIRV::ModuleAnalysisInfo &MAI);
+
+ void resetPerFunctionDebugState();
+ void preparePerFunctionDebug(const MachineFunction *MF);
+ void tryEmitDebugFunctionDefinition(SPIRV::ModuleAnalysisInfo &MAI);
+
void emitMCInst(MCInst &Inst);
MCRegister emitOpString(StringRef S, SPIRV::ModuleAnalysisInfo &MAI);
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll
new file mode 100644
index 0000000000000..ec3c93c5115b6
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll
@@ -0,0 +1,36 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; DebugFunctionDefinition must follow function-local OpVariable instructions.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK: [[FOO:%[0-9]+]] = OpFunction
+; CHECK: OpVariable {{.*}} Function
+; CHECK-NEXT: OpVariable {{.*}} Function
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FOO]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func void @foo() !dbg !4 {
+entry:
+ %x = alloca i32, align 4
+ %y = alloca i32, align 4
+ store i32 0, ptr %x
+ store i32 1, ptr %y
+ ret void, !dbg !7
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-function-definition-after-opvariable.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!4 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!5 = !DISubroutineType(types: !6)
+!6 = !{null}
+!7 = !DILocation(line: 2, column: 1, scope: !4)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
new file mode 100644
index 0000000000000..5cacfd5c14281
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
@@ -0,0 +1,40 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Exercise NonSemantic DebugFunctionDefinition for a defined function.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "/src/debug-function-definition.c"
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit {{.*}}
+; CHECK-DAG: [[TF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugTypeFunction {{.*}}
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]] [[TF]] [[DS]] {{.*}}
+; CHECK: [[ADD_ONE:%[0-9]+]] = OpFunction
+; CHECK: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[ADD_ONE]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @add_one(i32 %value) !dbg !5 {
+entry:
+ %result = add i32 %value, 1
+ ret i32 %result, !dbg !8
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-function-definition.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!4 = !DISubroutineType(types: !6)
+!6 = !{!7, !7}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 3, scope: !5)
>From 0878f72e9418ca11fbdf584472917383d9576afd Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 11:51:06 -0500
Subject: [PATCH 07/16] Keep original comment.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index b1793cc0db811..5cf5464d1926c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -223,6 +223,9 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
void beginInstruction(const MachineInstr *MI) override {}
void endInstruction() override {}
+ // Override beginFunctionImpl(), not beginFunction():
+ // DebugHandlerBase::beginFunction() populates LScopes and DbgValues needed
+ // for future DebugLine emission.
void beginFunctionImpl(const MachineFunction *MF) override;
void endFunctionImpl(const MachineFunction *MF) override;
>From 322607ca8357eabca0d24e23195b591b2d23832e Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 10:33:43 -0500
Subject: [PATCH 08/16] [SPIRV] Drive DebugFunctionDefinition placement via
begin/endInstruction
Replace the SPIRV-specific notifyMachineInstructionEmitted() callback with
DebugHandlerBase::beginInstruction()/endInstruction().
Keep notifyEntryLabelEmitted() for the synthesized entry OpLabel, which
has no MachineInstr.
---
llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp | 7 ++--
.../SPIRV/SPIRVNonSemanticDebugHandler.cpp | 36 +++++++++++--------
.../SPIRV/SPIRVNonSemanticDebugHandler.h | 20 +++++------
3 files changed, 32 insertions(+), 31 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 4c9c0255397cc..7376e97b80901 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -323,11 +323,8 @@ void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) {
getSubtargetInfo().getFeatureBits());
bool InstructionEmitted = !MAI->getSkipEmission(MI);
- if (InstructionEmitted) {
+ if (InstructionEmitted)
outputInstruction(MI);
- if (NSDebugHandler && !isHidden() && MAI)
- NSDebugHandler->notifyMachineInstructionEmitted(MI, *MF, *MAI);
- }
// Output OpLabel after OpFunction and OpFunctionParameter in the first MBB.
const MachineInstr *NextMI = MI->getNextNode();
@@ -341,7 +338,7 @@ void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) {
assert(MI->getParent()->getNumber() == MF->front().getNumber() &&
"OpFunction is not in the front MBB of MF");
emitOpLabel(*MI->getParent());
- if (NSDebugHandler && !isHidden() && MAI)
+ if (NSDebugHandler && !isHidden())
NSDebugHandler->notifyEntryLabelEmitted(*MF, *MAI);
}
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 23674755b2c17..b6f0bd98bf377 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1163,35 +1163,43 @@ void SPIRVNonSemanticDebugHandler::endFunctionImpl(const MachineFunction *MF) {
resetPerFunctionDebugState();
}
-void SPIRVNonSemanticDebugHandler::notifyMachineInstructionEmitted(
- const MachineInstr *MI, const MachineFunction &MF,
- SPIRV::ModuleAnalysisInfo &MAI) {
- if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+void SPIRVNonSemanticDebugHandler::beginInstruction(const MachineInstr *MI) {
+ assert(CurMI == nullptr && "CurMI must be null");
+ CurMI = MI;
+}
+
+void SPIRVNonSemanticDebugHandler::endInstruction() {
+ const MachineInstr *MI = CurMI;
+ CurMI = nullptr;
+
+ if (!MI || !GlobalNSDIEnabled || DebugFunctionDefinitionEmitted || !CurrentMF)
return;
- assert(CurrentMF == &MF &&
- "notification does not match the current MachineFunction");
- if (MI->getParent() != &MF.front())
+
+ if (MI != LastFunctionOpVariable)
return;
// If this is the last function-level OpVariable, emit the
// DebugFunctionDefinition. Otherwise, we had already done it before right
- // after the OpLabel.
- if (MI == LastFunctionOpVariable)
- tryEmitDebugFunctionDefinition(MAI);
+ // after the OpLabel (see notifyEntryLabelEmitted).
+ assert(CurrentMAI && "CurrentMAI must be set");
+ tryEmitDebugFunctionDefinition(*CurrentMAI);
}
void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
const MachineFunction &MF, SPIRV::ModuleAnalysisInfo &MAI) {
- if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+ if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted || !CurrentMF)
return;
+
assert(CurrentMF == &MF &&
"notification does not match the current MachineFunction");
+ if (LastFunctionOpVariable)
+ return;
+
// If there are no function-level OpVariables, emit the
// DebugFunctionDefinition. Otherwise, DebugFunctionDefinition is emitted
- // after the last OpVariable.
- if (!LastFunctionOpVariable)
- tryEmitDebugFunctionDefinition(MAI);
+ // after the last OpVariable (see endInstruction).
+ tryEmitDebugFunctionDefinition(*CurrentMAI);
}
bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 5cf5464d1926c..bd4752e85f06c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -46,8 +46,9 @@ class SPIRVSubtarget;
/// - emitNonSemanticGlobalDebugInfo() emits module-scope NSDI and sets
/// GlobalNSDIEnabled.
/// - beginFunctionImpl() prepares per-function DebugFunctionDefinition state.
-/// - SPIRVAsmPrinter notifies the handler when it emits MachineInstrs and the
-/// synthesized entry OpLabel.
+/// - endInstruction() emits DebugFunctionDefinition after the last function-
+/// level OpVariable; SPIRVAsmPrinter calls notifyEntryLabelEmitted() after
+/// the synthesized entry OpLabel when there are no OpVariables.
/// - endFunctionImpl() resets per-function state.
class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
struct CompileUnitInfo {
@@ -197,11 +198,6 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
/// \returns true when module-scope NSDI emission ran; false when skipped.
bool emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
- /// Called after an MI has been emitted.
- void notifyMachineInstructionEmitted(const MachineInstr *MI,
- const MachineFunction &MF,
- SPIRV::ModuleAnalysisInfo &MAI);
-
/// Called after the synthesized entry \c OpLabel has been emitted.
void notifyEntryLabelEmitted(const MachineFunction &MF,
SPIRV::ModuleAnalysisInfo &MAI);
@@ -215,13 +211,13 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// DebugHandlerBase stores MMI as a pointer copy from Asm->MMI at construction
// time (DebugHandlerBase.cpp: `MMI(Asm->MMI)`). The handler is constructed
// before AsmPrinter::doInitialization() runs, so Asm->MMI is null at that
- // point and MMI remains null for this handler's entire lifetime. The
- // base-class beginInstruction/endInstruction dereference MMI to create temp
- // symbols for label tracking and would crash. Override them as no-ops.
+ // point and MMI remains null for this handler's entire lifetime. Do not call
+ // the base-class beginInstruction/endInstruction — they dereference MMI to
+ // create temp symbols for label tracking and would crash.
// Future local NSDI that needs MCContext must use
// Asm->OutStreamer->getContext() rather than MMI->getContext().
- void beginInstruction(const MachineInstr *MI) override {}
- void endInstruction() override {}
+ void beginInstruction(const MachineInstr *MI) override;
+ void endInstruction() override;
// Override beginFunctionImpl(), not beginFunction():
// DebugHandlerBase::beginFunction() populates LScopes and DbgValues needed
>From fb8a7d5697e41720a41b5f11c86a73a89a911753 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 10:53:42 -0500
Subject: [PATCH 09/16] Fix path check.
---
llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
index 5cacfd5c14281..96007db44ea3c 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
@@ -6,7 +6,7 @@
; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
-; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "/src/debug-function-definition.c"
+; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}src{{[/\\]}}debug-function-definition.c"
; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit {{.*}}
>From b94cdf1046c36557a13e4c00009d87afcfb231a1 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 10:59:54 -0500
Subject: [PATCH 10/16] Remove CachedExtInstSetReg.
---
.../lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 11 ++---------
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h | 5 +++--
2 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index b6f0bd98bf377..9fcb75d6ecc0d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -284,7 +284,6 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
GlobalDIEmitted = false;
GlobalNSDIEnabled = false;
CurrentMAI = nullptr;
- CachedExtInstSetReg = MCRegister();
#ifndef NDEBUG
NonSemanticOpStringsSectionEmitted = false;
#endif
@@ -371,8 +370,6 @@ void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
// Add the NonSemantic.Shader.DebugInfo.100 entry to ExtInstSetMap so that
// outputOpExtInstImports() emits the OpExtInstImport instruction. Allocate a
// fresh result ID for it now; the same ID is used in emitExtInst() operands.
- constexpr unsigned NSSet = static_cast<unsigned>(
- SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100);
if (!MAI.ExtInstSetMap.count(NSSet))
MAI.ExtInstSetMap[NSSet] = MAI.getNextIDRegister();
}
@@ -1027,8 +1024,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
// Check that prepareModuleOutput() registered the extended instruction set.
// If the subtarget does not support the extension, neither strings nor ext
// insts are emitted.
- constexpr unsigned NSSet = static_cast<unsigned>(
- SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100);
if (!MAI.getExtInstSetReg(NSSet).isValid())
return;
@@ -1098,8 +1093,9 @@ void SPIRVNonSemanticDebugHandler::emitDebugFunctionDefinition(
assert(DebugFunctionReg.isValid() && OpFunctionReg.isValid() &&
"DebugFunctionDefinition operands must be valid");
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
+ MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
emitExtInst(SPIRV::NonSemanticExtInst::DebugFunctionDefinition, VoidTypeReg,
- CachedExtInstSetReg, {DebugFunctionReg, OpFunctionReg}, MAI);
+ ExtInstSetReg, {DebugFunctionReg, OpFunctionReg}, MAI);
}
void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
@@ -1215,8 +1211,6 @@ bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
}
// Retrieve the ext inst set register allocated by prepareModuleOutput().
- constexpr unsigned NSSet = static_cast<unsigned>(
- SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100);
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
if (!ExtInstSetReg.isValid()) {
GlobalNSDIEnabled = false;
@@ -1230,7 +1224,6 @@ bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
#endif
CurrentMAI = &MAI;
- CachedExtInstSetReg = ExtInstSetReg;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index bd4752e85f06c..e8b5bab3b2d15 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -51,6 +51,9 @@ class SPIRVSubtarget;
/// the synthesized entry OpLabel when there are no OpVariables.
/// - endFunctionImpl() resets per-function state.
class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
+ static constexpr unsigned NSSet = static_cast<unsigned>(
+ SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100);
+
struct CompileUnitInfo {
const DICompileUnit *TheCU = nullptr;
SmallString<128> FilePath;
@@ -158,8 +161,6 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
SPIRV::ModuleAnalysisInfo *CurrentMAI = nullptr;
- MCRegister CachedExtInstSetReg;
-
const MachineFunction *CurrentMF = nullptr;
const MachineInstr *LastFunctionOpVariable = nullptr;
>From becba347375056f537e4a4b5a15019316a3e8b4d Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 11:05:46 -0500
Subject: [PATCH 11/16] Change signature.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 9 ++++-----
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h | 4 ++--
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 9fcb75d6ecc0d..d8bffd54500a9 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1198,23 +1198,23 @@ void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
tryEmitDebugFunctionDefinition(*CurrentMAI);
}
-bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
+void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
SPIRV::ModuleAnalysisInfo &MAI) {
if (GlobalDIEmitted)
- return GlobalNSDIEnabled;
+ return;
GlobalDIEmitted = true;
if (CompileUnits.empty()) {
GlobalNSDIEnabled = false;
- return false;
+ return;
}
// Retrieve the ext inst set register allocated by prepareModuleOutput().
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
if (!ExtInstSetReg.isValid()) {
GlobalNSDIEnabled = false;
- return false;
+ return;
}
#ifndef NDEBUG
@@ -1390,7 +1390,6 @@ bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
MAI);
GlobalNSDIEnabled = true;
- return true;
}
SmallString<128>
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index e8b5bab3b2d15..b89381a2e07a6 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -196,8 +196,8 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
/// SPIRVAsmPrinter::outputModuleSections() at section 10 in place of
/// outputModuleSection(MB_NonSemanticGlobalDI). Requires
/// emitNonSemanticDebugStrings() to have run first when NSDI strings apply.
- /// \returns true when module-scope NSDI emission ran; false when skipped.
- bool emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
+ /// Sets \c GlobalNSDIEnabled when module-scope NSDI emission completes.
+ void emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
/// Called after the synthesized entry \c OpLabel has been emitted.
void notifyEntryLabelEmitted(const MachineFunction &MF,
>From f2541dea6002c4fd1c13bc26d87b46412888f818 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 11:17:02 -0500
Subject: [PATCH 12/16] Improve test.
---
.../debug-info/debug-function-definition.ll | 23 +++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
index 96007db44ea3c..57c246edff8f3 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
@@ -1,20 +1,26 @@
; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-; Exercise NonSemantic DebugFunctionDefinition for a defined function.
+; Exercise NonSemantic DebugFunctionDefinition for defined functions, including
+; per-function emitter state across multiple definitions in one module.
; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}src{{[/\\]}}debug-function-definition.c"
-; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
+; CHECK-DAG: [[NAME1:%[0-9]+]] = OpString "add_one"
+; CHECK-DAG: [[NAME2:%[0-9]+]] = OpString "add_two"
; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit {{.*}}
; CHECK-DAG: [[TF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugTypeFunction {{.*}}
-; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]] [[TF]] [[DS]] {{.*}}
+; CHECK-DAG: [[DF1:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME1]] [[TF]] [[DS]] {{.*}}
+; CHECK-DAG: [[DF2:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME2]] [[TF]] [[DS]] {{.*}}
; CHECK: [[ADD_ONE:%[0-9]+]] = OpFunction
; CHECK: OpLabel
-; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[ADD_ONE]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF1]] [[ADD_ONE]]
+; CHECK: [[ADD_TWO:%[0-9]+]] = OpFunction
+; CHECK: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF2]] [[ADD_TWO]]
target triple = "spirv64-unknown-unknown"
@@ -24,6 +30,12 @@ entry:
ret i32 %result, !dbg !8
}
+define spir_func i32 @add_two(i32 %value) !dbg !9 {
+entry:
+ %result = add i32 %value, 2
+ ret i32 %result, !dbg !10
+}
+
!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3}
@@ -38,3 +50,6 @@ entry:
!5 = distinct !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
!8 = !DILocation(line: 3, column: 3, scope: !5)
+
+!9 = distinct !DISubprogram(name: "add_two", linkageName: "add_two", scope: !1, file: !1, line: 5, type: !4, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DILocation(line: 7, column: 3, scope: !9)
>From f07070a0909e6ba00dcdf519b442c7ccf33e69eb Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 11:45:49 -0500
Subject: [PATCH 13/16] Fix rebase.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ------
1 file changed, 6 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index d8bffd54500a9..120f03c279247 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1068,12 +1068,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
}
- for (const DISubprogram *SP : SubprogramDefinitions) {
- emitOpStringIfNew(SP->getName(), MAI);
- emitOpStringIfNew(SP->getLinkageName(), MAI);
- ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
- }
-
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
>From 0dad4a04c6378e42a4b1641eaf20d571dd6462cc Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Thu, 30 Jul 2026 09:22:35 -0500
Subject: [PATCH 14/16] Remove unused parameter.
---
llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp | 2 +-
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 2 +-
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h | 3 +--
3 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 7376e97b80901..040a6858b9009 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -339,7 +339,7 @@ void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) {
"OpFunction is not in the front MBB of MF");
emitOpLabel(*MI->getParent());
if (NSDebugHandler && !isHidden())
- NSDebugHandler->notifyEntryLabelEmitted(*MF, *MAI);
+ NSDebugHandler->notifyEntryLabelEmitted(*MF);
}
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 120f03c279247..96d874eb017bb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1176,7 +1176,7 @@ void SPIRVNonSemanticDebugHandler::endInstruction() {
}
void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
- const MachineFunction &MF, SPIRV::ModuleAnalysisInfo &MAI) {
+ const MachineFunction &MF) {
if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted || !CurrentMF)
return;
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index b89381a2e07a6..4fce1b73004e1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -200,8 +200,7 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
void emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
/// Called after the synthesized entry \c OpLabel has been emitted.
- void notifyEntryLabelEmitted(const MachineFunction &MF,
- SPIRV::ModuleAnalysisInfo &MAI);
+ void notifyEntryLabelEmitted(const MachineFunction &MF);
protected:
// All module-level output is driven by emitNonSemanticGlobalDebugInfo(),
>From ee20b2c4f564181423b71a2ea88fb6330c16a345 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Mon, 3 Aug 2026 05:55:37 -0500
Subject: [PATCH 15/16] Improve tests.
---
...-function-definition-call-before-alloca.ll | 88 +++++++++++++
.../debug-function-definition-calls.ll | 124 ++++++++++++++++++
...debug-function-definition-external-call.ll | 56 ++++++++
3 files changed, 268 insertions(+)
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll
new file mode 100644
index 0000000000000..19d9c1b09db2d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll
@@ -0,0 +1,88 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; External call appears before the alloca in LLVM IR source order. The local
+; OpVariable is still inserted in the function preamble; DebugFunctionDefinition
+; must follow it, not the entry OpLabel.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "caller"
+; CHECK-DAG: [[NAME_NOARGS:%[0-9]+]] = OpString "caller_no_args"
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]]
+; CHECK-DAG: [[DF_NOARGS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_NOARGS]]
+; CHECK-DAG: OpName [[EXT_HELPER:%[0-9]+]] "external_helper"
+; CHECK-DAG: OpDecorate [[EXT_HELPER]] LinkageAttributes "external_helper" Import
+; CHECK-DAG: OpDecorate [[CALLER:%[0-9]+]] LinkageAttributes "caller" Export
+; CHECK-DAG: OpName [[EXT_HELPER_NOARGS:%[0-9]+]] "external_helper_no_args"
+; CHECK-DAG: OpDecorate [[EXT_HELPER_NOARGS]] LinkageAttributes "external_helper_no_args" Import
+; CHECK-DAG: OpDecorate [[CALLER_NOARGS:%[0-9]+]] LinkageAttributes "caller_no_args" Export
+
+; external_helper: hoisted declaration in the declarations section.
+; CHECK: [[EXT_HELPER]] = OpFunction %{{.*}}
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpFunctionEnd
+
+; CHECK: [[EXT_HELPER_NOARGS]] = OpFunction %{{.*}}
+; CHECK-NEXT: OpFunctionEnd
+
+; CHECK: [[CALLER]] = OpFunction %{{.*}} ; -- Begin function caller
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpVariable {{.*}} Function
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[CALLER]]
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+; caller_no_args: same placement rules, but no function parameters.
+; CHECK: [[CALLER_NOARGS]] = OpFunction %{{.*}} ; -- Begin function caller_no_args
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpVariable {{.*}} Function
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_NOARGS]] [[CALLER_NOARGS]]
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+declare spir_func i32 @external_helper(i32)
+declare spir_func i32 @external_helper_no_args()
+
+define spir_func i32 @caller(i32 %x) !dbg !5 {
+entry:
+ %r = call i32 @external_helper(i32 %x)
+ %a = alloca i32, align 4
+ store i32 %r, ptr %a
+ ret i32 %r, !dbg !8
+}
+
+define spir_func void @caller_no_args() !dbg !9 {
+entry:
+ %r = call i32 @external_helper_no_args()
+ %a = alloca i32, align 4
+ store i32 %r, ptr %a
+ ret void, !dbg !13
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-function-definition-call-before-alloca.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!4 = !DISubroutineType(types: !6)
+!6 = !{!7, !7}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "caller", linkageName: "caller", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 6, column: 3, scope: !5)
+
+!9 = distinct !DISubprogram(name: "caller_no_args", linkageName: "caller_no_args", scope: !1, file: !1, line: 10, type: !10, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DISubroutineType(types: !11)
+!11 = !{null}
+!13 = !DILocation(line: 15, column: 3, scope: !9)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
new file mode 100644
index 0000000000000..19189b8d5bca7
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
@@ -0,0 +1,124 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Exercise DebugFunctionDefinition placement across multiple defined functions
+; that call each other, with and without function-local OpVariable instructions.
+; Each function's definition must reference its own OpFunction id, placed after
+; the entry OpLabel (no locals) or after the last function-local OpVariable.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[NAME_LEAF:%[0-9]+]] = OpString "leaf_no_vars"
+; CHECK-DAG: [[NAME_WITH:%[0-9]+]] = OpString "helper_with_vars"
+; CHECK-DAG: [[NAME_CALLER:%[0-9]+]] = OpString "caller_no_vars"
+; CHECK-DAG: [[NAME_ORCH:%[0-9]+]] = OpString "orchestrator"
+; CHECK-DAG: [[DF_LEAF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_LEAF]]
+; CHECK-DAG: [[DF_WITH:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_WITH]]
+; CHECK-DAG: [[DF_CALLER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_CALLER]]
+; CHECK-DAG: [[DF_ORCH:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_ORCH]]
+; CHECK-DAG: OpDecorate [[LEAF:%[0-9]+]] LinkageAttributes "leaf_no_vars" Export
+; CHECK-DAG: OpDecorate [[WITH:%[0-9]+]] LinkageAttributes "helper_with_vars" Export
+; CHECK-DAG: OpDecorate [[CALLER:%[0-9]+]] LinkageAttributes "caller_no_vars" Export
+; CHECK-DAG: OpDecorate [[ORCH:%[0-9]+]] LinkageAttributes "orchestrator" Export
+
+; leaf_no_vars: no local variables -> DebugFunctionDefinition after OpLabel.
+; CHECK: [[LEAF]] = OpFunction %{{.*}} ; -- Begin function leaf_no_vars
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_LEAF]] [[LEAF]]
+; CHECK-NEXT: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+; helper_with_vars: two local variables -> DebugFunctionDefinition after them.
+; CHECK: [[WITH]] = OpFunction %{{.*}} ; -- Begin function helper_with_vars
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpVariable {{.*}} Function
+; CHECK-NEXT: OpVariable {{.*}} Function
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_WITH]] [[WITH]]
+; CHECK: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+; caller_no_vars: calls both helpers, no locals -> after OpLabel.
+; CHECK: [[CALLER]] = OpFunction %{{.*}} ; -- Begin function caller_no_vars
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_CALLER]] [[CALLER]]
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+; orchestrator: no args, no locals, only calls -> after OpLabel.
+; CHECK: [[ORCH]] = OpFunction %{{.*}} ; -- Begin function orchestrator
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_ORCH]] [[ORCH]]
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @leaf_no_vars(i32 %value) !dbg !5 {
+entry:
+ ret i32 %value, !dbg !8
+}
+
+define spir_func i32 @helper_with_vars(i32 %value) !dbg !9 {
+entry:
+ %x = alloca i32, align 4
+ %y = alloca i32, align 4
+ store i32 %value, ptr %x
+ store i32 0, ptr %y
+ %tmp = call spir_func i32 @leaf_no_vars(i32 %value), !dbg !12
+ %sum = add i32 %tmp, 1
+ ret i32 %sum, !dbg !13
+}
+
+define spir_func i32 @caller_no_vars(i32 %value) !dbg !14 {
+entry:
+ %a = call spir_func i32 @helper_with_vars(i32 %value), !dbg !17
+ %b = call spir_func i32 @leaf_no_vars(i32 %a), !dbg !18
+ ret i32 %b, !dbg !19
+}
+
+define spir_func void @orchestrator() !dbg !20 {
+entry:
+ %unused1 = call spir_func i32 @leaf_no_vars(i32 0), !dbg !23
+ %unused2 = call spir_func i32 @helper_with_vars(i32 1), !dbg !24
+ ret void, !dbg !25
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-function-definition-calls.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!4 = !DISubroutineType(types: !6)
+!6 = !{!7, !7}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "leaf_no_vars", linkageName: "leaf_no_vars", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 3, scope: !5)
+
+!9 = distinct !DISubprogram(name: "helper_with_vars", linkageName: "helper_with_vars", scope: !1, file: !1, line: 5, type: !4, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DILocalVariable(name: "x", scope: !9, file: !1, line: 6, type: !7)
+!11 = !DILocalVariable(name: "y", scope: !9, file: !1, line: 7, type: !7)
+!12 = !DILocation(line: 10, column: 10, scope: !9)
+!13 = !DILocation(line: 12, column: 3, scope: !9)
+
+!14 = distinct !DISubprogram(name: "caller_no_vars", linkageName: "caller_no_vars", scope: !1, file: !1, line: 14, type: !4, scopeLine: 14, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!17 = !DILocation(line: 16, column: 8, scope: !14)
+!18 = !DILocation(line: 17, column: 8, scope: !14)
+!19 = !DILocation(line: 18, column: 3, scope: !14)
+
+!20 = distinct !DISubprogram(name: "orchestrator", linkageName: "orchestrator", scope: !1, file: !1, line: 20, type: !21, scopeLine: 20, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!21 = !DISubroutineType(types: !22)
+!22 = !{null}
+!23 = !DILocation(line: 22, column: 13, scope: !20)
+!24 = !DILocation(line: 23, column: 13, scope: !20)
+!25 = !DILocation(line: 24, column: 3, scope: !20)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll
new file mode 100644
index 0000000000000..da5deeb43ac1f
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll
@@ -0,0 +1,56 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Forward-declared external callee plus function-local variables: the hoisted
+; OpFunction declaration must not cause DebugFunctionDefinition to be emitted
+; before the last function-local OpVariable.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "caller_with_vars"
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]]
+; CHECK-DAG: OpName [[EXT_HELPER:%[0-9]+]] "external_helper"
+; CHECK-DAG: OpDecorate [[EXT_HELPER]] LinkageAttributes "external_helper" Import
+; CHECK-DAG: OpDecorate [[CALLER:%[0-9]+]] LinkageAttributes "caller_with_vars" Export
+
+; external_helper: hoisted declaration in the declarations section.
+; CHECK: [[EXT_HELPER]] = OpFunction %{{.*}}
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpFunctionEnd
+
+; CHECK: [[CALLER]] = OpFunction %{{.*}} ; -- Begin function caller_with_vars
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpVariable {{.*}} Function
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[CALLER]]
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+declare spir_func i32 @external_helper(i32)
+
+define spir_func i32 @caller_with_vars(i32 %x) !dbg !5 {
+entry:
+ %a = alloca i32, align 4
+ store i32 %x, ptr %a
+ %r = call i32 @external_helper(i32 %x)
+ ret i32 %r, !dbg !8
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-function-definition-external-call.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!4 = !DISubroutineType(types: !6)
+!6 = !{!7, !7}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "caller_with_vars", linkageName: "caller_with_vars", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 5, column: 3, scope: !5)
>From cdad472b1c2d64a4a9eb1a230149bbeeb9665f1e Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Mon, 3 Aug 2026 05:56:00 -0500
Subject: [PATCH 16/16] Rename function.
---
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 96d874eb017bb..f921df3912d38 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -196,8 +196,8 @@ static uint32_t mapCompositeTypeTag(unsigned Tag) {
}
static const MachineInstr *
-findLastEmittedFunctionOpVariable(const MachineFunction &MF,
- SPIRV::ModuleAnalysisInfo &MAI) {
+findLastFunctionOpVariableDeclaration(const MachineFunction &MF,
+ SPIRV::ModuleAnalysisInfo &MAI) {
// We iterate over the instructions to find the last OpVariable instruction if
// any. The following SPIRV rule is used to terminate the traversal earlier:
@@ -1117,7 +1117,8 @@ void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
// DebugFunctionDefinition is emitted after the last function-level
// OpVariable. If there are none, it is emitted after the entry OpLabel.
- LastFunctionOpVariable = findLastEmittedFunctionOpVariable(*MF, *CurrentMAI);
+ LastFunctionOpVariable =
+ findLastFunctionOpVariableDeclaration(*MF, *CurrentMAI);
}
void SPIRVNonSemanticDebugHandler::tryEmitDebugFunctionDefinition(
More information about the llvm-commits
mailing list