[llvm-branch-commits] [llvm] [SPIRV] Forward-reference debug scopes with a reserved result id. (PR #229490)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Oct 7 04:04:36 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-spir-v

Author: Manuel Carrasco (mgcarrasco)

<details>
<summary>Changes</summary>

This PR brings support for cycles when processing DIScope(s). This is done using `SPV_KHR_relaxed_extended_instruction` for `OpExtInstWithForwardRefsKHR`, which permits forward references (a use prior to its definition).

The new emission algorithm works in the following way. DIScopes are recursively processed as it was implemented at the start of this stacked PRs. `getOrCreateDebugScope` reserves an id for the current scope, and recurses to first emit its dependencies. If in the recursion we hit a back edge (cycle), the scope even though not emitted yet will have a reserved id. That undefined but reserved id is then used as an operand alongside `OpExtInstWithForwardRefsKHR`.

When an instruction defining a DIScope is going to be emitted, `getOrCreateDebugScope` makes sure that the scope's instruction uses its reserved id. If the current scope is not supported by the NSDI support in the backend, it might be the case that the scope was still used as a forward reference. This forward reference, which has been emitted, must still be closed to remain valid. `getOrCreateDebugScope` keeps track of whether the scope has been used in a forward reference, and if unsupported, it defines it with a fresh DebugInfoNone. This might in some instances trigger invalid SPIRV due to conflicts with the spec or bugs in `spirv-val`. 

It is worth noting that ilegal SPIRV due to DebugInfoNone will only occur if the BE hits an unsupported feature, and the actual "bug" is the missing feature. A design where a scope chain is only emitted if all of it is supported would be much complex. If this is not acceptable by a consumer, this forward reference emission can be disabled via a flag. The BE has no support for cycles so it won't be a regression. This PR also provides foundations for a more complex design if needed.

As reference, the SPIRV-LLVM Translator already reserves ids for these cycles, but it does not use `SPV_KHR_relaxed_extended_instruction`, so the SPIRV is always invalid. Its SPIRV to LLVM path will also need to handle that extension for debug info in order to work with this implementation.

---

Patch is 29.29 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/229490.diff


10 Files Affected:

- (modified) llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp (+2-1) 
- (modified) llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp (+3-1) 
- (modified) llvm/lib/Target/SPIRV/SPIRVInstrInfo.td (+5) 
- (modified) llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp (+101-13) 
- (modified) llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h (+27-6) 
- (modified) llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td (+2) 
- (added) llvm/test/CodeGen/SPIRV/debug-info/debug-composite-parent-debuginfonone.ll (+57) 
- (added) llvm/test/CodeGen/SPIRV/debug-info/debug-type-composite-self-pointer.ll (+56) 
- (added) llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-base-debuginfonone.ll (+53) 
- (added) llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-cycle-unsupported-forward-ref.ll (+43) 


``````````diff
diff --git a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp
index 8a2618b5309a9..3d78ac036caa6 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 956396355140c..0a61506de6e95 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 cd4faf80c2d97..aa4c0ed1cea28 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 b741113c6540c..a1e5a1964f2ce 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -32,6 +32,7 @@
 #include "llvm/IR/Module.h"
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCStreamer.h"
+#include "llvm/Support/CommandLine.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/Path.h"
@@ -39,6 +40,12 @@
 
 using namespace llvm;
 
+static cl::opt<bool> SPIRVDebugScopeForwardRefs(
+    "spirv-debug-scope-forward-refs",
+    cl::desc("Forward-reference debug scopes when "
+             "SPV_KHR_relaxed_extended_instruction is enabled"),
+    cl::init(false));
+
 namespace {
 
 /// Look up \p Key in a register map and return its value, or std::nullopt when
@@ -340,6 +347,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   LexicalBlocks.clear();
   DebugScopeRegs.clear();
   ScopesInProgress.clear();
+  HasRelaxedExtInst = false;
   FailedScopes.clear();
   DebugInlinedAtRegs.clear();
   ScopeToPathOpStringReg.clear();
@@ -453,6 +461,14 @@ void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
   // Add the extension to requirements so OpExtension is output.
   MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info);
 
+  HasRelaxedExtInst =
+      SPIRVDebugScopeForwardRefs &&
+      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 +553,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 +801,11 @@ 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. When forward references are
+// enabled, that id is used with OpExtInstWithForwardRefsKHR, and a later
+// failure defines it as DebugInfoNone only when an emitted instruction already
+// named it. Otherwise the edge is dropped and the reserved id stays unused.
 auto SPIRVNonSemanticDebugHandler::getOrCreateDebugScope(const DIScope *S)
     -> EmitResult<MCRegister> {
   if (!S || FailedScopes.contains(S))
@@ -769,16 +813,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 +873,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 8e17d8cf0d39b..e0f351238bace 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -133,8 +133,20 @@ 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 -spirv-debug-scope-forward-refs is set and prepareModuleOutput
+  // saw SPV_KHR_relaxed_extended_instruction.
+  bool HasRelaxedExtInst = false;
 
   // Types and scopes that are not supported.
   SmallPtrSet<const DIScope *, 8> FailedScopes;
@@ -386,10 +398,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 +425,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 561d0fdef4026..a0604b451cd12 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 0000000000000..c6bb31bac1d00
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-composite-parent-debuginfonone.ll
@@ -0,0 +1,57 @@
+; 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 --spirv-debug-scope-forward-refs=true -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 --spirv-debug-scope-forward-refs=true -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | not spirv-val 2>&1 | FileCheck %s --check-prefix=VAL %}
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info,+SPV_KHR_relaxed_extended_instruction --spirv-debug-scope-forward-refs=0 -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,+SPV_KHR_relaxed_extended_instruction --spirv-debug-scope-forward-refs=0 -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-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. -spirv-debug-scope-forward-refs defaults to off,
+; so without that flag 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 0000000000000..2e4e4402e5de6
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-composite-self-pointer.ll
@@ -0,0 +1,56 @@
+; 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 --spirv-debug-scope-forward-...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/229490


More information about the llvm-branch-commits mailing list