[llvm] Add support for NSDI DebugScope, DebugNoScope and DebugInlinedAt (PR #218959)

Manuel Carrasco via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 7 02:00:24 PDT 2026


https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/218959

>From 954e91440970d69bf67d3d7f1e15c2359ad53438 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Wed, 26 Aug 2026 10:30:04 -0500
Subject: [PATCH] Add support for NSDI DebugScope, DebugNoScope and
 DebugInlinedAt

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 145 +++++++++++++++---
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      |  32 ++++
 ...-function-definition-call-before-alloca.ll |   2 +
 .../debug-function-definition-calls.ll        |   6 +
 ...debug-function-definition-external-call.ll |   1 +
 .../debug-info/debug-inlined-at-recursive.ll  |  83 ++++++++++
 .../SPIRV/debug-info/debug-inlined-at.ll      |  68 ++++++++
 .../SPIRV/debug-info/debug-line-block.ll      |   5 +-
 .../SPIRV/debug-info/debug-line-calls.ll      |   2 +
 .../SPIRV/debug-info/debug-line-if-phi.ll     |   5 +-
 .../SPIRV/debug-info/debug-line-loop-merge.ll |   2 +
 .../SPIRV/debug-info/debug-line-loop.ll       |   5 +-
 .../debug-info/debug-line-selection-merge.ll  |   4 +
 .../SPIRV/debug-info/debug-line-shared.ll     |   1 +
 .../CodeGen/SPIRV/debug-info/debug-line.ll    |   1 +
 .../CodeGen/SPIRV/debug-info/debug-no-line.ll |   3 +
 .../SPIRV/debug-info/debug-scope-block.ll     |  66 ++++++++
 .../SPIRV/debug-info/debug-scope-no-scope.ll  |  55 +++++++
 .../SPIRV/debug-info/debug-scope-same-line.ll |  63 ++++++++
 .../SPIRV/debug-info/debug-scope-shared.ll    |  62 ++++++++
 .../CodeGen/SPIRV/debug-info/debug-scope.ll   |  67 ++++++++
 21 files changed, 657 insertions(+), 21 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at-recursive.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-scope-block.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-scope-no-scope.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-scope-same-line.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-scope-shared.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-scope.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index fd289d3a5764c..bb311d61f59c0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -312,6 +312,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   GlobalVariableDebugInfoMap.clear();
   LexicalBlocks.clear();
   DebugScopeRegs.clear();
+  DebugInlinedAtRegs.clear();
   ScopeToPathOpStringReg.clear();
   DebugSourceRegByFileStr.clear();
   OpStringContentCache.clear();
@@ -697,6 +698,38 @@ std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugLexicalBlock(
                      ExtInstSetReg, Ops, MAI);
 }
 
