https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/183122
>From 457625f5839bffcece65258bb0c47abbdee67f1c Mon Sep 17 00:00:00 2001 From: Manuel Carrasco <[email protected]> Date: Tue, 24 Feb 2026 12:00:13 -0600 Subject: [PATCH] [SPIRV] Add support for emitting DebugFunction debug info instructions This commit adds support for emitting SPIRV DebugFunction and DebugFunctionDefinition instructions for function definitions. --- .../Target/SPIRV/SPIRVEmitNonSemanticDI.cpp | 218 ++++++++++++++++++ .../SPIRV/debug-info/debug-function.ll | 40 ++++ 2 files changed, 258 insertions(+) create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp index f016897915f61..904309f4ef93f 100644 --- a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp @@ -57,6 +57,7 @@ struct SPIRVEmitNonSemanticDI : public MachineFunctionPass { private: bool emitGlobalDI(MachineFunction &MF); + bool emitFunctionDI(MachineFunction &MF); static SourceLanguage convertDWARFToSPIRVSourceLanguage(int64_t LLVMSourceLanguage); const Module *getModule(MachineFunction &MF); @@ -69,6 +70,14 @@ struct SPIRVEmitNonSemanticDI : public MachineFunctionPass { const RegisterBankInfo *RBI, MachineFunction &MF, SPIRV::NonSemanticExtInst::NonSemanticExtInst Inst, std::initializer_list<Register> Registers); + static Register + emitDebugTypeFunction(MachineRegisterInfo &MRI, MachineIRBuilder &MIRBuilder, + SPIRVGlobalRegistry *GR, const SPIRVTypeInst &VoidTy, + const SPIRVTypeInst &I32Ty, const SPIRVInstrInfo *TII, + const SPIRVRegisterInfo *TRI, + const RegisterBankInfo *RBI, MachineFunction &MF, + DISubroutineType *FuncType, LLVMContext *Context); + static uint64_t getDebugFunctionFlags(const DISubprogram *SP); }; } // anonymous namespace @@ -156,6 +165,63 @@ Register SPIRVEmitNonSemanticDI::emitDIInstruction( return InstReg; } +// Emit a DebugTypeFunction instruction for the given DISubroutineType. +// This creates a SPIRV debug type function that represents the function +// signature, including type flags, return type, and parameter types. Currently +// only handles void functions with no parameters; type flags are not translated +// (using 0 as a placeholder), and full parameter and return type support is +// TODO. +Register SPIRVEmitNonSemanticDI::emitDebugTypeFunction( + MachineRegisterInfo &MRI, MachineIRBuilder &MIRBuilder, + SPIRVGlobalRegistry *GR, const SPIRVTypeInst &VoidTy, + const SPIRVTypeInst &I32Ty, const SPIRVInstrInfo *TII, + const SPIRVRegisterInfo *TRI, const RegisterBankInfo *RBI, + MachineFunction &MF, DISubroutineType *FuncType, LLVMContext *Context) { + // TODO: Translate flags from the function type. Currently not translating + // flags, using 0 as a placeholder. + const Register TypeFlagsReg = + GR->buildConstantInt(0, MIRBuilder, I32Ty, false); + + SmallVector<Register> TypeRegs; + TypeRegs.push_back(TypeFlagsReg); + + // TODO: Handle parameters and return types + // void function with no parameters - use OpTypeVoid for return type + const SPIRVTypeInst OpTypeVoidInst = + GR->getOrCreateSPIRVType(Type::getVoidTy(*Context), MIRBuilder, + SPIRV::AccessQualifier::ReadWrite, false); + const Register VoidTypeReg = GR->getSPIRVTypeID(OpTypeVoidInst); + TypeRegs.push_back(VoidTypeReg); + + // Emit DebugTypeFunction instruction + const Register InstReg = MRI.createVirtualRegister(&SPIRV::IDRegClass); + MRI.setType(InstReg, LLT::scalar(32)); + MachineInstrBuilder MIB = + MIRBuilder.buildInstr(SPIRV::OpExtInst) + .addDef(InstReg) + .addUse(GR->getSPIRVTypeID(VoidTy)) + .addImm(static_cast<int64_t>( + SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100)) + .addImm(SPIRV::NonSemanticExtInst::DebugTypeFunction); + for (auto Reg : TypeRegs) { + MIB.addUse(Reg); + } + MIB.constrainAllUses(*TII, *TRI, *RBI); + GR->assignSPIRVTypeToVReg(VoidTy, InstReg, MF); + return InstReg; +} + +// TODO: Support additional DebugFunction flags. Currently only FlagIsDefinition +// is handled. +uint64_t SPIRVEmitNonSemanticDI::getDebugFunctionFlags(const DISubprogram *SP) { + // Map flags - minimal implementation + // FlagIsDefinition = 1 << 3 + uint64_t Flags = 0; + if (SP->isDefinition()) + Flags |= (1 << 3); // FlagIsDefinition + return Flags; +} + bool SPIRVEmitNonSemanticDI::emitGlobalDI(MachineFunction &MF) { // If this MachineFunction doesn't have any BB repeat procedure // for the next @@ -381,8 +447,160 @@ bool SPIRVEmitNonSemanticDI::emitGlobalDI(MachineFunction &MF) { return true; } +// Emits the SPIRV DebugFunction instruction for a given MachineFunction. +bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { + const Function &F = MF.getFunction(); + + DISubprogram *SP = F.getSubprogram(); + // DISubProgram is not available, don't translate + if (!SP) { + return false; + } + + // TODO: Support declarations + // Only process function definitions, skip declarations. + // Function declarations require an optional operand in the DebugFunction + // instruction that is not yet supported. + if (!SP->isDefinition()) { + return false; + } + + // We insert at the first basic block available + if (MF.begin() == MF.end()) { + return false; + } + + // Get the scope from DISubProgram + DIScope *Scope = SP->getScope(); + if (!Scope) { + return false; + } + + // TODO: Support additional DIScope types beyond DIFile. + // Only translate when scope is DIFile + const DIFile *FileScope = dyn_cast<DIFile>(Scope); + if (!FileScope) { + return false; + } + + // Use SP->getUnit() as the scope for DebugSource. + // In SPIRV, the DebugSource scope cannot be a File, so we use the + // CompilationUnit instead. This matches what the translator does when + // handling DIFile scopes. + const DICompileUnit *CU = SP->getUnit(); + if (!CU) { + return false; + } + + // Check for function type - required for DebugTypeFunction + DISubroutineType *FuncType = SP->getType(); + if (!FuncType) { + return false; + } + + // TODO: Support functions with return types and parameters. + // Check that the function type array has exactly one element and it is null. + // This corresponds to void functions with no parameters. + DITypeArray TypeArray = FuncType->getTypeArray(); + if (TypeArray.size() != 1 || TypeArray[0] != nullptr) { + return false; + } + + const Module *M = getModule(MF); + LLVMContext *Context = &M->getContext(); + + // Emit DebugCompilationUnit and DebugFunction + { + const SPIRVInstrInfo *TII = TM->getSubtargetImpl()->getInstrInfo(); + const SPIRVRegisterInfo *TRI = TM->getSubtargetImpl()->getRegisterInfo(); + const RegisterBankInfo *RBI = TM->getSubtargetImpl()->getRegBankInfo(); + SPIRVGlobalRegistry *GR = TM->getSubtargetImpl()->getSPIRVGlobalRegistry(); + MachineRegisterInfo &MRI = MF.getRegInfo(); + MachineBasicBlock &MBB = *MF.begin(); + + MachineIRBuilder MIRBuilder(MBB, MBB.getFirstTerminator()); + + const SPIRVTypeInst VoidTy = + GR->getOrCreateSPIRVType(Type::getVoidTy(*Context), MIRBuilder, + SPIRV::AccessQualifier::ReadWrite, false); + + const SPIRVTypeInst I32Ty = + GR->getOrCreateSPIRVType(Type::getInt32Ty(*Context), MIRBuilder, + SPIRV::AccessQualifier::ReadWrite, false); + + // Get file path from DICompileUnit for DebugSource (needed for + // DebugFunction) + DIFile *File = CU->getFile(); + SmallString<128> FilePath; + sys::path::append(FilePath, File->getDirectory(), File->getFilename()); + + // Emit DebugSource (needed for DebugFunction) + const Register FilePathStrReg = emitOpString(MRI, MIRBuilder, FilePath); + const Register DebugSourceReg = emitDIInstruction( + MRI, MIRBuilder, GR, VoidTy, TII, TRI, RBI, MF, + SPIRV::NonSemanticExtInst::DebugSource, {FilePathStrReg}); + + // Look up the DebugCompilationUnit register from emitGlobalDI + auto It = CompileUnitRegMap.find(CU); + assert(It != CompileUnitRegMap.end() && + "DebugCompilationUnit register should have been created in " + "emitGlobalDI"); + const Register DebugCompUnitReg = It->second; + + // Emit DebugFunction + // Get function metadata + StringRef FuncName = SP->getName(); + StringRef LinkageName = SP->getLinkageName(); + unsigned Line = SP->getLine(); + unsigned ScopeLine = SP->getScopeLine(); + + uint64_t Flags = getDebugFunctionFlags(SP); + + const Register NameStrReg = emitOpString(MRI, MIRBuilder, FuncName); + const Register LinkageNameStrReg = + emitOpString(MRI, MIRBuilder, LinkageName); + + // Emit DebugTypeFunction + const Register DebugTypeFunctionReg = + emitDebugTypeFunction(MRI, MIRBuilder, GR, VoidTy, I32Ty, TII, TRI, RBI, + MF, FuncType, Context); + + const Register LineReg = + GR->buildConstantInt(Line, MIRBuilder, I32Ty, false); + const Register ColumnReg = + GR->buildConstantInt(0, MIRBuilder, I32Ty, false); + const Register FlagsReg = + GR->buildConstantInt(Flags, MIRBuilder, I32Ty, false); + const Register ScopeLineReg = + GR->buildConstantInt(ScopeLine, MIRBuilder, I32Ty, false); + + // TODO: Handle function declarations. Declarations require an optional + // operand in the DebugFunction instruction that specifies the declaration's + // location. + const Register DebugFuncReg = emitDIInstruction( + MRI, MIRBuilder, GR, VoidTy, TII, TRI, RBI, MF, + SPIRV::NonSemanticExtInst::DebugFunction, + {NameStrReg, DebugTypeFunctionReg, DebugSourceReg, LineReg, ColumnReg, + DebugCompUnitReg, LinkageNameStrReg, FlagsReg, ScopeLineReg}); + + // Emit DebugFunctionDefinition to link the DebugFunction to the actual + // OpFunction. + const MachineInstr *OpFunctionMI = GR->getFunctionDefinition(&F); + if (OpFunctionMI && OpFunctionMI->getOpcode() == SPIRV::OpFunction) { + Register FuncReg = OpFunctionMI->getOperand(0).getReg(); + emitDIInstruction(MRI, MIRBuilder, GR, VoidTy, TII, TRI, RBI, MF, + SPIRV::NonSemanticExtInst::DebugFunctionDefinition, + {DebugFuncReg, FuncReg}); + } + + return true; + } +} + +// TODO: Deduplicate instructions bool SPIRVEmitNonSemanticDI::runOnMachineFunction(MachineFunction &MF) { CompileUnitRegMap.clear(); bool Res = emitGlobalDI(MF); + Res |= emitFunctionDI(MF); return Res; } diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll new file mode 100644 index 0000000000000..d9bbbe950515f --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll @@ -0,0 +1,40 @@ +; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s +; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %} + +; The path separator may be '/' on Unix or '\' on Windows, so we use a flexible pattern +; CHECK: OpString "{{[/\]}}A{{[/\]}}B{{[/\]}}C{{[/\]}}example.cpp" +; CHECK: [[FILE_STRING:%[0-9]+]] = OpString "{{[/\]}}A{{[/\]}}B{{[/\]}}C{{[/\]}}example.cpp" +; CHECK: [[NAME:%[0-9]+]] = OpString "test1" +; CHECK: [[LINKAGE:%[0-9]+]] = OpString "XXXX" +; CHECK: [[VOID_TYPE:%[0-9]+]] = OpTypeVoid +; CHECK: [[ZERO:%[0-9]+]] = OpConstant {{.*}} 0 +; CHECK: [[LINE:%[0-9]+]] = OpConstant {{.*}} 1 +; CHECK: [[FLAGS:%[0-9]+]] = OpConstant {{.*}} 8 +; CHECK: OpExtInst {{.*}} DebugSource {{.*}} +; CHECK: [[PARENT:%[0-9]+]] = OpExtInst {{.*}} DebugCompilationUnit {{.*}} +; CHECK: [[SOURCE:%[0-9]+]] = OpExtInst {{.*}} DebugSource [[FILE_STRING]] +; CHECK: [[TYPE:%[0-9]+]] = OpExtInst {{.*}} DebugTypeFunction [[ZERO]] [[VOID_TYPE]] +; CHECK: [[DEBUG_FUNC:%[0-9]+]] = OpExtInst {{.*}} DebugFunction [[NAME]] [[TYPE]] [[SOURCE]] [[LINE]] [[ZERO]] [[PARENT]] [[LINKAGE]] [[FLAGS]] [[LINE]] +; CHECK: [[FUNC:%[0-9]+]] = OpFunction {{.*}} +; DebugFunctionDefinition must be after OpFunction +; CHECK: OpExtInst {{.*}} DebugFunctionDefinition [[DEBUG_FUNC]] [[FUNC]] + +target triple = "spirv64-unknown-unknown" + +define spir_func void @test1() !dbg !9 { +entry: + ret void +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_Zig, file: !1, producer: "clang version XX.X.XXXX (F F)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "example.cpp", directory: "/A/B/C") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"frame-pointer", i32 2} +!9 = distinct !DISubprogram(name: "test1", linkageName: "XXXX", scope: !1, file: !1, line: 1, type: !10, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0) +!10 = !DISubroutineType(types: !11) +!11 = !{null} _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
