.. Copyright (C) 2014-2022 Free Software Foundation, Inc. Originally contributed by David Malcolm 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 . .. default-domain:: cpp Tutorial part 2: Creating a trivial machine code function --------------------------------------------------------- Consider this C function: .. code-block:: c int square (int i) { return i * i; } How can we construct this at run-time using libgccjit's C++ API? First we need to include the relevant header: .. code-block:: c++ #include All state associated with compilation is associated with a :type:`gccjit::context`, which is a thin C++ wrapper around the C API's :c:expr:`gcc_jit_context *`. Create one using :func:`gccjit::context::acquire`: .. code-block:: c++ gccjit::context ctxt; ctxt = gccjit::context::acquire (); The JIT library has a system of types. It is statically-typed: every expression is of a specific type, fixed at compile-time. In our example, all of the expressions are of the C `int` type, so let's obtain this from the context, as a :type:`gccjit::type`, using :func:`gccjit::context::get_type`: .. code-block:: c++ gccjit::type int_type = ctxt.get_type (GCC_JIT_TYPE_INT); :type:`gccjit::type` is an example of a "contextual" object: every entity in the API is associated with a :type:`gccjit::context`. Memory management is easy: all such "contextual" objects are automatically cleaned up for you when the context is released, using :func:`gccjit::context::release`: .. code-block:: c++ ctxt.release (); so you don't need to manually track and cleanup all objects, just the contexts. All of the C++ classes in the API are thin wrappers around pointers to types in the C API. The C++ class hierarchy within the ``gccjit`` namespace looks like this:: +- object +- location +- type +- struct +- field +- function +- block +- rvalue +- lvalue +- param One thing you can do with a :type:`gccjit::object` is to ask it for a human-readable description as a :type:`std::string`, using :func:`gccjit::object::get_debug_string`: .. code-block:: c++ printf ("obj: %s\n", obj.get_debug_string ().c_str ()); giving this text on stdout: .. code-block:: bash obj: int This is invaluable when debugging. Let's create the function. To do so, we first need to construct its single parameter, specifying its type and giving it a name, using :func:`gccjit::context::new_param`: .. code-block:: c++ gccjit::param param_i = ctxt.new_param (int_type, "i"); and we can then make a vector of all of the params of the function, in this case just one: .. code-block:: c++ std::vector params; params.push_back (param_i); Now we can create the function, using :cpp:func:`gccjit::context::new_function`: .. code-block:: c++ gccjit::function func = ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED, int_type, "square", params, 0); To define the code within the function, we must create basic blocks containing statements. Every basic block contains a list of statements, eventually terminated by a statement that either returns, or jumps to another basic block. Our function has no control-flow, so we just need one basic block: .. code-block:: c++ gccjit::block block = func.new_block (); Our basic block is relatively simple: it immediately terminates by returning the value of an expression. We can build the expression using :func:`gccjit::context::new_binary_op`: .. code-block:: c++ gccjit::rvalue expr = ctxt.new_binary_op ( GCC_JIT_BINARY_OP_MULT, int_type, param_i, param_i); A :type:`gccjit::rvalue` is another example of a :type:`gccjit::object` subclass. As before, we can print it with :func:`gccjit::object::get_debug_string`. .. code-block:: c++ printf ("expr: %s\n", expr.get_debug_string ().c_str ()); giving this output: .. code-block:: bash expr: i * i Note that :type:`gccjit::rvalue` provides numerous overloaded operators which can be used to dramatically reduce the amount of typing needed. We can build the above binary operation more directly with this one-liner: .. code-block:: c++ gccjit::rvalue expr = param_i * param_i; Creating the expression in itself doesn't do anything; we have to add this expression to a statement within the block. In this case, we use it to build a return statement, which terminates the basic block: .. code-block:: c++ block.end_with_return (expr); OK, we've populated the context. We can now compile it using :func:`gccjit::context::compile`: .. code-block:: c++ gcc_jit_result *result; result = ctxt.compile (); and get a :c:expr:`gcc_jit_result *`. We can now use :c:func:`gcc_jit_result_get_code` to look up a specific machine code routine within the result, in this case, the function we created above. .. code-block:: c++ void *fn_ptr = gcc_jit_result_get_code (result, "square"); if (!fn_ptr) { fprintf (stderr, "NULL fn_ptr"); goto error; } We can now cast the pointer to an appropriate function pointer type, and then call it: .. code-block:: c++ typedef int (*fn_type) (int); fn_type square = (fn_type)fn_ptr; printf ("result: %d", square (5)); .. code-block:: bash result: 25 Options ******* To get more information on what's going on, you can set debugging flags on the context using :func:`gccjit::context::set_bool_option`. .. (I'm deliberately not mentioning :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE` here since I think it's probably more of use to implementors than to users) Setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE` will dump a C-like representation to stderr when you compile (GCC's "GIMPLE" representation): .. code-block:: c++ ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1); result = ctxt.compile (); .. code-block:: c square (signed int i) { signed int D.260; entry: D.260 = i * i; return D.260; } We can see the generated machine code in assembler form (on stderr) by setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE` on the context before compiling: .. code-block:: c++ ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, 1); result = ctxt.compile (); .. code-block:: gas .file "fake.c" .text .globl square .type square, @function square: .LFB6: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) .L14: movl -4(%rbp), %eax imull -4(%rbp), %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE6: .size square, .-square .ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2)" .section .note.GNU-stack,"",@progbits By default, no optimizations are performed, the equivalent of GCC's `-O0` option. We can turn things up to e.g. `-O3` by calling :func:`gccjit::context::set_int_option` with :c:macro:`GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL`: .. code-block:: c++ ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3); .. code-block:: gas .file "fake.c" .text .p2align 4,,15 .globl square .type square, @function square: .LFB7: .cfi_startproc .L16: movl %edi, %eax imull %edi, %eax ret .cfi_endproc .LFE7: .size square, .-square .ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2)" .section .note.GNU-stack,"",@progbits Naturally this has only a small effect on such a trivial function. Full example ************ Here's what the above looks like as a complete program: .. literalinclude:: ../../examples/tut02-square.cc :lines: 1- :language: c++ Building and running it: .. code-block:: console $ gcc \ tut02-square.cc \ -o tut02-square \ -lgccjit # Run the built program: $ ./tut02-square result: 25