+MCRegister SPIRVNonSemanticDebugHandler::getOrEmitDebugInlinedAt(
+    const DILocation *IA, MCRegister VoidTypeReg, MCRegister I32TypeReg,
+    MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
+  assert(IA && "IA must not be null in getOrEmitDebugInlinedAt");
+
+  if (MCRegister Cached = DebugInlinedAtRegs.lookup(IA))
+    return Cached;
+
+  auto ScopeRegOpt = resolveScope(IA->getScope());
+  if (!ScopeRegOpt)
+    return MCRegister();
+
+  MCRegister LineReg =
+      emitOpConstantI32(static_cast<uint32_t>(IA->getLine()), I32TypeReg, MAI);
+
+  SmallVector<MCRegister, 3> Ops{LineReg, *ScopeRegOpt};
+  // Recurse before building this instruction's operands so an outer
+  // inlined-at link is always available.
+  if (const DILocation *Outer = IA->getInlinedAt()) {
+    MCRegister OuterReg = getOrEmitDebugInlinedAt(
+        Outer, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
+    if (!OuterReg.isValid())
+      return MCRegister();
+    Ops.push_back(OuterReg);
+  }
+
+  MCRegister Reg = emitExtInst(SPIRV::NonSemanticExtInst::DebugInlinedAt,
+                               VoidTypeReg, ExtInstSetReg, Ops, MAI);
+  DebugInlinedAtRegs[IA] = Reg;
+  return Reg;
+}
+
 std::optional<MCRegister>
 SPIRVNonSemanticDebugHandler::emitDebugFunctionDeclaration(
     const DISubprogram *SP, MCRegister VoidTypeReg, MCRegister I32TypeReg,
@@ -1142,6 +1175,7 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   LastFunctionOpVariable = nullptr;
   DebugFunctionDefinitionEmitted = false;
   LastLineMI = nullptr;
+  LastScopeMI = nullptr;
 }
 
 void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
@@ -1206,7 +1240,13 @@ void SPIRVNonSemanticDebugHandler::beginInstruction(const MachineInstr *MI) {
 
   if (!DebugFunctionDefinitionEmitted)
     return;
-  emitDebugLineForInstruction(MI);
+
+  std::optional<const MachineInstr *> Target = resolveDebugLocTarget(MI);
+  if (!Target)
+    return;
+
+  emitDebugScopeForInstruction(*Target);
+  emitDebugLineForInstruction(*Target);
 }
 
 static bool isMergeInstruction(unsigned Opcode) {
@@ -1214,8 +1254,8 @@ static bool isMergeInstruction(unsigned Opcode) {
          Opcode == SPIRV::OpLoopControlINTEL;
 }
 
-static bool isDebugLineTarget(const MachineInstr *MI,
-                              SPIRV::ModuleAnalysisInfo &MAI) {
+static bool isDebugLocTarget(const MachineInstr *MI,
+                             SPIRV::ModuleAnalysisInfo &MAI) {
   if (MAI.getSkipEmission(MI))
     return false;
   switch (MI->getOpcode()) {
@@ -1243,25 +1283,23 @@ findAdjacentEmittedInstruction(const MachineInstr *MI,
   return nullptr;
 }
 
-void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
-    const MachineInstr *MI) {
-  assert(DebugFunctionDefinitionEmitted &&
-         "DebugFunctionDefinition must be emitted");
+std::optional<const MachineInstr *>
+SPIRVNonSemanticDebugHandler::resolveDebugLocTarget(const MachineInstr *MI) {
   assert(CurrentMAI && "CurrentMAI must be set");
-
   SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
 
-  // Structural opcodes don't require a DebugLine, other opcodes might have
-  // already been emitted in the module scope.
-  if (!isDebugLineTarget(MI, MAI))
-    return;
+  // Structural opcodes don't require a DebugLine/DebugScope, other opcodes
+  // might have already been emitted in the module scope.
+  if (!isDebugLocTarget(MI, MAI))
+    return std::nullopt;
 
-  // DebugLine can be emitted before a merge instruction, but not after it
-  // (nothing may sit between the merge and its terminator). We can use either
-  // the merge's or the terminator's debug info; we emit the terminator's one.
+  // DebugLine/DebugScope can be emitted before a merge instruction, but not
+  // after it (nothing may sit between the merge and its terminator). We can
+  // use either the merge's or the terminator's debug info; we emit the
+  // terminator's one.
   const MachineInstr *Prev = findAdjacentEmittedInstruction(MI, MAI, false);
   if (Prev && isMergeInstruction(Prev->getOpcode()))
-    return;
+    return std::nullopt;
 
   if (isMergeInstruction(MI->getOpcode())) {
     // Use the terminator's debug info; when we reach it later, the check
@@ -1270,10 +1308,76 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
     assert(MI && "Merge instruction must be followed by a terminator");
   }
 
-  // The range of DebugLine must be reset at each basic block boundary.
+  // Both regions are implicitly closed at each basic block boundary. They are
+  // tracked separately because a DebugScope region usually spans several
+  // DebugLine regions, and either one can skip emission on a cache miss.
   if (LastLineMI && MI->getParent() != LastLineMI->getParent())
     LastLineMI = nullptr;
+  if (LastScopeMI && MI->getParent() != LastScopeMI->getParent())
+    LastScopeMI = nullptr;
+
+  return MI;
+}
+
+void SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
+    const MachineInstr *MI) {
+  assert(DebugFunctionDefinitionEmitted &&
+         "DebugFunctionDefinition must be emitted");
+  assert(CurrentMAI && "CurrentMAI must be set");
 
+  SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
+  MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
+  MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
+
+  const DILocation *CurDL = MI->getDebugLoc().get();
+  if (!CurDL) {
+    // No location for the current instruction.
+    if (LastScopeMI) {
+      // Close the current DebugScope region.
+      emitExtInst(SPIRV::NonSemanticExtInst::DebugNoScope, VoidTypeReg,
+                  ExtInstSetReg, {}, MAI);
+      LastScopeMI = nullptr;
+    }
+    return;
+  }
+
+  const DIScope *CurScope = CurDL->getScope();
+  const DILocation *CurInlinedAt = CurDL->getInlinedAt();
+
+  if (LastScopeMI) {
+    const DILocation *LastDL = LastScopeMI->getDebugLoc().get();
+    if (LastDL->getScope() == CurScope &&
+        LastDL->getInlinedAt() == CurInlinedAt)
+      return;
+  }
+
+  auto CurScopeRegOpt = resolveScope(CurScope);
+  if (!CurScopeRegOpt)
+    return;
+
+  SmallVector<MCRegister, 2> Ops{*CurScopeRegOpt};
+  if (CurInlinedAt) {
+    // If the global emission did not include this inlined-at case, we skip it.
+    MCRegister InlinedReg = DebugInlinedAtRegs.lookup(CurInlinedAt);
+    if (!InlinedReg.isValid())
+      return;
+    Ops.push_back(InlinedReg);
+  }
+
+  // A new DebugScope region is needed.
+  emitExtInst(SPIRV::NonSemanticExtInst::DebugScope, VoidTypeReg, ExtInstSetReg,
+              Ops, MAI);
+
+  LastScopeMI = MI;
+}
+
+void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
+    const MachineInstr *MI) {
+  assert(DebugFunctionDefinitionEmitted &&
+         "DebugFunctionDefinition must be emitted");
+  assert(CurrentMAI && "CurrentMAI must be set");
+
+  SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
   MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
   MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
 
@@ -1317,7 +1421,7 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
   if (LastLineMI && MI->getDebugLoc() == LastLineMI->getDebugLoc())
     return;
 
-  // A new DebugLine region is needed. Emit it and update LastLineMI.
+  // A new DebugLine region is needed.
   emitExtInst(SPIRV::NonSemanticExtInst::DebugLine, VoidTypeReg, ExtInstSetReg,
               {SrcReg, LineReg, LineReg, ColStartReg, ColEndReg}, MAI);
 
@@ -1572,6 +1676,11 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
     emitDebugGlobalVariable(GV, Info, VoidTypeReg, I32TypeReg, ExtInstSetReg,
                             MAI);
 
+  // Emit DebugInlinedAt allowing recursive inlining.
+  for (const DILocation *DL : UniqueDebugLocations)
+    if (const DILocation *IA = DL->getInlinedAt())
+      getOrEmitDebugInlinedAt(IA, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
+
   for (const DILocation *DL : UniqueDebugLocations) {
     emitOpConstantI32(DL->getLine(), I32TypeReg, MAI);
     emitOpConstantI32(DL->getColumn(), I32TypeReg, MAI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index ecb23450517c7..ce02b274af75e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -108,6 +108,9 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // order, collected in beginModule() for DebugLexicalBlock emission.
   SetVector<const DIScope *> LexicalBlocks;
 
+  // DebugInlinedAt result id per DILocation used as an inlined-at chain link.
+  DenseMap<const DILocation *, MCRegister> DebugInlinedAtRegs;
+
   // 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
@@ -166,6 +169,8 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   const MachineInstr *LastLineMI = nullptr;
 
+  const MachineInstr *LastScopeMI = nullptr;
+
 public:
   explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);
 
@@ -231,6 +236,16 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   void resetPerFunctionDebugState();
 
+  /// Resolve the instruction that a per-instruction DebugLine/DebugScope
+  /// update should attach to: \p MI adjusted forward past a merge
+  /// instruction to its terminator, or \c std::nullopt if \p MI is not a
+  /// valid attachment point (skip-emission, or one of the structural opcodes
+  /// that can never carry DebugLine/DebugScope: OpFunction,
+  /// OpFunctionParameter, OpFunctionEnd, OpLabel, OpPhi).
+  std::optional<const MachineInstr *>
+  resolveDebugLocTarget(const MachineInstr *MI);
+
+  void emitDebugScopeForInstruction(const MachineInstr *MI);
   void emitDebugLineForInstruction(const MachineInstr *MI);
   void preparePerFunctionDebug(const MachineFunction *MF);
   void tryEmitDebugFunctionDefinition(SPIRV::ModuleAnalysisInfo &MAI);
@@ -485,6 +500,23 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   emitDebugLexicalBlock(const DIScope *S, MCRegister VoidTypeReg,
                         MCRegister I32TypeReg, MCRegister ExtInstSetReg,
                         SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Return a cached \c DebugInlinedAt id for \p IA, or emit one (recursing
+  /// into \c IA->getInlinedAt() first for the optional Inlined operand, so
+  /// outer frames are always emitted before the inner frame that references
+  /// them). Must run after \c DebugScopeRegs is populated, since the Scope
+  /// operand is resolved through \c resolveScope. \c DebugInlinedAt is not in
+  /// the spec's in-block instruction list, so this is only ever called from
+  /// module-scope emission (\c emitNonSemanticGlobalDebugInfo), never from
+  /// per-instruction emission.
+  ///
+  /// \returns An invalid (default-constructed) \c MCRegister, and emits
+  /// nothing, if \p IA's Scope does not resolve.
+  MCRegister getOrEmitDebugInlinedAt(const DILocation *IA,
+                                     MCRegister VoidTypeReg,
+                                     MCRegister I32TypeReg,
+                                     MCRegister ExtInstSetReg,
+                                     SPIRV::ModuleAnalysisInfo &MAI);
 };
 
 } // namespace llvm
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
index ed46c25d3ba20..8c3329316d931 100644
--- 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
@@ -33,6 +33,7 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[CALLER]]
 ; CHECK-NEXT: OpFunctionCall
 ; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
@@ -44,6 +45,7 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_NOARGS]] [[CALLER_NOARGS]]
 ; CHECK-NEXT: OpFunctionCall
 ; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_NOARGS]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturn
 ; CHECK-NEXT: OpFunctionEnd
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
index 8db6cc586a01e..e577379311155 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
@@ -26,6 +26,7 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_LEAF]] [[LEAF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_LEAF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
@@ -39,10 +40,13 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_WITH]] [[WITH]]
 ; CHECK-NEXT: OpStore
 ; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_WITH]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoScope
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoLine
 ; CHECK-NEXT: OpIAdd
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_WITH]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
@@ -52,6 +56,7 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_CALLER]] [[CALLER]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_CALLER]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpFunctionCall
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
@@ -64,6 +69,7 @@
 ; CHECK: [[ORCH]] = OpFunction %{{.*}} ; -- Begin function orchestrator
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_ORCH]] [[ORCH]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_ORCH]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpFunctionCall
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
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
index bd0c6b56a7dd4..f72fe5c2b01f8 100644
--- 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
@@ -25,6 +25,7 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[CALLER]]
 ; CHECK-NEXT: OpStore
 ; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at-recursive.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at-recursive.ll
