Consider this C function:
int square (int i)
{
return i * i;
}
How can we construct this at run-time using libgccjit?
First we need to include the relevant header:
#include <libgccjit.h>
All state associated with compilation is associated with a :c:type:`gcc_jit_context *`.
Create one using :c:func:`gcc_jit_context_acquire`:
gcc_jit_context *ctxt;
ctxt = gcc_jit_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 :c:type:`gcc_jit_type *`, using :c:func:`gcc_jit_context_get_type`:
gcc_jit_type *int_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
:c:type:`gcc_jit_type *` is an example of a “contextual” object: every entity in the API is associated with a :c:type:`gcc_jit_context *`.
Memory management is easy: all such “contextual” objects are automatically cleaned up for you when the context is released, using :c:func:`gcc_jit_context_release`:
gcc_jit_context_release (ctxt);
so you don’t need to manually track and cleanup all objects, just the contexts.
Although the API is C-based, there is a form of class hierarchy, which looks like this:
+- gcc_jit_object
+- gcc_jit_location
+- gcc_jit_type
+- gcc_jit_struct
+- gcc_jit_field
+- gcc_jit_function
+- gcc_jit_block
+- gcc_jit_rvalue
+- gcc_jit_lvalue
+- gcc_jit_param
There are casting methods for upcasting from subclasses to parent classes. For example, :c:func:`gcc_jit_type_as_object`:
gcc_jit_object *obj = gcc_jit_type_as_object (int_type);
One thing you can do with a :c:type:`gcc_jit_object *` is to ask it for a human-readable description, using :c:func:`gcc_jit_object_get_debug_string`:
printf ("obj: %s\n", gcc_jit_object_get_debug_string (obj));
giving this text on stdout:
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 :c:func:`gcc_jit_context_new_param`:
gcc_jit_param *param_i =
gcc_jit_context_new_param (ctxt, NULL, int_type, "i");
Now we can create the function, using :c:func:`gcc_jit_context_new_function`:
gcc_jit_function *func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_EXPORTED,
int_type,
"square",
1, ¶m_i,
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:
gcc_jit_block *block = gcc_jit_function_new_block (func, NULL);
Our basic block is relatively simple: it immediately terminates by returning the value of an expression.
We can build the expression using :c:func:`gcc_jit_context_new_binary_op`:
gcc_jit_rvalue *expr =
gcc_jit_context_new_binary_op (
ctxt, NULL,
GCC_JIT_BINARY_OP_MULT, int_type,
gcc_jit_param_as_rvalue (param_i),
gcc_jit_param_as_rvalue (param_i));
A :c:type:`gcc_jit_rvalue *` is another example of a :c:type:`gcc_jit_object *` subclass. We can upcast it using :c:func:`gcc_jit_rvalue_as_object` and as before print it with :c:func:`gcc_jit_object_get_debug_string`.
printf ("expr: %s\n",
gcc_jit_object_get_debug_string (
gcc_jit_rvalue_as_object (expr)));
giving this output:
expr: i * 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:
gcc_jit_block_end_with_return (block, NULL, expr);
OK, we’ve populated the context. We can now compile it using :c:func:`gcc_jit_context_compile`:
gcc_jit_result *result;
result = gcc_jit_context_compile (ctxt);
and get a :c:type:`gcc_jit_result *`.
At this point we’re done with the context; we can release it:
gcc_jit_context_release (ctxt);
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.
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:
typedef int (*fn_type) (int);
fn_type square = (fn_type)fn_ptr;
printf ("result: %d", square (5));
result: 25
Once we’re done with the code, we can release the result:
gcc_jit_result_release (result);
We can’t call square anymore once we’ve released result.
Various kinds of errors are possible when using the API, such as mismatched types in an assignment. You can only compile and get code from a context if no errors occur.
Errors are printed on stderr; they typically contain the name of the API entrypoint where the error occurred, and pertinent information on the problem:
./buggy-program: error: gcc_jit_block_add_assignment: mismatching types: assignment to i (type: int) from "hello world" (type: const char *)
The API is designed to cope with errors without crashing, so you can get away with having a single error-handling check in your code:
void *fn_ptr = gcc_jit_result_get_code (result, "square");
if (!fn_ptr)
{
fprintf (stderr, "NULL fn_ptr");
goto error;
}
For more information, see the error-handling guide within the Topic eference.
To get more information on what’s going on, you can set debugging flags on the context using :c:func:`gcc_jit_context_set_bool_option`.
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):
gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
1);
result = gcc_jit_context_compile (ctxt);
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:
gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
1);
result = gcc_jit_context_compile (ctxt);
.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 :c:func:`gcc_jit_context_set_int_option` with :c:macro:`GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL`:
gcc_jit_context_set_int_option (
ctxt,
GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
3);
.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.
Here’s what the above looks like as a complete program:
/* Usage example for libgccjit.so Copyright (C) 2014-2016 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 (gcc_jit_context *ctxt) { /* Let's try to inject the equivalent of: int square (int i) { return i * i; } */ gcc_jit_type *int_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT); gcc_jit_param *param_i = gcc_jit_context_new_param (ctxt, NULL, int_type, "i"); gcc_jit_function *func = gcc_jit_context_new_function (ctxt, NULL, GCC_JIT_FUNCTION_EXPORTED, int_type, "square", 1, ¶m_i, 0); gcc_jit_block *block = gcc_jit_function_new_block (func, NULL); gcc_jit_rvalue *expr = gcc_jit_context_new_binary_op ( ctxt, NULL, GCC_JIT_BINARY_OP_MULT, int_type, gcc_jit_param_as_rvalue (param_i), gcc_jit_param_as_rvalue (param_i)); gcc_jit_block_end_with_return (block, NULL, expr); } int main (int argc, char **argv) { gcc_jit_context *ctxt = NULL; gcc_jit_result *result = NULL; /* Get a "context" object for working with the library. */ ctxt = gcc_jit_context_acquire (); if (!ctxt) { fprintf (stderr, "NULL ctxt"); goto error; } /* Set some options on the context. Let's see the code being generated, in assembler form. */ gcc_jit_context_set_bool_option ( ctxt, GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, 0); /* Populate the context. */ create_code (ctxt); /* Compile the code. */ result = gcc_jit_context_compile (ctxt); if (!result) { fprintf (stderr, "NULL result"); goto error; } /* We're done with the context; we can release it: */ gcc_jit_context_release (ctxt); ctxt = NULL; /* Extract the generated code from "result". */ void *fn_ptr = gcc_jit_result_get_code (result, "square"); if (!fn_ptr) { fprintf (stderr, "NULL fn_ptr"); goto error; } typedef int (*fn_type) (int); fn_type square = (fn_type)fn_ptr; printf ("result: %d\n", square (5)); error: if (ctxt) gcc_jit_context_release (ctxt); if (result) gcc_jit_result_release (result); return 0; }
Building and running it:
$ gcc \
tut02-square.c \
-o tut02-square \
-lgccjit
# Run the built program:
$ ./tut02-square
result: 25