Adding debug metadata – Advanced IR Generation-3
To create the debug metadata for the function, we have to create a type for the signature first, and then the metadata for the function itself. This is similar to the creation of IR for a function. The signature of the function is an array with all the types of parameters in source order and the return type of the function as the first element at index 0. Usually, this array is constructed dynamically. In our case, we can also construct the metadata statically. This is useful for internal functions, such as for module initializing. Typically, the parameters of these functions are always known, and the compiler writer can hard-code them:
llvm::Metadata *DbgSigTy = {DbgIntTy};
llvm::DITypeRefArray DbgParamsTy =
DBuilder.getOrCreateTypeArray(DbgSigTy);
llvm::DISubroutineType *DbgFuncTy =
DBuilder.createSubroutineType(DbgParamsTy);
Our function has the INTEGER return type and no further parameters, so the DbgSigTy array only contains the pointer to the metadata for this type. This static array is turned into a type array, which is then used to create the type for the function.
The function itself requires more data:
unsigned LineNo = 5;
unsigned ScopeLine = 5;
llvm::DISubprogram *DbgFunc = DBuilder.createFunction(
DbgCU, “Func”, “_t4File4Func”, DbgFile, LineNo,
DbgFuncTy, ScopeLine, llvm::DISubprogram::FlagPrivate,
llvm::DISubprogram::SPFlagLocalToUnit);
A function belongs to a compilation unit, which in our case is stored in the DbgCU variable. We need to specify the name of the function in the source file, which is Func, and the mangled name is stored in the object file. This information helps the debugger locate the machine code of the function. The mangled name, based on the rules of tinylang, is _t4File4Func. We also have to specify the file that contains the function.
This may sound surprising at first, but think of the include mechanism in C and C++: a function can be stored in a different file, which is then included with include in the main compilation unit. Here, this is not the case and we use the same file as the one the compilation unit uses. Next, the line number of the function and the function type are passed. The line number of the function may not be the line number where the lexical scope of the function begins. In this case, you can specify a different ScopeLine. A function also has protection, which we specify here with the FlagPrivate value to indicate a private function. Other possible values for function protection are FlagPublic and FlagProtected, for public and protected functions, respectively.
Besides the protection level, other flags can be specified here. For example, FlagVirtual indicates a virtual function and FlagNoReturn indicates that the function does not return to the caller. You can find the complete list of possible values in the LLVM include file – that is, llvm/include/llvm/IR/DebugInfoFlags.def.
Lastly, flags specific to a function can be specified. The most commonly used flag is the SPFlagLocalToUnit value, which indicates that the function is local to this compilation unit. The MainSubprogram value is also used often, indicating that this function is the main function of the application. The LLVM include file mentioned previously also lists all possible values related to flags specific to functions.
So far, we have only created the metadata referring to static data. Variables are dynamic, so we’ll explore how to attach the static metadata to the IR code for accessing variables in the next section.