.. Copyright (C) 2014-2022 Free Software Foundation, Inc. Originally contributed by David Malcolm This 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 of the License, or (at your option) any later version. This program 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 this program. If not, see . .. default-domain:: cpp Tutorial part 3: Loops and variables ------------------------------------ Consider this C function: .. code-block:: c 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: .. code-block:: c 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: .. figure:: ../../intro/sum-of-squares.png :alt: image of a control flow graph As before, we include the libgccjit++ header and make a :type:`gccjit::context`. .. code-block:: c++ #include 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 .. code-block:: c++ gccjit::type the_type = ctxt.get_type (ctxt, GCC_JIT_TYPE_INT); though we could equally well make it work on, say, `double`: .. code-block:: c++ gccjit::type the_type = ctxt.get_type (ctxt, GCC_JIT_TYPE_DOUBLE); For integer types we can use :func:`gccjit::context::get_int_type` to directly bind a specific type: .. code-block:: c++ gccjit::type the_type = ctxt.get_int_type (); Let's build the function: .. code-block:: c++ gcc_jit_param n = ctxt.new_param (the_type, "n"); std::vector 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 :type:`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 :type:`gccjit::type`. Another important class is :type:`gccjit::lvalue`. A :type:`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: .. code-block:: c LVALUE = RVALUE; Note that :type:`gccjit::lvalue` is a subclass of :type:`gccjit::rvalue`, where in an assignment of the form: .. code-block:: c 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: 1. the multiplication `i * i`: .. code-block:: c++ 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 :type:`gccjit::rvalue`, and 2. the various function parameters: `param_i` and `param_n`, instances of :type:`gccjit::param`, which is a subclass of :type:`gccjit::lvalue` (and, in turn, of :type:`gccjit::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 :func:`gccjit::function::new_local`, supplying a type and a name: .. code-block:: c++ /* Build locals: */ gccjit::lvalue i = func.new_local (the_type, "i"); gccjit::lvalue sum = func.new_local (the_type, "sum"); These are instances of :type:`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: .. code-block:: 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: 1. before the loop (initializing the locals) 2. the conditional at the top of the loop (comparing `i < n`) 3. the body of the loop 4. after the loop terminates (`return sum`) so we create these as :type:`gccjit::block` instances within the :type:`gccjit::function`: .. code-block:: c++ 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 :func:`gccjit::block::add_assignment` to add an assignment statement, and using :func:`gccjit::context::zero` to get the constant value `0` for the relevant type for the right-hand side of the assignment: .. code-block:: c++ /* 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: .. code-block:: c++ 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 :type:`gccjit::rvalue`, in this case the comparison of `i` and `n`. We could build the comparison using :func:`gccjit::context::new_comparison`: .. code-block:: c++ 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 :func:`gccjit::block::end_with_conditional`: .. code-block:: c++ b_loop_cond.end_with_conditional (guard, b_after_loop, // on_true b_loop_body); // on_false However :type:`gccjit::rvalue` has overloaded operators for this, so we express the conditional as .. code-block:: c++ gccjit::rvalue guard = (i >= n); and hence we can write the block more concisely as: .. code-block:: c++ 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 :func:`gccjit::block::add_assignment_op` to handle these operations: .. code-block:: c++ /* 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 :c:func:`gcc_jit_context_one` to get the constant value `1` (for the relevant type) for the right-hand side of the assignment. .. code-block:: c++ /* 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 :func:`gccjit::context::new_rvalue`, which has overloads for both ``int`` and ``double``. The loop body completes by jumping back to the conditional: .. code-block:: c++ 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: .. code-block:: c++ return sum; so the block is just one statement: .. code-block:: c++ /* 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: .. code-block:: c++ 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)); .. code-block:: bash result: 285 Visualizing the control flow graph ********************************** You can see the control flow graph of a function using :func:`gccjit::function::dump_to_dot`: .. code-block:: c++ 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`: .. code-block:: bash $ 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`): .. figure:: ../../intro/sum-of-squares.png :alt: image of a control flow graph Full example ************ .. literalinclude:: ../../examples/tut03-sum-of-squares.cc :lines: 1- :language: c++ Building and running it: .. code-block:: console $ gcc \ tut03-sum-of-squares.cc \ -o tut03-sum-of-squares \ -lgccjit # Run the built program: $ ./tut03-sum-of-squares loop_test returned: 285