new file mode 100644
index 0000000000000..a490cb80b6faa
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at-recursive.ll
@@ -0,0 +1,83 @@
+; 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 a two-level DebugInlinedAt chain. 
+;
+;   static inline __attribute__((always_inline)) int innermost(int x) {
+;     return x * 3;                     // line 2
+;   }
+;   static inline __attribute__((always_inline)) int middle(int y) {
+;     return innermost(y) ^ 7;          // line 6, calls innermost at col 10
+;   }
+;   int top(int z) {
+;     int r = middle(z);                // line 10, calls middle at col 11
+;     return r - 5;                     // line 11
+;   }
+;
+
+; 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: [[NAME_INNER:%[0-9]+]] = OpString "innermost"
+; CHECK-DAG: [[NAME_MID:%[0-9]+]] = OpString "middle"
+; CHECK-DAG: [[NAME_TOP:%[0-9]+]] = OpString "top"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource
+; CHECK-DAG: [[DF_INNER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_INNER]]
+; CHECK-DAG: [[DF_MID:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_MID]]
+; CHECK-DAG: [[DF_TOP:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_TOP]]
+; CHECK-DAG: [[V6:%[0-9]+]] = OpConstant [[I32]] 6{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+
+; CHECK: [[OUTER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugInlinedAt [[V10]] [[DF_TOP]]
+; CHECK-NEXT: [[INNER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugInlinedAt [[V6]] [[DF_MID]] [[OUTER]]
+
+; CHECK: [[TOP:%[0-9]+]] = OpFunction {{.*}} ; -- Begin function top
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_TOP]] [[TOP]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_INNER]] [[INNER]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
+; CHECK-NEXT: OpIMul
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_MID]] [[OUTER]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
+; CHECK-NEXT: OpBitwiseXor
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_TOP]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
+; CHECK-NEXT: OpIAdd
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
+; CHECK-NEXT: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @top(i32 %z) !dbg !11 {
+entry:
+  %mul.i = mul nsw i32 %z, 3, !dbg !29
+  %xor.i = xor i32 %mul.i, 7, !dbg !30
+  %sub = add nsw i32 %xor.i, -5, !dbg !31
+  ret i32 %sub, !dbg !32
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-inlined-at-recursive.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!11 = distinct !DISubprogram(name: "top", scope: !1, file: !1, line: 9, type: !12, scopeLine: 9, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!12 = !DISubroutineType(cc: DW_CC_LLVM_SpirFunction, types: !13)
+!13 = !{!14, !14}
+!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!20 = distinct !DISubprogram(name: "middle", scope: !1, file: !1, line: 5, type: !12, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!25 = distinct !DISubprogram(name: "innermost", scope: !1, file: !1, line: 1, type: !12, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+
+!23 = distinct !DILocation(line: 10, column: 11, scope: !11)
+!28 = distinct !DILocation(line: 6, column: 10, scope: !20, inlinedAt: !23)
+
+!29 = !DILocation(line: 2, column: 12, scope: !25, inlinedAt: !28)
+!30 = !DILocation(line: 6, column: 23, scope: !20, inlinedAt: !23)
+!31 = !DILocation(line: 11, column: 12, scope: !11)
+!32 = !DILocation(line: 11, column: 3, scope: !11)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll
new file mode 100644
index 0000000000000..8a186fa818dc5
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll
@@ -0,0 +1,68 @@
+; 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 a single-level DebugInlinedAt.
+;
+;   static inline __attribute__((always_inline)) int callee(int x) {
+;     return x * 3;                     // line 2
+;   }
+;   int caller(int y) {
+;     int r = callee(y);                // line 6, calls callee at col 11
+;     return r - 5;                     // line 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: [[NAME_CALLEE:%[0-9]+]] = OpString "callee"
+; CHECK-DAG: [[NAME_CALLER:%[0-9]+]] = OpString "caller"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource
+; CHECK-DAG: [[DF_CALLEE:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_CALLEE]]
+; CHECK-DAG: [[DF_CALLER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_CALLER]]
+; CHECK-DAG: [[V6:%[0-9]+]] = OpConstant [[I32]] 6{{$}}
+
+; CHECK: [[IA:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugInlinedAt [[V6]] [[DF_CALLER]]
+
+; CHECK: [[FN:%[0-9]+]] = OpFunction {{.*}} ; -- Begin function caller
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_CALLER]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_CALLEE]] [[IA]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
+; CHECK-NEXT: OpIMul
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_CALLER]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
+; CHECK-NEXT: OpIAdd
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
+; CHECK-NEXT: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @caller(i32 %y) !dbg !11 {
+entry:
+  %mul.i = mul nsw i32 %y, 3, !dbg !24
+  %sub = add nsw i32 %mul.i, -5, !dbg !25
+  ret i32 %sub, !dbg !26
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-inlined-at.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!11 = distinct !DISubprogram(name: "caller", scope: !1, file: !1, line: 5, type: !12, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!12 = !DISubroutineType(cc: DW_CC_LLVM_SpirFunction, types: !13)
+!13 = !{!14, !14}
+!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!20 = distinct !DISubprogram(name: "callee", scope: !1, file: !1, line: 1, type: !12, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+
+!23 = distinct !DILocation(line: 6, column: 11, scope: !11)
+!24 = !DILocation(line: 2, column: 12, scope: !20, inlinedAt: !23)
+!25 = !DILocation(line: 7, column: 12, scope: !11)
+!26 = !DILocation(line: 7, column: 3, scope: !11)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll
index 48431e188b9d3..b643bc26633eb 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll
@@ -20,13 +20,16 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
 ; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]]
 ; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpBranch
 
-; then block: mul reuses line 3; region must be reopened at the block boundary.
+; then block: mul reuses line 3; both DebugScope and DebugLine regions must be
+; reopened at the block boundary, even though neither scope nor line changed.
 ; CHECK:      OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
 ; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll
index 8928b121401bc..f6452e6bffeaf 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll
@@ -27,6 +27,7 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_INC]] [[INC]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_INC]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V2]] [[V2]] [[V10]] [[V11]]
 ; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V3]] [[V4]]
