[llvm] 3eecc40 - [SPIRV] Emit NonSemantic DebugValue (#224158)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 23 08:47:36 PDT 2026
Author: Diego Novillo
Date: 2026-09-23T11:47:27-04:00
New Revision: 3eecc4081d4ee225eda7375eee9f79e065f3c411
URL: https://github.com/llvm/llvm-project/commit/3eecc4081d4ee225eda7375eee9f79e065f3c411
DIFF: https://github.com/llvm/llvm-project/commit/3eecc4081d4ee225eda7375eee9f79e065f3c411.diff
LOG: [SPIRV] Emit NonSemantic DebugValue (#224158)
This adds support for emitting `DebugValue` in
`SPIRVNonSemanticDebugHandler` for `DBG_VALUE` records.
NOTE: Much of the new code was necessary to emit constants and their
types at module scope. I also added a bunch of tests as I started
getting into corner cases.
Since
[`DebugValue`](https://github.khronos.org/SPIRV-Registry/nonsemantic/NonSemantic.Shader.DebugInfo.html#DebugValue)
cannot have forward references, it can only reference already emitted
instructions. `DebugValue` cannot come after merge instructions either,
and SPIR-V requires an id defined in a function block to dominate a
non-phi use. The change has four parts:
1. `analyzeDebugRecords()` resolves placement and availability before
emission begins. Each record stays at its source position unless it lies
between a merge instruction and its terminator, in which case it moves
before the merge. A register value is accepted only when its definition
dominates the record. Records without a legal value are dropped.
2. A constant assignment needs a typed SPIR-V result id, but Machine IR
carries only untyped immediates. The type comes from the variable, and
`buildConstDbgValue()` decides whether the value is sign or zero
extended. The backend reuses a module `OpConstant` with that type and
value, or emits one alongside the line and column constants.
3. Debug info must be removable without changing what the module
requires. The handler creates `OpTypeBool` and 32-bit scalar types,
which need no capability, and reuses 8, 16 and 64-bit types only when
the module already defines them. Any other constant assignment is
dropped.
5. `SPIRVPostLegalizer::generateAssignType` placed `ASSIGN_TYPE` after a
`DBG_VALUE` naming the same register, because `updateRegType()` advances
the insertion point past the debug record. This change resets the
insertion point to just after the definition, so `ASSIGN_TYPE` comes
before the record that names it.
Added:
llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value-cross-block.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value-merge-region.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value-module-scope.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value-postlegalizer-order.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value-typedef-cycle.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value-unsupported.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-value.ll
Modified:
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
llvm/test/CodeGen/SPIRV/debug-info/debug-declare-dbg-value-variadic.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-declare.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-inlined-at.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-line-selection-merge.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-local-variable-dbg-value.ll
llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
Removed:
################################################################################
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 3fcd0a7558356..05e79d8f8fb98 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -11,11 +11,15 @@
#include "MCTargetDesc/SPIRVMCTargetDesc.h"
#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"
@@ -259,6 +263,67 @@ unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
}
}
+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);
+}
+
+// The DIBasicType that \p Ty resolves to through any typedefs, or null.
+static const DIBasicType *stripToScalarType(const DIType *Ty) {
+ // 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 || !Seen.insert(DT).second)
+ 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:
+ 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;
+ }
+}
+
// Collect distinct DILocations and DILocalVariables from LLVM IR.
//
// DILocations come from instruction debug locations and from the debug records
@@ -337,10 +402,13 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
ScopeToPathOpStringReg.clear();
DebugSourceRegByFileStr.clear();
OpStringContentCache.clear();
- I32ConstantCache.clear();
+ ScalarTypeCache.clear();
+ ScalarConstantCache.clear();
+ ConstantValueRegs.clear();
DebugTypeFunctionCache.clear();
DebugOperationCache.clear();
DebugExpressionCache.clear();
+ ModuleScopeIds.clear();
GlobalDIEmitted = false;
GlobalNSDIEnabled = false;
CurrentMAI = nullptr;
@@ -426,6 +494,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 +519,11 @@ 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();
+
+ for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
+ if (MCRegister Id = getResultId(*MI, MAI))
+ ModuleScopeIds.insert(Id);
+ }
}
void SPIRVNonSemanticDebugHandler::emitMCInst(MCInst &Inst) {
@@ -511,19 +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;
-
- 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(
@@ -585,6 +657,165 @@ MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeVoid(
return Reg;
}
+// Whether declaring \p BT's SPIR-V type would require a capability. The type
+// 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))
+ 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 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();
+
+ 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 there is nothing to compare.
+ if (!IsBool && !constantHasValue(*MI, Value, IsWide))
+ continue;
+ return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+ }
+ return MCRegister();
+}
+
+// \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());
+ 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 (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);
+ }
+ }
+ emitMCInst(Inst);
+ CacheIt->second = Reg;
+ 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)) {
@@ -898,24 +1129,34 @@ 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;
+}
+
+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;
@@ -1343,6 +1584,7 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
DebugFunctionDefinitionEmitted = false;
LastLineMI = nullptr;
LastScopeMI = nullptr;
+ Records.clear();
}
void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
@@ -1366,6 +1608,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(
@@ -1408,14 +1652,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
@@ -1454,15 +1698,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;
@@ -1471,15 +1713,75 @@ void SPIRVNonSemanticDebugHandler::emitDebugDeclare(const MachineInstr *MI) {
if (!ExprRegOpt)
return;
- SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
- MCRegister StorageReg = MAI.getRegisterAlias(MI->getMF(), *LocReg);
- if (!StorageReg.isValid())
+ 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 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;
+
+ 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()->getValue(), BT);
+ else
+ Bits = constantBits(Value.getFPImm()->getValueAPF().bitcastToAPInt(), BT);
+ return std::make_pair(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;
+}
+
+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) {
@@ -1487,9 +1789,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:
@@ -1509,7 +1816,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;
}
@@ -1539,12 +1846,105 @@ 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,
+ const MachineDominatorTree &DomTree) {
+ 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;
+ }
+
+ 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");
+
+ // 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.emplace(const_cast<MachineFunction &>(MF));
+ resolveDebugRecord(MI, LastEmitted, *DomTree);
+ }
+
+ 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");
@@ -1569,7 +1969,7 @@ void SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
ExtInstSetReg, {}, MAI);
LastScopeMI = nullptr;
}
- return;
+ return true;
}
const DIScope *CurScope = CurDL->getScope();
@@ -1579,19 +1979,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);
}
@@ -1600,6 +2000,7 @@ void SPIRVNonSemanticDebugHandler::emitDebugScopeForInstruction(
Ops, MAI);
LastScopeMI = MI;
+ return true;
}
void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
@@ -1640,10 +2041,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
@@ -1669,10 +2071,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
@@ -1699,22 +2101,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)
@@ -1955,6 +2364,25 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
if (const DILocation *IA = DL->getInlinedAt())
getOrEmitDebugInlinedAt(IA, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
+ // 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(Const->first, Const->second, 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..2008e7d0f1be0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -21,6 +21,7 @@
#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"
@@ -34,6 +35,7 @@
namespace llvm {
class GlobalVariable;
+class MachineDominatorTree;
class SPIRVSubtarget;
/// AsmPrinter handler that emits NonSemantic.Shader.DebugInfo.100 (NSDI)
@@ -46,10 +48,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>(
@@ -154,9 +159,20 @@ 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. Keying
+ // on the type lets two DIBasicTypes describing it share a constant.
+ DenseMap<std::pair<unsigned, uint64_t>, MCRegister> ScalarConstantCache;
+
+ // 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).
@@ -198,6 +214,21 @@ 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.
+ };
+
+ // 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 +303,11 @@ 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 \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);
void tryEmitDebugFunctionDefinition(SPIRV::ModuleAnalysisInfo &MAI);
@@ -462,15 +497,66 @@ 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,
+ 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);
+
+ void emitAnalyzedRecords(const MachineInstr *Anchor);
+
+ /// 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
+ /// \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, 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
+ /// 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..87f75529d1290 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPostLegalizer.cpp
@@ -523,6 +523,7 @@ static void generateAssignType(MachineInstr &MI, Register ResultRegister,
<< " with type: " << *ResultType);
MachineIRBuilder MIB(MI);
updateRegType(ResultRegister, nullptr, ResultType, GR, MIB, MRI);
+ 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..9766cd72e6521
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant-existing-type.ll
@@ -0,0 +1,123 @@
+; 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.
+;
+; 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
+; 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: [[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]]
+; 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 [[I8VAR]] [[I8C]]
+; 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 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
+; UNIQUE-NOT: OpTypeFloat 64
+; UNIQUE-NOT: OpTypeInt 32 0
+
+target triple = "spirv64-unknown-unknown"
+
+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)
+ #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, !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)
+!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)
+!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
new file mode 100644
index 0000000000000..1574c253d01d0
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-value-constant.ll
@@ -0,0 +1,177 @@
+; 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
+; 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 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
+; 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
+
+; 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"
+; 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: [[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]]
+; 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-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]]
+; 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: OpExtInst {{%[0-9]+}} {{%[0-9]+}} DebugValue [[VERSIONVAR]] [[C100]]
+; 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(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)
+ #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(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
+}
+
+!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)
+!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-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-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)
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)
More information about the llvm-commits
mailing list