[llvm] [SPIRV] Emit NonSemantic DebugValue (PR #224158)

Diego Novillo via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 06:18:38 PDT 2026


https://github.com/dnovillo updated https://github.com/llvm/llvm-project/pull/224158

>From 19ce267e4f57aa8b858cbaa864c536608a6fafb9 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Wed, 16 Sep 2026 18:37:34 -0400
Subject: [PATCH 1/2] [SPIRV] Emit NonSemantic DebugValue

Emit DebugValue for DBG_VALUE records, so the generated debug information connects a source local variable to the SPIR-V result or constant that holds its current value.
---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 543 ++++++++++++++++--
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      | 127 +++-
 llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp  |   5 +
 .../debug-declare-dbg-value-variadic.ll       |   4 +-
 .../CodeGen/SPIRV/debug-info/debug-declare.ll |   2 +-
 .../SPIRV/debug-info/debug-inlined-at.ll      |  16 +-
 .../debug-info/debug-line-selection-merge.ll  |  35 +-
 .../debug-local-variable-dbg-value.ll         |  10 +-
 .../SPIRV/debug-info/debug-type-pointer.ll    |  16 +-
 .../debug-value-constant-existing-type.ll     | 108 ++++
 .../SPIRV/debug-info/debug-value-constant.ll  | 154 +++++
 .../debug-info/debug-value-cross-block.ll     |  49 ++
 .../debug-info/debug-value-merge-region.ll    |  82 +++
 .../debug-info/debug-value-module-scope.ll    |  46 ++
 .../debug-value-postlegalizer-order.ll        |  39 ++
 .../debug-info/debug-value-unsupported.ll     |  96 ++++
 .../CodeGen/SPIRV/debug-info/debug-value.ll   | 101 ++++
 17 files changed, 1373 insertions(+), 60 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-cross-block.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-merge-region.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-module-scope.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-postlegalizer-order.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-unsupported.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 55ea4bc365d8d..20a1aa7e9a25b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -11,6 +11,7 @@
 #include "MCTargetDesc/SPIRVMCTargetDesc.h"
 #include "SPIRVSubtarget.h"
 #include "SPIRVUtils.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/ADT/Twine.h"
@@ -259,6 +260,100 @@ unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
   }
 }
 
+// Whether \p BT is a SPIR-V OpTypeBool.
+static bool isBooleanType(const DIBasicType *BT) {
+  return BT->getEncoding() == dwarf::DW_ATE_boolean;
+}
+
+// \p Value truncated to the width of \p BT, or 0 or 1 for a boolean.
+static uint64_t constantBits(uint64_t Value, const DIBasicType *BT) {
+  // A boolean is one of two values and a DIBasicType may give it no size, so
+  // masking to its width would turn every true into a false.
+  if (isBooleanType(BT))
+    return Value != 0;
+  unsigned Width = BT->getSizeInBits();
+  return Width >= 64 ? Value : Value & ((uint64_t(1) << Width) - 1);
+}
+
+// \p Value truncated to the width of \p BT. APInt::getZExtValue() asserts
+// above 64 bits, so the truncation comes first.
+static uint64_t constantBits(const APInt &Value, const DIBasicType *BT) {
+  unsigned Width = isBooleanType(BT) ? 1 : BT->getSizeInBits();
+  return constantBits(Value.getActiveBits() > 64
+                          ? Value.trunc(std::min(Width, 64u)).getZExtValue()
+                          : Value.getZExtValue(),
+                      BT);
+}
+
+// \p CI as a 64-bit value, truncated to the width of \p BT. A one-bit constant
+// is zero extended, widths 2 through 64 are sign extended, and anything wider
+// is truncated.
+static uint64_t constantBits(const ConstantInt *CI, const DIBasicType *BT) {
+  const APInt &Value = CI->getValue();
+  if (Value.getBitWidth() > 64)
+    return constantBits(Value, BT);
+  if (Value.getBitWidth() == 1)
+    return constantBits(Value.getZExtValue(), BT);
+  return constantBits(static_cast<uint64_t>(Value.getSExtValue()), BT);
+}
+
+// The DIBasicType that \p Ty resolves to through any typedefs, or null.
+//
+// This accepts: a boolean of any size, and a 16, 32 or 64-bit integer or float.
+// Anything else gives null, including a type reached through a cv-qualifier.
+static const DIBasicType *stripToScalarType(const DIType *Ty) {
+  // Only a typedef is looked through. A cv-qualified type is not emitted as a
+  // DebugType, so emitDebugLocalVariable() cannot name it and the variable
+  // gets no DebugLocalVariable.
+  while (const auto *DT = dyn_cast_or_null<DIDerivedType>(Ty)) {
+    if (DT->getTag() != dwarf::DW_TAG_typedef)
+      return nullptr;
+    Ty = DT->getBaseType();
+  }
+
+  const auto *BT = dyn_cast_or_null<DIBasicType>(Ty);
+  if (!BT)
+    return nullptr;
+
+  // OpTypeBool carries no width, so a boolean of any size is accepted.
+  if (isBooleanType(BT))
+    return BT;
+
+  unsigned Size = BT->getSizeInBits();
+  switch (BT->getEncoding()) {
+  case dwarf::DW_ATE_signed:
+  case dwarf::DW_ATE_unsigned:
+  case dwarf::DW_ATE_signed_char:
+  case dwarf::DW_ATE_unsigned_char:
+  case dwarf::DW_ATE_float:
+    return Size == 16 || Size == 32 || Size == 64 ? BT : nullptr;
+  default:
+    return nullptr;
+  }
+}
+
+static bool isLowerableExpression(const DIExpression *Expr);
+
+// The constant assignment \p DVR makes to \p LV, or nullopt when this backend
+// cannot name the assigned value.
+static std::optional<SPIRV::ConstantAssignment>
+getConstantAssignment(const DbgVariableRecord *DVR, const DILocalVariable *LV) {
+  if (!DVR->isDbgValue() || DVR->getNumVariableLocationOps() != 1)
+    return std::nullopt;
+  const auto *BT = stripToScalarType(LV->getType());
+  if (!BT || !isLowerableExpression(DVR->getExpression()))
+    return std::nullopt;
+  const Value *V = DVR->getVariableLocationOp(0);
+  std::optional<uint64_t> Bits;
+  if (const auto *CI = dyn_cast_or_null<ConstantInt>(V))
+    Bits = constantBits(CI, BT);
+  else if (const auto *CFP = dyn_cast_or_null<ConstantFP>(V))
+    Bits = constantBits(CFP->getValueAPF().bitcastToAPInt(), BT);
+  if (!Bits)
+    return std::nullopt;
+  return SPIRV::ConstantAssignment{BT, *Bits, LV, DVR->getExpression()};
+}
+
 // Collect distinct DILocations and DILocalVariables from LLVM IR.
 //
 // DILocations come from instruction debug locations and from the debug records
@@ -272,7 +367,8 @@ unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
 // still get a DebugLocalVariable.
 static void collectDebugLocationsAndLocalVariables(
     const Module &M, SetVector<const DILocation *> &Locations,
-    SetVector<const DILocalVariable *> &LVs) {
+    SetVector<const DILocalVariable *> &LVs,
+    SmallVectorImpl<SPIRV::ConstantAssignment> &Assignments) {
   for (const Function &F : M) {
     const DISubprogram *SP = F.getSubprogram();
     if (!SP)
@@ -287,8 +383,14 @@ static void collectDebugLocationsAndLocalVariables(
         if (const DILocation *DL = DR.getDebugLoc().get())
           Locations.insert(DL);
         if (const auto *DVR = dyn_cast<DbgVariableRecord>(&DR))
-          if (const DILocalVariable *LV = DVR->getVariable())
+          if (const DILocalVariable *LV = DVR->getVariable()) {
             LVs.insert(LV);
+            // The OpConstant has to precede every function body, so it is
+            // collected here and emitted with the rest of module-scope debug
+            // info.
+            if (auto CA = getConstantAssignment(DVR, LV))
+              Assignments.push_back(*CA);
+          }
       }
     }
   }
@@ -338,9 +440,14 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   DebugSourceRegByFileStr.clear();
   OpStringContentCache.clear();
   I32ConstantCache.clear();
+  ScalarTypeCache.clear();
+  ScalarConstantCache.clear();
+  ConstantValueRegs.clear();
+  ConstantAssignments.clear();
   DebugTypeFunctionCache.clear();
   DebugOperationCache.clear();
   DebugExpressionCache.clear();
+  ModuleScopeIds.clear();
   GlobalDIEmitted = false;
   GlobalNSDIEnabled = false;
   CurrentMAI = nullptr;
@@ -417,7 +524,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   }
 
   collectDebugLocationsAndLocalVariables(*M, UniqueDebugLocations,
-                                         LocalVariables);
+                                         LocalVariables, ConstantAssignments);
 
   // DILexicalBlock and DINamespace scopes are lowered to DebugLexicalBlock.
   // Collect them in parent-before-child order so they can be later emitted in a
@@ -426,6 +533,16 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
     collectLexicalBlockChain(S, LexicalBlocks);
 }
 
+// The emitted SPIR-V id of \p MI, or an invalid register when \p MI defines
+// no result.
+static MCRegister getResultId(const MachineInstr &MI,
+                              SPIRV::ModuleAnalysisInfo &MAI) {
+  if (MI.getNumOperands() == 0 || !MI.getOperand(0).isReg() ||
+      !MI.getOperand(0).isDef())
+    return MCRegister();
+  return MAI.getRegisterAlias(MI.getMF(), MI.getOperand(0).getReg());
+}
+
 void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
     const SPIRVSubtarget &ST, SPIRV::ModuleAnalysisInfo &MAI) {
   if (CompileUnits.empty())
@@ -441,6 +558,16 @@ void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
   // fresh result ID for it now; the same ID is used in emitExtInst() operands.
   if (!MAI.ExtInstSetMap.count(NSSet))
     MAI.ExtInstSetMap[NSSet] = MAI.getNextIDRegister();
+
+  // Types, constants and globals never reach endInstruction(), and this section
+  // is written before every function body, so record them as already emitted.
+  // handleTypeDeclOrConstant() keeps only one instruction per signature here
+  // and aliases every duplicate to its id (which identifies them).
+  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
+    MCRegister Id = getResultId(*MI, MAI);
+    if (Id.isValid())
+      ModuleScopeIds.insert(Id);
+  }
 }
 
 void SPIRVNonSemanticDebugHandler::emitMCInst(MCInst &Inst) {
@@ -515,6 +642,18 @@ MCRegister SPIRVNonSemanticDebugHandler::emitOpConstantI32(
   if (!Inserted)
     return It->second;
 
+  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
+    if (MI->getOpcode() != SPIRV::OpConstantI || MI->getNumOperands() < 3 ||
+        !MI->getOperand(2).isImm())
+      continue;
+    if (MAI.getRegisterAlias(MI->getMF(), MI->getOperand(1).getReg()) !=
+            I32TypeReg ||
+        static_cast<uint64_t>(MI->getOperand(2).getImm()) != Value)
+      continue;
+    It->second = MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+    return It->second;
+  }
+
   MCRegister Reg = MAI.getNextIDRegister();
   It->second = Reg;
   MCInst Inst;
@@ -585,6 +724,157 @@ MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeVoid(
   return Reg;
 }
 
+// Whether declaring \p BT's SPIR-V type would require a capability. The type
+// width determines the requirement: Int16, Int64, Float16 and Float64 in
+// SPIRVModuleAnalysis::collectReqs(). A boolean and a 32-bit scalar are free.
+static bool scalarTypeNeedsCapability(const DIBasicType *BT) {
+  if (isBooleanType(BT))
+    return false;
+  unsigned Width = BT->getSizeInBits();
+  return Width != 32;
+}
+
+MCRegister SPIRVNonSemanticDebugHandler::findOrEmitScalarType(
+    const DIBasicType *BT, SPIRV::ModuleAnalysisInfo &MAI) {
+  bool IsBool = isBooleanType(BT);
+  bool IsFloat = BT->getEncoding() == dwarf::DW_ATE_float;
+  unsigned Opcode = IsBool    ? SPIRV::OpTypeBool
+                    : IsFloat ? SPIRV::OpTypeFloat
+                              : SPIRV::OpTypeInt;
+  // Two DIBasicTypes can describe one SPIR-V type, and OpTypeBool has no
+  // width, so the key is the opcode and the width rather than the node.
+  int64_t Width = IsBool ? 0 : BT->getSizeInBits();
+
+  // OpTypeInt 32 0 is already owned by getOrEmitOpTypeInt32Reg(), which every
+  // line and column constant needs. Declaring a second one is a duplicate type
+  // declaration, which the validator rejects.
+  if (Opcode == SPIRV::OpTypeInt && Width == 32)
+    return getOrEmitOpTypeInt32Reg(MAI);
+
+  auto [CacheIt, Inserted] =
+      ScalarTypeCache.try_emplace({Opcode, Width}, MCRegister());
+  if (!Inserted)
+    return CacheIt->second;
+
+  // The backend writes every integer type with signedness 0, so a signed and
+  // an unsigned DIBasicType of the same width share one OpTypeInt.
+  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
+    if (MI->getOpcode() != Opcode)
+      continue;
+    if (IsBool || (MI->getOperand(1).getImm() == Width &&
+                   (IsFloat || MI->getOperand(2).getImm() == 0))) {
+      CacheIt->second =
+          MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+      return CacheIt->second;
+    }
+  }
+
+  // A non-semantic instruction "has no semantic impact, and can be safely
+  // removed from the module", so debug info must not make the module require
+  // something it otherwise would not. Declaring OpTypeInt 64 would force the
+  // module to declare Int64, and in Vulkan that ties it to a device feature,
+  // so we drop all the types that the module doesn't already have.
+  if (scalarTypeNeedsCapability(BT))
+    return MCRegister();
+
+  MCRegister Reg = MAI.getNextIDRegister();
+  MCInst Inst;
+  Inst.setOpcode(Opcode);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  if (!IsBool) {
+    Inst.addOperand(MCOperand::createImm(Width));
+    if (!IsFloat)
+      Inst.addOperand(MCOperand::createImm(0));
+  }
+  emitMCInst(Inst);
+  CacheIt->second = Reg;
+  return Reg;
+}
+
+// Whether the constant \p MI holds \p Value, read as two words when \p IsWide.
+static bool constantHasValue(const MachineInstr &MI, uint64_t Value,
+                             bool IsWide) {
+  unsigned Words = IsWide ? 2 : 1;
+  if (MI.getNumOperands() < 2 + Words)
+    return false;
+  uint64_t Found = 0;
+  for (unsigned I = 0; I < Words; ++I) {
+    if (!MI.getOperand(2 + I).isImm())
+      return false;
+    Found |= static_cast<uint64_t>(
+                 static_cast<uint32_t>(MI.getOperand(2 + I).getImm()))
+             << (32 * I);
+  }
+  return Found == Value;
+}
+
+MCRegister SPIRVNonSemanticDebugHandler::findModuleConstant(
+    unsigned Opcode, MCRegister TypeReg, uint64_t Value, bool IsBool,
+    bool IsWide, SPIRV::ModuleAnalysisInfo &MAI) {
+  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
+    if (MI->getOpcode() != Opcode || MI->getNumOperands() < 2)
+      continue;
+    if (MAI.getRegisterAlias(MI->getMF(), MI->getOperand(1).getReg()) !=
+        TypeReg)
+      continue;
+    // A boolean holds its value in its opcode, so the opcode and the type are
+    // the whole comparison.
+    if (!IsBool && !constantHasValue(*MI, Value, IsWide))
+      continue;
+    return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+  }
+  return MCRegister();
+}
+
+MCRegister SPIRVNonSemanticDebugHandler::findOrEmitScalarConstant(
+    const DIBasicType *BT, uint64_t Value, SPIRV::ModuleAnalysisInfo &MAI) {
+  bool IsBool = isBooleanType(BT);
+  bool IsFloat = BT->getEncoding() == dwarf::DW_ATE_float;
+  bool IsWide = !IsBool && BT->getSizeInBits() == 64;
+  MCRegister TypeReg = findOrEmitScalarType(BT, MAI);
+  if (!TypeReg.isValid())
+    return MCRegister();
+
+  unsigned Opcode =
+      IsBool    ? (Value ? SPIRV::OpConstantTrue : SPIRV::OpConstantFalse)
+      : IsFloat ? SPIRV::OpConstantF
+                : SPIRV::OpConstantI;
+
+  auto [CacheIt, Inserted] =
+      ScalarConstantCache.try_emplace({TypeReg.id(), Value}, MCRegister());
+  if (!Inserted)
+    return CacheIt->second;
+
+  MCRegister Found =
+      findModuleConstant(Opcode, TypeReg, Value, IsBool, IsWide, MAI);
+  if (Found.isValid()) {
+    CacheIt->second = Found;
+    return Found;
+  }
+
+  MCRegister Reg = MAI.getNextIDRegister();
+  MCInst Inst;
+  Inst.setOpcode(Opcode);
+  Inst.addOperand(MCOperand::createReg(Reg));
+  Inst.addOperand(MCOperand::createReg(TypeReg));
+  if (!IsBool) {
+    // OpConstant's Value is "the bit pattern for the constant. Types 32 bits
+    // wide or smaller take one word. Larger types take multiple words, with
+    // low-order words appearing first" (SPIR-V specification, OpConstant).
+    Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Lo_32(Value))));
+    if (IsWide) {
+      Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Hi_32(Value))));
+    } else if (BT->getSizeInBits() == 16) {
+      // A 16-bit float takes one word, the same as a 32-bit one, so the text
+      // printer needs the width to pick the right APFloat semantics.
+      Inst.setFlags(SPIRV::INST_PRINTER_WIDTH16);
+    }
+  }
+  emitMCInst(Inst);
+  CacheIt->second = Reg;
+  return Reg;
+}
+
 MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeInt32(
     SPIRV::ModuleAnalysisInfo &MAI) {
   for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
@@ -898,24 +1188,41 @@ mapDwarfOpToNonSemanticOp(uint64_t DwarfOp) {
   }
 }
 
