Adding debug support to tinylang – Advanced IR Generation-3


  1. When the end of a procedure is reached, we must inform the builder to finish constructing the debug information for this procedure. We also need to remove the procedure from the scope stack:

void CGDebugInfo::emitProcedureEnd(
ProcedureDeclaration *Decl, llvm::Function *Fn) {
if (Fn && Fn->getSubprogram())
DBuilder.finalizeSubprogram(Fn->getSubprogram());
closeScope();
}

  1. Lastly, when we’ve finished adding the debug information, we need to implement the finalize() method on the builder. The generated debug information is then validated. This is an important step during development as it helps you find wrongly generated metadata:

void CGDebugInfo::finalize() { DBuilder.finalize(); }

Debug information should only be generated if the user requested it. This means that we will need a new command-line switch for this. We will add this to the file of the CGModule class, and we will also use it inside this class:

static llvm::cl::opt
Debug(“g”, llvm::cl::desc(“Generate debug information”),
llvm::cl::init(false));

The -g option can be used with the tinylang compiler to generate debug metadata.
Furthermore, the CGModule class holds an instance of the std::unique_ptr class. The pointer is initialized in the constructor for setting the command-line switch:

if (Debug)
DebugInfo.reset(new CGDebugInfo(*this));

In the getter method defined in CGModule.h, we simply return the pointer:

CGDebugInfo *getDbgInfo() {
return DebugInfo.get();
}

The common pattern to generate the debug metadata is to retrieve the pointer and check if it is valid. For example, after creating a global variable, we can add the debug information like so:

VariableDeclaration *Var = …;
llvm::GlobalVariable *V = …;
if (CGDebugInfo *Dbg = getDbgInfo())
Dbg->emitGlobalVariable(Var, V);

To add line number information, we need a conversion method called getDebugLoc() in the CGDebugInfo class, which turns the location information from the AST into the debug metadata:

llvm::DebugLoc CGDebugInfo::getDebugLoc(SMLoc Loc) {
std::pair LineAndCol =
CGM.getASTCtx().getSourceMgr().getLineAndColumn(Loc);
llvm::DILocation *DILoc = llvm::DILocation::get(
CGM.getLLVMCtx(), LineAndCol.first, LineAndCol.second,
getScope());
return llvm::DebugLoc(DILoc);
}

Additionally, a utility function in the CGModule class can be called to add the line number information to an instruction:

void CGModule::applyLocation(llvm::Instruction *Inst,
llvm::SMLoc Loc) {
if (CGDebugInfo *Dbg = getDbgInfo())
Inst->setDebugLoc(Dbg->getDebugLoc(Loc));
}

In this way, you can add the debug information for your compiler.
Summary
In this chapter, you learned how throwing and catching exceptions work in LLVM and the IR you can generate to exploit this feature. To enhance the scope of IR, you learned how you can attach various metadata to instructions. Metadata for type-based alias analysis provides additional information to the LLVM optimizer and helps with certain optimizations to produce better machine code. Users always appreciate the possibility of using a source-level debugger, and by adding debug information to the IR code, you can implement this important feature of a compiler.
Optimizing the IR code is the core task of LLVM. In the next chapter, we will learn how the pass manager works and how we can influence the optimization pipeline the pass manager governs.

Leave a Reply

Your email address will not be published. Required fields are marked *