| //===-- SPIRVNonSemanticDebugHandler.h - 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file declares SPIRVNonSemanticDebugHandler, a DebugHandlerBase subclass | |
| // that emits NonSemantic.Shader.DebugInfo.100 instructions in the SPIR-V | |
| // AsmPrinter. It replaces SPIRVEmitNonSemanticDI, which was a | |
| // MachineFunctionPass, with a handler that controls instruction placement | |
| // directly instead of routing through SPIRVModuleAnalysis. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #ifndef LLVM_LIB_TARGET_SPIRV_SPIRVNONSEMANTICDEBUGHANDLER_H | |
| #define LLVM_LIB_TARGET_SPIRV_SPIRVNONSEMANTICDEBUGHANDLER_H | |
| #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/SmallPtrSet.h" | |
| #include "llvm/ADT/SmallString.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringMap.h" | |
| #include "llvm/CodeGen/DebugHandlerBase.h" | |
| #include "llvm/IR/DebugInfoMetadata.h" | |
| #include "llvm/MC/MCInst.h" | |
| #include "llvm/MC/MCRegister.h" | |
| #include <optional> | |
| #include <type_traits> | |
| #include <utility> | |
| namespace llvm { | |
| class GlobalVariable; | |
| class MachineDominatorTree; | |
| class SPIRVSubtarget; | |
| /// AsmPrinter handler that emits NonSemantic.Shader.DebugInfo.100 (NSDI) | |
| /// instructions for the SPIR-V backend. Registered with SPIRVAsmPrinter when | |
| /// the module contains debug info (llvm.dbg.cu). | |
| /// | |
| /// Call sequence: | |
| /// - beginModule() collects compile-unit metadata. | |
| /// - prepareModuleOutput() adds the extension and ext-inst set to MAI. | |
| /// - emitNonSemanticDebugStrings() emits NSDI OpStrings in section 7. | |
| /// - emitNonSemanticGlobalDebugInfo() emits module-scope NSDI and sets | |
| /// GlobalNSDIEnabled. | |
| /// - 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>( | |
| SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100); | |
| struct CompileUnitInfo { | |
| const DICompileUnit *TheCU = nullptr; | |
| SmallString<128> FilePath; | |
| unsigned SpirvSourceLanguage = 0; // NonSemantic.Shader.DebugInfo.100 source | |
| // language code (section 4.3) | |
| }; | |
| SmallVector<CompileUnitInfo> CompileUnits; | |
| int64_t DwarfVersion = 0; | |
| SmallVector<const DIType *> DebugTypes; | |
| // Compile unit whose enums list names each enum. DIBuilder gives a file-scope | |
| // enum a null scope, so this list is the only record of its unit. | |
| DenseMap<const DICompositeType *, const DICompileUnit *> EnumCompileUnits; | |
| // NonSemantic debug instruction result id per emitted scope. | |
| DenseMap<const DIScope *, MCRegister> DebugScopeRegs; | |
| enum class EmitStatus { | |
| Emitted, | |
| Unsupported, | |
| InProgress, | |
| }; | |
| // One scope instruction, built but not yet emitted. getOrCreateDebugScope | |
| // is the only place that emits it. | |
| struct PreparedScope { | |
| SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode{}; | |
| SmallVector<MCRegister, 8> Operands; | |
| PreparedScope() = default; | |
| PreparedScope(SPIRV::NonSemanticExtInst::NonSemanticExtInst Op, | |
| ArrayRef<MCRegister> Ops) | |
| : Opcode(Op), Operands(Ops.begin(), Ops.end()) {} | |
| }; | |
| // Status plus a payload. Value is set only when the status is Emitted, so | |
| // InProgress and Unsupported carry no id and no PreparedScope. The payload | |
| // is an id for a scope that already exists, and a PreparedScope for an | |
| // instruction that getOrCreateDebugScope has not emitted yet. | |
| template <typename T> struct EmitResult { | |
| EmitStatus Status = EmitStatus::Unsupported; | |
| std::optional<T> Value; | |
| static EmitResult emitted(T V) { | |
| EmitResult R; | |
| R.Status = EmitStatus::Emitted; | |
| R.Value = std::move(V); | |
| return R; | |
| } | |
| static EmitResult unsupported() { return {}; } | |
| static EmitResult inProgress() { | |
| EmitResult R; | |
| R.Status = EmitStatus::InProgress; | |
| return R; | |
| } | |
| template <typename U> | |
| static EmitResult failure(const EmitResult<U> &Other) { | |
| assert(!Other && "failure() requires a non-emitted result"); | |
| EmitResult R; | |
| R.Status = Other.Status; | |
| return R; | |
| } | |
| explicit operator bool() const { | |
| assert(Value.has_value() == (Status == EmitStatus::Emitted) && | |
| "payload is set exactly when the result is emitted"); | |
| if constexpr (std::is_same_v<T, MCRegister>) | |
| assert(!Value || Value->isValid()); | |
| return Status == EmitStatus::Emitted; | |
| } | |
| }; | |
| // Used to track which scopes are in progress, and which operands are used by | |
| // a forward references. Operands must be later defined even if the scope | |
| // fails to emit. | |
| struct InProgressScope { | |
| MCRegister Id; | |
| bool ForwardReferenced = false; | |
| }; | |
| // Types and scopes whose instruction is currently being built. | |
| SmallDenseMap<const DIScope *, InProgressScope, 8> ScopesInProgress; | |
| // True when -spirv-debug-scope-forward-refs is set and prepareModuleOutput | |
| // saw SPV_KHR_relaxed_extended_instruction. | |
| bool HasRelaxedExtInst = false; | |
| // Types and scopes that are not supported. | |
| SmallPtrSet<const DIScope *, 8> FailedScopes; | |
| // DISubprogram nodes collected in beginModule(). | |
| SmallVector<const DISubprogram *> Subprograms; | |
| // Distinct DILocations from instruction !dbg attachments and debug program | |
| // records (#dbg_declare, #dbg_value, #dbg_assign, #dbg_label). | |
| SetVector<const DILocation *> UniqueDebugLocations; | |
| struct GlobalVariableDebugInfo { | |
| const DIExpression *Expr = nullptr; | |
| const GlobalVariable *LLVMGV = nullptr; | |
| }; | |
| MapVector<const DIGlobalVariable *, GlobalVariableDebugInfo> | |
| GlobalVariableDebugInfoMap; | |
| // Distinct DILocalVariable nodes collected in beginModule() from dbg records | |
| // and from DISubprogram retained nodes. | |
| SetVector<const DILocalVariable *> LocalVariables; | |
| // DebugLocalVariable result id per variable that module-scope emission | |
| // actually emitted. DebugDeclare needs it for its Local Variable operand; a | |
| // variable missing here (skipped type or scope) gets no declare. | |
| DenseMap<const DILocalVariable *, MCRegister> DebugLocalVariableRegs; | |
| // DebugExpression result id per DIExpression that could be lowered. An | |
| // expression missing here uses operations with no NonSemantic counterpart, | |
| // so declares referencing it are skipped rather than described wrongly. | |
| DenseMap<const DIExpression *, MCRegister> DebugExpressionRegs; | |
| // Distinct DILexicalBlock, DINamespace, and DILexicalBlockFile scopes, | |
| // parent-before-child order, collected in beginModule(). | |
| SetVector<const DIScope *> LexicalBlocks; | |
| // DebugInlinedAt result id per DILocation used as an inlined-at chain link. | |
| DenseMap<const DILocation *, MCRegister> DebugInlinedAtRegs; | |
| // Path \c OpString result id per \c DIScope (CU, \c DIFile, declaration | |
| // \c DISubprogram, …). Filled during \c emitNonSemanticDebugStrings() using | |
| // \c getDebugFullPath + \c emitOpStringIfNew; section 10 uses it for | |
| // \c DebugSource without recomputing path text. | |
| DenseMap<const DIScope *, MCRegister> ScopeToPathOpStringReg; | |
| // DebugSource result id keyed by path \c OpString id (\c MCRegister::id()), | |
| // deduplicating when the same file string is reused. | |
| DenseMap<unsigned, MCRegister> DebugSourceRegByFileStr; | |
| // Maps OpString contents to result id. Populated only by emitOpStringIfNew() | |
| // during section 7; section 10 uses getCachedOpStringReg() (lookup only). | |
| StringMap<MCRegister> OpStringContentCache; | |
| #ifndef NDEBUG // Only declare the variable for debugging purposes. | |
| // True after emitNonSemanticDebugStrings() emitted the NSDI OpStrings for | |
| // this module. SPIRVAsmPrinter calls that before | |
| // emitNonSemanticGlobalDebugInfo(). | |
| bool NonSemanticOpStringsSectionEmitted = false; | |
| #endif | |
| MCRegister CachedEmptyStringReg; | |
| MCRegister CachedDebugInfoNoneReg; | |
| MCRegister CachedOpTypeVoidReg; | |
| MCRegister CachedOpTypeInt32Reg; | |
| // 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 DebugOperation instructions, keyed by NonSemantic | |
| // opcode followed by the 32-bit operation arguments. Inline size 3 is the | |
| // spec maximum (opcode plus at most two operands: BitPiece, Fragment). | |
| DenseMap<SmallVector<uint32_t, 3>, MCRegister> DebugOperationCache; | |
| // Cache of already-emitted DebugExpression instructions, keyed by the | |
| // DebugOperation result ids in operand order. Useful for debug values | |
| // and global-variable init expressions. | |
| DenseMap<SmallVector<MCRegister>, MCRegister> DebugExpressionCache; | |
| // True once emitNonSemanticGlobalDebugInfo() has run. Both | |
| // SPIRVAsmPrinter::emitFunctionHeader() and emitEndOfAsmFile() may call | |
| // outputModuleSections(), each guarded by ModuleSectionsEmitted, so only | |
| // one fires. This flag provides a secondary guard in case the call sites | |
| // change. | |
| bool GlobalDIEmitted = false; | |
| // True when emitNonSemanticGlobalDebugInfo() completed module-scope NSDI | |
| // emission for this module. | |
| bool GlobalNSDIEnabled = false; | |
| SPIRV::ModuleAnalysisInfo *CurrentMAI = nullptr; | |
| const MachineFunction *CurrentMF = nullptr; | |
| const MachineInstr *LastFunctionOpVariable = nullptr; | |
| bool DebugFunctionDefinitionEmitted = false; | |
| // Instruction that opened the DebugLine / DebugScope region currently in | |
| // effect, or nullptr when no region is open. The two are tracked separately | |
| // because a DebugScope region usually spans several DebugLine regions, and | |
| // either one can skip emission on a cache miss. | |
| 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); | |
| /// Collect compile-unit metadata from the module. Called by | |
| /// AsmPrinter::doInitialization() via the handler list. No emission. | |
| void beginModule(Module *M) override; | |
| /// Emit OpString instructions for all NSDI file paths and basic type names | |
| /// into the debug section (section 7 of the SPIR-V module layout). Must be | |
| /// called from SPIRVAsmPrinter::outputDebugSourceAndStrings(), after | |
| /// prepareModuleOutput() has registered the ext inst set. Registers are | |
| /// stored in \c OpStringContentCache and \c ScopeToPathOpStringReg; | |
| /// \c emitNonSemanticGlobalDebugInfo() resolves them via | |
| /// \c getCachedOpStringReg() and path maps. | |
| void emitNonSemanticDebugStrings(SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Add SPV_KHR_non_semantic_info extension and | |
| /// NonSemantic.Shader.DebugInfo.100 ext inst set entry to MAI. Must be called | |
| /// before outputGlobalRequirements() and outputOpExtInstImports() in | |
| /// SPIRVAsmPrinter::outputModuleSections(). | |
| void prepareModuleOutput(const SPIRVSubtarget &ST, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Emit module-scope NSDI instructions (DebugSource, DebugCompilationUnit, | |
| /// DebugTypeBasic, DebugTypePointer, DebugTypeFunction, | |
| /// DebugFunctionDeclaration, DebugFunction). Called by | |
| /// SPIRVAsmPrinter::outputModuleSections() at section 10 in place of | |
| /// outputModuleSection(MB_NonSemanticGlobalDI). Requires | |
| /// emitNonSemanticDebugStrings() to have run first when NSDI strings apply. | |
| /// Sets \c GlobalNSDIEnabled when module-scope NSDI emission completes. | |
| void emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Called after the synthesized entry \c OpLabel has been emitted. | |
| void notifyEntryLabelEmitted(const MachineFunction &MF); | |
| protected: | |
| // All module-level output is driven by emitNonSemanticGlobalDebugInfo(), | |
| // called explicitly from SPIRVAsmPrinter::outputModuleSections(). Nothing | |
| // needs to happen in the AsmPrinterHandler::endModule() callback. | |
| void endModule() override {} | |
| // DebugHandlerBase stores MMI as a pointer copy from Asm->MMI at construction | |
| // time (DebugHandlerBase.cpp: `MMI(Asm->MMI)`). The handler is constructed | |
| // before AsmPrinter::doInitialization() runs, so Asm->MMI is null at that | |
| // point and MMI remains null for this handler's entire lifetime. Do not call | |
| // the base-class beginInstruction/endInstruction — they dereference MMI to | |
| // create temp symbols for label tracking and would crash. | |
| // Future local NSDI that needs MCContext must use | |
| // Asm->OutStreamer->getContext() rather than MMI->getContext(). | |
| void beginInstruction(const MachineInstr *MI) override; | |
| void endInstruction() override; | |
| // Override beginFunctionImpl(), not beginFunction(): | |
| // DebugHandlerBase::beginFunction() populates LScopes and DbgValues needed | |
| // for future DebugLine emission. | |
| void beginFunctionImpl(const MachineFunction *MF) override; | |
| void endFunctionImpl(const MachineFunction *MF) override; | |
| private: | |
| void emitDebugFunctionDefinition(MCRegister DebugFunctionReg, | |
| MCRegister OpFunctionReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| void resetPerFunctionDebugState(); | |
| /// Resolve the instruction that a per-instruction DebugLine/DebugScope | |
| /// update should attach to: \p MI adjusted forward past a merge | |
| /// instruction to its terminator, or \c std::nullopt if \p MI is not a | |
| /// valid attachment point (skip-emission, or one of the structural opcodes | |
| /// that can never carry DebugLine/DebugScope: OpFunction, | |
| /// OpFunctionParameter, OpFunctionEnd, OpLabel, OpPhi). | |
| std::optional<const MachineInstr *> | |
| resolveDebugLocTarget(const MachineInstr *MI); | |
| /// 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); | |
| void emitMCInst(MCInst &Inst); | |
| MCRegister emitOpString(StringRef S, SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Section 7 only: emit OpString and cache it if not already present. Must | |
| /// not be called after NonSemanticOpStringsSectionEmitted is set. Returns | |
| /// the path (or string) \c OpString result id. | |
| MCRegister emitOpStringIfNew(StringRef S, SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Section 10 only: lookup OpString id from cache; asserts if missing or if | |
| /// section 7 did not complete. | |
| MCRegister getCachedOpStringReg(StringRef S); | |
| /// Section 7 only: emit the path \c OpString for \p Scope and cache it under | |
| /// \p Scope. Returns the \c OpString result id. A \p Scope already seen | |
| /// returns the cached id without rebuilding the path. A null \p Scope maps to | |
| /// the empty path and is cached like any other, though section 10 reads it | |
| /// through \c getCachedScopePathOpStringReg, which handles null separately. | |
| MCRegister emitAndCacheScopePathOpStringReg(const DIScope *Scope, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Section 10 only: lookup path \c OpString id for \p Scope from | |
| /// \c ScopeToPathOpStringReg; asserts if missing or invalid. When | |
| /// \p UseEmptyPathIfNullScope is true and \p Scope is null, returns | |
| /// \c CachedEmptyStringReg instead. | |
| MCRegister | |
| getCachedScopePathOpStringReg(const DIScope *Scope, | |
| bool UseEmptyPathIfNullScope = false); | |
| MCRegister emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Mark in-progress scopes named by \p Operands. Return true when any | |
| /// operand is such an id. A marked id needs a definition if that scope fails. | |
| bool markForwardReferencedOperands(ArrayRef<MCRegister> Operands); | |
| MCRegister emitExtInst(SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode, | |
| MCRegister VoidTypeReg, MCRegister ExtInstSetReg, | |
| ArrayRef<MCRegister> Operands, | |
| SPIRV::ModuleAnalysisInfo &MAI, | |
| std::optional<MCRegister> ResultReg = std::nullopt); | |
| /// Return OpTypeVoid id for this module (lazy lookup / emit, then cache). | |
| MCRegister getOrEmitOpTypeVoidReg(SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Return OpTypeInt 32 0 id for this module (lazy lookup / emit, then cache). | |
| MCRegister getOrEmitOpTypeInt32Reg(SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Find OpTypeVoid in the already-emitted TypeConstVars section, or emit one | |
| /// if the module does not contain it (e.g. no void-returning functions). | |
| MCRegister findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Find OpTypeInt 32 0 in the already-emitted TypeConstVars section, or emit | |
| /// one if the module does not contain it. | |
| MCRegister findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Return the id for the type or scope \p S, emitting it and the types and | |
| /// scopes it names first. One already in \c DebugScopeRegs is Emitted. A null | |
| /// \p S or one in \c FailedScopes is Unsupported. Entering a scope reserves | |
| /// its result id. A back edge (\p S already in \c ScopesInProgress) returns | |
| /// that id when \c HasRelaxedExtInst is set, and InProgress otherwise. If | |
| /// emission fails after an emitted instruction used that id, it is defined as | |
| /// DebugInfoNone. An id that nothing named stays undefined. An Unsupported | |
| /// result is recorded in \c FailedScopes; an InProgress one is not, so a | |
| /// later walk can retry it. | |
| EmitResult<MCRegister> getOrCreateDebugScope(const DIScope *S); | |
| /// Build the instruction for \p S. Only \c getOrCreateDebugScope calls this | |
| /// and emits it. | |
| EmitResult<PreparedScope> emitDebugScope(const DIScope *S); | |
| /// Build the \c DebugType* instruction for \p CT, picked by its tag. | |
| EmitResult<PreparedScope> emitDebugTypeForCompositeType( | |
| const DICompositeType *CT, MCRegister VoidTypeReg, MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build the \c DebugType* instruction for \p DT, picked by its tag. | |
| EmitResult<PreparedScope> | |
| emitDebugTypeForDerivedType(const DIDerivedType *DT, MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugTypeBasic for \p BT. Unsupported when the size does not fit | |
| /// in 32 bits. | |
| EmitResult<PreparedScope> emitDebugTypeBasic(const DIBasicType *BT, | |
| MCRegister I32TypeReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugTypePointer for \p PT. Unsupported without a DWARF address | |
| /// space, which gives the storage class. | |
| EmitResult<PreparedScope> | |
| emitDebugTypePointer(const DIDerivedType *PT, SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build one DebugTypeFunction for ST when every DI operand maps to a debug | |
| /// type id; otherwise build nothing and return the first failing slot's | |
| /// status. | |
| EmitResult<PreparedScope> | |
| emitDebugTypeFunctionForSubroutineType(const DISubroutineType *ST, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugFunctionDeclaration for the non-defining subprogram \p SP. | |
| EmitResult<PreparedScope> | |
| emitDebugFunctionDeclaration(const DISubprogram *SP, MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugFunction for the defining subprogram \p SP. | |
| EmitResult<PreparedScope> emitDebugFunction(const DISubprogram *SP, | |
| MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Emit \c DebugLocalVariable for the source local variable \p LV: | |
| /// Name, Type, Source, Line, Column, Parent, Flags, and an optional Arg | |
| /// Number. Line, Column, Flags, and Arg Number are emitted as \c OpConstant | |
| /// ids as required for non-semantic debug info. Column is always 0: | |
| /// \c DILocalVariable has no column field. | |
| /// | |
| /// Arg Number is appended when \p LV is a parameter. | |
| /// | |
| /// \returns The result id register on success. Returns \c std::nullopt and | |
| /// emits nothing if \p LV's scope is not an emitted local scope, | |
| /// if a non-null type was not emitted in \c DebugScopeRegs, or if | |
| /// \c resolveScope returns no id for the Parent operand. | |
| std::optional<MCRegister> | |
| emitDebugLocalVariable(const DILocalVariable *LV, MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Emit \c DebugGlobalVariable for the source global variable \p GV. | |
| /// | |
| /// (\c SPIRVDebug::Operand::GlobalVariable): Name, Type, Source, Line, | |
| /// Column, Parent, Linkage Name, Variable, Flags, and an optional Static | |
| /// Member Declaration. Line, Column, and Flags are emitted as \c OpConstant | |
| /// ids as required for non-semantic debug info. | |
| /// | |
| /// \c DebugInfoNone is used for two operands when LLVM has no value to | |
| /// supply: | |
| /// \c Type when \p GV is a declaration with no DI type (e.g. \c extern void; | |
| /// valid IR, \c isDefinition: false); \c Variable when no \c | |
| /// llvm::GlobalVariable in this module carries \p GV in its \c !dbg metadata. | |
| /// | |
| /// \returns The result id register on success. Returns \c std::nullopt and | |
| /// emits nothing if a non-null \p GV type was not emitted in \c | |
| /// DebugScopeRegs, or \p GV has a static data member declaration that was not | |
| /// emitted in \c DebugScopeRegs. | |
| std::optional<MCRegister> emitDebugGlobalVariable( | |
| const DIGlobalVariable *GV, const GlobalVariableDebugInfo &Info, | |
| MCRegister VoidTypeReg, MCRegister I32TypeReg, MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Collect the \c DIExpression of every debug value in the module | |
| /// (\c DBG_VALUE, \c DBG_VALUE_LIST, \c DBG_INSTR_REF), in MIR order. | |
| /// | |
| /// Reads MIR rather than IR because only MIR shows which debug values | |
| /// survived codegen and in what form, and because an expression synthesized | |
| /// during lowering never appears in the IR at all. Must be called from | |
| /// module-scope emission, which is where the resulting \c DebugExpression | |
| /// instructions have to be emitted. The debug values come from | |
| /// \c SPIRVModuleAnalysis, which sees every \c MachineFunction. | |
| /// | |
| /// Deliberately independent of what the consumers can currently emit, so | |
| /// that adding an instruction that needs an expression (\c DebugValue) needs | |
| /// no change here. The cost is a \c DebugExpression that nothing references | |
| /// yet, for a debug value no instruction is emitted for. | |
| void collectDebugExpressions(const SPIRV::ModuleAnalysisInfo &MAI, | |
| SetVector<const DIExpression *> &Out) const; | |
| /// Emit one \c DebugOperation for \p Op, reusing a cached result id when the | |
| /// same opcode and arguments were already emitted. | |
| /// | |
| /// \returns The result id register on success. Returns \c std::nullopt and | |
| /// emits nothing if \p Op has no NonSemantic counterpart, or carries an | |
| /// argument too large for the 32-bit \c OpConstant operands this set | |
| /// requires. | |
| std::optional<MCRegister> | |
| emitDebugOperation(const DIExpression::ExprOperand &Op, | |
| MCRegister VoidTypeReg, MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Emit one \c DebugOperation per element of \p Expr followed by the | |
| /// \c DebugExpression that lists them. Reuses a cached \c DebugExpression | |
| /// when that sequence of \c DebugOperation ids was already emitted. An | |
| /// empty \p Expr yields a \c DebugExpression with no operands, which is | |
| /// what a plain \c !DIExpression() means. | |
| /// | |
| /// Must be called from module-scope emission only: \c DebugExpression and | |
| /// \c DebugOperation are not in the spec's list of instructions allowed | |
| /// inside a function, and forward references were removed in Rev 2. | |
| /// | |
| /// \returns The result id register on success. Returns \c std::nullopt and | |
| /// does not emit the \c DebugExpression if any element has no NonSemantic | |
| /// counterpart, or carries an argument too large for the 32-bit \c OpConstant | |
| /// operands this set requires. | |
| std::optional<MCRegister> emitDebugExpression(const DIExpression *Expr, | |
| MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// 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. | |
| /// | |
| /// 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); | |
| /// Build \c DebugTypeVector for the vector composite type \p VT. | |
| /// | |
| /// \returns Emitted when the operands are ready. Unsupported, building | |
| /// nothing, if \p VT has no \c DIBasicType base type, if \p VT has more than | |
| /// one \c DISubrange element, or if the component count is not a | |
| /// compile-time constant. If the base cannot be emitted, returns its status. | |
| EmitResult<PreparedScope> emitDebugTypeVector(const DICompositeType *VT, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugTypeArray for the array composite type \p AT. | |
| /// | |
| /// Operands are the element (base) type id followed by one Component Count | |
| /// per \c DISubrange, in DWARF subrange order. A count that is not a | |
| /// compile-time constant is 0, matching \c OpTypeRuntimeArray. A matrix | |
| /// arrives here as a multi-subrange array and is built with one count per | |
| /// dimension. | |
| /// | |
| /// \returns Emitted when the operands are ready. If \p AT's element type | |
| /// cannot be emitted, builds nothing and returns the element's status. | |
| EmitResult<PreparedScope> emitDebugTypeArray(const DICompositeType *AT, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugTypeMember for the data member \p M (a \c DIDerivedType with | |
| /// \c DW_TAG_member). Operands: Name, Type, Source, Line, Column, Offset, | |
| /// Size, Flags. NonSemantic \c DebugTypeMember carries no Parent operand: the | |
| /// enclosing \c DebugTypeComposite references its members, not the reverse. | |
| /// | |
| /// \returns Emitted when the operands are ready. Returns the base's status | |
| /// and builds nothing if \p M's type cannot be emitted. | |
| EmitResult<PreparedScope> emitDebugTypeMember(const DIDerivedType *M, | |
| MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugTypeComposite for the struct, class, or union \p CT, after a | |
| /// \c DebugTypeMember for each data member whose type can be emitted. A | |
| /// forward declaration uses \c DebugInfoNone for Size and no members. | |
| /// | |
| /// \returns Emitted when the operands are ready. Builds nothing and returns | |
| /// the parent's status if the Parent scope cannot be resolved, or | |
| /// Unsupported if the size does not fit in 32 bits. Both are checked before | |
| /// any member is streamed. A member whose type fails is dropped, whatever | |
| /// the reason, and does not fail the composite. | |
| EmitResult<PreparedScope> | |
| emitDebugTypeComposite(const DICompositeType *CT, MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugTypeEnum for the enumeration type \p ET. Operands: Name, | |
| /// Underlying Type, Source, Line, Column, Parent, Size, Flags, then a Value | |
| /// and Name pair per enumerator. Uses \c DebugInfoNone for Underlying Type | |
| /// when \p ET has no base type, and drops an enumerator whose value does not | |
| /// fit in a 32-bit integer. | |
| /// | |
| /// \returns An Emitted result when the operands are ready. An Unsupported | |
| /// result, building nothing, if the size is 0 or does not fit in 32 bits. | |
| /// If the base or Parent fail, it returns code for the one that failed. | |
| EmitResult<PreparedScope> emitDebugTypeEnum(const DICompositeType *ET, | |
| MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugTypedef for the typedef derived type \p TD (a \c | |
| /// DIDerivedType with \c DW_TAG_typedef). Operands: Name, Base Type, Source, | |
| /// Line, Column, Parent. Parent is the enclosing type when \c TD->getScope() | |
| /// is an emitted \c DIType, otherwise the first module \c | |
| /// DebugCompilationUnit. | |
| /// | |
| /// \returns The base's or Parent's status if either fails. | |
| EmitResult<PreparedScope> emitDebugTypedef(const DIDerivedType *TD, | |
| MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Map a \c DISubroutineType::getTypeArray() element to an operand register | |
| /// for | |
| /// \c DebugTypeFunction. A non-null \p Ty is created through | |
| /// \c getOrCreateDebugScope, and its status is returned. A null \p Ty is not | |
| /// a \c getOrCreateDebugScope call and is always Emitted. | |
| /// | |
| /// LLVM encodes a void return as a null first element (and may use null in | |
| /// later slots). NonSemantic \c DebugTypeFunction | |
| /// requires a concrete return-type operand, so when \p ReturnType is true and | |
| /// \p Ty is null, this returns \p VoidTypeReg (\c OpTypeVoid). When | |
| /// \p ReturnType is false and \p Ty is null, this returns | |
| /// \c CachedDebugInfoNoneReg (\c DebugInfoNone). | |
| EmitResult<MCRegister> mapDISignatureTypeToReg(const DIType *Ty, | |
| MCRegister VoidTypeReg, | |
| bool ReturnType); | |
| /// Map a DWARF source language code to a NonSemantic.Shader.DebugInfo.100 | |
| /// source language code. | |
| static unsigned toNSDISrcLang(unsigned DwarfSrcLang); | |
| /// Build a full path from debug \p Scope for OpString / DebugSource, matching | |
| /// SPIRV-LLVM-Translator \c getFullPath (OCLUtil.h): \c DIScope::getFilename, | |
| /// \c getDirectory, and \c sys::path::Style::native. Works for any \c DIScope | |
| /// that carries file path fields (e.g. \c DIFile, \c DISubprogram, | |
| /// \c DICompileUnit). Returns an empty path when \p Scope is null. | |
| SmallString<128> getDebugFullPath(const DIScope *Scope) const; | |
| /// Return an existing \c DebugSource id for file path \c OpString \p | |
| /// FileStrReg or emit \c DebugSource and cache it (keyed by \p FileStrReg | |
| /// id). | |
| MCRegister getOrEmitDebugSourceForFileStrReg(MCRegister FileStrReg, | |
| MCRegister VoidTypeReg, | |
| MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Map \p Scope to the NonSemantic debug id used as a \c Parent operand. | |
| /// | |
| /// A \c DIType, \c DILexicalBlock, \c DINamespace, or \c DISubprogram scope | |
| /// is created through \c getOrCreateDebugScope. When | |
| /// \c -spirv-debug-lexical-block-discriminator-scope is enabled, a | |
| /// \c DILexicalBlockFile is also created this way, becoming a | |
| /// \c DebugLexicalBlockDiscriminator; otherwise it is unwrapped to its | |
| /// enclosing scope first. When \p Scope is null, a \c DIFile, or another | |
| /// scope without a dedicated debug instruction, it falls back to | |
| /// \p FallbackCU or the first module \c DebugCompilationUnit recorded in | |
| /// \c DebugScopeRegs; no such compile unit is Unsupported. | |
| EmitResult<MCRegister> | |
| resolveScope(const DIScope *Scope, const DICompileUnit *FallbackCU = nullptr); | |
| /// Build \c DebugLexicalBlock for \p S, which must be a \c DILexicalBlock or | |
| /// a \c DINamespace. A \c DILexicalBlock supplies Line/Column | |
| /// from \c getLine()/getColumn(); a \c DINamespace has neither, so both are | |
| /// 0, and its Name is appended as an extra \c OpString operand. | |
| /// | |
| /// \returns Emitted when the operands are ready. If \c resolveScope cannot | |
| /// emit \c S->getScope(), builds nothing and returns that status. | |
| EmitResult<PreparedScope> | |
| emitDebugLexicalBlock(const DIScope *S, MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Build \c DebugLexicalBlockDiscriminator for \p LBF. | |
| /// Operands are Source, Discriminator, and Parent (\c LBF->getScope()). | |
| EmitResult<PreparedScope> emitDebugLexicalBlockDiscriminator( | |
| const DILexicalBlockFile *LBF, MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| /// Return a cached \c DebugInlinedAt id for \p IA, or emit one (recursing | |
| /// into \c IA->getInlinedAt() first for the optional Inlined operand, so | |
| /// outer frames are always emitted before the inner frame that references | |
| /// them). Must run after \c DebugScopeRegs is populated, since the Scope | |
| /// operand is resolved through \c resolveScope. \c DebugInlinedAt is not in | |
| /// the spec's in-block instruction list, so this is only ever called from | |
| /// module-scope emission (\c emitNonSemanticGlobalDebugInfo), never from | |
| /// per-instruction emission. | |
| /// | |
| /// \returns An invalid (default-constructed) \c MCRegister, and emits | |
| /// nothing, if \p IA's Scope does not resolve. | |
| MCRegister getOrEmitDebugInlinedAt(const DILocation *IA, | |
| MCRegister VoidTypeReg, | |
| MCRegister I32TypeReg, | |
| MCRegister ExtInstSetReg, | |
| SPIRV::ModuleAnalysisInfo &MAI); | |
| }; | |
| } // namespace llvm | |
| #endif // LLVM_LIB_TARGET_SPIRV_SPIRVNONSEMANTICDEBUGHANDLER_H |