Catching an exception – Advanced IR Generation
To generate the IR code to catch an exception, we must add the addLandingPad() method. The generated IR extracts the type information from the exception. If it matches the C++ int type, then the exception is handled by printing Divide by zero! to the console and returning from the function. If the type does not match, we simply execute the resume instruction, which transfers control back to the runtime. As there are no other functions in the call hierarchy to handle this exception, the runtime will terminate the application. The following steps describe the code that is needed to generate the IR for catching an exception:
- In the generated IR, we need to call the __cxa_begin_catch() and __cxa_end_catch() functions from the C++ runtime library. To print the error message, we will generate a call to the puts() function from the C runtime library. Furthermore, to get the type information from the exception, we must generate a call to the llvm.eh.typeid.for intrinsic. We also need the FunctionType and Function instances for all of them; we will take advantage of our createFunc() method to create them:
void addLandingPad() {
FunctionType *TypeIdFty; Function *TypeIdFn;
createFunc(TypeIdFty, TypeIdFn,
“llvm.eh.typeid.for”, Int32Ty,
{Int8PtrTy});
FunctionType *BeginCatchFty; Function *BeginCatchFn;
createFunc(BeginCatchFty, BeginCatchFn,
“__cxa_begin_catch”, Int8PtrTy,
{Int8PtrTy});
FunctionType *EndCatchFty; Function *EndCatchFn;
createFunc(EndCatchFty, EndCatchFn,
“__cxa_end_catch”, VoidTy);
FunctionType *PutsFty; Function *PutsFn;
createFunc(PutsFty, PutsFn, “puts”, Int32Ty,
{Int8PtrTy});
- The landingpad instruction is the first instruction we generate. The result type is a structure containing fields of an i8 pointer and an i32 type. This structure is generated with a call to the StructType::get() function. Moreover, since we need to handle an exception of a C++ int type, we need to also add this as a clause to the landingpad instruction, which must be a constant of an i8 pointer type. This means that generating a bitcast instruction is required to convert the TypeInfo value into this type. After, we must store the value that’s returned from the instruction for later use in the Exc variable: LandingPadInst *Exc = Builder.CreateLandingPad(
StructType::get(Int8PtrTy, Int32Ty), 1, “exc”);
Exc->addClause(
ConstantExpr::getBitCast(TypeInfo, Int8PtrTy));
- Next, we extract the type selector from the returned value. With a call to the llvm.eh.typeid.for intrinsic, we retrieve the type ID for the TypeInfo field, representing the C++ int type. With this IR, we have generated the two values we need to compare to decide if we can handle the exception: Value *Sel =
Builder.CreateExtractValue(Exc, {1}, “exc.sel”);
CallInst *Id =
Builder.CreateCall(TypeIdFty, TypeIdFn,
{ConstantExpr::getBitCast(
TypeInfo, Int8PtrTy)});
- To generate the IR for the comparison, we must call our createICmpEq() function. This function also generates two basic blocks, which we store in the TrueDest and FalseDest variables: BasicBlock *TrueDest, *FalseDest;
createICmpEq(Sel, Id, TrueDest, FalseDest, “match”,
“resume”);
- If the two values do not match, the control flow continues at the FalseDest basic block. This basic block only contains a resume instruction, to give control back to the C++ runtime: Builder.SetInsertPoint(FalseDest);
Builder.CreateResume(Exc);
- If the two values are equal, the control flow continues at the TrueDest basic block. First, we generate the IR code to extract the pointer to the exception from the return value of the landingpad instruction, stored in the Exc variable. Then, we generate a call to the __cxa_begin_catch () function, passing the pointer to the exception as a parameter. This indicates the beginning of handling the exception for the runtime: Builder.SetInsertPoint(TrueDest);
Value *Ptr =
Builder.CreateExtractValue(Exc, {0}, “exc.ptr”);
Builder.CreateCall(BeginCatchFty, BeginCatchFn,
{Ptr});
- The exception is then handled by calling the puts() function to print a message to the console. For this, we generate a pointer to the string with a call to the CreateGlobalStringPtr() function, and then pass this pointer as a parameter in the generated call to the puts() function: Value *MsgPtr = Builder.CreateGlobalStringPtr(
“Divide by zero!”, “msg”, 0, M);
Builder.CreateCall(PutsFty, PutsFn, {MsgPtr});
- Now that we’ve handled the exception, we must generate a call to the __cxa_end_catch() function to inform the runtime about it. Finally, we return from the function with a ret instruction: Builder.CreateCall(EndCatchFty, EndCatchFn);
Builder.CreateRet(Int32Zero);
}
With the addThrow() and addLandingPad() functions, we can generate the IR to raise an exception and handle an exception. However, we still need to add the IR to check if the divisor is 0. We’ll cover this in the next section.