blob: 3bc5ae432efdf4aac5d756adaba452032f36e082 [file] [log] [blame]
\input texinfo @c -*-texinfo-*-
@c %**start of header
@setfilename libgccjit.info
@documentencoding UTF-8
@ifinfo
@*Generated by Sphinx 1.1.3.@*
@end ifinfo
@settitle libgccjit Documentation
@defindex ge
@paragraphindent 2
@exampleindent 4
@afourlatex
@dircategory Miscellaneous
@direntry
* libgccjit: (libgccjit.info). One line description of project.
@end direntry
@c %**end of header
@copying
@quotation
libgccjit 8.0.0 (experimental 20171031), October 31, 2017
David Malcolm
Copyright @copyright{} 2014-2018 Free Software Foundation, Inc.
@end quotation
@end copying
@titlepage
@title libgccjit Documentation
@insertcopying
@end titlepage
@contents
@c %** start of user preamble
@c %** end of user preamble
@ifnottex
@node Top
@top libgccjit Documentation
@insertcopying
@end ifnottex
@c %**start of body
@anchor{index doc}@anchor{0}
@c Copyright (C) 2014-2018 Free Software Foundation, Inc.
@c Originally contributed by David Malcolm <dmalcolm@redhat.com>
@c
@c This is free software: you can redistribute it and/or modify it
@c under the terms of the GNU General Public License as published by
@c the Free Software Foundation, either version 3 of the License, or
@c (at your option) any later version.
@c
@c This program is distributed in the hope that it will be useful, but
@c WITHOUT ANY WARRANTY; without even the implied warranty of
@c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@c General Public License for more details.
@c
@c You should have received a copy of the GNU General Public License
@c along with this program. If not, see
@c <http://www.gnu.org/licenses/>.
This document describes libgccjit@footnote{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:
@itemize *
@item
a pure C API: @code{libgccjit.h}
@item
a C++ wrapper API: @code{libgccjit++.h}. This is a collection of "thin"
wrapper classes around the C API, to save typing.
@end itemize
Contents:
@c Copyright (C) 2014-2018 Free Software Foundation, Inc.
@c Originally contributed by David Malcolm <dmalcolm@redhat.com>
@c
@c This is free software: you can redistribute it and/or modify it
@c under the terms of the GNU General Public License as published by
@c the Free Software Foundation, either version 3 of the License, or
@c (at your option) any later version.
@c
@c This program is distributed in the hope that it will be useful, but
@c WITHOUT ANY WARRANTY; without even the implied warranty of
@c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@c General Public License for more details.
@c
@c You should have received a copy of the GNU General Public License
@c along with this program. If not, see
@c <http://www.gnu.org/licenses/>.
@menu
* Tutorial::
* Topic Reference::
* C++ bindings for libgccjit::
* Internals::
* Indices and tables::
* Index::
@detailmenu
--- The Detailed Node Listing ---
Tutorial
* Tutorial part 1; "Hello world": Tutorial part 1 "Hello world".
* Tutorial part 2; Creating a trivial machine code function: Tutorial part 2 Creating a trivial machine code function.
* Tutorial part 3; Loops and variables: Tutorial part 3 Loops and variables.
* Tutorial part 4; Adding JIT-compilation to a toy interpreter: Tutorial part 4 Adding JIT-compilation to a toy interpreter.
* Tutorial part 5; Implementing an Ahead-of-Time compiler: Tutorial part 5 Implementing an Ahead-of-Time compiler.
Tutorial part 2: Creating a trivial machine code function
* Error-handling::
* Options::
* Full example::
Tutorial part 3: Loops and variables
* Expressions; lvalues and rvalues: Expressions lvalues and rvalues.
* Control flow::
* Visualizing the control flow graph::
* Full example: Full example<2>.
Tutorial part 4: Adding JIT-compilation to a toy interpreter
* Our toy interpreter::
* Compiling to machine code::
* Setting things up::
* Populating the function::
* Verifying the control flow graph::
* Compiling the context::
* Single-stepping through the generated code::
* Examining the generated code::
* Putting it all together::
* Behind the curtain; How does our code get optimized?: Behind the curtain How does our code get optimized?.
Behind the curtain: How does our code get optimized?
* Optimizing away stack manipulation::
* Elimination of tail recursion::
Tutorial part 5: Implementing an Ahead-of-Time compiler
* The "brainf" language::
* Converting a brainf script to libgccjit IR::
* Compiling a context to a file::
* Other forms of ahead-of-time-compilation::
Topic Reference
* Compilation contexts::
* Objects::
* Types::
* Expressions::
* Creating and using functions::
* Function pointers: Function pointers<2>.
* Source Locations::
* Compiling a context::
* ABI and API compatibility::
* Performance::
Compilation contexts
* Lifetime-management::
* Thread-safety::
* Error-handling: Error-handling<2>.
* Debugging::
* Options: Options<2>.
Options
* String Options::
* Boolean options::
* Integer options::
* Additional command-line options::
Types
* Standard types::
* Pointers@comma{} const@comma{} and volatile: Pointers const and volatile.
* Vector types::
* Structures and unions::
* Function pointer types::
Expressions
* Rvalues::
* Lvalues::
* Working with pointers@comma{} structs and unions: Working with pointers structs and unions.
Rvalues
* Simple expressions::
* Vector expressions::
* Unary Operations::
* Binary Operations::
* Comparisons::
* Function calls::
* Function pointers::
* Type-coercion::
Lvalues
* Global variables::
Creating and using functions
* Params::
* Functions::
* Blocks::
* Statements::
Source Locations
* Faking it::
Compiling a context
* In-memory compilation::
* Ahead-of-time compilation::
ABI and API compatibility
* ABI symbol tags::
ABI symbol tags
* LIBGCCJIT_ABI_0::
* LIBGCCJIT_ABI_1::
* LIBGCCJIT_ABI_2::
* LIBGCCJIT_ABI_3::
* LIBGCCJIT_ABI_4::
* LIBGCCJIT_ABI_5::
* LIBGCCJIT_ABI_6::
* LIBGCCJIT_ABI_7::
* LIBGCCJIT_ABI_8::
* LIBGCCJIT_ABI_9::
* LIBGCCJIT_ABI_10::
Performance
* The timing API::
C++ bindings for libgccjit
* Tutorial: Tutorial<2>.
* Topic Reference: Topic Reference<2>.
Tutorial
* Tutorial part 1; "Hello world": Tutorial part 1 "Hello world"<2>.
* Tutorial part 2; Creating a trivial machine code function: Tutorial part 2 Creating a trivial machine code function<2>.
* Tutorial part 3; Loops and variables: Tutorial part 3 Loops and variables<2>.
* Tutorial part 4; Adding JIT-compilation to a toy interpreter: Tutorial part 4 Adding JIT-compilation to a toy interpreter<2>.
Tutorial part 2: Creating a trivial machine code function
* Options: Options<3>.
* Full example: Full example<3>.
Tutorial part 3: Loops and variables
* Expressions; lvalues and rvalues: Expressions lvalues and rvalues<2>.
* Control flow: Control flow<2>.
* Visualizing the control flow graph: Visualizing the control flow graph<2>.
* Full example: Full example<4>.
Tutorial part 4: Adding JIT-compilation to a toy interpreter
* Our toy interpreter: Our toy interpreter<2>.
* Compiling to machine code: Compiling to machine code<2>.
* Setting things up: Setting things up<2>.
* Populating the function: Populating the function<2>.
* Verifying the control flow graph: Verifying the control flow graph<2>.
* Compiling the context: Compiling the context<2>.
* Single-stepping through the generated code: Single-stepping through the generated code<2>.
* Examining the generated code: Examining the generated code<2>.
* Putting it all together: Putting it all together<2>.
* Behind the curtain; How does our code get optimized?: Behind the curtain How does our code get optimized?<2>.
Behind the curtain: How does our code get optimized?
* Optimizing away stack manipulation: Optimizing away stack manipulation<2>.
* Elimination of tail recursion: Elimination of tail recursion<2>.
Topic Reference
* Compilation contexts: Compilation contexts<2>.
* Objects: Objects<2>.
* Types: Types<2>.
* Expressions: Expressions<2>.
* Creating and using functions: Creating and using functions<2>.
* Source Locations: Source Locations<2>.
* Compiling a context: Compiling a context<2>.
Compilation contexts
* Lifetime-management: Lifetime-management<2>.
* Thread-safety: Thread-safety<2>.
* Error-handling: Error-handling<3>.
* Debugging: Debugging<2>.
* Options: Options<4>.
Options
* String Options: String Options<2>.
* Boolean options: Boolean options<2>.
* Integer options: Integer options<2>.
* Additional command-line options: Additional command-line options<2>.
Types
* Standard types: Standard types<2>.
* Pointers@comma{} const@comma{} and volatile: Pointers const and volatile<2>.
* Vector types: Vector types<2>.
* Structures and unions: Structures and unions<2>.
Expressions
* Rvalues: Rvalues<2>.
* Lvalues: Lvalues<2>.
* Working with pointers@comma{} structs and unions: Working with pointers structs and unions<2>.
Rvalues
* Simple expressions: Simple expressions<2>.
* Vector expressions: Vector expressions<2>.
* Unary Operations: Unary Operations<2>.
* Binary Operations: Binary Operations<2>.
* Comparisons: Comparisons<2>.
* Function calls: Function calls<2>.
* Function pointers: Function pointers<3>.
* Type-coercion: Type-coercion<2>.
Lvalues
* Global variables: Global variables<2>.
Creating and using functions
* Params: Params<2>.
* Functions: Functions<2>.
* Blocks: Blocks<2>.
* Statements: Statements<2>.
Source Locations
* Faking it: Faking it<2>.
Compiling a context
* In-memory compilation: In-memory compilation<2>.
* Ahead-of-time compilation: Ahead-of-time compilation<2>.
Internals
* Working on the JIT library::
* Running the test suite::
* Environment variables::
* Packaging notes::
* Overview of code structure::
* Design notes::
* Submitting patches::
Running the test suite
* Running under valgrind::
@end detailmenu
@end menu
@node Tutorial,Topic Reference,Top,Top
@anchor{intro/index libgccjit}@anchor{1}@anchor{intro/index doc}@anchor{2}@anchor{intro/index tutorial}@anchor{3}
@chapter Tutorial
@c Copyright (C) 2014-2018 Free Software Foundation, Inc.
@c Originally contributed by David Malcolm <dmalcolm@redhat.com>
@c
@c This is free software: you can redistribute it and/or modify it
@c under the terms of the GNU General Public License as published by
@c the Free Software Foundation, either version 3 of the License, or
@c (at your option) any later version.
@c
@c This program is distributed in the hope that it will be useful, but
@c WITHOUT ANY WARRANTY; without even the implied warranty of
@c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@c General Public License for more details.
@c
@c You should have received a copy of the GNU General Public License
@c along with this program. If not, see
@c <http://www.gnu.org/licenses/>.
@menu
* Tutorial part 1; "Hello world": Tutorial part 1 "Hello world".
* Tutorial part 2; Creating a trivial machine code function: Tutorial part 2 Creating a trivial machine code function.
* Tutorial part 3; Loops and variables: Tutorial part 3 Loops and variables.
* Tutorial part 4; Adding JIT-compilation to a toy interpreter: Tutorial part 4 Adding JIT-compilation to a toy interpreter.
* Tutorial part 5; Implementing an Ahead-of-Time compiler: Tutorial part 5 Implementing an Ahead-of-Time compiler.
@end menu
@node Tutorial part 1 "Hello world",Tutorial part 2 Creating a trivial machine code function,,Tutorial
@anchor{intro/tutorial01 doc}@anchor{4}@anchor{intro/tutorial01 tutorial-part-1-hello-world}@anchor{5}
@section 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 to synthesize
a call to @cite{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.
@quotation
@example
/* Smoketest example for libgccjit.so
Copyright (C) 2014-2018 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 (gcc_jit_context *ctxt)
@{
/* Let's try to inject the equivalent of:
void
greet (const char *name)
@{
printf ("hello %s\n", name);
@}
*/
gcc_jit_type *void_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_VOID);
gcc_jit_type *const_char_ptr_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_CONST_CHAR_PTR);
gcc_jit_param *param_name =
gcc_jit_context_new_param (ctxt, NULL, const_char_ptr_type, "name");
gcc_jit_function *func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_EXPORTED,
void_type,
"greet",
1, &param_name,
0);
gcc_jit_param *param_format =
gcc_jit_context_new_param (ctxt, NULL, const_char_ptr_type, "format");
gcc_jit_function *printf_func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_IMPORTED,
gcc_jit_context_get_type (
ctxt, GCC_JIT_TYPE_INT),
"printf",
1, &param_format,
1);
gcc_jit_rvalue *args[2];
args[0] = gcc_jit_context_new_string_literal (ctxt, "hello %s\n");
args[1] = gcc_jit_param_as_rvalue (param_name);
gcc_jit_block *block = gcc_jit_function_new_block (func, NULL);
gcc_jit_block_add_eval (
block, NULL,
gcc_jit_context_new_call (ctxt,
NULL,
printf_func,
2, args));
gcc_jit_block_end_with_void_return (block, NULL);
@}
int
main (int argc, char **argv)
@{
gcc_jit_context *ctxt;
gcc_jit_result *result;
/* Get a "context" object for working with the library. */
ctxt = gcc_jit_context_acquire ();
if (!ctxt)
@{
fprintf (stderr, "NULL ctxt");
exit (1);
@}
/* 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");
exit (1);
@}
/* 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_context_release (ctxt);
gcc_jit_result_release (result);
return 0;
@}
@end example
@noindent
@end quotation
Copy the above to @cite{tut01-hello-world.c}.
Assuming you have the jit library installed, build the test program
using:
@example
$ gcc \
tut01-hello-world.c \
-o tut01-hello-world \
-lgccjit
@end example
@noindent
You should then be able to run the built program:
@example
$ ./tut01-hello-world
hello world
@end example
@noindent
@c Copyright (C) 2014-2018 Free Software Foundation, Inc.
@c Originally contributed by David Malcolm <dmalcolm@redhat.com>
@c
@c This is free software: you can redistribute it and/or modify it
@c under the terms of the GNU General Public License as published by
@c the Free Software Foundation, either version 3 of the License, or
@c (at your option) any later version.
@c
@c This program is distributed in the hope that it will be useful, but
@c WITHOUT ANY WARRANTY; without even the implied warranty of
@c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@c General Public License for more details.
@c
@c You should have received a copy of the GNU General Public License
@c along with this program. If not, see
@c <http://www.gnu.org/licenses/>.
@node Tutorial part 2 Creating a trivial machine code function,Tutorial part 3 Loops and variables,Tutorial part 1 "Hello world",Tutorial
@anchor{intro/tutorial02 doc}@anchor{6}@anchor{intro/tutorial02 tutorial-part-2-creating-a-trivial-machine-code-function}@anchor{7}
@section Tutorial part 2: Creating a trivial machine code function
Consider this C function:
@example
int square (int i)
@{
return i * i;
@}
@end example
@noindent
How can we construct this at run-time using libgccjit?
First we need to include the relevant header:
@example
#include <libgccjit.h>
@end example
@noindent
All state associated with compilation is associated with a
@pxref{8,,gcc_jit_context *}.
Create one using @pxref{9,,gcc_jit_context_acquire()}:
@example
gcc_jit_context *ctxt;
ctxt = gcc_jit_context_acquire ();
@end example
@noindent
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 @cite{int} type, so let's obtain this from
the context, as a @pxref{a,,gcc_jit_type *}, using
@pxref{b,,gcc_jit_context_get_type()}:
@example
gcc_jit_type *int_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
@end example
@noindent
@pxref{a,,gcc_jit_type *} is an example of a "contextual" object: every
entity in the API is associated with a @pxref{8,,gcc_jit_context *}.
Memory management is easy: all such "contextual" objects are automatically
cleaned up for you when the context is released, using
@pxref{c,,gcc_jit_context_release()}:
@example
gcc_jit_context_release (ctxt);
@end example
@noindent
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:
@example
+- 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
@end example
@noindent
There are casting methods for upcasting from subclasses to parent classes.
For example, @pxref{d,,gcc_jit_type_as_object()}:
@example
gcc_jit_object *obj = gcc_jit_type_as_object (int_type);
@end example
@noindent
One thing you can do with a @pxref{e,,gcc_jit_object *} is
to ask it for a human-readable description, using
@pxref{f,,gcc_jit_object_get_debug_string()}:
@example
printf ("obj: %s\n", gcc_jit_object_get_debug_string (obj));
@end example
@noindent
giving this text on stdout:
@example
obj: int
@end example
@noindent
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 @pxref{10,,gcc_jit_context_new_param()}:
@example
gcc_jit_param *param_i =
gcc_jit_context_new_param (ctxt, NULL, int_type, "i");
@end example
@noindent
Now we can create the function, using
@pxref{11,,gcc_jit_context_new_function()}:
@example
gcc_jit_function *func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_EXPORTED,
int_type,
"square",
1, &param_i,
0);
@end example
@noindent
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:
@example
gcc_jit_block *block = gcc_jit_function_new_block (func, NULL);
@end example
@noindent
Our basic block is relatively simple: it immediately terminates by
returning the value of an expression.
We can build the expression using @pxref{12,,gcc_jit_context_new_binary_op()}:
@example
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));
@end example
@noindent
A @pxref{13,,gcc_jit_rvalue *} is another example of a
@pxref{e,,gcc_jit_object *} subclass. We can upcast it using
@pxref{14,,gcc_jit_rvalue_as_object()} and as before print it with
@pxref{f,,gcc_jit_object_get_debug_string()}.
@example
printf ("expr: %s\n",
gcc_jit_object_get_debug_string (
gcc_jit_rvalue_as_object (expr)));
@end example
@noindent
giving this output:
@example
expr: i * i
@end example
@noindent
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:
@example
gcc_jit_block_end_with_return (block, NULL, expr);
@end example
@noindent
OK, we've populated the context. We can now compile it using
@pxref{15,,gcc_jit_context_compile()}:
@example
gcc_jit_result *result;
result = gcc_jit_context_compile (ctxt);
@end example
@noindent
and get a @pxref{16,,gcc_jit_result *}.
At this point we're done with the context; we can release it:
@example
gcc_jit_context_release (ctxt);
@end example
@noindent
We can now use @pxref{17,,gcc_jit_result_get_code()} to look up a specific
machine code routine within the result, in this case, the function we
created above.
@example
void *fn_ptr = gcc_jit_result_get_code (result, "square");
if (!fn_ptr)
@{
fprintf (stderr, "NULL fn_ptr");
goto error;
@}
@end example
@noindent
We can now cast the pointer to an appropriate function pointer type, and
then call it:
@example
typedef int (*fn_type) (int);
fn_type square = (fn_type)fn_ptr;
printf ("result: %d", square (5));
@end example
@noindent
@example
result: 25
@end example
@noindent
Once we're done with the code, we can release the result:
@example
gcc_jit_result_release (result);
@end example
@noindent
We can't call @code{square} anymore once we've released @code{result}.
@menu
* Error-handling::
* Options::
* Full example::
@end menu
@node Error-handling,Options,,Tutorial part 2 Creating a trivial machine code function
@anchor{intro/tutorial02 error-handling}@anchor{18}
@subsection Error-handling
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:
@example
./buggy-program: error: gcc_jit_block_add_assignment: mismatching types: assignment to i (type: int) from "hello world" (type: const char *)
@end example
@noindent
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:
@example
void *fn_ptr = gcc_jit_result_get_code (result, "square");
if (!fn_ptr)
@{
fprintf (stderr, "NULL fn_ptr");
goto error;
@}
@end example
@noindent
For more information, see the @pxref{19,,error-handling guide}
within the Topic eference.
@node Options,Full example,Error-handling,Tutorial part 2 Creating a trivial machine code function
@anchor{intro/tutorial02 options}@anchor{1a}
@subsection Options
To get more information on what's going on, you can set debugging flags
on the context using @pxref{1b,,gcc_jit_context_set_bool_option()}.
@c (I'm deliberately not mentioning
@c :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE` here since I think
@c it's probably more of use to implementors than to users)
Setting @pxref{1c,,GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE} will dump a
C-like representation to stderr when you compile (GCC's "GIMPLE"
representation):
@example
gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
1);
result = gcc_jit_context_compile (ctxt);
@end example
@noindent
@example
square (signed int i)
@{
signed int D.260;
entry:
D.260 = i * i;
return D.260;
@}
@end example
@noindent
We can see the generated machine code in assembler form (on stderr) by
setting @pxref{1d,,GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE} on the context
before compiling:
@example
gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
1);
result = gcc_jit_context_compile (ctxt);
@end example
@noindent
@example
.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
@end example
@noindent
By default, no optimizations are performed, the equivalent of GCC's
@cite{-O0} option. We can turn things up to e.g. @cite{-O3} by calling
@pxref{1e,,gcc_jit_context_set_int_option()} with
@pxref{1f,,GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL}:
@example
gcc_jit_context_set_int_option (
ctxt,
GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
3);
@end example
@noindent
@example
.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
@end example
@noindent
Naturally this has only a small effect on such a trivial function.
@node Full example,,Options,Tutorial part 2 Creating a trivial machine code function
@anchor{intro/tutorial02 full-example}@anchor{20}
@subsection Full example
Here's what the above looks like as a complete program:
@quotation
@example
/* Usage example for libgccjit.so
Copyright (C) 2014-2018 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, &param_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;
@}
@end example
@noindent
@end quotation
Building and running it:
@example
$ gcc \
tut02-square.c \
-o tut02-square \
-lgccjit
# Run the built program:
$ ./tut02-square
result: 25
@end example
@noindent
@c Copyright (C) 2014-2018 Free Software Foundation, Inc.
@c Originally contributed by David Malcolm <dmalcolm@redhat.com>
@c
@c This is free software: you can redistribute it and/or modify it
@c under the terms of the GNU General Public License as published by
@c the Free Software Foundation, either version 3 of the License, or
@c (at your option) any later version.
@c
@c This program is distributed in the hope that it will be useful, but
@c WITHOUT ANY WARRANTY; without even the implied warranty of
@c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@c General Public License for more details.
@c
@c You should have received a copy of the GNU General Public License
@c along with this program. If not, see
@c <http://www.gnu.org/licenses/>.
@node Tutorial part 3 Loops and variables,Tutorial part 4 Adding JIT-compilation to a toy interpreter,Tutorial part 2 Creating a trivial machine code function,Tutorial
@anchor{intro/tutorial03 tutorial-part-3-loops-and-variables}@anchor{21}@anchor{intro/tutorial03 doc}@anchor{22}
@section Tutorial part 3: Loops and variables
Consider this C function:
@quotation
@example
int loop_test (int n)
@{
int sum = 0;
for (int i = 0; i < n; i++)
sum += i * i;
return sum;
@}
@end example
@noindent
@end quotation
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 @cite{for} loop as a @cite{while} loop, giving:
@quotation
@example
int loop_test (int n)
@{
int sum = 0;
int i = 0;
while (i < n)
@{
sum += i * i;
i++;
@}
return sum;
@}
@end example
@noindent
@end quotation
Here's what the final control flow graph will look like:
@quotation
@float Figure
@image{sum-of-squares1,,,image of a control flow graph,png}
@end float
@end quotation
As before, we include the libgccjit header and make a
@pxref{8,,gcc_jit_context *}.
@example
#include <libgccjit.h>
void test (void)
@{
gcc_jit_context *ctxt;
ctxt = gcc_jit_context_acquire ();
@end example
@noindent
The function works with the C @cite{int} type:
@example
gcc_jit_type *the_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
gcc_jit_type *return_type = the_type;
@end example
@noindent
though we could equally well make it work on, say, @cite{double}:
@example
gcc_jit_type *the_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_DOUBLE);
@end example
@noindent
Let's build the function:
@example
gcc_jit_param *n =
gcc_jit_context_new_param (ctxt, NULL, the_type, "n");
gcc_jit_param *params[1] = @{n@};
gcc_jit_function *func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_EXPORTED,
return_type,
"loop_test",
1, params, 0);
@end example
@noindent
@menu
* Expressions; lvalues and rvalues: Expressions lvalues and rvalues.
* Control flow::
* Visualizing the control flow graph::
* Full example: Full example<2>.
@end menu
@node Expressions lvalues and rvalues,Control flow,,Tutorial part 3 Loops and variables
@anchor{intro/tutorial03 expressions-lvalues-and-rvalues}@anchor{23}
@subsection Expressions: lvalues and rvalues
The base class of expression is the @pxref{13,,gcc_jit_rvalue *},
representing an expression that can be on the @emph{right}-hand side of
an assignment: a value that can be computed somehow, and assigned
@emph{to} a storage area (such as a variable). It has a specific
@pxref{a,,gcc_jit_type *}.
Anothe important class is @pxref{24,,gcc_jit_lvalue *}.
A @pxref{24,,gcc_jit_lvalue *}. is something that can of the @emph{left}-hand
side of an assignment: a storage area (such as a variable).
In other words, every assignment can be thought of as:
@example
LVALUE = RVALUE;
@end example
@noindent
Note that @pxref{24,,gcc_jit_lvalue *} is a subclass of
@pxref{13,,gcc_jit_rvalue *}, where in an assignment of the form:
@example
LVALUE_A = LVALUE_B;
@end example
@noindent
the @cite{LVALUE_B} implies reading the current value of that storage
area, assigning it into the @cite{LVALUE_A}.
So far the only expressions we've seen are @cite{i * i}:
@example
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));
@end example
@noindent
which is a @pxref{13,,gcc_jit_rvalue *}, and the various function
parameters: @cite{param_i} and @cite{param_n}, instances of
@pxref{25,,gcc_jit_param *}, which is a subclass of
@pxref{24,,gcc_jit_lvalue *} (and, in turn, of @pxref{13,,gcc_jit_rvalue *}):
we can both read from and write to function parameters within the
body of a function.
Our new example has a couple of local variables. We create them by
calling @pxref{26,,gcc_jit_function_new_local()}, supplying a type and a
name:
@example
/* Build locals: */
gcc_jit_lvalue *i =
gcc_jit_function_new_local (func, NULL, the_type, "i");
gcc_jit_lvalue *sum =
gcc_jit_function_new_local (func, NULL, the_type, "sum");
@end example
@noindent
These are instances of @pxref{24,,gcc_jit_lvalue *} - they can be read from
and written to.
Note that there is no precanned way to create @emph{and} initialize a variable
like in C:
@example
int i = 0;
@end example
@noindent
Instead, having added the local to the function, we have to separately add
an assignment of @cite{0} to @cite{local_i} at the beginning of the function.
@node Control flow,Visualizing the control flow graph,Expressions lvalues and rvalues,Tutorial part 3 Loops and variables
@anchor{intro/tutorial03 control-flow}@anchor{27}
@subsection 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:
@enumerate
@item
before the loop (initializing the locals)
@item
the conditional at the top of the loop (comparing @cite{i < n})
@item
the body of the loop
@item
after the loop terminates (@cite{return sum})
@end enumerate
so we create these as @pxref{28,,gcc_jit_block *} instances within the
@pxref{29,,gcc_jit_function *}:
@example
gcc_jit_block *b_initial =
gcc_jit_function_new_block (func, "initial");
gcc_jit_block *b_loop_cond =
gcc_jit_function_new_block (func, "loop_cond");
gcc_jit_block *b_loop_body =
gcc_jit_function_new_block (func, "loop_body");
gcc_jit_block *b_after_loop =
gcc_jit_function_new_block (func, "after_loop");
@end example
@noindent
We now populate each block with statements.
The entry block @cite{b_initial} consists of initializations followed by a jump
to the conditional. We assign @cite{0} to @cite{i} and to @cite{sum}, using
@pxref{2a,,gcc_jit_block_add_assignment()} to add
an assignment statement, and using @pxref{2b,,gcc_jit_context_zero()} to get
the constant value @cite{0} for the relevant type for the right-hand side of
the assignment:
@example
/* sum = 0; */
gcc_jit_block_add_assignment (
b_initial, NULL,
sum,
gcc_jit_context_zero (ctxt, the_type));
/* i = 0; */
gcc_jit_block_add_assignment (
b_initial, NULL,
i,
gcc_jit_context_zero (ctxt, the_type));
@end example
@noindent
We can then terminate the entry block by jumping to the conditional:
@example
gcc_jit_block_end_with_jump (b_initial, NULL, b_loop_cond);
@end example
@noindent
The conditional block is equivalent to the line @cite{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
@pxref{13,,gcc_jit_rvalue *}, in this case the comparison of @cite{i} and @cite{n}.
We build the comparison using @pxref{2c,,gcc_jit_context_new_comparison()}:
@example
/* (i >= n) */
gcc_jit_rvalue *guard =
gcc_jit_context_new_comparison (
ctxt, NULL,
GCC_JIT_COMPARISON_GE,
gcc_jit_lvalue_as_rvalue (i),
gcc_jit_param_as_rvalue (n));
@end example
@noindent
and can then use this to add @cite{b_loop_cond}'s sole statement, via
@pxref{2d,,gcc_jit_block_end_with_conditional()}:
@example
/* Equivalent to:
if (guard)
goto after_loop;
else
goto loop_body; */
gcc_jit_block_end_with_conditional (
b_loop_cond, NULL,
guard,
b_after_loop, /* on_true */
b_loop_body); /* on_false */
@end example
@noindent
Next, we populate the body of the loop.
The C statement @cite{sum += i * i;} is an assignment operation, where an
lvalue is modified "in-place". We use
@pxref{2e,,gcc_jit_block_add_assignment_op()} to handle these operations:
@example
/* sum += i * i */
gcc_jit_block_add_assignment_op (
b_loop_body, NULL,
sum,
GCC_JIT_BINARY_OP_PLUS,
gcc_jit_context_new_binary_op (
ctxt, NULL,
GCC_JIT_BINARY_OP_MULT, the_type,
gcc_jit_lvalue_as_rvalue (i),
gcc_jit_lvalue_as_rvalue (i)));
@end example
@noindent
The @cite{i++} can be thought of as @cite{i += 1}, and can thus be handled in
a similar way. We use @pxref{2f,,gcc_jit_context_one()} to get the constant
value @cite{1} (for the relevant type) for the right-hand side
of the assignment.
@example
/* i++ */
gcc_jit_block_add_assignment_op (
b_loop_body, NULL,
i,
GCC_JIT_BINARY_OP_PLUS,
gcc_jit_context_one (ctxt, the_type));
@end example
@noindent
@cartouche
@quotation Note
For numeric constants other than 0 or 1, we could use
@pxref{30,,gcc_jit_context_new_rvalue_from_int()} and
@pxref{31,,gcc_jit_context_new_rvalue_from_double()}.
@end quotation
@end cartouche
The loop body completes by jumping back to the conditional:
@example
gcc_jit_block_end_with_jump (b_loop_body, NULL, b_loop_cond);
@end example
@noindent
Finally, we populate the @cite{b_after_loop} block, reached when the loop
conditional is false. We want to generate the equivalent of:
@example
return sum;
@end example
@noindent
so the block is just one statement:
@example
/* return sum */
gcc_jit_block_end_with_return (
b_after_loop,
NULL,
gcc_jit_lvalue_as_rvalue (sum));
@end example
@noindent
@cartouche
@quotation 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,
@emph{then} all the statements.
@end quotation
@end cartouche
We've finished populating the function. As before, we can now compile it
to machine code:
@example
gcc_jit_result *result;
result = gcc_jit_context_compile (ctxt);
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)
goto error;
printf ("result: %d", loop_test (10));
@end example
@noindent
@example
result: 285
@end example
@noindent
@node Visualizing the control flow graph,Full example<2>,Control flow,Tutorial part 3 Loops and variables
@anchor{intro/tutorial03 visualizing-the-control-flow-graph}@anchor{32}
@subsection Visualizing the control flow graph
You can see the control flow graph of a function using
@pxref{33,,gcc_jit_function_dump_to_dot()}:
@example
gcc_jit_function_dump_to_dot (func, "/tmp/sum-of-squares.dot");
@end example
@noindent
giving a .dot file in GraphViz format.
You can convert this to an image using @cite{dot}:
@example
$ dot -Tpng /tmp/sum-of-squares.dot -o /tmp/sum-of-squares.png
@end example
@noindent
or use a viewer (my preferred one is xdot.py; see
@indicateurl{https://github.com/jrfonseca/xdot.py}; on Fedora you can
install it with @cite{yum install python-xdot}):
@quotation
@float Figure
@image{sum-of-squares1,,,image of a control flow graph,png}
@end float
@end quotation
@node Full example<2>,,Visualizing the control flow graph,Tutorial part 3 Loops and variables
@anchor{intro/tutorial03 full-example}@anchor{34}
@subsection Full example
@quotation
@example
/* Usage example for libgccjit.so
Copyright (C) 2014-2018 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)
@{
/*
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;
*/
gcc_jit_type *the_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
gcc_jit_type *return_type = the_type;
gcc_jit_param *n =
gcc_jit_context_new_param (ctxt, NULL, the_type, "n");
gcc_jit_param *params[1] = @{n@};
gcc_jit_function *func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_EXPORTED,
return_type,
"loop_test",
1, params, 0);
/* Build locals: */
gcc_jit_lvalue *i =
gcc_jit_function_new_local (func, NULL, the_type, "i");
gcc_jit_lvalue *sum =
gcc_jit_function_new_local (func, NULL, the_type, "sum");
gcc_jit_block *b_initial =
gcc_jit_function_new_block (func, "initial");
gcc_jit_block *b_loop_cond =
gcc_jit_function_new_block (func, "loop_cond");
gcc_jit_block *b_loop_body =
gcc_jit_function_new_block (func, "loop_body");
gcc_jit_block *b_after_loop =
gcc_jit_function_new_block (func, "after_loop");
/* sum = 0; */
gcc_jit_block_add_assignment (
b_initial, NULL,
sum,
gcc_jit_context_zero (ctxt, the_type));
/* i = 0; */
gcc_jit_block_add_assignment (
b_initial, NULL,
i,
gcc_jit_context_zero (ctxt, the_type));
gcc_jit_block_end_with_jump (b_initial, NULL, b_loop_cond);
/* if (i >= n) */
gcc_jit_block_end_with_conditional (
b_loop_cond, NULL,
gcc_jit_context_new_comparison (
ctxt, NULL,
GCC_JIT_COMPARISON_GE,
gcc_jit_lvalue_as_rvalue (i),
gcc_jit_param_as_rvalue (n)),
b_after_loop,
b_loop_body);
/* sum += i * i */
gcc_jit_block_add_assignment_op (
b_loop_body, NULL,
sum,
GCC_JIT_BINARY_OP_PLUS,
gcc_jit_context_new_binary_op (
ctxt, NULL,
GCC_JIT_BINARY_OP_MULT, the_type,
gcc_jit_lvalue_as_rvalue (i),
gcc_jit_lvalue_as_rvalue (i)));
/* i++ */
gcc_jit_block_add_assignment_op (
b_loop_body, NULL,
i,
GCC_JIT_BINARY_OP_PLUS,
gcc_jit_context_one (ctxt, the_type));
gcc_jit_block_end_with_jump (b_loop_body, NULL, b_loop_cond);
/* return sum */
gcc_jit_block_end_with_return (
b_after_loop,
NULL,
gcc_jit_lvalue_as_rvalue (sum));
@}
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;
@}
/* 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");
goto error;
@}
/* Run the generated code. */
int val = loop_test (10);
printf("loop_test returned: %d\n", val);
error:
gcc_jit_context_release (ctxt);
gcc_jit_result_release (result);
return 0;
@}
@end example
@noindent
@end quotation
Building and running it:
@example
$ gcc \
tut03-sum-of-squares.c \
-o tut03-sum-of-squares \
-lgccjit
# Run the built program:
$ ./tut03-sum-of-squares
loop_test returned: 285
@end example
@noindent
@c Copyright (C) 2014-2018 Free Software Foundation, Inc.
@c Originally contributed by David Malcolm <dmalcolm@redhat.com>
@c
@c This is free software: you can redistribute it and/or modify it
@c under the terms of the GNU General Public License as published by
@c the Free Software Foundation, either version 3 of the License, or
@c (at your option) any later version.
@c
@c This program is distributed in the hope that it will be useful, but
@c WITHOUT ANY WARRANTY; without even the implied warranty of
@c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@c General Public License for more details.
@c
@c You should have received a copy of the GNU General Public License
@c along with this program. If not, see
@c <http://www.gnu.org/licenses/>.
@node Tutorial part 4 Adding JIT-compilation to a toy interpreter,Tutorial part 5 Implementing an Ahead-of-Time compiler,Tutorial part 3 Loops and variables,Tutorial
@anchor{intro/tutorial04 tutorial-part-4-adding-jit-compilation-to-a-toy-interpreter}@anchor{35}@anchor{intro/tutorial04 doc}@anchor{36}
@section Tutorial part 4: Adding JIT-compilation to a toy interpreter
In this example we construct a "toy" interpreter, and add JIT-compilation
to it.
@menu
* Our toy interpreter::
* Compiling to machine code::
* Setting things up::
* Populating the function::
* Verifying the control flow graph::
* Compiling the context::
* Single-stepping through the generated code::
* Examining the generated code::
* Putting it all together::
* Behind the curtain; How does our code get optimized?: Behind the curtain How does our code get optimized?.
@end menu
@node Our toy interpreter,Compiling to machine code,,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 our-toy-interpreter}@anchor{37}
@subsection 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:
@quotation
@itemize *
@item
The only data type is @cite{int}
@item
It can only work on one function at a time (so that the only
function call that can be made is to recurse).
@item
Functions can only take one parameter.
@item
Functions have a stack of @cite{int} values.
@item
We'll implement function call within the interpreter by calling a
function in our implementation, rather than implementing our own
frame stack.
@item
The parser is only good enough to get the examples to work.
@end itemize
@end quotation
Naturally, a real interpreter would be much more complicated that this.
The following operations are supported:
@multitable {xxxxxxxxxxxxxxxxxxxxxxxx} {xxxxxxxxxxxxxxxxxxxxxxxxxx} {xxxxxxxxxxxxxxxxx} {xxxxxxxxxxxxxxxxxx}
@headitem
Operation
@tab
Meaning
@tab
Old Stack
@tab
New Stack
@item
DUP
@tab
Duplicate top of stack.
@tab
@code{[..., x]}
@tab
@code{[..., x, x]}
@item
ROT
@tab
Swap top two elements
of stack.
@tab
@code{[..., x, y]}
@tab
@code{[..., y, x]}
@item
BINARY_ADD
@tab
Add the top two elements
on the stack.
@tab
@code{[..., x, y]}
@tab
@code{[..., (x+y)]}
@item
BINARY_SUBTRACT
@tab
Likewise, but subtract.
@tab
@code{[..., x, y]}
@tab
@code{[..., (x-y)]}
@item
BINARY_MULT
@tab
Likewise, but multiply.
@tab
@code{[..., x, y]}
@tab
@code{[..., (x*y)]}
@item
BINARY_COMPARE_LT
@tab
Compare the top two
elements on the stack
and push a nonzero/zero
if (x<y).
@tab
@code{[..., x, y]}
@tab
@code{[..., (x<y)]}
@item
RECURSE
@tab
Recurse, passing the top
of the stack, and
popping the result.
@tab
@code{[..., x]}
@tab
@code{[..., fn(x)]}
@item
RETURN
@tab
Return the top of the
stack.
@tab
@code{[x]}
@tab
@code{[]}
@item
PUSH_CONST @cite{arg}
@tab
Push an int const.
@tab
@code{[...]}
@tab
@code{[..., arg]}
@item
JUMP_ABS_IF_TRUE @cite{arg}
@tab
Pop; if top of stack was
nonzero, jump to
@code{arg}.
@tab
@code{[..., x]}
@tab
@code{[...]}
@end multitable
Programs can be interpreted, disassembled, and compiled to machine code.
The interpreter reads @code{.toy} scripts. Here's what a simple recursive
factorial program looks like, the script @code{factorial.toy}.
The parser ignores lines beginning with a @cite{#}.
@quotation
@example
# Simple recursive factorial implementation, roughly equivalent to:
#
# int factorial (int arg)
# @{
# if (arg < 2)
# return arg
# return arg * factorial (arg - 1)
# @}
# Initial state:
# stack: [arg]
# 0:
DUP
# stack: [arg, arg]
# 1:
PUSH_CONST 2
# stack: [arg, arg, 2]
# 2:
BINARY_COMPARE_LT
# stack: [arg, (arg < 2)]
# 3:
JUMP_ABS_IF_TRUE 9
# stack: [arg]
# 4:
DUP
# stack: [arg, arg]
# 5:
PUSH_CONST 1
# stack: [arg, arg, 1]
# 6:
BINARY_SUBTRACT
# stack: [arg, (arg - 1)
# 7:
RECURSE
# stack: [arg, factorial(arg - 1)]
# 8:
BINARY_MULT
# stack: [arg * factorial(arg - 1)]
# 9:
RETURN
@end example
@noindent
@end quotation
The interpreter is a simple infinite loop with a big @code{switch} statement
based on what the next opcode is:
@quotation
@example
static int
toyvm_function_interpret (toyvm_function *fn, int arg, FILE *trace)
@{
toyvm_frame frame;
#define PUSH(ARG) (toyvm_frame_push (&frame, (ARG)))
#define POP(ARG) (toyvm_frame_pop (&frame))
frame.frm_function = fn;
frame.frm_pc = 0;
frame.frm_cur_depth = 0;
PUSH (arg);
while (1)
@{
toyvm_op *op;
int x, y;
assert (frame.frm_pc < fn->fn_num_ops);
op = &fn->fn_ops[frame.frm_pc++];
if (trace)
@{
toyvm_frame_dump_stack (&frame, trace);
toyvm_function_disassemble_op (fn, 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 = toyvm_function_interpret (fn, 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
@}
@end example
@noindent
@end quotation
@node Compiling to machine code,Setting things up,Our toy interpreter,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 compiling-to-machine-code}@anchor{38}
@subsection Compiling to machine code
We want to generate machine code that can be cast to this type and
then directly executed in-process:
@quotation
@example
typedef int (*toyvm_compiled_code) (int);
@end example
@noindent
@end quotation
The lifetime of the code is tied to that of a @pxref{16,,gcc_jit_result *}.
We'll handle this by bundling them up in a structure, so that we can
clean them up together by calling @pxref{39,,gcc_jit_result_release()}:
@quotation
@example
struct toyvm_compiled_function
@{
gcc_jit_result *cf_jit_result;
toyvm_compiled_code cf_code;
@};
@end example
@noindent
@end quotation
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 @code{stack} array and @code{stack_depth}
variables, local within the generated function, equivalent to this C code:
@example
int stack_depth;
int stack[MAX_STACK_DEPTH];
@end example
@noindent
We'll also have local variables @code{x} and @code{y} for use when implementing
the opcodes, equivalent to this:
@example
int x;
int y;
@end example
@noindent
This means our compiler has the following state:
@quotation
@example
struct compilation_state
@{
gcc_jit_context *ctxt;
gcc_jit_type *int_type;
gcc_jit_type *bool_type;
gcc_jit_type *stack_type; /* int[MAX_STACK_DEPTH] */
gcc_jit_rvalue *const_one;
gcc_jit_function *fn;
gcc_jit_param *param_arg;
gcc_jit_lvalue *stack;
gcc_jit_lvalue *stack_depth;
gcc_jit_lvalue *x;
gcc_jit_lvalue *y;
gcc_jit_location *op_locs[MAX_OPS];
gcc_jit_block *initial_block;
gcc_jit_block *op_blocks[MAX_OPS];
@};
@end example
@noindent
@end quotation
@node Setting things up,Populating the function,Compiling to machine code,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 setting-things-up}@anchor{3a}
@subsection Setting things up
First we create our types:
@quotation
@example
state.int_type =
gcc_jit_context_get_type (state.ctxt, GCC_JIT_TYPE_INT);
state.bool_type =
gcc_jit_context_get_type (state.ctxt, GCC_JIT_TYPE_BOOL);
state.stack_type =
gcc_jit_context_new_array_type (state.ctxt, NULL,
state.int_type, MAX_STACK_DEPTH);
@end example
@noindent
@end quotation
along with extracting a useful @cite{int} constant:
@quotation
@example
state.const_one = gcc_jit_context_one (state.ctxt, state.int_type);
@end example
@noindent
@end quotation
We'll implement push and pop in terms of the @code{stack} array and
@code{stack_depth}. Here are helper functions for adding statements to
a block, implementing pushing and popping values:
@quotation
@example
static void
add_push (compilation_state *state,
gcc_jit_block *block,
gcc_jit_rvalue *rvalue,
gcc_jit_location *loc)
@{
/* stack[stack_depth] = RVALUE */
gcc_jit_block_add_assignment (
block,
loc,
/* stack[stack_depth] */
gcc_jit_context_new_array_access (
state->ctxt,
loc,
gcc_jit_lvalue_as_rvalue (state->stack),
gcc_jit_lvalue_as_rvalue (state->stack_depth)),
rvalue);
/* "stack_depth++;". */
gcc_jit_block_add_assignment_op (
block,
loc,
state->stack_depth,
GCC_JIT_BINARY_OP_PLUS,
state->const_one);
@}
static void
add_pop (compilation_state *state,
gcc_jit_block *block,
gcc_jit_lvalue *lvalue,
gcc_jit_location *loc)
@{
/* "--stack_depth;". */
gcc_jit_block_add_assignment_op (
block,
loc,
state->stack_depth,
GCC_JIT_BINARY_OP_MINUS,
state->const_one);
/* "LVALUE = stack[stack_depth];". */
gcc_jit_block_add_assignment (
block,
loc,
lvalue,
/* stack[stack_depth] */
gcc_jit_lvalue_as_rvalue (
gcc_jit_context_new_array_access (
state->ctxt,
loc,
gcc_jit_lvalue_as_rvalue (state->stack),
gcc_jit_lvalue_as_rvalue (state->stack_depth))));
@}
@end example
@noindent
@end quotation
We will support single-stepping through the generated code in the
debugger, so we need to create @pxref{3b,,gcc_jit_location} instances, one
per operation in the source code. These will reference the lines of
e.g. @code{factorial.toy}.
@quotation
@example
for (pc = 0; pc < fn->fn_num_ops; pc++)
@{
toyvm_op *op = &fn->fn_ops[pc];
state.op_locs[pc] = gcc_jit_context_new_location (state.ctxt,
fn->fn_filename,
op->op_linenum,
0); /* column */
@}
@end example
@noindent
@end quotation
Let's create the function itself. As usual, we create its parameter
first, then use the parameter to create the function:
@quotation
@example
state.param_arg =
gcc_jit_context_new_param (state.ctxt, state.op_locs[0],
state.int_type, "arg");
state.fn =
gcc_jit_context_new_function (state.ctxt,
state.op_locs[0],
GCC_JIT_FUNCTION_EXPORTED,
state.int_type,
funcname,
1, &state.param_arg, 0);
@end example
@noindent
@end quotation
We create the locals within the function.
@quotation
@example
state.stack =
gcc_jit_function_new_local (state.fn, NULL,
state.stack_type, "stack");
state.stack_depth =
gcc_jit_function_new_local (state.fn, NULL,
state.int_type, "stack_depth");
state.x =
gcc_jit_function_new_local (state.fn, NULL,
state.int_type, "x");
state.y =
gcc_jit_function_new_local (state.fn, NULL,
state.int_type, "y");
@end example
@noindent
@end quotation
@node Populating the function,Verifying the control flow graph,Setting things up,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 populating-the-function}@anchor{3c}
@subsection 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:
@quotation
@example
state.initial_block = gcc_jit_function_new_block (state.fn, "initial");
@end example
@noindent
@end quotation
We can now create blocks for each of the operations. Most of these will
be consolidated into larger blocks when the optimizer runs.
@quotation
@example
for (pc = 0; pc < fn->fn_num_ops; pc++)
@{
char buf[16];
sprintf (buf, "instr%i", pc);
state.op_blocks[pc] = gcc_jit_function_new_block (state.fn, buf);
@}
@end example
@noindent
@end quotation
Now that we have a block it can jump to when it's done, we can populate
the initial block:
@quotation
@example
/* "stack_depth = 0;". */
gcc_jit_block_add_assignment (
state.initial_block,
state.op_locs[0],
state.stack_depth,
gcc_jit_context_zero (state.ctxt, state.int_type));
/* "PUSH (arg);". */
add_push (&state,
state.initial_block,
gcc_jit_param_as_rvalue (state.param_arg),
state.op_locs[0]);
/* ...and jump to insn 0. */
gcc_jit_block_end_with_jump (state.initial_block,
state.op_locs[0],
state.op_blocks[0]);
@end example
@noindent
@end quotation
We can now populate the blocks for the individual operations. We loop
through them, adding instructions to their blocks:
@quotation
@example
for (pc = 0; pc < fn->fn_num_ops; pc++)
@{
gcc_jit_location *loc = state.op_locs[pc];
gcc_jit_block *block = state.op_blocks[pc];
gcc_jit_block *next_block = (pc < fn->fn_num_ops
? state.op_blocks[pc + 1]
: NULL);
toyvm_op *op;
op = &fn->fn_ops[pc];
@end example
@noindent
@end quotation
We're going to have another big @code{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 @code{switch} statement that's coming up look as much as
possible like the one above within the interpreter:
@example
#define X_EQUALS_POP()\
add_pop (&state, block, state.x, loc)
#define Y_EQUALS_POP()\
add_pop (&state, block, state.y, loc)
#define PUSH_RVALUE(RVALUE)\
add_push (&state, block, (RVALUE), loc)
#define PUSH_X()\
PUSH_RVALUE (gcc_jit_lvalue_as_rvalue (state.x))
#define PUSH_Y() \
PUSH_RVALUE (gcc_jit_lvalue_as_rvalue (state.y))
@end example
@noindent
@cartouche
@quotation Note
A particularly clever implementation would have an @emph{identical}
@code{switch} statement shared by the interpreter and the compiler, with
some preprocessor "magic". We're not doing that here, for the sake
of simplicity.
@end quotation
@end cartouche
When I first implemented this compiler, I accidentally missed an edit
when copying and pasting the @code{Y_EQUALS_POP} macro, so that popping the
stack into @code{y} instead erroneously assigned it to @code{x}, leaving @code{y}
uninitialized.
To track this kind of thing down, we can use
@pxref{3d,,gcc_jit_block_add_comment()} to add descriptive comments
to the internal representation. This is invaluable when looking through
the generated IR for, say @code{factorial}:
@quotation
@example
gcc_jit_block_add_comment (block, loc, opcode_names[op->op_opcode]);
@end example
@noindent
@end quotation
We can now write the big @code{switch} statement that implements the
individual opcodes, populating the relevant block with statements:
@quotation
@example
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 (
gcc_jit_context_new_binary_op (
state.ctxt,
loc,
GCC_JIT_BINARY_OP_PLUS,
state.int_type,
gcc_jit_lvalue_as_rvalue (state.x),
gcc_jit_lvalue_as_rvalue (state.y)));
break;
case BINARY_SUBTRACT:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_RVALUE (
gcc_jit_context_new_binary_op (
state.ctxt,
loc,
GCC_JIT_BINARY_OP_MINUS,
state.int_type,
gcc_jit_lvalue_as_rvalue (state.x),
gcc_jit_lvalue_as_rvalue (state.y)));
break;
case BINARY_MULT:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_RVALUE (
gcc_jit_context_new_binary_op (
state.ctxt,
loc,
GCC_JIT_BINARY_OP_MULT,
state.int_type,
gcc_jit_lvalue_as_rvalue (state.x),
gcc_jit_lvalue_as_rvalue (state.y)));
break;
case BINARY_COMPARE_LT:
Y_EQUALS_POP ();
X_EQUALS_POP ();
PUSH_RVALUE (
/* cast of bool to int */
gcc_jit_context_new_cast (
state.ctxt,
loc,
/* (x < y) as a bool */
gcc_jit_context_new_comparison (
state.ctxt,
loc,
GCC_JIT_COMPARISON_LT,
gcc_jit_lvalue_as_rvalue (state.x),
gcc_jit_lvalue_as_rvalue (state.y)),
state.int_type));
break;
case RECURSE:
@{
X_EQUALS_POP ();
gcc_jit_rvalue *arg = gcc_jit_lvalue_as_rvalue (state.x);
PUSH_RVALUE (
gcc_jit_context_new_call (
state.ctxt,
loc,
state.fn,
1, &arg));
break;
@}
case RETURN:
X_EQUALS_POP ();
gcc_jit_block_end_with_return (
block,
loc,
gcc_jit_lvalue_as_rvalue (state.x));
break;
/* Ops taking an operand. */
case PUSH_CONST:
PUSH_RVALUE (
gcc_jit_context_new_rvalue_from_int (
state.ctxt,
state.int_type,
op->op_operand));
break;
case JUMP_ABS_IF_TRUE:
X_EQUALS_POP ();
gcc_jit_block_end_with_conditional (
block,
loc,
/* "(bool)x". */
gcc_jit_context_new_cast (
state.ctxt,
loc,
gcc_jit_lvalue_as_rvalue (state.x),
state.bool_type),
state.op_blocks[op->op_operand], /* on_true */
next_block); /* on_false */
break;
default:
assert(0);
@} /* end of switch on opcode */
@end example
@noindent
@end quotation
Every block must be terminated, via a call to one of the
@code{gcc_jit_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.
@quotation
@example
if (op->op_opcode != JUMP_ABS_IF_TRUE
&& op->op_opcode != RETURN)
gcc_jit_block_end_with_jump (
block,
loc,
next_block);
@end example
@noindent
@end quotation
This is analogous to simply incrementing the program counter.
@node Verifying the control flow graph,Compiling the context,Populating the function,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 verifying-the-control-flow-graph}@anchor{3e}
@subsection 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 @pxref{33,,gcc_jit_function_dump_to_dot()}:
@example
gcc_jit_function_dump_to_dot (state.fn, "/tmp/factorial.dot");
@end example
@noindent
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.
@quotation
@float Figure
@image{factorial1,,,image of a control flow graph,png}
@end float
@end quotation
@node Compiling the context,Single-stepping through the generated code,Verifying the control flow graph,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 compiling-the-context}@anchor{3f}
@subsection 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:
@quotation
@example
gcc_jit_result *jit_result = gcc_jit_context_compile (state.ctxt);
gcc_jit_context_release (state.ctxt);
toyvm_compiled_function *toyvm_result =
(toyvm_compiled_function *)calloc (1, sizeof (toyvm_compiled_function));
if (!toyvm_result)
@{
fprintf (stderr, "out of memory allocating toyvm_compiled_function\n");
gcc_jit_result_release (jit_result);
return NULL;
@}
toyvm_result->cf_jit_result = jit_result;
toyvm_result->cf_code =
(toyvm_compiled_code)gcc_jit_result_get_code (jit_result,
funcname);
free (funcname);
return toyvm_result;
@}
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_function *compiled_fn;
toyvm_compiled_code code;
int compiled_result;
snprintf (test, sizeof (test), "toyvm.c: %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 = toyvm_function_interpret (fn, input, NULL);
CHECK_VALUE (interpreted_result, expected_result);
compiled_fn = toyvm_function_compile (fn);
CHECK_NON_NULL (compiled_fn);
code = (toyvm_compiled_code)compiled_fn->cf_code;
CHECK_NON_NULL (code);
compiled_result = code (input);
CHECK_VALUE (compiled_result, expected_result);
gcc_jit_result_release (compiled_fn->cf_jit_result);
free (compiled_fn);
free (fn);
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.c");
/* 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)
toyvm_function_disassemble (fn, stdout);
printf ("interpreter result: %d\n",
toyvm_function_interpret (fn, atoi (argv[2]), NULL));
/* JIT-compilation. */
toyvm_compiled_function *compiled_fn
= toyvm_function_compile (fn);
toyvm_compiled_code code = compiled_fn->cf_code;
printf ("compiler result: %d\n",
code (atoi (argv[2])));
gcc_jit_result_release (compiled_fn->cf_jit_result);
free (compiled_fn);
return 0;
@}
@end example
@noindent
@end quotation
We can now run the result:
@quotation
@example
toyvm_compiled_function *compiled_fn
= toyvm_function_compile (fn);
toyvm_compiled_code code = compiled_fn->cf_code;
printf ("compiler result: %d\n",
code (atoi (argv[2])));
gcc_jit_result_release (compiled_fn->cf_jit_result);
free (compiled_fn);
@end example
@noindent
@end quotation
@node Single-stepping through the generated code,Examining the generated code,Compiling the context,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 single-stepping-through-the-generated-code}@anchor{40}
@subsection Single-stepping through the generated code
It's possible to debug the generated code. To do this we need to both:
@quotation
@itemize *
@item
Set up source code locations for our statements, so that we can
meaningfully step through the code. We did this above by
calling @pxref{41,,gcc_jit_context_new_location()} and using the
results.
@item
Enable the generation of debugging information, by setting
@pxref{42,,GCC_JIT_BOOL_OPTION_DEBUGINFO} on the
@pxref{8,,gcc_jit_context} via
@pxref{1b,,gcc_jit_context_set_bool_option()}:
@example
gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DEBUGINFO,
1);
@end example
@noindent
@end itemize
@end quotation
Having done this, we can put a breakpoint on the generated function:
@example
$ 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
@end example
@noindent
We've set up location information, which references @code{factorial.toy}.
This allows us to use e.g. @code{list} to see where we are in the script:
@example
(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
@end example
@noindent
and to step through the function, examining the data:
@example
(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
@end example
@noindent
You'll see that the parts of the @code{stack} array that haven't been
touched yet are uninitialized.
@cartouche
@quotation 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.
@end quotation
@end cartouche
@node Examining the generated code,Putting it all together,Single-stepping through the generated code,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 examining-the-generated-code}@anchor{43}
@subsection Examining the generated code
How good is the optimized code?
We can turn up optimizations, by calling
@pxref{1e,,gcc_jit_context_set_int_option()} with
@pxref{1f,,GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL}:
@example
gcc_jit_context_set_int_option (
ctxt,
GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
3);
@end example
@noindent
One of GCC's internal representations is called "gimple". A dump of the
initial gimple representation of the code can be seen by setting:
@example
gcc_jit_context_set_bool_option (ctxt,
GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
1);
@end example
@noindent
With optimization on and source locations displayed, this gives:
@c We'll use "c" for gimple dumps
@example
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 */
@end example
@noindent
You can see the generated machine code in assembly form via:
@example
gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
1);
result = gcc_jit_context_compile (ctxt);
@end example
@noindent
which shows that (on this x86_64 box) the compiler has unrolled the loop
and is using MMX instructions to perform several multiplications
simultaneously:
@example
.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
@end example
@noindent
This is clearly overkill for a function that will likely overflow the
@code{int} type before the vectorization is worthwhile - but then again, this
is a toy example.
Turning down the optimization level to 2:
@example
gcc_jit_context_set_int_option (
ctxt,
GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
3);
@end example
@noindent
yields this code, which is simple enough to quote in its entirety:
@example
.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
@end example
@noindent
Note that the stack pushing and popping have been eliminated, as has the
recursive call (in favor of an iteration).
@node Putting it all together,Behind the curtain How does our code get optimized?,Examining the generated code,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 putting-it-all-together}@anchor{44}
@subsection Putting it all together
The complete example can be seen in the source tree at
@code{gcc/jit/docs/examples/tut04-toyvm/toyvm.c}
along with a Makefile and a couple of sample .toy scripts:
@example
$ 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.c
$ make toyvm
g++ -Wall -g -o toyvm toyvm.c -lgccjit
$ ./toyvm factorial.toy 10
interpreter result: 3628800
compiler result: 3628800
$ ./toyvm fibonacci.toy 10
interpreter result: 55
compiler result: 55
@end example
@noindent
@node Behind the curtain How does our code get optimized?,,Putting it all together,Tutorial part 4 Adding JIT-compilation to a toy interpreter
@anchor{intro/tutorial04 behind-the-curtain-how-does-our-code-get-optimized}@anchor{45}
@subsection 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:
@example
gcc_jit_context_set_bool_option (state.ctxt,
GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING,
1);
gcc_jit_context_set_bool_option (state.ctxt,
GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES,
1);
@end example
@noindent
This will dump detailed information about the compiler's state to a
directory under @code{/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):
@example
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
@end example
@noindent
The gimple code is converted into Static Single Assignment form,
with annotations for use when generating the debuginfo:
@example
$ less /tmp/libgccjit-KPQbGw/fake.c.016t.ssa
@end example
@noindent
@example
;; 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 */
@end example
@noindent
We can perhaps better see the code by turning off
@pxref{42,,GCC_JIT_BOOL_OPTION_DEBUGINFO} to suppress all those @code{DEBUG}
statements, giving:
@example
$ less /tmp/libgccjit-1Hywc0/fake.c.016t.ssa
@end example
@noindent
@example
;; 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;
@}
@end example
@noindent
Note in the above how all the @pxref{28,,gcc_jit_block} instances we
created have been consolidated into just 3 blocks in GCC's internal
representation: @code{initial}, @code{instr4} and @code{instr9}.
@menu
* Optimizing away stack manipulation::
* Elimination of tail recursion::
@end menu
@node Optimizing away stack manipulation,Elimination of tail recursion,,Behind the curtain How does our code get optimized?
@anchor{intro/tutorial04 optimizing-away-stack-manipulation}@anchor{46}
@subsubsection 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:
@example
$ less /tmp/libgccjit-1Hywc0/fake.c.021t.ccp1
@end example
@noindent
@example
;; 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;
@}
@end example
@noindent
Note how, in the above, all those @code{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:
@example
$ less /tmp/libgccjit-1Hywc0/fake.c.024t.esra
@end example
@noindent
@example
;; 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;
@}
@end example
@noindent
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:
@example
$ less /tmp/libgccjit-1Hywc0/fake.c.026t.copyprop1
@end example
@noindent
@example
;; 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;
@}
@end example
@noindent
Later on, another pass finally eliminated @code{stack_depth} local and the
unused parts of the @cite{stack`} array altogether:
@example
$ less /tmp/libgccjit-1Hywc0/fake.c.036t.release_ssa
@end example
@noindent
@example
;; 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;
@}
@end example
@noindent
@node Elimination of tail recursion,,Optimizing away stack manipulation,Behind the curtain How does our code get optimized?
@anchor{intro/tutorial04 elimination-of-tail-recursion}@anchor{47}
@subsubsection Elimination of tail recursion
Another significant optimization is the detection that the call to
@code{factorial} is tail recursion, which can be eliminated in favor of
an iteration:
@example
$ less /tmp/libgccjit-1Hywc0/fake.c.030t.tailr1
@end example
@noindent
@example
;; 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;
@}
@end example
@noindent
@c Copyright (C) 2015-2018 Free Software Foundation, Inc.
@c Originally contributed by David Malcolm <dmalcolm@redhat.com>
@c
@c This is free software: you can redistribute it and/or modify it
@c under the terms of the GNU General Public License as published by
@c the Free Software Foundation, either version 3 of the License, or
@c (at your option) any later version.
@c
@c This program is distributed in the hope that it will be useful, but
@c WITHOUT ANY WARRANTY; without even the implied warranty of
@c MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@c General Public License for more details.
@c
@c You should have received a copy of the GNU General Public License
@c along with this program. If not, see
@c <http://www.gnu.org/licenses/>.
@node Tutorial part 5 Implementing an Ahead-of-Time compiler,,Tutorial part 4 Adding JIT-compilation to a toy interpreter,Tutorial
@anchor{intro/tutorial05 doc}@anchor{48}@anchor{intro/tutorial05 tutorial-part-5-implementing-an-ahead-of-time-compiler}@anchor{49}
@section Tutorial part 5: Implementing an Ahead-of-Time compiler
If you have a pre-existing language frontend that's compatible with
libgccjit's license, it's possible to hook it up to libgccjit as a
backend. In the previous example we showed
how to do that for in-memory JIT-compilation, but libgccjit can also
compile code directly to a file, allowing you to implement a more
traditional ahead-of-time compiler ("JIT" is something of a misnomer
for this use-case).
The essential difference is to compile the context using
@pxref{4a,,gcc_jit_context_compile_to_file()} rather than
@pxref{15,,gcc_jit_context_compile()}.
@menu
* The "brainf" language::
* Converting a brainf script to libgccjit IR::
* Compiling a context to a file::
* Other forms of ahead-of-time-compilation::
@end menu
@node The "brainf" language,Converting a brainf script to libgccjit IR,,Tutorial part 5 Implementing an Ahead-of-Time compiler
@anchor{intro/tutorial05 the-brainf-language}@anchor{4b}
@subsection The "brainf" language
In this example we use libgccjit to construct an ahead-of-time compiler
for an esoteric programming language that we shall refer to as "brainf".
brainf scripts operate on an array of bytes, with a notional data pointer
within the array.
brainf is hard for humans to read, but it's trivial to write a parser for
it, as there is no lexing; just a stream of bytes. The operations are:
@multitable {xxxxxxxxxxxxxxxxxxxxxxxx} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
@headitem
Character
@tab
Meaning
@item
@code{>}
@tab
@code{idx += 1}
@item
@code{<}
@tab
@code{idx -= 1}
@item
@code{+}
@tab
@code{data[idx] += 1}
@item
@code{-}
@tab
@code{data[idx] -= 1}
@item
@code{.}
@tab
@code{output (data[idx])}
@item
@code{,}
@tab
@code{data[idx] = input ()}
@item
@code{[}
@tab
loop until @code{data[idx] == 0}
@item
@code{]}
@tab
end of loop
@item
Anything else
@tab
ignored
@end multitable
Unlike the previous example, we'll implement an ahead-of-time compiler,
which reads @code{.bf} scripts and outputs executables (though it would
be trivial to have it run them JIT-compiled in-process).
Here's what a simple @code{.bf} script looks like:
@quotation
@example
[
Emit the uppercase alphabet
]
cell 0 = 26
++++++++++++++++++++++++++
cell 1 = 65
>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++<
while cell#0 != 0
[
>
. emit cell#1
+ increment cell@@1
<- decrement cell@@0
]
@end example
@noindent
@end quotation
@cartouche
@quotation Note
This example makes use of whitespace and comments for legibility, but
could have been written as:
@example
++++++++++++++++++++++++++
>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++<
[>.+<-]
@end example
@noindent
It's not a particularly useful language, except for providing
compiler-writers with a test case that's easy to parse. The point
is that you can use @pxref{4a,,gcc_jit_context_compile_to_file()}
to use libgccjit as a backend for a pre-existing language frontend
(provided that the pre-existing frontend is compatible with libgccjit's
license).
@end quotation
@end cartouche
@node Converting a brainf script to libgccjit IR,Compiling a context to a file,The "brainf" language,Tutorial part 5 Implementing an Ahead-of-Time compiler
@anchor{intro/tutorial05 converting-a-brainf-script-to-libgccjit-ir}@anchor{4c}
@subsection Converting a brainf script to libgccjit IR
As before we write simple code to populate a @pxref{8,,gcc_jit_context *}.
@quotation
@example
typedef struct bf_compiler
@{
const char *filename;
int line;
int column;
gcc_jit_context *ctxt;
gcc_jit_type *void_type;
gcc_jit_type *int_type;
gcc_jit_type *byte_type;
gcc_jit_type *array_type;
gcc_jit_function *func_getchar;
gcc_jit_function *func_putchar;
gcc_jit_function *func;
gcc_jit_block *curblock;
gcc_jit_rvalue *int_zero;
gcc_jit_rvalue *int_one;
gcc_jit_rvalue *byte_zero;
gcc_jit_rvalue *byte_one;
gcc_jit_lvalue *data_cells;
gcc_jit_lvalue *idx;
int num_open_parens;
gcc_jit_block *paren_test[MAX_OPEN_PARENS];
gcc_jit_block *paren_body[MAX_OPEN_PARENS];
gcc_jit_block *paren_after[MAX_OPEN_PARENS];
@} bf_compiler;
/* Bail out, with a message on stderr. */
static void
fatal_error (bf_compiler *bfc, const char *msg)
@{
fprintf (stderr,
"%s:%i:%i: %s",
bfc->filename, bfc->line, bfc->column, msg);
abort ();
@}
/* Get "data_cells[idx]" as an lvalue. */
static gcc_jit_lvalue *
bf_get_current_data (bf_compiler *bfc, gcc_jit_location *loc)
@{
return gcc_jit_context_new_array_access (
bfc->ctxt,
loc,
gcc_jit_lvalue_as_rvalue (bfc->data_cells),
gcc_jit_lvalue_as_rvalue (bfc->idx));
@}
/* Get "data_cells[idx] == 0" as a boolean rvalue. */
static gcc_jit_rvalue *
bf_current_data_is_zero (bf_compiler *bfc, gcc_jit_location *loc)
@{
return gcc_jit_context_new_comparison (
bfc->ctxt,
loc,
GCC_JIT_COMPARISON_EQ,
gcc_jit_lvalue_as_rvalue (bf_get_current_data (bfc, loc)),
bfc->byte_zero);
@}
/* Compile one bf character. */
static void
bf_compile_char (bf_compiler *bfc,
unsigned char ch)
@{
gcc_jit_location *loc =
gcc_jit_context_new_location (bfc->ctxt,
bfc->filename,
bfc->line,
bfc->column);
/* Turn this on to trace execution, by injecting putchar ()
of each source char. */
if (0)
@{
gcc_jit_rvalue *arg =
gcc_jit_context_new_rvalue_from_int (
bfc->ctxt,
bfc->int_type,
ch);
gcc_jit_rvalue *call =
gcc_jit_context_new_call (bfc->ctxt,
loc,
bfc->func_putchar,
1, &arg);
gcc_jit_block_add_eval (bfc->curblock,
loc,
call);
@}
switch (ch)
@{
case '>':
gcc_jit_block_add_comment (bfc->curblock,
loc,
"'>': idx += 1;");
gcc_jit_block_add_assignment_op (bfc->curblock,
loc,
bfc->idx,
GCC_JIT_BINARY_OP_PLUS,
bfc->int_one);
break;
case '<':
gcc_jit_block_add_comment (bfc->curblock,
loc,
"'<': idx -= 1;");
gcc_jit_block_add_assignment_op (bfc->curblock,
loc,
bfc->idx,
GCC_JIT_BINARY_OP_MINUS,
bfc->int_one);
break;
case '+':
gcc_jit_block_add_comment (bfc->curblock,
loc,
"'+': data[idx] += 1;");
gcc_jit_block_add_assignment_op (bfc->curblock,
loc,
bf_get_current_data (bfc, loc),
GCC_JIT_BINARY_OP_PLUS,
bfc->byte_one);
break;
case '-':
gcc_jit_block_add_comment (bfc->curblock,
loc,
"'-': data[idx] -= 1;");
gcc_jit_block_add_assignment_op (bfc->curblock,
loc,
bf_get_current_data (bfc, loc),
GCC_JIT_BINARY_OP_MINUS,
bfc->byte_one);
break;
case '.':
@{
gcc_jit_rvalue *arg =
gcc_jit_context_new_cast (
bfc->ctxt,
loc,
gcc_jit_lvalue_as_rvalue (bf_get_current_data (bfc, loc)),
bfc->int_type);
gcc_jit_rvalue *call =
gcc_jit_context_new_call (bfc->ctxt,
loc,
bfc->func_putchar,
1, &arg);
gcc_jit_block_add_comment (bfc->curblock,
loc,
"'.': putchar ((int)data[idx]);");
gcc_jit_block_add_eval (bfc->curblock,
loc,
call);
@}
break;
case ',':
@{
gcc_jit_rvalue *call =
gcc_jit_context_new_call (bfc->ctxt,
loc,
bfc->func_getchar,
0, NULL);
gcc_jit_block_add_comment (
bfc->curblock,
loc,
"',': data[idx] = (unsigned char)getchar ();");
gcc_jit_block_add_assignment (bfc->curblock,
loc,
bf_get_current_data (bfc, loc),
gcc_jit_context_new_cast (
bfc->ctxt,
loc,
call,
bfc->byte_type));
@}
break;
case '[':
@{
gcc_jit_block *loop_test =
gcc_jit_function_new_block (bfc->func, NULL);
gcc_jit_block *on_zero =
gcc_jit_function_new_block (bfc->func, NULL);
gcc_jit_block *on_non_zero =
gcc_jit_function_new_block (bfc->func, NULL);
if (bfc->num_open_parens == MAX_OPEN_PARENS)
fatal_error (bfc, "too many open parens");
gcc_jit_block_end_with_jump (
bfc->curblock,
loc,
loop_test);
gcc_jit_block_add_comment (
loop_test,
loc,
"'['");
gcc_jit_block_end_with_conditional (
loop_test,
loc,
bf_current_data_is_zero (bfc, loc),
on_zero,
on_non_zero);
bfc->paren_test[bfc->num_open_parens] = loop_test;
bfc->paren_body[bfc->num_open_parens] = on_non_zero;
bfc->paren_after[bfc->num_open_parens] = on_zero;
bfc->num_open_parens += 1;
bfc->curblock = on_non_zero;
@}
break;
case ']':
@{
gcc_jit_block_add_comment (
bfc->curblock,
loc,
"']'");
if (bfc->num_open_parens == 0)
fatal_error (bfc, "mismatching parens");
bfc->num_open_parens -= 1;
gcc_jit_block_end_with_jump (
bfc->curblock,
loc,
bfc->paren_test[bfc->num_open_parens]);
bfc->curblock = bfc->paren_after[bfc->num_open_parens];
@}
break;
case '\n':
bfc->line +=1;
bfc->column = 0;
break;
@}
if (ch != '\n')
bfc->column += 1;
@}
/* Compile the given .bf file into a gcc_jit_context, containing a
single "main" function suitable for compiling into an executable. */
gcc_jit_context *
bf_compile (const char *filename)
@{
bf_compiler bfc;
FILE *f_in;
int ch;
memset (&bfc, 0, sizeof (bfc));
bfc.filename = filename;
f_in = fopen (filename, "r");
if (!f_in)
fatal_error (&bfc, "unable to open file");
bfc.line = 1;
bfc.ctxt = gcc_jit_context_acquire ();
gcc_jit_context_set_int_option (
bfc.ctxt,
GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
3);
gcc_jit_context_set_bool_option (
bfc.ctxt,
GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
0);
gcc_jit_context_set_bool_option (
bfc.ctxt,
GCC_JIT_BOOL_OPTION_DEBUGINFO,
1);
gcc_jit_context_set_bool_option (
bfc.ctxt,
GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING,
0);
gcc_jit_context_set_bool_option (
bfc.ctxt,
GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES,
0);
bfc.void_type =
gcc_jit_context_get_type (bfc.ctxt, GCC_JIT_TYPE_VOID);
bfc.int_type =
gcc_jit_context_get_type (bfc.ctxt, GCC_JIT_TYPE_INT);
bfc.byte_type =
gcc_jit_context_get_type (bfc.ctxt, GCC_JIT_TYPE_UNSIGNED_CHAR);
bfc.array_type =
gcc_jit_context_new_array_type (bfc.ctxt,
NULL,
bfc.byte_type,
30000);
bfc.func_getchar =
gcc_jit_context_new_function (bfc.ctxt, NULL,
GCC_JIT_FUNCTION_IMPORTED,
bfc.int_type,
"getchar",
0, NULL,
0);
gcc_jit_param *param_c =
gcc_jit_context_new_param (bfc.ctxt, NULL, bfc.int_type, "c");
bfc.func_putchar =
gcc_jit_context_new_function (bfc.ctxt, NULL,
GCC_JIT_FUNCTION_IMPORTED,
bfc.void_type,
"putchar",
1, &param_c,
0);
bfc.func = make_main (bfc.ctxt);
bfc.curblock =
gcc_jit_function_new_block (bfc.func, "initial");
bfc.int_zero = gcc_jit_context_zero (bfc.ctxt, bfc.int_type);
bfc.int_one = gcc_jit_context_one (bfc.ctxt, bfc.int_type);
bfc.byte_zero = gcc_jit_context_zero (bfc.ctxt, bfc.byte_type);
bfc.byte_one = gcc_jit_context_one (bfc.ctxt, bfc.byte_type);
bfc.data_cells =
gcc_jit_context_new_global (bfc.ctxt, NULL,
GCC_JIT_GLOBAL_INTERNAL,
bfc.array_type,
"data_cells");
bfc.idx =