@@ -38,6 +39,7 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_CALLER]] [[CALLER]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_CALLER]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V6]] [[V6]] [[V12]] [[V13]]
 ; CHECK-NEXT: OpFunctionCall [[I32]] [[INC]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V7]] [[V7]] [[V12]] [[V13]]
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-if-phi.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-if-phi.ll
index 14ec10f70aeef..2c9ffb7ab91f4 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-if-phi.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-if-phi.ll
@@ -1,7 +1,7 @@
 ; 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 %}
 
-; DebugLine/DebugNoLine must not appear before OpPhi.
+; DebugLine/DebugNoLine/DebugScope/DebugNoScope must not appear before OpPhi.
 
 ; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
 ; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
@@ -22,18 +22,21 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
 ; CHECK-NEXT: OpSLessThan
 ; CHECK-NEXT: OpBranchConditional
 
 ; then
 ; CHECK:      OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V5]] [[V6]]
 ; CHECK-NEXT: OpIAdd
 ; CHECK-NEXT: OpBranch
 
 ; CHECK:      OpLabel
 ; CHECK-NEXT: [[PHI:%[0-9]+]] = OpPhi [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V9]] [[V9]] [[V3]] [[V4]]
 ; CHECK-NEXT: OpReturnValue [[PHI]]
 ; CHECK-NEXT: OpFunctionEnd
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop-merge.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop-merge.ll
index 171203202bdf4..00db7e47e8191 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop-merge.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop-merge.ll
@@ -9,6 +9,7 @@
 ; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
 ; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}src{{[/\\]}}debug-line-loop-merge.c"
 ; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
 ; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
 ; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
 ; CHECK-DAG: [[V50:%[0-9]+]] = OpConstant [[I32]] 50{{$}}
