blob: 859b4bbefbbad9ce172d13cd8928e6e016fe5de2 [file] [edit]
//===-- SPIRVNonSemanticDebugHandler.cpp - NSDI AsmPrinter handler -*- C++
//-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "SPIRVNonSemanticDebugHandler.h"
#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"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/DebugProgramInstruction.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Path.h"
#include <cassert>
using namespace llvm;
namespace {
/// Look up \p Key in a register map and return its value, or std::nullopt when
/// the key is absent.
template <typename MapT>
static std::optional<MCRegister> lookupOptReg(const MapT &Map,
typename MapT::key_type Key) {
auto It = Map.find(Key);
if (It == Map.end())
return std::nullopt;
assert(It->second.isValid() && "invalid register stored in map");
return It->second;
}
/// Partition \p Ty into \p BasicTypes, \p PointerTypes, \p SubroutineTypes,
/// \p VectorTypes, \p ArrayTypes, \p CompositeTypes, and \p TypedefTypes for
/// NSDI emission. Used when iterating DebugInfoFinder.types(); each DI node is
/// seen once, so no recursion into pointer bases. Other composites and the
/// remaining derived kinds are ignored because they are not yet supported.
/// Only types that are supported (later used) are partitioned.
static void
partitionTypes(const DIType *Ty, SmallVector<const DIBasicType *> &BasicTypes,
SmallVector<const DIDerivedType *> &PointerTypes,
SmallVector<const DISubroutineType *> &SubroutineTypes,
SmallVector<const DICompositeType *> &VectorTypes,
SmallVector<const DICompositeType *> &ArrayTypes,
SmallVector<const DICompositeType *> &CompositeTypes,
SmallVector<const DIDerivedType *> &TypedefTypes) {
if (const auto *BT = dyn_cast<DIBasicType>(Ty)) {
BasicTypes.push_back(BT);
return;
}
if (const auto *ST = dyn_cast<DISubroutineType>(Ty)) {
SubroutineTypes.push_back(ST);
return;
}
if (const auto *CT = dyn_cast<DICompositeType>(Ty)) {
if (CT->getTag() == dwarf::DW_TAG_array_type) {
// A vector is an array with DINode::FlagVector. A plain array is the
// same tag without it. A matrix is also lowered to a DW_TAG_array_type
// (two subranges), so it is indistinguishable from a 2D array here and
// is emitted as a DebugTypeArray.
//
// FIXME: Emitting a matrix as a DebugTypeArray is valid but loses the
// matrix shape. DWARF has no matrix tag, so distinguishing a matrix needs
// a new DINode flag analogous to FlagVector, set on the array, plus a way
// to carry column-major vs row-major traits. Array-of-vectors alone would
// not disambiguate a matrix from a genuine array of vectors. Once the
// frontend marks matrices, route them to a DebugTypeMatrix path here.
if (CT->isVector())
VectorTypes.push_back(CT);
else
ArrayTypes.push_back(CT);
} else if (CT->getTag() == dwarf::DW_TAG_structure_type ||
CT->getTag() == dwarf::DW_TAG_class_type ||
CT->getTag() == dwarf::DW_TAG_union_type) {
CompositeTypes.push_back(CT);
}
return;
}
const auto *DT = dyn_cast<DIDerivedType>(Ty);
if (DT && DT->getTag() == dwarf::DW_TAG_pointer_type)
PointerTypes.push_back(DT);
else if (DT && DT->getTag() == dwarf::DW_TAG_typedef)
TypedefTypes.push_back(DT);
}
enum : uint32_t {
NSDIFlagIsProtected = 1u << 0,
NSDIFlagIsPrivate = 1u << 1,
NSDIFlagIsPublic = NSDIFlagIsPrivate | NSDIFlagIsProtected,
NSDIFlagIsLocal = 1u << 2,
NSDIFlagIsDefinition = 1u << 3,
NSDIFlagFwdDecl = 1u << 4,
NSDIFlagArtificial = 1u << 5,
NSDIFlagExplicit = 1u << 6,
NSDIFlagPrototyped = 1u << 7,
NSDIFlagObjectPointer = 1u << 8,
NSDIFlagStaticMember = 1u << 9,
NSDIFlagIndirectVariable = 1u << 10,
NSDIFlagLValueReference = 1u << 11,
NSDIFlagRValueReference = 1u << 12,
NSDIFlagIsOptimized = 1u << 13,
NSDIFlagIsEnumClass = 1u << 14,
NSDIFlagTypePassByValue = 1u << 15,
NSDIFlagTypePassByReference = 1u << 16,
NSDIFlagUnknownPhysicalLayout = 1u << 17,
};
static uint32_t mapDIFlagsToNonSemantic(DINode::DIFlags DFlags) {
uint32_t Flags = 0;
if ((DFlags & DINode::FlagAccessibility) == DINode::FlagPublic)
Flags |= NSDIFlagIsPublic;
if ((DFlags & DINode::FlagAccessibility) == DINode::FlagProtected)
Flags |= NSDIFlagIsProtected;
if ((DFlags & DINode::FlagAccessibility) == DINode::FlagPrivate)
Flags |= NSDIFlagIsPrivate;
if (DFlags & DINode::FlagFwdDecl)
Flags |= NSDIFlagFwdDecl;
if (DFlags & DINode::FlagArtificial)
Flags |= NSDIFlagArtificial;
if (DFlags & DINode::FlagExplicit)
Flags |= NSDIFlagExplicit;
if (DFlags & DINode::FlagPrototyped)
Flags |= NSDIFlagPrototyped;
if (DFlags & DINode::FlagObjectPointer)
Flags |= NSDIFlagObjectPointer;
if (DFlags & DINode::FlagStaticMember)
Flags |= NSDIFlagStaticMember;
if (DFlags & DINode::FlagLValueReference)
Flags |= NSDIFlagLValueReference;
if (DFlags & DINode::FlagRValueReference)
Flags |= NSDIFlagRValueReference;
if (DFlags & DINode::FlagTypePassByValue)
Flags |= NSDIFlagTypePassByValue;
if (DFlags & DINode::FlagTypePassByReference)
Flags |= NSDIFlagTypePassByReference;
if (DFlags & DINode::FlagEnumClass)
Flags |= NSDIFlagIsEnumClass;
return Flags;
}
static uint32_t transDebugFlags(const DINode *DN) {
uint32_t Flags = 0;
if (const auto *GV = dyn_cast<DIGlobalVariable>(DN)) {
if (GV->isLocalToUnit())
Flags |= NSDIFlagIsLocal;
if (GV->isDefinition())
Flags |= NSDIFlagIsDefinition;
}
if (const auto *SP = dyn_cast<DISubprogram>(DN)) {
if (SP->isLocalToUnit())
Flags |= NSDIFlagIsLocal;
if (SP->isOptimized())
Flags |= NSDIFlagIsOptimized;
if (SP->isDefinition())
Flags |= NSDIFlagIsDefinition;
Flags |= mapDIFlagsToNonSemantic(SP->getFlags());
}
if (DN->getTag() == dwarf::DW_TAG_reference_type)
Flags |= NSDIFlagLValueReference;
if (DN->getTag() == dwarf::DW_TAG_rvalue_reference_type)
Flags |= NSDIFlagRValueReference;
if (const auto *Ty = dyn_cast<DIType>(DN))
Flags |= mapDIFlagsToNonSemantic(Ty->getFlags());
if (const auto *LV = dyn_cast<DILocalVariable>(DN))
Flags |= mapDIFlagsToNonSemantic(LV->getFlags());
return Flags;
}
// Map a DWARF composite tag to a NonSemantic.Shader.DebugInfo Composite Type
// value: Class 0, Structure 1, Union 2.
static uint32_t mapCompositeTypeTag(unsigned Tag) {
switch (Tag) {
case dwarf::DW_TAG_class_type:
return 0;
case dwarf::DW_TAG_structure_type:
return 1;
case dwarf::DW_TAG_union_type:
return 2;
default:
reportFatalInternalError("unexpected DWARF composite tag " + Twine(Tag) +
". Expecting 0, 1 or 2");
}
}
static const MachineInstr *
findLastFunctionOpVariableDeclaration(const MachineFunction &MF,
SPIRV::ModuleAnalysisInfo &MAI) {
// We iterate over the instructions to find the last OpVariable instruction if
// any. The following SPIRV rule is used to terminate the traversal earlier:
// SPIR-V 2.16.1, Function Structure: "All OpVariable instructions in a
// function must be in the first block in the function. These instructions,
// together with any intermixed OpLine and OpNoLine instructions, must be the
// first instructions in that block."
const MachineInstr *LastOpVariable = nullptr;
bool SeenOpVariable = false;
for (const MachineInstr &MI : MF.front()) {
if (MI.getOpcode() == SPIRV::OpVariable) {
SeenOpVariable = true;
if (!MAI.getSkipEmission(&MI))
LastOpVariable = &MI;
continue;
}
bool CanInterleaveWithOpVariable =
MI.getOpcode() == SPIRV::OpLine || MI.getOpcode() == SPIRV::OpNoLine;
if (SeenOpVariable && !CanInterleaveWithOpVariable &&
!MAI.getSkipEmission(&MI))
break;
}
return LastOpVariable;
}
} // namespace
SPIRVNonSemanticDebugHandler::SPIRVNonSemanticDebugHandler(AsmPrinter &AP)
: DebugHandlerBase(&AP) {}
// Map DWARF source language codes to NonSemantic.Shader.DebugInfo.100 source
// language codes. Values are from the SourceLanguage enum in the
// NonSemantic.Shader.DebugInfo.100 specification, section 4.3.
unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
switch (DwarfSrcLang) {
case dwarf::DW_LANG_OpenCL:
return 3; // OpenCL_C
case dwarf::DW_LANG_OpenCL_CPP:
return 4; // OpenCL_CPP
case dwarf::DW_LANG_CPP_for_OpenCL:
return 6; // CPP_for_OpenCL
case dwarf::DW_LANG_GLSL:
return 2; // GLSL
case dwarf::DW_LANG_HLSL:
return 5; // HLSL
case dwarf::DW_LANG_SYCL:
return 7; // SYCL
case dwarf::DW_LANG_Zig:
return 12; // Zig
default:
return 0; // Unknown
}
}
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
// attached to them. DebugLine pre-emission and MIR lookups assume every
// machine-instruction debug location already appeared here; a codegen-only
// location would not be collected and emission will be skipped.
//
// DILocalVariables come from the DbgVariableRecords attached to instructions
// and from the retained nodes of each DISubprogram. Retained nodes are needed
// because a variable with no remaining debug record (e.g. optimized away) must
// still get a DebugLocalVariable.
static void collectDebugLocationsAndLocalVariables(
const Module &M, SetVector<const DILocation *> &Locations,
SetVector<const DILocalVariable *> &LVs) {
for (const Function &F : M) {
const DISubprogram *SP = F.getSubprogram();
if (!SP)
continue;
for (const MDNode *N : SP->getRetainedNodes())
if (const auto *LV = dyn_cast_or_null<DILocalVariable>(N))
LVs.insert(LV);
for (const Instruction &I : instructions(F)) {
if (const DILocation *DL = I.getDebugLoc().get())
Locations.insert(DL);
for (DbgRecord &DR : I.getDbgRecordRange()) {
if (const DILocation *DL = DR.getDebugLoc().get())
Locations.insert(DL);
if (const auto *DVR = dyn_cast<DbgVariableRecord>(&DR))
if (const DILocalVariable *LV = DVR->getVariable())
LVs.insert(LV);
}
}
}
}
// Insert \p S and its enclosing DILexicalBlock/DINamespace chain into \p Out,
// parent before child, so single-pass emission never needs a forward
// reference for the Parent operand.
static void collectLexicalBlockChain(const DIScope *S,
SetVector<const DIScope *> &Out) {
// Walk up child-first, then insert in reverse to get parents in first.
SmallVector<const DIScope *, 8> Chain;
while (S && !Out.contains(S) && isa<DILexicalBlock, DINamespace>(S)) {
Chain.push_back(S);
S = S->getScope();
}
Out.insert(Chain.rbegin(), Chain.rend());
}
void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
// The base class sets Asm = nullptr when the module has no compile units,
// and initializes lexical scope tracking otherwise.
DebugHandlerBase::beginModule(M);
if (!Asm)
return;
CompileUnits.clear();
BasicTypes.clear();
PointerTypes.clear();
SubroutineTypes.clear();
VectorTypes.clear();
ArrayTypes.clear();
CompositeTypes.clear();
TypedefTypes.clear();
SubprogramDeclarations.clear();
SubprogramDefinitions.clear();
UniqueDebugLocations.clear();
GlobalVariableDebugInfoMap.clear();
LocalVariables.clear();
DebugLocalVariableRegs.clear();
DebugExpressionRegs.clear();
LexicalBlocks.clear();
DebugScopeRegs.clear();
DebugInlinedAtRegs.clear();
ScopeToPathOpStringReg.clear();
DebugSourceRegByFileStr.clear();
OpStringContentCache.clear();
ScalarTypeCache.clear();
ScalarConstantCache.clear();
ConstantValueRegs.clear();
DebugTypeFunctionCache.clear();
DebugOperationCache.clear();
DebugExpressionCache.clear();
ModuleScopeIds.clear();
GlobalDIEmitted = false;
GlobalNSDIEnabled = false;
CurrentMAI = nullptr;
#ifndef NDEBUG
NonSemanticOpStringsSectionEmitted = false;
#endif
CachedDebugInfoNoneReg = MCRegister();
CachedEmptyStringReg = MCRegister();
CachedOpTypeVoidReg = MCRegister();
CachedOpTypeInt32Reg = MCRegister();
// Collect compile-unit info: file paths and source languages.
for (const DICompileUnit *CU : M->debug_compile_units()) {
const DIFile *File = CU->getFile();
CompileUnitInfo Info;
Info.TheCU = CU;
if (sys::path::is_absolute(File->getFilename()))
Info.FilePath = File->getFilename();
else
sys::path::append(Info.FilePath, File->getDirectory(),
File->getFilename());
// getName() returns the language code regardless of whether the name is
// versioned. getUnversionedName() would assert on versioned names.
Info.SpirvSourceLanguage = toNSDISrcLang(CU->getSourceLanguage().getName());
CompileUnits.push_back(std::move(Info));
}
// Collect DWARF version from module flags. For CodeView modules there is no
// "Dwarf Version" flag; DwarfVersion remains 0, which is the correct value
// for the DebugCompilationUnit DWARF Version operand in that case.
if (const NamedMDNode *Flags = M->getNamedMetadata("llvm.module.flags")) {
for (const auto *Op : Flags->operands()) {
const MDOperand &NameOp = Op->getOperand(1);
if (NameOp.equalsStr("Dwarf Version"))
DwarfVersion =
cast<ConstantInt>(
cast<ConstantAsMetadata>(Op->getOperand(2))->getValue())
->getSExtValue();
}
}
// Find all debug info types that may be referenced by NSDI instructions.
DebugInfoFinder Finder;
Finder.processModule(*M);
llvm::for_each(Finder.types(), [&](DIType *Ty) {
partitionTypes(Ty, BasicTypes, PointerTypes, SubroutineTypes, VectorTypes,
ArrayTypes, CompositeTypes, TypedefTypes);
});
for (const DISubprogram *SP : Finder.subprograms()) {
if (SP->isDefinition())
SubprogramDefinitions.push_back(SP);
else
SubprogramDeclarations.push_back(SP);
}
// Walk LLVM globals to map each DIGlobalVariable to its llvm::GlobalVariable.
DenseMap<const DIGlobalVariable *, const GlobalVariable *> DIGVToLLVMGV;
for (const GlobalVariable &G : M->globals()) {
SmallVector<DIGlobalVariableExpression *> GVEs;
G.getDebugInfo(GVEs);
for (DIGlobalVariableExpression *GVE : GVEs) {
if (const DIGlobalVariable *GV = GVE->getVariable()) {
DIGVToLLVMGV.try_emplace(GV, &G);
}
}
}
for (const DIGlobalVariableExpression *GVE : Finder.global_variables()) {
const DIGlobalVariable *GV = GVE->getVariable();
const DIExpression *Expr = GVE->getExpression();
GlobalVariableDebugInfoMap.try_emplace(
GV, GlobalVariableDebugInfo{Expr, DIGVToLLVMGV.lookup(GV)});
}
collectDebugLocationsAndLocalVariables(*M, UniqueDebugLocations,
LocalVariables);
// DILexicalBlock and DINamespace scopes are lowered to DebugLexicalBlock.
// Collect them in parent-before-child order so they can be later emitted in a
// single pass.
for (const DIScope *S : Finder.scopes())
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())
return;
if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_non_semantic_info))
return;
// Add the extension to requirements so OpExtension is output.
MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info);
// Add the NonSemantic.Shader.DebugInfo.100 entry to ExtInstSetMap so that
// outputOpExtInstImports() emits the OpExtInstImport instruction. Allocate a
// fresh result ID for it now; the same ID is used in emitExtInst() operands.
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) {
Asm->OutStreamer->emitInstruction(Inst, Asm->getSubtargetInfo());
}
MCRegister
SPIRVNonSemanticDebugHandler::emitOpString(StringRef S,
SPIRV::ModuleAnalysisInfo &MAI) {
MCRegister Reg = MAI.getNextIDRegister();
MCInst Inst;
Inst.setOpcode(SPIRV::OpString);
Inst.addOperand(MCOperand::createReg(Reg));
addStringImm(S, Inst);
emitMCInst(Inst);
return Reg;
}
MCRegister SPIRVNonSemanticDebugHandler::emitOpStringIfNew(
StringRef S, SPIRV::ModuleAnalysisInfo &MAI) {
#ifndef NDEBUG
assert(!NonSemanticOpStringsSectionEmitted &&
"emitOpStringIfNew is only valid while emitting SPIR-V section 7");
#endif
auto [It, Inserted] = OpStringContentCache.try_emplace(S, MCRegister());
if (Inserted)
It->second = emitOpString(S, MAI);
return It->second;
}
MCRegister SPIRVNonSemanticDebugHandler::getCachedOpStringReg(StringRef S) {
#ifndef NDEBUG
assert(NonSemanticOpStringsSectionEmitted &&
"getCachedOpStringReg requires emitNonSemanticDebugStrings() first");
#endif
auto It = OpStringContentCache.find(S);
assert(It != OpStringContentCache.end() &&
"NSDI OpString missing from cache; emitNonSemanticDebugStrings must "
"cache every string used in section 10");
return It->second;
}
MCRegister SPIRVNonSemanticDebugHandler::emitAndCacheScopePathOpStringReg(
const DIScope *Scope, SPIRV::ModuleAnalysisInfo &MAI) {
auto [It, Inserted] = ScopeToPathOpStringReg.try_emplace(Scope, MCRegister());
if (Inserted)
It->second = emitOpStringIfNew(getDebugFullPath(Scope), MAI);
return It->second;
}
MCRegister SPIRVNonSemanticDebugHandler::getCachedScopePathOpStringReg(
const DIScope *Scope, bool UseEmptyPathIfNullScope) {
if (!Scope) {
assert(UseEmptyPathIfNullScope &&
"null scope path lookup requires UseEmptyPathIfNullScope");
assert(CachedEmptyStringReg.isValid() &&
"empty path OpString must be cached in emitNonSemanticDebugStrings");
return CachedEmptyStringReg;
}
auto It = ScopeToPathOpStringReg.find(Scope);
assert(It != ScopeToPathOpStringReg.end() &&
"path OpString must be cached in emitNonSemanticDebugStrings");
MCRegister FileStrReg = It->second;
assert(FileStrReg.isValid() && "path OpString id must be valid once cached");
return FileStrReg;
}
MCRegister SPIRVNonSemanticDebugHandler::emitOpConstantI32(
uint32_t Value, MCRegister I32TypeReg, SPIRV::ModuleAnalysisInfo &MAI) {
return findOrEmitConstant(SPIRV::OpConstantI, I32TypeReg, Value,
/*Width=*/32, MAI);
}
MCRegister SPIRVNonSemanticDebugHandler::emitExtInst(
SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
MCRegister VoidTypeReg, MCRegister ExtInstSetReg,
ArrayRef<MCRegister> Operands, SPIRV::ModuleAnalysisInfo &MAI) {
MCRegister Reg = MAI.getNextIDRegister();
MCInst Inst;
Inst.setOpcode(SPIRV::OpExtInst);
Inst.addOperand(MCOperand::createReg(Reg));
Inst.addOperand(MCOperand::createReg(VoidTypeReg));
Inst.addOperand(MCOperand::createReg(ExtInstSetReg));
Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Opcode)));
for (MCRegister R : Operands)
Inst.addOperand(MCOperand::createReg(R));
emitMCInst(Inst);
return Reg;
}
MCRegister SPIRVNonSemanticDebugHandler::getOrEmitDebugTypeFunction(
ArrayRef<MCRegister> Ops, MCRegister VoidTypeReg, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
auto [It, Inserted] =
DebugTypeFunctionCache.try_emplace(SmallVector<MCRegister, 8>(Ops));
if (!Inserted)
return It->second;
MCRegister Reg = emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeFunction,
VoidTypeReg, ExtInstSetReg, Ops, MAI);
It->second = Reg;
return Reg;
}
MCRegister SPIRVNonSemanticDebugHandler::getOrEmitOpTypeVoidReg(
SPIRV::ModuleAnalysisInfo &MAI) {
if (!CachedOpTypeVoidReg.isValid())
CachedOpTypeVoidReg = findOrEmitOpTypeVoid(MAI);
return CachedOpTypeVoidReg;
}
MCRegister SPIRVNonSemanticDebugHandler::getOrEmitOpTypeInt32Reg(
SPIRV::ModuleAnalysisInfo &MAI) {
if (!CachedOpTypeInt32Reg.isValid())
CachedOpTypeInt32Reg = findOrEmitOpTypeInt32(MAI);
return CachedOpTypeInt32Reg;
}
MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeVoid(
SPIRV::ModuleAnalysisInfo &MAI) {
for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
if (MI->getOpcode() == SPIRV::OpTypeVoid)
return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
}
MCRegister Reg = MAI.getNextIDRegister();
MCInst Inst;
Inst.setOpcode(SPIRV::OpTypeVoid);
Inst.addOperand(MCOperand::createReg(Reg));
emitMCInst(Inst);
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)) {
if (MI->getOpcode() == SPIRV::OpTypeInt &&
MI->getOperand(1).getImm() == 32 && MI->getOperand(2).getImm() == 0)
return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
}
MCRegister Reg = MAI.getNextIDRegister();
MCInst Inst;
Inst.setOpcode(SPIRV::OpTypeInt);
Inst.addOperand(MCOperand::createReg(Reg));
Inst.addOperand(MCOperand::createImm(32)); // width
Inst.addOperand(MCOperand::createImm(0)); // signedness (unsigned)
emitMCInst(Inst);
return Reg;
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypePointer(
const DIDerivedType *PT, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
// A DWARF address space is required to determine the SPIR-V storage class.
// Skip pointer types that do not carry one.
if (!PT->getDWARFAddressSpace().has_value())
return std::nullopt;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
MCRegister DebugTypePointerFlagsReg =
emitOpConstantI32(transDebugFlags(PT), I32TypeReg, MAI);
// For SPIR-V targets, Clang sets DwarfAddressSpace to the LLVM IR address
// space, which addressSpaceToStorageClass expects.
const auto &ST = static_cast<const SPIRVSubtarget &>(Asm->getSubtargetInfo());
MCRegister StorageClassReg = emitOpConstantI32(
addressSpaceToStorageClass(PT->getDWARFAddressSpace().value(), ST),
I32TypeReg, MAI);
if (const DIType *BaseTy = PT->getBaseType()) {
auto BaseIt = DebugScopeRegs.find(BaseTy);
if (BaseIt != DebugScopeRegs.end())
return emitExtInst(
SPIRV::NonSemanticExtInst::DebugTypePointer, VoidTypeReg,
ExtInstSetReg,
{BaseIt->second, StorageClassReg, DebugTypePointerFlagsReg}, MAI);
// Unsupported type, no DebugType* id available.
return std::nullopt;
}
// No getBaseType() (typical for void*): use DebugInfoNone as Base Type,
// same as SPIRV-LLVM-Translator (see issue #109287 and the DISABLED
// spirv-val run in debug-type-pointer.ll). spirv-val may still reject this
// encoding; see https://github.com/KhronosGroup/SPIRV-Registry/pull/287.
return emitExtInst(
SPIRV::NonSemanticExtInst::DebugTypePointer, VoidTypeReg, ExtInstSetReg,
{CachedDebugInfoNoneReg, StorageClassReg, DebugTypePointerFlagsReg}, MAI);
}
std::optional<MCRegister>
SPIRVNonSemanticDebugHandler::emitDebugTypeFunctionForSubroutineType(
const DISubroutineType *ST, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
MCRegister DebugTypeFunctionFlagsReg =
emitOpConstantI32(transDebugFlags(ST), I32TypeReg, MAI);
DITypeArray TA = ST->getTypeArray();
SmallVector<MCRegister, 8> Ops;
Ops.push_back(DebugTypeFunctionFlagsReg);
// Empty DI type tuple: no explicit return or parameter slots (hand-written IR
// may use !{}). Emit void-only prototype. Same as SPIRV-LLVM-Translator when
// DISubroutineType::getTypeArray() has zero elements.
if (TA.empty()) {
Ops.push_back(VoidTypeReg);
} else {
for (unsigned I = 0, E = TA.size(); I != E; ++I) {
bool IsReturnType = (I == 0);
auto OptReg = mapDISignatureTypeToReg(TA[I], VoidTypeReg, IsReturnType);
// No emitted DebugType* id for this slot (e.g., pointer that
// was skipped due missing address space, etc.).
if (!OptReg)
return std::nullopt;
Ops.push_back(*OptReg);
}
}
return getOrEmitDebugTypeFunction(Ops, VoidTypeReg, ExtInstSetReg, MAI);
}
// Match SPIRV-LLVM-Translator's selection logic for the Parent operand.
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::resolveScope(
const DIScope *Scope, const DICompileUnit *FallbackCU) const {
// DebugLexicalBlockDiscriminator cannot be used as a scope, so unwrap it.
while (const auto *DLBF = dyn_cast_or_null<DILexicalBlockFile>(Scope))
Scope = DLBF->getScope();
if (isa_and_nonnull<DIType, DILexicalBlock, DINamespace, DISubprogram>(Scope))
return lookupOptReg(DebugScopeRegs, Scope);
// For a file, compile-unit, or absent scope, fall back to a compile unit.
if (FallbackCU)
return lookupOptReg(DebugScopeRegs, FallbackCU);
if (CompileUnits.empty())
return std::nullopt;
return lookupOptReg(DebugScopeRegs, CompileUnits[0].TheCU);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugLexicalBlock(
const DIScope *S, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
assert((isa<DILexicalBlock, DINamespace>(S)) &&
"S must be a DILexicalBlock or DINamespace in emitDebugLexicalBlock");
auto ParentRegOpt = resolveScope(S->getScope());
if (!ParentRegOpt)
return std::nullopt;
MCRegister FileStrReg = getCachedScopePathOpStringReg(
S->getFile(), /*UseEmptyPathIfNullScope=*/true);
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
SmallVector<MCRegister, 5> Ops;
if (const auto *LB = dyn_cast<DILexicalBlock>(S)) {
MCRegister LineReg = emitOpConstantI32(static_cast<uint32_t>(LB->getLine()),
I32TypeReg, MAI);
MCRegister ColReg = emitOpConstantI32(
static_cast<uint32_t>(LB->getColumn()), I32TypeReg, MAI);
Ops = {SrcReg, LineReg, ColReg, *ParentRegOpt};
} else {
const auto *NS = cast<DINamespace>(S);
// DINamespace carries no line/column info.
MCRegister LineReg = emitOpConstantI32(0, I32TypeReg, MAI);
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
MCRegister NameReg = getCachedOpStringReg(NS->getName());
Ops = {SrcReg, LineReg, ColReg, *ParentRegOpt, NameReg};
}
return emitExtInst(SPIRV::NonSemanticExtInst::DebugLexicalBlock, VoidTypeReg,
ExtInstSetReg, Ops, MAI);
}
MCRegister SPIRVNonSemanticDebugHandler::getOrEmitDebugInlinedAt(
const DILocation *IA, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
assert(IA && "IA must not be null in getOrEmitDebugInlinedAt");
if (MCRegister Cached = DebugInlinedAtRegs.lookup(IA))
return Cached;
auto ScopeRegOpt = resolveScope(IA->getScope());
if (!ScopeRegOpt)
return MCRegister();
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(IA->getLine()), I32TypeReg, MAI);
SmallVector<MCRegister, 3> Ops{LineReg, *ScopeRegOpt};
// Recurse before building this instruction's operands so an outer
// inlined-at link is always available.
if (const DILocation *Outer = IA->getInlinedAt()) {
MCRegister OuterReg = getOrEmitDebugInlinedAt(
Outer, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
if (!OuterReg.isValid())
return MCRegister();
Ops.push_back(OuterReg);
}
MCRegister Reg = emitExtInst(SPIRV::NonSemanticExtInst::DebugInlinedAt,
VoidTypeReg, ExtInstSetReg, Ops, MAI);
DebugInlinedAtRegs[IA] = Reg;
return Reg;
}
std::optional<MCRegister>
SPIRVNonSemanticDebugHandler::emitDebugFunctionDeclaration(
const DISubprogram *SP, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
assert(SP && "SP must not be null in emitDebugFunctionDeclaration");
assert(!SP->isDefinition() &&
"SP must not be a definition in emitDebugFunctionDeclaration");
// The IR verifier already enforces that this cannot be null.
const DISubroutineType *ST = SP->getType();
auto FnTyRegOpt = lookupOptReg(DebugScopeRegs, ST);
if (!FnTyRegOpt)
return std::nullopt;
MCRegister FnTyReg = *FnTyRegOpt;
auto ParentRegOpt = resolveScope(SP->getScope(), SP->getUnit());
if (!ParentRegOpt)
return std::nullopt;
MCRegister ParentReg = *ParentRegOpt;
MCRegister FileStrReg = getCachedScopePathOpStringReg(SP);
MCRegister NameReg = getCachedOpStringReg(SP->getName());
MCRegister LinkageReg = getCachedOpStringReg(SP->getLinkageName());
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(SP->getLine()), I32TypeReg, MAI);
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
uint32_t FlagsVal = transDebugFlags(SP);
// TODO: When composite scopes are DebugFunctionDeclaration parents (available
// in DebugScopeRegs), sync declaration Flags with SPIRV-LLVM-Translator.
FlagsVal &= ~NSDIFlagIsDefinition;
MCRegister FlagsReg = emitOpConstantI32(FlagsVal, I32TypeReg, MAI);
return emitExtInst(SPIRV::NonSemanticExtInst::DebugFunctionDeclaration,
VoidTypeReg, ExtInstSetReg,
{NameReg, FnTyReg, SrcReg, LineReg, ColReg, ParentReg,
LinkageReg, FlagsReg},
MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugFunction(
const DISubprogram *SP, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
assert(SP && "SP must not be null in emitDebugFunction");
assert(SP->isDefinition() && "SP must be a definition in emitDebugFunction");
const DISubroutineType *ST = SP->getType();
auto FnTyRegOpt = lookupOptReg(DebugScopeRegs, ST);
if (!FnTyRegOpt)
return std::nullopt;
auto ParentRegOpt = resolveScope(SP->getScope(), SP->getUnit());
if (!ParentRegOpt)
return std::nullopt;
MCRegister NameReg = getCachedOpStringReg(SP->getName());
MCRegister LinkageReg = getCachedOpStringReg(SP->getLinkageName());
MCRegister FileStrReg = getCachedScopePathOpStringReg(SP);
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(SP->getLine()), I32TypeReg, MAI);
// LLVM's DISubprogram has no column field but SPIR-V expects one in
// DebugFunction.
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(SP), I32TypeReg, MAI);
MCRegister ScopeLineReg = emitOpConstantI32(
static_cast<uint32_t>(SP->getScopeLine()), I32TypeReg, MAI);
SmallVector<MCRegister, 10> Ops = {NameReg, *FnTyRegOpt, SrcReg,
LineReg, ColReg, *ParentRegOpt,
LinkageReg, FlagsReg, ScopeLineReg};
if (const DISubprogram *Decl = SP->getDeclaration()) {
if (auto DeclRegOpt = lookupOptReg(DebugScopeRegs, Decl))
Ops.push_back(*DeclRegOpt);
}
return emitExtInst(SPIRV::NonSemanticExtInst::DebugFunction, VoidTypeReg,
ExtInstSetReg, Ops, MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::mapDISignatureTypeToReg(
const DIType *Ty, MCRegister VoidTypeReg, bool ReturnType) {
if (!Ty) {
if (ReturnType)
return VoidTypeReg;
assert(CachedDebugInfoNoneReg.isValid() &&
"DebugInfoNone must be emitted before DISubroutineType operands");
return CachedDebugInfoNoneReg;
}
return lookupOptReg(DebugScopeRegs, Ty);
}
// NonSemantic.Shader.DebugInfo.100 debug operation encodings
// (section 4.5, "Debug Operations").
enum class NonSemanticDebugOp : uint32_t {
Deref = 0,
Plus = 1,
Minus = 2,
PlusUconst = 3,
BitPiece = 4,
Swap = 5,
Xderef = 6,
StackValue = 7,
Constu = 8,
Fragment = 9
};
static std::optional<NonSemanticDebugOp>
mapDwarfOpToNonSemanticOp(uint64_t DwarfOp) {
switch (DwarfOp) {
case dwarf::DW_OP_deref:
return NonSemanticDebugOp::Deref;
case dwarf::DW_OP_plus:
return NonSemanticDebugOp::Plus;
case dwarf::DW_OP_minus:
return NonSemanticDebugOp::Minus;
case dwarf::DW_OP_plus_uconst:
return NonSemanticDebugOp::PlusUconst;
case dwarf::DW_OP_bit_piece:
return NonSemanticDebugOp::BitPiece;
case dwarf::DW_OP_swap:
return NonSemanticDebugOp::Swap;
case dwarf::DW_OP_xderef:
return NonSemanticDebugOp::Xderef;
case dwarf::DW_OP_stack_value:
return NonSemanticDebugOp::StackValue;
case dwarf::DW_OP_constu:
return NonSemanticDebugOp::Constu;
case dwarf::DW_OP_LLVM_fragment:
return NonSemanticDebugOp::Fragment;
default:
return std::nullopt;
}
}
// 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> 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;
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));
if (!Inserted)
return It->second;
SmallVector<MCRegister, 3> Operands;
for (uint32_t V : It->first)
Operands.push_back(emitOpConstantI32(V, I32TypeReg, MAI));
MCRegister Reg = emitExtInst(SPIRV::NonSemanticExtInst::DebugOperation,
VoidTypeReg, ExtInstSetReg, Operands, MAI);
It->second = Reg;
return Reg;
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugExpression(
const DIExpression *Expr, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
assert(Expr && "Expr must not be null in emitDebugExpression");
SmallVector<MCRegister> OperationRegs;
for (const DIExpression::ExprOperand &Op : Expr->expr_ops()) {
std::optional<MCRegister> OpReg =
emitDebugOperation(Op, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
if (!OpReg)
return std::nullopt;
OperationRegs.push_back(*OpReg);
}
auto [It, Inserted] =
DebugExpressionCache.try_emplace(std::move(OperationRegs));
if (!Inserted)
return It->second;
MCRegister Reg = emitExtInst(SPIRV::NonSemanticExtInst::DebugExpression,
VoidTypeReg, ExtInstSetReg, It->first, MAI);
It->second = Reg;
return Reg;
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugGlobalVariable(
const DIGlobalVariable *GV, const GlobalVariableDebugInfo &Info,
MCRegister VoidTypeReg, MCRegister I32TypeReg, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
assert(GV && "GV must not be null in emitDebugGlobalVariable");
auto ParentRegOpt = resolveScope(GV->getScope());
if (!ParentRegOpt)
return std::nullopt;
MCRegister ParentReg = *ParentRegOpt;
// TyReg: DebugInfoNone when GV has no DI type (as done in
// SPIRV-LLVM-Translator). Declarations (isDefinition: false) can have null
// getType() while definitions must have a non-null one (enforced by the IR
// verifier).
MCRegister TyReg = CachedDebugInfoNoneReg;
if (const DIType *Ty = GV->getType()) {
auto TyRegOpt = lookupOptReg(DebugScopeRegs, Ty);
if (!TyRegOpt)
return std::nullopt;
TyReg = *TyRegOpt;
}
std::optional<MCRegister> StaticMemberRegOpt;
if (const DIDerivedType *SM = GV->getStaticDataMemberDeclaration()) {
StaticMemberRegOpt = lookupOptReg(DebugScopeRegs, SM);
if (!StaticMemberRegOpt)
return std::nullopt;
}
MCRegister NameReg = getCachedOpStringReg(GV->getName());
MCRegister LinkageReg = getCachedOpStringReg(GV->getLinkageName());
MCRegister FileStrReg = getCachedScopePathOpStringReg(
GV->getFile(), /*UseEmptyPathIfNullScope=*/true);
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(GV->getLine()), I32TypeReg, MAI);
// DIGlobalVariable or DIGlobalVariableExpression metadata carry no column
// field. Column is hardcoded to 0 (because it can't be determined), matching
// SPIRV-LLVM-Translator.
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
// Variable: @g OpVariable id when !dbg matches; else a DebugExpression for
// the GVE init value when no @g exists and the expression is non-empty; else
// DebugInfoNone. As per spec, the DebugExpression must contain the constant
// value of the variable that was optimized out. An empty expression contains
// no value, so we emit DebugInfoNone instead.
MCRegister VariableReg = CachedDebugInfoNoneReg;
if (const GlobalVariable *LLVMGV = Info.LLVMGV) {
MCRegister GVReg = MAI.getGlobalObjReg(LLVMGV);
if (GVReg.isValid())
VariableReg = GVReg;
} else if (Info.Expr && Info.Expr->getNumElements() != 0) {
if (auto ExprReg = emitDebugExpression(Info.Expr, VoidTypeReg, I32TypeReg,
ExtInstSetReg, MAI))
VariableReg = *ExprReg;
}
MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(GV), I32TypeReg, MAI);
SmallVector<MCRegister, 10> Ops = {NameReg, TyReg, SrcReg,
LineReg, ColReg, ParentReg,
LinkageReg, VariableReg, FlagsReg};
if (StaticMemberRegOpt)
Ops.push_back(*StaticMemberRegOpt);
return emitExtInst(SPIRV::NonSemanticExtInst::DebugGlobalVariable,
VoidTypeReg, ExtInstSetReg, Ops, MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugLocalVariable(
const DILocalVariable *LV, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
assert(LV && "LV must not be null in emitDebugLocalVariable");
auto ParentRegOpt = resolveScope(LV->getScope());
if (!ParentRegOpt)
return std::nullopt;
MCRegister TyReg = CachedDebugInfoNoneReg;
if (const DIType *Ty = LV->getType()) {
auto TyRegOpt = lookupOptReg(DebugScopeRegs, Ty);
if (!TyRegOpt)
return std::nullopt;
TyReg = *TyRegOpt;
}
MCRegister NameReg = getCachedOpStringReg(LV->getName());
MCRegister FileStrReg = getCachedScopePathOpStringReg(
LV->getFile(), /*UseEmptyPathIfNullScope=*/true);
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(LV->getLine()), I32TypeReg, MAI);
// DILocalVariable has no column field. Column is hardcoded to 0.
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(LV), I32TypeReg, MAI);
SmallVector<MCRegister, 8> Ops = {NameReg, TyReg, SrcReg, LineReg,
ColReg, *ParentRegOpt, FlagsReg};
if (unsigned Arg = LV->getArg())
Ops.push_back(emitOpConstantI32(Arg, I32TypeReg, MAI));
return emitExtInst(SPIRV::NonSemanticExtInst::DebugLocalVariable, VoidTypeReg,
ExtInstSetReg, Ops, MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeVector(
const DICompositeType *VT, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
const auto *BaseTy = dyn_cast_or_null<DIBasicType>(VT->getBaseType());
if (!BaseTy)
return std::nullopt;
auto BTIt = DebugScopeRegs.find(BaseTy);
if (BTIt == DebugScopeRegs.end())
return std::nullopt;
// DebugTypeVector models only 1D vectors (multi-subrange types cannot be
// encoded).
DINodeArray Elements = VT->getElements();
if (Elements.size() != 1)
return std::nullopt;
const auto *SR = cast<DISubrange>(Elements[0]);
const auto *CI = dyn_cast_if_present<ConstantInt *>(SR->getCount());
if (!CI)
return std::nullopt;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
MCRegister CountReg = emitOpConstantI32(
static_cast<uint32_t>(CI->getZExtValue()), I32TypeReg, MAI);
return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeVector, VoidTypeReg,
ExtInstSetReg, {BTIt->second, CountReg}, MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeArray(
const DICompositeType *AT, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
// The element (base) type must already be in DebugScopeRegs. Unlike
// DebugTypeVector, the element may be any debug type, not only a basic type.
auto BaseRegOpt = lookupOptReg(DebugScopeRegs, AT->getBaseType());
if (!BaseRegOpt)
return std::nullopt;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
SmallVector<MCRegister> Ops;
Ops.push_back(*BaseRegOpt);
// One component count per DISubrange, in DWARF subrange order. Emit 0 for
// counts that are not a compile-time constant (dynamic arrays). This matches
// OpTypeRuntimeArray.
for (const DINode *Element : AT->getElements()) {
const auto *SR = dyn_cast<DISubrange>(Element);
if (!SR)
continue;
// A DIVariable count (a variable-length array) is not a ConstantInt, so it
// maps to 0 here. DebugTypeArray also allows a DebugLocalVariable or
// DebugGlobalVariable id for it, but no frontend we target emits one. A
// constant wider than 32 bits maps to 0 too, since the count operand is a
// 32-bit OpConstant and such an array cannot occur in a shader.
uint32_t Count = 0;
if (const auto *CI = dyn_cast_if_present<ConstantInt *>(SR->getCount())) {
const APInt &Value = CI->getValue();
if (Value.getActiveBits() <= 32)
Count = static_cast<uint32_t>(Value.getZExtValue());
}
Ops.push_back(emitOpConstantI32(Count, I32TypeReg, MAI));
}
return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeArray, VoidTypeReg,
ExtInstSetReg, Ops, MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeMember(
const DIDerivedType *M, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
// The member type must already be in DebugScopeRegs.
auto TyRegOpt = lookupOptReg(DebugScopeRegs, M->getBaseType());
if (!TyRegOpt)
return std::nullopt;
if (!isUInt<32>(M->getOffsetInBits()) || !isUInt<32>(M->getSizeInBits()))
return std::nullopt;
MCRegister NameReg = getCachedOpStringReg(M->getName());
MCRegister FileStrReg = getCachedScopePathOpStringReg(
M->getFile(), /*UseEmptyPathIfNullScope=*/true);
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(M->getLine()), I32TypeReg, MAI);
// DIDerivedType members carry no column, so emit 0.
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
MCRegister OffsetReg = emitOpConstantI32(
static_cast<uint32_t>(M->getOffsetInBits()), I32TypeReg, MAI);
MCRegister SizeReg = emitOpConstantI32(
static_cast<uint32_t>(M->getSizeInBits()), I32TypeReg, MAI);
MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(M), I32TypeReg, MAI);
// In NonSemantic.Shader.DebugInfo a DebugTypeMember has no Parent operand:
// only the composite references its members. This is by design, it drops the
// Parent that OpenCL.DebugInfo.100 had, and it avoids a composite/member
// reference cycle.
//
// FIXME: Static members are not handled yet: their constant initializer is
// available but is not emitted as the optional Value operand, and under DWARF
// 5 a static member is tagged DW_TAG_variable, which the caller's member loop
// skips.
return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeMember, VoidTypeReg,
ExtInstSetReg,
{NameReg, *TyRegOpt, SrcReg, LineReg, ColReg, OffsetReg,
SizeReg, FlagsReg},
MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeComposite(
const DICompositeType *CT, ArrayRef<MCRegister> MemberRegs,
MCRegister VoidTypeReg, MCRegister I32TypeReg, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
auto ParentRegOpt = resolveScope(CT->getScope());
if (!ParentRegOpt)
return std::nullopt;
if (!isUInt<32>(CT->getSizeInBits()))
return std::nullopt;
MCRegister NameReg = getCachedOpStringReg(CT->getName());
MCRegister LinkageReg = getCachedOpStringReg(CT->getIdentifier());
MCRegister FileStrReg = getCachedScopePathOpStringReg(
CT->getFile(), /*UseEmptyPathIfNullScope=*/true);
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
MCRegister TagReg =
emitOpConstantI32(mapCompositeTypeTag(CT->getTag()), I32TypeReg, MAI);
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(CT->getLine()), I32TypeReg, MAI);
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
// A forward declaration has no known size or members: Size is DebugInfoNone.
MCRegister SizeReg = CachedDebugInfoNoneReg;
if (!CT->isForwardDecl())
SizeReg = emitOpConstantI32(static_cast<uint32_t>(CT->getSizeInBits()),
I32TypeReg, MAI);
MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(CT), I32TypeReg, MAI);
SmallVector<MCRegister> Ops = {NameReg, TagReg, SrcReg,
LineReg, ColReg, *ParentRegOpt,
LinkageReg, SizeReg, FlagsReg};
Ops.append(MemberRegs.begin(), MemberRegs.end());
return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeComposite, VoidTypeReg,
ExtInstSetReg, Ops, MAI);
}
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypedef(
const DIDerivedType *TD, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
// The underlying (base) type must already be in DebugScopeRegs.
auto BaseRegOpt = lookupOptReg(DebugScopeRegs, TD->getBaseType());
if (!BaseRegOpt)
return std::nullopt;
MCRegister NameReg = getCachedOpStringReg(TD->getName());
MCRegister FileStrReg = getCachedScopePathOpStringReg(
TD->getFile(), /*UseEmptyPathIfNullScope=*/true);
MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
ExtInstSetReg, MAI);
MCRegister LineReg =
emitOpConstantI32(static_cast<uint32_t>(TD->getLine()), I32TypeReg, MAI);
// DIDerivedType typedefs carry no column, so emit 0.
MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
// Parent must be a lexical scope. Valid NSDI lexical scopes are
// DebugCompilationUnit, DebugFunction, DebugLexicalBlock, or
// DebugTypeComposite.
auto ParentRegOpt = resolveScope(TD->getScope());
if (!ParentRegOpt)
return std::nullopt;
MCRegister ParentReg = *ParentRegOpt;
return emitExtInst(
SPIRV::NonSemanticExtInst::DebugTypedef, VoidTypeReg, ExtInstSetReg,
{NameReg, *BaseRegOpt, SrcReg, LineReg, ColReg, ParentReg}, MAI);
}
void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
SPIRV::ModuleAnalysisInfo &MAI) {
if (CompileUnits.empty())
return;
// Check that prepareModuleOutput() registered the extended instruction set.
// If the subtarget does not support the extension, neither strings nor ext
// insts are emitted.
if (!MAI.getExtInstSetReg(NSSet).isValid())
return;
for (const CompileUnitInfo &Info : CompileUnits) {
if (Info.TheCU) {
MCRegister PathReg = emitOpStringIfNew(Info.FilePath, MAI);
ScopeToPathOpStringReg[Info.TheCU] = PathReg;
if (const DIFile *F = Info.TheCU->getFile())
ScopeToPathOpStringReg[F] = PathReg;
}
}
for (const DIBasicType *BT : BasicTypes)
emitOpStringIfNew(BT->getName(), MAI);
for (const DISubprogram *SP : concat<const DISubprogram *>(
SubprogramDeclarations, SubprogramDefinitions)) {
emitOpStringIfNew(SP->getName(), MAI);
emitOpStringIfNew(SP->getLinkageName(), MAI);
emitAndCacheScopePathOpStringReg(SP, MAI);
}
// Cache the OpStrings each DebugTypeComposite and its DebugTypeMembers use:
// the composite name, identifier (linkage name), and path, plus each member
// name and path.
for (const DICompositeType *CT : CompositeTypes) {
emitOpStringIfNew(CT->getName(), MAI);
emitOpStringIfNew(CT->getIdentifier(), MAI);
emitAndCacheScopePathOpStringReg(CT->getFile(), MAI);
for (const DINode *Element : CT->getElements()) {
const auto *M = dyn_cast<DIDerivedType>(Element);
if (!M || M->getTag() != dwarf::DW_TAG_member)
continue;
emitOpStringIfNew(M->getName(), MAI);
emitAndCacheScopePathOpStringReg(M->getFile(), MAI);
}
}
// Cache the name and path OpStrings each DebugTypedef uses.
for (const DIDerivedType *TD : TypedefTypes) {
emitOpStringIfNew(TD->getName(), MAI);
emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
}
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
emitAndCacheScopePathOpStringReg(GV->getFile(), MAI);
}
for (const DILocalVariable *LV : LocalVariables) {
emitOpStringIfNew(LV->getName(), MAI);
emitAndCacheScopePathOpStringReg(LV->getFile(), MAI);
}
// Cache the path OpString each DebugLexicalBlock uses (source file), plus
// the Name OpString for the DINamespace case.
for (const DIScope *S : LexicalBlocks) {
emitAndCacheScopePathOpStringReg(S->getFile(), MAI);
if (const auto *NS = dyn_cast<DINamespace>(S))
emitOpStringIfNew(NS->getName(), MAI);
}
for (const DILocation *DL : UniqueDebugLocations)
emitAndCacheScopePathOpStringReg(DL->getScope(), MAI);
CachedEmptyStringReg = emitOpStringIfNew("", MAI);
#ifndef NDEBUG
NonSemanticOpStringsSectionEmitted = true;
#endif
}
void SPIRVNonSemanticDebugHandler::emitDebugFunctionDefinition(
MCRegister DebugFunctionReg, MCRegister OpFunctionReg,
SPIRV::ModuleAnalysisInfo &MAI) {
assert(DebugFunctionReg.isValid() && OpFunctionReg.isValid() &&
"DebugFunctionDefinition operands must be valid");
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
emitExtInst(SPIRV::NonSemanticExtInst::DebugFunctionDefinition, VoidTypeReg,
ExtInstSetReg, {DebugFunctionReg, OpFunctionReg}, MAI);
}
void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
CurrentMF = nullptr;
LastFunctionOpVariable = nullptr;
DebugFunctionDefinitionEmitted = false;
LastLineMI = nullptr;
LastScopeMI = nullptr;
Records.clear();
}
void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
const MachineFunction *MF) {
resetPerFunctionDebugState();
if (!GlobalNSDIEnabled || !CurrentMAI)
return;
CurrentMF = MF;
if (MF->getFunction()
.getFnAttribute(SPIRV_BACKEND_SERVICE_FUN_NAME)
.isValid())
return;
const DISubprogram *SP = MF->getFunction().getSubprogram();
if (!SP || !SP->isDefinition())
return;
// DebugFunctionDefinition is emitted after the last function-level
// OpVariable. If there are none, it is emitted after the entry OpLabel.
LastFunctionOpVariable =
findLastFunctionOpVariableDeclaration(*MF, *CurrentMAI);
analyzeDebugRecords(*MF);
}
void SPIRVNonSemanticDebugHandler::tryEmitDebugFunctionDefinition(
SPIRV::ModuleAnalysisInfo &MAI) {
if (DebugFunctionDefinitionEmitted || !GlobalNSDIEnabled)
return;
assert(CurrentMF && "no current MachineFunction");
const Function &F = CurrentMF->getFunction();
const DISubprogram *SP = F.getSubprogram();
if (!SP || !SP->isDefinition())
return;
auto DFIt = DebugScopeRegs.find(SP);
if (DFIt == DebugScopeRegs.end())
return;
MCRegister OpFunctionReg = MAI.getGlobalObjReg(&F);
if (!OpFunctionReg.isValid())
return;
emitDebugFunctionDefinition(DFIt->second, OpFunctionReg, MAI);
DebugFunctionDefinitionEmitted = true;
}
void SPIRVNonSemanticDebugHandler::beginFunctionImpl(
const MachineFunction *MF) {
preparePerFunctionDebug(MF);
}
void SPIRVNonSemanticDebugHandler::endFunctionImpl(const MachineFunction *MF) {
(void)MF;
resetPerFunctionDebugState();
}
void SPIRVNonSemanticDebugHandler::beginInstruction(const MachineInstr *MI) {
assert(CurMI == nullptr && "CurMI must be null");
CurMI = MI;
if (!DebugFunctionDefinitionEmitted)
return;
emitAnalyzedRecords(MI);
std::optional<const MachineInstr *> Target = resolveDebugLocTarget(MI);
if (!Target)
return;
(void)emitDebugScopeForInstruction(*Target);
emitDebugLineForInstruction(*Target);
}
// The register that holds the variable's address in \p MI, or std::nullopt
// when \p MI is not a declare this backend can describe.
//
// The spec requires DebugDeclare's Variable operand to be "the <id> of an
// OpVariable instruction that defines the local variable". MIR has no
// DBG_DECLARE, so what this looks for is an indirect DBG_VALUE whose location
// register an OpVariable defines.
static std::optional<Register>
getDebugDeclareStorageReg(const MachineInstr &MI) {
// #dbg_declare is an indirect DBG_VALUE in MIR; #dbg_value is normally a
// direct one except for the variadic case.
if (!MI.isIndirectDebugValue())
return std::nullopt;
// A variadic #dbg_value becomes DBG_VALUE $noreg, 0, ... which is indirect
// too, and $noreg is not virtual.
Register LocReg = MI.getDebugOperand(0).getReg();
if (!LocReg.isVirtual())
return std::nullopt;
// DebugDeclare can only encode the address of an OpVariable.
// Other legitimate #dbg_declare cannot be encoded.
// Examples: an access chain for a field, an OpFunctionParameter for a byval
// argument, or a module-scope constant for a null or fixed address.
// LocReg may also have no def at all: erasing dead storage leaves the
// DBG_VALUE pointing at an undefined register. MachineVerifier permits that
// because LiveDebugVariables normally clears it, but this pipeline has no
// register allocation, so LiveDebugVariables never runs.
const MachineInstr *Def = MI.getMF()->getRegInfo().getUniqueVRegDef(LocReg);
if (!Def || Def->getOpcode() != SPIRV::OpVariable)
return std::nullopt;
return LocReg;
}
void SPIRVNonSemanticDebugHandler::emitDebugBinding(
SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
const MachineInstr *MI, MCRegister LocationReg) {
assert(DebugFunctionDefinitionEmitted &&
"DebugFunctionDefinition must be emitted");
assert(CurrentMAI && "CurrentMAI must be set");
auto VarRegOpt = lookupOptReg(DebugLocalVariableRegs, MI->getDebugVariable());
if (!VarRegOpt)
return;
auto ExprRegOpt = lookupOptReg(DebugExpressionRegs, MI->getDebugExpression());
if (!ExprRegOpt)
return;
if (!emitDebugScopeForInstruction(MI))
return;
emitDebugLineForInstruction(MI);
SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
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->isPHI()) || 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) {
return Opcode == SPIRV::OpSelectionMerge || Opcode == SPIRV::OpLoopMerge ||
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 (!isEmitted(MI, MAI))
return false;
switch (MI->getOpcode()) {
case SPIRV::OpFunction:
case SPIRV::OpFunctionParameter:
case SPIRV::OpFunctionEnd:
case SPIRV::OpLabel:
case TargetOpcode::PHI:
return false;
default:
return true;
}
}
static const MachineInstr *
findAdjacentEmittedInstruction(const MachineInstr *MI,
SPIRV::ModuleAnalysisInfo &MAI, bool Forward) {
for (const MachineInstr *Adj = Forward ? MI->getNextNode()
: MI->getPrevNode();
Adj; Adj = Forward ? Adj->getNextNode() : Adj->getPrevNode()) {
if (!isEmitted(Adj, MAI))
continue;
return Adj;
}
return nullptr;
}
std::optional<const MachineInstr *>
SPIRVNonSemanticDebugHandler::resolveDebugLocTarget(const MachineInstr *MI) {
assert(CurrentMAI && "CurrentMAI must be set");
SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
// Structural opcodes don't require a DebugLine/DebugScope, other opcodes
// might have already been emitted in the module scope.
if (!isDebugLocTarget(MI, MAI))
return std::nullopt;
// DebugLine/DebugScope can be emitted before a merge instruction, but not
// after it (nothing may sit between the merge and its terminator). We can
// use either the merge's or the terminator's debug info; we emit the
// terminator's one.
const MachineInstr *Prev = findAdjacentEmittedInstruction(MI, MAI, false);
if (Prev && isMergeInstruction(Prev->getOpcode()))
return std::nullopt;
if (isMergeInstruction(MI->getOpcode())) {
// Use the terminator's debug info; when we reach it later, the check
// 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::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");
assert(CurrentMAI && "CurrentMAI must be set");
// The region is implicitly closed at each basic block boundary, so a
// LastScopeMI from another block must be dropped before it is read below:
// the new block needs its own DebugScope, and has no region left to close.
if (LastScopeMI && MI->getParent() != LastScopeMI->getParent())
LastScopeMI = nullptr;
SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
const DILocation *CurDL = MI->getDebugLoc().get();
if (!CurDL) {
// No location for the current instruction.
if (LastScopeMI) {
// Close the current DebugScope region.
emitExtInst(SPIRV::NonSemanticExtInst::DebugNoScope, VoidTypeReg,
ExtInstSetReg, {}, MAI);
LastScopeMI = nullptr;
}
return true;
}
const DIScope *CurScope = CurDL->getScope();
const DILocation *CurInlinedAt = CurDL->getInlinedAt();
if (LastScopeMI) {
const DILocation *LastDL = LastScopeMI->getDebugLoc().get();
if (LastDL->getScope() == CurScope &&
LastDL->getInlinedAt() == CurInlinedAt)
return true;
}
auto CurScopeRegOpt = resolveScope(CurScope);
if (!CurScopeRegOpt)
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 false;
Ops.push_back(InlinedReg);
}
// A new DebugScope region is needed.
emitExtInst(SPIRV::NonSemanticExtInst::DebugScope, VoidTypeReg, ExtInstSetReg,
Ops, MAI);
LastScopeMI = MI;
return true;
}
void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
const MachineInstr *MI) {
assert(DebugFunctionDefinitionEmitted &&
"DebugFunctionDefinition must be emitted");
assert(CurrentMAI && "CurrentMAI must be set");
// The region is implicitly closed at each basic block boundary, so a
// LastLineMI from another block must be dropped before it is read below:
// the new block needs its own DebugLine, and has no region left to close.
if (LastLineMI && MI->getParent() != LastLineMI->getParent())
LastLineMI = nullptr;
SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
const DILocation *DL = MI->getDebugLoc().get();
if (!DL) {
// No location for the current instruction
if (LastLineMI) {
// Close the current DebugLine region.
emitExtInst(SPIRV::NonSemanticExtInst::DebugNoLine, VoidTypeReg,
ExtInstSetReg, {}, MAI);
LastLineMI = nullptr;
}
// No DebugLine region to close.
return;
}
// At this point, there is a location for the current instruction.
// If it matches the last emitted DebugLine, no new DebugLine region is
// needed. Otherwise, emit a new DebugLine region and update LastLineMI.
MCRegister FileStrReg = getCachedScopePathOpStringReg(
DL->getScope(), /*UseEmptyPathIfNullScope=*/true);
unsigned Line = DL->getLine();
unsigned Col = DL->getColumn();
unsigned I32TypeId = CachedOpTypeInt32Reg.id();
MCRegister SrcReg = DebugSourceRegByFileStr.lookup(FileStrReg.id());
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
// debug-program records; MIR is expected to reuse those same locations (or
// carry none). A lookup miss means codegen attached a source position whose
// elements were never pre-emitted, and debug-line emission is skipped.
if (!SrcReg.isValid() || !LineReg.isValid() || !ColStartReg.isValid() ||
!ColEndReg.isValid())
return;
// Current location matches the last emitted DebugLine region.
if (LastLineMI && MI->getDebugLoc() == LastLineMI->getDebugLoc())
return;
// A new DebugLine region is needed.
emitExtInst(SPIRV::NonSemanticExtInst::DebugLine, VoidTypeReg, ExtInstSetReg,
{SrcReg, LineReg, LineReg, ColStartReg, ColEndReg}, MAI);
LastLineMI = MI;
}
void SPIRVNonSemanticDebugHandler::endInstruction() {
const MachineInstr *MI = CurMI;
CurMI = nullptr;
if (!MI || !GlobalNSDIEnabled || !CurrentMF)
return;
if (DebugFunctionDefinitionEmitted || MI != LastFunctionOpVariable)
return;
// If this is the last function-level OpVariable, emit the
// DebugFunctionDefinition. Otherwise, we had already done it before right
// after the OpLabel (see notifyEntryLabelEmitted).
assert(CurrentMAI && "CurrentMAI must be set");
tryEmitDebugFunctionDefinition(*CurrentMAI);
}
void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
const MachineFunction &MF) {
if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted || !CurrentMF)
return;
assert(CurrentMF == &MF &&
"notification does not match the current MachineFunction");
if (LastFunctionOpVariable)
return;
// If there are no function-level OpVariables, emit the
// DebugFunctionDefinition. Otherwise, DebugFunctionDefinition is emitted
// after the last OpVariable (see endInstruction).
tryEmitDebugFunctionDefinition(*CurrentMAI);
}
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())
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)
return;
GlobalDIEmitted = true;
if (CompileUnits.empty()) {
GlobalNSDIEnabled = false;
return;
}
// Retrieve the ext inst set register allocated by prepareModuleOutput().
MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
if (!ExtInstSetReg.isValid()) {
GlobalNSDIEnabled = false;
return;
}
#ifndef NDEBUG
assert(NonSemanticOpStringsSectionEmitted &&
"emitNonSemanticDebugStrings() must run before "
"emitNonSemanticGlobalDebugInfo()");
#endif
CurrentMAI = &MAI;
MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
CachedDebugInfoNoneReg = emitExtInst(SPIRV::NonSemanticExtInst::DebugInfoNone,
VoidTypeReg, ExtInstSetReg, {}, MAI);
// Emit integer constants shared across all NSDI instructions. The constant
// cache ensures each value is emitted at most once even when referenced from
// multiple instructions. All constants are pre-emitted before any DebugSource
// so that the output order is: constants, then
// DebugSource+DebugCompilationUnit pairs. This keeps OpConstant instructions
// grouped before the OpExtInst instructions.
// The Version operand of DebugCompilationUnit is the version of the
// NonSemantic.Shader.DebugInfo instruction set, which is 100 for
// "NonSemantic.Shader.DebugInfo.100" (NonSemanticShaderDebugInfo100Version).
MCRegister DebugInfoVersionReg = emitOpConstantI32(100, I32TypeReg, MAI);
MCRegister DwarfVersionReg =
emitOpConstantI32(static_cast<uint32_t>(DwarfVersion), I32TypeReg, MAI);
// Pre-emit source language constants for all compile units before entering
// the DebugSource loop.
SmallVector<MCRegister> SrcLangRegs =
map_to_vector(CompileUnits, [&](const CompileUnitInfo &Info) {
return emitOpConstantI32(Info.SpirvSourceLanguage, I32TypeReg, MAI);
});
// Emit DebugSource and DebugCompilationUnit for each compile unit.
for (auto [Info, SrcLangReg] : llvm::zip(CompileUnits, SrcLangRegs)) {
MCRegister FileStrReg = ScopeToPathOpStringReg.lookup(Info.TheCU);
assert(FileStrReg.isValid() &&
"CU path OpString must be emitted in emitNonSemanticDebugStrings");
MCRegister DebugSourceReg = getOrEmitDebugSourceForFileStrReg(
FileStrReg, VoidTypeReg, ExtInstSetReg, MAI);
MCRegister CUDbgReg = emitExtInst(
SPIRV::NonSemanticExtInst::DebugCompilationUnit, VoidTypeReg,
ExtInstSetReg,
{DebugInfoVersionReg, DwarfVersionReg, DebugSourceReg, SrcLangReg},
MAI);
if (Info.TheCU)
DebugScopeRegs[Info.TheCU] = CUDbgReg;
}
// Zero constant used as the Flags operand in DebugTypeBasic and
// DebugTypePointer. Cached with other i32 constants.
MCRegister I32ZeroReg = emitOpConstantI32(0, I32TypeReg, MAI);
for (const DIBasicType *BT : BasicTypes) {
if (!isUInt<32>(BT->getSizeInBits()))
continue;
MCRegister NameReg = getCachedOpStringReg(BT->getName());
MCRegister SizeReg = emitOpConstantI32(
static_cast<uint32_t>(BT->getSizeInBits()), I32TypeReg, MAI);
// Map DWARF base type encodings to NSDI encoding codes per
// NonSemantic.Shader.DebugInfo.100 specification, section 4.5.
unsigned Encoding = 0; // Unspecified
switch (BT->getEncoding()) {
case dwarf::DW_ATE_address:
Encoding = 1;
break;
case dwarf::DW_ATE_boolean:
Encoding = 2;
break;
case dwarf::DW_ATE_float:
Encoding = 3;
break;
case dwarf::DW_ATE_signed:
Encoding = 4;
break;
case dwarf::DW_ATE_signed_char:
Encoding = 5;
break;
case dwarf::DW_ATE_unsigned:
Encoding = 6;
break;
case dwarf::DW_ATE_unsigned_char:
Encoding = 7;
break;
}
MCRegister EncodingReg = emitOpConstantI32(Encoding, I32TypeReg, MAI);
MCRegister BTReg = emitExtInst(
SPIRV::NonSemanticExtInst::DebugTypeBasic, VoidTypeReg, ExtInstSetReg,
{NameReg, SizeReg, EncodingReg, I32ZeroReg}, MAI);
DebugScopeRegs[BT] = BTReg;
}
// Emit DebugTypeVector for each collected vector type.
for (const DICompositeType *VT : VectorTypes) {
if (auto VecReg = emitDebugTypeVector(VT, ExtInstSetReg, MAI))
DebugScopeRegs[VT] = *VecReg;
}
// Emit DebugTypePointer for each referenced pointer type.
for (const DIDerivedType *PT : PointerTypes) {
if (auto PtrReg = emitDebugTypePointer(PT, ExtInstSetReg, MAI))
DebugScopeRegs[PT] = *PtrReg;
}
// Emit DebugTypeArray for each collected array type. Placed after the basic,
// vector, and pointer types so an array over any of them can resolve its
// element id. An array whose element type was not emitted is skipped.
for (const DICompositeType *AT : ArrayTypes) {
if (auto ArrReg = emitDebugTypeArray(AT, ExtInstSetReg, MAI))
DebugScopeRegs[AT] = *ArrReg;
}
// Emit DebugTypeFunction for each distinct DISubroutineType.
for (const DISubroutineType *ST : SubroutineTypes) {
if (auto FnTyReg =
emitDebugTypeFunctionForSubroutineType(ST, ExtInstSetReg, MAI))
DebugScopeRegs[ST] = *FnTyReg;
}
// Emit DebugLexicalBlock for each collected DINamespace, in parent-before-
// child order. Placed before any DINamespace-scoped entity (typedefs,
// function declarations, composite types, functions, global variables) so
// their Parent operand can reference an already-emitted DebugLexicalBlock.
// DINamespace never chains through a DISubprogram (DINamespace::getScope()
// returns DIScope, not DILocalScope), so this never depends on
// DebugScopeRegs.
for (const DIScope *S :
make_filter_range(LexicalBlocks, IsaPred<DINamespace>)) {
if (auto LBReg = emitDebugLexicalBlock(S, VoidTypeReg, I32TypeReg,
ExtInstSetReg, MAI))
DebugScopeRegs[S] = *LBReg;
}
// Emit DebugTypedef for each typedef. Placed after the other type loops so a
// typedef can resolve its underlying type. A typedef whose base type is not
// emitted is skipped. A typedef whose base is another typedef emitted later
// in this same pass is also skipped, the emission-order gap tracked in
// https://github.com/llvm/llvm-project/issues/211850.
for (const DIDerivedType *TD : TypedefTypes) {
if (auto TDReg =
emitDebugTypedef(TD, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI))
DebugScopeRegs[TD] = *TDReg;
}
// Emit DebugFunctionDeclaration for DISubprogram declarations.
for (const DISubprogram *SP : SubprogramDeclarations) {
if (auto DeclReg = emitDebugFunctionDeclaration(SP, VoidTypeReg, I32TypeReg,
ExtInstSetReg, MAI))
DebugScopeRegs[SP] = *DeclReg;
}
// Emit DebugTypeMember and DebugTypeComposite for each struct, class, or
// union. Each member is emitted before the composite that lists it, so the
// Members operand references already-defined ids. A member whose type is not
// in DebugScopeRegs is skipped.
for (const DICompositeType *CT : CompositeTypes) {
SmallVector<MCRegister> MemberRegs;
for (const DINode *Element : CT->getElements()) {
const auto *M = dyn_cast<DIDerivedType>(Element);
if (!M || M->getTag() != dwarf::DW_TAG_member)
continue;
if (auto MemberReg = emitDebugTypeMember(M, VoidTypeReg, I32TypeReg,
ExtInstSetReg, MAI))
MemberRegs.push_back(*MemberReg);
}
if (auto CompReg = emitDebugTypeComposite(CT, MemberRegs, VoidTypeReg,
I32TypeReg, ExtInstSetReg, MAI))
DebugScopeRegs[CT] = *CompReg;
}
// Emit DebugFunction for DISubprogram definitions.
for (const DISubprogram *SP : SubprogramDefinitions) {
if (auto FnReg =
emitDebugFunction(SP, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI))
DebugScopeRegs[SP] = *FnReg;
}
// Emit DebugLexicalBlock for each collected DILexicalBlock, in parent-
// before-child order. Placed after DebugFunction so a block directly
// enclosed by a function (the common case) can resolve its Parent operand;
// DINamespace entries were already emitted above.
for (const DIScope *S :
make_filter_range(LexicalBlocks, IsaPred<DILexicalBlock>)) {
if (auto LBReg = emitDebugLexicalBlock(S, VoidTypeReg, I32TypeReg,
ExtInstSetReg, MAI))
DebugScopeRegs[S] = *LBReg;
}
// Emit DebugLocalVariable after DebugFunction and their lexical blocks so the
// Parent operand can resolve. Record the ids for DebugDeclare.
for (const DILocalVariable *LV : LocalVariables)
if (auto LVReg = emitDebugLocalVariable(LV, VoidTypeReg, I32TypeReg,
ExtInstSetReg, MAI))
DebugLocalVariableRegs[LV] = *LVReg;
// Opcodes like DebugDeclare are part of the function body, but
// DebugExpression is not. For such opcodes, we collect the expressions
// directly from the MIR to avoid inconsistencies with those in the LLVM IR
// module.
SetVector<const DIExpression *> Expressions;
collectDebugExpressions(Expressions);
for (const DIExpression *Expr : Expressions)
if (auto ExprReg = emitDebugExpression(Expr, VoidTypeReg, I32TypeReg,
ExtInstSetReg, MAI))
DebugExpressionRegs[Expr] = *ExprReg;
// Emit DebugGlobalVariable for each collected DIGlobalVariable.
for (const auto &[GV, Info] : GlobalVariableDebugInfoMap)
emitDebugGlobalVariable(GV, Info, VoidTypeReg, I32TypeReg, ExtInstSetReg,
MAI);
// Emit DebugInlinedAt allowing recursive inlining.
for (const DILocation *DL : UniqueDebugLocations)
if (const DILocation *IA = DL->getInlinedAt())
getOrEmitDebugInlinedAt(IA, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
// 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);
emitOpConstantI32(DL->getColumn() + 1, I32TypeReg, MAI);
MCRegister FileStrReg =
getCachedScopePathOpStringReg(DL->getScope(),
/*UseEmptyPathIfNullScope=*/true);
getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg, ExtInstSetReg,
MAI);
}
GlobalNSDIEnabled = true;
}
SmallString<128>
SPIRVNonSemanticDebugHandler::getDebugFullPath(const DIScope *Scope) const {
SmallString<128> Out;
if (!Scope)
return Out;
StringRef Filename = Scope->getFilename();
const auto Style = sys::path::Style::native;
if (sys::path::is_absolute(Filename, Style))
Out.assign(Filename.begin(), Filename.end());
else {
StringRef Dir = Scope->getDirectory();
Out.assign(Dir.begin(), Dir.end());
sys::path::append(Out, Style, Filename);
}
return Out;
}
MCRegister SPIRVNonSemanticDebugHandler::getOrEmitDebugSourceForFileStrReg(
MCRegister FileStrReg, MCRegister VoidTypeReg, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI) {
const unsigned Key = FileStrReg.id();
auto It = DebugSourceRegByFileStr.find(Key);
if (It != DebugSourceRegByFileStr.end())
return It->second;
MCRegister DS = emitExtInst(SPIRV::NonSemanticExtInst::DebugSource,
VoidTypeReg, ExtInstSetReg, {FileStrReg}, MAI);
DebugSourceRegByFileStr[Key] = DS;
return DS;
}