Throwing and catching exceptions – Advanced IR Generation-4
Categories :
With this, the exception handling facility is complete.
To use exception handling in the compiler for your programming language, the simplest strategy is to piggyback on the existing C++ runtime functions. This also has the advantage that your exceptions are interoperable with C++. The disadvantage is that you tie some of the C++ runtime into the runtime of your language, most notably memory management. If you want to avoid this, then you need to create your own equivalents of the cxa functions. Still, you will want to use libunwind, which provides the stack unwinding mechanism:
- Let’s look at how to create this IR. We created the calc expression compiler in Chapter 2, The Structure of a Compiler. Now, we will extend the code generator of the expression compiler to raise and handle an exception in case a division by zero is performed. The generated IR will check if the divisor of a division is 0. If true, then an exception will be raised. We will also add a landing pad to the function, which catches the exception and prints Divide by zero! to the console and ends the calculation. Using exception handling is not necessary in this simple case, but it allows us to concentrate on the code generation process. We must add all the code to the CodeGen.cpp file. We begin by adding the required new fields and some helper methods. First of all, we need to store the LLVM declaration of the __cxa_allocate_exception() and __cxa_throw() functions, which consist of the function type and the function itself. A GlobalVariable instance is needed to hold the type information. We also need references to the basic blocks holding the landing pad and a basic block containing just an unreachable instruction: GlobalVariable *TypeInfo = nullptr;
FunctionType *AllocEHFty = nullptr;
Function *AllocEHFn = nullptr;
FunctionType *ThrowEHFty = nullptr;
Function *ThrowEHFn = nullptr;
BasicBlock *LPadBB = nullptr;
BasicBlock *UnreachableBB = nullptr; - We will also add a new helper function to create the IR for comparing two values. The createICmpEq() function takes the Left and Right values to compare as parameters. It creates a compare instruction testing for equality of the values, and a branch instruction to two basic blocks, for the equal and inequal cases. The two basic blocks are returned via references in the TrueDest and FalseDest parameters. Furthermore, a label for the new basic blocks can be given in the TrueLabel and FalseLabel parameters. The code is as follows: void createICmpEq(Value *Left, Value *Right,
BasicBlock *&TrueDest,
BasicBlock *&FalseDest,
const Twine &TrueLabel = “”,
const Twine &FalseLabel = “”) {
Function *Fn =
Builder.GetInsertBlock()->getParent();
TrueDest = BasicBlock::Create(M->getContext(),
TrueLabel, Fn);
FalseDest = BasicBlock::Create(M->getContext(),
FalseLabel, Fn);
Value *Cmp = Builder.CreateCmp(CmpInst::ICMP_EQ,
Left, Right);
Builder.CreateCondBr(Cmp, TrueDest, FalseDest);
} - To use the functions from the runtime, we need to create several function declarations. In LLVM, a function type gives the signature, and the function itself must be constructed. We use the createFunc() method to create both objects. The functions need references to the FunctionType and Function pointers, the name of the newly declared function, and the result type. The parameter type list is optional, and the flag to indicate a variable parameter list is set to false, indicating that there is no variable part in the parameter list: void createFunc(FunctionType *&Fty, Function *&Fn,
const Twine &N, Type *Result,
ArrayRef Params = None,
bool IsVarArgs = false) {
Fty = FunctionType::get(Result, Params, IsVarArgs);
Fn = Function::Create(
Fty, GlobalValue::ExternalLinkage, N, M);
}
With these preparations done, we can generate the IR to raise an exception.