@@ -16,6 +17,7 @@
 ; CHECK-DAG: [[V99:%[0-9]+]] = OpConstant [[I32]] 99{{$}}
 
 ; CHECK:      OpPhi [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V3]] [[V4]]
 ; CHECK-NEXT: OpSLessThan
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V99]] [[V99]] [[V50]] [[V51]]
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop.ll
index 6feaea35912ba..fe988b85cd9e9 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-loop.ll
@@ -1,13 +1,15 @@
 ; 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 %}
 
-; DebugLine/DebugNoLine must not appear before OpPhi in a loop header.
+; DebugLine/DebugNoLine/DebugScope/DebugNoScope must not appear before OpPhi in
+; a loop header.
 
 ; 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-line-loop.c"
 ; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
 ; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
 ; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
 
@@ -15,6 +17,7 @@
 ; CHECK:      OpBranch
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: [[PHI:%[0-9]+]] = OpPhi [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V3]] [[V4]]
 ; CHECK-NEXT: OpSLessThan
 ; CHECK-NEXT: OpBranchConditional
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-selection-merge.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-selection-merge.ll
index 9d34026de31db..13d7b048b58b9 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-selection-merge.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-selection-merge.ll
@@ -26,6 +26,7 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
 ; CHECK-NEXT: OpSLessThan
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V99]] [[V99]] [[V50]] [[V51]]
@@ -34,12 +35,14 @@
 
 ; else
 ; CHECK:      OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V6]] [[V6]] [[V5]] [[V6]]
 ; CHECK-NEXT: OpISub
 ; CHECK-NEXT: OpBranch
 
 ; then
 ; CHECK:      OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V5]] [[V6]]
 ; CHECK-NEXT: OpIAdd
 ; CHECK-NEXT: OpBranch
