[llvm-branch-commits] [llvm] [SPIRV] Forward-reference debug scopes with a reserved result id. (PR #229490)
Manuel Carrasco via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Oct 7 03:17:01 PDT 2026
https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/229490
>From 667618032fb450164262547dd4a6f442cca95449 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 6 Oct 2026 11:59:19 -0500
Subject: [PATCH] [SPIRV] Forward-reference debug scopes with a reserved result
id.
Visiting a scope reserves its result id. With SPV_KHR_relaxed_extended_instruction a back edge uses that id through OpExtInstWithForwardRefsKHR, and a later failure defines it as DebugInfoNone when an emitted instruction already named it.
Without the extension the edge is dropped and a later walk can retry the scope.
---
.../SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp | 3 +-
llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp | 4 +-
llvm/lib/Target/SPIRV/SPIRVInstrInfo.td | 5 +
.../SPIRV/SPIRVNonSemanticDebugHandler.cpp | 106 +++++++++++++++---
.../SPIRV/SPIRVNonSemanticDebugHandler.h | 32 +++++-
.../lib/Target/SPIRV/SPIRVSymbolicOperands.td | 2 +
.../debug-composite-parent-debuginfonone.ll | 55 +++++++++
.../debug-type-composite-self-pointer.ll | 55 +++++++++
.../debug-typedef-base-debuginfonone.ll | 52 +++++++++
...g-typedef-cycle-unsupported-forward-ref.ll | 42 +++++++
10 files changed, 335 insertions(+), 21 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-composite-parent-debuginfonone.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-type-composite-self-pointer.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-base-debuginfonone.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-cycle-unsupported-forward-ref.ll
diff --git a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp
index 8a2618b5309a96..3d78ac036caa64 100644
--- a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp
@@ -176,7 +176,8 @@ void SPIRVInstPrinter::printInst(const MCInst *MI, uint64_t Address,
printOpDecorate(MI, OS);
} else if (OpCode == SPIRV::OpExtInstImport) {
recordOpExtInstImport(MI);
- } else if (OpCode == SPIRV::OpExtInst) {
+ } else if (OpCode == SPIRV::OpExtInst ||
+ OpCode == SPIRV::OpExtInstWithForwardRefsKHR) {
printOpExtInst(MI, OS);
} else if (OpCode == SPIRV::UNKNOWN_type) {
printUnknownType(MI, OS);
diff --git a/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp b/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp
index 956396355140c2..0a61506de6e956 100644
--- a/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp
@@ -178,7 +178,9 @@ static const StringMap<SPIRV::Extension::Extension> SPIRVExtensionMap = {
SPIRV::Extension::Extension::SPV_KHR_poison_freeze},
{"SPV_KHR_untyped_pointers",
SPIRV::Extension::Extension::SPV_KHR_untyped_pointers},
- {"SPV_EXT_long_vector", SPIRV::Extension::Extension::SPV_EXT_long_vector}};
+ {"SPV_EXT_long_vector", SPIRV::Extension::Extension::SPV_EXT_long_vector},
+ {"SPV_KHR_relaxed_extended_instruction",
+ SPIRV::Extension::Extension::SPV_KHR_relaxed_extended_instruction}};
bool SPIRVExtensionsParser::parse(cl::Option &O, StringRef ArgName,
StringRef ArgValue, ExtensionSet &Vals) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td b/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
index cd4faf80c2d97c..aa4c0ed1cea28b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
+++ b/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
@@ -152,6 +152,11 @@ def OpExtInstImport: Op<11, (outs ID:$res), (ins StringImm:$extInstsName, variab
// perspective of the instruction selection pass
def OpExtInst: Op<12, (outs ID:$res), (ins TYPE:$ty, i32imm:$set, Extension:$inst, variable_ops),
"$res = OpExtInst $ty $set $inst">;
+// Same layout as OpExtInst; allows operands that are defined later in the
+// module (SPV_KHR_relaxed_extended_instruction).
+def OpExtInstWithForwardRefsKHR: Op<4433, (outs ID:$res),
+ (ins TYPE:$ty, i32imm:$set, Extension:$inst, variable_ops),
+ "$res = OpExtInstWithForwardRefsKHR $ty $set $inst">;
// 3.42.5 Mode-Setting Instructions
def OpMemoryModel: Op<14, (outs), (ins AddressingModel:$addr, MemoryModel:$mem),
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index b741113c6540ca..cf5432b42ecf59 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -340,6 +340,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
LexicalBlocks.clear();
DebugScopeRegs.clear();
ScopesInProgress.clear();
+ HasRelaxedExtInst = false;
FailedScopes.clear();
DebugInlinedAtRegs.clear();
ScopeToPathOpStringReg.clear();
@@ -453,6 +454,12 @@ void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
// Add the extension to requirements so OpExtension is output.
MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info);
+ HasRelaxedExtInst = ST.canUseExtension(
+ SPIRV::Extension::SPV_KHR_relaxed_extended_instruction);
+ if (HasRelaxedExtInst)
+ MAI.Reqs.addExtension(
+ SPIRV::Extension::SPV_KHR_relaxed_extended_instruction);
+
// 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.
@@ -537,14 +544,39 @@ MCRegister SPIRVNonSemanticDebugHandler::emitOpConstantI32(
/*Width=*/32, MAI);
}
+bool SPIRVNonSemanticDebugHandler::markForwardReferencedOperands(
+ ArrayRef<MCRegister> Operands) {
+ bool HasForwardRef = false;
+ for (auto &Entry : ScopesInProgress) {
+ InProgressScope &InProgress = Entry.second;
+ assert(InProgress.Id.isValid() &&
+ "in-progress scope must have a reserved id");
+ if (!is_contained(Operands, InProgress.Id))
+ continue;
+ InProgress.ForwardReferenced = true;
+ HasForwardRef = true;
+ }
+ return HasForwardRef;
+}
+
MCRegister SPIRVNonSemanticDebugHandler::emitExtInst(
SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
MCRegister VoidTypeReg, MCRegister ExtInstSetReg,
- ArrayRef<MCRegister> Operands, SPIRV::ModuleAnalysisInfo &MAI) {
- MCRegister Reg = MAI.getNextIDRegister();
+ ArrayRef<MCRegister> Operands, SPIRV::ModuleAnalysisInfo &MAI,
+ std::optional<MCRegister> ResultReg) {
+ assert((!ResultReg || ResultReg->isValid()) &&
+ "ResultReg must be a valid id when provided");
+ MCRegister Reg = ResultReg ? *ResultReg : MAI.getNextIDRegister();
+
+ // An operand that is still an in-progress scope id is a forward reference.
+ // Tag the instruction with OpExtInstWithForwardRefsKHR and remember the id:
+ // if that scope later fails, the id still needs a definition.
+ bool HasForwardRef =
+ HasRelaxedExtInst && markForwardReferencedOperands(Operands);
MCInst Inst;
- Inst.setOpcode(SPIRV::OpExtInst);
+ Inst.setOpcode(HasForwardRef ? SPIRV::OpExtInstWithForwardRefsKHR
+ : SPIRV::OpExtInst);
Inst.addOperand(MCOperand::createReg(Reg));
Inst.addOperand(MCOperand::createReg(VoidTypeReg));
Inst.addOperand(MCOperand::createReg(ExtInstSetReg));
@@ -760,8 +792,12 @@ MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeInt32(
}
// Each node emits its dependencies first. E.g. for `void f() { struct L; }`,
-// node L emits f, then L. For `struct S { S *p; }`, S* is a back edge, so S
-// is emitted without member p.
+// node L emits f, then L. Entering a scope reserves its result id. For
+// `struct S { S *p; }`, S* is a back edge, and without
+// SPV_KHR_relaxed_extended_instruction the edge is dropped and the reserved id
+// stays unused. With it, that id is used with OpExtInstWithForwardRefsKHR.
+// A later failure defines it as DebugInfoNone only when an emitted instruction
+// already named it.
auto SPIRVNonSemanticDebugHandler::getOrCreateDebugScope(const DIScope *S)
-> EmitResult<MCRegister> {
if (!S || FailedScopes.contains(S))
@@ -769,16 +805,55 @@ auto SPIRVNonSemanticDebugHandler::getOrCreateDebugScope(const DIScope *S)
if (auto Reg = lookupOptReg(DebugScopeRegs, S))
return EmitResult<MCRegister>::emitted(*Reg);
- // Mark the node as in progress while its dependencies are resolved/emitted.
- // This also prevents cycles from happening.
- if (!ScopesInProgress.insert(S).second)
- return EmitResult<MCRegister>::inProgress();
+ // Mark the scope node as in progress while its dependencies are
+ // resolved/emitted.
+ auto [InProgressIt, FirstVisit] = ScopesInProgress.try_emplace(S);
+ if (!FirstVisit) { // We hit a back edge, it is not the first insertion.
+ if (!HasRelaxedExtInst)
+ return EmitResult<MCRegister>::inProgress(); // No support for forward
+ // refs.
+ // Use the reserved id for the back edge when forward references are
+ // supported.
+ MCRegister ReservedId = InProgressIt->second.Id;
+ assert(ReservedId.isValid() && "in-progress scope must have a reserved id");
+ return EmitResult<MCRegister>::emitted(ReservedId);
+ }
+
+ // Reserve the result id first: a dependency may refer back to this scope.
+ assert(CurrentMAI && "debug scope id allocation requires CurrentMAI");
+ MCRegister Id = CurrentMAI->getNextIDRegister();
+ assert(Id.isValid() && "debug scope id must be a valid register");
+ InProgressIt->second.Id = Id;
EmitResult<PreparedScope> P = emitDebugScope(S);
- ScopesInProgress.erase(S);
- assert((!P || !GlobalNSDIEnabled) &&
- "debug type or scope created after module-scope NSDI emission");
+ assert((!HasRelaxedExtInst || P.Status == EmitStatus::Emitted ||
+ P.Status == EmitStatus::Unsupported) &&
+ "InProgress is only returned when forward references are unsupported");
if (!P) {
+ // Emission during recursion update the container. Look the scope up again.
+ InProgressIt = ScopesInProgress.find(S);
+ assert(InProgressIt != ScopesInProgress.end() &&
+ "scope must stay in progress until its instruction is emitted");
+ bool ForwardReferenced = InProgressIt->second.ForwardReferenced;
+ ScopesInProgress.erase(InProgressIt);
+ // P here is either unsupported or in progress. When forward references are
+ // supported, P can only be unsupported. Define the id only when an
+ // already-emitted instruction named it.
+ if (ForwardReferenced) {
+ assert(HasRelaxedExtInst &&
+ "a forward reference requires relaxed extended instructions");
+ assert(P.Status == EmitStatus::Unsupported &&
+ "a forward-referenced scope fails as unsupported");
+ SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
+ emitExtInst(SPIRV::NonSemanticExtInst::DebugInfoNone,
+ getOrEmitOpTypeVoidReg(MAI), MAI.getExtInstSetReg(NSSet), {},
+ MAI, Id);
+ }
+
+ // InProgress is a dropped back edge (when forward references are not
+ // supported). Leave it out of FailedScopes so a later walk can retry the
+ // scope. Regardless of forward references, cache unsupported scopes to
+ // avoid retrying them.
if (P.Status == EmitStatus::Unsupported)
FailedScopes.insert(S);
return P.Status == EmitStatus::InProgress
@@ -790,7 +865,12 @@ auto SPIRVNonSemanticDebugHandler::getOrCreateDebugScope(const DIScope *S)
SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
MCRegister Reg =
emitExtInst(P.Value->Opcode, getOrEmitOpTypeVoidReg(MAI),
- MAI.getExtInstSetReg(NSSet), P.Value->Operands, MAI);
+ MAI.getExtInstSetReg(NSSet), P.Value->Operands, MAI, Id);
+ assert(Reg == Id &&
+ "debug scope result id must reuse the id reserved at entry");
+ ScopesInProgress.erase(S);
+ assert(!GlobalNSDIEnabled &&
+ "debug type or scope created after module-scope NSDI emission");
DebugScopeRegs[S] = Reg;
return EmitResult<MCRegister>::emitted(Reg);
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 8e17d8cf0d39ba..5a630a5a6dd8a1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -133,8 +133,19 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
}
};
+ // Used to track which scopes are in progress, and which operands are used by
+ // a forward references. Operands must be later defined even if the scope
+ // fails to emit.
+ struct InProgressScope {
+ MCRegister Id;
+ bool ForwardReferenced = false;
+ };
+
// Types and scopes whose instruction is currently being built.
- SmallPtrSet<const DIScope *, 8> ScopesInProgress;
+ SmallDenseMap<const DIScope *, InProgressScope, 8> ScopesInProgress;
+
+ // True when prepareModuleOutput saw SPV_KHR_relaxed_extended_instruction.
+ bool HasRelaxedExtInst = false;
// Types and scopes that are not supported.
SmallPtrSet<const DIScope *, 8> FailedScopes;
@@ -386,10 +397,16 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
bool UseEmptyPathIfNullScope = false);
MCRegister emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
SPIRV::ModuleAnalysisInfo &MAI);
+
+ /// Mark in-progress scopes named by \p Operands. Return true when any
+ /// operand is such an id. A marked id needs a definition if that scope fails.
+ bool markForwardReferencedOperands(ArrayRef<MCRegister> Operands);
+
MCRegister emitExtInst(SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
MCRegister VoidTypeReg, MCRegister ExtInstSetReg,
ArrayRef<MCRegister> Operands,
- SPIRV::ModuleAnalysisInfo &MAI);
+ SPIRV::ModuleAnalysisInfo &MAI,
+ std::optional<MCRegister> ResultReg = std::nullopt);
/// Return OpTypeVoid id for this module (lazy lookup / emit, then cache).
MCRegister getOrEmitOpTypeVoidReg(SPIRV::ModuleAnalysisInfo &MAI);
@@ -407,10 +424,13 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
/// Return the id for the type or scope \p S, emitting it and the types and
/// scopes it names first. One already in \c DebugScopeRegs is Emitted. A null
- /// \p S or one in \c FailedScopes is Unsupported. One already in
- /// \c ScopesInProgress is the back edge of a cycle and is InProgress; nothing
- /// is emitted for that edge. An Unsupported result is recorded in
- /// \c FailedScopes, an InProgress one is not.
+ /// \p S or one in \c FailedScopes is Unsupported. Entering a scope reserves
+ /// its result id. A back edge (\p S already in \c ScopesInProgress) returns
+ /// that id when \c HasRelaxedExtInst is set, and InProgress otherwise. If
+ /// emission fails after an emitted instruction used that id, it is defined as
+ /// DebugInfoNone. An id that nothing named stays undefined. An Unsupported
+ /// result is recorded in \c FailedScopes; an InProgress one is not, so a
+ /// later walk can retry it.
EmitResult<MCRegister> getOrCreateDebugScope(const DIScope *S);
/// Build the instruction for \p S. Only \c getOrCreateDebugScope calls this
diff --git a/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td b/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
index 561d0fdef40264..a0604b451cd122 100644
--- a/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
+++ b/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
@@ -406,6 +406,8 @@ defm SPV_KHR_abort : ExtensionOperand<138, [EnvOpenCL]>;
defm SPV_KHR_poison_freeze : ExtensionOperand<139, [EnvVulkan, EnvOpenCL]>;
defm SPV_KHR_untyped_pointers : ExtensionOperand<140, [EnvOpenCL]>;
defm SPV_EXT_long_vector : ExtensionOperand<141, [EnvOpenCL, EnvVulkan]>;
+defm SPV_KHR_relaxed_extended_instruction
+ : ExtensionOperand<142, [EnvVulkan, EnvOpenCL]>;
//===----------------------------------------------------------------------===//
// Multiclass used to define Capabilities enum values and at the same time
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-composite-parent-debuginfonone.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-composite-parent-debuginfonone.ll
new file mode 100644
index 00000000000000..b088c6be238e80
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-composite-parent-debuginfonone.ll
@@ -0,0 +1,55 @@
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; 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 %}
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=FWD
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | not spirv-val 2>&1 | FileCheck %s --check-prefix=VAL %}
+
+; struct S { int x; } is declared in namespace ns, and ns is declared in f.
+; f is scoped in an unsupported enumeration.
+;
+; Namespaces are emitted before types, so ns is entered first and enters f
+; while ns is still open. f's parameter type is S*, and S's Parent is ns, so
+; S is emitted with ns's reserved id. f then fails, ns fails, and that id is
+; defined as DebugInfoNone. Without the extension the back edge is dropped
+; and S is not emitted.
+;
+; NonSemantic.Shader.DebugInfo says DebugTypeComposite's Parent must be a
+; DebugCompilationUnit, DebugFunction, DebugLexicalBlock, or
+; DebugTypeComposite. DebugInfoNone is not one of those. spirv-val rejects
+; it, and that matches the spec.
+
+; CHECK-SPIRV-NOT: DebugTypeComposite
+; CHECK-SPIRV-NOT: OpExtInstWithForwardRefsKHR
+
+; FWD: OpExtension "SPV_KHR_relaxed_extended_instruction"
+; FWD: [[ext:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; FWD-DAG: [[void:%[0-9]+]] = OpTypeVoid
+; FWD-DAG: [[name_s:%[0-9]+]] = OpString "S"
+; FWD: [[s:%[0-9]+]] = OpExtInstWithForwardRefsKHR [[void]] [[ext]] DebugTypeComposite [[name_s]] {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}} [[parent:%[0-9]+]]
+; FWD: [[parent]] = OpExtInst [[void]] [[ext]] DebugInfoNone
+
+; VAL: DebugTypeComposite: expected operand Parent must be a result id of a lexical scope
+
+define spir_func void @f(ptr addrspace(4) %p) !dbg !6 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!1, !2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !{i32 7, !"Dwarf Version", i32 5}
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIFile(filename: "ns-parent.cpp", directory: "/src")
+
+!6 = distinct !DISubprogram(name: "f", scope: !7, file: !3, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !DICompositeType(tag: DW_TAG_enumeration_type, name: "E", file: !3, line: 1, size: 32, elements: !12)
+!8 = !DISubroutineType(types: !11)
+!11 = !{null, !14}
+!12 = !{}
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!13 = !DINamespace(name: "ns", scope: !6)
+!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "S", scope: !13, file: !3, line: 2, size: 32, elements: !15)
+!15 = !{!16}
+!16 = !DIDerivedType(tag: DW_TAG_member, name: "x", file: !3, line: 2, baseType: !9, size: 32)
+!14 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !5, size: 64, dwarfAddressSpace: 4)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-composite-self-pointer.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-composite-self-pointer.ll
new file mode 100644
index 00000000000000..90e7c3f86145f8
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-composite-self-pointer.ll
@@ -0,0 +1,55 @@
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; 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 %}
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=FWD
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; struct S { int x; struct S *next; };
+;
+; Without SPV_KHR_relaxed_extended_instruction the back edge is in progress
+; while S is open, so S lists only member x. Once S and the pointer exist, the
+; later walk emits member next. With the extension, the pointer is
+; OpExtInstWithForwardRefsKHR naming S, and both members are emitted.
+
+; CHECK-SPIRV: [[ext:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-SPIRV-DAG: [[void:%[0-9]+]] = OpTypeVoid
+; CHECK-SPIRV-DAG: [[str_S:%[0-9]+]] = OpString "S"
+; CHECK-SPIRV-DAG: [[str_x:%[0-9]+]] = OpString "x"
+; CHECK-SPIRV-DAG: [[str_next:%[0-9]+]] = OpString "next"
+; CHECK-SPIRV: [[mem_x:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugTypeMember [[str_x]]
+; CHECK-SPIRV-NOT: DebugTypeMember [[str_next]]
+; CHECK-SPIRV: [[comp:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugTypeComposite [[str_S]] {{.*}} [[mem_x]]{{$}}
+; CHECK-SPIRV: OpExtInst [[void]] [[ext]] DebugTypePointer [[comp]]
+; CHECK-SPIRV: OpExtInst [[void]] [[ext]] DebugTypeMember [[str_next]]
+
+; FWD: OpExtension "SPV_KHR_relaxed_extended_instruction"
+; FWD: [[ext:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; FWD-DAG: [[void:%[0-9]+]] = OpTypeVoid
+; FWD-DAG: [[str_S:%[0-9]+]] = OpString "S"
+; FWD-DAG: [[str_next:%[0-9]+]] = OpString "next"
+; FWD: [[ptr:%[0-9]+]] = OpExtInstWithForwardRefsKHR [[void]] [[ext]] DebugTypePointer [[comp:%[0-9]+]]
+; FWD: [[mem_next:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugTypeMember [[str_next]] [[ptr]]
+; FWD: [[comp]] = OpExtInst [[void]] [[ext]] DebugTypeComposite [[str_S]] {{.*}} [[mem_next]]
+
+define spir_func void @test() !dbg !6 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!1, !2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4)
+!1 = !{i32 7, !"Dwarf Version", i32 5}
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIFile(filename: "self-pointer.c", directory: "/src")
+
+!4 = !{!5}
+!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "S", file: !3, line: 1, size: 128, elements: !11)
+!6 = distinct !DISubprogram(name: "test", scope: !3, file: !3, line: 5, type: !8, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DISubroutineType(types: !10)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{null}
+!11 = !{!12, !13}
+!12 = !DIDerivedType(tag: DW_TAG_member, name: "x", file: !3, line: 2, baseType: !9, size: 32)
+!13 = !DIDerivedType(tag: DW_TAG_member, name: "next", file: !3, line: 3, baseType: !14, size: 64, offset: 64)
+!14 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !5, size: 64, dwarfAddressSpace: 4)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-base-debuginfonone.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-base-debuginfonone.ll
new file mode 100644
index 00000000000000..1883df3b35d1dc
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-base-debuginfonone.ll
@@ -0,0 +1,52 @@
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; 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 %}
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=FWD
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | not spirv-val 2>&1 | FileCheck %s --check-prefix=VAL %}
+
+; typedef U whose base is T, and T whose base is U. T is scoped in an
+; unsupported enumeration.
+;
+; With SPV_KHR_relaxed_extended_instruction, U is emitted while T is open, so
+; U's Base Type is T's reserved id. T then fails, and that id is defined as
+; DebugInfoNone. Without the extension the back edge is dropped and neither
+; typedef is emitted.
+;
+; NonSemantic.Shader.DebugInfo says DebugTypedef's Base Type is a debugging
+; instruction for the named type, and DebugInfoNone is what other instructions
+; refer to when the information is unknown. spirv-val still rejects this: it
+; requires DebugTypeBasic. That check does not match the spec.
+
+; CHECK-SPIRV-NOT: DebugTypedef
+; CHECK-SPIRV-NOT: OpExtInstWithForwardRefsKHR
+
+; FWD: OpExtension "SPV_KHR_relaxed_extended_instruction"
+; FWD: [[ext:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; FWD-DAG: [[void:%[0-9]+]] = OpTypeVoid
+; FWD-DAG: [[name_u:%[0-9]+]] = OpString "U"
+; FWD: [[u:%[0-9]+]] = OpExtInstWithForwardRefsKHR [[void]] [[ext]] DebugTypedef [[name_u]] [[base:%[0-9]+]]
+; FWD: [[base]] = OpExtInst [[void]] [[ext]] DebugInfoNone
+; FWD-NOT: DebugTypedef
+
+; VAL: DebugTypedef: expected operand Base Type must be a result id of DebugTypeBasic
+
+define spir_func void @test() !dbg !6 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!1, !2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4)
+!1 = !{i32 7, !"Dwarf Version", i32 5}
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIFile(filename: "typedef-none.c", directory: "/src")
+
+!4 = !{!5}
+!5 = !DIDerivedType(tag: DW_TAG_typedef, name: "T", file: !3, line: 2, baseType: !8, scope: !7)
+!6 = distinct !DISubprogram(name: "test", scope: !3, file: !3, line: 5, type: !10, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !DICompositeType(tag: DW_TAG_enumeration_type, name: "E", file: !3, line: 1, size: 32, elements: !12)
+!8 = !DIDerivedType(tag: DW_TAG_typedef, name: "U", file: !3, line: 3, baseType: !5, scope: !3)
+!10 = !DISubroutineType(types: !11)
+!11 = !{null}
+!12 = !{}
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-cycle-unsupported-forward-ref.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-cycle-unsupported-forward-ref.ll
new file mode 100644
index 00000000000000..f7381dc35eb35b
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-cycle-unsupported-forward-ref.ll
@@ -0,0 +1,42 @@
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; 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 %}
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=FWD
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Typedef T whose base is a pointer back to T, scoped in an enumeration type.
+; The enumeration is unsupported, so T fails after the pointer has already named
+; T's forward id. With SPV_KHR_relaxed_extended_instruction that id is defined
+; as DebugInfoNone; without it both edges are dropped.
+
+; CHECK-SPIRV-NOT: DebugTypedef
+; CHECK-SPIRV-NOT: DebugTypePointer
+; CHECK-SPIRV-NOT: OpExtInstWithForwardRefsKHR
+
+; FWD: OpExtension "SPV_KHR_relaxed_extended_instruction"
+; FWD: [[ext:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; FWD-DAG: [[void:%[0-9]+]] = OpTypeVoid
+; FWD: OpExtInstWithForwardRefsKHR [[void]] [[ext]] DebugTypePointer [[none:%[0-9]+]]
+; FWD: [[none]] = OpExtInst [[void]] [[ext]] DebugInfoNone
+; FWD-NOT: DebugTypedef
+
+define spir_func void @test() !dbg !6 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!1, !2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4)
+!1 = !{i32 7, !"Dwarf Version", i32 5}
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIFile(filename: "typedef-cycle.c", directory: "/src")
+
+!4 = !{!5, !8}
+!5 = !DIDerivedType(tag: DW_TAG_typedef, name: "T", file: !3, line: 2, baseType: !8, scope: !7)
+!6 = distinct !DISubprogram(name: "test", scope: !3, file: !3, line: 5, type: !10, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !DICompositeType(tag: DW_TAG_enumeration_type, name: "E", file: !3, line: 1, size: 32, elements: !12)
+!8 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !5, size: 64, dwarfAddressSpace: 4)
+!10 = !DISubroutineType(types: !11)
+!11 = !{null}
+!12 = !{}
More information about the llvm-branch-commits
mailing list