Commit 29df5715 by David Malcolm Committed by David Malcolm

Document libgccjit++.h

gcc/jit/ChangeLog:
	* docs/cp/index.rst: New file.
	* docs/cp/intro/index.rst: New file.
	* docs/cp/intro/tutorial01.rst: New file.
	* docs/cp/intro/tutorial02.rst: New file.
	* docs/cp/intro/tutorial03.rst: New file.
	* docs/cp/intro/tutorial04.rst: New file.
	* docs/cp/topics/contexts.rst: New file.
	* docs/cp/topics/expressions.rst: New file.
	* docs/cp/topics/functions.rst: New file.
	* docs/cp/topics/index.rst: New file.
	* docs/cp/topics/locations.rst: New file.
	* docs/cp/topics/objects.rst: New file.
	* docs/cp/topics/results.rst: New file.
	* docs/cp/topics/types.rst: New file.
	* docs/examples/tut01-hello-world.cc: New file.
	* docs/examples/tut02-square.c: Fix missing newline in output.
	* docs/examples/tut02-square.cc: New file.
	* docs/examples/tut03-sum-of-squares.cc: New file.
	* docs/examples/tut04-toyvm/toyvm.cc: New file.
	* docs/index.rst: Move summary to above the table of contents.
	Add text about the C vs C++ APIs.
	* docs/topics/contexts.rst: Fix a typo.

	* docs/_build/texinfo/libgccjit.texi: Regenerate.
	* docs/_build/texinfo/factorial1.png: New file.
	* docs/_build/texinfo/sum-of-squares1.png: New file.

