OCaml LLVM bindings tutorial, part 4
- OCaml LLVM bindings tutorial, part 1
- OCaml LLVM bindings tutorial, part 2
- OCaml LLVM bindings tutorial, part 3
In the previous examples, we’ve seen how to build OCaml applications to read, manipulate and write LLVM bitcode.
To be able to generate realistic code, we now need to add a few more things. This part explains how to create bitcode with a correctly specified target triple, how to verify bitcode, and write a hello world application.
Target Triple and Data Layout
While LLVM IR is (or should be) target independent, there are a few things that are not. For example, the support for some instructions, the padding and alignment inside structures, the endianness, the size of pointers, etc. All these things are specified in two attributes of modules: the target triple, and the data layout.
In the current (3.5) version of LLVM, these two attributes are optional. However, they could become mandatory in the future, so it is best specifying them.
Note: in my personal opinion, specifying that inside the module is clearly
redundant with the
-march= option of
llc. Most of this could have been
handled by compiler flags, instead of creating situations where one can give a
target triple in the module, and use a different target on the command-line.
Let’s suppose that there are “historic” reasons.
The target triple is a string that describes the target host. It is usually a
simple string (
i686), or a minus-separated (
-) string to give the full
x86_64-apple-macosx10.7.0). It is the same as the argument of
-march=<target> option of clang or gcc, so this one should be easy to guess.
The data layout is a compact string, for example
e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128, that describes the
specifications of the data layout in memory. All fields are minus-separated
In the previous example string, this can be decoded as:
m:e: ELF mangling of names is enabled
p:32:32: size of a pointer is 32 bits, preferred alignment is 32 bits
f64:32:64: for floating point size 64 bits, abi is 32 bits and alignment is 64 bits
f80:32: for floating point size 80 bits, abi is 32 bits
n8:16:32: set of native integer widths of target CPU
S128: natural alignment of stack is 128 bits
Specifying the target triple and data layout can be tedious and error-prone. Instead of building the string manually, we’ll use the LLVM functions to find the target, the machine and the data layout from the target triple:
let lltarget = Llvm_target.Target.by_triple triple in let llmachine = Llvm_target.TargetMachine.create ~triple:triple lltarget in let lldly = Llvm_target.TargetMachine.data_layout llmachine in
triple is the name of the target architecture, for example
Then, we can set this information into the module:
set_target_triple (Llvm_target.TargetMachine.triple llmachine) llm ; set_data_layout (Llvm_target.DataLayout.as_string lldly) llm ;
If you want to print the values (for debugging purposes):
Printf.printf "lltarget: %s\n" (Llvm_target.Target.name lltarget); Printf.printf "llmachine: %s\n" (Llvm_target.TargetMachine.triple llmachine); Printf.printf "lldly: %s\n" (Llvm_target.DataLayout.as_string lldly) ;
We create a function to easily add the data layout and target triple, and will use that for every tutorial from now.
To verify a module, LLVM provides a very help function that will run many tests,
and print the validation report to stderr, and abort if the module is invalid.
To call it, just add the
llvm.analysis pkg to the
Makefile, and call:
Llvm_analysis.assert_valid_module llm ;
Trust me, you really want to use this. This will save you a lot of trouble. In fact, if you produce an invalid LLVM module, all tools will probably just segfault, including OCaml bindings functions. Unless you compiled LLVM in debug mode, the segfaults give no clue of what the problem is. So if you don’t want to become crazy, always verify the generated LLVM modules.
As usual in C, to call a function you first need to declare its prototype. In
the previous tutorial, we’ve seen how
to declare the prototype of a simple (fixed number of arguments) function, for
example, to declare the equivalent to the C function
let i32_t = i32_type llctx in let fty = function_type i32_t [| |] in let f = define_function "test" fty llm in
Our current example is to create the call
printf("Hello, world!\n"). However,
printf belongs to another kind of functions, accepting a variable number of arguments.
The first argument of
printf is a (constant) string. There is no such type in
LLVM, the equivalent being a pointer to an integer of size 8 (
We define the equivalent prototype of
int32_t printf(int8_t*, ...):
let i8_t = i8_type llctx in let i32_t = i32_type llctx in let printf_ty = var_arg_function_type i32_t [| pointer_type i8_t |] in let printf = declare_function "printf" printf_ty llm in
This gives a perfectly usable definition of
printf. While this works, we also
should add some function attributes. These attributes are important, because
they help the LLVM compiler for optimizations and verifications, and in some
cases they are even required to not generate wrong code.
Attributes are defined in the
Attribute module of
One attribute to add to the
printf function is
nounwind, meaning that it
will not raise any exception:
add_function_attr printf Attribute.Nounwind ;
The other kind of attributes that can be set is on parameters. Here, the
nocapture attribute is added on the first parameter, to declare that
does not make any copy of it, that survives the callee of
add_param_attr (param printf 0) Attribute.Nocapture ;
Remember that attributes are declarative, they are not checked. If you declare wrong attributes, the compiler can generate wrong code, that will probably be invalid or segfault at runtime.
Now that the prototype is correct, we only need to call
printf. The last thing
to do is to create the constant string.
In LLVM, a constant string is a global constant, defined as a NULL-terminated array of characters. It needs to be declared as a global value:
let s = build_global_stringptr "Hello, world!\n" "" llbuilder in
Remember that this only works for constant strings.
Last thing before using it as argument to
printf: the type of the constant is
not the same. The constant has type
[15 x i8], which means an array of 15
elements of integers of size 8, while the expected type is
It’s not the same (even if some C programmers thinks so), so it must be converted to get the address of the first element of the array. This is done using the getelementptr function (often called GEP):
let zero = const_int i32_t 0 in let s = build_in_bounds_gep s [| zero |] "" llbuilder in
Note that, this function is so confusing that it has its own FAQ in the documentation!
Finally, call the
printf function, and return:
let _ = build_call printf [| s |] "" llbuilder in let _ = build_ret (const_int i32_t 0) llbuilder in
The Previous tutorial already covered the compilation of the module, so I’ll just show the instructions:
$ LD_LIBRARY_PATH=/usr/lib/ocaml/llvm-3.5/ ./build/tutorial04/src/tutorial04.byte 2>hello.bc $ llc-3.5 hello.bc $ clang -o hello hello.s
and the execution:
$ ./hello Hello, world!
In fact, the conversion is optional (at least in OCaml bindings for LLVM 3.5).
If you call
printf without the GEP, dumping the module will show you that LLVM
has inserted an inline GEP:
%0 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @0, i32 0, i32 0))
This part may seem very long for a rather small result. Yet, we’ve seen some useful functions: how to set the target triple and data layout, and how to verify the module. These functions should always be used, to simplify creating valid code, and not spend hours debugging LLVM for segfaults.
In part 5 we’ll see how to use the execution engine to create dynamically generated code, using the JIT (Just-In Time) engine.