[llvm] [SPIRV] Emitting DebugSource, DebugCompileUnit (PR #97558)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 11 03:36:27 PDT 2024


================
@@ -0,0 +1,201 @@
+#include "MCTargetDesc/SPIRVBaseInfo.h"
+#include "MCTargetDesc/SPIRVMCTargetDesc.h"
+#include "SPIRVGlobalRegistry.h"
+#include "SPIRVRegisterInfo.h"
+#include "SPIRVTargetMachine.h"
+#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstr.h"
+#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/MachineOperand.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/PassRegistry.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/Path.h"
+
+#define DEBUG_TYPE "spirv-nonsemantic-debug-info"
+
+namespace llvm {
+struct SPIRVEmitNonSemanticDI : public MachineFunctionPass {
+  static char ID;
+  SPIRVTargetMachine *TM;
+  SPIRVEmitNonSemanticDI(SPIRVTargetMachine *TM);
+  SPIRVEmitNonSemanticDI();
+
+  bool runOnMachineFunction(MachineFunction &MF) override;
+
+private:
+  bool IsGlobalDIEmitted = false;
+  bool emitGlobalDI(MachineFunction &MF);
+};
+
+void initializeSPIRVEmitNonSemanticDIPass(PassRegistry &);
+
+FunctionPass *createSPIRVEmitNonSemanticDIPass(SPIRVTargetMachine *TM) {
+  return new SPIRVEmitNonSemanticDI(TM);
+}
+} // namespace llvm
+
+using namespace llvm;
+
+INITIALIZE_PASS(SPIRVEmitNonSemanticDI, DEBUG_TYPE,
+                "SPIRV NonSemantic.Shader.DebugInfo.100 emitter", false, false)
+
+char SPIRVEmitNonSemanticDI::ID = 0;
+
+SPIRVEmitNonSemanticDI::SPIRVEmitNonSemanticDI(SPIRVTargetMachine *TM)
+    : MachineFunctionPass(ID), TM(TM) {
+  initializeSPIRVEmitNonSemanticDIPass(*PassRegistry::getPassRegistry());
+}
+
+SPIRVEmitNonSemanticDI::SPIRVEmitNonSemanticDI() : MachineFunctionPass(ID) {
+  initializeSPIRVEmitNonSemanticDIPass(*PassRegistry::getPassRegistry());
+}
+
+bool SPIRVEmitNonSemanticDI::emitGlobalDI(MachineFunction &MF) {
+  // If this MachineFunction doesn't have any BB repeat procedure
+  // for the next
+  if (MF.begin() == MF.end()) {
+    IsGlobalDIEmitted = false;
+    return false;
+  }
+
+  // Required variables to get from metadata search
+  LLVMContext *Context;
+  std::string FilePath;
+  unsigned SourceLanguage = 0;
+  int64_t DwarfVersion = 0;
+  int64_t DebugInfoVersion = 0;
+
+  // Searching through the Module metadata to find nescessary
+  // information like DwarfVersion or SourceLanguage
+  {
+    const MachineModuleInfo &MMI = MF.getMMI();
+    const Module *M = MMI.getModule();
+    Context = &M->getContext();
+    const NamedMDNode *DbgCu = M->getNamedMetadata("llvm.dbg.cu");
+    if (!DbgCu)
+      return false;
+    for (const auto *Op : DbgCu->operands()) {
+      if (const auto *CompileUnit = dyn_cast<DICompileUnit>(Op)) {
+        DIFile *File = CompileUnit->getFile();
+        FilePath = ((File->getDirectory() + sys::path::get_separator() +
+                     File->getFilename()))
+                       .str();
+        SourceLanguage = CompileUnit->getSourceLanguage();
+        break;
+      }
+    }
+    const NamedMDNode *ModuleFlags = M->getNamedMetadata("llvm.module.flags");
+    for (const auto *Op : ModuleFlags->operands()) {
+      const MDOperand &MaybeStrOp = Op->getOperand(1);
+      if (MaybeStrOp.equalsStr("Dwarf Version"))
+        DwarfVersion =
+            cast<ConstantInt>(
+                cast<ConstantAsMetadata>(Op->getOperand(2))->getValue())
+                ->getSExtValue();
+      else if (MaybeStrOp.equalsStr("Debug Info Version"))
+        DebugInfoVersion =
+            cast<ConstantInt>(
+                cast<ConstantAsMetadata>(Op->getOperand(2))->getValue())
+                ->getSExtValue();
+    }
+  }
+  // NonSemantic.Shader.DebugInfo.100 global DI instruction emitting
+  {
+    // Required LLVM variables for emitting logic
+    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.begin());
+
+    // Emit OpString with FilePath which is required by DebugSource
+    const Register StrReg = MRI.createVirtualRegister(&SPIRV::IDRegClass);
+    MRI.setType(StrReg, LLT::scalar(32));
+    MachineInstrBuilder MIB = MIRBuilder.buildInstr(SPIRV::OpString);
+    MIB.addDef(StrReg);
+    addStringImm(FilePath, MIB);
+
+    const SPIRVType *VoidTyMI =
+        GR->getOrCreateSPIRVType(Type::getVoidTy(*Context), MIRBuilder);
+    GR->assignSPIRVTypeToVReg(VoidTyMI, GR->getSPIRVTypeID(VoidTyMI), MF);
+
+    // Emit DebugSource which is required by DebugCompilationUnit
+    const Register DebugSourceResIdReg =
+        MRI.createVirtualRegister(&SPIRV::IDRegClass);
+    MRI.setType(DebugSourceResIdReg, LLT::scalar(32));
+    MIB = MIRBuilder.buildInstr(SPIRV::OpExtInst)
+              .addDef(DebugSourceResIdReg)
+              .addUse(GR->getSPIRVTypeID(VoidTyMI))
+              .addImm(static_cast<int64_t>(
+                  SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100))
+              .addImm(SPIRV::NonSemanticExtInst::DebugSource)
+              .addUse(StrReg);
+    MIB.constrainAllUses(*TII, *TRI, *RBI);
+
+    const SPIRVType *I32Ty =
+        GR->getOrCreateSPIRVType(Type::getInt32Ty(*Context), MIRBuilder);
+    GR->assignSPIRVTypeToVReg(I32Ty, GR->getSPIRVTypeID(I32Ty), MF);
+
+    // Convert DwarfVersion, DebugInfo and SourceLanguage integers to OpConstant
+    // instructions required by DebugCompilationUnit
+    const Register DwarfVersionReg =
----------------
bwlodarcz wrote:

We have two scenarios to consider then:
a) When newly created function is inlined.
b) When it's not inlined.

