| .. Copyright (C) 2014-2021 Free Software Foundation, Inc. |
| Originally contributed by David Malcolm <dmalcolm@redhat.com> |
| |
| This is free software: you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see |
| <http://www.gnu.org/licenses/>. |
| |
| .. default-domain:: c |
| |
| Tutorial part 2: Creating a trivial machine code function |
| --------------------------------------------------------- |
| |
| Consider this C function: |
| |
| .. code-block:: c |
| |
| int square (int i) |
| { |
| return i * i; |
| } |
| |
| How can we construct this at run-time using libgccjit? |
| |
| First we need to include the relevant header: |
| |
| .. code-block:: c |
| |
| #include <libgccjit.h> |
| |
| All state associated with compilation is associated with a |
| :c:type:`gcc_jit_context *`. |
| |
| Create one using :c:func:`gcc_jit_context_acquire`: |
| |
| .. code-block:: c |
| |
| gcc_jit_context *ctxt; |
| ctxt = gcc_jit_context_acquire (); |
| |
| The JIT library has a system of types. It is statically-typed: every |
| expression is of a specific type, fixed at compile-time. In our example, |
| all of the expressions are of the C `int` type, so let's obtain this from |
| the context, as a :c:type:`gcc_jit_type *`, using |
| :c:func:`gcc_jit_context_get_type`: |
| |
| .. code-block:: c |
| |
| gcc_jit_type *int_type = |
| gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT); |
| |
| :c:type:`gcc_jit_type *` is an example of a "contextual" object: every |
| entity in the API is associated with a :c:type:`gcc_jit_context *`. |
| |
| Memory management is easy: all such "contextual" objects are automatically |
| cleaned up for you when the context is released, using |
| :c:func:`gcc_jit_context_release`: |
| |
| .. code-block:: c |
| |
| gcc_jit_context_release (ctxt); |
| |
| so you don't need to manually track and cleanup all objects, just the |
| contexts. |
| |
| Although the API is C-based, there is a form of class hierarchy, which |
| looks like this:: |
| |
| +- gcc_jit_object |
| +- gcc_jit_location |
| +- gcc_jit_type |
| +- gcc_jit_struct |
| +- gcc_jit_field |
| +- gcc_jit_function |
| +- gcc_jit_block |
| +- gcc_jit_rvalue |
| +- gcc_jit_lvalue |
| +- gcc_jit_param |
| |
| There are casting methods for upcasting from subclasses to parent classes. |
| For example, :c:func:`gcc_jit_type_as_object`: |
| |
| .. code-block:: c |
| |
| gcc_jit_object *obj = gcc_jit_type_as_object (int_type); |
| |
| One thing you can do with a :c:type:`gcc_jit_object *` is |
| to ask it for a human-readable description, using |
| :c:func:`gcc_jit_object_get_debug_string`: |
| |
| .. code-block:: c |
| |
| printf ("obj: %s\n", gcc_jit_object_get_debug_string (obj)); |
| |
| giving this text on stdout: |
| |
| .. code-block:: bash |
| |
| obj: int |
| |
| This is invaluable when debugging. |
| |
| Let's create the function. To do so, we first need to construct |
| its single parameter, specifying its type and giving it a name, |
| using :c:func:`gcc_jit_context_new_param`: |
| |
| .. code-block:: c |
| |
| gcc_jit_param *param_i = |
| gcc_jit_context_new_param (ctxt, NULL, int_type, "i"); |
| |
| Now we can create the function, using |
| :c:func:`gcc_jit_context_new_function`: |
| |
| .. code-block:: c |
| |
| gcc_jit_function *func = |
| gcc_jit_context_new_function (ctxt, NULL, |
| GCC_JIT_FUNCTION_EXPORTED, |
| int_type, |
| "square", |
| 1, ¶m_i, |
| 0); |
| |
| To define the code within the function, we must create basic blocks |
| containing statements. |
| |
| Every basic block contains a list of statements, eventually terminated |
| by a statement that either returns, or jumps to another basic block. |
| |
| Our function has no control-flow, so we just need one basic block: |
| |
| .. code-block:: c |
| |
| gcc_jit_block *block = gcc_jit_function_new_block (func, NULL); |
| |
| Our basic block is relatively simple: it immediately terminates by |
| returning the value of an expression. |
| |
| We can build the expression using :c:func:`gcc_jit_context_new_binary_op`: |
| |
| .. code-block:: c |
| |
| gcc_jit_rvalue *expr = |
| gcc_jit_context_new_binary_op ( |
| ctxt, NULL, |
| GCC_JIT_BINARY_OP_MULT, int_type, |
| gcc_jit_param_as_rvalue (param_i), |
| gcc_jit_param_as_rvalue (param_i)); |
| |
| A :c:type:`gcc_jit_rvalue *` is another example of a |
| :c:type:`gcc_jit_object *` subclass. We can upcast it using |
| :c:func:`gcc_jit_rvalue_as_object` and as before print it with |
| :c:func:`gcc_jit_object_get_debug_string`. |
| |
| .. code-block:: c |
| |
| printf ("expr: %s\n", |
| gcc_jit_object_get_debug_string ( |
| gcc_jit_rvalue_as_object (expr))); |
| |
| giving this output: |
| |
| .. code-block:: bash |
| |
| expr: i * i |
| |
| Creating the expression in itself doesn't do anything; we have to add |
| this expression to a statement within the block. In this case, we use it |
| to build a return statement, which terminates the basic block: |
| |
| .. code-block:: c |
| |
| gcc_jit_block_end_with_return (block, NULL, expr); |
| |
| OK, we've populated the context. We can now compile it using |
| :c:func:`gcc_jit_context_compile`: |
| |
| .. code-block:: c |
| |
| gcc_jit_result *result; |
| result = gcc_jit_context_compile (ctxt); |
| |
| and get a :c:type:`gcc_jit_result *`. |
| |
| At this point we're done with the context; we can release it: |
| |
| .. code-block:: c |
| |
| gcc_jit_context_release (ctxt); |
| |
| We can now use :c:func:`gcc_jit_result_get_code` to look up a specific |
| machine code routine within the result, in this case, the function we |
| created above. |
| |
| .. code-block:: c |
| |
| void *fn_ptr = gcc_jit_result_get_code (result, "square"); |
| if (!fn_ptr) |
| { |
| fprintf (stderr, "NULL fn_ptr"); |
| goto error; |
| } |
| |
| We can now cast the pointer to an appropriate function pointer type, and |
| then call it: |
| |
| .. code-block:: c |
| |
| typedef int (*fn_type) (int); |
| fn_type square = (fn_type)fn_ptr; |
| printf ("result: %d", square (5)); |
| |
| .. code-block:: bash |
| |
| result: 25 |
| |
| Once we're done with the code, we can release the result: |
| |
| .. code-block:: c |
| |
| gcc_jit_result_release (result); |
| |
| We can't call ``square`` anymore once we've released ``result``. |
| |
| |
| 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: |
| |
| .. code-block:: console |
| |
| ./buggy-program: error: gcc_jit_block_add_assignment: mismatching types: assignment to i (type: int) from "hello world" (type: const char *) |
| |
| The API is designed to cope with errors without crashing, so you can get |
| away with having a single error-handling check in your code: |
| |
| .. code-block:: c |
| |
| void *fn_ptr = gcc_jit_result_get_code (result, "square"); |
| if (!fn_ptr) |
| { |
| fprintf (stderr, "NULL fn_ptr"); |
| goto error; |
| } |
| |
| For more information, see the :ref:`error-handling guide <error-handling>` |
| within the Topic eference. |
| |
| |
| Options |
| ******* |
| |
| To get more information on what's going on, you can set debugging flags |
| on the context using :c:func:`gcc_jit_context_set_bool_option`. |
| |
| .. (I'm deliberately not mentioning |
| :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE` here since I think |
| it's probably more of use to implementors than to users) |
| |
| Setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE` will dump a |
| C-like representation to stderr when you compile (GCC's "GIMPLE" |
| representation): |
| |
| .. code-block:: c |
| |
| gcc_jit_context_set_bool_option ( |
| ctxt, |
| GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, |
| 1); |
| result = gcc_jit_context_compile (ctxt); |
| |
| .. code-block:: c |
| |
| square (signed int i) |
| { |
| signed int D.260; |
| |
| entry: |
| D.260 = i * i; |
| return D.260; |
| } |
| |
| We can see the generated machine code in assembler form (on stderr) by |
| setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE` on the context |
| before compiling: |
| |
| .. code-block:: c |
| |
| gcc_jit_context_set_bool_option ( |
| ctxt, |
| GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, |
| 1); |
| result = gcc_jit_context_compile (ctxt); |
| |
| .. code-block:: gas |
| |
| .file "fake.c" |
| .text |
| .globl square |
| .type square, @function |
| square: |
| .LFB6: |
| .cfi_startproc |
| pushq %rbp |
| .cfi_def_cfa_offset 16 |
| .cfi_offset 6, -16 |
| movq %rsp, %rbp |
| .cfi_def_cfa_register 6 |
| movl %edi, -4(%rbp) |
| .L14: |
| movl -4(%rbp), %eax |
| imull -4(%rbp), %eax |
| popq %rbp |
| .cfi_def_cfa 7, 8 |
| ret |
| .cfi_endproc |
| .LFE6: |
| .size square, .-square |
| .ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2-0.5.1920c315ff984892399893b380305ab36e07b455.fc20)" |
| .section .note.GNU-stack,"",@progbits |
| |
| By default, no optimizations are performed, the equivalent of GCC's |
| `-O0` option. We can turn things up to e.g. `-O3` by calling |
| :c:func:`gcc_jit_context_set_int_option` with |
| :c:macro:`GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL`: |
| |
| .. code-block:: c |
| |
| gcc_jit_context_set_int_option ( |
| ctxt, |
| GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, |
| 3); |
| |
| .. code-block:: gas |
| |
| .file "fake.c" |
| .text |
| .p2align 4,,15 |
| .globl square |
| .type square, @function |
| square: |
| .LFB7: |
| .cfi_startproc |
| .L16: |
| movl %edi, %eax |
| imull %edi, %eax |
| ret |
| .cfi_endproc |
| .LFE7: |
| .size square, .-square |
| .ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2-0.5.1920c315ff984892399893b380305ab36e07b455.fc20)" |
| .section .note.GNU-stack,"",@progbits |
| |
| Naturally this has only a small effect on such a trivial function. |
| |
| |
| Full example |
| ************ |
| |
| Here's what the above looks like as a complete program: |
| |
| .. literalinclude:: ../examples/tut02-square.c |
| :lines: 1- |
| :language: c |
| |
| Building and running it: |
| |
| .. code-block:: console |
| |
| $ gcc \ |
| tut02-square.c \ |
| -o tut02-square \ |
| -lgccjit |
| |
| # Run the built program: |
| $ ./tut02-square |
| result: 25 |