-std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugOperation(
-    const DIExpression::ExprOperand &Op, MCRegister VoidTypeReg,
-    MCRegister I32TypeReg, MCRegister ExtInstSetReg,
-    SPIRV::ModuleAnalysisInfo &MAI) {
+// The DebugOperation operand values for \p Op. Each becomes an OpConstant of
+// OpTypeInt 32 0, so every argument must fit in 32 bits.
+static std::optional<SmallVector<uint32_t, 3>>
+mapExprOperand(const DIExpression::ExprOperand &Op) {
   std::optional<NonSemanticDebugOp> NSOp =
       mapDwarfOpToNonSemanticOp(Op.getOp());
   if (!NSOp)
     return std::nullopt;
 
-  SmallVector<uint32_t, 3> Key{static_cast<uint32_t>(*NSOp)};
+  SmallVector<uint32_t, 3> Operands{static_cast<uint32_t>(*NSOp)};
   for (unsigned I = 0, E = Op.getNumArgs(); I != E; ++I) {
     uint64_t Arg = Op.getArg(I);
     if (!isUInt<32>(Arg))
       return std::nullopt;
-    Key.push_back(static_cast<uint32_t>(Arg));
+    Operands.push_back(static_cast<uint32_t>(Arg));
   }
+  return Operands;
+}
+
+// Whether every operand of \p Expr maps to a DebugOperation.
+static bool isLowerableExpression(const DIExpression *Expr) {
+  return all_of(Expr->expr_ops(), [](const DIExpression::ExprOperand &Op) {
+    return mapExprOperand(Op).has_value();
+  });
+}
+
+std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugOperation(
+    const DIExpression::ExprOperand &Op, MCRegister VoidTypeReg,
+    MCRegister I32TypeReg, MCRegister ExtInstSetReg,
+    SPIRV::ModuleAnalysisInfo &MAI) {
+  std::optional<SmallVector<uint32_t, 3>> Key = mapExprOperand(Op);
+  if (!Key)
+    return std::nullopt;
 
-  auto [It, Inserted] = DebugOperationCache.try_emplace(std::move(Key));
+  auto [It, Inserted] = DebugOperationCache.try_emplace(std::move(*Key));
   if (!Inserted)
     return It->second;
 
@@ -1337,6 +1644,8 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   DebugFunctionDefinitionEmitted = false;
   LastLineMI = nullptr;
   LastScopeMI = nullptr;
+  Records.clear();
+  DomTree.reset();
 }
 
 void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
@@ -1360,6 +1669,8 @@ void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
   // OpVariable. If there are none, it is emitted after the entry OpLabel.
   LastFunctionOpVariable =
       findLastFunctionOpVariableDeclaration(*MF, *CurrentMAI);
+
+  analyzeDebugRecords(*MF);
 }
 
 void SPIRVNonSemanticDebugHandler::tryEmitDebugFunctionDefinition(
@@ -1402,14 +1713,14 @@ void SPIRVNonSemanticDebugHandler::beginInstruction(const MachineInstr *MI) {
   if (!DebugFunctionDefinitionEmitted)
     return;
 
+  emitAnalyzedRecords(MI);
+
   std::optional<const MachineInstr *> Target = resolveDebugLocTarget(MI);
   if (!Target)
     return;
 
-  emitDebugScopeForInstruction(*Target);
+  (void)emitDebugScopeForInstruction(*Target);
   emitDebugLineForInstruction(*Target);
-
-  emitDebugDeclare(MI);
 }
 
 // The register that holds the variable's address in \p MI, or std::nullopt
@@ -1448,15 +1759,13 @@ getDebugDeclareStorageReg(const MachineInstr &MI) {
   return LocReg;
 }
 
-void SPIRVNonSemanticDebugHandler::emitDebugDeclare(const MachineInstr *MI) {
+void SPIRVNonSemanticDebugHandler::emitDebugBinding(
+    SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
+    const MachineInstr *MI, MCRegister LocationReg) {
   assert(DebugFunctionDefinitionEmitted &&
          "DebugFunctionDefinition must be emitted");
   assert(CurrentMAI && "CurrentMAI must be set");
 
-  std::optional<Register> LocReg = getDebugDeclareStorageReg(*MI);
-  if (!LocReg)
-    return;
-
   auto VarRegOpt = lookupOptReg(DebugLocalVariableRegs, MI->getDebugVariable());
   if (!VarRegOpt)
     return;
@@ -1465,15 +1774,72 @@ void SPIRVNonSemanticDebugHandler::emitDebugDeclare(const MachineInstr *MI) {
   if (!ExprRegOpt)
     return;
 
-  SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
-  MCRegister StorageReg = MAI.getRegisterAlias(MI->getMF(), *LocReg);
-  if (!StorageReg.isValid())
+  // Both lookups come first, so only a record that will be emitted opens a
+  // region.
+  //
+  // emitDebugScopeForInstruction() fails when this record's scope has no
+  // emitted id. The record would then fall into whatever region is open and
+  // name a scope the variable is not in, so drop it.
+  if (!emitDebugScopeForInstruction(MI))
     return;
+  emitDebugLineForInstruction(MI);
 
+  SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
   MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
   MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
-  emitExtInst(SPIRV::NonSemanticExtInst::DebugDeclare, VoidTypeReg,
-              ExtInstSetReg, {*VarRegOpt, StorageReg, *ExprRegOpt}, MAI);
+  emitExtInst(Opcode, VoidTypeReg, ExtInstSetReg,
+              {*VarRegOpt, LocationReg, *ExprRegOpt}, MAI);
+}
+
+// The OpConstant id for the constant that \p MI assigns, or nullopt if \p MI
+// does not assign a constant collected by this backend.
+std::optional<MCRegister> SPIRVNonSemanticDebugHandler::getConstantValueReg(
+    const MachineInstr &MI) const {
+  if (!MI.isNonListDebugValue() || MI.isIndirectDebugValue())
+    return std::nullopt;
+
+  const MachineOperand &Value = MI.getDebugOperand(0);
+  if (!Value.isImm() && !Value.isCImm() && !Value.isFPImm())
+    return std::nullopt;
+
+  const DILocalVariable *LV = MI.getDebugVariable();
+  const DIBasicType *BT = LV ? stripToScalarType(LV->getType()) : nullptr;
+  if (!BT)
+    return std::nullopt;
+
+  uint64_t Bits;
+  if (Value.isImm())
+    Bits = constantBits(static_cast<uint64_t>(Value.getImm()), BT);
+  else if (Value.isCImm())
+    Bits = constantBits(Value.getCImm(), BT);
+  else
+    Bits = constantBits(Value.getFPImm()->getValueAPF().bitcastToAPInt(), BT);
+
+  auto It = ConstantValueRegs.find({BT, Bits});
+  if (It == ConstantValueRegs.end() || !It->second.isValid())
+    return std::nullopt;
+  return It->second;
+}
+
+static std::optional<Register> getDebugValueReg(const MachineInstr &MI) {
+  if (!MI.isNonListDebugValue() || MI.isIndirectDebugValue())
+    return std::nullopt;
+
+  const MachineOperand &Value = MI.getDebugOperand(0);
+  if (!Value.isReg())
+    return std::nullopt;
+
+  Register ValueReg = Value.getReg();
+  if (!ValueReg.isVirtual())
+    return std::nullopt;
+
+  const MachineInstr *Def = MI.getMF()->getRegInfo().getUniqueVRegDef(ValueReg);
+  if (!Def || Def->isPseudo() || Def->isMetaInstruction() ||
+      Def->getNumOperands() == 0 || !Def->getOperand(0).isReg() ||
+      !Def->getOperand(0).isDef() || Def->getOperand(0).getReg() != ValueReg)
+    return std::nullopt;
+
+  return ValueReg;
 }
 
 static bool isMergeInstruction(unsigned Opcode) {
@@ -1481,9 +1847,14 @@ static bool isMergeInstruction(unsigned Opcode) {
          Opcode == SPIRV::OpLoopControlINTEL;
 }
 
+// Whether \p MI becomes a SPIR-V instruction in the output.
+static bool isEmitted(const MachineInstr *MI, SPIRV::ModuleAnalysisInfo &MAI) {
+  return !MI->isMetaInstruction() && !MAI.getSkipEmission(MI);
+}
+
 static bool isDebugLocTarget(const MachineInstr *MI,
                              SPIRV::ModuleAnalysisInfo &MAI) {
-  if (MAI.getSkipEmission(MI))
+  if (!isEmitted(MI, MAI))
     return false;
   switch (MI->getOpcode()) {
   case SPIRV::OpFunction:
@@ -1503,7 +1874,7 @@ findAdjacentEmittedInstruction(const MachineInstr *MI,
   for (const MachineInstr *Adj = Forward ? MI->getNextNode()
                                          : MI->getPrevNode();
        Adj; Adj = Forward ? Adj->getNextNode() : Adj->getPrevNode()) {
-    if (MAI.getSkipEmission(Adj))
+    if (!isEmitted(Adj, MAI))
       continue;
     return Adj;
   }
@@ -1533,12 +1904,104 @@ SPIRVNonSemanticDebugHandler::resolveDebugLocTarget(const MachineInstr *MI) {
     // above skips it.
     MI = findAdjacentEmittedInstruction(MI, MAI, true);
     assert(MI && "Merge instruction must be followed by a terminator");
+    // The assert is compiled out in a release build, and returning MI there
+    // would give an engaged optional holding null.
+    if (!MI)
+      return std::nullopt;
   }
 
   return MI;
 }
 
-void SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
+void SPIRVNonSemanticDebugHandler::resolveDebugRecord(
+    const MachineInstr &MI, const MachineInstr *LastEmitted) {
+  SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
+  const MachineFunction &MF = *MI.getMF();
+
+  // A record sits at its own position, except in a merge region: nothing may
+  // be emitted between a merge and its terminator, so it belongs ahead of the
+  // merge.
+  const MachineInstr *Anchor =
+      LastEmitted && isMergeInstruction(LastEmitted->getOpcode()) ? LastEmitted
+                                                                  : &MI;
+
+  // A #dbg_declare reaches Machine IR as an indirect DBG_VALUE. Its storage is
+  // an OpVariable in the entry block, so it is in the output by the time any
+  // record naming it is reached.
+  if (std::optional<Register> LocReg = getDebugDeclareStorageReg(MI)) {
+    MCRegister StorageReg = MAI.getRegisterAlias(&MF, *LocReg);
+    if (StorageReg.isValid())
+      placeRecord(SPIRV::NonSemanticExtInst::DebugDeclare, MI, StorageReg,
+                  Anchor);
+    return;
+  }
+
+  // A constant assignment names the id emitted for it at module scope, which
+  // every function body follows.
+  if (std::optional<MCRegister> ConstReg = getConstantValueReg(MI)) {
+    placeRecord(SPIRV::NonSemanticExtInst::DebugValue, MI, *ConstReg, Anchor);
+    return;
+  }
+
+  std::optional<Register> ValReg = getDebugValueReg(MI);
+  if (!ValReg)
+    return;
+  MCRegister ValueReg = MAI.getRegisterAlias(&MF, *ValReg);
+  if (!ValueReg.isValid())
+    return;
+
+  // NonSemantic.Shader.DebugInfo states that "Forward references are not
+  // allowed, to be compliant with SPV_KHR_non_semantic_info", so the id has to
+  // be in the output already. A module section precedes every function body.
+  //
+  // For anything else, SPIR-V requires a definition in a function block to
+  // dominate a non-phi use, and requires a block to precede every block it
+  // dominates, so a dominating definition is also one already printed. A
+  // record naming a definition that does not dominate it is dropped.
+  const MachineInstr *Def = MF.getRegInfo().getUniqueVRegDef(*ValReg);
+  if (ModuleScopeIds.contains(ValueReg) ||
+      (isEmitted(Def, MAI) && DomTree->dominates(Def, &MI)))
+    placeRecord(SPIRV::NonSemanticExtInst::DebugValue, MI, ValueReg, Anchor);
+}
+
+void SPIRVNonSemanticDebugHandler::analyzeDebugRecords(
+    const MachineFunction &MF) {
+  assert(CurrentMAI && "CurrentMAI must be set");
+
+  for (const MachineBasicBlock &MBB : MF) {
+    const MachineInstr *LastEmitted = nullptr;
+    for (const MachineInstr &MI : MBB) {
+      if (MI.isDebugValueLike()) {
+        if (!DomTree) {
+          DomTree = std::make_unique<MachineDominatorTree>();
+          DomTree->recalculate(const_cast<MachineFunction &>(MF));
+        }
+        resolveDebugRecord(MI, LastEmitted);
+      }
+
+      if (isEmitted(&MI, *CurrentMAI))
+        LastEmitted = &MI;
+    }
+  }
+}
+
+void SPIRVNonSemanticDebugHandler::placeRecord(
+    SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
+    const MachineInstr &MI, MCRegister LocationReg,
+    const MachineInstr *Anchor) {
+  Records[Anchor].push_back({&MI, Opcode, LocationReg});
+}
+
+void SPIRVNonSemanticDebugHandler::emitAnalyzedRecords(
+    const MachineInstr *Anchor) {
+  auto It = Records.find(Anchor);
+  if (It == Records.end())
+    return;
+  for (const DebugRecord &R : It->second)
+    emitDebugBinding(R.Opcode, R.MI, R.LocationReg);
+}
+
+bool SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
     const MachineInstr *MI) {
   assert(DebugFunctionDefinitionEmitted &&
          "DebugFunctionDefinition must be emitted");
@@ -1563,7 +2026,7 @@ void SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
                   ExtInstSetReg, {}, MAI);
       LastScopeMI = nullptr;
     }
-    return;
+    return true;
   }
 
   const DIScope *CurScope = CurDL->getScope();
@@ -1573,19 +2036,19 @@ void SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
     const DILocation *LastDL = LastScopeMI->getDebugLoc().get();
     if (LastDL->getScope() == CurScope &&
         LastDL->getInlinedAt() == CurInlinedAt)
-      return;
+      return true;
   }
 
   auto CurScopeRegOpt = resolveScope(CurScope);
   if (!CurScopeRegOpt)
-    return;
+    return false;
 
   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;
+      return false;
     Ops.push_back(InlinedReg);
   }
 
@@ -1594,6 +2057,7 @@ void SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
               Ops, MAI);
 
   LastScopeMI = MI;
+  return true;
 }
 
 void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
