[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