@@ -47,6 +50,7 @@
 ; merge
 ; CHECK:      OpLabel
 ; CHECK-NEXT: [[PHI:%[0-9]+]] = OpPhi [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V9]] [[V9]] [[V3]] [[V4]]
 ; CHECK-NEXT: OpReturnValue [[PHI]]
 ; CHECK-NEXT: OpFunctionEnd
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll
index 6f106d8ba4d44..0f0f4cd5c4a33 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll
@@ -21,6 +21,7 @@
 ; CHECK-NEXT: [[B:%[0-9]+]] = OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
 ; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]] [[A]] [[B]]
 ; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugLine
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll
index 0178126982ca2..ebcaaea7e78a3 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll
@@ -33,6 +33,7 @@
 ; CHECK-NEXT: [[B:%[0-9]+]] = OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
 ; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]] [[A]] [[B]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V12]] [[V13]]
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll
index ce5acc50391bb..03bf06d507aff 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll
@@ -25,10 +25,13 @@
 ; CHECK-NEXT: [[B:%[0-9]+]] = OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
 ; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]] [[A]] [[B]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoScope
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoLine
 ; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]] [[T0]] [[A]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
 ; CHECK-NEXT: OpReturnValue [[T1]]
 ; CHECK-NEXT: OpFunctionEnd
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-block.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-block.ll
new file mode 100644
index 0000000000000..09ad3976cf40d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-block.ll
@@ -0,0 +1,66 @@
+; 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 the implicit close of a DebugScope region at a basic block boundary,
+; for a lexical block that spans two blocks.
+;
+; 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-scope-block.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[DS]] {{.*}} [[DF]]
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V6:%[0-9]+]] = OpConstant [[I32]] 6{{$}}
+; CHECK-DAG: [[V7:%[0-9]+]] = OpConstant [[I32]] 7{{$}}
+
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[LB]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V5]] [[V6]]
+; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]]
+; CHECK-NEXT: OpBranch
+
+; then: both regions reopen even though neither scope nor position changed.
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[LB]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V5]] [[V6]]
+; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V7]] [[V7]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[T1]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @block_scope(i32 %x) !dbg !5 {
+entry:
+  %t0 = add i32 %x, 1, !dbg !9     ; lexical block, line 4, col 5
+  br label %then, !dbg !9          ; same scope and position as the add
+
+then:
+  %t1 = mul i32 %t0, %t0, !dbg !9  ; same scope and position, new block
+  ret i32 %t1, !dbg !12            ; back to function scope, line 7, col 3
+}
+
+!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-scope-block.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: "block_scope", linkageName: "block_scope", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!10 = distinct !DILexicalBlock(scope: !5, file: !1, line: 3, column: 5)
+!9 = !DILocation(line: 4, column: 5, scope: !10)
+!12 = !DILocation(line: 7, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-no-scope.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-no-scope.ll
new file mode 100644
index 0000000000000..a84c1e21aaa9e
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-no-scope.ll
@@ -0,0 +1,55 @@
+; 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 %}
+
+; 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-scope-no-scope.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: [[A:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
+; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]] [[A]] [[B]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoScope
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoLine
+; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]] [[T0]] [[A]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[T1]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @maybe_scope(i32 %a, i32 %b) !dbg !5 {
+entry:
+  %t0 = add i32 %a, %b, !dbg !8   ; line 3, col 10
+  %t1 = mul i32 %t0, %a           ; no debug location
+  ret i32 %t1, !dbg !10           ; line 5, col 3
+}
+
+!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-scope-no-scope.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}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "maybe_scope", linkageName: "maybe_scope", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 10, scope: !5)
+!10 = !DILocation(line: 5, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-same-line.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-same-line.ll
new file mode 100644
index 0000000000000..606ec21e75754
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-same-line.ll
@@ -0,0 +1,63 @@
+; 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 %}
+
+; The scope changes while the source position stays identical.
+; At the moment, the emission of DebugLine depends on the DILocation pointer
+; which leads to an additional DebugLine being emitted.
+
+; 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-scope-same-line.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[DS]] {{.*}} [[DF]]
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V6:%[0-9]+]] = OpConstant [[I32]] 6{{$}}
+
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: [[N:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V5]] [[V6]]
+; CHECK-NEXT: [[A:%[0-9]+]] = OpIAdd [[I32]]
+
+; Scope-only change: new DebugScope, plus a DebugLine identical to the one above.
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[LB]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V5]] [[V6]]
+; CHECK-NEXT: [[B:%[0-9]+]] = OpIAdd [[I32]]
+
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[B]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @same_line(i32 %n) !dbg !5 {
+entry:
+  %a = add i32 %n, 1, !dbg !9    ; function scope, line 3, col 5
+  %b = add i32 %a, 1, !dbg !11   ; lexical block, line 3, col 5 -- scope only
+  ret i32 %b, !dbg !12           ; back to function scope, line 4, col 3
+}
+
+!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-scope-same-line.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: "same_line", linkageName: "same_line", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DILocation(line: 3, column: 5, scope: !5)
+!10 = distinct !DILexicalBlock(scope: !5, file: !1, line: 2, column: 6)
+!11 = !DILocation(line: 3, column: 5, scope: !10)
+!12 = !DILocation(line: 4, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-shared.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-shared.ll
new file mode 100644
index 0000000000000..402f100b0fa9d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-shared.ll
@@ -0,0 +1,62 @@
+; 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 %}
+
+; One DebugScope region spans several DebugLine regions.
+
+; 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-scope-shared.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[V2:%[0-9]+]] = OpConstant [[I32]] 2{{$}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+
+; A single DebugScope opens the region; the fully pinned CHECK-NEXT chain below
+; leaves no room for a second one anywhere in the function.
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: [[N:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V2]] [[V2]] [[V10]] [[V11]]
+; CHECK-NEXT: [[A:%[0-9]+]] = OpIAdd [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
+; CHECK-NEXT: [[B:%[0-9]+]] = OpIAdd [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V10]] [[V11]]
+; CHECK-NEXT: [[C:%[0-9]+]] = OpIAdd [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[C]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @spans(i32 %n) !dbg !5 {
+entry:
+  %a = add i32 %n, 1, !dbg !9    ; line 2, col 10
+  %b = add i32 %a, 1, !dbg !10   ; line 3, col 10 -- line changes, scope does not
+  %c = add i32 %b, 1, !dbg !11   ; line 4, col 10 -- line changes, scope does not
+  ret i32 %c, !dbg !12           ; line 5, col 3  -- line changes, scope does not
+}
+
+!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-scope-shared.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: "spans", linkageName: "spans", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DILocation(line: 2, column: 10, scope: !5)
+!10 = !DILocation(line: 3, column: 10, scope: !5)
+!11 = !DILocation(line: 4, column: 10, scope: !5)
+!12 = !DILocation(line: 5, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-scope.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope.ll
new file mode 100644
index 0000000000000..ba3ac63f076f1
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope.ll
@@ -0,0 +1,67 @@
+; 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 %}
+
+; 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-scope.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[DS]] {{.*}} [[DF]]
+; CHECK-DAG: [[V2:%[0-9]+]] = OpConstant [[I32]] 2{{$}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V7:%[0-9]+]] = OpConstant [[I32]] 7{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: [[N:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V2]] [[V2]] [[V10]] [[V11]]
+; CHECK-NEXT: [[A:%[0-9]+]] = OpIAdd [[I32]] [[N]]
+; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugScope
+; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugLine
+; CHECK-NEXT: [[B:%[0-9]+]] = OpIAdd [[I32]] [[A]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[LB]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
+; CHECK-NEXT: [[C:%[0-9]+]] = OpIAdd [[I32]] [[B]]
+; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugScope
+; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugLine
+; CHECK-NEXT: [[D:%[0-9]+]] = OpIAdd [[I32]] [[C]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V7]] [[V7]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[D]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @scoped(i32 %n) !dbg !5 {
+entry:
+  %a = add i32 %n, 1, !dbg !9    ; function scope, line 2, col 10
+  %b = add i32 %a, 1, !dbg !9    ; same scope+loc -> dedup
+  %c = add i32 %b, 1, !dbg !11   ; nested lexical block scope, line 5, col 3
+  %d = add i32 %c, 1, !dbg !11   ; same scope+loc -> dedup
+  ret i32 %d, !dbg !13           ; back to function scope, line 7, col 3
+}
+
+!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-scope.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: "scoped", linkageName: "scoped", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DILocation(line: 2, column: 10, scope: !5)
+!10 = distinct !DILexicalBlock(scope: !5, file: !1, line: 4, column: 5)
+!11 = !DILocation(line: 5, column: 3, scope: !10)
+!13 = !DILocation(line: 7, column: 3, scope: !5)



More information about the llvm-commits mailing list