]>
Commit | Line | Data |
---|---|---|
83ffe9cd | 1 | .. Copyright (C) 2014-2023 Free Software Foundation, Inc. |
29df5715 DM |
2 | Originally contributed by David Malcolm <dmalcolm@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 | |
786973ce | 16 | <https://www.gnu.org/licenses/>. |
29df5715 DM |
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 | |
85c943f3 | 42 | :c:expr:`gcc_jit_context *`. |
29df5715 DM |
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 | |
a8a282d5 | 124 | :cpp:func:`gccjit::context::new_function`: |
29df5715 DM |
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 | ||
85c943f3 | 197 | and get a :c:expr:`gcc_jit_result *`. |
29df5715 DM |
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 | |
06520873 | 289 | .ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2)" |
29df5715 DM |
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 | |
06520873 | 318 | .ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2)" |
29df5715 DM |
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 |