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 example demonstrates some more features of libgccjit, with local variables and a loop.
To break this down into libgccjit terms, it’s usually easier 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 include the libgccjit++ header and make a
gccjit::context
.
#include <libgccjit++.h>
void test (void)
{
gccjit::context ctxt;
ctxt = gccjit::context::acquire ();
The function works with the C int type.
In the previous tutorial we acquired this via
gccjit::type the_type = ctxt.get_type (ctxt, GCC_JIT_TYPE_INT);
though we could equally well make it work on, say, double:
gccjit::type the_type = ctxt.get_type (ctxt, GCC_JIT_TYPE_DOUBLE);
For integer types we can use gccjit::context::get_int_type
to directly bind a specific type:
gccjit::type the_type = ctxt.get_int_type <int> ();
Let’s build the function:
gcc_jit_param n = ctxt.new_param (the_type, "n");
std::vector<gccjit::param> params;
params.push_back (n);
gccjit::function func =
ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
return_type,
"loop_test",
params, 0);
Expressions: lvalues and rvalues#
The base class of expression is the 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 class is gccjit::lvalue
.
A gccjit::lvalue
. is something that can of 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 subclass 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 from the previous tutorial:
the multiplication i * i:
gccjit::rvalue expr = ctxt.new_binary_op ( GCC_JIT_BINARY_OP_MULT, int_type, param_i, param_i); /* Alternatively, using operator-overloading: */ gccjit::rvalue expr = param_i * param_i;which is a
gccjit::rvalue
, and
the various function parameters: param_i and param_n, instances of
gccjit::param
, which is a subclass ofgccjit::lvalue
(and, in turn, ofgccjit::rvalue
): we can both read from and write to function parameters within the body of a function.
Our new example has a new kind of expression: we have two local
variables. We create them by calling
gccjit::function::new_local()
, supplying a type and a name:
/* Build locals: */
gccjit::lvalue i = func.new_local (the_type, "i");
gccjit::lvalue sum = func.new_local (the_type, "sum");
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.
Control flow#
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
:
gccjit::block b_initial = func.new_block ("initial");
gccjit::block b_loop_cond = func.new_block ("loop_cond");
gccjit::block b_loop_body = func.new_block ("loop_body");
gccjit::block b_after_loop = func.new_block ("after_loop");
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 to sum, using
gccjit::block::add_assignment()
to add
an assignment statement, and using gccjit::context::zero()
to get
the constant value 0 for the relevant type for the right-hand side of
the assignment:
/* sum = 0; */
b_initial.add_assignment (sum, ctxt.zero (the_type));
/* i = 0; */
b_initial.add_assignment (i, ctxt.zero (the_type));
We can then terminate the entry block by jumping to the conditional:
b_initial.end_with_jump (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 could build the comparison using gccjit::context::new_comparison()
:
gccjit::rvalue guard =
ctxt.new_comparison (GCC_JIT_COMPARISON_GE,
i, n);
and can then use this to add b_loop_cond’s sole statement, via
gccjit::block::end_with_conditional()
:
b_loop_cond.end_with_conditional (guard,
b_after_loop, // on_true
b_loop_body); // on_false
However gccjit::rvalue
has overloaded operators for this, so we
express the conditional as
gccjit::rvalue guard = (i >= n);
and hence we can write the block more concisely as:
b_loop_cond.end_with_conditional (
i >= n,
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::block::add_assignment_op()
to handle these operations:
/* sum += i * i */
b_loop_body.add_assignment_op (sum,
GCC_JIT_BINARY_OP_PLUS,
i * i);
The i++ can be thought of as i += 1, and can thus be handled in
a similar way. We use gcc_jit_context_one()
to get the constant
value 1 (for the relevant type) for the right-hand side
of the assignment.
/* i++ */
b_loop_body.add_assignment_op (i,
GCC_JIT_BINARY_OP_PLUS,
ctxt.one (the_type));
Note
For numeric constants other than 0 or 1, we could use
gccjit::context::new_rvalue()
, which has overloads
for both int
and double
.
The loop body completes by jumping back to the conditional:
b_loop_body.end_with_jump (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:
/* return sum */
b_after_loop.end_with_return (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 now compile it to machine code:
gcc_jit_result *result;
result = ctxt.compile ();
ctxt.release ();
if (!result)
{
fprintf (stderr, "NULL result");
return 1;
}
typedef int (*loop_test_fn_type) (int);
loop_test_fn_type loop_test =
(loop_test_fn_type)gcc_jit_result_get_code (result, "loop_test");
if (!loop_test)
{
fprintf (stderr, "NULL loop_test");
gcc_jit_result_release (result);
return 1;
}
printf ("result: %d", loop_test (10));
result: 285
Visualizing the control flow graph#
You can see the control flow graph of a function using
gccjit::function::dump_to_dot()
:
func.dump_to_dot ("/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 (my preferred one is xdot.py; see https://github.com/jrfonseca/xdot.py; on Fedora you can install it with yum install python-xdot):
Full example#
/* Usage example for libgccjit.so's C++ API Copyright (C) 2014-2022 Free Software Foundation, Inc. This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see <http://www.gnu.org/licenses/>. */ #include <libgccjit++.h> #include <stdlib.h> #include <stdio.h> void create_code (gccjit::context ctxt) { /* 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; */ gccjit::type the_type = ctxt.get_int_type <int> (); gccjit::type return_type = the_type; gccjit::param n = ctxt.new_param (the_type, "n"); std::vector<gccjit::param> params; params.push_back (n); gccjit::function func = ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED, return_type, "loop_test", params, 0); /* Build locals: */ gccjit::lvalue i = func.new_local (the_type, "i"); gccjit::lvalue sum = func.new_local (the_type, "sum"); gccjit::block b_initial = func.new_block ("initial"); gccjit::block b_loop_cond = func.new_block ("loop_cond"); gccjit::block b_loop_body = func.new_block ("loop_body"); gccjit::block b_after_loop = func.new_block ("after_loop"); /* sum = 0; */ b_initial.add_assignment (sum, ctxt.zero (the_type)); /* i = 0; */ b_initial.add_assignment (i, ctxt.zero (the_type)); b_initial.end_with_jump (b_loop_cond); /* if (i >= n) */ b_loop_cond.end_with_conditional ( i >= n, b_after_loop, b_loop_body); /* sum += i * i */ b_loop_body.add_assignment_op (sum, GCC_JIT_BINARY_OP_PLUS, i * i); /* i++ */ b_loop_body.add_assignment_op (i, GCC_JIT_BINARY_OP_PLUS, ctxt.one (the_type)); b_loop_body.end_with_jump (b_loop_cond); /* return sum */ b_after_loop.end_with_return (sum); } int main (int argc, char **argv) { gccjit::context ctxt; gcc_jit_result *result = NULL; /* Get a "context" object for working with the library. */ ctxt = gccjit::context::acquire (); /* Set some options on the context. Turn this on to see the code being generated, in assembler form. */ ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, 0); /* Populate the context. */ create_code (ctxt); /* Compile the code. */ result = ctxt.compile (); ctxt.release (); if (!result) { fprintf (stderr, "NULL result"); return 1; } /* Extract the generated code from "result". */ typedef int (*loop_test_fn_type) (int); loop_test_fn_type loop_test = (loop_test_fn_type)gcc_jit_result_get_code (result, "loop_test"); if (!loop_test) { fprintf (stderr, "NULL loop_test"); gcc_jit_result_release (result); return 1; } /* Run the generated code. */ int val = loop_test (10); printf("loop_test returned: %d\n", val); gcc_jit_result_release (result); return 0; }
Building and running it:
$ gcc \
tut03-sum-of-squares.cc \
-o tut03-sum-of-squares \
-lgccjit
# Run the built program:
$ ./tut03-sum-of-squares
loop_test returned: 285