@@ -1663,10 +2127,10 @@ void SPIRVNonSemanticDebugHandler::endInstruction() {
   const MachineInstr *MI = CurMI;
   CurMI = nullptr;
 
-  if (!MI || !GlobalNSDIEnabled || DebugFunctionDefinitionEmitted || !CurrentMF)
+  if (!MI || !GlobalNSDIEnabled || !CurrentMF)
     return;
 
-  if (MI != LastFunctionOpVariable)
+  if (DebugFunctionDefinitionEmitted || MI != LastFunctionOpVariable)
     return;
 
   // If this is the last function-level OpVariable, emit the
@@ -1946,6 +2410,21 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
     if (const DILocation *IA = DL->getInlinedAt())
       getOrEmitDebugInlinedAt(IA, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
 
+  // Both maps are populated by now, so repeating the lookups that
+  // emitDebugBinding() performs emits a type and a constant only for an
+  // assignment that will reach a DebugValue.
+  for (const SPIRV::ConstantAssignment &CA : ConstantAssignments) {
+    if (!DebugLocalVariableRegs.contains(CA.Var) ||
+        !DebugExpressionRegs.contains(CA.Expr))
+      continue;
+    auto [It, Inserted] =
+        ConstantValueRegs.try_emplace({CA.Type, CA.Bits}, MCRegister());
+    if (Inserted)
+      It->second = findOrEmitScalarConstant(CA.Type, CA.Bits, MAI);
+    if (!It->second.isValid())
+      ConstantValueRegs.erase(It);
+  }
+
   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 16e4dd3cb8e3b..30c48afeafc4f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -21,11 +21,13 @@
 #include "MCTargetDesc/SPIRVBaseInfo.h"
 #include "SPIRVModuleAnalysis.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/CodeGen/DebugHandlerBase.h"
+#include "llvm/CodeGen/MachineDominators.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCRegister.h"
@@ -36,6 +38,16 @@ namespace llvm {
 class GlobalVariable;
 class SPIRVSubtarget;
 
+namespace SPIRV {
+/// A #dbg_value that assigns a literal, as collected from LLVM IR.
+struct ConstantAssignment {
+  const DIBasicType *Type; ///< The variable's type, stripped to a scalar.
+  uint64_t Bits;           ///< The value, narrowed to that type.
+  const DILocalVariable *Var;
+  const DIExpression *Expr;
+};
+} // namespace SPIRV
+
 /// AsmPrinter handler that emits NonSemantic.Shader.DebugInfo.100 (NSDI)
 /// instructions for the SPIR-V backend. Registered with SPIRVAsmPrinter when
 /// the module contains debug info (llvm.dbg.cu).
@@ -46,10 +58,13 @@ class SPIRVSubtarget;
 /// - emitNonSemanticDebugStrings() emits NSDI OpStrings in section 7.
 /// - emitNonSemanticGlobalDebugInfo() emits module-scope NSDI and sets
 ///   GlobalNSDIEnabled.
-/// - beginFunctionImpl() prepares per-function DebugFunctionDefinition state.
+/// - beginFunctionImpl() prepares per-function DebugFunctionDefinition state
+///   and runs analyzeDebugRecords() over the whole function.
 /// - endInstruction() emits DebugFunctionDefinition after the last function-
 ///   level OpVariable; SPIRVAsmPrinter calls notifyEntryLabelEmitted() after
 ///   the synthesized entry OpLabel when there are no OpVariables.
+/// - beginInstruction() emits the DebugScope and DebugLine for the instruction
+///   being printed, and the DebugDeclare and DebugValue anchored on it.
 /// - endFunctionImpl() resets per-function state.
 class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   static constexpr unsigned NSSet = static_cast<unsigned>(
@@ -158,6 +173,27 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // duplicate OpConstant instructions for the same integer value.
   DenseMap<uint32_t, MCRegister> I32ConstantCache;
 
+  // Result id per SPIR-V scalar type this handler needed, keyed by opcode and
+  // width. MB_TypeConstVars holds only what module analysis collected, so a
+  // type emitted here has to be remembered separately to stay unique.
+  DenseMap<std::pair<unsigned, int64_t>, MCRegister> ScalarTypeCache;
+
+  // Result id per scalar constant, keyed by its type's id and its bits.
+  // MB_TypeConstVars does not hold what this handler emitted either, and
+  // keying on the type lets two DIBasicTypes describing it share a constant.
+  DenseMap<std::pair<unsigned, uint64_t>, MCRegister> ScalarConstantCache;
+
+  // Constant assignments collected in beginModule(). A record only earns an
+  // OpConstant when it can produce a DebugValue, so these are filtered rather
+  // than emitted wholesale.
+  SmallVector<SPIRV::ConstantAssignment> ConstantAssignments;
+
+  // Result id per collected constant, keyed by its type and bits. Filled
+  // during module-scope emission, since an OpConstant has to precede every
+  // function body that names it.
+  DenseMap<std::pair<const DIBasicType *, uint64_t>, MCRegister>
+      ConstantValueRegs;
+
   // Cache of already-emitted DebugTypeFunction instructions, keyed by operand
   // ids (flags, return type, parameters).
   DenseMap<SmallVector<MCRegister, 8>, MCRegister> DebugTypeFunctionCache;
@@ -198,6 +234,27 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   const MachineInstr *LastLineMI = nullptr;
   const MachineInstr *LastScopeMI = nullptr;
 
+  // Result ids in MB_TypeConstVars, which precedes every function body. Filled
+  // once per module in prepareModuleOutput().
+  DenseSet<MCRegister> ModuleScopeIds;
+
+  /// A DebugDeclare or DebugValue waiting for its anchor to be printed.
+  struct DebugRecord {
+    const MachineInstr *MI; ///< The DBG_VALUE this came from.
+    SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode;
+    MCRegister LocationReg; ///< The Variable or Value operand.
+  };
+
+  // A record can name a function-local definition only when that definition
+  // dominates the record. Requiring MachineDominatorTreeWrapperPass in
+  // SPIRVAsmPrinter::getAnalysisUsage() would build a tree for every SPIR-V
+  // compilation, so this one is built on the first record of each function.
+  std::unique_ptr<MachineDominatorTree> DomTree;
+
+  // Records to emit before their anchor is printed, built once per function by
+  // analyzeDebugRecords() and only read afterwards.
+  DenseMap<const MachineInstr *, SmallVector<DebugRecord>> Records;
+
 public:
   explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);
 
@@ -272,7 +329,12 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   std::optional<const MachineInstr *>
   resolveDebugLocTarget(const MachineInstr *MI);
 
-  void emitDebugScopeForInstruction(const MachineInstr *MI);
+  /// Open the DebugScope region \p MI belongs to, closing the previous one.
+  ///
+  /// \returns False when module-scope emission produced no DebugScope for that
+  /// scope, or no DebugInlinedAt for its chain, so the open region is not
+  /// \p MI's.
+  bool emitDebugScopeForInstruction(const MachineInstr *MI);
   void emitDebugLineForInstruction(const MachineInstr *MI);
   void preparePerFunctionDebug(const MachineFunction *MF);
   void tryEmitDebugFunctionDefinition(SPIRV::ModuleAnalysisInfo &MAI);
@@ -462,15 +524,60 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
                                                 MCRegister ExtInstSetReg,
                                                 SPIRV::ModuleAnalysisInfo &MAI);
 
-  /// Emit \c DebugDeclare for \p MI when it is an indirect \c DBG_VALUE whose
-  /// location register is defined by \c OpVariable, which is the shape
-  /// \c IRTranslator gives a \c #dbg_declare on storage the backend kept.
+  /// Fill \c Records from the debug records in \p MF.
+  ///
+  /// Where a record may legally sit is a property of the whole function, and
+  /// emission is unbuffered, so it is settled here rather than while printing.
+  void analyzeDebugRecords(const MachineFunction &MF);
+
+  /// Place the single record \p MI. \p LastEmitted is the nearest preceding
+  /// instruction in this block that reaches the output, or null when there is
+  /// none.
   ///
-  /// Emits nothing when \p MI is not such a declare, when the variable has no
-  /// \c DebugLocalVariable, when the expression was not lowered, or when the
-  /// storage is anything other than an \c OpVariable (an access chain, a
-  /// constant, a function parameter, or a dead alloca with no def at all).
-  void emitDebugDeclare(const MachineInstr *MI);
+  /// Places nothing when this backend cannot name the record: an unavailable
+  /// location, a declare whose storage is not an \c OpVariable, a constant
+  /// with no module-scope id, a physical register, a variadic value, an
+  /// instruction reference, or a definition that does not dominate \p MI.
+  void resolveDebugRecord(const MachineInstr &MI,
+                          const MachineInstr *LastEmitted);
+
+  /// Store \p MI on \p Anchor, to emit before \p Anchor is printed.
+  void placeRecord(SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
+                   const MachineInstr &MI, MCRegister LocationReg,
+                   const MachineInstr *Anchor);
+
+  /// Emit the records \c analyzeDebugRecords() anchored on \p Anchor.
+  void emitAnalyzedRecords(const MachineInstr *Anchor);
+
+  /// The module-scope constant \p MI assigns, or \c std::nullopt when \p MI
+  /// is not a constant assignment this backend collected a constant for.
+  std::optional<MCRegister> getConstantValueReg(const MachineInstr &MI) const;
+
+  /// Result id of the constant \p Opcode of type \p TypeReg holding
+  /// \p Value in \c MB_TypeConstVars, or an invalid register when the module
+  /// does not already define it.
+  MCRegister findModuleConstant(unsigned Opcode, MCRegister TypeReg,
+                                uint64_t Value, bool IsBool, bool IsWide,
+                                SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Result id of the SPIR-V type for \p BT, reusing one from
+  /// \c MB_TypeConstVars when the module already defines it. Invalid when
+  /// declaring it would require a capability the module does not have, since
+  /// debug info must not change what the module requires.
+  MCRegister findOrEmitScalarType(const DIBasicType *BT,
+                                  SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Result id of the constant \p Value of type \p BT, reusing one from
+  /// \c MB_TypeConstVars when the module already defines it.
+  MCRegister findOrEmitScalarConstant(const DIBasicType *BT, uint64_t Value,
+                                      SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Emit \p Opcode naming \p MI's variable, \p LocationReg and \p MI's
+  /// expression, after \p MI's own \c DebugScope and \c DebugLine. Emits
+  /// nothing when \p MI's variable has no \c DebugLocalVariable, its
+  /// expression no \c DebugExpression, or its scope no id.
+  void emitDebugBinding(SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
+                        const MachineInstr *MI, MCRegister LocationReg);
 
   /// Emit \c DebugTypeVector for the vector composite type \p VT.
   ///
diff --git a/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
index 8136b054f007b..3def8d190dddc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
@@ -523,6 +523,11 @@ static void generateAssignType(MachineInstr &MI, Register ResultRegister,
                     << " with type: " << *ResultType);
   MachineIRBuilder MIB(MI);
   updateRegType(ResultRegister, nullptr, ResultType, GR, MIB, MRI);
+  // updateRegType() moves the insert point past any debug record following the
+  // definition. Leaving it there puts the ASSIGN_TYPE, which takes over the
+  // def below, after a DBG_VALUE that names the register, so the record would
+  // precede its own definition. Pin the insert point back.
+  MIB.setInsertPt(*MI.getParent(), std::next(MI.getIterator()));
 
   // Tablegen definition assumes SPIRV::ASSIGN_TYPE pseudo-instruction is
   // present after each auto-folded instruction to take a type reference
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-declare-dbg-value-variadic.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-declare-dbg-value-variadic.ll
index ee22e024a2397..28d6c79e5426d 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-declare-dbg-value-variadic.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-declare-dbg-value-variadic.ll
@@ -1,10 +1,10 @@
-; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not=DebugDeclare --implicit-check-not=DebugExpression
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not=DebugDeclare --implicit-check-not=DebugExpression --implicit-check-not=DebugValue
 ; 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 %}
 
 ; A variadic #dbg_value. IRTranslator cannot lower the DIArgList.
 ; It emits DBG_VALUE $noreg, 0, which is an indirect DBG_VALUE just like a
 ; declare is.
-; The test checks that it must not become a DebugDeclare.
+; The test checks that it becomes neither DebugDeclare nor DebugValue.
 
 ; Clang seem to emit no DIArgList. The optimizer does,
 ; when it deletes a dead binary operation and rewrites the variable as an
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-declare.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-declare.ll
index fd8fa157c2466..0cb25fc35f958 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-declare.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-declare.ll
@@ -1,4 +1,4 @@
-; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not=DebugValue
 ; 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 %}
 
 ; A #dbg_declare on an alloca that survives to MIR: the parameter copy and a
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll
index 8a186fa818dc5..b326293ebb09c 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll
@@ -1,4 +1,6 @@
-; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; asm-verbose=0 keeps AsmPrinter's ;DEBUG_VALUE: comment out of the output so
+; the CHECK-NEXT chain tracks SPIR-V instructions only.
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --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.
@@ -21,16 +23,22 @@
 ; 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-DAG: [[NAME_X:%[0-9]+]] = OpString "x"
+; CHECK-DAG: [[VAR_X:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[NAME_X]]
+; CHECK-DAG: [[EXPR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
 
 ; CHECK: [[IA:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugInlinedAt [[V6]] [[DF_CALLER]]
 
-; CHECK: [[FN:%[0-9]+]] = OpFunction {{.*}} ; -- Begin function caller
+; The binding for the inlined callee's variable must sit inside the DebugScope
+; that carries the DebugInlinedAt, not the caller's scope that follows it.
+; CHECK: [[FN:%[0-9]+]] = OpFunction
 ; 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: [[MUL:%[0-9]+]] = OpIMul
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugValue [[VAR_X]] [[MUL]] [[EXPR]]{{ *$}}
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF_CALLER]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]]
 ; CHECK-NEXT: OpIAdd
@@ -43,6 +51,7 @@ target triple = "spirv64-unknown-unknown"
 define spir_func i32 @caller(i32 %y) !dbg !11 {
 entry:
   %mul.i = mul nsw i32 %y, 3, !dbg !24
+    #dbg_value(i32 %mul.i, !21, !DIExpression(), !24)
   %sub = add nsw i32 %mul.i, -5, !dbg !25
   ret i32 %sub, !dbg !26
 }
@@ -61,6 +70,7 @@ entry:
 !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)
+!21 = !DILocalVariable(name: "x", arg: 1, scope: !20, file: !1, line: 1, type: !14)
 
 !23 = distinct !DILocation(line: 6, column: 11, scope: !11)
 !24 = !DILocation(line: 2, column: 12, scope: !20, inlinedAt: !23)
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 13d7b048b58b9..1cf40b32bfba1 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
@@ -1,4 +1,7 @@
-; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; asm-verbose=0 keeps AsmPrinter's ;DEBUG_VALUE: comments out of the output, so
+; the CHECK-NEXT chain around the merge asserts adjacency of SPIR-V
+; instructions rather than of text this backend does not own.
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --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 for the branch is emitted before OpSelectionMerge.
@@ -10,10 +13,15 @@
 ; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}src{{[/\\]}}debug-line-selection-merge.c"
 ; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
 ; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[CONDVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable
+; CHECK-DAG: [[EXPR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
 ; 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-DAG: [[V20:%[0-9]+]] = OpConstant [[I32]] 20{{$}}
+; CHECK-DAG: [[V21:%[0-9]+]] = OpConstant [[I32]] 21{{$}}
 ; CHECK-DAG: [[V9:%[0-9]+]] = OpConstant [[I32]] 9{{$}}
 ; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
 ; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
@@ -28,7 +36,10 @@
 ; 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: [[CMP:%[0-9]+]] = OpSLessThan
+; A DebugValue between the merge and branch in MIR is emitted before the merge.
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugValue [[CONDVAR]] [[CMP]] [[EXPR]]{{ *$}}
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V99]] [[V99]] [[V50]] [[V51]]
 ; CHECK-NEXT: OpSelectionMerge
 ; CHECK-NEXT: OpBranchConditional
@@ -45,6 +56,13 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DF]]
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V5]] [[V6]]
 ; CHECK-NEXT: OpIAdd
+; CHECK-NEXT: [[THENCOND:%[0-9]+]] = OpSLessThan
+; Rebinding the variable in one successor must preserve the entry binding on
+; the other successor.
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V7]] [[V7]] [[V20]] [[V21]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugValue [[CONDVAR]] [[THENCOND]] [[EXPR]]{{ *$}}
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V5]] [[V6]]
+; CHECK-NEXT: OpSelect
 ; CHECK-NEXT: OpBranch
 
 ; merge
