In this recipe, we will look into the exception handling infrastructure of LLVM. We will discuss how the exception handling information looks in the IR and the intrinsic functions provided by LLVM for exception handling.
You must understand how exception handling works normally and the concepts of try
, catch
and throw
and so on. You must also have Clang and LLVM installed in your path.
We will take an example to describe how exception handling works in LLVM:
$ cat eh.cpp class Ex1 {}; void throw_exception(int a, int b) { Ex1 ex1; if (a > b) { throw ex1; } } int test_try_catch() { try { throw_exception(2, 1); } catch(...) { return 1; } return 0; }
$ clang -c eh.cpp -emit-llvm -o eh.bc
$ llvm-dis eh.bc -o - ; ModuleID = 'eh.bc' target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" %class.Ex1 = type { i8 } @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* @_ZTS3Ex1 = linkonce_odr constant [5 x i8] c"3Ex1 0" @_ZTI3Ex1 = linkonce_odr constant { i8*, i8* } { i8* bitcast (i8** getelementptr inbounds (i8** @_ZTVN10__cxxabiv117__class_type_infoE, i64 2) to i8*), i8* getelementptr inbounds ([5 x i8]* @_ZTS3Ex1, i32 0, i32 0) } ; Function Attrs: uwtable define void @_Z15throw_exceptionii(i32 %a, i32 %b) #0 { %1 = alloca i32, align 4 %2 = alloca i32, align 4 %ex1 = alloca %class.Ex1, align 1 store i32 %a, i32* %1, align 4 store i32 %b, i32* %2, align 4 %3 = load i32* %1, align 4 %4 = load i32* %2, align 4 %5 = icmp sgt i32 %3, %4 br i1 %5, label %6, label %9 ; <label>:6 ; preds = %0 %7 = call i8* @__cxa_allocate_exception(i64 1) #1 %8 = bitcast i8* %7 to %class.Ex1* call void @__cxa_throw(i8* %7, i8* bitcast ({ i8*, i8* }* @_ZTI3Ex1 to i8*), i8* null) #2 unreachable ; <label>:9 ; preds = %0 ret void } declare i8* @__cxa_allocate_exception(i64) declare void @__cxa_throw(i8*, i8*, i8*) ; Function Attrs: uwtable define i32 @_Z14test_try_catchv() #0 { %1 = alloca i32, align 4 %2 = alloca i8* %3 = alloca i32 %4 = alloca i32 invoke void @_Z15throw_exceptionii(i32 2, i32 1) to label %5 unwind label %6 ; <label>:5 ; preds = %0 br label %13 ; <label>:6 ; preds = %0 %7 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) catch i8* null %8 = extractvalue { i8*, i32 } %7, 0 store i8* %8, i8** %2 %9 = extractvalue { i8*, i32 } %7, 1 store i32 %9, i32* %3 br label %10 ; <label>:10 ; preds = %6 %11 = load i8** %2 %12 = call i8* @__cxa_begin_catch(i8* %11) #1 store i32 1, i32* %1 store i32 1, i32* %4 call void @__cxa_end_catch() br label %14 ; <label>:13 ; preds = %5 store i32 0, i32* %1 br label %14 ; <label>:14 ; preds = %13, %10 %15 = load i32* %1 ret i32 %15 } declare i32 @__gxx_personality_v0(...) declare i8* @__cxa_begin_catch(i8*) declare void @__cxa_end_catch() attributes #0 = { uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } attributes #1 = { nounwind } attributes #2 = { noreturn } !llvm.ident = !{!0} !0 = metadata !{metadata !"clang version 3.6.0 (220636)"}
In LLVM, if an exception is thrown, the runtime tries its best to find a handler. It tries to find an exception frame corresponding to the function where the exception was thrown. This exception frame contains a reference to the exception table, which contains the implementation—how to handle the exception when a programming language supports exception handling. When the language does not support exception handling, the information on how to unwind the current activation and restore the state of the prior activation is found in this exception frame.
Let's look at the preceding example to see how to generate exception handling code with LLVM.
The try
block is translated to invoke instruction in LLVM:
invoke void @_Z15throw_exceptionii(i32 2, i32 1) to label %5 unwind label %6
The preceding line tells the compiler how it should handle an exception if the throw_exception
function throws it. If no exception is thrown, then normal execution will take place through the %5
label. But if an exception is thrown, it will branch into the %6
label, which is the landing pad. This corresponds roughly to the catch
portion of a try
/catch
sequence. When execution resumes at a landing pad, it receives an exception structure and a selector value corresponding to the type of exception thrown. The selector is then used to determine which catch
function should actually process the exception. In this case, it looks something like this:
%7 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) catch i8* null
The %7
in the preceding code snippet represents the information describing the exception. The { i8*, i32 }
part of the code describes the type of information. The i8*
part of the code represents the exception pointer part, and i32
is the selector value. In this case, we have only one selector value, as the catch
function accepts all types of exception objects thrown. The @__gxx_personality_v0
function is the personality
function. It receives the context of the exception, an exception structure containing the exception object type and value, and a reference to the exception table for the current function. The personality function for the current compile unit is specified in a common exception frame. In our case, the @__gxx_personality_v0
function represents the fact that we are dealing with C++ exceptions.
So, the %8 = extractvalue { i8*, i32 } %7, 0
will represent the exception object, and %9 = extractvalue { i8*, i32 } %7, 1
represents the selector value.
The following are some noteworthy IR functions:
__cxa_throw
: This is a function used to throw an exception__cxa_begin_catch
: This takes an exception structure reference as an argument and returns the value of the exception object__cxa_end_catch
: This locates the most recently caught exception and decrements its handler count, removing the exception from the caught state if this counter goes down to zero3.149.236.27