Home | History | Annotate | Line # | Download | only in intro
      1 .. Copyright (C) 2014-2022 Free Software Foundation, Inc.
      2    Originally contributed by David Malcolm <dmalcolm (a] redhat.com>
      3 
      4    This is free software: you can redistribute it and/or modify it
      5    under the terms of the GNU General Public License as published by
      6    the Free Software Foundation, either version 3 of the License, or
      7    (at your option) any later version.
      8 
      9    This program is distributed in the hope that it will be useful, but
     10    WITHOUT ANY WARRANTY; without even the implied warranty of
     11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12    General Public License for more details.
     13 
     14    You should have received a copy of the GNU General Public License
     15    along with this program.  If not, see
     16    <https://www.gnu.org/licenses/>.
     17 
     18 .. default-domain:: cpp
     19 
     20 Tutorial part 2: Creating a trivial machine code function
     21 ---------------------------------------------------------
     22 
     23 Consider this C function:
     24 
     25 .. code-block:: c
     26 
     27    int square (int i)
     28    {
     29      return i * i;
     30    }
     31 
     32 How can we construct this at run-time using libgccjit's C++ API?
     33 
     34 First we need to include the relevant header:
     35 
     36 .. code-block:: c++
     37 
     38   #include <libgccjit++.h>
     39 
     40 All state associated with compilation is associated with a
     41 :type:`gccjit::context`, which is a thin C++ wrapper around the C API's
     42 :c:type:`gcc_jit_context *`.
     43 
     44 Create one using :func:`gccjit::context::acquire`:
     45 
     46 .. code-block:: c++
     47 
     48   gccjit::context ctxt;
     49   ctxt = gccjit::context::acquire ();
     50 
     51 The JIT library has a system of types.  It is statically-typed: every
     52 expression is of a specific type, fixed at compile-time.  In our example,
     53 all of the expressions are of the C `int` type, so let's obtain this from
     54 the context, as a :type:`gccjit::type`, using
     55 :func:`gccjit::context::get_type`:
     56 
     57 .. code-block:: c++
     58 
     59   gccjit::type int_type = ctxt.get_type (GCC_JIT_TYPE_INT);
     60 
     61 :type:`gccjit::type` is an example of a "contextual" object: every
     62 entity in the API is associated with a :type:`gccjit::context`.
     63 
     64 Memory management is easy: all such "contextual" objects are automatically
     65 cleaned up for you when the context is released, using
     66 :func:`gccjit::context::release`:
     67 
     68 .. code-block:: c++
     69 
     70   ctxt.release ();
     71 
     72 so you don't need to manually track and cleanup all objects, just the
     73 contexts.
     74 
     75 All of the C++ classes in the API are thin wrappers around pointers to
     76 types in the C API.
     77 
     78 The C++ class hierarchy within the ``gccjit`` namespace looks like this::
     79 
     80   +- object
     81       +- location
     82       +- type
     83          +- struct
     84       +- field
     85       +- function
     86       +- block
     87       +- rvalue
     88           +- lvalue
     89              +- param
     90 
     91 One thing you can do with a :type:`gccjit::object` is
     92 to ask it for a human-readable description as a :type:`std::string`, using
     93 :func:`gccjit::object::get_debug_string`:
     94 
     95 .. code-block:: c++
     96 
     97    printf ("obj: %s\n", obj.get_debug_string ().c_str ());
     98 
     99 giving this text on stdout:
    100 
    101 .. code-block:: bash
    102 
    103    obj: int
    104 
    105 This is invaluable when debugging.
    106 
    107 Let's create the function.  To do so, we first need to construct
    108 its single parameter, specifying its type and giving it a name,
    109 using :func:`gccjit::context::new_param`:
    110 
    111 .. code-block:: c++
    112 
    113   gccjit::param param_i = ctxt.new_param (int_type, "i");
    114 
    115 and we can then make a vector of all of the params of the function,
    116 in this case just one:
    117 
    118 .. code-block:: c++
    119 
    120   std::vector<gccjit::param> params;
    121   params.push_back (param_i);
    122 
    123 Now we can create the function, using
    124 :c:func:`gccjit::context::new_function`:
    125 
    126 .. code-block:: c++
    127 
    128   gccjit::function func =
    129     ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
    130                        int_type,
    131                        "square",
    132                        params,
    133                        0);
    134 
    135 To define the code within the function, we must create basic blocks
    136 containing statements.
    137 
    138 Every basic block contains a list of statements, eventually terminated
    139 by a statement that either returns, or jumps to another basic block.
    140 
    141 Our function has no control-flow, so we just need one basic block:
    142 
    143 .. code-block:: c++
    144 
    145   gccjit::block block = func.new_block ();
    146 
    147 Our basic block is relatively simple: it immediately terminates by
    148 returning the value of an expression.
    149 
    150 We can build the expression using :func:`gccjit::context::new_binary_op`:
    151 
    152 .. code-block:: c++
    153 
    154    gccjit::rvalue expr =
    155      ctxt.new_binary_op (
    156        GCC_JIT_BINARY_OP_MULT, int_type,
    157        param_i, param_i);
    158 
    159 A :type:`gccjit::rvalue` is another example of a
    160 :type:`gccjit::object` subclass.  As before, we can print it with
    161 :func:`gccjit::object::get_debug_string`.
    162 
    163 .. code-block:: c++
    164 
    165    printf ("expr: %s\n", expr.get_debug_string ().c_str ());
    166 
    167 giving this output:
    168 
    169 .. code-block:: bash
    170 
    171    expr: i * i
    172 
    173 Note that :type:`gccjit::rvalue` provides numerous overloaded operators
    174 which can be used to dramatically reduce the amount of typing needed.
    175 We can build the above binary operation more directly with this one-liner:
    176 
    177 .. code-block:: c++
    178 
    179    gccjit::rvalue expr = param_i * param_i;
    180 
    181 Creating the expression in itself doesn't do anything; we have to add
    182 this expression to a statement within the block.  In this case, we use it
    183 to build a return statement, which terminates the basic block:
    184 
    185 .. code-block:: c++
    186 
    187   block.end_with_return (expr);
    188 
    189 OK, we've populated the context.  We can now compile it using
    190 :func:`gccjit::context::compile`:
    191 
    192 .. code-block:: c++
    193 
    194    gcc_jit_result *result;
    195    result = ctxt.compile ();
    196 
    197 and get a :c:type:`gcc_jit_result *`.
    198 
    199 We can now use :c:func:`gcc_jit_result_get_code` to look up a specific
    200 machine code routine within the result, in this case, the function we
    201 created above.
    202 
    203 .. code-block:: c++
    204 
    205    void *fn_ptr = gcc_jit_result_get_code (result, "square");
    206    if (!fn_ptr)
    207      {
    208        fprintf (stderr, "NULL fn_ptr");
    209        goto error;
    210      }
    211 
    212 We can now cast the pointer to an appropriate function pointer type, and
    213 then call it:
    214 
    215 .. code-block:: c++
    216 
    217   typedef int (*fn_type) (int);
    218   fn_type square = (fn_type)fn_ptr;
    219   printf ("result: %d", square (5));
    220 
    221 .. code-block:: bash
    222 
    223   result: 25
    224 
    225 
    226 Options
    227 *******
    228 
    229 To get more information on what's going on, you can set debugging flags
    230 on the context using :func:`gccjit::context::set_bool_option`.
    231 
    232 .. (I'm deliberately not mentioning
    233     :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE` here since I think
    234     it's probably more of use to implementors than to users)
    235 
    236 Setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE` will dump a
    237 C-like representation to stderr when you compile (GCC's "GIMPLE"
    238 representation):
    239 
    240 .. code-block:: c++
    241 
    242    ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1);
    243    result = ctxt.compile ();
    244 
    245 .. code-block:: c
    246 
    247   square (signed int i)
    248   {
    249     signed int D.260;
    250 
    251     entry:
    252     D.260 = i * i;
    253     return D.260;
    254   }
    255 
    256 We can see the generated machine code in assembler form (on stderr) by
    257 setting :c:macro:`GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE` on the context
    258 before compiling:
    259 
    260 .. code-block:: c++
    261 
    262   ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE, 1);
    263   result = ctxt.compile ();
    264 
    265 .. code-block:: gas
    266 
    267         .file   "fake.c"
    268         .text
    269         .globl  square
    270         .type   square, @function
    271   square:
    272   .LFB6:
    273         .cfi_startproc
    274         pushq   %rbp
    275         .cfi_def_cfa_offset 16
    276         .cfi_offset 6, -16
    277         movq    %rsp, %rbp
    278         .cfi_def_cfa_register 6
    279         movl    %edi, -4(%rbp)
    280   .L14:
    281         movl    -4(%rbp), %eax
    282         imull   -4(%rbp), %eax
    283         popq    %rbp
    284         .cfi_def_cfa 7, 8
    285         ret
    286         .cfi_endproc
    287   .LFE6:
    288         .size   square, .-square
    289         .ident  "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2-0.5.1920c315ff984892399893b380305ab36e07b455.fc20)"
    290         .section       .note.GNU-stack,"",@progbits
    291 
    292 By default, no optimizations are performed, the equivalent of GCC's
    293 `-O0` option.  We can turn things up to e.g. `-O3` by calling
    294 :func:`gccjit::context::set_int_option` with
    295 :c:macro:`GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL`:
    296 
    297 .. code-block:: c++
    298 
    299   ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3);
    300 
    301 .. code-block:: gas
    302 
    303         .file   "fake.c"
    304         .text
    305         .p2align 4,,15
    306         .globl  square
    307         .type   square, @function
    308   square:
    309   .LFB7:
    310         .cfi_startproc
    311   .L16:
    312         movl    %edi, %eax
    313         imull   %edi, %eax
    314         ret
    315         .cfi_endproc
    316   .LFE7:
    317         .size   square, .-square
    318         .ident  "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2-0.5.1920c315ff984892399893b380305ab36e07b455.fc20)"
    319         .section        .note.GNU-stack,"",@progbits
    320 
    321 Naturally this has only a small effect on such a trivial function.
    322 
    323 
    324 Full example
    325 ************
    326 
    327 Here's what the above looks like as a complete program:
    328 
    329    .. literalinclude:: ../../examples/tut02-square.cc
    330     :lines: 1-
    331     :language: c++
    332 
    333 Building and running it:
    334 
    335 .. code-block:: console
    336 
    337   $ gcc \
    338       tut02-square.cc \
    339       -o tut02-square \
    340       -lgccjit
    341 
    342   # Run the built program:
    343   $ ./tut02-square
    344   result: 25
    345