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