[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