Simple “if-then-else” Branching

First let’s take a look at a very simple function, that computes the maximum of two integers. This is implemented using a single if control statement.

int max(int a, int b) {
  if (a > b) {
    return a;
  } else {
    return b;
  }
}

Remember that in LLVM IR control-flow is implemented by jumping between basic blocks, which contain instruction sequences that do not change control flow. Each basic block ends with an instruction that changes the control flow. The most common branching instruction is br [2]. br can be used conditionally, then it implements a simple if-then-else, taking a boolean condition flag and two basic block labels as parameter.

br i1 %cond, label %iftrue, label %iffalse

br can also be used to unconditinally jump to a certain destination:

br label %dest
define i32 @max(i32 %a, i32 %b) {
entry:
  %retval = alloca i32, align 4
  %0 = icmp sgt i32 %a, %b
  br i1 %0, label %btrue, label %bfalse

btrue:                                      ; preds = %2
  store i32 %a, i32* %retval, align 4
  br label %end

bfalse:                                     ; preds = %2
  store i32 %b, i32* %retval, align 4
  br label %end

end:                                     ; preds = %btrue, %bfalse
  %1 = load i32, i32* %retval, align 4
  ret i32 %1
}

In the example above, there are 4 basic blocks. The first one is the function entry block. There space is allocated on the stack with alloca [1], which acts as a temporary storage for the bigger value. Then the two paramter %a and %b are compared using the icmp instruction [3]. The result is a boolean (i1) flag, which is then used as condition for the br instruction. Then depending on the taken branch, either %a or %b is stored into the temporary %retval variable. Each of the branches then end with a unconditional branch to the last basic block %end. There the value from %retval is loaded and returned.

You can get a graphical representation of the control-flow in the form of a control-flow graph (CFG). This can be generated by using opt -dot-cfg input.ll.

Control-Flow Graph of the max function

Control-Flow Graph of the max function

LLVM IR is a rather rich intermediate code format. So when compiling the above snippet with higher optimization levels, LLVM will optimize the code to use the select instruction [4] instead of generating branches. The select instruction simply chooses between two values, based on a boolean condition. This shortens the code significantly.

define i32 @max(i32 %a, i32 %b) {
  %1 = icmp sgt i32 %a, %b
  %2 = select i1 %1, i32 %a, i32 %b
  ret i32 %2
}