From-SVN: r218588
parent e56e603b
2014-12-10 David Malcolm <dmalcolm@redhat.com>
* docs/cp/index.rst: New file.
* docs/cp/intro/index.rst: New file.
* docs/cp/intro/tutorial01.rst: New file.
* docs/cp/intro/tutorial02.rst: New file.
* docs/cp/intro/tutorial03.rst: New file.
* docs/cp/intro/tutorial04.rst: New file.
* docs/cp/topics/contexts.rst: New file.
* docs/cp/topics/expressions.rst: New file.
* docs/cp/topics/functions.rst: New file.
* docs/cp/topics/index.rst: New file.
* docs/cp/topics/locations.rst: New file.
* docs/cp/topics/objects.rst: New file.
* docs/cp/topics/results.rst: New file.
* docs/cp/topics/types.rst: New file.
* docs/examples/tut01-hello-world.cc: New file.
* docs/examples/tut02-square.c: Fix missing newline in output.
* docs/examples/tut02-square.cc: New file.
* docs/examples/tut03-sum-of-squares.cc: New file.
* docs/examples/tut04-toyvm/toyvm.cc: New file.
* docs/index.rst: Move summary to above the table of contents.
Add text about the C vs C++ APIs.
* docs/topics/contexts.rst: Fix a typo.
* docs/_build/texinfo/libgccjit.texi: Regenerate.
* docs/_build/texinfo/factorial1.png: New file.
* docs/_build/texinfo/sum-of-squares1.png: New file.
2014-12-09 David Malcolm <dmalcolm@redhat.com> 2014-12-09 David Malcolm <dmalcolm@redhat.com>
* docs/examples/tut04-toyvm/toyvm.c (toyvm_function_compile): Move * docs/examples/tut04-toyvm/toyvm.c (toyvm_function_compile): Move
......
This source diff could not be displayed because it is too large. You can view the blob instead.
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
C++ bindings for libgccjit
==========================
This document describes the C++ bindings to
`libgccjit <http://gcc.gnu.org/wiki/JIT>`_, an API for embedding GCC
inside programs and libraries.
The C++ bindings consist of a single header file ``libgccjit++.h``.
This is a collection of "thin" wrapper classes around the C API.
Everything is an inline function, implemented in terms of the C API,
so there is nothing extra to link against.
Note that libgccjit is currently of "Alpha" quality;
the APIs are not yet set in stone, and they shouldn't be used in
production yet.
Contents:
.. toctree::
:maxdepth: 2
intro/index.rst
topics/index.rst
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
Tutorial
========
.. toctree::
:maxdepth: 2
tutorial01.rst
tutorial02.rst
tutorial03.rst
tutorial04.rst
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Tutorial part 1: "Hello world"
==============================
Before we look at the details of the API, let's look at building and
running programs that use the library.
Here's a toy "hello world" program that uses the library's C++ API to
synthesize a call to `printf` and uses it to write a message to stdout.
Don't worry about the content of the program for now; we'll cover
the details in later parts of this tutorial.
.. literalinclude:: ../../examples/tut01-hello-world.cc
:language: c++
Copy the above to `tut01-hello-world.cc`.
Assuming you have the jit library installed, build the test program
using:
.. code-block:: console
$ gcc \
tut01-hello-world.cc \
-o tut01-hello-world \
-lgccjit
You should then be able to run the built program:
.. code-block:: console
$ ./tut01-hello-world
hello world
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Tutorial part 2: Creating a trivial machine code function
---------------------------------------------------------
Consider this C function:
.. code-block:: c
int square (int i)
{
return i * i;
}
How can we construct this at run-time using libgccjit's C++ API?
First we need to include the relevant header:
.. code-block:: c++
#include <libgccjit++.h>
All state associated with compilation is associated with a
:type:`gccjit::context`, which is a thin C++ wrapper around the C API's
:c:type:`gcc_jit_context *`.
Create one using :func:`gccjit::context::acquire`:
.. code-block:: c++
gccjit::context ctxt;
ctxt = gccjit::context::acquire ();
The JIT library has a system of types. It is statically-typed: every
expression is of a specific type, fixed at compile-time. In our example,
all of the expressions are of the C `int` type, so let's obtain this from
the context, as a :type:`gccjit::type`, using
:func:`gccjit::context::get_type`:
.. code-block:: c++
gccjit::type int_type = ctxt.get_type (GCC_JIT_TYPE_INT);
:type:`gccjit::type` is an example of a "contextual" object: every
entity in the API is associated with a :type:`gccjit::context`.
Memory management is easy: all such "contextual" objects are automatically
cleaned up for you when the context is released, using
:func:`gccjit::context::release`:
.. code-block:: c++
ctxt.release ();
so you don't need to manually track and cleanup all objects, just the
contexts.
All of the C++ classes in the API are thin wrappers around pointers to
types in the C API.
The C++ class hierarchy within the ``gccjit`` namespace looks like this::
+- object
+- location
+- type
+- struct
+- field
+- function
+- block
+- rvalue
+- lvalue
+- param
One thing you can do with a :type:`gccjit::object` is
to ask it for a human-readable description as a :type:`std::string`, using
:func:`gccjit::object::get_debug_string`:
.. code-block:: c++
printf ("obj: %s\n", obj.get_debug_string ().c_str ());
giving this text on stdout:
.. code-block:: bash
obj: int
This is invaluable when debugging.
Let's create the function. To do so, we first need to construct
its single parameter, specifying its type and giving it a name,
using :func:`gccjit::context::new_param`:
.. code-block:: c++
gccjit::param param_i = ctxt.new_param (int_type, "i");
and we can then make a vector of all of the params of the function,
in this case just one:
.. code-block:: c++
std::vector<gccjit::param> params;
params.push_back (param_i);
Now we can create the function, using
:c:func:`gccjit::context::new_function`:
.. code-block:: c++
gccjit::function func =
ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
int_type,
"square",
params,
0);
To define the code within the function, we must create basic blocks
containing statements.
Every basic block contains a list of statements, eventually terminated
by a statement that either returns, or jumps to another basic block.
Our function has no control-flow, so we just need one basic block:
.. code-block:: c++
gccjit::block block = func.new_block ();
Our basic block is relatively simple: it immediately terminates by
returning the value of an expression.
We can build the expression using :func:`gccjit::context::new_binary_op`:
.. code-block:: c++
gccjit::rvalue expr =
ctxt.new_binary_op (
GCC_JIT_BINARY_OP_MULT, int_type,
param_i, param_i);
A :type:`gccjit::rvalue` is another example of a
:type:`gccjit::object` subclass. As before, we can print it with
:func:`gccjit::object::get_debug_string`.
.. code-block:: c++
printf ("expr: %s\n", expr.get_debug_string ().c_str ());
giving this output:
.. code-block:: bash
expr: i * i
Note that :type:`gccjit::rvalue` provides numerous overloaded operators
which can be used to dramatically reduce the amount of typing needed.
We can build the above binary operation more directly with this one-liner:
.. code-block:: c++
gccjit::rvalue expr = param_i * param_i;
Creating the expression in itself doesn't do anything; we have to add
this expression to a statement within the block. In this case, we use it
to build a return statement, which terminates the basic block:
.. code-block:: c++
block.end_with_return (expr);
OK, we've populated the context. We can now compile it using
:func:`gccjit::context::compile`:
.. code-block:: c++
gcc_jit_result *result;
result = ctxt.compile ();
and get a :c:type:`gcc_jit_result *`.
We can now use :c:func:`gcc_jit_result_get_code` to look up a specific
machine code routine within the result, in this case, the function we
created above.
.. code-block:: c++
void *fn_ptr = gcc_jit_result_get_code (result, "square");
if (!fn_ptr)
{
fprintf (stderr, "NULL fn_ptr");
goto error;
}
We can now cast the pointer to an appropriate function pointer type, and
then call it:
.. code-block:: c++
typedef int (*fn_type) (int);
fn_type square = (fn_type)fn_ptr;
printf ("result: %d", square (5));
.. code-block:: bash
result: 25
Options
*******
To get more information on what's going on, you can set debugging flags
on the context using :func:`gccjit::context::set_bool_option`.
.. (I'm deliberately not mentioning
:c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE` here since I think
it's probably more of use to implementors than to users)
Setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE` will dump a
C-like representation to stderr when you compile (GCC's "GIMPLE"
representation):
.. code-block:: c++
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1);
result = ctxt.compile ();
.. code-block:: c
square (signed int i)
{
signed int D.260;
entry:
D.260 = i * i;
return D.260;
}
We can see the generated machine code in assembler form (on stderr) by
setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE` on the context
before compiling:
.. code-block:: c++
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, 1);
result = ctxt.compile ();
.. code-block:: gas
.file "fake.c"
.text
.globl square
.type square, @function
square:
.LFB6:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
.L14:
movl -4(%rbp), %eax
imull -4(%rbp), %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size square, .-square
.ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2-0.5.1920c315ff984892399893b380305ab36e07b455.fc20)"
.section .note.GNU-stack,"",@progbits
By default, no optimizations are performed, the equivalent of GCC's
`-O0` option. We can turn things up to e.g. `-O3` by calling
:func:`gccjit::context::set_int_option` with
:c:macro:`GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL`:
.. code-block:: c++
ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3);
.. code-block:: gas
.file "fake.c"
.text
.p2align 4,,15
.globl square
.type square, @function
square:
.LFB7:
.cfi_startproc
.L16:
movl %edi, %eax
imull %edi, %eax
ret
.cfi_endproc
.LFE7:
.size square, .-square
.ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2-0.5.1920c315ff984892399893b380305ab36e07b455.fc20)"
.section .note.GNU-stack,"",@progbits
Naturally this has only a small effect on such a trivial function.
Full example
************
Here's what the above looks like as a complete program:
.. literalinclude:: ../../examples/tut02-square.cc
:lines: 1-
:language: c++
Building and running it:
.. code-block:: console
$ gcc \
tut02-square.cc \
-o tut02-square \
-lgccjit
# Run the built program:
$ ./tut02-square
result: 25
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. 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 <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
.. 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<T>`
to directly bind a specific type:
.. code-block:: c++
gccjit::type the_type = ctxt.get_int_type <int> ();
Let's build the function:
.. code-block:: c++
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 :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`.
Anothe 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);
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 write the block more concisely as:
.. code-block:: c++
b_loop_cond.end_with_conditional (
i >= n,
b_after_loop,
b_loop_body);
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
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Tutorial part 4: Adding JIT-compilation to a toy interpreter
------------------------------------------------------------
In this example we construct a "toy" interpreter, and add JIT-compilation
to it.
Our toy interpreter
*******************
It's a stack-based interpreter, and is intended as a (very simple) example
of the kind of bytecode interpreter seen in dynamic languages such as
Python, Ruby etc.
For the sake of simplicity, our toy virtual machine is very limited:
* The only data type is `int`
* It can only work on one function at a time (so that the only
function call that can be made is to recurse).
* Functions can only take one parameter.
* Functions have a stack of `int` values.
* We'll implement function call within the interpreter by calling a
function in our implementation, rather than implementing our own
frame stack.
* The parser is only good enough to get the examples to work.
Naturally, a real interpreter would be much more complicated that this.
The following operations are supported:
====================== ======================== =============== ==============
Operation Meaning Old Stack New Stack
====================== ======================== =============== ==============
DUP Duplicate top of stack. ``[..., x]`` ``[..., x, x]``
ROT Swap top two elements ``[..., x, y]`` ``[..., y, x]``
of stack.
BINARY_ADD Add the top two elements ``[..., x, y]`` ``[..., (x+y)]``
on the stack.
BINARY_SUBTRACT Likewise, but subtract. ``[..., x, y]`` ``[..., (x-y)]``
BINARY_MULT Likewise, but multiply. ``[..., x, y]`` ``[..., (x*y)]``
BINARY_COMPARE_LT Compare the top two ``[..., x, y]`` ``[..., (x<y)]``
elements on the stack
and push a nonzero/zero
if (x<y).
RECURSE Recurse, passing the top ``[..., x]`` ``[..., fn(x)]``
of the stack, and
popping the result.
RETURN Return the top of the ``[x]`` ``[]``
stack.
PUSH_CONST `arg` Push an int const. ``[...]`` ``[..., arg]``
JUMP_ABS_IF_TRUE `arg` Pop; if top of stack was ``[..., x]`` ``[...]``
nonzero, jump to
``arg``.
====================== ======================== =============== ==============
Programs can be interpreted, disassembled, and compiled to machine code.
The interpreter reads ``.toy`` scripts. Here's what a simple recursive
factorial program looks like, the script ``factorial.toy``.
The parser ignores lines beginning with a `#`.
.. literalinclude:: ../../examples/tut04-toyvm/factorial.toy
:lines: 1-
:language: c
The interpreter is a simple infinite loop with a big ``switch`` statement
based on what the next opcode is:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Execute the given function. */
:end-before: /* JIT compilation. */
:language: c++
Compiling to machine code
*************************
We want to generate machine code that can be cast to this type and
then directly executed in-process:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Functions are compiled to this function ptr type. */
:end-before: enum opcode
:language: c++
Our compiler isn't very sophisticated; it takes the implementation of
each opcode above, and maps it directly to the operations supported by
the libgccjit API.
How should we handle the stack? In theory we could calculate what the
stack depth will be at each opcode, and optimize away the stack
manipulation "by hand". We'll see below that libgccjit is able to do
this for us, so we'll implement stack manipulation
in a direct way, by creating a ``stack`` array and ``stack_depth``
variables, local within the generated function, equivalent to this C code:
.. code-block:: c
int stack_depth;
int stack[MAX_STACK_DEPTH];
We'll also have local variables ``x`` and ``y`` for use when implementing
the opcodes, equivalent to this:
.. code-block:: c
int x;
int y;
This means our compiler has the following state:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* State. */
:end-before: };
:language: c++
Setting things up
*****************
First we create our types:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Create types. */
:end-before: /* The constant value 1. */
:language: c++
along with extracting a useful `int` constant:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* The constant value 1. */
:end-before: /* Create locations. */
:language: c++
We'll implement push and pop in terms of the ``stack`` array and
``stack_depth``. Here are helper functions for adding statements to
a block, implementing pushing and popping values:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Stack manipulation. */
:end-before: /* Create the context. */
:language: c++
We will support single-stepping through the generated code in the
debugger, so we need to create :type:`gccjit::location` instances, one
per operation in the source code. These will reference the lines of
e.g. ``factorial.toy``.
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Create locations. */
:end-before: /* Creating the function. */
:language: c++
Let's create the function itself. As usual, we create its parameter
first, then use the parameter to create the function:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Creating the function. */
:end-before: /* Create stack lvalues. */
:language: c++
We create the locals within the function.
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Create stack lvalues. */
:end-before: /* 1st pass: create blocks, one per opcode.
:language: c++
Populating the function
***********************
There's some one-time initialization, and the API treats the first block
you create as the entrypoint of the function, so we need to create that
block first:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: first. */
:end-before: /* Create a block per operation. */
:language: c++
We can now create blocks for each of the operations. Most of these will
be consolidated into larger blocks when the optimizer runs.
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Create a block per operation. */
:end-before: /* Populate the initial block. */
:language: c++
Now that we have a block it can jump to when it's done, we can populate
the initial block:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Populate the initial block. */
:end-before: /* 2nd pass: fill in instructions. */
:language: c++
We can now populate the blocks for the individual operations. We loop
through them, adding instructions to their blocks:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* 2nd pass: fill in instructions. */
:end-before: /* Helper macros. */
:language: c++
We're going to have another big ``switch`` statement for implementing
the opcodes, this time for compiling them, rather than interpreting
them. It's helpful to have macros for implementing push and pop, so that
we can make the ``switch`` statement that's coming up look as much as
possible like the one above within the interpreter:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Helper macros. */
:end-before: block.add_comment
:language: c++
.. note::
A particularly clever implementation would have an *identical*
``switch`` statement shared by the interpreter and the compiler, with
some preprocessor "magic". We're not doing that here, for the sake
of simplicity.
When I first implemented this compiler, I accidentally missed an edit
when copying and pasting the ``Y_EQUALS_POP`` macro, so that popping the
stack into ``y`` instead erroneously assigned it to ``x``, leaving ``y``
uninitialized.
To track this kind of thing down, we can use
:func:`gccjit::block::add_comment` to add descriptive comments
to the internal representation. This is invaluable when looking through
the generated IR for, say ``factorial``:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: PUSH_RVALUE (y)
:end-before: /* Handle the individual opcodes. */
:language: c++
We can now write the big ``switch`` statement that implements the
individual opcodes, populating the relevant block with statements:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Handle the individual opcodes. */
:end-before: /* Go to the next block. */
:language: c++
Every block must be terminated, via a call to one of the
``gccjit::block::end_with_`` entrypoints. This has been done for two
of the opcodes, but we need to do it for the other ones, by jumping
to the next block.
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* Go to the next block. */
:end-before: /* end of loop on PC locations. */
:language: c++
This is analogous to simply incrementing the program counter.
Verifying the control flow graph
********************************
Having finished looping over the blocks, the context is complete.
As before, we can verify that the control flow and statements are sane by
using :func:`gccjit::function::dump_to_dot`:
.. code-block:: c++
fn.dump_to_dot ("/tmp/factorial.dot");
and viewing the result. Note how the label names, comments, and
variable names show up in the dump, to make it easier to spot
errors in our compiler.
.. figure:: ../../intro/factorial.png
:alt: image of a control flow graph
Compiling the context
*********************
Having finished looping over the blocks and populating them with
statements, the context is complete.
We can now compile it, and extract machine code from the result:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* We've now finished populating the context. Compile it. */
:end-before: /* (this leaks "result" and "funcname") */
:language: c++
We can now run the result:
.. literalinclude:: ../../examples/tut04-toyvm/toyvm.cc
:start-after: /* JIT-compilation. */
:end-before: return 0;
:language: c++
Single-stepping through the generated code
******************************************
It's possible to debug the generated code. To do this we need to both:
* Set up source code locations for our statements, so that we can
meaningfully step through the code. We did this above by
calling :func:`gccjit::context::new_location` and using the
results.
* Enable the generation of debugging information, by setting
:c:macro:`GCC_JIT_BOOL_OPTION_DEBUGINFO` on the
:type:`gccjit::context` via
:func:`gccjit::context::set_bool_option`:
.. code-block:: c++
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO, 1);
Having done this, we can put a breakpoint on the generated function:
.. code-block:: console
$ gdb --args ./toyvm factorial.toy 10
(gdb) break factorial
Function "factorial" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (factorial) pending.
(gdb) run
Breakpoint 1, factorial (arg=10) at factorial.toy:14
14 DUP
We've set up location information, which references ``factorial.toy``.
This allows us to use e.g. ``list`` to see where we are in the script:
.. code-block:: console
(gdb) list
9
10 # Initial state:
11 # stack: [arg]
12
13 # 0:
14 DUP
15 # stack: [arg, arg]
16
17 # 1:
18 PUSH_CONST 2
and to step through the function, examining the data:
.. code-block:: console
(gdb) n
18 PUSH_CONST 2
(gdb) n
22 BINARY_COMPARE_LT
(gdb) print stack
$5 = {10, 10, 2, 0, -7152, 32767, 0, 0}
(gdb) print stack_depth
$6 = 3
You'll see that the parts of the ``stack`` array that haven't been
touched yet are uninitialized.
.. note::
Turning on optimizations may lead to unpredictable results when
stepping through the generated code: the execution may appear to
"jump around" the source code. This is analogous to turning up the
optimization level in a regular compiler.
Examining the generated code
****************************
How good is the optimized code?
We can turn up optimizations, by calling
:cpp:func:`gccjit::context::set_int_option` with
:c:macro:`GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL`:
.. code-block:: c++
ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3);
One of GCC's internal representations is called "gimple". A dump of the
initial gimple representation of the code can be seen by setting:
.. code-block:: c++
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1);
With optimization on and source locations displayed, this gives:
.. We'll use "c" for gimple dumps
.. code-block:: c
factorial (signed int arg)
{
<unnamed type> D.80;
signed int D.81;
signed int D.82;
signed int D.83;
signed int D.84;
signed int D.85;
signed int y;
signed int x;
signed int stack_depth;
signed int stack[8];
try
{
initial:
stack_depth = 0;
stack[stack_depth] = arg;
stack_depth = stack_depth + 1;
goto instr0;
instr0:
/* DUP */:
stack_depth = stack_depth + -1;
x = stack[stack_depth];
stack[stack_depth] = x;
stack_depth = stack_depth + 1;
stack[stack_depth] = x;
stack_depth = stack_depth + 1;
goto instr1;
instr1:
/* PUSH_CONST */:
stack[stack_depth] = 2;
stack_depth = stack_depth + 1;
goto instr2;
/* etc */
You can see the generated machine code in assembly form via:
.. code-block:: c++
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, 1);
result = ctxt.compile ();
which shows that (on this x86_64 box) the compiler has unrolled the loop
and is using MMX instructions to perform several multiplications
simultaneously:
.. code-block:: gas
.file "fake.c"
.text
.Ltext0:
.p2align 4,,15
.globl factorial
.type factorial, @function
factorial:
.LFB0:
.file 1 "factorial.toy"
.loc 1 14 0
.cfi_startproc
.LVL0:
.L2:
.loc 1 26 0
cmpl $1, %edi
jle .L13
leal -1(%rdi), %edx
movl %edx, %ecx
shrl $2, %ecx
leal 0(,%rcx,4), %esi
testl %esi, %esi
je .L14
cmpl $9, %edx
jbe .L14
leal -2(%rdi), %eax
movl %eax, -16(%rsp)
leal -3(%rdi), %eax
movd -16(%rsp), %xmm0
movl %edi, -16(%rsp)
movl %eax, -12(%rsp)
movd -16(%rsp), %xmm1
xorl %eax, %eax
movl %edx, -16(%rsp)
movd -12(%rsp), %xmm4
movd -16(%rsp), %xmm6
punpckldq %xmm4, %xmm0
movdqa .LC1(%rip), %xmm4
punpckldq %xmm6, %xmm1
punpcklqdq %xmm0, %xmm1
movdqa .LC0(%rip), %xmm0
jmp .L5
# etc - edited for brevity
This is clearly overkill for a function that will likely overflow the
``int`` type before the vectorization is worthwhile - but then again, this
is a toy example.
Turning down the optimization level to 2:
.. code-block:: c++
ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 2);
yields this code, which is simple enough to quote in its entirety:
.. code-block:: gas
.file "fake.c"
.text
.p2align 4,,15
.globl factorial
.type factorial, @function
factorial:
.LFB0:
.cfi_startproc
.L2:
cmpl $1, %edi
jle .L8
movl $1, %edx
jmp .L4
.p2align 4,,10
.p2align 3
.L6:
movl %eax, %edi
.L4:
.L5:
leal -1(%rdi), %eax
imull %edi, %edx
cmpl $1, %eax
jne .L6
.L3:
.L7:
imull %edx, %eax
ret
.L8:
movl %edi, %eax
movl $1, %edx
jmp .L7
.cfi_endproc
.LFE0:
.size factorial, .-factorial
.ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2-%{gcc_release})"
.section .note.GNU-stack,"",@progbits
Note that the stack pushing and popping have been eliminated, as has the
recursive call (in favor of an iteration).
Putting it all together
***********************
The complete example can be seen in the source tree at
``gcc/jit/docs/examples/tut04-toyvm/toyvm.cc``
along with a Makefile and a couple of sample .toy scripts:
.. code-block:: console
$ ls -al
drwxrwxr-x. 2 david david 4096 Sep 19 17:46 .
drwxrwxr-x. 3 david david 4096 Sep 19 15:26 ..
-rw-rw-r--. 1 david david 615 Sep 19 12:43 factorial.toy
-rw-rw-r--. 1 david david 834 Sep 19 13:08 fibonacci.toy
-rw-rw-r--. 1 david david 238 Sep 19 14:22 Makefile
-rw-rw-r--. 1 david david 16457 Sep 19 17:07 toyvm.cc
$ make toyvm
g++ -Wall -g -o toyvm toyvm.cc -lgccjit
$ ./toyvm factorial.toy 10
interpreter result: 3628800
compiler result: 3628800
$ ./toyvm fibonacci.toy 10
interpreter result: 55
compiler result: 55
Behind the curtain: How does our code get optimized?
****************************************************
Our example is done, but you may be wondering about exactly how the
compiler turned what we gave it into the machine code seen above.
We can examine what the compiler is doing in detail by setting:
.. code-block:: c++
state.ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING, 1);
state.ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES, 1);
This will dump detailed information about the compiler's state to a
directory under ``/tmp``, and keep it from being cleaned up.
The precise names and their formats of these files is subject to change.
Higher optimization levels lead to more files.
Here's what I saw (edited for brevity; there were almost 200 files):
.. code-block:: console
intermediate files written to /tmp/libgccjit-KPQbGw
$ ls /tmp/libgccjit-KPQbGw/
fake.c.000i.cgraph
fake.c.000i.type-inheritance
fake.c.004t.gimple
fake.c.007t.omplower
fake.c.008t.lower
fake.c.011t.eh
fake.c.012t.cfg
fake.c.014i.visibility
fake.c.015i.early_local_cleanups
fake.c.016t.ssa
# etc
The gimple code is converted into Static Single Assignment form,
with annotations for use when generating the debuginfo:
.. code-block:: console
$ less /tmp/libgccjit-KPQbGw/fake.c.016t.ssa
.. code-block:: c
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)
factorial (signed int arg)
{
signed int stack[8];
signed int stack_depth;
signed int x;
signed int y;
<unnamed type> _20;
signed int _21;
signed int _38;
signed int _44;
signed int _51;
signed int _56;
initial:
stack_depth_3 = 0;
# DEBUG stack_depth => stack_depth_3
stack[stack_depth_3] = arg_5(D);
stack_depth_7 = stack_depth_3 + 1;
# DEBUG stack_depth => stack_depth_7
# DEBUG instr0 => NULL
# DEBUG /* DUP */ => NULL
stack_depth_8 = stack_depth_7 + -1;
# DEBUG stack_depth => stack_depth_8
x_9 = stack[stack_depth_8];
# DEBUG x => x_9
stack[stack_depth_8] = x_9;
stack_depth_11 = stack_depth_8 + 1;
# DEBUG stack_depth => stack_depth_11
stack[stack_depth_11] = x_9;
stack_depth_13 = stack_depth_11 + 1;
# DEBUG stack_depth => stack_depth_13
# DEBUG instr1 => NULL
# DEBUG /* PUSH_CONST */ => NULL
stack[stack_depth_13] = 2;
/* etc; edited for brevity */
We can perhaps better see the code by turning off
:c:macro:`GCC_JIT_BOOL_OPTION_DEBUGINFO` to suppress all those ``DEBUG``
statements, giving:
.. code-block:: console
$ less /tmp/libgccjit-1Hywc0/fake.c.016t.ssa
.. code-block:: c
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)
factorial (signed int arg)
{
signed int stack[8];
signed int stack_depth;
signed int x;
signed int y;
<unnamed type> _20;
signed int _21;
signed int _38;
signed int _44;
signed int _51;
signed int _56;
initial:
stack_depth_3 = 0;
stack[stack_depth_3] = arg_5(D);
stack_depth_7 = stack_depth_3 + 1;
stack_depth_8 = stack_depth_7 + -1;
x_9 = stack[stack_depth_8];
stack[stack_depth_8] = x_9;
stack_depth_11 = stack_depth_8 + 1;
stack[stack_depth_11] = x_9;
stack_depth_13 = stack_depth_11 + 1;
stack[stack_depth_13] = 2;
stack_depth_15 = stack_depth_13 + 1;
stack_depth_16 = stack_depth_15 + -1;
y_17 = stack[stack_depth_16];
stack_depth_18 = stack_depth_16 + -1;
x_19 = stack[stack_depth_18];
_20 = x_19 < y_17;
_21 = (signed int) _20;
stack[stack_depth_18] = _21;
stack_depth_23 = stack_depth_18 + 1;
stack_depth_24 = stack_depth_23 + -1;
x_25 = stack[stack_depth_24];
if (x_25 != 0)
goto <bb 4> (instr9);
else
goto <bb 3> (instr4);
instr4:
/* DUP */:
stack_depth_26 = stack_depth_24 + -1;
x_27 = stack[stack_depth_26];
stack[stack_depth_26] = x_27;
stack_depth_29 = stack_depth_26 + 1;
stack[stack_depth_29] = x_27;
stack_depth_31 = stack_depth_29 + 1;
stack[stack_depth_31] = 1;
stack_depth_33 = stack_depth_31 + 1;
stack_depth_34 = stack_depth_33 + -1;
y_35 = stack[stack_depth_34];
stack_depth_36 = stack_depth_34 + -1;
x_37 = stack[stack_depth_36];
_38 = x_37 - y_35;
stack[stack_depth_36] = _38;
stack_depth_40 = stack_depth_36 + 1;
stack_depth_41 = stack_depth_40 + -1;
x_42 = stack[stack_depth_41];
_44 = factorial (x_42);
stack[stack_depth_41] = _44;
stack_depth_46 = stack_depth_41 + 1;
stack_depth_47 = stack_depth_46 + -1;
y_48 = stack[stack_depth_47];
stack_depth_49 = stack_depth_47 + -1;
x_50 = stack[stack_depth_49];
_51 = x_50 * y_48;
stack[stack_depth_49] = _51;
stack_depth_53 = stack_depth_49 + 1;
# stack_depth_1 = PHI <stack_depth_24(2), stack_depth_53(3)>
instr9:
/* RETURN */:
stack_depth_54 = stack_depth_1 + -1;
x_55 = stack[stack_depth_54];
_56 = x_55;
stack ={v} {CLOBBER};
return _56;
}
Note in the above how all the :type:`gccjit::block` instances we
created have been consolidated into just 3 blocks in GCC's internal
representation: ``initial``, ``instr4`` and ``instr9``.
Optimizing away stack manipulation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Recall our simple implementation of stack operations. Let's examine
how the stack operations are optimized away.
After a pass of constant-propagation, the depth of the stack at each
opcode can be determined at compile-time:
.. code-block:: console
$ less /tmp/libgccjit-1Hywc0/fake.c.021t.ccp1
.. code-block:: c
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)
factorial (signed int arg)
{
signed int stack[8];
signed int stack_depth;
signed int x;
signed int y;
<unnamed type> _20;
signed int _21;
signed int _38;
signed int _44;
signed int _51;
initial:
stack[0] = arg_5(D);
x_9 = stack[0];
stack[0] = x_9;
stack[1] = x_9;
stack[2] = 2;
y_17 = stack[2];
x_19 = stack[1];
_20 = x_19 < y_17;
_21 = (signed int) _20;
stack[1] = _21;
x_25 = stack[1];
if (x_25 != 0)
goto <bb 4> (instr9);
else
goto <bb 3> (instr4);
instr4:
/* DUP */:
x_27 = stack[0];
stack[0] = x_27;
stack[1] = x_27;
stack[2] = 1;
y_35 = stack[2];
x_37 = stack[1];
_38 = x_37 - y_35;
stack[1] = _38;
x_42 = stack[1];
_44 = factorial (x_42);
stack[1] = _44;
y_48 = stack[1];
x_50 = stack[0];
_51 = x_50 * y_48;
stack[0] = _51;
instr9:
/* RETURN */:
x_55 = stack[0];
x_56 = x_55;
stack ={v} {CLOBBER};
return x_56;
}
Note how, in the above, all those ``stack_depth`` values are now just
constants: we're accessing specific stack locations at each opcode.
The "esra" pass ("Early Scalar Replacement of Aggregates") breaks
out our "stack" array into individual elements:
.. code-block:: console
$ less /tmp/libgccjit-1Hywc0/fake.c.024t.esra
.. code-block:: c
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)
Created a replacement for stack offset: 0, size: 32: stack$0
Created a replacement for stack offset: 32, size: 32: stack$1
Created a replacement for stack offset: 64, size: 32: stack$2
Symbols to be put in SSA form
{ D.89 D.90 D.91 }
Incremental SSA update started at block: 0
Number of blocks in CFG: 5
Number of blocks to update: 4 ( 80%)
factorial (signed int arg)
{
signed int stack$2;
signed int stack$1;
signed int stack$0;
signed int stack[8];
signed int stack_depth;
signed int x;
signed int y;
<unnamed type> _20;
signed int _21;
signed int _38;
signed int _44;
signed int _51;
initial:
stack$0_45 = arg_5(D);
x_9 = stack$0_45;
stack$0_39 = x_9;
stack$1_32 = x_9;
stack$2_30 = 2;
y_17 = stack$2_30;
x_19 = stack$1_32;
_20 = x_19 < y_17;
_21 = (signed int) _20;
stack$1_28 = _21;
x_25 = stack$1_28;
if (x_25 != 0)
goto <bb 4> (instr9);
else
goto <bb 3> (instr4);
instr4:
/* DUP */:
x_27 = stack$0_39;
stack$0_22 = x_27;
stack$1_14 = x_27;
stack$2_12 = 1;
y_35 = stack$2_12;
x_37 = stack$1_14;
_38 = x_37 - y_35;
stack$1_10 = _38;
x_42 = stack$1_10;
_44 = factorial (x_42);
stack$1_6 = _44;
y_48 = stack$1_6;
x_50 = stack$0_22;
_51 = x_50 * y_48;
stack$0_1 = _51;
# stack$0_52 = PHI <stack$0_39(2), stack$0_1(3)>
instr9:
/* RETURN */:
x_55 = stack$0_52;
x_56 = x_55;
stack ={v} {CLOBBER};
return x_56;
}
Hence at this point, all those pushes and pops of the stack are now
simply assignments to specific temporary variables.
After some copy propagation, the stack manipulation has been completely
optimized away:
.. code-block:: console
$ less /tmp/libgccjit-1Hywc0/fake.c.026t.copyprop1
.. code-block:: c
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)
factorial (signed int arg)
{
signed int stack$2;
signed int stack$1;
signed int stack$0;
signed int stack[8];
signed int stack_depth;
signed int x;
signed int y;
<unnamed type> _20;
signed int _21;
signed int _38;
signed int _44;
signed int _51;
initial:
stack$0_39 = arg_5(D);
_20 = arg_5(D) <= 1;
_21 = (signed int) _20;
if (_21 != 0)
goto <bb 4> (instr9);
else
goto <bb 3> (instr4);
instr4:
/* DUP */:
_38 = arg_5(D) + -1;
_44 = factorial (_38);
_51 = arg_5(D) * _44;
stack$0_1 = _51;
# stack$0_52 = PHI <arg_5(D)(2), _51(3)>
instr9:
/* RETURN */:
stack ={v} {CLOBBER};
return stack$0_52;
}
Later on, another pass finally eliminated ``stack_depth`` local and the
unused parts of the `stack`` array altogether:
.. code-block:: console
$ less /tmp/libgccjit-1Hywc0/fake.c.036t.release_ssa
.. code-block:: c
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)
Released 44 names, 314.29%, removed 44 holes
factorial (signed int arg)
{
signed int stack$0;
signed int mult_acc_1;
<unnamed type> _5;
signed int _6;
signed int _7;
signed int mul_tmp_10;
signed int mult_acc_11;
signed int mult_acc_13;
# arg_9 = PHI <arg_8(D)(0)>
# mult_acc_13 = PHI <1(0)>
initial:
<bb 5>:
# arg_4 = PHI <arg_9(2), _7(3)>
# mult_acc_1 = PHI <mult_acc_13(2), mult_acc_11(3)>
_5 = arg_4 <= 1;
_6 = (signed int) _5;
if (_6 != 0)
goto <bb 4> (instr9);
else
goto <bb 3> (instr4);
instr4:
/* DUP */:
_7 = arg_4 + -1;
mult_acc_11 = mult_acc_1 * arg_4;
goto <bb 5>;
# stack$0_12 = PHI <arg_4(5)>
instr9:
/* RETURN */:
mul_tmp_10 = mult_acc_1 * stack$0_12;
return mul_tmp_10;
}
Elimination of tail recursion
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Another significant optimization is the detection that the call to
``factorial`` is tail recursion, which can be eliminated in favor of
an iteration:
.. code-block:: console
$ less /tmp/libgccjit-1Hywc0/fake.c.030t.tailr1
.. code-block:: c
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)
Symbols to be put in SSA form
{ D.88 }
Incremental SSA update started at block: 0
Number of blocks in CFG: 5
Number of blocks to update: 4 ( 80%)
factorial (signed int arg)
{
signed int stack$2;
signed int stack$1;
signed int stack$0;
signed int stack[8];
signed int stack_depth;
signed int x;
signed int y;
signed int mult_acc_1;
<unnamed type> _20;
signed int _21;
signed int _38;
signed int mul_tmp_44;
signed int mult_acc_51;
# arg_5 = PHI <arg_39(D)(0), _38(3)>
# mult_acc_1 = PHI <1(0), mult_acc_51(3)>
initial:
_20 = arg_5 <= 1;
_21 = (signed int) _20;
if (_21 != 0)
goto <bb 4> (instr9);
else
goto <bb 3> (instr4);
instr4:
/* DUP */:
_38 = arg_5 + -1;
mult_acc_51 = mult_acc_1 * arg_5;
goto <bb 2> (initial);
# stack$0_52 = PHI <arg_5(2)>
instr9:
/* RETURN */:
stack ={v} {CLOBBER};
mul_tmp_44 = mult_acc_1 * stack$0_52;
return mul_tmp_44;
}
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Compilation contexts
====================
.. class:: gccjit::context
The top-level of the C++ API is the :class:`gccjit::context` type.
A :class:`gccjit::context` instance encapsulates the state of a
compilation.
You can set up options on it, and add types, functions and code.
Invoking :func:`gccjit::context::compile` on it gives you a
:c:type:`gcc_jit_result *`.
It is a thin wrapper around the C API's :c:type:`gcc_jit_context *`.
Lifetime-management
-------------------
Contexts are the unit of lifetime-management within the API: objects
have their lifetime bounded by the context they are created within, and
cleanup of such objects is done for you when the context is released.
.. function:: gccjit::context gccjit::context::acquire ()
This function acquires a new :class:`gccjit::context` instance,
which is independent of any others that may be present within this
process.
.. function:: void gccjit::context::release ()
This function releases all resources associated with the given context.
Both the context itself and all of its :c:type:`gccjit::object *`
instances are cleaned up. It should be called exactly once on a given
context.
It is invalid to use the context or any of its "contextual" objects
after calling this.
.. code-block:: c++
ctxt.release ();
.. function:: gccjit::context \
gccjit::context::new_child_context ()
Given an existing JIT context, create a child context.
The child inherits a copy of all option-settings from the parent.
The child can reference objects created within the parent, but not
vice-versa.
The lifetime of the child context must be bounded by that of the
parent: you should release a child context before releasing the parent
context.
If you use a function from a parent context within a child context,
you have to compile the parent context before you can compile the
child context, and the gccjit::result of the parent context must
outlive the gccjit::result of the child context.
This allows caching of shared initializations. For example, you could
create types and declarations of global functions in a parent context
once within a process, and then create child contexts whenever a
function or loop becomes hot. Each such child context can be used for
JIT-compiling just one function or loop, but can reference types
and helper functions created within the parent context.
Contexts can be arbitrarily nested, provided the above rules are
followed, but it's probably not worth going above 2 or 3 levels, and
there will likely be a performance hit for such nesting.
Thread-safety
-------------
Instances of :class:`gccjit::context` created via
:func:`gccjit::context::acquire` are independent from each other:
only one thread may use a given context at once, but multiple threads
could each have their own contexts without needing locks.
Contexts created via :func:`gccjit::context::new_child_context` are
related to their parent context. They can be partitioned by their
ultimate ancestor into independent "family trees". Only one thread
within a process may use a given "family tree" of such contexts at once,
and if you're using multiple threads you should provide your own locking
around entire such context partitions.
Error-handling
--------------
.. FIXME: How does error-handling work for C++ API?
You can only compile and get code from a context if no errors occur.
In general, if an error occurs when using an API entrypoint, it returns
NULL. You don't have to check everywhere for NULL results, since the
API gracefully handles a NULL being passed in for any argument.
Errors are printed on stderr and can be queried using
:func:`gccjit::context::get_first_error`.
.. function:: const char *\
gccjit::context::get_first_error (gccjit::context *ctxt)
Returns the first error message that occurred on the context.
The returned string is valid for the rest of the lifetime of the
context.
If no errors occurred, this will be NULL.
Debugging
---------
.. function:: void\
gccjit::context::dump_to_file (const std::string &path, \
int update_locations)
To help with debugging: dump a C-like representation to the given path,
describing what's been set up on the context.
If "update_locations" is true, then also set up :class:`gccjit::location`
information throughout the context, pointing at the dump file as if it
were a source file. This may be of use in conjunction with
:c:macro:`GCCJIT::BOOL_OPTION_DEBUGINFO` to allow stepping through the
code in a debugger.
Options
-------
..
FIXME: gccjit::context::set_str_option doesn't seem to exist yet in the
C++ API
Boolean options
***************
.. function:: void \
gccjit::context::set_bool_option(enum gcc_jit_bool_option, \
int value)
Set a boolean option of the context.
This is a thin wrapper around the C API
:c:func:`gcc_jit_context_set_bool_option`; the options have the same
meaning.
Integer options
***************
.. function:: void \
gccjit::context::set_int_option (enum gcc_jit_int_option, \
int value)
Set an integer option of the context.
This is a thin wrapper around the C API
:c:func:`gcc_jit_context_set_int_option`; the options have the same
meaning.
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Expressions
===========
Rvalues
-------
.. class:: gccjit::rvalue
A :class:`gccjit::rvalue` is an expression that can be computed. It is a
subclass of :class:`gccjit::object`, and is a thin wrapper around
:c:type:`gcc_jit_rvalue *` from the C API.
It can be simple, e.g.:
* an integer value e.g. `0` or `42`
* a string literal e.g. `"Hello world"`
* a variable e.g. `i`. These are also lvalues (see below).
or compound e.g.:
* a unary expression e.g. `!cond`
* a binary expression e.g. `(a + b)`
* a function call e.g. `get_distance (&player_ship, &target)`
* etc.
Every rvalue has an associated type, and the API will check to ensure
that types match up correctly (otherwise the context will emit an error).
.. function:: gccjit::type gccjit::rvalue::get_type ()
Get the type of this rvalue.
Simple expressions
******************
.. function:: gccjit::rvalue \
gccjit::context::new_rvalue (gccjit::type numeric_type, \
int value) const
Given a numeric type (integer or floating point), build an rvalue for
the given constant ``int`` value.
.. function:: gccjit::rvalue \
gccjit::context::zero (gccjit::type numeric_type) const
Given a numeric type (integer or floating point), get the rvalue for
zero. Essentially this is just a shortcut for:
.. code-block:: c++
ctxt.new_rvalue (numeric_type, 0)
.. function:: gccjit::rvalue \
gccjit::context::one (gccjit::type numeric_type) const
Given a numeric type (integer or floating point), get the rvalue for
zero. Essentially this is just a shortcut for:
.. code-block:: c++
ctxt.new_rvalue (numeric_type, 1)
.. function:: gccjit::rvalue \
gccjit::context::new_rvalue (gccjit::type numeric_type, \
double value) const
Given a numeric type (integer or floating point), build an rvalue for
the given constant value.
.. function:: gccjit::rvalue \
gccjit::context::new_rvalue (gccjit::type pointer_type, \
void *value) const
Given a pointer type, build an rvalue for the given address.
.. function:: gccjit::rvalue \
gccjit::context::new_rvalue (const std::string &value) const
Generate an rvalue of type :c:data:`GCC_JIT_TYPE_CONST_CHAR_PTR` for
the given string. This is akin to a string literal.
Unary Operations
****************
.. function:: gccjit::rvalue \
gccjit::context::new_unary_op (enum gcc_jit_unary_op, \
gccjit::type result_type, \
gccjit::rvalue rvalue, \
gccjit::location loc)
Build a unary operation out of an input rvalue.
Parameter ``loc`` is optional.
This is a thin wrapper around the C API's
:c:func:`gcc_jit_context_new_unary_op` and the available unary
operations are documented there.
There are shorter ways to spell the various specific kinds of unary
operation:
.. function:: gccjit::rvalue \
gccjit::context::new_minus (gccjit::type result_type, \
gccjit::rvalue a, \
gccjit::location loc)
Negate an arithmetic value; for example:
.. code-block:: c++
gccjit::rvalue negpi = ctxt.new_minus (t_double, pi);
builds the equivalent of this C expression:
.. code-block:: c
-pi
.. function:: gccjit::rvalue \
new_bitwise_negate (gccjit::type result_type, \
gccjit::rvalue a, \
gccjit::location loc)
Bitwise negation of an integer value (one's complement); for example:
.. code-block:: c++
gccjit::rvalue mask = ctxt.new_bitwise_negate (t_int, a);
builds the equivalent of this C expression:
.. code-block:: c
~a
.. function:: gccjit::rvalue \
new_logical_negate (gccjit::type result_type, \
gccjit::rvalue a, \
gccjit::location loc)
Logical negation of an arithmetic or pointer value; for example:
.. code-block:: c++
gccjit::rvalue guard = ctxt.new_logical_negate (t_bool, cond);
builds the equivalent of this C expression:
.. code-block:: c
!cond
The most concise way to spell them is with overloaded operators:
.. function:: gccjit::rvalue operator- (gccjit::rvalue a)
.. code-block:: c++
gccjit::rvalue negpi = -pi;
.. function:: gccjit::rvalue operator~ (gccjit::rvalue a)
.. code-block:: c++
gccjit::rvalue mask = ~a;
.. function:: gccjit::rvalue operator! (gccjit::rvalue a)
.. code-block:: c++
gccjit::rvalue guard = !cond;
Binary Operations
*****************
.. function:: gccjit::rvalue\
gccjit::context::new_binary_op (enum gcc_jit_binary_op, \
gccjit::type result_type, \
gccjit::rvalue a, \
gccjit::rvalue b, \
gccjit::location loc)
Build a binary operation out of two constituent rvalues.
Parameter ``loc`` is optional.
This is a thin wrapper around the C API's
:c:func:`gcc_jit_context_new_binary_op` and the available binary
operations are documented there.
There are shorter ways to spell the various specific kinds of binary
operation:
.. function:: gccjit::rvalue \
gccjit::context::new_plus (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_minus (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_mult (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_divide (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_modulo (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_bitwise_and (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_bitwise_xor (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_bitwise_or (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_logical_and (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_logical_or (gccjit::type result_type, \
gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
The most concise way to spell them is with overloaded operators:
.. function:: gccjit::rvalue operator+ (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue sum = a + b;
.. function:: gccjit::rvalue operator- (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue diff = a - b;
.. function:: gccjit::rvalue operator* (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue prod = a * b;
.. function:: gccjit::rvalue operator/ (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue result = a / b;
.. function:: gccjit::rvalue operator% (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue mod = a % b;
.. function:: gccjit::rvalue operator& (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue x = a & b;
.. function:: gccjit::rvalue operator^ (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue x = a ^ b;
.. function:: gccjit::rvalue operator| (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue x = a | b;
.. function:: gccjit::rvalue operator&& (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = a && b;
.. function:: gccjit::rvalue operator|| (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = a || b;
These can of course be combined, giving a terse way to build compound
expressions:
.. code-block:: c++
gccjit::rvalue discriminant = (b * b) - (four * a * c);
Comparisons
***********
.. function:: gccjit::rvalue \
gccjit::context::new_comparison (enum gcc_jit_comparison,\
gccjit::rvalue a, \
gccjit::rvalue b, \
gccjit::location loc)
Build a boolean rvalue out of the comparison of two other rvalues.
Parameter ``loc`` is optional.
This is a thin wrapper around the C API's
:c:func:`gcc_jit_context_new_comparison` and the available kinds
of comparison are documented there.
There are shorter ways to spell the various specific kinds of binary
operation:
.. function:: gccjit::rvalue \
gccjit::context::new_eq (gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_ne (gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_lt (gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_le (gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_gt (gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
.. function:: gccjit::rvalue \
gccjit::context::new_ge (gccjit::rvalue a, gccjit::rvalue b, \
gccjit::location loc)
The most concise way to spell them is with overloaded operators:
.. function:: gccjit::rvalue \
operator== (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = (a == ctxt.zero (t_int));
.. function:: gccjit::rvalue \
operator!= (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = (i != j);
.. function:: gccjit::rvalue \
operator< (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = i < n;
.. function:: gccjit::rvalue \
operator<= (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = i <= n;
.. function:: gccjit::rvalue \
operator> (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = (ch > limit);
.. function:: gccjit::rvalue \
operator>= (gccjit::rvalue a, gccjit::rvalue b)
.. code-block:: c++
gccjit::rvalue cond = (score >= ctxt.new_rvalue (t_int, 100));
.. TODO: beyond this point
Function calls
**************
.. function:: gcc_jit_rvalue *\
gcc_jit_context_new_call (gcc_jit_context *ctxt,\
gcc_jit_location *loc,\
gcc_jit_function *func,\
int numargs , gcc_jit_rvalue **args)
Given a function and the given table of argument rvalues, construct a
call to the function, with the result as an rvalue.
.. note::
:func:`gccjit::context::new_call` merely builds a
:class:`gccjit::rvalue` i.e. an expression that can be evaluated,
perhaps as part of a more complicated expression.
The call *won't* happen unless you add a statement to a function
that evaluates the expression.
For example, if you want to call a function and discard the result
(or to call a function with ``void`` return type), use
:func:`gccjit::block::add_eval`:
.. code-block:: c++
/* Add "(void)printf (arg0, arg1);". */
block.add_eval (ctxt.new_call (printf_func, arg0, arg1));
Type-coercion
*************
.. function:: gccjit::rvalue \
gccjit::context::new_cast (gccjit::rvalue rvalue,\
gccjit::type type, \
gccjit::location loc)
Given an rvalue of T, construct another rvalue of another type.
Currently only a limited set of conversions are possible:
* int <-> float
* int <-> bool
* P* <-> Q*, for pointer types P and Q
Lvalues
-------
.. class:: gccjit::lvalue
An lvalue is something that can of the *left*-hand side of an assignment:
a storage area (such as a variable). It is a subclass of
:class:`gccjit::rvalue`, where the rvalue is computed by reading from the
storage area.
It iss a thin wrapper around :c:type:`gcc_jit_lvalue *` from the C API.
.. function:: gccjit::rvalue \
gccjit::lvalue::get_address (gccjit::location loc)
Take the address of an lvalue; analogous to:
.. code-block:: c
&(EXPR)
in C.
Parameter "loc" is optional.
Global variables
****************
.. function:: gccjit::lvalue \
gccjit::context::new_global (gccjit::type type, \
const char *name, \
gccjit::location loc)
Add a new global variable of the given type and name to the context.
Working with pointers, structs and unions
-----------------------------------------
.. function:: gccjit::lvalue \
gccjit::rvalue::dereference (gccjit::location loc)
Given an rvalue of pointer type ``T *``, dereferencing the pointer,
getting an lvalue of type ``T``. Analogous to:
.. code-block:: c++
*(EXPR)
in C.
Parameter "loc" is optional.
If you don't need to specify the location, this can also be expressed using
an overloaded operator:
.. function:: gccjit::lvalue \
gccjit::rvalue::operator* ();
.. code-block:: c++
gccjit::lvalue content = *ptr;
Field access is provided separately for both lvalues and rvalues:
.. function:: gccjit::lvalue \
gccjit::lvalue::access_field (gccjit::field field, \
gccjit::location loc)
Given an lvalue of struct or union type, access the given field,
getting an lvalue of the field's type. Analogous to:
.. code-block:: c++
(EXPR).field = ...;
in C.
.. function:: gccjit::rvalue \
gccjit::rvalue::access_field (gccjit::field field, \
gccjit::location loc)
Given an rvalue of struct or union type, access the given field
as an rvalue. Analogous to:
.. code-block:: c++
(EXPR).field
in C.
.. function:: gccjit::lvalue \
gccjit::rvalue::dereference_field (gccjit::field field, \
gccjit::location loc)
Given an rvalue of pointer type ``T *`` where T is of struct or union
type, access the given field as an lvalue. Analogous to:
.. code-block:: c++
(EXPR)->field
in C, itself equivalent to ``(*EXPR).FIELD``.
.. function:: gccjit::lvalue \
gccjit::context::new_array_access (gccjit::rvalue ptr, \
gccjit::rvalue index, \
gccjit::location loc)
Given an rvalue of pointer type ``T *``, get at the element `T` at
the given index, using standard C array indexing rules i.e. each
increment of ``index`` corresponds to ``sizeof(T)`` bytes.
Analogous to:
.. code-block:: c++
PTR[INDEX]
in C (or, indeed, to ``PTR + INDEX``).
Parameter "loc" is optional.
For array accesses where you don't need to specify a :class:`gccjit::location`,
two overloaded operators are available:
gccjit::lvalue gccjit::rvalue::operator[] (gccjit::rvalue index)
.. code-block:: c++
gccjit::lvalue element = array[idx];
gccjit::lvalue gccjit::rvalue::operator[] (int index)
.. code-block:: c++
gccjit::lvalue element = array[0];
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Creating and using functions
============================
Params
------
.. class:: gccjit::param
A `gccjit::param` represents a parameter to a function.
.. function:: gccjit::param \
gccjit::context::new_param (gccjit::type type,\
const char *name, \
gccjit::location loc)
In preparation for creating a function, create a new parameter of the
given type and name.
:class:`gccjit::param` is a subclass of :class:`gccjit::lvalue` (and thus
of :class:`gccjit::rvalue` and :class:`gccjit::object`). It is a thin
wrapper around the C API's :c:type:`gcc_jit_param *`.
Functions
---------
.. class:: gccjit::function
A `gccjit::function` represents a function - either one that we're
creating ourselves, or one that we're referencing.
.. function:: gccjit::function \
gccjit::context::new_function (enum gcc_jit_function_kind,\
gccjit::type return_type, \
const char *name, \
std::vector<param> &params, \
int is_variadic, \
gccjit::location loc) \
Create a gcc_jit_function with the given name and parameters.
Parameters "is_variadic" and "loc" are optional.
This is a wrapper around the C API's :c:func:`gcc_jit_context_new_function`.
.. function:: gccjit::function \
gccjit::context::get_builtin_function (const char *name)
This is a wrapper around the C API's
:c:func:`gcc_jit_context_get_builtin_function`.
.. function:: gccjit::param \
gccjit::function::get_param (int index) const
Get the param of the given index (0-based).
.. function:: void \
gccjit::function::dump_to_dot (const char *path)
Emit the function in graphviz format to the given path.
.. function:: gccjit::lvalue \
gccjit::function::new_local (gccjit::type type,\
const char *name, \
gccjit::location loc)
Create a new local variable within the function, of the given type and
name.
Blocks
------
.. class:: gccjit::block
A `gccjit::block` represents a basic block within a function i.e. a
sequence of statements with a single entry point and a single exit
point.
:class:`gccjit::block` is a subclass of :class:`gccjit::object`.
The first basic block that you create within a function will
be the entrypoint.
Each basic block that you create within a function must be
terminated, either with a conditional, a jump, or a return.
It's legal to have multiple basic blocks that return within
one function.
.. function:: gccjit::block \
gccjit::function::new_block (const char *name)
Create a basic block of the given name. The name may be NULL, but
providing meaningful names is often helpful when debugging: it may
show up in dumps of the internal representation, and in error
messages.
Statements
----------
.. function:: void\
gccjit::block::add_eval (gccjit::rvalue rvalue, \
gccjit::location loc)
Add evaluation of an rvalue, discarding the result
(e.g. a function call that "returns" void).
This is equivalent to this C code:
.. code-block:: c
(void)expression;
.. function:: void\
gccjit::block::add_assignment (gccjit::lvalue lvalue, \
gccjit::rvalue rvalue, \
gccjit::location loc)
Add evaluation of an rvalue, assigning the result to the given
lvalue.
This is roughly equivalent to this C code:
.. code-block:: c
lvalue = rvalue;
.. function:: void\
gccjit::block::add_assignment_op (gccjit::lvalue lvalue, \
enum gcc_jit_binary_op, \
gccjit::rvalue rvalue, \
gccjit::location loc)
Add evaluation of an rvalue, using the result to modify an
lvalue.
This is analogous to "+=" and friends:
.. code-block:: c
lvalue += rvalue;
lvalue *= rvalue;
lvalue /= rvalue;
etc. For example:
.. code-block:: c
/* "i++" */
loop_body.add_assignment_op (
i,
GCC_JIT_BINARY_OP_PLUS,
ctxt.one (int_type));
.. function:: void\
gccjit::block::add_comment (const char *text, \
gccjit::location loc)
Add a no-op textual comment to the internal representation of the
code. It will be optimized away, but will be visible in the dumps
seen via :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE`
and :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE`,
and thus may be of use when debugging how your project's internal
representation gets converted to the libgccjit IR.
Parameter "loc" is optional.
.. function:: void\
gccjit::block::end_with_conditional (gccjit::rvalue boolval,\
gccjit::block on_true,\
gccjit::block on_false, \
gccjit::location loc)
Terminate a block by adding evaluation of an rvalue, branching on the
result to the appropriate successor block.
This is roughly equivalent to this C code:
.. code-block:: c
if (boolval)
goto on_true;
else
goto on_false;
block, boolval, on_true, and on_false must be non-NULL.
.. function:: void\
gccjit::block::end_with_jump (gccjit::block target, \
gccjit::location loc)
Terminate a block by adding a jump to the given target block.
This is roughly equivalent to this C code:
.. code-block:: c
goto target;
.. function:: void\
gccjit::block::end_with_return (gccjit::rvalue rvalue, \
gccjit::location loc)
Terminate a block.
Both params are optional.
An rvalue must be provided for a function returning non-void, and
must not be provided by a function "returning" `void`.
If an rvalue is provided, the block is terminated by evaluating the
rvalue and returning the value.
This is roughly equivalent to this C code:
.. code-block:: c
return expression;
If an rvalue is not provided, the block is terminated by adding a
valueless return, for use within a function with "void" return type.
This is equivalent to this C code:
.. code-block:: c
return;
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
Topic Reference
===============
.. toctree::
:maxdepth: 2
contexts.rst
objects.rst
types.rst
expressions.rst
functions.rst
locations.rst
results.rst
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Source Locations
================
.. class:: gccjit::location
A `gccjit::location` encapsulates a source code location, so that
you can (optionally) associate locations in your language with
statements in the JIT-compiled code, allowing the debugger to
single-step through your language.
`gccjit::location` instances are optional: you can always omit them
from any C++ API entrypoint accepting one.
You can construct them using :func:`gccjit::context::new_location`.
You need to enable :c:macro:`GCC_JIT_BOOL_OPTION_DEBUGINFO` on the
:class:`gccjit::context` for these locations to actually be usable by
the debugger:
.. code-block:: cpp
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO, 1);
.. function:: gccjit::location \
gccjit::context::new_location (const char *filename, \
int line, \
int column)
Create a `gccjit::location` instance representing the given source
location.
Faking it
---------
If you don't have source code for your internal representation, but need
to debug, you can generate a C-like representation of the functions in
your context using :func:`gccjit::context::dump_to_file()`:
.. code-block:: cpp
ctxt.dump_to_file ("/tmp/something.c",
1 /* update_locations */);
This will dump C-like code to the given path. If the `update_locations`
argument is true, this will also set up `gccjit::location` information
throughout the context, pointing at the dump file as if it were a source
file, giving you *something* you can step through in the debugger.
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Objects
=======
.. class:: gccjit::object
Almost every entity in the API (with the exception of
:class:`gccjit::context` and :c:type:`gcc_jit_result *`) is a
"contextual" object, a :class:`gccjit::object`.
A JIT object:
* is associated with a :class:`gccjit::context`.
* is automatically cleaned up for you when its context is released so
you don't need to manually track and cleanup all objects, just the
contexts.
The C++ class hierarchy within the ``gccjit`` namespace looks like this::
+- object
+- location
+- type
+- struct
+- field
+- function
+- block
+- rvalue
+- lvalue
+- param
The :class:`gccjit::object` base class has the following operations:
.. function:: gccjit::context gccjit::object::get_context () const
Which context is the obj within?
.. function:: std::string gccjit::object::get_debug_string () const
Generate a human-readable description for the given object.
For example,
.. code-block:: c++
printf ("obj: %s\n", obj.get_debug_string ().c_str ());
might give this text on stdout:
.. code-block:: bash
obj: 4.0 * (float)i
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Compilation results
===================
.. type:: gcc_jit_result
A `gcc_jit_result` encapsulates the result of compiling a context.
.. function:: gcc_jit_result *\
gccjit::context::compile ()
This calls into GCC and builds the code, returning a
`gcc_jit_result *`.
.. function:: void *\
gcc_jit_result_get_code (gcc_jit_result *result,\
const char *funcname)
Locate a given function within the built machine code.
This will need to be cast to a function pointer of the
correct type before it can be called.
.. function:: void\
gcc_jit_result_release (gcc_jit_result *result)
Once we're done with the code, this unloads the built .so file.
This cleans up the result; after calling this, it's no longer
valid to use the result.
.. Copyright (C) 2014 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
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
<http://www.gnu.org/licenses/>.
.. default-domain:: cpp
Types
=====
.. class:: gccjit::type
gccjit::type represents a type within the library. It is a subclass
of :class:`gccjit::object`.
Types can be created in several ways:
* fundamental types can be accessed using
:func:`gccjit::context::get_type`:
.. code-block:: c++
gccjit::type int_type = ctxt.get_type (GCC_JIT_TYPE_INT);
or using the :func:`gccjit::context::get_int_type<T>` template:
.. code-block:: c++
gccjit::type t = ctxt.get_int_type <unsigned short> ();
See :c:func:`gcc_jit_context_get_type` for the available types.
* derived types can be accessed by using functions such as
:func:`gccjit::type::get_pointer` and :func:`gccjit::type::get_const`:
.. code-block:: c++
gccjit::type const_int_star = int_type.get_const ().get_pointer ();
gccjit::type int_const_star = int_type.get_pointer ().get_const ();
* by creating structures (see below).
Standard types
--------------
.. function:: gccjit::type gccjit::context::get_type (enum gcc_jit_types)
Access a specific type. This is a thin wrapper around
:c:func:`gcc_jit_context_get_type`; the parameter has the same meaning.
.. function:: gccjit::type \
gccjit::context::get_int_type (size_t num_bytes, int is_signed)
Access the integer type of the given size.
.. function:: gccjit::type \
gccjit::context::get_int_type <T> ()
Access the given integer type. For example, you could map the
``unsigned short`` type into a gccjit::type via:
.. code-block:: c++
gccjit::type t = ctxt.get_int_type <unsigned short> ();
Pointers, `const`, and `volatile`
---------------------------------
.. function:: gccjit::type gccjit::type::get_pointer ()
Given type "T", get type "T*".
.. FIXME: get_const doesn't seem to exist
.. function:: gccjit::type gccjit::type::get_const ()
Given type "T", get type "const T".
.. function:: gccjit::type gccjit::type::get_volatile ()
Given type "T", get type "volatile T".
.. function:: gccjit::type \
gccjit::context::new_array_type (gccjit::type element_type, \
int num_elements, \
gccjit::location loc)
Given type "T", get type "T[N]" (for a constant N).
Param "loc" is optional.
Structures and unions
---------------------
.. class:: gccjit::struct_
A compound type analagous to a C `struct`.
:class:`gccjit::struct_` is a subclass of :class:`gccjit::type` (and thus
of :class:`gccjit::object` in turn).
.. class:: gccjit::field
A field within a :class:`gccjit::struct_`.
:class:`gccjit::field` is a subclass of :class:`gccjit::object`.
You can model C `struct` types by creating :class:`gccjit::struct_` and
:class:`gccjit::field` instances, in either order:
* by creating the fields, then the structure. For example, to model:
.. code-block:: c
struct coord {double x; double y; };
you could call:
.. code-block:: c++
gccjit::field field_x = ctxt.new_field (double_type, "x");
gccjit::field field_y = ctxt.new_field (double_type, "y");
std::vector fields;
fields.push_back (field_x);
fields.push_back (field_y);
gccjit::struct_ coord = ctxt.new_struct_type ("coord", fields);
* by creating the structure, then populating it with fields, typically
to allow modelling self-referential structs such as:
.. code-block:: c
struct node { int m_hash; struct node *m_next; };
like this:
.. code-block:: c++
gccjit::struct_ node = ctxt.new_opaque_struct_type ("node");
gccjit::type node_ptr = node.get_pointer ();
gccjit::field field_hash = ctxt.new_field (int_type, "m_hash");
gccjit::field field_next = ctxt.new_field (node_ptr, "m_next");
std::vector fields;
fields.push_back (field_hash);
fields.push_back (field_next);
node.set_fields (fields);
.. FIXME: the above API doesn't seem to exist yet
.. function:: gccjit::field \
gccjit::context::new_field (gccjit::type type,\
const char *name, \
gccjit::location loc)
Construct a new field, with the given type and name.
.. function:: gccjit::struct_ \
gccjit::context::new_struct_type (const std::string &name,\
std::vector<field> &fields,\
gccjit::location loc)
Construct a new struct type, with the given name and fields.
.. function:: gccjit::struct_ \
gccjit::context::new_opaque_struct (const std::string &name, \
gccjit::location loc)
Construct a new struct type, with the given name, but without
specifying the fields. The fields can be omitted (in which case the
size of the struct is not known), or later specified using
:c:func:`gcc_jit_struct_set_fields`.
/* Smoketest example for libgccjit.so C++ API
Copyright (C) 2014 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>
static void
create_code (gccjit::context ctxt)
{
/* Let's try to inject the equivalent of this C code:
void
greet (const char *name)
{
printf ("hello %s\n", name);
}
*/
gccjit::type void_type = ctxt.get_type (GCC_JIT_TYPE_VOID);
gccjit::type const_char_ptr_type =
ctxt.get_type (GCC_JIT_TYPE_CONST_CHAR_PTR);
gccjit::param param_name =
ctxt.new_param (const_char_ptr_type, "name");
std::vector<gccjit::param> func_params;
func_params.push_back (param_name);
gccjit::function func =
ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
void_type,
"greet",
func_params, 0);
gccjit::param param_format =
ctxt.new_param (const_char_ptr_type, "format");
std::vector<gccjit::param> printf_params;
printf_params.push_back (param_format);
gccjit::function printf_func =
ctxt.new_function (GCC_JIT_FUNCTION_IMPORTED,
ctxt.get_type (GCC_JIT_TYPE_INT),
"printf",
printf_params, 1);
gccjit::block block = func.new_block ();
block.add_eval (ctxt.new_call (printf_func,
ctxt.new_rvalue ("hello %s\n"),
param_name));
block.end_with_return ();
}
int
main (int argc, char **argv)
{
gccjit::context ctxt;
gcc_jit_result *result;
/* 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 ();
if (!result)
{
fprintf (stderr, "NULL result");
exit (1);
}
ctxt.release ();
/* Extract the generated code from "result". */
typedef void (*fn_type) (const char *);
fn_type greet =
(fn_type)gcc_jit_result_get_code (result, "greet");
if (!greet)
{
fprintf (stderr, "NULL greet");
exit (1);
}
/* Now call the generated function: */
greet ("world");
fflush (stdout);
gcc_jit_result_release (result);
return 0;
}
...@@ -102,7 +102,7 @@ main (int argc, char **argv) ...@@ -102,7 +102,7 @@ main (int argc, char **argv)
typedef int (*fn_type) (int); typedef int (*fn_type) (int);
fn_type square = (fn_type)fn_ptr; fn_type square = (fn_type)fn_ptr;
printf ("result: %d", square (5)); printf ("result: %d\n", square (5));
error: error:
if (ctxt) if (ctxt)
......
/* Usage example for libgccjit.so's C++ API
Copyright (C) 2014 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)
{
/* Let's try to inject the equivalent of this C code:
int square (int i)
{
return i * i;
}
*/
gccjit::type int_type = ctxt.get_type (GCC_JIT_TYPE_INT);
gccjit::param param_i = ctxt.new_param (int_type, "i");
std::vector<gccjit::param> params;
params.push_back (param_i);
gccjit::function func = ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
int_type,
"square",
params, 0);
gccjit::block block = func.new_block ();
gccjit::rvalue expr =
ctxt.new_binary_op (GCC_JIT_BINARY_OP_MULT, int_type,
param_i, param_i);
block.end_with_return (expr);
}
int
main (int argc, char **argv)
{
/* Get a "context" object for working with the library. */
gccjit::context 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. */
gcc_jit_result *result = ctxt.compile ();
/* We're done with the context; we can release it: */
ctxt.release ();
if (!result)
{
fprintf (stderr, "NULL result");
return 1;
}
/* Extract the generated code from "result". */
void *fn_ptr = gcc_jit_result_get_code (result, "square");
if (!fn_ptr)
{
fprintf (stderr, "NULL fn_ptr");
gcc_jit_result_release (result);
return 1;
}
typedef int (*fn_type) (int);
fn_type square = (fn_type)fn_ptr;
printf ("result: %d\n", square (5));
gcc_jit_result_release (result);
return 0;
}
/* Usage example for libgccjit.so's C++ API
Copyright (C) 2014 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;
}
/* A simple stack-based virtual machine to demonstrate
JIT-compilation.
Copyright (C) 2014 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 <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dejagnu.h>
#include <libgccjit++.h>
/* Functions are compiled to this function ptr type. */
typedef int (*toyvm_compiled_func) (int);
enum opcode {
/* Ops taking no operand. */
DUP,
ROT,
BINARY_ADD,
BINARY_SUBTRACT,
BINARY_MULT,
BINARY_COMPARE_LT,
RECURSE,
RETURN,
/* Ops taking an operand. */
PUSH_CONST,
JUMP_ABS_IF_TRUE
};
#define FIRST_UNARY_OPCODE (PUSH_CONST)
const char * const opcode_names[] = {
"DUP",
"ROT",
"BINARY_ADD",
"BINARY_SUBTRACT",
"BINARY_MULT",
"BINARY_COMPARE_LT",
"RECURSE",
"RETURN",
"PUSH_CONST",
"JUMP_ABS_IF_TRUE",
};
struct toyvm_op
{
/* Which operation. */
enum opcode op_opcode;
/* Some opcodes take an argument. */
int op_operand;
/* The line number of the operation within the source file. */
int op_linenum;
};
#define MAX_OPS (64)
class toyvm_function
{
public:
void
add_op (enum opcode opcode,
int operand, int linenum);
void
add_unary_op (enum opcode opcode,
const char *rest_of_line, int linenum);
static toyvm_function *
parse (const char *filename, const char *name);
void
disassemble_op (toyvm_op *op, int index, FILE *out);
void
disassemble (FILE *out);
int
interpret (int arg, FILE *trace);
toyvm_compiled_func
compile ();
private:
const char *fn_filename;
int fn_num_ops;
toyvm_op fn_ops[MAX_OPS];
friend struct compilation_state;
};
#define MAX_STACK_DEPTH (8)
class toyvm_frame
{
public:
void push (int arg);
int pop ();
void dump_stack (FILE *out);
private:
toyvm_function *frm_function;
int frm_pc;
int frm_stack[MAX_STACK_DEPTH];
int frm_cur_depth;
friend int toyvm_function::interpret (int arg, FILE *trace);
};
void
toyvm_function::add_op (enum opcode opcode,
int operand, int linenum)
{
toyvm_op *op;
assert (fn_num_ops < MAX_OPS);
op = &fn_ops[fn_num_ops++];
op->op_opcode = opcode;
op->op_operand = operand;
op->op_linenum = linenum;
}
void
toyvm_function::add_unary_op (enum opcode opcode,
const char *rest_of_line, int linenum)
{
int operand = atoi (rest_of_line);
add_op (opcode, operand, linenum);
}
static char *
get_function_name (const char *filename)
{
/* Skip any path separators. */
const char *pathsep = strrchr (filename, '/');
if (pathsep)
filename = pathsep + 1;
/* Copy filename to funcname. */
char *funcname = (char *)malloc (strlen (filename) + 1);
strcpy (funcname, filename);
/* Convert "." to NIL terminator. */
*(strchr (funcname, '.')) = '\0';
return funcname;
}
toyvm_function *
toyvm_function::parse (const char *filename, const char *name)
{
FILE *f = NULL;
toyvm_function *fn = NULL;
char *line = NULL;
ssize_t linelen;
size_t bufsize;
int linenum = 0;
assert (filename);
assert (name);
f = fopen (filename, "r");
if (!f)
{
fprintf (stderr,
"cannot open file %s: %s\n",
filename, strerror (errno));
goto error;
}
fn = (toyvm_function *)calloc (1, sizeof (toyvm_function));
if (!fn)
{
fprintf (stderr, "out of memory allocating toyvm_function\n");
goto error;
}
fn->fn_filename = filename;
/* Read the lines of the file. */
while ((linelen = getline (&line, &bufsize, f)) != -1)
{
/* Note that this is a terrible parser, but it avoids the need to
bring in lex/yacc as a dependency. */
linenum++;
if (0)
fprintf (stdout, "%3d: %s", linenum, line);
/* Lines beginning with # are comments. */
if (line[0] == '#')
continue;
/* Skip blank lines. */
if (line[0] == '\n')
continue;
#define LINE_MATCHES(OPCODE) (0 == strncmp ((OPCODE), line, strlen (OPCODE)))
if (LINE_MATCHES ("DUP\n"))
fn->add_op (DUP, 0, linenum);
else if (LINE_MATCHES ("ROT\n"))
fn->add_op (ROT, 0, linenum);
else if (LINE_MATCHES ("BINARY_ADD\n"))
fn->add_op (BINARY_ADD, 0, linenum);
else if (LINE_MATCHES ("BINARY_SUBTRACT\n"))
fn->add_op (BINARY_SUBTRACT, 0, linenum);
else if (LINE_MATCHES ("BINARY_MULT\n"))
fn->add_op (BINARY_MULT, 0, linenum);
else if (LINE_MATCHES ("BINARY_COMPARE_LT\n"))
fn->add_op (BINARY_COMPARE_LT, 0, linenum);
else if (LINE_MATCHES ("RECURSE\n"))
fn->add_op (RECURSE, 0, linenum);
else if (LINE_MATCHES ("RETURN\n"))
fn->add_op (RETURN, 0, linenum);
else if (LINE_MATCHES ("PUSH_CONST "))
fn->add_unary_op (PUSH_CONST,
line + strlen ("PUSH_CONST "), linenum);
else if (LINE_MATCHES ("JUMP_ABS_IF_TRUE "))
fn->add_unary_op (JUMP_ABS_IF_TRUE,
line + strlen("JUMP_ABS_IF_TRUE "), linenum);
else
{
fprintf (stderr, "%s:%d: parse error\n", filename, linenum);
free (fn);
fn = NULL;
goto error;
}
#undef LINE_MATCHES
}
free (line);
fclose (f);
return fn;
error:
free (line);
if (f)
fclose (f);
free (fn);
return NULL;
}
void
toyvm_function::disassemble_op (toyvm_op *op, int index, FILE *out)
{
fprintf (out, "%s:%d: index %d: %s",
fn_filename, op->op_linenum, index,
opcode_names[op->op_opcode]);
if (op->op_opcode >= FIRST_UNARY_OPCODE)
fprintf (out, " %d", op->op_operand);
fprintf (out, "\n");
}
void
toyvm_function::disassemble (FILE *out)
{
int i;
for (i = 0; i < fn_num_ops; i++)
{
toyvm_op *op = &fn_ops[i];
disassemble_op (op, i, out);
}
}
void
toyvm_frame::push (int arg)
{
assert (frm_cur_depth < MAX_STACK_DEPTH);
frm_stack[frm_cur_depth++] = arg;
}
int
toyvm_frame::pop ()
{
assert (frm_cur_depth > 0);
return frm_stack[--frm_cur_depth];
}
void
toyvm_frame::dump_stack (FILE *out)
{
int i;
fprintf (out, "stack:");
for (i = 0; i < frm_cur_depth; i++)
{
fprintf (out, " %d", frm_stack[i]);
}
fprintf (out, "\n");
}
/* Execute the given function. */
int
toyvm_function::interpret (int arg, FILE *trace)
{
toyvm_frame frame;
#define PUSH(ARG) (frame.push (ARG))
#define POP(ARG) (frame.pop ())
frame.frm_function = this;
frame.frm_pc = 0;
frame.frm_cur_depth = 0;
PUSH (arg);
while (1)
{
toyvm_op *op;
int x, y;
assert (frame.frm_pc < fn_num_ops);
op = &fn_ops[frame.frm_pc++];
if (trace)
{
frame.dump_stack (trace);
disassemble_op (op, frame.frm_pc, trace);
}
switch (op->op_opcode)
{
/* Ops taking no operand. */
case DUP:
x = POP ();
PUSH (x);
PUSH (x);
break;
case ROT:
y = POP ();
x = POP ();
PUSH (y);
PUSH (x);
break;
case BINARY_ADD:
y = POP ();
x = POP ();
PUSH (x + y);
break;
case BINARY_SUBTRACT:
y = POP ();
x = POP ();
PUSH (x - y);
break;
case BINARY_MULT:
y = POP ();
x = POP ();
PUSH (x * y);
break;
case BINARY_COMPARE_LT:
y = POP ();
x = POP ();
PUSH (x < y);
break;
case RECURSE:
x = POP ();
x = interpret (x, trace);
PUSH (x);
break;
case RETURN:
return POP ();
/* Ops taking an operand. */
case PUSH_CONST:
PUSH (op->op_operand);
break;
case JUMP_ABS_IF_TRUE:
x = POP ();
if (x)
frame.frm_pc = op->op_operand;
break;
default:
assert (0); /* unknown opcode */
} /* end of switch on opcode */
} /* end of while loop */
#undef PUSH
#undef POP
}
/* JIT compilation. */
class compilation_state
{
public:
compilation_state (toyvm_function &toyvmfn) :
toyvmfn (toyvmfn)
{}
void create_context ();
void create_types ();
void create_locations ();
void create_function (const char *funcname);
gcc_jit_result *compile ();
private:
void
add_push (gccjit::block block,
gccjit::rvalue rvalue,
gccjit::location loc);
void
add_pop (gccjit::block block,
gccjit::lvalue lvalue,
gccjit::location loc);
private:
/* State. */
toyvm_function &toyvmfn;
gccjit::context ctxt;
gccjit::type int_type;
gccjit::type bool_type;
gccjit::type stack_type; /* int[MAX_STACK_DEPTH] */
gccjit::rvalue const_one;
gccjit::function fn;
gccjit::param param_arg;
gccjit::lvalue stack;
gccjit::lvalue stack_depth;
gccjit::lvalue x;
gccjit::lvalue y;
gccjit::location op_locs[MAX_OPS];
gccjit::block initial_block;
gccjit::block op_blocks[MAX_OPS];
};
/* The main compilation hook. */
toyvm_compiled_func
toyvm_function::compile ()
{
compilation_state state (*this);
char *funcname;
funcname = get_function_name (fn_filename);
state.create_context ();
state.create_types ();
state.create_locations ();
state.create_function (funcname);
/* We've now finished populating the context. Compile it. */
gcc_jit_result *result = state.compile ();
return (toyvm_compiled_func)gcc_jit_result_get_code (result, funcname);
/* (this leaks "result" and "funcname") */
}
/* Stack manipulation. */
void
compilation_state::add_push (gccjit::block block,
gccjit::rvalue rvalue,
gccjit::location loc)
{
/* stack[stack_depth] = RVALUE */
block.add_assignment (
/* stack[stack_depth] */
ctxt.new_array_access (
stack,
stack_depth,
loc),
rvalue,
loc);
/* "stack_depth++;". */
block.add_assignment_op (
stack_depth,
GCC_JIT_BINARY_OP_PLUS,
const_one,
loc);
}
void
compilation_state::add_pop (gccjit::block block,
gccjit::lvalue lvalue,
gccjit::location loc)
{
/* "--stack_depth;". */
block.add_assignment_op (
stack_depth,
GCC_JIT_BINARY_OP_MINUS,
const_one,
loc);
/* "LVALUE = stack[stack_depth];". */
block.add_assignment (
lvalue,
/* stack[stack_depth] */
ctxt.new_array_access (stack,
stack_depth,
loc),
loc);
}
/* Create the context. */
void
compilation_state::create_context ()
{
ctxt = gccjit::context::acquire ();
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
0);
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
0);
ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
3);
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES,
0);
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING,
0);
ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO,
1);
}
/* Create types. */
void
compilation_state::create_types ()
{
/* Create types. */
int_type = ctxt.get_type (GCC_JIT_TYPE_INT);
bool_type = ctxt.get_type (GCC_JIT_TYPE_BOOL);
stack_type = ctxt.new_array_type (int_type, MAX_STACK_DEPTH);
/* The constant value 1. */
const_one = ctxt.one (int_type);
}
/* Create locations. */
void
compilation_state::create_locations ()
{
for (int pc = 0; pc < toyvmfn.fn_num_ops; pc++)
{
toyvm_op *op = &toyvmfn.fn_ops[pc];
op_locs[pc] = ctxt.new_location (toyvmfn.fn_filename,
op->op_linenum,
0); /* column */
}
}
/* Creating the function. */
void
compilation_state::create_function (const char *funcname)
{
std::vector <gccjit::param> params;
param_arg = ctxt.new_param (int_type, "arg", op_locs[0]);
params.push_back (param_arg);
fn = ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
int_type,
funcname,
params, 0,
op_locs[0]);
/* Create stack lvalues. */
stack = fn.new_local (stack_type, "stack");
stack_depth = fn.new_local (int_type, "stack_depth");
x = fn.new_local (int_type, "x");
y = fn.new_local (int_type, "y");
/* 1st pass: create blocks, one per opcode. */
/* We need an entry block to do one-time initialization, so create that
first. */
initial_block = fn.new_block ("initial");
/* Create a block per operation. */
for (int pc = 0; pc < toyvmfn.fn_num_ops; pc++)
{
char buf[16];
sprintf (buf, "instr%i", pc);
op_blocks[pc] = fn.new_block (buf);
}
/* Populate the initial block. */
/* "stack_depth = 0;". */
initial_block.add_assignment (stack_depth,
ctxt.zero (int_type),
op_locs[0]);
/* "PUSH (arg);". */
add_push (initial_block,
param_arg,
op_locs[0]);
/* ...and jump to insn 0. */
initial_block.end_with_jump (op_blocks[0],
op_locs[0]);
/* 2nd pass: fill in instructions. */
for (int pc = 0; pc < toyvmfn.fn_num_ops; pc++)
{
gccjit::location loc = op_locs[pc];
gccjit::block block = op_blocks[pc];
gccjit::block next_block = (pc < toyvmfn.fn_num_ops
? op_blocks[pc + 1]
: NULL);
toyvm_op *op;
op = &toyvmfn.fn_ops[pc];
/* Helper macros. */
#define X_EQUALS_POP()\
add_pop (block, x, loc)
#define Y_EQUALS_POP()\
add_pop (block, y, loc)
#define PUSH_RVALUE(RVALUE)\
add_push (block, (RVALUE), loc)
#define PUSH_X()\
PUSH_RVALUE (x)
#define PUSH_Y() \
PUSH_RVALUE (y)
block.add_comment (opcode_names[op->op_opcode], loc);
/* Handle the individual opcodes. */
switch (op->op_opcode)
{
case DUP:
X_EQUALS_POP ();
PUSH_X ();
PUSH_X ();
break;
case ROT:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_Y ();
PUSH_X ();
break;
case BINARY_ADD:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_RVALUE (
ctxt.new_binary_op (
GCC_JIT_BINARY_OP_PLUS,
int_type,
x, y,
loc));
break;
case BINARY_SUBTRACT:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_RVALUE (
ctxt.new_binary_op (
GCC_JIT_BINARY_OP_MINUS,
int_type,
x, y,
loc));
break;
case BINARY_MULT:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_RVALUE (
ctxt.new_binary_op (
GCC_JIT_BINARY_OP_MULT,
int_type,
x, y,
loc));
break;
case BINARY_COMPARE_LT:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_RVALUE (
/* cast of bool to int */
ctxt.new_cast (
/* (x < y) as a bool */
ctxt.new_comparison (
GCC_JIT_COMPARISON_LT,
x, y,
loc),
int_type,
loc));
break;
case RECURSE:
{
X_EQUALS_POP ();
PUSH_RVALUE (
ctxt.new_call (
fn,
x,
loc));
break;
}
case RETURN:
X_EQUALS_POP ();
block.end_with_return (x, loc);
break;
/* Ops taking an operand. */
case PUSH_CONST:
PUSH_RVALUE (
ctxt.new_rvalue (int_type, op->op_operand));
break;
case JUMP_ABS_IF_TRUE:
X_EQUALS_POP ();
block.end_with_conditional (
/* "(bool)x". */
ctxt.new_cast (x, bool_type, loc),
op_blocks[op->op_operand], /* on_true */
next_block, /* on_false */
loc);
break;
default:
assert(0);
} /* end of switch on opcode */
/* Go to the next block. */
if (op->op_opcode != JUMP_ABS_IF_TRUE
&& op->op_opcode != RETURN)
block.end_with_jump (next_block, loc);
} /* end of loop on PC locations. */
}
gcc_jit_result *
compilation_state::compile ()
{
return ctxt.compile ();
}
char test[1024];
#define CHECK_NON_NULL(PTR) \
do { \
if ((PTR) != NULL) \
{ \
pass ("%s: %s is non-null", test, #PTR); \
} \
else \
{ \
fail ("%s: %s is NULL", test, #PTR); \
abort (); \
} \
} while (0)
#define CHECK_VALUE(ACTUAL, EXPECTED) \
do { \
if ((ACTUAL) == (EXPECTED)) \
{ \
pass ("%s: actual: %s == expected: %s", test, #ACTUAL, #EXPECTED); \
} \
else \
{ \
fail ("%s: actual: %s != expected: %s", test, #ACTUAL, #EXPECTED); \
fprintf (stderr, "incorrect value\n"); \
abort (); \
} \
} while (0)
static void
test_script (const char *scripts_dir, const char *script_name, int input,
int expected_result)
{
char *script_path;
toyvm_function *fn;
int interpreted_result;
toyvm_compiled_func code;
int compiled_result;
snprintf (test, sizeof (test), "toyvm.cc: %s", script_name);
script_path = (char *)malloc (strlen (scripts_dir)
+ strlen (script_name) + 1);
CHECK_NON_NULL (script_path);
sprintf (script_path, "%s%s", scripts_dir, script_name);
fn = toyvm_function::parse (script_path, script_name);
CHECK_NON_NULL (fn);
interpreted_result = fn->interpret (input, NULL);
CHECK_VALUE (interpreted_result, expected_result);
code = fn->compile ();
CHECK_NON_NULL (code);
compiled_result = code (input);
CHECK_VALUE (compiled_result, expected_result);
free (script_path);
}
#define PATH_TO_SCRIPTS ("/jit/docs/examples/tut04-toyvm/")
static void
test_suite (void)
{
const char *srcdir;
char *scripts_dir;
snprintf (test, sizeof (test), "toyvm.cc");
/* We need to locate the test scripts.
Rely on "srcdir" being set in the environment. */
srcdir = getenv ("srcdir");
CHECK_NON_NULL (srcdir);
scripts_dir = (char *)malloc (strlen (srcdir) + strlen(PATH_TO_SCRIPTS)
+ 1);
CHECK_NON_NULL (scripts_dir);
sprintf (scripts_dir, "%s%s", srcdir, PATH_TO_SCRIPTS);
test_script (scripts_dir, "factorial.toy", 10, 3628800);
test_script (scripts_dir, "fibonacci.toy", 10, 55);
free (scripts_dir);
}
int
main (int argc, char **argv)
{
const char *filename = NULL;
toyvm_function *fn = NULL;
/* If called with no args, assume we're being run by the test suite. */
if (argc < 3)
{
test_suite ();
return 0;
}
if (argc != 3)
{
fprintf (stdout,
"%s FILENAME INPUT: Parse and run a .toy file\n",
argv[0]);
exit (1);
}
filename = argv[1];
fn = toyvm_function::parse (filename, filename);
if (!fn)
exit (1);
if (0)
fn->disassemble (stdout);
printf ("interpreter result: %d\n",
fn->interpret (atoi (argv[2]), NULL));
/* JIT-compilation. */
toyvm_compiled_func code = fn->compile ();
printf ("compiler result: %d\n",
code (atoi (argv[2])));
return 0;
}
...@@ -18,6 +18,20 @@ ...@@ -18,6 +18,20 @@
libgccjit libgccjit
========= =========
This document describes `libgccjit <http://gcc.gnu.org/wiki/JIT>`_, an API
for embedding GCC inside programs and libraries.
Note that libgccjit is currently of "Alpha" quality;
the APIs are not yet set in stone, and they shouldn't be used in
production yet.
There are actually two APIs for the library:
* a pure C API: ``libgccjit.h``
* a C++ wrapper API: ``libgccjit++.h``. This is a collection of "thin"
wrapper classes around the C API, to save typing.
Contents: Contents:
.. toctree:: .. toctree::
...@@ -25,15 +39,9 @@ Contents: ...@@ -25,15 +39,9 @@ Contents:
intro/index.rst intro/index.rst
topics/index.rst topics/index.rst
cp/index.rst
internals/index.rst internals/index.rst
This document describes `libgccjit <http://gcc.gnu.org/wiki/JIT>`_, an API
for embedding GCC inside programs and libraries.
Note that libgccjit is currently of "Alpha" quality;
the APIs are not yet set in stone, and they shouldn't be used in
production yet.
Indices and tables Indices and tables
================== ==================
......
...@@ -89,7 +89,7 @@ cleanup of such objects is done for you when the context is released. ...@@ -89,7 +89,7 @@ cleanup of such objects is done for you when the context is released.
Thread-safety Thread-safety
------------- -------------
Instances of :c:type:`gcc_jit_object *` created via Instances of :c:type:`gcc_jit_context *` created via
:c:func:`gcc_jit_context_acquire` are independent from each other: :c:func:`gcc_jit_context_acquire` are independent from each other:
only one thread may use a given context at once, but multiple threads only one thread may use a given context at once, but multiple threads
could each have their own contexts without needing locks. could each have their own contexts without needing locks.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment