-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial part 3: Loops and variables
Consider this C function:
int loop_test (int n)
{
int sum = 0;
for (int i = 0; i < n; i++)
sum += i * i;
return sum;
}
This examples demonstrates some more features of libgccjit
, with local
variables and a loop.
To break this down into libgccjit
terms, it's usually easire to reword the
for
loop as a while
loop, giving:
int loop_test (int n)
{
int sum = 0;
int i = 0;
while (i < n)
{
sum += i * i;
i++;
}
return sum;
}
Here's what the final control flow graph will look like:
As before, we open the Gccjit
module and make a Gccjit.context
.
open Gccjit
let test () =
let ctx = Context.create () in
The function works with the C int
type:
let the_type = Type.(get ctx Int) in
let return_type = the_type in
Let's build the function:
let param_n = Param.create ctx the_type "n" in
let func = Function.create ctx Function.Exported return_type "loop_test" [ param_n ] in
The type of expressions is Gccjit.rvalue
, representing an expression that can
be on the right-hand side of an assignment: a value that can be computed
somehow, and assigned to a storage area (such as a variable). It has a
specific Gccjit.type_
.
Another important type is Gccjit.lvalue
. A Gccjit.lvalue
is something that
can be the left-hand side of an assignment: a storage area (such as a
variable).
In other words, every assignment can be thought of as:
LVALUE = RVALUE
Note that Gccjit.lvalue
is a subtype of Gccjit.rvalue
, where in an
assignment of the form
LVALUE_A = LVALUE_B
the LVALUE_B
implies reading the current value of that storage area, assigning
it into the LVALUE_A
.
So far the only expressions we've seen are i * i
:
let expr = RValue.binary_op ctx Mult int_type (RValue.param param_i) (RValue.param param_i) in
which is a Gccjit.rvalue
, and the various function parameters: param_i
and
param_n
, of type Gccjit.param
, which is a subtype of Gccjit.lvalue
(and,
in turn of Gccjit.rvalue
): we can both read from and write to function
parameters within the body of a function.
Our new example has a couple of local variables. We create them by calling
Gccjit.Function.local
, supplying a type and a name:
(* Build locals *)
let local_i = Function.local func the_type "i" in
let sum = Function.local func the_type "sum" in
These are instances of Gccjit.lvalue
-- they can be read from and written to.
Note that there is no precanned way to create and initialize a variable like in C:
int i = 0;
Instead, having added the local to the function, we have to separately add an
assignment of 0
to local_i
at the beginning of the function.
This function has a loop, so we need to build some basic blocks to handle the control flow. In this case, we need 4 blocks:
- before the loop (initializing the locals)
- the conditional at the top of the loop (comparing i < n)
- the body of the loop
- after the loop terminates (
return sum
)
so we create these as Gccjit.block
instances within the Gccjit.function_
:
let b_initial = Block.create ~name:"initial" fn in
let b_loop_cond = Block.create ~name:"loop_cond" fn in
let b_loop_body = Block.create ~name:"loop_body" fn in
let b_after_loop = Block.create ~nmae:"after_loop" fn in
We now populate each block with statements.
The entry block b_initial
consists of initializations followed by a jump to
the conditional. We assign 0
to i
and sum
, using Gccjit.add_assignment
to add an assignment statement, and using Gccjit.zero
to get the constant
value 0
for the relevant type for the right-hand side of the assignment:
(* sum = 0; *)
Block.assign b_initial sum (RValue.zero ctx the_type);
Block.assign b_initial i (RValue.zero ctx the_type);
We can the terminate the entry block by jumping to the conditional:
Block.jump b_initial b_loop_cond;
The conditional block is equivalent to the line while (i < n)
from our C
example. It contains a single statement: a conditional, which jumps to one of
two destination blocks depending on a boolean Gccjit.rvalue
, in this case the
comparison of i
and n
. We build the comparison using
Gccjit.new_comparison
:
(* (i >= n) *)
let guard = RValue.comparison ctx Ge (RValue.lvalue local_i) (RValue.param param_n) in
and can then use this to add b_loop_cond
's sole statement, via Gccjit.end_with_conditional
:
(* Equivalent to:
if (guard)
goto after_loop;
else
goto loop_body; *)
Block.cond_jump b_loop_cond guard
b_after_loop (* on true *)
b_loop_body; (* on false *)
Next, we populate the body of the loop.
The C statement sum += i * i;
is an assignment operation, where an lvalue is
modified "in-place". We use Gccjit.add_assignment_op
to handle these
operations:
Block.assign_op b_loop_body sum Plus
(RValue.binary_op ctx Mult the_type (RValue.lvalue local_i) (RValue.lvalue local_i))
The i++
can be thought of as i += 1
, and can thus be handled in a similar
way. We use Gccjit.one
to get the constant value 1
(fro the relevant type)
for the right-hand side of the assignment.
(* i++ *)
Block.assign_op b_loop_body local_i Plus (RValue.one ctx the_type)
NOTE: For numeric constants other than 0
and 1
, we could use
Gccjit.RValue.int
and Gccjit.RValue.double
.
The loop body completes by jumping back to the conditional:
Block.jump b_loop_body b_loop_cond;
Finally, we populate the b_after_loop
block, reached when the loop conditional
is false. We want to generate the equivalent of:
return sum;
so the block is just one statement:
Block.return b_after_loop local_sum;
NOTE: You can intermingle block creation with statement creation, but given that the terminator statements generally include references to other blocks, I find it's clearer to create all the blocks, then all the statements.
We've finished populating the function. As before, we can compile it to machine code:
let result = Context.compile ctx in
let loop_test = Result.code result Ctypes.(int @-> returning int) in
Printf.printf "result: %d\n%!" (loop_test 10)
result: 285
You can see the control flow graph of a function using Gccjit.dump_to_dot
:
Function.dump_dot func "/tmp/sum-of-squares.dot"
giving a .dot
file in GraphViz format.
You can convert this to an image using dot
:
$ dot -Tpng /tmp/sum-of-squares.dot -o /tmp/sum-of-squares.png
or use a viewer (on OS X you can install one with brew install --with-app graphviz
).
(* Usage example for libgccjit *)
open Gccjit
let create_code ctx =
(*
Simple sum-of-squares, to test conditionals and looping
int loop_test (int n)
{
int i;
int sum = 0;
for (i = 0; i < n ; i ++)
{
sum += i * i;
}
return sum;
*)
let n = Param.create ctx Type.(get ctx Int) "n" in
let func = Function.create ctx Function.Exported Type.(get ctx Int) "loop_test" [ n ] in
(* Build locals: *)
let i = Function.local func Type.(get ctx Int) "i" in
let sum = Function.local func Type.(get ctx Int) "sum" in
let b_initial = Block.create ~name:"initial" func in
let b_loop_cond = Block.create ~name:"loop_cond" func in
let b_loop_body = Block.create ~name:"loop_body" func in
let b_after_loop = Block.create ~name:"after_loop" func in
(* sum = 0; *)
Block.assign b_initial sum (RValue.zero ctx Type.(get ctx Int));
(* i = 0; *)
Block.assign b_initial i (RValue.zero ctx Type.(get ctx Int));
Block.jump b_initial b_loop_cond;
(* if (i >= n) *)
Block.cond_jump b_loop_cond (RValue.comparison ctx Ge (RValue.lvalue i) (RValue.param n))
b_after_loop b_loop_body;
(* sum += i * i *)
Block.assign_op b_loop_body sum Plus
(RValue.binary_op ctx Mult Type.(get ctx Int) (RValue.lvalue i) (RValue.lvalue i));
(* i++ *)
Block.assign_op b_loop_body i Plus (RValue.one ctx Type.(get ctx Int));
Block.jump b_loop_body b_loop_cond;
(* return sum *)
Block.return b_after_loop (RValue.lvalue sum)
let () =
let ctx = Context.create () in
(* Set some options on the context. Let's see the code being generated, in
assembler form. *)
Context.set_option ctx Context.Dump_generated_code true;
(* Populate the context. *)
create_code ctx;
(* Compile the code. *)
let result = Context.compile ctx in
(* Extract the generated code from "result". *)
let loop_test = Result.code result "loop_test" Ctypes.(int @-> returning int) in
(* Run the generated code. *)
let v = loop_test 10 in
Printf.printf "loop_test returned: %d\n%!" v;
Context.release ctx;
Result.release result
Building and running it:
$ ocamlbuild -package gccjit tut03_sum_of_squares.byte
# Run the built program:
$ ./tut03_sum_of_squares.byte
loop_test returned: 285