| \input texinfo @c -*-texinfo-*- |
| @c %**start of header |
| @setfilename libgccjit.info |
| @documentencoding UTF-8 |
| @ifinfo |
| @*Generated by Sphinx 2.2.2.@* |
| @end ifinfo |
| @settitle libgccjit Documentation |
| @defindex ge |
| @paragraphindent 0 |
| @exampleindent 4 |
| @finalout |
| @dircategory Miscellaneous |
| @direntry |
| * libgccjit: (libgccjit.info). GCC-based Just In Time compiler library. |
| @end direntry |
| |
| @definfoenclose strong,`,' |
| @definfoenclose emph,`,' |
| @c %**end of header |
| |
| @copying |
| @quotation |
| libgccjit 12.0.1 (experimental 20220411), Apr 12, 2022 |
| |
| David Malcolm |
| |
| Copyright @copyright{} 2014-2022 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-2022 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 <https://www.gnu.org/licenses/>. |
| |
| This document describes libgccjit@footnote{https://gcc.gnu.org/wiki/JIT}, an API |
| for embedding GCC inside programs and libraries. |
| |
| 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-2022 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 <https://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:: |
| * Using Assembly Language with libgccjit:: |
| |
| 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:: |
| * Reflection API:: |
| |
| Expressions |
| |
| * Rvalues:: |
| * Lvalues:: |
| * Working with pointers@comma{} structs and unions: Working with pointers structs and unions. |
| |
| Rvalues |
| |
| * Simple expressions:: |
| * Constructor 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 |
| |
| * Programmatically checking version:: |
| * 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:: |
| * LIBGCCJIT_ABI_11:: |
| * LIBGCCJIT_ABI_12:: |
| * LIBGCCJIT_ABI_13:: |
| * LIBGCCJIT_ABI_14:: |
| * LIBGCCJIT_ABI_15:: |
| * LIBGCCJIT_ABI_16:: |
| * LIBGCCJIT_ABI_17:: |
| * LIBGCCJIT_ABI_18:: |
| * LIBGCCJIT_ABI_19:: |
| * LIBGCCJIT_ABI_20:: |
| * LIBGCCJIT_ABI_21:: |
| * LIBGCCJIT_ABI_22:: |
| * LIBGCCJIT_ABI_23:: |
| * LIBGCCJIT_ABI_24:: |
| |
| Performance |
| |
| * The timing API:: |
| |
| Using Assembly Language with libgccjit |
| |
| * Adding assembler instructions within a function:: |
| * Adding top-level assembler statements:: |
| |
| 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>. |
| * Using Assembly Language with libgccjit++:: |
| |
| 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>. |
| |
| Using Assembly Language with libgccjit++ |
| |
| * Adding assembler instructions within a function: Adding assembler instructions within a function<2>. |
| * Adding top-level assembler statements: Adding top-level assembler statements<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 doc}@anchor{1}@anchor{intro/index libgccjit}@anchor{2}@anchor{intro/index tutorial}@anchor{3} |
| @chapter Tutorial |
| |
| |
| @c Copyright (C) 2014-2022 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 <https://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-2022 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, ¶m_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, ¶m_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 |
| @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 |
| |
| You should then be able to run the built program: |
| |
| @example |
| $ ./tut01-hello-world |
| hello world |
| @end example |
| |
| @c Copyright (C) 2014-2022 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 <https://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 |
| |
| How can we construct this at run-time using libgccjit? |
| |
| First we need to include the relevant header: |
| |
| @example |
| #include <libgccjit.h> |
| @end example |
| |
| All state associated with compilation is associated with a |
| @ref{8,,gcc_jit_context *}. |
| |
| Create one using @ref{9,,gcc_jit_context_acquire()}: |
| |
| @example |
| gcc_jit_context *ctxt; |
| ctxt = gcc_jit_context_acquire (); |
| @end example |
| |
| 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 @ref{a,,gcc_jit_type *}, using |
| @ref{b,,gcc_jit_context_get_type()}: |
| |
| @example |
| gcc_jit_type *int_type = |
| gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT); |
| @end example |
| |
| @ref{a,,gcc_jit_type *} is an example of a “contextual” object: every |
| entity in the API is associated with a @ref{8,,gcc_jit_context *}. |
| |
| Memory management is easy: all such “contextual” objects are automatically |
| cleaned up for you when the context is released, using |
| @ref{c,,gcc_jit_context_release()}: |
| |
| @example |
| gcc_jit_context_release (ctxt); |
| @end example |
| |
| 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 |
| |
| There are casting methods for upcasting from subclasses to parent classes. |
| For example, @ref{d,,gcc_jit_type_as_object()}: |
| |
| @example |
| gcc_jit_object *obj = gcc_jit_type_as_object (int_type); |
| @end example |
| |
| One thing you can do with a @ref{e,,gcc_jit_object *} is |
| to ask it for a human-readable description, using |
| @ref{f,,gcc_jit_object_get_debug_string()}: |
| |
| @example |
| printf ("obj: %s\n", gcc_jit_object_get_debug_string (obj)); |
| @end example |
| |
| giving this text on stdout: |
| |
| @example |
| obj: int |
| @end example |
| |
| 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 @ref{10,,gcc_jit_context_new_param()}: |
| |
| @example |
| gcc_jit_param *param_i = |
| gcc_jit_context_new_param (ctxt, NULL, int_type, "i"); |
| @end example |
| |
| Now we can create the function, using |
| @ref{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, ¶m_i, |
| 0); |
| @end example |
| |
| 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 |
| |
| Our basic block is relatively simple: it immediately terminates by |
| returning the value of an expression. |
| |
| We can build the expression using @ref{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 |
| |
| A @ref{13,,gcc_jit_rvalue *} is another example of a |
| @ref{e,,gcc_jit_object *} subclass. We can upcast it using |
| @ref{14,,gcc_jit_rvalue_as_object()} and as before print it with |
| @ref{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 |
| |
| giving this output: |
| |
| @example |
| expr: i * i |
| @end example |
| |
| 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 |
| |
| OK, we’ve populated the context. We can now compile it using |
| @ref{15,,gcc_jit_context_compile()}: |
| |
| @example |
| gcc_jit_result *result; |
| result = gcc_jit_context_compile (ctxt); |
| @end example |
| |
| and get a @ref{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 |
| |
| We can now use @ref{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 |
| |
| 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 |
| |
| @example |
| result: 25 |
| @end example |
| |
| Once we’re done with the code, we can release the result: |
| |
| @example |
| gcc_jit_result_release (result); |
| @end example |
| |
| 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 |
| |
| 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 |
| |
| For more information, see the @ref{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 @ref{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 @ref{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 |
| |
| @example |
| square (signed int i) |
| @{ |
| signed int D.260; |
| |
| entry: |
| D.260 = i * i; |
| return D.260; |
| @} |
| @end example |
| |
| We can see the generated machine code in assembler form (on stderr) by |
| setting @ref{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 |
| |
| @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 |
| |
| 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 |
| @ref{1e,,gcc_jit_context_set_int_option()} with |
| @ref{1f,,GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL}: |
| |
| @example |
| gcc_jit_context_set_int_option ( |
| ctxt, |
| GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, |
| 3); |
| @end example |
| |
| @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 |
| |
| 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-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include <libgccjit.h> |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| |
| void |
| create_code (gcc_jit_context *ctxt) |
| @{ |
| /* Let's try to inject the equivalent of: |
| |
| int square (int i) |
| @{ |
| return i * i; |
| @} |
| */ |
| gcc_jit_type *int_type = |
| gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT); |
| gcc_jit_param *param_i = |
| gcc_jit_context_new_param (ctxt, NULL, int_type, "i"); |
| gcc_jit_function *func = |
| gcc_jit_context_new_function (ctxt, NULL, |
| GCC_JIT_FUNCTION_EXPORTED, |
| int_type, |
| "square", |
| 1, ¶m_i, |
| 0); |
| |
| gcc_jit_block *block = gcc_jit_function_new_block (func, NULL); |
| |
| gcc_jit_rvalue *expr = |
| gcc_jit_context_new_binary_op ( |
| ctxt, NULL, |
| GCC_JIT_BINARY_OP_MULT, int_type, |
| gcc_jit_param_as_rvalue (param_i), |
| gcc_jit_param_as_rvalue (param_i)); |
| |
| gcc_jit_block_end_with_return (block, NULL, expr); |
| @} |
| |
| int |
| main (int argc, char **argv) |
| @{ |
| gcc_jit_context *ctxt = NULL; |
| gcc_jit_result *result = NULL; |
| |
| /* Get a "context" object for working with the library. */ |
| ctxt = gcc_jit_context_acquire (); |
| if (!ctxt) |
| @{ |
| fprintf (stderr, "NULL ctxt"); |
| goto error; |
| @} |
| |
| /* Set some options on the context. |
| Let's see the code being generated, in assembler form. */ |
| gcc_jit_context_set_bool_option ( |
| ctxt, |
| GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, |
| 0); |
| |
| /* Populate the context. */ |
| create_code (ctxt); |
| |
| /* Compile the code. */ |
| result = gcc_jit_context_compile (ctxt); |
| if (!result) |
| @{ |
| fprintf (stderr, "NULL result"); |
| goto error; |
| @} |
| |
| /* We're done with the context; we can release it: */ |
| gcc_jit_context_release (ctxt); |
| ctxt = NULL; |
| |
| /* Extract the generated code from "result". */ |
| void *fn_ptr = gcc_jit_result_get_code (result, "square"); |
| if (!fn_ptr) |
| @{ |
| fprintf (stderr, "NULL fn_ptr"); |
| goto error; |
| @} |
| |
| typedef int (*fn_type) (int); |
| fn_type square = (fn_type)fn_ptr; |
| printf ("result: %d\n", square (5)); |
| |
| error: |
| if (ctxt) |
| gcc_jit_context_release (ctxt); |
| if (result) |
| gcc_jit_result_release (result); |
| return 0; |
| @} |
| @end example |
| @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 |
| |
| @c Copyright (C) 2014-2022 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 <https://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 doc}@anchor{21}@anchor{intro/tutorial03 tutorial-part-3-loops-and-variables}@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 |
| @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 |
| @end quotation |
| |
| Here’s what the final control flow graph will look like: |
| |
| @quotation |
| |
| |
| @float Figure |
| |
| @image{libgccjit-figures/sum-of-squares1,,,image of a control flow graph,png} |
| |
| @end float |
| |
| @end quotation |
| |
| As before, we include the libgccjit header and make a |
| @ref{8,,gcc_jit_context *}. |
| |
| @example |
| #include <libgccjit.h> |
| |
| void test (void) |
| @{ |
| gcc_jit_context *ctxt; |
| ctxt = gcc_jit_context_acquire (); |
| @end example |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| @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 @ref{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 |
| @ref{a,,gcc_jit_type *}. |
| |
| Anothe important class is @ref{24,,gcc_jit_lvalue *}. |
| A @ref{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 |
| |
| Note that @ref{24,,gcc_jit_lvalue *} is a subclass of |
| @ref{13,,gcc_jit_rvalue *}, where in an assignment of the form: |
| |
| @example |
| LVALUE_A = LVALUE_B; |
| @end example |
| |
| 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 |
| |
| which is a @ref{13,,gcc_jit_rvalue *}, and the various function |
| parameters: @cite{param_i} and @cite{param_n}, instances of |
| @ref{25,,gcc_jit_param *}, which is a subclass of |
| @ref{24,,gcc_jit_lvalue *} (and, in turn, of @ref{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 @ref{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 |
| |
| These are instances of @ref{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 |
| |
| 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 @ref{28,,gcc_jit_block *} instances within the |
| @ref{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 |
| |
| 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 |
| @ref{2a,,gcc_jit_block_add_assignment()} to add |
| an assignment statement, and using @ref{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 |
| |
| 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 |
| |
| 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 |
| @ref{13,,gcc_jit_rvalue *}, in this case the comparison of @cite{i} and @cite{n}. |
| We build the comparison using @ref{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 |
| |
| and can then use this to add @cite{b_loop_cond}’s sole statement, via |
| @ref{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 |
| |
| 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 |
| @ref{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 |
| |
| The @cite{i++} can be thought of as @cite{i += 1}, and can thus be handled in |
| a similar way. We use @ref{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 |
| |
| @cartouche |
| @quotation Note |
| For numeric constants other than 0 or 1, we could use |
| @ref{30,,gcc_jit_context_new_rvalue_from_int()} and |
| @ref{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 |
| |
| 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 |
| |
| 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 |
| |
| @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 |
| |
| @example |
| result: 285 |
| @end example |
| |
| @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 |
| @ref{33,,gcc_jit_function_dump_to_dot()}: |
| |
| @example |
| gcc_jit_function_dump_to_dot (func, "/tmp/sum-of-squares.dot"); |
| @end example |
| |
| 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 |
| |
| 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{libgccjit-figures/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-2022 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 |
| @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 |
| |
| @c Copyright (C) 2014-2022 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 <https://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 doc}@anchor{35}@anchor{intro/tutorial04 tutorial-part-4-adding-jit-compilation-to-a-toy-interpreter}@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 |
| @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 |
| @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 |
| @end quotation |
| |
| The lifetime of the code is tied to that of a @ref{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 @ref{39,,gcc_jit_result_release()}: |
| |
| @quotation |
| |
| @example |
| |
| struct toyvm_compiled_function |
| @{ |
| gcc_jit_result *cf_jit_result; |
| toyvm_compiled_code cf_code; |
| @}; |
| |
| @end example |
| @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 |
| |
| 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 |
| |
| 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 |
| @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 |
| @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 |
| @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 |
| @end quotation |
| |
| We will support single-stepping through the generated code in the |
| debugger, so we need to create @ref{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 |
| @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 |
| @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 |
| @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 |
| @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[100]; |
| sprintf (buf, "instr%i", pc); |
| state.op_blocks[pc] = gcc_jit_function_new_block (state.fn, buf); |
| @} |
| |
| @end example |
| @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 |
| @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 |
| @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 |
| |
| @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 |
| @ref{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 |
| @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 |
| @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 |
| @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 @ref{33,,gcc_jit_function_dump_to_dot()}: |
| |
| @example |
| gcc_jit_function_dump_to_dot (state.fn, "/tmp/factorial.dot"); |
| @end example |
| |
| 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{libgccjit-figures/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 |
| @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 |
| @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 @ref{41,,gcc_jit_context_new_location()} and using the |
| results. |
| |
| @item |
| Enable the generation of debugging information, by setting |
| @ref{42,,GCC_JIT_BOOL_OPTION_DEBUGINFO} on the |
| @ref{8,,gcc_jit_context} via |
| @ref{1b,,gcc_jit_context_set_bool_option()}: |
| |
| @example |
| gcc_jit_context_set_bool_option ( |
| ctxt, |
| GCC_JIT_BOOL_OPTION_DEBUGINFO, |
| 1); |
| @end example |
| @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 |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| @ref{1e,,gcc_jit_context_set_int_option()} with |
| @ref{1f,,GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL}: |
| |
| @example |
| gcc_jit_context_set_int_option ( |
| ctxt, |
| GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, |
| 3); |
| @end example |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| @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 |
| |
| 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 |
| |
| 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 |
| |
| @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 |
| |
| We can perhaps better see the code by turning off |
| @ref{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 |
| |
| @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 |
| |
| Note in the above how all the @ref{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 |
| |
| @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 |
| |
| 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 |
| |
| @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 |
| |
| 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 |
| |
| @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 |
| |
| 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 |
| |
| @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 |
| |
| @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 |
| |
| @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 |
| |
| @c Copyright (C) 2015-2022 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 <https://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 |
| @ref{4a,,gcc_jit_context_compile_to_file()} rather than |
| @ref{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 |
| @end quotation |
| |
| @cartouche |
| @quotation Note |
| This example makes use of whitespace and comments for legibility, but |
| could have been written as: |
| |
| @example |
| ++++++++++++++++++++++++++ |
| >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< |
| [>.+<-] |
| @end example |
| |
| 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 @ref{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 @ref{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, ¶m_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 = |
| gcc_jit_function_new_local (bfc.func, NULL, |
| bfc.int_type, |
| "idx"); |
| |
| gcc_jit_block_add_comment (bfc.curblock, |
| NULL, |
| "idx = 0;"); |
| gcc_jit_block_add_assignment (bfc.curblock, |
| NULL, |
| bfc.idx, |
| bfc.int_zero); |
| |
| bfc.num_open_parens = 0; |
| |
| while ( EOF != (ch = fgetc (f_in))) |
| bf_compile_char (&bfc, (unsigned char)ch); |
| |
| gcc_jit_block_end_with_return (bfc.curblock, NULL, bfc.int_zero); |
| |
| fclose (f_in); |
| |
| return bfc.ctxt; |
| @} |
| |
| @end example |
| @end quotation |
| |
| @node Compiling a context to a file,Other forms of ahead-of-time-compilation,Converting a brainf script to libgccjit IR,Tutorial part 5 Implementing an Ahead-of-Time compiler |
| @anchor{intro/tutorial05 compiling-a-context-to-a-file}@anchor{4d} |
| @subsection Compiling a context to a file |
| |
| |
| Unlike the previous tutorial, this time we’ll compile the context |
| directly to an executable, using @ref{4a,,gcc_jit_context_compile_to_file()}: |
| |
| @example |
| gcc_jit_context_compile_to_file (ctxt, |
| GCC_JIT_OUTPUT_KIND_EXECUTABLE, |
| output_file); |
| @end example |
| |
| Here’s the top-level of the compiler, which is what actually calls into |
| @ref{4a,,gcc_jit_context_compile_to_file()}: |
| |
| @quotation |
| |
| @example |
| |
| int |
| main (int argc, char **argv) |
| @{ |
| const char *input_file; |
| const char *output_file; |
| gcc_jit_context *ctxt; |
| const char *err; |
| |
| if (argc != 3) |
| @{ |
| fprintf (stderr, "%s: INPUT_FILE OUTPUT_FILE\n", argv[0]); |
| return 1; |
| @} |
| |
| input_file = argv[1]; |
| output_file = argv[2]; |
| ctxt = bf_compile (input_file); |
| |
| gcc_jit_context_compile_to_file (ctxt, |
| GCC_JIT_OUTPUT_KIND_EXECUTABLE, |
| output_file); |
| |
| err = gcc_jit_context_get_first_error (ctxt); |
| |
| if (err) |
| @{ |
| gcc_jit_context_release (ctxt); |
| return 1; |
| @} |
| |
| gcc_jit_context_release (ctxt); |
| return 0; |
| @} |
| |
| @end example |
| @end quotation |
| |
| Note how once the context is populated you could trivially instead compile |
| it to memory using @ref{15,,gcc_jit_context_compile()} and run it in-process |
| as in the previous tutorial. |
| |
| To create an executable, we need to export a @code{main} function. Here’s |
| how to create one from the JIT API: |
| |
| @quotation |
| |
| @example |
| |
| /* Make "main" function: |
| int |
| main (int argc, char **argv) |
| @{ |
| ... |
| @} |
| */ |
| static gcc_jit_function * |
| make_main (gcc_jit_context *ctxt) |
| @{ |
| gcc_jit_type *int_type = |
| gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT); |
| gcc_jit_param *param_argc = |
| gcc_jit_context_new_param (ctxt, NULL, int_type, "argc"); |
| gcc_jit_type *char_ptr_ptr_type = |
| gcc_jit_type_get_pointer ( |
| gcc_jit_type_get_pointer ( |
| gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_CHAR))); |
| gcc_jit_param *param_argv = |
| gcc_jit_context_new_param (ctxt, NULL, char_ptr_ptr_type, "argv"); |
| gcc_jit_param *params[2] = @{param_argc, param_argv@}; |
| gcc_jit_function *func_main = |
| gcc_jit_context_new_function (ctxt, NULL, |
| GCC_JIT_FUNCTION_EXPORTED, |
| int_type, |
| "main", |
| 2, params, |
| 0); |
| return func_main; |
| @} |
| |
| @end example |
| @end quotation |
| |
| @cartouche |
| @quotation Note |
| The above implementation ignores @code{argc} and @code{argv}, but you could |
| make use of them by exposing @code{param_argc} and @code{param_argv} to the |
| caller. |
| @end quotation |
| @end cartouche |
| |
| Upon compiling this C code, we obtain a bf-to-machine-code compiler; |
| let’s call it @code{bfc}: |
| |
| @example |
| $ gcc \ |
| tut05-bf.c \ |
| -o bfc \ |
| -lgccjit |
| @end example |
| |
| We can now use @code{bfc} to compile .bf files into machine code executables: |
| |
| @example |
| $ ./bfc \ |
| emit-alphabet.bf \ |
| a.out |
| @end example |
| |
| which we can run directly: |
| |
| @example |
| $ ./a.out |
| ABCDEFGHIJKLMNOPQRSTUVWXYZ |
| @end example |
| |
| Success! |
| |
| We can also inspect the generated executable using standard tools: |
| |
| @example |
| $ objdump -d a.out |less |
| @end example |
| |
| which shows that libgccjit has managed to optimize the function |
| somewhat (for example, the runs of 26 and 65 increment operations |
| have become integer constants 0x1a and 0x41): |
| |
| @example |
| 0000000000400620 <main>: |
| 400620: 80 3d 39 0a 20 00 00 cmpb $0x0,0x200a39(%rip) # 601060 <data |
| 400627: 74 07 je 400630 <main |
| 400629: eb fe jmp 400629 <main+0x9> |
| 40062b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) |
| 400630: 48 83 ec 08 sub $0x8,%rsp |
| 400634: 0f b6 05 26 0a 20 00 movzbl 0x200a26(%rip),%eax # 601061 <data_cells+0x1> |
| 40063b: c6 05 1e 0a 20 00 1a movb $0x1a,0x200a1e(%rip) # 601060 <data_cells> |
| 400642: 8d 78 41 lea 0x41(%rax),%edi |
| 400645: 40 88 3d 15 0a 20 00 mov %dil,0x200a15(%rip) # 601061 <data_cells+0x1> |
| 40064c: 0f 1f 40 00 nopl 0x0(%rax) |
| 400650: 40 0f b6 ff movzbl %dil,%edi |
| 400654: e8 87 fe ff ff callq 4004e0 <putchar@@plt> |
| 400659: 0f b6 05 01 0a 20 00 movzbl 0x200a01(%rip),%eax # 601061 <data_cells+0x1> |
| 400660: 80 2d f9 09 20 00 01 subb $0x1,0x2009f9(%rip) # 601060 <data_cells> |
| 400667: 8d 78 01 lea 0x1(%rax),%edi |
| 40066a: 40 88 3d f0 09 20 00 mov %dil,0x2009f0(%rip) # 601061 <data_cells+0x1> |
| 400671: 75 dd jne 400650 <main+0x30> |
| 400673: 31 c0 xor %eax,%eax |
| 400675: 48 83 c4 08 add $0x8,%rsp |
| 400679: c3 retq |
| 40067a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) |
| @end example |
| |
| We also set up debugging information (via |
| @ref{41,,gcc_jit_context_new_location()} and |
| @ref{42,,GCC_JIT_BOOL_OPTION_DEBUGINFO}), so it’s possible to use @code{gdb} |
| to singlestep through the generated binary and inspect the internal |
| state @code{idx} and @code{data_cells}: |
| |
| @example |
| (gdb) break main |
| Breakpoint 1 at 0x400790 |
| (gdb) run |
| Starting program: a.out |
| |
| Breakpoint 1, 0x0000000000400790 in main (argc=1, argv=0x7fffffffe448) |
| (gdb) stepi |
| 0x0000000000400797 in main (argc=1, argv=0x7fffffffe448) |
| (gdb) stepi |
| 0x00000000004007a0 in main (argc=1, argv=0x7fffffffe448) |
| (gdb) stepi |
| 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< |
| (gdb) list |
| 4 |
| 5 cell 0 = 26 |
| 6 ++++++++++++++++++++++++++ |
| 7 |
| 8 cell 1 = 65 |
| 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< |
| 10 |
| 11 while cell#0 != 0 |
| 12 [ |
| 13 > |
| (gdb) n |
| 6 ++++++++++++++++++++++++++ |
| (gdb) n |
| 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< |
| (gdb) p idx |
| $1 = 1 |
| (gdb) p data_cells |
| $2 = "\032", '\000' <repeats 29998 times> |
| (gdb) p data_cells[0] |
| $3 = 26 '\032' |
| (gdb) p data_cells[1] |
| $4 = 0 '\000' |
| (gdb) list |
| 4 |
| 5 cell 0 = 26 |
| 6 ++++++++++++++++++++++++++ |
| 7 |
| 8 cell 1 = 65 |
| 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< |
| 10 |
| 11 while cell#0 != 0 |
| 12 [ |
| 13 > |
| @end example |
| |
| @node Other forms of ahead-of-time-compilation,,Compiling a context to a file,Tutorial part 5 Implementing an Ahead-of-Time compiler |
| @anchor{intro/tutorial05 other-forms-of-ahead-of-time-compilation}@anchor{4e} |
| @subsection Other forms of ahead-of-time-compilation |
| |
| |
| The above demonstrates compiling a @ref{8,,gcc_jit_context *} directly |
| to an executable. It’s also possible to compile it to an object file, |
| and to a dynamic library. See the documentation of |
| @ref{4a,,gcc_jit_context_compile_to_file()} for more information. |
| |
| @c Copyright (C) 2014-2022 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 <https://www.gnu.org/licenses/>. |
| |
| @node Topic Reference,C++ bindings for libgccjit,Tutorial,Top |
| @anchor{topics/index doc}@anchor{4f}@anchor{topics/index topic-reference}@anchor{50} |
| @chapter Topic Reference |
| |
| |
| @c Copyright (C) 2014-2022 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 <https://www.gnu.org/licenses/>. |
| |
| @menu |
| * Compilation contexts:: |
| * Objects:: |
| * Types:: |
| * Expressions:: |
| * Creating and using functions:: |
| * Function pointers: Function pointers<2>. |
| * Source Locations:: |
| * Compiling a context:: |
| * ABI and API compatibility:: |
| * Performance:: |
| * Using Assembly Language with libgccjit:: |
| |
| @end menu |
| |
| @node Compilation contexts,Objects,,Topic Reference |
| @anchor{topics/contexts doc}@anchor{51}@anchor{topics/contexts compilation-contexts}@anchor{52} |
| @section Compilation contexts |
| |
| |
| @geindex gcc_jit_context (C type) |
| @anchor{topics/contexts c gcc_jit_context}@anchor{8} |
| @deffn {C Type} gcc_jit_context |
| @end deffn |
| |
| The top-level of the API is the @ref{8,,gcc_jit_context} type. |
| |
| A @ref{8,,gcc_jit_context} instance encapsulates the state of a |
| compilation. |
| |
| You can set up options on it, and add types, functions and code. |
| Invoking @ref{15,,gcc_jit_context_compile()} on it gives you a |
| @ref{16,,gcc_jit_result}. |
| |
| @menu |
| * Lifetime-management:: |
| * Thread-safety:: |
| * Error-handling: Error-handling<2>. |
| * Debugging:: |
| * Options: Options<2>. |
| |
| @end menu |
| |
| @node Lifetime-management,Thread-safety,,Compilation contexts |
| @anchor{topics/contexts lifetime-management}@anchor{53} |
| @subsection Lifetime-management |
| |
| |
| Contexts are the unit of lifetime-management within the API: objects |
| have their lifetime bounded by the context they are created within, and |
| cleanup of such objects is done for you when the context is released. |
| |
| @geindex gcc_jit_context_acquire (C function) |
| @anchor{topics/contexts c
|