Performance (less important):
If it's inlined (a case) the resulting assembly is exactly the same.
If it's not inlined then you need to have a `call` instruction and by typical calling convention you will need to load pointers to the correct registers. Without going into much detail this kind of exchanges needs read from the stack and write to it so additional cost. Yes, you are lowering pressure on icache by reducing the amount of code generated but in this concrete scenario (three same consecutive instruction sets next to each other which have a lot of calls) it doesn't really matter because all the calls will be in icache after first OpConstantInt emission exactly because they are next to each other and the same. Plus you of course paying the at least one cycle by additional `call` and it's ceremonies.

Readability:
As I said in some of the previous posts - the tastes about what's clean and what isn't are subjective. I prefer to minimize contexts (how much I need to remember between important sections) and jumps between code pieces (maximizing locality, so I e.g. not need to have two [or more] windows open to see the full picture). Basically simplicity and pragmatism over fanciness. Introduction of additional frame raises requirement to write declaration for the new procedure and it's arguments. To ensure locality (to see all relevant code in one window) it's needed to be lambda placed above the calls. In addition to that you will need to map caller and callee arguments in so there is additional reading overhead. When we consider just three consecutive calls I think that current state is simpler approach. 

Summary: 
There are moments and places when addition of the frame is definitely worth it but this case isn't, in my opinion, one of them. 

`inline` keyword in the function context is just an information for the compiler to lower the required score for inlining the procedure. It doesn't guarantee it and it's generally deprecated (at least in the cpp community which I know) to using the keyword with such intention (it has different more specific use cases). To enforce inline a compiler specific attribute `always_inline` can be used but it will abort compilation if the function can't be inlined (we don't want that here).

https://github.com/llvm/llvm-project/pull/97558


More information about the llvm-commits mailing list