.. Copyright 2014 David Malcolm Copyright 2014 Red Hat, Inc. 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 . 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. Let's construct this from Python. 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:: ../sum-of-squares.png :alt: image of a control flow graph As before, we import the libgccjit Python bindings and make a :py:class:`gccjit.Context`: >>> import gccjit >>> ctxt = gccjit.Context() The function works with the C `int` type: >>> the_type = ctxt.get_type(gccjit.TypeKind.INT) though we could equally well make it work on, say, `double`: >>> the_type = ctxt.get_type(gccjit.TypeKind.DOUBLE) Let's build the function: >>> return_type = the_type >>> param_n = ctxt.new_param(the_type, b"n") >>> fn = ctxt.new_function(gccjit.FunctionKind.EXPORTED, ... return_type, ... b"loop_test", ... [param_n]) >>> print(fn) loop_test The base class of expression is the :py:class:`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 :py:class:`gccjit.Type`. Anothe important class is :py:class:`gccjit.LValue`. A :py:class:`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 :py:class:`gccjit.LValue` is a subclass of :py:class:`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 `i * i`:: ctxt.new_binary_op(gccjit.BinaryOp.MULT, int_type, param_i, param_i) which is a :py:class:`gccjit.RValue`, and the various function parameters: `param_i` and `param_n`, instances of :py:class:`gccjit.Param`, which is a subclass of :py:class:`gccjit.LValue` (and, in turn, of :py:class:`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 :py:meth:`gccjit.Function.new_local`, supplying a type and a name: >>> local_i = fn.new_local(the_type, b"i") >>> print(local_i) i >>> local_sum = fn.new_local(the_type, b"sum") >>> print(local_sum) sum These are instances of :py:class:`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. 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 :py:class:`gccjit.Block` instances within the :py:class:`gccjit.Function`: >>> entry_block = fn.new_block(b'entry') >>> cond_block = fn.new_block(b"cond") >>> loop_block = fn.new_block(b"loop") >>> after_loop_block = fn.new_block(b"after_loop") We now populate each block with statements. The entry block consists of initializations followed by a jump to the conditional. We assign `0` to `i` and to `sum`, using :py:meth:`gccjit.Block.add_assignment` to add an assignment statement, and using :py:meth:`gccjit.Context.zero` to get the constant value `0` for the relevant type for the right-hand side of the assignment: >>> entry_block.add_assignment(local_i, ctxt.zero(the_type)) >>> entry_block.add_assignment(local_sum, ctxt.zero(the_type)) We can then terminate the entry block by jumping to the conditional: >>> entry_block.end_with_jump(cond_block) 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 :py:class:`gccjit.RValue`, in this case the comparison of `i` and `n`. We build the comparison using :py:meth:`gccjit.Context.new_comparison`: >>> guard = ctxt.new_comparison(gccjit.Comparison.LT, local_i, param_n) >>> print(guard) i < n and can then use this to add `cond_block`'s sole statement, via :py:meth:`gccjit.Block.end_with_conditional`: >>> cond_block.end_with_conditional(guard, ... loop_block, # on true ... after_loop_block) # 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 :py:meth:`gccjit.Block.add_assignment_op` to handle these operations: >>> loop_block.add_assignment_op(local_sum, ... gccjit.BinaryOp.PLUS, ... ctxt.new_binary_op(gccjit.BinaryOp.MULT, ... the_type, ... local_i, local_i)) The `i++` can be thought of as `i += 1`, and can thus be handled in a similar way. We use :py:meth:`gccjit.Context.one` to get the constant value `1` (for the relevant type) for the right-hand side of the assignment: >>> loop_block.add_assignment_op(local_i, ... gccjit.BinaryOp.PLUS, ... ctxt.one(the_type)) The loop body completes by jumping back to the conditional: >>> loop_block.end_with_jump(cond_block) Finally, we populate the `after_loop` block, reached when the loop conditional is false. At the C level this is simply: .. code-block:: c return sum; so the block is just one statement: >>> after_loop_block.end_with_return(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 now compile it to machine code: >>> jit_result = ctxt.compile() >>> void_ptr = jit_result.get_code(b'loop_test') and use `ctypes` to turn it into a Python callable: >>> import ctypes >>> int_int_func_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int) >>> callable = int_int_func_type(void_ptr) Now we can call it: >>> callable(10) 285 Visualizing the control flow graph ********************************** You can see the control flow graph of a function using :py:meth:`gccjit.Function.dump_to_dot`: >>> fn.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:: ../sum-of-squares.png :alt: image of a control flow graph Full example ************ Here's what the above looks like as a complete program: .. literalinclude:: ../../examples/sum_of_squares.py :lines: 34- :language: python