Integrating the exception handling code into the application – Advanced IR Generation
The IR for the division is generated inside the visit(BinaryOp &) method. Instead of just generating a sdiv instruction, we must generate an IR to compare the divisor with 0. If the divisor is 0, then the control flow continues in a basic block, raising the exception. Otherwise, the control flow continues in a basic block with the sdiv instruction. With the help of the createICmpEq() and addThrow() functions, we can code this very easily:
case BinaryOp::Div:
BasicBlock *TrueDest, *FalseDest;
createICmpEq(Right, Int32Zero, TrueDest,
FalseDest, “divbyzero”, “notzero”);
Builder.SetInsertPoint(TrueDest);
addThrow(42); // Arbitrary payload value.
Builder.SetInsertPoint(FalseDest);
V = Builder.CreateSDiv(Left, Right);
break;
The code generation part is now complete. To build the application, we must change into the build directory and run the ninja tool:
$ ninja
Once the build has finished, you can check the generated IR with the with a: 3/a expression:
$ src/calc “with a: 3/a”
You will see the additional IR needed to raise and catch the exception.
The generated IR now depends on the C++ runtime. The easiest way to link against the required libraries is to use the clang++ compiler. Rename the rtcalc.c file with the runtime functions for the expression calculator to rtcalc.cpp, and add extern “C” in front of each function inside the file. Then, use the llc tool to turn the generated IR into an object file, and the clang++ compiler to create an executable:
$ src/calc “with a: 3/a” | llc -filetype obj -o exp.o
$ clang++ -o exp exp.o ../rtcalc.cpp
Now, we can run the generated application with different values:
$ ./exp
Enter a value for a: 1
The result is: 3
$ ./exp
Enter a value for a: 0
Divide by zero!
In the second run, the input is 0, and this raises the exception. It works as expected!
In this section, we learned how to raise and catch exceptions. The code to generate the IR can be used as a blueprint for other compilers. Of course, the type information that’s used and the number of catch clauses depends on the input to the compiler, but the IR we need to generate still follows the pattern presented in this section.
Adding metadata is another way to provide further information to LLVM. In the next section, we’ll add type metadata to support the LLVM optimizer in certain situations.
Generating metadata for type-based alias analysis
Two pointers may point to the same memory cell, at which point they alias each other. Memory is not typed in the LLVM model, which makes it difficult for the optimizer to decide if two pointers alias each other or not. If the compiler can prove that two pointers do not alias each other, then more optimizations are possible. In the next section, we will have a closer look at the problem and investigate how adding additional metadata will help before we implement this approach.