Raising an exception – Advanced IR Generation
Categories :
To generate the IR code to raise an exception, we will add the addThrow() method. This new method needs to initialize the new fields and then generate the IR to raise an exception via the __cxa_throw() function. The payload of the raised exception is of the int type and can be set to an arbitrary value. Here is what we need to code:
- The new addThrow() method begins by checking if the TypeInfo field has been initialized. If it has not been initialized, then a global external constant of an i8 pointer type called _ZTIi is created. This represents the C++ metadata describing the C++ int type: void addThrow(int PayloadVal) {
if (!TypeInfo) {
TypeInfo = new GlobalVariable(
M, Int8PtrTy, /isConstant=/true, GlobalValue::ExternalLinkage, /Initializer=*/nullptr, “_ZTIi”); - The initialization continues with creating the IR declaration for the __cxa_allocate_exception() and __cxa_throw() functions using our helper createFunc() method: createFunc(AllocEHFty, AllocEHFn,
“__cxa_allocate_exception”, Int8PtrTy,
{Int64Ty});
createFunc(ThrowEHFty, ThrowEHFn, “__cxa_throw”,
VoidTy,
{Int8PtrTy, Int8PtrTy, Int8PtrTy}); - A function that uses exception handling needs a personality function, which helps with stack unwinding. We add the IR code to declare the __gxx_personality_v0() personality function from the C++ library and set it as the personality routine of the current function. The current function is not stored as a field, but we can use the Builder instance to query the current basic block, which has the function stored as a Parent field: FunctionType *PersFty;
Function *PersFn;
createFunc(PersFty, PersFn,
“__gxx_personality_v0”, Int32Ty, std::nulopt, true);
Function *Fn =
Builder.GetInsertBlock()->getParent();
Fn->setPersonalityFn(PersFn); - Next, we must create and populate the basic block for the landing pad. First, we need to save the pointer to the current basic block. Then, we must create a new basic block, set it in the builder so that it can be used as the basic block to insert instructions, and call the addLandingPad() method. This method generates the IR code for handling an exception and is described in the next section, Catching an exception. This code populates the basic block for the landing pad: BasicBlock *SaveBB = Builder.GetInsertBlock();
LPadBB = BasicBlock::Create(M->getContext(),
“lpad”, Fn);
Builder.SetInsertPoint(LPadBB);
addLandingPad(); - The initialization part is completed by creating the basic block holding an unreachable instruction. Again, we create the basic block and set it as an insertion point at the builder. Then, we can add the unreachable instruction to it. Lastly, we can set the insertion point of the builder back to the saved SaveBB instance so that the following IR is added to the right basic block: UnreachableBB = BasicBlock::Create(
M->getContext(), “unreachable”, Fn);
Builder.SetInsertPoint(UnreachableBB);
Builder.CreateUnreachable();
Builder.SetInsertPoint(SaveBB);
} - To raise an exception, we need to allocate memory for the exception and the payload via a call to the __cxa_allocate_exception() function. Our payload is of the C++ int type, which usually has a size of 4 bytes. We create a constant unsigned value for the size and call the function with it as a parameter. The function type and the function declaration are already initialized, so we only need to create the call instruction: Constant *PayloadSz =
ConstantInt::get(Int64Ty, 4, false);
CallInst *EH = Builder.CreateCall(
AllocEHFty, AllocEHFn, {PayloadSz}); - Next, we store the PayloadVal value in the allocated memory. To do so, we need to create an LLVM IR constant with a call to the ConstantInt::get() function. The pointer to the allocated memory is of an i8 pointer type; to store a value of the i32 type, we need to create a bitcast instruction to cast the type: Value *PayloadPtr =
Builder.CreateBitCast(EH, Int32PtrTy);
Builder.CreateStore(
ConstantInt::get(Int32Ty, PayloadVal, true),
PayloadPtr); - Finally, we must raise the exception with a call to the __cxa_throw() function. As this function raises an exception, which is also handled in the same function, we need to use the invoke instruction instead of the call instruction. Unlike the call instruction, the invoke instruction ends a basic block because it has two successor basic blocks. Here, these are the UnreachableBB and LPadBB basic blocks. If the function raises no exception, the control flow is transferred to the UnreachableBB basic blocks. Due to the design of the __cxa_throw() function, this will never happen because the control flow is transferred to the LPadBB basic block to handle the exception. This finishes the implementation of the addThrow() method: Builder.CreateInvoke(
ThrowEHFty, ThrowEHFn, UnreachableBB, LPadBB,
{EH,
ConstantExpr::getBitCast(TypeInfo, Int8PtrTy),
ConstantPointerNull::get(Int8PtrTy)});
}
Next, we’ll add the code to generate the IR to handle the exception.