@@ -61,10 +79,16 @@ define spir_func i32 @if_else(i32 %x) !dbg !5 {
 entry:
   %cmp = icmp slt i32 %x, 0, !dbg !8
   call void @llvm.spv.selection.merge.p0(ptr blockaddress(@if_else, %merge), i32 0), !dbg !14
+    #dbg_value(i1 %cmp, !16, !DIExpression(), !14)
   br i1 %cmp, label %then, label %else, !dbg !13
 
 then:
-  %t = add i32 %x, 1, !dbg !9
+  %sum = add i32 %x, 1, !dbg !9
+  %thencond = icmp slt i32 %sum, 0, !dbg !9
+    #dbg_value(i1 %thencond, !16, !DIExpression(), !17)
+  ; %thencond needs a non-debug use, or the selector drops it as dead and the
+  ; rebind this test is about never reaches the output.
+  %t = select i1 %thencond, i32 %sum, i32 %x, !dbg !9
   br label %merge, !dbg !9
 
 else:
@@ -98,3 +122,8 @@ declare void @llvm.spv.selection.merge.p0(ptr, i32 immarg)
 !12 = !DILocation(line: 9, column: 3, scope: !5)
 !13 = !DILocation(line: 99, column: 50, scope: !5)
 !14 = !DILocation(line: 7, column: 1, scope: !5)
+!15 = !DIBasicType(name: "bool", size: 1, encoding: DW_ATE_boolean)
+!16 = !DILocalVariable(name: "condition", scope: !5, file: !1, line: 7, type: !15)
+; Column 20 keeps this record's DebugLine operands clear of the OpConstant 1
+; the IR's own "add i32 %x, 1" already needs.
+!17 = !DILocation(line: 7, column: 20, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-local-variable-dbg-value.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-local-variable-dbg-value.ll
index 91699d80cbe54..c2f7bcfa80f34 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-local-variable-dbg-value.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-local-variable-dbg-value.ll
@@ -3,8 +3,8 @@
 
 ; Collect DILocalVariable from a #dbg_value record. No retainedNodes.
 ;
-; The record is a plain #dbg_value, so it lowers to a direct DBG_VALUE and gets
-; no DebugDeclare.
+; The record is a plain #dbg_value, so it lowers to a direct DBG_VALUE and
+; becomes DebugValue rather than DebugDeclare.
 
 ; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
 ; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
@@ -19,7 +19,11 @@
 ; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
 ; CHECK-DAG: [[INT:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugTypeBasic [[INTNAME]] {{.*}} [[C0]]
 ; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
-; CHECK: OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[XNAME]] [[INT]] [[DS]] [[C8]] [[C0]] [[DF]] [[C0]] [[C1]]
+; CHECK-DAG: [[XVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[XNAME]] [[INT]] [[DS]] [[C8]] [[C0]] [[DF]] [[C0]] [[C1]]
+; CHECK-DAG: [[EXPR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
+; CHECK: [[XVAL:%[0-9]+]] = OpFunctionParameter [[I32T]]
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[XVAR]] [[XVAL]] [[EXPR]]{{ *$}}
 
 target triple = "spirv64-unknown-unknown"
 
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
index 4f6a5f930af7d..8a34b4f17e2d2 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
@@ -1,15 +1,19 @@
 ; 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: llc --verify-machineinstrs -O0 -mtriple=spirv64-amd-amdhsa %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
 ; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=CHECK-OPTION
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=UNIQUE
 ; 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 %}
 
-; Anchor on OpTypeVoid rather than OpTypeInt 32 0. The module may already contain
-; OpConstant i32 N instructions (e.g. for array dimensions) before the NSDI section,
-; which have the same pattern as the NSDI-emitted constants. Anchoring after
-; OpTypeVoid (emitted immediately before the NSDI constants) ensures the DAG group
-; binds [[i32_8]] etc. to the NSDI constants rather than earlier module constants.
+; UNIQUE: [[i32:%[0-9]+]] = OpTypeInt 32 0
+; UNIQUE-COUNT-1: OpConstant [[i32]] 8{{$}}
+; UNIQUE-NOT: OpConstant [[i32]] 8{{$}}
+
+; Anchor the DAG group on OpTypeInt 32 0. A module constant with the same type
+; and value is now reused rather than emitted a second time for the NSDI
+; section, so there is one OpConstant i32 N to bind and it can sit anywhere
+; after the type. The UNIQUE prefix above asserts that reuse: a DAG group only
+; proves a match exists, so it would pass against a duplicate.
 ; CHECK-SPIRV:	[[i32type:%[0-9]+]] = OpTypeInt 32 0
-; CHECK-SPIRV:	[[void_type:%[0-9]+]] = OpTypeVoid
 ; CHECK-SPIRV-DAG:	[[i32_8:%[0-9]+]] = OpConstant [[i32type]] 8{{$}}
 ; CHECK-SPIRV-DAG:	[[i32_0:%[0-9]+]] = OpConstant [[i32type]] 0{{$}}
 ; CHECK-SPIRV-DAG:	[[i32_5:%[0-9]+]] = OpConstant [[i32type]] 5{{$}}
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
new file mode 100644
index 0000000000000..464306463fd10
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
@@ -0,0 +1,108 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=UNIQUE
+; 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 %}
+
+; A narrow or wide constant can be named when the semantic module already
+; defines its scalar type. The function signature below requires all four
+; optional-capability types, so the debug handler must reuse each type and
+; emit only the constants needed by the DebugValue records.
+;
+; The signature uses no 32-bit integer, so this is the one module in the suite
+; where the handler creates OpTypeInt 32 0 rather than finding it. That type
+; has a second owner, the line and column constants every DebugLine needs, and
+; two declarations of it are a duplicate the validator rejects.
+
+; CHECK-DAG: OpCapability Int16
+; CHECK-DAG: OpCapability Int64
+; CHECK-DAG: OpCapability Float16
+; CHECK-DAG: OpCapability Float64
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[I16:%[0-9]+]] = OpTypeInt 16 0
+; CHECK-DAG: [[I64:%[0-9]+]] = OpTypeInt 64 0
+; CHECK-DAG: [[F16:%[0-9]+]] = OpTypeFloat 16
+; CHECK-DAG: [[F64:%[0-9]+]] = OpTypeFloat 64
+; CHECK-DAG: [[I16C:%[0-9]+]] = OpConstant [[I16]] 7{{ *$}}
+; CHECK-DAG: [[I64C:%[0-9]+]] = OpConstant [[I64]] 1234605616436508552{{ *$}}
+; A half prints as its raw bit pattern and a double prints as a value, so
+; pinning both covers the width the printer is told and the two-word
+; reconstruction that 1.0 needs.
+; CHECK-DAG: [[F16C:%[0-9]+]] = OpConstant [[F16]] 15360{{ *$}}
+; CHECK-DAG: [[F64C:%[0-9]+]] = OpConstant [[F64]] 1{{ *$}}
+; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[I32C:%[0-9]+]] = OpConstant [[I32]] 987654321{{ *$}}
+; CHECK-DAG: [[I16NAME:%[0-9]+]] = OpString "i16_constant"
+; CHECK-DAG: [[I64NAME:%[0-9]+]] = OpString "i64_constant"
+; CHECK-DAG: [[F16NAME:%[0-9]+]] = OpString "half_constant"
+; CHECK-DAG: [[F64NAME:%[0-9]+]] = OpString "double_constant"
+; CHECK-DAG: [[I32NAME:%[0-9]+]] = OpString "i32_constant"
+; CHECK-DAG: [[I16VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[I16NAME]]
+; CHECK-DAG: [[I64VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[I64NAME]]
+; CHECK-DAG: [[F16VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[F16NAME]]
+; CHECK-DAG: [[F64VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[F64NAME]]
+; CHECK-DAG: [[I32VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[I32NAME]]
+
+; CHECK: OpFunction
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[I16VAR]] [[I16C]]
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[I64VAR]] [[I64C]]
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[F16VAR]] [[F16C]]
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[F64VAR]] [[F64C]]
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[I32VAR]] [[I32C]]
+; Returning the i64 literal makes semantic code create it too. DebugValue and
+; OpReturnValue naming one id exercises module-constant reuse and the two-word
+; comparison of a 64-bit value.
+; CHECK: OpReturnValue [[I64C]]
+
+; The handler must reuse each existing type rather than declare a second copy.
+; This needs its own FileCheck pass: a bare CHECK-NOT would close the CHECK-DAG
+; group above it, and the OpStrings that group matches come earlier in the
+; output than the types.
+;
+; One DAG group then five negatives, so every negative region starts after the
+; whole semantic type block and reaches end of file. Pairing each positive with
+; its own negative would end each region at the next positive, before the point
+; where emitNonSemanticGlobalDebugInfo() would add a duplicate.
+; UNIQUE-DAG: OpTypeInt 16 0
+; UNIQUE-DAG: OpTypeInt 64 0
+; UNIQUE-DAG: OpTypeFloat 16
+; UNIQUE-DAG: OpTypeFloat 64
+; UNIQUE-DAG: OpTypeInt 32 0
+; UNIQUE-NOT: OpTypeInt 16 0
+; UNIQUE-NOT: OpTypeInt 64 0
+; UNIQUE-NOT: OpTypeFloat 16
+; UNIQUE-NOT: OpTypeFloat 64
+; UNIQUE-NOT: OpTypeInt 32 0
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i64 @existing_types(i16 %s, i64 %l, half %h, double %d) !dbg !5 {
+entry:
+    #dbg_value(i16 7, !11, !DIExpression(), !15)
+    #dbg_value(i64 1234605616436508552, !12, !DIExpression(), !15)
+    #dbg_value(half 0xH3C00, !13, !DIExpression(), !15)
+    #dbg_value(double 1.000000e+00, !14, !DIExpression(), !15)
+    #dbg_value(i32 987654321, !17, !DIExpression(), !15)
+  ret i64 1234605616436508552, !dbg !15
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-value-constant-existing-type.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !6)
+!5 = distinct !DISubprogram(name: "existing_types", linkageName: "existing_types", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!6 = !{!8, !7, !8, !9, !10}
+!7 = !DIBasicType(name: "short", size: 16, encoding: DW_ATE_signed)
+!8 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+!9 = !DIBasicType(name: "half", size: 16, encoding: DW_ATE_float)
+!10 = !DIBasicType(name: "double", size: 64, encoding: DW_ATE_float)
+!11 = !DILocalVariable(name: "i16_constant", scope: !5, file: !1, line: 2, type: !7)
+!12 = !DILocalVariable(name: "i64_constant", scope: !5, file: !1, line: 3, type: !8)
+!13 = !DILocalVariable(name: "half_constant", scope: !5, file: !1, line: 4, type: !9)
+!14 = !DILocalVariable(name: "double_constant", scope: !5, file: !1, line: 5, type: !10)
+!15 = !DILocation(line: 6, column: 3, scope: !5)
+!16 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!17 = !DILocalVariable(name: "i32_constant", scope: !5, file: !1, line: 7, type: !16)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
new file mode 100644
index 0000000000000..0afb7cf28e0b4
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
@@ -0,0 +1,154 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not="OpCapability Int16" --implicit-check-not="OpCapability Int64" --implicit-check-not="OpCapability Float16" --implicit-check-not="OpCapability Float64"
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=UNIQUE
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=DROPPED
+; 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 %}
+
+; A constant assignment names an OpConstant, which satisfies DebugValue's
+; requirement that its Value operand be the result id of a non-debug
+; instruction. Machine IR stores the constant as an untyped immediate, so the
+; type comes from the variable and the id is emitted at module scope, which
+; every function body follows.
+;
+; A boolean is spelled OpConstantTrue or OpConstantFalse of OpTypeBool rather
+; than OpConstant, and both share the one OpTypeBool.
+; An omitted Boolean size must still preserve the constant's truth value.
+;
+; A location operand wider than 64 bits is narrowed before entering the
+; handler's 64-bit type-and-value key. An unsupported expression is rejected
+; before its wider floating-point payload is read.
+; A negative integer location narrower than its variable is sign-extended by
+; MachineIRBuilder, so IR collection must use that same value for the lookup.
+;
+; A non-semantic instruction can be removed from a module without changing it,
+; so debug info must not make the module require something it otherwise would
+; not. OpTypeInt 16, OpTypeInt 64, OpTypeFloat 16 and OpTypeFloat 64 each
+; oblige the module to declare a capability, and spirv-val rejects the module
+; without it, so a narrow or wide constant the module does not already define
+; is dropped instead. The implicit-check-not options on the first RUN line
+; assert that none of the four is added. OpTypeBool and the 32-bit scalars
+; carry no such requirement and are created freely. OpTypeFloat takes no
+; signedness operand, so creating the 32-bit float exercises a shape the
+; integer path cannot.
+
+; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[C42:%[0-9]+]] = OpConstant [[I32]] 42{{ *$}}
+; CHECK-DAG: [[CNEG:%[0-9]+]] = OpConstant [[I32]] 4294967295{{ *$}}
+; CHECK-DAG: [[F32:%[0-9]+]] = OpTypeFloat 32{{ *$}}
+; CHECK-DAG: [[F32C:%[0-9]+]] = OpConstant [[F32]] 1{{ *$}}
+; CHECK-DAG: [[BOOL:%[0-9]+]] = OpTypeBool
+; Both constants naming one [[BOOL]] is what says the handler reuses the type
+; it emitted rather than adding a second OpTypeBool per constant. The UNIQUE
+; prefix scans from the top of the output, so the COUNT-1 and NOT pair below
+; fails on a duplicate, which a CHECK-DAG group cannot.
+; CHECK-DAG: [[TRUE:%[0-9]+]] = OpConstantTrue [[BOOL]]
+; CHECK-DAG: [[FALSE:%[0-9]+]] = OpConstantFalse [[BOOL]]
+
+; UNIQUE-COUNT-1: OpTypeBool
+; UNIQUE-NOT: OpTypeBool
+; CHECK-DAG: [[NEGNAME:%[0-9]+]] = OpString "negative"
+; CHECK-DAG: [[NARROWNAME:%[0-9]+]] = OpString "narrow"
+; CHECK-DAG: [[WIDENAME:%[0-9]+]] = OpString "wide"
+; CHECK-DAG: [[HALFNAME:%[0-9]+]] = OpString "half"
+; CHECK-DAG: [[DOUBLENAME:%[0-9]+]] = OpString "double"
+; CHECK-DAG: [[UNSIZEDYESNAME:%[0-9]+]] = OpString "unsized_yes"
+; CHECK-DAG: [[UNSIZEDNONAME:%[0-9]+]] = OpString "unsized_no"
+; CHECK-DAG: [[WIDESOURCENAME:%[0-9]+]] = OpString "wide_source"
+; CHECK-DAG: [[WIDEFLOATNAME:%[0-9]+]] = OpString "wide_float_location"
+; CHECK-DAG: [[WIDENEDNEGNAME:%[0-9]+]] = OpString "widened_negative"
+; CHECK-DAG: [[F32NAME:%[0-9]+]] = OpString "single"
+; CHECK-DAG: [[NEGVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[NEGNAME]]
+; The narrow and wide variables still get a DebugLocalVariable. Only the
+; binding to their value is dropped, since naming it would need a type the
+; module does not have. The DROPPED prefix below asserts that: it captures the
+; four ids from the module section, which precedes every function body, so its
+; negative region covers all of them.
+; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[NARROWNAME]]
+; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[WIDENAME]]
+; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[HALFNAME]]
+; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DOUBLENAME]]
+
+; DROPPED-DAG: [[DNARROWNAME:%[0-9]+]] = OpString "narrow"
+; DROPPED-DAG: [[DWIDENAME:%[0-9]+]] = OpString "wide"
+; DROPPED-DAG: [[DHALFNAME:%[0-9]+]] = OpString "half"
+; DROPPED-DAG: [[DDOUBLENAME:%[0-9]+]] = OpString "double"
+; DROPPED-DAG: [[DNARROW:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DNARROWNAME]]
+; DROPPED-DAG: [[DWIDE:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DWIDENAME]]
+; DROPPED-DAG: [[DHALF:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DHALFNAME]]
+; DROPPED-DAG: [[DDOUBLE:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DDOUBLENAME]]
+; DROPPED-NOT: DebugValue [[DNARROW]]
+; DROPPED-NOT: DebugValue [[DWIDE]]
+; DROPPED-NOT: DebugValue [[DHALF]]
+; DROPPED-NOT: DebugValue [[DDOUBLE]]
+; CHECK-DAG: [[UNSIZEDYESVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[UNSIZEDYESNAME]]
+; CHECK-DAG: [[UNSIZEDNOVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[UNSIZEDNONAME]]
+; CHECK-DAG: [[WIDESOURCEVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[WIDESOURCENAME]]
+; CHECK-DAG: [[WIDEFLOATVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[WIDEFLOATNAME]]
+; CHECK-DAG: [[WIDENEDNEGVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[WIDENEDNEGNAME]]
+; CHECK-DAG: [[F32VAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[F32NAME]]
+
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue {{%[0-9]+}} [[C42]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue {{%[0-9]+}} [[TRUE]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue {{%[0-9]+}} [[FALSE]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[NEGVAR]] [[CNEG]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[UNSIZEDYESVAR]] [[TRUE]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[UNSIZEDNOVAR]] [[FALSE]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[WIDESOURCEVAR]] [[C42]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[WIDENEDNEGVAR]] [[CNEG]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[F32VAR]] [[F32C]]
+; CHECK-NOT: DebugValue [[WIDEFLOATVAR]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @constant_value(i32 %x) !dbg !5 {
+entry:
+    #dbg_value(i32 42, !9, !DIExpression(), !10)
+    #dbg_value(i1 true, !11, !DIExpression(), !10)
+    #dbg_value(i1 false, !12, !DIExpression(), !10)
+    #dbg_value(i32 -1, !13, !DIExpression(), !10)
+    #dbg_value(i16 7, !14, !DIExpression(), !10)
+    #dbg_value(i64 1234605616436508552, !15, !DIExpression(), !10)
+    #dbg_value(half 0xH3C00, !16, !DIExpression(), !10)
+    #dbg_value(double 1.000000e+00, !17, !DIExpression(), !10)
+    #dbg_value(i1 true, !22, !DIExpression(), !10)
+    #dbg_value(i1 false, !23, !DIExpression(), !10)
+    #dbg_value(i128 18446744073709551658, !25, !DIExpression(), !10)
+    #dbg_value(i16 -1, !27, !DIExpression(), !10)
+    #dbg_value(float 1.000000e+00, !30, !DIExpression(), !10)
+    #dbg_value(fp128 0xL00000000000000003FFF000000000000, !26, !DIExpression(DW_OP_LLVM_convert, 64, DW_ATE_unsigned), !10)
+  ret i32 %x, !dbg !10
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-value-constant.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: "constant_value", linkageName: "constant_value", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DIBasicType(name: "bool", size: 8, encoding: DW_ATE_boolean)
+!9 = !DILocalVariable(name: "constant", scope: !5, file: !1, line: 2, type: !7)
+!10 = !DILocation(line: 3, column: 3, scope: !5)
+!11 = !DILocalVariable(name: "yes", scope: !5, file: !1, line: 4, type: !8)
+!12 = !DILocalVariable(name: "no", scope: !5, file: !1, line: 5, type: !8)
+!13 = !DILocalVariable(name: "negative", scope: !5, file: !1, line: 6, type: !7)
+!14 = !DILocalVariable(name: "narrow", scope: !5, file: !1, line: 7, type: !18)
+!15 = !DILocalVariable(name: "wide", scope: !5, file: !1, line: 8, type: !19)
+!16 = !DILocalVariable(name: "half", scope: !5, file: !1, line: 9, type: !20)
+!17 = !DILocalVariable(name: "double", scope: !5, file: !1, line: 10, type: !21)
+!18 = !DIBasicType(name: "short", size: 16, encoding: DW_ATE_signed)
+!19 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+!20 = !DIBasicType(name: "half", size: 16, encoding: DW_ATE_float)
+!21 = !DIBasicType(name: "double", size: 64, encoding: DW_ATE_float)
+!22 = !DILocalVariable(name: "unsized_yes", scope: !5, file: !1, line: 11, type: !24)
+!23 = !DILocalVariable(name: "unsized_no", scope: !5, file: !1, line: 12, type: !24)
+!24 = !DIBasicType(name: "unsized_bool", encoding: DW_ATE_boolean)
+!25 = !DILocalVariable(name: "wide_source", scope: !5, file: !1, line: 13, type: !7)
+!26 = !DILocalVariable(name: "wide_float_location", scope: !5, file: !1, line: 14, type: !21)
+!27 = !DILocalVariable(name: "widened_negative", scope: !5, file: !1, line: 15, type: !28)
+!28 = !DIBasicType(name: "another int", size: 32, encoding: DW_ATE_signed)
+!29 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
+!30 = !DILocalVariable(name: "single", scope: !5, file: !1, line: 16, type: !29)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-cross-block.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-cross-block.ll
new file mode 100644
index 0000000000000..736ef3a8f148b
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-cross-block.ll
@@ -0,0 +1,49 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --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 %}
+
+; A record in one block naming a value defined in another. SPIR-V requires an
+; id defined in a function block to dominate a non-phi use, and requires a
+; block to precede every block it dominates, so a dominating definition has
+; also been printed already and can be named.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "v"
+; CHECK-DAG: [[VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[NAME]]
+; CHECK-DAG: [[EXPR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
+
+; CHECK: [[SUM:%[0-9]+]] = OpIAdd
+; The record is in the successor block, after its OpLabel, naming the entry
+; block's definition.
+; CHECK: OpLabel
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[VAR]] [[SUM]] [[EXPR]]{{ *$}}
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @f(i32 %x) !dbg !5 {
+entry:
+  %sum = add i32 %x, %x, !dbg !10
+  %cmp = icmp slt i32 %sum, 0, !dbg !10
+  br i1 %cmp, label %then, label %exit, !dbg !10
+
+then:
+    #dbg_value(i32 %sum, !9, !DIExpression(), !10)
+  br label %exit, !dbg !10
+
+exit:
+  ret i32 %sum, !dbg !10
+}
+
+!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-value-cross-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: "f", linkageName: "f", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DILocalVariable(name: "v", scope: !5, file: !1, line: 2, type: !7)
+!10 = !DILocation(line: 3, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-merge-region.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-merge-region.ll
new file mode 100644
index 0000000000000..e8f07e865a2e1
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-merge-region.ll
@@ -0,0 +1,82 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --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 %}
+
+; Two debug records sit between the merge instruction and its terminator, one
+; value and one declare. NonSemantic.Shader.DebugInfo says DebugDeclare,
+; DebugValue, DebugLine, DebugNoLine and DebugFunctionDefinition "cannot come
+; after a 'Merge Instruction'", so both move ahead of the merge and nothing is
+; left between the merge and the branch.
+;
+; A zero-operand fake use between the merge and the records becomes a
+; non-debug meta instruction that emits no SPIR-V. It must not hide the merge
+; when the records choose their output anchor.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[CONDNAME:%[0-9]+]] = OpString "condition"
+; CHECK-DAG: [[SLOTNAME:%[0-9]+]] = OpString "slot"
+; CHECK-DAG: [[CONDVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[CONDNAME]]
+; CHECK-DAG: [[SLOTVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[SLOTNAME]]
+; CHECK-DAG: [[EXPR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
+
+; The merge path emits both records while visiting the merge and relies on each
+; record's own visit emitting nothing, so the counts are pinned: a second copy
+; of either one has to fail.
+; CHECK: [[SLOT:%[0-9]+]] = OpVariable
+; CHECK: [[CMP:%[0-9]+]] = OpSLessThan
+; CHECK-COUNT-1: OpExtInst [[VOID]] [[EXT]] DebugValue [[CONDVAR]] [[CMP]] [[EXPR]]{{ *$}}
+; CHECK-NOT: DebugValue
+; CHECK-COUNT-1: OpExtInst [[VOID]] [[EXT]] DebugDeclare [[SLOTVAR]] [[SLOT]] [[EXPR]]{{ *$}}
+; CHECK-NOT: DebugDeclare
+; CHECK: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional
+; CHECK-NOT: DebugValue
+; CHECK-NOT: DebugDeclare
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @if_else(i32 %x) !dbg !5 {
+entry:
+  %slot = alloca i32, align 4
+  store i32 %x, ptr %slot, align 4
+  %cmp = icmp slt i32 %x, 0, !dbg !8
+  call void @llvm.spv.selection.merge.p0(ptr blockaddress(@if_else, %merge), i32 0), !dbg !14
+  call void (...) @llvm.fake.use()
+    #dbg_value(i1 %cmp, !16, !DIExpression(), !14)
+    #dbg_declare(ptr %slot, !17, !DIExpression(), !14)
+  br i1 %cmp, label %then, label %else, !dbg !13
+
+then:
+  br label %merge, !dbg !9
+
+else:
+  br label %merge, !dbg !10
+
+merge:
+  %r = load i32, ptr %slot, align 4, !dbg !12
+  ret i32 %r, !dbg !12
+}
+
+declare void @llvm.spv.selection.merge.p0(ptr, i32 immarg)
+declare void @llvm.fake.use(...)
+
+!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-value-merge-region.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: "if_else", linkageName: "if_else", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 10, scope: !5)
+!9 = !DILocation(line: 4, column: 5, scope: !5)
+!10 = !DILocation(line: 5, column: 5, scope: !5)
+!12 = !DILocation(line: 9, column: 3, scope: !5)
+!13 = !DILocation(line: 99, column: 50, scope: !5)
+!14 = !DILocation(line: 7, column: 1, scope: !5)
+!15 = !DIBasicType(name: "bool", size: 1, encoding: DW_ATE_boolean)
+!16 = !DILocalVariable(name: "condition", scope: !5, file: !1, line: 7, type: !15)
+!17 = !DILocalVariable(name: "slot", scope: !5, file: !1, line: 8, type: !7)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-module-scope.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-module-scope.ll
new file mode 100644
index 0000000000000..ba8377e5e8a2d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-module-scope.ll
@@ -0,0 +1,46 @@
+; asm-verbose=0 keeps AsmPrinter's ;DEBUG_VALUE: comments out of the output so
+; the CHECK-NEXT chain tracks SPIR-V instructions only.
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --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 value folds to a constant, which module analysis hoists into
+; MB_TypeConstVars and marks skip-emission where it was. That section is
+; written before every function body, so the id is available to a record that
+; names it even though nothing in the body defines it.
+
+; 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:%[0-9]+]] = OpString "folded"
+; CHECK-DAG: [[VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[NAME]]
+; CHECK-DAG: [[EXPR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
+
+; The binding is emitted at its own position, ahead of the only instruction in
+; the body that uses the constant.
+; CHECK: OpFunction
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[VAR]] [[C3:%[0-9]+]] [[EXPR]]{{ *$}}
+; CHECK-NEXT: OpIAdd [[I32]] {{%[0-9]+}} [[C3]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @f(i32 %x) !dbg !5 {
+entry:
+  %c = add i32 1, 2, !dbg !10
+    #dbg_value(i32 %c, !9, !DIExpression(), !10)
+  %r = add i32 %x, %c, !dbg !10
+  ret i32 %r, !dbg !10
+}
+
+!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-value-module-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: "f", linkageName: "f", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DILocalVariable(name: "folded", scope: !5, file: !1, line: 2, type: !7)
+!10 = !DILocation(line: 3, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-postlegalizer-order.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-postlegalizer-order.ll
new file mode 100644
index 0000000000000..2158e43031233
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-postlegalizer-order.ll
@@ -0,0 +1,39 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info --stop-after=spirv-postlegalizer %s -o - | FileCheck %s
+
+; SPIRVPostLegalizer::generateAssignType() must leave the ASSIGN_TYPE that takes
+; over a definition ahead of any DBG_VALUE naming it. updateRegType() moves the
+; insert point past debug records, so without pinning it back the ASSIGN_TYPE
+; lands after the record and the record precedes its own definition.
+;
+; This is checked on Machine IR because the final SPIR-V cannot show it. The
+; handler drops a record whose definition it has not reached, so a regression
+; here removes the DebugValue from the module rather than misplacing it, and no
+; output test would name what went missing.
+
+; CHECK: %[[#SUM:]]:iid(s32) = G_ADD
+; CHECK-NEXT: %[[#ASSIGNED:]]:iid(s32) = ASSIGN_TYPE %[[#SUM]](s32)
+; CHECK-NEXT: DBG_VALUE %[[#ASSIGNED]](s32)
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @add_one(i32 %x) !dbg !5 {
+entry:
+  %sum = add i32 %x, %x, !dbg !10
+    #dbg_value(i32 %sum, !9, !DIExpression(), !11)
+  ret i32 %sum, !dbg !11
+}
+
+!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-value-postlegalizer-order.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !6)
+!6 = !{!7, !7}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!5 = distinct !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DILocalVariable(name: "result", scope: !5, file: !1, line: 2, type: !7)
+!10 = !DILocation(line: 3, column: 7, scope: !5)
+!11 = !DILocation(line: 4, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-unsupported.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-unsupported.ll
new file mode 100644
index 0000000000000..797a69761ded4
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-unsupported.ll
@@ -0,0 +1,96 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not=DebugValue --implicit-check-not=DebugOperation --implicit-check-not="OpCapability Float64" --implicit-check-not="OpTypeFloat 64"
+; 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 %}
+
+; Shapes this backend does not emit a DebugValue for. An unavailable value has
+; no result id to name, and an expression with no NonSemantic counterpart has
+; no emitted DebugExpression. A cv-qualified type is not emitted as a
+; DebugType, so its variable gets no DebugLocalVariable. A rejected constant
+; record must not leave behind an otherwise unused semantic type, constant or
+; capability, whether its expression, type, or parent scope rejects it. A value
+; defined in a sibling block does not dominate the record, and SPIR-V requires
+; a definition in a function block to dominate a non-phi use.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[UNAVAILABLE:%[0-9]+]] = OpString "unavailable"
+; CHECK-DAG: [[EXPRESSION:%[0-9]+]] = OpString "unsupported_expression"
+; CHECK-DAG: [[CONSTANT:%[0-9]+]] = OpString "unsupported_constant"
+; CHECK-DAG: OpString "qualified_constant"
+; CHECK-DAG: OpString "scope_rejected_constant"
+; CHECK-DAG: [[SIBLING:%[0-9]+]] = OpString "sibling_block_value"
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[UNAVAILABLE]]
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[EXPRESSION]]
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[CONSTANT]]
+; The sibling-block variable still gets a DebugLocalVariable, which is what
+; says its record reached the availability test rather than an earlier one.
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[SIBLING]]
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
+; CHECK: OpFunction
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @f(i32 %x) !dbg !5 {
+entry:
+    #dbg_value(i32 poison, !10, !DIExpression(), !12)
+    #dbg_value(i32 %x, !11, !DIExpression(DW_OP_LLVM_convert, 32, DW_ATE_signed), !12)
+    #dbg_value(double 1.000000e+00, !13, !DIExpression(DW_OP_LLVM_convert, 64, DW_ATE_unsigned), !12)
+    #dbg_value(double 2.000000e+00, !15, !DIExpression(), !12)
+  ret i32 %x, !dbg !12
+}
+
+; A qualifier in the subprogram signature prevents DebugTypeFunction and
+; DebugFunction emission. The local variable therefore has no parent scope,
+; so its constant must not change the semantic module requirements.
+define spir_func i32 @missing_scope(i32 %x) !dbg !17 {
+entry:
+    #dbg_value(double 3.000000e+00, !20, !DIExpression(), !21)
+  ret i32 %x, !dbg !21
+}
+
+; A value defined in a sibling block reaches the record through neither a
+; dominating definition nor the module section, so the record is dropped.
+define spir_func i32 @not_dominating(i32 %x) !dbg !23 {
+entry:
+  %c = icmp slt i32 %x, 0, !dbg !26
+  br i1 %c, label %a, label %b, !dbg !26
+
+a:
+  %v = add i32 %x, 1, !dbg !26
+  br label %join, !dbg !26
+
+b:
+    #dbg_value(i32 %v, !25, !DIExpression(), !26)
+  br label %join, !dbg !26
+
+join:
+  %r = phi i32 [ %v, %a ], [ 0, %b ], !dbg !26
+  ret i32 %r, !dbg !26
+}
+
+!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-value-unsupported.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: "f", linkageName: "f", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DILocalVariable(name: "unavailable", scope: !5, file: !1, line: 3, type: !7)
+!11 = !DILocalVariable(name: "unsupported_expression", scope: !5, file: !1, line: 4, type: !7)
+!12 = !DILocation(line: 5, column: 3, scope: !5)
+!13 = !DILocalVariable(name: "unsupported_constant", scope: !5, file: !1, line: 5, type: !14)
+!14 = !DIBasicType(name: "double", size: 64, encoding: DW_ATE_float)
+!15 = !DILocalVariable(name: "qualified_constant", scope: !5, file: !1, line: 6, type: !16)
+!16 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !14)
+!17 = distinct !DISubprogram(name: "missing_scope", linkageName: "missing_scope", scope: !1, file: !1, line: 8, type: !18, scopeLine: 8, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!18 = !DISubroutineType(types: !19)
+!19 = !{!22, !7}
+!20 = !DILocalVariable(name: "scope_rejected_constant", scope: !17, file: !1, line: 9, type: !14)
+!21 = !DILocation(line: 10, column: 3, scope: !17)
+!22 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !7)
+!23 = distinct !DISubprogram(name: "not_dominating", linkageName: "not_dominating", scope: !1, file: !1, line: 12, type: !4, scopeLine: 12, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!25 = !DILocalVariable(name: "sibling_block_value", scope: !23, file: !1, line: 13, type: !7)
+!26 = !DILocation(line: 14, column: 3, scope: !23)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value.ll
new file mode 100644
index 0000000000000..ab2a233a20b7c
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value.ll
@@ -0,0 +1,101 @@
+; asm-verbose=0 keeps AsmPrinter's ;DEBUG_VALUE: comments out of the output so
+; the CHECK-NEXT chains track SPIR-V instructions only.
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not=DebugDeclare
+; 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 %}
+
+; A direct DBG_VALUE whose virtual register is defined by an emitted SPIR-V
+; instruction. The fragment stays in DebugExpression; it is not emitted as a
+; DebugValue index.
+
+; 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-value.c"
+; CHECK-DAG: [[RESULTNAME:%[0-9]+]] = OpString "result"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[RESULTVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[RESULTNAME]]
+; CHECK-DAG: [[C0:%[0-9]+]] = OpConstant [[I32]] 0{{ *$}}
+; CHECK-DAG: [[C9:%[0-9]+]] = OpConstant [[I32]] 9{{ *$}}
+; CHECK-DAG: [[C10:%[0-9]+]] = OpConstant [[I32]] 10{{ *$}}
+; CHECK-DAG: [[C16:%[0-9]+]] = OpConstant [[I32]] 16{{ *$}}
+; CHECK-DAG: [[C20:%[0-9]+]] = OpConstant [[I32]] 20{{ *$}}
+; CHECK-DAG: [[C30:%[0-9]+]] = OpConstant [[I32]] 30{{ *$}}
+; CHECK-DAG: [[C31:%[0-9]+]] = OpConstant [[I32]] 31{{ *$}}
+; CHECK-DAG: [[C32:%[0-9]+]] = OpConstant [[I32]] 32{{ *$}}
+; CHECK-DAG: [[FIRSTNAME:%[0-9]+]] = OpString "first"
+; CHECK-DAG: [[ALIASNAME:%[0-9]+]] = OpString "alias"
+; CHECK-DAG: [[SECONDNAME:%[0-9]+]] = OpString "second"
+; CHECK-DAG: [[FIRSTVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[FIRSTNAME]]
+; CHECK-DAG: [[ALIASVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[ALIASNAME]]
+; CHECK-DAG: [[SECONDVAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[SECONDNAME]]
+; CHECK-DAG: [[FRAGMENT:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugOperation [[C9]] [[C0]] [[C16]]{{ *$}}
+; CHECK-DAG: [[EXPR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression [[FRAGMENT]]{{ *$}}
+; CHECK-DAG: [[EMPTY:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugExpression{{ *$}}
+
+; The binding follows the instruction that defines its value. Anchor the
+; negative check at OpFunction so its region does not depend on where the
+; CHECK-DAG group above happened to match.
+; CHECK: OpFunction
+; CHECK-NOT: DebugValue
+; CHECK: [[SUM:%[0-9]+]] = OpIAdd
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[C20]] [[C20]] [[C9]] [[C10]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugValue [[RESULTVAR]] [[SUM]] [[EXPR]]{{ *$}}
+; CHECK-NOT: DebugValue
+
+; Two variables bound to one value, and a second value in the same block. Each
+; binding must land on the instruction that defines it, not on whichever
+; definition happens to come first or last.
+; CHECK: OpFunction
+; CHECK: [[P:%[0-9]+]] = OpIMul
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[C30]] [[C30]] [[C9]] [[C10]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugValue [[FIRSTVAR]] [[P]] [[EMPTY]]{{ *$}}
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[C31]] [[C31]] [[C9]] [[C10]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugValue [[ALIASVAR]] [[P]] [[EMPTY]]{{ *$}}
+; CHECK: [[D:%[0-9]+]] = OpISub
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[C32]] [[C32]] [[C9]] [[C10]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugValue [[SECONDVAR]] [[D]] [[EMPTY]]{{ *$}}
+; CHECK-NOT: DebugValue
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @add_one(i32 %x) !dbg !5 {
+entry:
+  %sum = add i32 %x, %x, !dbg !10
+    #dbg_value(i32 %sum, !9, !DIExpression(DW_OP_LLVM_fragment, 0, 16), !11)
+  ret i32 %sum, !dbg !12
+}
+
+define spir_func i32 @two_values(i32 %x) !dbg !20 {
+entry:
+  %prod = mul i32 %x, %x, !dbg !27
+    #dbg_value(i32 %prod, !21, !DIExpression(), !24)
+    #dbg_value(i32 %prod, !22, !DIExpression(), !25)
+  %diff = sub i32 %prod, %x, !dbg !27
+    #dbg_value(i32 %diff, !23, !DIExpression(), !26)
+  ret i32 %diff, !dbg !28
+}
+
+!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-value.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !6)
+!6 = !{!7, !7}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!5 = distinct !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DILocalVariable(name: "result", scope: !5, file: !1, line: 20, type: !7)
+!10 = !DILocation(line: 3, column: 7, scope: !5)
+!11 = !DILocation(line: 20, column: 9, scope: !5)
+!12 = !DILocation(line: 4, column: 3, scope: !5)
+!20 = distinct !DISubprogram(name: "two_values", linkageName: "two_values", scope: !1, file: !1, line: 10, type: !4, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!21 = !DILocalVariable(name: "first", scope: !20, file: !1, line: 30, type: !7)
+!22 = !DILocalVariable(name: "alias", scope: !20, file: !1, line: 31, type: !7)
+!23 = !DILocalVariable(name: "second", scope: !20, file: !1, line: 32, type: !7)
+!24 = !DILocation(line: 30, column: 9, scope: !20)
+!25 = !DILocation(line: 31, column: 9, scope: !20)
+!26 = !DILocation(line: 32, column: 9, scope: !20)
+!27 = !DILocation(line: 11, column: 7, scope: !20)
+!28 = !DILocation(line: 12, column: 3, scope: !20)

>From 75c0726bd214444d540878c3b2044fee940eabd7 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Tue, 22 Sep 2026 09:12:49 -0400
Subject: [PATCH 2/2] Address review feedback.

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 263 +++++++-----------
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      |  55 ++--
 llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp  |   4 -
 .../debug-value-constant-existing-type.ll     |  19 +-
 .../SPIRV/debug-info/debug-value-constant.ll  |  27 +-
 .../debug-info/debug-value-typedef-cycle.ll   |  36 +++
 6 files changed, 201 insertions(+), 203 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-value-typedef-cycle.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 20a1aa7e9a25b..d27cad42ab0e5 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -12,11 +12,14 @@
 #include "SPIRVSubtarget.h"
 #include "SPIRVUtils.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/BinaryFormat/Dwarf.h"
 #include "llvm/CodeGen/AsmPrinter.h"
+#include "llvm/CodeGen/MachineDominators.h"
 #include "llvm/CodeGen/MachineFunction.h"
 #include "llvm/CodeGen/MachineInstr.h"
 #include "llvm/CodeGen/MachineModuleInfo.h"
@@ -260,7 +263,6 @@ unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
   }
 }
 
-// Whether \p BT is a SPIR-V OpTypeBool.
 static bool isBooleanType(const DIBasicType *BT) {
   return BT->getEncoding() == dwarf::DW_ATE_boolean;
 }
@@ -285,28 +287,16 @@ static uint64_t constantBits(const APInt &Value, const DIBasicType *BT) {
                       BT);
 }
 
-// \p CI as a 64-bit value, truncated to the width of \p BT. A one-bit constant
-// is zero extended, widths 2 through 64 are sign extended, and anything wider
-// is truncated.
-static uint64_t constantBits(const ConstantInt *CI, const DIBasicType *BT) {
-  const APInt &Value = CI->getValue();
-  if (Value.getBitWidth() > 64)
-    return constantBits(Value, BT);
-  if (Value.getBitWidth() == 1)
-    return constantBits(Value.getZExtValue(), BT);
-  return constantBits(static_cast<uint64_t>(Value.getSExtValue()), BT);
-}
-
 // The DIBasicType that \p Ty resolves to through any typedefs, or null.
-//
-// This accepts: a boolean of any size, and a 16, 32 or 64-bit integer or float.
-// Anything else gives null, including a type reached through a cv-qualifier.
 static const DIBasicType *stripToScalarType(const DIType *Ty) {
-  // Only a typedef is looked through. A cv-qualified type is not emitted as a
-  // DebugType, so emitDebugLocalVariable() cannot name it and the variable
-  // gets no DebugLocalVariable.
+  // A cv-qualified type gets no DebugType, so its variable has no
+  // DebugLocalVariable and its records are dropped.
+  //
+  // Since visitDIDerivedType accepts a cyclic typedef chain, this stops once it
+  // reaches an already seen type.
+  SmallPtrSet<const DIDerivedType *, 4> Seen;
   while (const auto *DT = dyn_cast_or_null<DIDerivedType>(Ty)) {
-    if (DT->getTag() != dwarf::DW_TAG_typedef)
+    if (DT->getTag() != dwarf::DW_TAG_typedef || !Seen.insert(DT).second)
       return nullptr;
     Ty = DT->getBaseType();
   }
@@ -325,35 +315,15 @@ static const DIBasicType *stripToScalarType(const DIType *Ty) {
   case dwarf::DW_ATE_unsigned:
   case dwarf::DW_ATE_signed_char:
   case dwarf::DW_ATE_unsigned_char:
+    return Size == 8 || Size == 16 || Size == 32 || Size == 64 ? BT : nullptr;
   case dwarf::DW_ATE_float:
+    // SPIR-V has no 8-bit float.
     return Size == 16 || Size == 32 || Size == 64 ? BT : nullptr;
   default:
     return nullptr;
   }
 }
 
-static bool isLowerableExpression(const DIExpression *Expr);
-
-// The constant assignment \p DVR makes to \p LV, or nullopt when this backend
-// cannot name the assigned value.
-static std::optional<SPIRV::ConstantAssignment>
-getConstantAssignment(const DbgVariableRecord *DVR, const DILocalVariable *LV) {
-  if (!DVR->isDbgValue() || DVR->getNumVariableLocationOps() != 1)
-    return std::nullopt;
-  const auto *BT = stripToScalarType(LV->getType());
-  if (!BT || !isLowerableExpression(DVR->getExpression()))
-    return std::nullopt;
-  const Value *V = DVR->getVariableLocationOp(0);
-  std::optional<uint64_t> Bits;
-  if (const auto *CI = dyn_cast_or_null<ConstantInt>(V))
-    Bits = constantBits(CI, BT);
-  else if (const auto *CFP = dyn_cast_or_null<ConstantFP>(V))
-    Bits = constantBits(CFP->getValueAPF().bitcastToAPInt(), BT);
-  if (!Bits)
-    return std::nullopt;
-  return SPIRV::ConstantAssignment{BT, *Bits, LV, DVR->getExpression()};
-}
-
 // Collect distinct DILocations and DILocalVariables from LLVM IR.
 //
 // DILocations come from instruction debug locations and from the debug records
@@ -367,8 +337,7 @@ getConstantAssignment(const DbgVariableRecord *DVR, const DILocalVariable *LV) {
 // still get a DebugLocalVariable.
 static void collectDebugLocationsAndLocalVariables(
     const Module &M, SetVector<const DILocation *> &Locations,
-    SetVector<const DILocalVariable *> &LVs,
-    SmallVectorImpl<SPIRV::ConstantAssignment> &Assignments) {
+    SetVector<const DILocalVariable *> &LVs) {
   for (const Function &F : M) {
     const DISubprogram *SP = F.getSubprogram();
     if (!SP)
@@ -383,14 +352,8 @@ static void collectDebugLocationsAndLocalVariables(
         if (const DILocation *DL = DR.getDebugLoc().get())
           Locations.insert(DL);
         if (const auto *DVR = dyn_cast<DbgVariableRecord>(&DR))
-          if (const DILocalVariable *LV = DVR->getVariable()) {
+          if (const DILocalVariable *LV = DVR->getVariable())
             LVs.insert(LV);
-            // The OpConstant has to precede every function body, so it is
-            // collected here and emitted with the rest of module-scope debug
-            // info.
-            if (auto CA = getConstantAssignment(DVR, LV))
-              Assignments.push_back(*CA);
-          }
       }
     }
   }
@@ -439,11 +402,9 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   ScopeToPathOpStringReg.clear();
   DebugSourceRegByFileStr.clear();
   OpStringContentCache.clear();
-  I32ConstantCache.clear();
   ScalarTypeCache.clear();
   ScalarConstantCache.clear();
   ConstantValueRegs.clear();
-  ConstantAssignments.clear();
   DebugTypeFunctionCache.clear();
   DebugOperationCache.clear();
   DebugExpressionCache.clear();
@@ -524,7 +485,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   }
 
   collectDebugLocationsAndLocalVariables(*M, UniqueDebugLocations,
-                                         LocalVariables, ConstantAssignments);
+                                         LocalVariables);
 
   // DILexicalBlock and DINamespace scopes are lowered to DebugLexicalBlock.
   // Collect them in parent-before-child order so they can be later emitted in a
@@ -559,13 +520,8 @@ void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
   if (!MAI.ExtInstSetMap.count(NSSet))
     MAI.ExtInstSetMap[NSSet] = MAI.getNextIDRegister();
 
-  // Types, constants and globals never reach endInstruction(), and this section
-  // is written before every function body, so record them as already emitted.
-  // handleTypeDeclOrConstant() keeps only one instruction per signature here
-  // and aliases every duplicate to its id (which identifies them).
   for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
-    MCRegister Id = getResultId(*MI, MAI);
-    if (Id.isValid())
+    if (MCRegister Id = getResultId(*MI, MAI))
       ModuleScopeIds.insert(Id);
   }
 }
@@ -638,31 +594,8 @@ MCRegister SPIRVNonSemanticDebugHandler::getCachedScopePathOpStringReg(
 
 MCRegister SPIRVNonSemanticDebugHandler::emitOpConstantI32(
     uint32_t Value, MCRegister I32TypeReg, SPIRV::ModuleAnalysisInfo &MAI) {
-  auto [It, Inserted] = I32ConstantCache.try_emplace(Value);
-  if (!Inserted)
-    return It->second;
-
-  for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
-    if (MI->getOpcode() != SPIRV::OpConstantI || MI->getNumOperands() < 3 ||
-        !MI->getOperand(2).isImm())
-      continue;
-    if (MAI.getRegisterAlias(MI->getMF(), MI->getOperand(1).getReg()) !=
-            I32TypeReg ||
-        static_cast<uint64_t>(MI->getOperand(2).getImm()) != Value)
-      continue;
-    It->second = MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
-    return It->second;
-  }
-
-  MCRegister Reg = MAI.getNextIDRegister();
-  It->second = Reg;
-  MCInst Inst;
-  Inst.setOpcode(SPIRV::OpConstantI);
-  Inst.addOperand(MCOperand::createReg(Reg));
-  Inst.addOperand(MCOperand::createReg(I32TypeReg));
-  Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Value)));
-  emitMCInst(Inst);
-  return Reg;
+  return findOrEmitConstant(SPIRV::OpConstantI, I32TypeReg, Value,
+                            /*Width=*/32, MAI);
 }
 
 MCRegister SPIRVNonSemanticDebugHandler::emitExtInst(
@@ -725,7 +658,7 @@ MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeVoid(
 }
 
 // Whether declaring \p BT's SPIR-V type would require a capability. The type
-// width determines the requirement: Int16, Int64, Float16 and Float64 in
+// width determines the requirement: Int8, Int16, Int64, Float16 and Float64 in
 // SPIRVModuleAnalysis::collectReqs(). A boolean and a 32-bit scalar are free.
 static bool scalarTypeNeedsCapability(const DIBasicType *BT) {
   if (isBooleanType(BT))
@@ -770,10 +703,10 @@ MCRegister SPIRVNonSemanticDebugHandler::findOrEmitScalarType(
   }
 
   // A non-semantic instruction "has no semantic impact, and can be safely
-  // removed from the module", so debug info must not make the module require
-  // something it otherwise would not. Declaring OpTypeInt 64 would force the
-  // module to declare Int64, and in Vulkan that ties it to a device feature,
-  // so we drop all the types that the module doesn't already have.
+  // removed from the module", so debug info must leave the module's
+  // requirements alone. Declaring OpTypeInt 64 forces Int64, which Vulkan ties
+  // to a device feature, so this returns nothing if the type requires a new
+  // capability.
   if (scalarTypeNeedsCapability(BT))
     return MCRegister();
 
@@ -817,8 +750,7 @@ MCRegister SPIRVNonSemanticDebugHandler::findModuleConstant(
     if (MAI.getRegisterAlias(MI->getMF(), MI->getOperand(1).getReg()) !=
         TypeReg)
       continue;
-    // A boolean holds its value in its opcode, so the opcode and the type are
-    // the whole comparison.
+    // A boolean holds its value in its opcode, so there is nothing to compare.
     if (!IsBool && !constantHasValue(*MI, Value, IsWide))
       continue;
     return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
@@ -826,19 +758,12 @@ MCRegister SPIRVNonSemanticDebugHandler::findModuleConstant(
   return MCRegister();
 }
 
-MCRegister SPIRVNonSemanticDebugHandler::findOrEmitScalarConstant(
-    const DIBasicType *BT, uint64_t Value, SPIRV::ModuleAnalysisInfo &MAI) {
-  bool IsBool = isBooleanType(BT);
-  bool IsFloat = BT->getEncoding() == dwarf::DW_ATE_float;
-  bool IsWide = !IsBool && BT->getSizeInBits() == 64;
-  MCRegister TypeReg = findOrEmitScalarType(BT, MAI);
-  if (!TypeReg.isValid())
-    return MCRegister();
-
-  unsigned Opcode =
-      IsBool    ? (Value ? SPIRV::OpConstantTrue : SPIRV::OpConstantFalse)
-      : IsFloat ? SPIRV::OpConstantF
-                : SPIRV::OpConstantI;
+// \p Width is 0 for a boolean, whose value lives in \p Opcode.
+MCRegister SPIRVNonSemanticDebugHandler::findOrEmitConstant(
+    unsigned Opcode, MCRegister TypeReg, uint64_t Value, unsigned Width,
+    SPIRV::ModuleAnalysisInfo &MAI) {
+  bool IsBool = Width == 0;
+  bool IsWide = Width == 64;
 
   auto [CacheIt, Inserted] =
       ScalarConstantCache.try_emplace({TypeReg.id(), Value}, MCRegister());
@@ -864,7 +789,7 @@ MCRegister SPIRVNonSemanticDebugHandler::findOrEmitScalarConstant(
     Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Lo_32(Value))));
     if (IsWide) {
       Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Hi_32(Value))));
-    } else if (BT->getSizeInBits() == 16) {
+    } else if (Width == 16) {
       // A 16-bit float takes one word, the same as a 32-bit one, so the text
       // printer needs the width to pick the right APFloat semantics.
       Inst.setFlags(SPIRV::INST_PRINTER_WIDTH16);
@@ -875,6 +800,22 @@ MCRegister SPIRVNonSemanticDebugHandler::findOrEmitScalarConstant(
   return Reg;
 }
 
+MCRegister SPIRVNonSemanticDebugHandler::findOrEmitScalarConstant(
+    const DIBasicType *BT, uint64_t Value, SPIRV::ModuleAnalysisInfo &MAI) {
+  MCRegister TypeReg = findOrEmitScalarType(BT, MAI);
+  if (!TypeReg.isValid())
+    return MCRegister();
+
+  bool IsBool = isBooleanType(BT);
+  bool IsFloat = BT->getEncoding() == dwarf::DW_ATE_float;
+  unsigned Opcode =
+      IsBool    ? (Value ? SPIRV::OpConstantTrue : SPIRV::OpConstantFalse)
+      : IsFloat ? SPIRV::OpConstantF
+                : SPIRV::OpConstantI;
+  return findOrEmitConstant(Opcode, TypeReg, Value,
+                            IsBool ? 0 : BT->getSizeInBits(), MAI);
+}
+
 MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeInt32(
     SPIRV::ModuleAnalysisInfo &MAI) {
   for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
@@ -1207,13 +1148,6 @@ mapExprOperand(const DIExpression::ExprOperand &Op) {
   return Operands;
 }
 
-// Whether every operand of \p Expr maps to a DebugOperation.
-static bool isLowerableExpression(const DIExpression *Expr) {
-  return all_of(Expr->expr_ops(), [](const DIExpression::ExprOperand &Op) {
-    return mapExprOperand(Op).has_value();
-  });
-}
-
 std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugOperation(
     const DIExpression::ExprOperand &Op, MCRegister VoidTypeReg,
     MCRegister I32TypeReg, MCRegister ExtInstSetReg,
@@ -1645,7 +1579,6 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   LastLineMI = nullptr;
   LastScopeMI = nullptr;
   Records.clear();
-  DomTree.reset();
 }
 
 void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
@@ -1774,12 +1707,6 @@ void SPIRVNonSemanticDebugHandler::emitDebugBinding(
   if (!ExprRegOpt)
     return;
 
-  // Both lookups come first, so only a record that will be emitted opens a
-  // region.
-  //
-  // emitDebugScopeForInstruction() fails when this record's scope has no
-  // emitted id. The record would then fall into whatever region is open and
-  // name a scope the variable is not in, so drop it.
   if (!emitDebugScopeForInstruction(MI))
     return;
   emitDebugLineForInstruction(MI);
@@ -1791,10 +1718,10 @@ void SPIRVNonSemanticDebugHandler::emitDebugBinding(
               {*VarRegOpt, LocationReg, *ExprRegOpt}, MAI);
 }
 
-// The OpConstant id for the constant that \p MI assigns, or nullopt if \p MI
-// does not assign a constant collected by this backend.
-std::optional<MCRegister> SPIRVNonSemanticDebugHandler::getConstantValueReg(
-    const MachineInstr &MI) const {
+// The scalar type and value of the constant \p MI assigns, or nullopt when
+// this backend cannot name it.
+static std::optional<std::pair<const DIBasicType *, uint64_t>>
+getConstantDbgValueBits(const MachineInstr &MI) {
   if (!MI.isNonListDebugValue() || MI.isIndirectDebugValue())
     return std::nullopt;
 
@@ -1811,11 +1738,20 @@ std::optional<MCRegister> SPIRVNonSemanticDebugHandler::getConstantValueReg(
   if (Value.isImm())
     Bits = constantBits(static_cast<uint64_t>(Value.getImm()), BT);
   else if (Value.isCImm())
-    Bits = constantBits(Value.getCImm(), BT);
+    Bits = constantBits(Value.getCImm()->getValue(), BT);
   else
     Bits = constantBits(Value.getFPImm()->getValueAPF().bitcastToAPInt(), BT);
+  return std::make_pair(BT, Bits);
+}
 
-  auto It = ConstantValueRegs.find({BT, Bits});
+std::optional<MCRegister> SPIRVNonSemanticDebugHandler::getConstantValueReg(
+    const MachineInstr &MI) const {
+  std::optional<std::pair<const DIBasicType *, uint64_t>> Const =
+      getConstantDbgValueBits(MI);
+  if (!Const)
+    return std::nullopt;
+
+  auto It = ConstantValueRegs.find(*Const);
   if (It == ConstantValueRegs.end() || !It->second.isValid())
     return std::nullopt;
   return It->second;
@@ -1914,7 +1850,8 @@ SPIRVNonSemanticDebugHandler::resolveDebugLocTarget(const MachineInstr *MI) {
 }
 
 void SPIRVNonSemanticDebugHandler::resolveDebugRecord(
-    const MachineInstr &MI, const MachineInstr *LastEmitted) {
+    const MachineInstr &MI, const MachineInstr *LastEmitted,
+    const MachineDominatorTree &DomTree) {
   SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
   const MachineFunction &MF = *MI.getMF();
 
@@ -1936,8 +1873,6 @@ void SPIRVNonSemanticDebugHandler::resolveDebugRecord(
     return;
   }
 
-  // A constant assignment names the id emitted for it at module scope, which
-  // every function body follows.
   if (std::optional<MCRegister> ConstReg = getConstantValueReg(MI)) {
     placeRecord(SPIRV::NonSemanticExtInst::DebugValue, MI, *ConstReg, Anchor);
     return;
@@ -1960,7 +1895,7 @@ void SPIRVNonSemanticDebugHandler::resolveDebugRecord(
   // record naming a definition that does not dominate it is dropped.
   const MachineInstr *Def = MF.getRegInfo().getUniqueVRegDef(*ValReg);
   if (ModuleScopeIds.contains(ValueReg) ||
-      (isEmitted(Def, MAI) && DomTree->dominates(Def, &MI)))
+      (isEmitted(Def, MAI) && DomTree.dominates(Def, &MI)))
     placeRecord(SPIRV::NonSemanticExtInst::DebugValue, MI, ValueReg, Anchor);
 }
 
@@ -1968,15 +1903,17 @@ void SPIRVNonSemanticDebugHandler::analyzeDebugRecords(
     const MachineFunction &MF) {
   assert(CurrentMAI && "CurrentMAI must be set");
 
+  // Built on the first record rather than required as an analysis pass, which
+  // would build a tree for every SPIR-V compilation.
+  std::optional<MachineDominatorTree> DomTree;
+
   for (const MachineBasicBlock &MBB : MF) {
     const MachineInstr *LastEmitted = nullptr;
     for (const MachineInstr &MI : MBB) {
       if (MI.isDebugValueLike()) {
-        if (!DomTree) {
-          DomTree = std::make_unique<MachineDominatorTree>();
-          DomTree->recalculate(const_cast<MachineFunction &>(MF));
-        }
-        resolveDebugRecord(MI, LastEmitted);
+        if (!DomTree)
+          DomTree.emplace(const_cast<MachineFunction &>(MF));
+        resolveDebugRecord(MI, LastEmitted, *DomTree);
       }
 
       if (isEmitted(&MI, *CurrentMAI))
@@ -2098,10 +2035,11 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
   unsigned Line = DL->getLine();
   unsigned Col = DL->getColumn();
 
+  unsigned I32TypeId = CachedOpTypeInt32Reg.id();
   MCRegister SrcReg = DebugSourceRegByFileStr.lookup(FileStrReg.id());
-  MCRegister LineReg = I32ConstantCache.lookup(Line);
-  MCRegister ColStartReg = I32ConstantCache.lookup(Col);
-  MCRegister ColEndReg = I32ConstantCache.lookup(Col + 1);
+  MCRegister LineReg = ScalarConstantCache.lookup({I32TypeId, Line});
+  MCRegister ColStartReg = ScalarConstantCache.lookup({I32TypeId, Col});
+  MCRegister ColEndReg = ScalarConstantCache.lookup({I32TypeId, Col + 1});
 
   // The elements of each collected DILocation (DebugSource, line/column
   // constants) are pre-emitted from LLVM-IR instruction !dbg attachments and
@@ -2157,22 +2095,29 @@ void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
   tryEmitDebugFunctionDefinition(*CurrentMAI);
 }
 
-void SPIRVNonSemanticDebugHandler::collectDebugExpressions(
-    SetVector<const DIExpression *> &Out) const {
-  MachineModuleInfo *ModuleInfo = Asm->MMI;
-  assert(ModuleInfo && "MachineModuleInfo must be set during module output");
-
-  for (const Function &F : *ModuleInfo->getModule()) {
-    const MachineFunction *MF = ModuleInfo->getMachineFunction(F);
+static void forEachDebugValueLike(const MachineModuleInfo &ModuleInfo,
+                                  function_ref<void(const MachineInstr &)> Fn) {
+  for (const Function &F : *ModuleInfo.getModule()) {
+    const MachineFunction *MF = ModuleInfo.getMachineFunction(F);
     if (!MF)
       continue;
     for (const MachineBasicBlock &MBB : *MF)
       for (const MachineInstr &MI : MBB)
         if (MI.isDebugValueLike())
-          Out.insert(MI.getDebugExpression());
+          Fn(MI);
   }
 }
 
+void SPIRVNonSemanticDebugHandler::collectDebugExpressions(
+    SetVector<const DIExpression *> &Out) const {
+  MachineModuleInfo *ModuleInfo = Asm->MMI;
+  assert(ModuleInfo && "MachineModuleInfo must be set during module output");
+
+  forEachDebugValueLike(*ModuleInfo, [&](const MachineInstr &MI) {
+    Out.insert(MI.getDebugExpression());
+  });
+}
+
 void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
     SPIRV::ModuleAnalysisInfo &MAI) {
   if (GlobalDIEmitted)
@@ -2410,20 +2355,24 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
     if (const DILocation *IA = DL->getInlinedAt())
       getOrEmitDebugInlinedAt(IA, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
 
-  // Both maps are populated by now, so repeating the lookups that
-  // emitDebugBinding() performs emits a type and a constant only for an
-  // assignment that will reach a DebugValue.
-  for (const SPIRV::ConstantAssignment &CA : ConstantAssignments) {
-    if (!DebugLocalVariableRegs.contains(CA.Var) ||
-        !DebugExpressionRegs.contains(CA.Expr))
-      continue;
-    auto [It, Inserted] =
-        ConstantValueRegs.try_emplace({CA.Type, CA.Bits}, MCRegister());
+  // Repeating the lookups emitDebugBinding() performs emits a type and a
+  // constant only for an assignment that will reach a DebugValue.
+  MachineModuleInfo *ModuleInfo = Asm->MMI;
+  assert(ModuleInfo && "MachineModuleInfo must be set during module output");
+  forEachDebugValueLike(*ModuleInfo, [&](const MachineInstr &MI) {
+    std::optional<std::pair<const DIBasicType *, uint64_t>> Const =
+        getConstantDbgValueBits(MI);
+    if (!Const)
+      return;
+    if (!DebugLocalVariableRegs.contains(MI.getDebugVariable()) ||
+        !DebugExpressionRegs.contains(MI.getDebugExpression()))
+      return;
+    auto [It, Inserted] = ConstantValueRegs.try_emplace(*Const, MCRegister());
     if (Inserted)
-      It->second = findOrEmitScalarConstant(CA.Type, CA.Bits, MAI);
+      It->second = findOrEmitScalarConstant(Const->first, Const->second, MAI);
     if (!It->second.isValid())
       ConstantValueRegs.erase(It);
-  }
+  });
 
   for (const DILocation *DL : UniqueDebugLocations) {
     emitOpConstantI32(DL->getLine(), I32TypeReg, MAI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 30c48afeafc4f..2008e7d0f1be0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -27,7 +27,6 @@
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/CodeGen/DebugHandlerBase.h"
-#include "llvm/CodeGen/MachineDominators.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCRegister.h"
@@ -36,18 +35,9 @@
 namespace llvm {
 
 class GlobalVariable;
+class MachineDominatorTree;
 class SPIRVSubtarget;
 
-namespace SPIRV {
-/// A #dbg_value that assigns a literal, as collected from LLVM IR.
-struct ConstantAssignment {
-  const DIBasicType *Type; ///< The variable's type, stripped to a scalar.
-  uint64_t Bits;           ///< The value, narrowed to that type.
-  const DILocalVariable *Var;
-  const DIExpression *Expr;
-};
-} // namespace SPIRV
-
 /// AsmPrinter handler that emits NonSemantic.Shader.DebugInfo.100 (NSDI)
 /// instructions for the SPIR-V backend. Registered with SPIRVAsmPrinter when
 /// the module contains debug info (llvm.dbg.cu).
@@ -169,25 +159,15 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   MCRegister CachedOpTypeInt32Reg;
 
-  // Cache of already-emitted i32 constants, keyed by value. Prevents
-  // duplicate OpConstant instructions for the same integer value.
-  DenseMap<uint32_t, MCRegister> I32ConstantCache;
-
   // Result id per SPIR-V scalar type this handler needed, keyed by opcode and
   // width. MB_TypeConstVars holds only what module analysis collected, so a
   // type emitted here has to be remembered separately to stay unique.
   DenseMap<std::pair<unsigned, int64_t>, MCRegister> ScalarTypeCache;
 
-  // Result id per scalar constant, keyed by its type's id and its bits.
-  // MB_TypeConstVars does not hold what this handler emitted either, and
-  // keying on the type lets two DIBasicTypes describing it share a constant.
+  // Result id per scalar constant, keyed by its type's id and its bits. Keying
+  // on the type lets two DIBasicTypes describing it share a constant.
   DenseMap<std::pair<unsigned, uint64_t>, MCRegister> ScalarConstantCache;
 
-  // Constant assignments collected in beginModule(). A record only earns an
-  // OpConstant when it can produce a DebugValue, so these are filtered rather
-  // than emitted wholesale.
-  SmallVector<SPIRV::ConstantAssignment> ConstantAssignments;
-
   // Result id per collected constant, keyed by its type and bits. Filled
   // during module-scope emission, since an OpConstant has to precede every
   // function body that names it.
@@ -245,12 +225,6 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
     MCRegister LocationReg; ///< The Variable or Value operand.
   };
 
-  // A record can name a function-local definition only when that definition
-  // dominates the record. Requiring MachineDominatorTreeWrapperPass in
-  // SPIRVAsmPrinter::getAnalysisUsage() would build a tree for every SPIR-V
-  // compilation, so this one is built on the first record of each function.
-  std::unique_ptr<MachineDominatorTree> DomTree;
-
   // Records to emit before their anchor is printed, built once per function by
   // analyzeDebugRecords() and only read afterwards.
   DenseMap<const MachineInstr *, SmallVector<DebugRecord>> Records;
@@ -331,9 +305,8 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   /// Open the DebugScope region \p MI belongs to, closing the previous one.
   ///
-  /// \returns False when module-scope emission produced no DebugScope for that
-  /// scope, or no DebugInlinedAt for its chain, so the open region is not
-  /// \p MI's.
+  /// \returns False when \p MI's scope has no \c DebugScope, or its chain no
+  /// \c DebugInlinedAt, leaving the open region someone else's.
   bool emitDebugScopeForInstruction(const MachineInstr *MI);
   void emitDebugLineForInstruction(const MachineInstr *MI);
   void preparePerFunctionDebug(const MachineFunction *MF);
@@ -539,18 +512,18 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   /// with no module-scope id, a physical register, a variadic value, an
   /// instruction reference, or a definition that does not dominate \p MI.
   void resolveDebugRecord(const MachineInstr &MI,
-                          const MachineInstr *LastEmitted);
+                          const MachineInstr *LastEmitted,
+                          const MachineDominatorTree &DomTree);
 
   /// Store \p MI on \p Anchor, to emit before \p Anchor is printed.
   void placeRecord(SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
                    const MachineInstr &MI, MCRegister LocationReg,
                    const MachineInstr *Anchor);
 
-  /// Emit the records \c analyzeDebugRecords() anchored on \p Anchor.
   void emitAnalyzedRecords(const MachineInstr *Anchor);
 
-  /// The module-scope constant \p MI assigns, or \c std::nullopt when \p MI
-  /// is not a constant assignment this backend collected a constant for.
+  /// The \c OpConstant id for the constant \p MI assigns, or \c std::nullopt
+  /// when \p MI assigns no constant this backend emitted.
   std::optional<MCRegister> getConstantValueReg(const MachineInstr &MI) const;
 
   /// Result id of the constant \p Opcode of type \p TypeReg holding
@@ -567,11 +540,17 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   MCRegister findOrEmitScalarType(const DIBasicType *BT,
                                   SPIRV::ModuleAnalysisInfo &MAI);
 
-  /// Result id of the constant \p Value of type \p BT, reusing one from
-  /// \c MB_TypeConstVars when the module already defines it.
+  /// Result id of the constant \p Value, with \p BT giving both the SPIR-V
+  /// type and the opcode. Invalid when the type may not be created.
   MCRegister findOrEmitScalarConstant(const DIBasicType *BT, uint64_t Value,
                                       SPIRV::ModuleAnalysisInfo &MAI);
 
+  /// Result id of the constant \p Value of type \p TypeReg, reusing one from
+  /// \c MB_TypeConstVars or from an earlier call when either already has it.
+  MCRegister findOrEmitConstant(unsigned Opcode, MCRegister TypeReg,
+                                uint64_t Value, unsigned Width,
+                                SPIRV::ModuleAnalysisInfo &MAI);
+
   /// Emit \p Opcode naming \p MI's variable, \p LocationReg and \p MI's
   /// expression, after \p MI's own \c DebugScope and \c DebugLine. Emits
   /// nothing when \p MI's variable has no \c DebugLocalVariable, its
diff --git a/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
index 3def8d190dddc..87f75529d1290 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
@@ -523,10 +523,6 @@ static void generateAssignType(MachineInstr &MI, Register ResultRegister,
                     << " with type: " << *ResultType);
   MachineIRBuilder MIB(MI);
   updateRegType(ResultRegister, nullptr, ResultType, GR, MIB, MRI);
-  // updateRegType() moves the insert point past any debug record following the
-  // definition. Leaving it there puts the ASSIGN_TYPE, which takes over the
-  // def below, after a DBG_VALUE that names the register, so the record would
-  // precede its own definition. Pin the insert point back.
   MIB.setInsertPt(*MI.getParent(), std::next(MI.getIterator()));
 
   // Tablegen definition assumes SPIRV::ASSIGN_TYPE pseudo-instruction is
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
index 464306463fd10..9766cd72e6521 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
@@ -7,21 +7,28 @@
 ; optional-capability types, so the debug handler must reuse each type and
 ; emit only the constants needed by the DebugValue records.
 ;
+; An 8-bit integer is gated by Int8 exactly as 16 and 64 are gated by Int16 and
+; Int64, so a char assignment binds here and is dropped in a module without the
+; type.
+;
 ; The signature uses no 32-bit integer, so this is the one module in the suite
 ; where the handler creates OpTypeInt 32 0 rather than finding it. That type
 ; has a second owner, the line and column constants every DebugLine needs, and
 ; two declarations of it are a duplicate the validator rejects.
 
+; CHECK-DAG: OpCapability Int8
 ; CHECK-DAG: OpCapability Int16
 ; CHECK-DAG: OpCapability Int64
 ; CHECK-DAG: OpCapability Float16
 ; CHECK-DAG: OpCapability Float64
 ; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
 ; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[I8:%[0-9]+]] = OpTypeInt 8 0
 ; CHECK-DAG: [[I16:%[0-9]+]] = OpTypeInt 16 0
 ; CHECK-DAG: [[I64:%[0-9]+]] = OpTypeInt 64 0
 ; CHECK-DAG: [[F16:%[0-9]+]] = OpTypeFloat 16
 ; CHECK-DAG: [[F64:%[0-9]+]] = OpTypeFloat 64
+; CHECK-DAG: [[I8C:%[0-9]+]] = OpConstant [[I8]] 3{{ *$}}
 ; CHECK-DAG: [[I16C:%[0-9]+]] = OpConstant [[I16]] 7{{ *$}}
 ; CHECK-DAG: [[I64C:%[0-9]+]] = OpConstant [[I64]] 1234605616436508552{{ *$}}
 ; A half prints as its raw bit pattern and a double prints as a value, so
@@ -31,11 +38,13 @@
 ; CHECK-DAG: [[F64C:%[0-9]+]] = OpConstant [[F64]] 1{{ *$}}
 ; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
 ; CHECK-DAG: [[I32C:%[0-9]+]] = OpConstant [[I32]] 987654321{{ *$}}
+; CHECK-DAG: [[I8NAME:%[0-9]+]] = OpString "i8_constant"
 ; CHECK-DAG: [[I16NAME:%[0-9]+]] = OpString "i16_constant"
 ; CHECK-DAG: [[I64NAME:%[0-9]+]] = OpString "i64_constant"
 ; CHECK-DAG: [[F16NAME:%[0-9]+]] = OpString "half_constant"
 ; CHECK-DAG: [[F64NAME:%[0-9]+]] = OpString "double_constant"
 ; CHECK-DAG: [[I32NAME:%[0-9]+]] = OpString "i32_constant"
+; CHECK-DAG: [[I8VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[I8NAME]]
 ; CHECK-DAG: [[I16VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[I16NAME]]
 ; CHECK-DAG: [[I64VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[I64NAME]]
 ; CHECK-DAG: [[F16VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[F16NAME]]
@@ -43,6 +52,7 @@
 ; CHECK-DAG: [[I32VAR:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLocalVariable [[I32NAME]]
 
 ; CHECK: OpFunction
+; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[I8VAR]] [[I8C]]
 ; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[I16VAR]] [[I16C]]
 ; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[I64VAR]] [[I64C]]
 ; CHECK: OpExtInst [[VOID]] [[EXT]] DebugValue [[F16VAR]] [[F16C]]
@@ -62,11 +72,13 @@
 ; whole semantic type block and reaches end of file. Pairing each positive with
 ; its own negative would end each region at the next positive, before the point
 ; where emitNonSemanticGlobalDebugInfo() would add a duplicate.
+; UNIQUE-DAG: OpTypeInt 8 0
 ; UNIQUE-DAG: OpTypeInt 16 0
 ; UNIQUE-DAG: OpTypeInt 64 0
 ; UNIQUE-DAG: OpTypeFloat 16
 ; UNIQUE-DAG: OpTypeFloat 64
 ; UNIQUE-DAG: OpTypeInt 32 0
+; UNIQUE-NOT: OpTypeInt 8 0
 ; UNIQUE-NOT: OpTypeInt 16 0
 ; UNIQUE-NOT: OpTypeInt 64 0
 ; UNIQUE-NOT: OpTypeFloat 16
@@ -75,8 +87,9 @@
 
 target triple = "spirv64-unknown-unknown"
 
-define spir_func i64 @existing_types(i16 %s, i64 %l, half %h, double %d) !dbg !5 {
+define spir_func i64 @existing_types(i8 %c, i16 %s, i64 %l, half %h, double %d) !dbg !5 {
 entry:
+    #dbg_value(i8 3, !18, !DIExpression(), !15)
     #dbg_value(i16 7, !11, !DIExpression(), !15)
     #dbg_value(i64 1234605616436508552, !12, !DIExpression(), !15)
     #dbg_value(half 0xH3C00, !13, !DIExpression(), !15)
@@ -94,7 +107,7 @@ entry:
 !3 = !{i32 2, !"Debug Info Version", i32 3}
 !4 = !DISubroutineType(types: !6)
 !5 = distinct !DISubprogram(name: "existing_types", linkageName: "existing_types", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
-!6 = !{!8, !7, !8, !9, !10}
+!6 = !{!8, !19, !7, !8, !9, !10}
 !7 = !DIBasicType(name: "short", size: 16, encoding: DW_ATE_signed)
 !8 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
 !9 = !DIBasicType(name: "half", size: 16, encoding: DW_ATE_float)
@@ -106,3 +119,5 @@ entry:
 !15 = !DILocation(line: 6, column: 3, scope: !5)
 !16 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
 !17 = !DILocalVariable(name: "i32_constant", scope: !5, file: !1, line: 7, type: !16)
+!18 = !DILocalVariable(name: "i8_constant", scope: !5, file: !1, line: 8, type: !19)
+!19 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
index 0afb7cf28e0b4..1574c253d01d0 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
@@ -1,6 +1,7 @@
-; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not="OpCapability Int16" --implicit-check-not="OpCapability Int64" --implicit-check-not="OpCapability Float16" --implicit-check-not="OpCapability Float64"
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --implicit-check-not="OpCapability Int8" --implicit-check-not="OpCapability Int16" --implicit-check-not="OpCapability Int64" --implicit-check-not="OpCapability Float16" --implicit-check-not="OpCapability Float64"
 ; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=UNIQUE
 ; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=DROPPED
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --asm-verbose=0 --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=DEDUP
 ; 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 %}
 
 ; A constant assignment names an OpConstant, which satisfies DebugValue's
@@ -21,7 +22,7 @@
 ;
 ; A non-semantic instruction can be removed from a module without changing it,
 ; so debug info must not make the module require something it otherwise would
-; not. OpTypeInt 16, OpTypeInt 64, OpTypeFloat 16 and OpTypeFloat 64 each
+; not. OpTypeInt 8, OpTypeInt 16, OpTypeInt 64, OpTypeFloat 16 and OpTypeFloat 64 each
 ; oblige the module to declare a capability, and spirv-val rejects the module
 ; without it, so a narrow or wide constant the module does not already define
 ; is dropped instead. The implicit-check-not options on the first RUN line
@@ -45,7 +46,16 @@
 
 ; UNIQUE-COUNT-1: OpTypeBool
 ; UNIQUE-NOT: OpTypeBool
+
+; A constant assignment whose value collides with one the handler emits for its
+; own use must reuse it rather than declare a second. 100 is the
+; DebugInfoVersion operand of DebugCompilationUnit, and it is emitted well
+; before the assignment that also wants it, so this needs its own FileCheck
+; pass to keep the negative region running to end of file.
+; DEDUP-COUNT-1: OpConstant {{%[0-9]+}} 100{{ *$}}
+; DEDUP-NOT: OpConstant {{%[0-9]+}} 100{{ *$}}
 ; CHECK-DAG: [[NEGNAME:%[0-9]+]] = OpString "negative"
+; CHECK-DAG: [[TINYNAME:%[0-9]+]] = OpString "tiny"
 ; CHECK-DAG: [[NARROWNAME:%[0-9]+]] = OpString "narrow"
 ; CHECK-DAG: [[WIDENAME:%[0-9]+]] = OpString "wide"
 ; CHECK-DAG: [[HALFNAME:%[0-9]+]] = OpString "half"
@@ -55,26 +65,32 @@
 ; CHECK-DAG: [[WIDESOURCENAME:%[0-9]+]] = OpString "wide_source"
 ; CHECK-DAG: [[WIDEFLOATNAME:%[0-9]+]] = OpString "wide_float_location"
 ; CHECK-DAG: [[WIDENEDNEGNAME:%[0-9]+]] = OpString "widened_negative"
+; CHECK-DAG: [[VERSIONNAME:%[0-9]+]] = OpString "collides_with_version"
 ; CHECK-DAG: [[F32NAME:%[0-9]+]] = OpString "single"
+; CHECK-DAG: [[C100:%[0-9]+]] = OpConstant [[I32]] 100{{ *$}}
 ; CHECK-DAG: [[NEGVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[NEGNAME]]
 ; The narrow and wide variables still get a DebugLocalVariable. Only the
 ; binding to their value is dropped, since naming it would need a type the
 ; module does not have. The DROPPED prefix below asserts that: it captures the
 ; four ids from the module section, which precedes every function body, so its
 ; negative region covers all of them.
+; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[TINYNAME]]
 ; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[NARROWNAME]]
 ; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[WIDENAME]]
 ; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[HALFNAME]]
 ; CHECK-DAG: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DOUBLENAME]]
 
+; DROPPED-DAG: [[DTINYNAME:%[0-9]+]] = OpString "tiny"
 ; DROPPED-DAG: [[DNARROWNAME:%[0-9]+]] = OpString "narrow"
 ; DROPPED-DAG: [[DWIDENAME:%[0-9]+]] = OpString "wide"
 ; DROPPED-DAG: [[DHALFNAME:%[0-9]+]] = OpString "half"
 ; DROPPED-DAG: [[DDOUBLENAME:%[0-9]+]] = OpString "double"
+; DROPPED-DAG: [[DTINY:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DTINYNAME]]
 ; DROPPED-DAG: [[DNARROW:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DNARROWNAME]]
 ; DROPPED-DAG: [[DWIDE:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DWIDENAME]]
 ; DROPPED-DAG: [[DHALF:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DHALFNAME]]
 ; DROPPED-DAG: [[DDOUBLE:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[DDOUBLENAME]]
+; DROPPED-NOT: DebugValue [[DTINY]]
 ; DROPPED-NOT: DebugValue [[DNARROW]]
 ; DROPPED-NOT: DebugValue [[DWIDE]]
 ; DROPPED-NOT: DebugValue [[DHALF]]
@@ -85,6 +101,7 @@
 ; CHECK-DAG: [[WIDEFLOATVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[WIDEFLOATNAME]]
 ; CHECK-DAG: [[WIDENEDNEGVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[WIDENEDNEGNAME]]
 ; CHECK-DAG: [[F32VAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[F32NAME]]
+; CHECK-DAG: [[VERSIONVAR:%[0-9]+]] = OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugLocalVariable [[VERSIONNAME]]
 
 ; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue {{%[0-9]+}} [[C42]]
 ; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue {{%[0-9]+}} [[TRUE]]
@@ -95,6 +112,7 @@
 ; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[WIDESOURCEVAR]] [[C42]]
 ; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[WIDENEDNEGVAR]] [[CNEG]]
 ; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[F32VAR]] [[F32C]]
+; CHECK: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[VERSIONVAR]] [[C100]]
 ; CHECK-NOT: DebugValue [[WIDEFLOATVAR]]
 
 target triple = "spirv64-unknown-unknown"
@@ -105,6 +123,7 @@ entry:
     #dbg_value(i1 true, !11, !DIExpression(), !10)
     #dbg_value(i1 false, !12, !DIExpression(), !10)
     #dbg_value(i32 -1, !13, !DIExpression(), !10)
+    #dbg_value(i8 3, !32, !DIExpression(), !10)
     #dbg_value(i16 7, !14, !DIExpression(), !10)
     #dbg_value(i64 1234605616436508552, !15, !DIExpression(), !10)
     #dbg_value(half 0xH3C00, !16, !DIExpression(), !10)
@@ -114,6 +133,7 @@ entry:
     #dbg_value(i128 18446744073709551658, !25, !DIExpression(), !10)
     #dbg_value(i16 -1, !27, !DIExpression(), !10)
     #dbg_value(float 1.000000e+00, !30, !DIExpression(), !10)
+    #dbg_value(i32 100, !31, !DIExpression(), !10)
     #dbg_value(fp128 0xL00000000000000003FFF000000000000, !26, !DIExpression(DW_OP_LLVM_convert, 64, DW_ATE_unsigned), !10)
   ret i32 %x, !dbg !10
 }
@@ -152,3 +172,6 @@ entry:
 !28 = !DIBasicType(name: "another int", size: 32, encoding: DW_ATE_signed)
 !29 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
 !30 = !DILocalVariable(name: "single", scope: !5, file: !1, line: 16, type: !29)
+!31 = !DILocalVariable(name: "collides_with_version", scope: !5, file: !1, line: 17, type: !7)
+!32 = !DILocalVariable(name: "tiny", scope: !5, file: !1, line: 18, type: !33)
+!33 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-value-typedef-cycle.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-typedef-cycle.ll
new file mode 100644
index 0000000000000..ae53ce6fd73ab
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-typedef-cycle.ll
@@ -0,0 +1,36 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
+
+; A typedef chain that closes on itself. Verifier::visitDIDerivedType checks
+; only that a base type is a type, so this passes the IR verifier, and
+; stripToScalarType() has to terminate on it rather than walk the cycle
+; forever. No source language can write one, since a typedef name is not in
+; scope in its own declaration.
+;
+; The variable's type never resolves to a DIBasicType, so the constant is not
+; named and the record is dropped. Reaching the end of the module at all is
+; what this test is for.
+
+; CHECK: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func void @cyclic_typedef() !dbg !5 {
+entry:
+    #dbg_value(i32 7, !9, !DIExpression(), !10)
+  ret void, !dbg !10
+}
+
+!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-value-typedef-cycle.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !6)
+!6 = !{null}
+!5 = distinct !DISubprogram(name: "cyclic_typedef", linkageName: "cyclic_typedef", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !DIDerivedType(tag: DW_TAG_typedef, name: "a", baseType: !8)
+!8 = !DIDerivedType(tag: DW_TAG_typedef, name: "b", baseType: !7)
+!9 = !DILocalVariable(name: "cyclic", scope: !5, file: !1, line: 2, type: !7)
+!10 = !DILocation(line: 3, column: 3, scope: !5)



More information about the llvm-commits mailing list