Exception handling in LLVM

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.

Getting ready...

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.

How to do it…

We will take an example to describe how exception handling works in LLVM:

  1. Open a file to write down the source code, and enter the source code to test exception handling:
    $ 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;
    }
    
  2. Generate the bitcode file using the following command:
    $ clang -c eh.cpp -emit-llvm -o eh.bc
    
  3. To view the IR on the screen, run the following command, which will give you the output as shown:
    $ 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"3Ex10"
    @_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)"}
    

How it works…

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 zero

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.86.211