For no reason in particular I decided to dig into LLVM today. I decided to try out its assembly language by writing a line count program in it (I chose this problem since I had previously written an optimized x86 version using SSE). This was of course significantly trickier than I initially expected and took me several hours to figure out.
SSA took some getting used to. I understood the basic idea before but had never worked with it. The surprising thing is that you don’t get registers, which means that to do something as simple as the “i++” of a for loop you have to:
%current_i_val = load i32* %i %new_i_val = add i32 %current_i_val, 1 store i32 %new_i_val, i32* %i
In this example, “%i” is the address of a stack variable where “i” lives. I have to load the current value, increment it, and store it again.
This load/modify/store business made me uncomfortable because it felt really inefficient. You just have to trust that when LLVM does register allocation that it’s smart enough to know that it doesn’t actually have to do the load/store each time.
I was also surprised that LLVM assembly language is more strict that real assembly languages about the structure of each function. Each function has a series of “code blocks” and each code block must begin with a label and end with a “termination instruction.” As an example, here’s a function definition and the first two blocks in that function:
; Returns the number of newlines found in a buffer.
define i32 @count_newlines(i8* %buffer, i32 %bufsize) {
; Local variables.
%i = alloca i32
%num_lines = alloca i32
store i32 0, i32* %i ; Initialize to zero.
store i32 0, i32* %num_lines ; Initialize to zero.
br label %loop ; can't just fall through to %loop
loop:
%i_val = load i32* %i
%is_done = icmp uge i32 %i_val, %bufsize
br i1 %is_done, label %done, label %continue_loop
So you can’t just put labels anywhere willy-nilly, you only get one label at the beginning of the block. The termination instructions are mostly branches of various sorts. Basically this means that you can’t just “fall through” to the next label without branching — if you want to proceed to the next block you have to explicitly end your current block by branching to the next one. So in this sense the order of the blocks as you write them in the file is insignificant — the only significance is the first one, which is automatically run when you enter the function.
Once you write your LLVM assembly language, you can assemble it with llvm-as, and then either run it with “lli” (which will JIT+run it) or compile it to x86 assembly with “llc” and then assemble it with gcc.
$ llvm-as wc.ll $ lli wc.bc 638818 $ llc wc.bc $ gcc -o wc wc.s $ ./wc 638818