]>
Commit | Line | Data |
---|---|---|
83ffe9cd | 1 | .. Copyright (C) 2015-2023 Free Software Foundation, Inc. |
fdce7209 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/>. |
fdce7209 DM |
17 | |
18 | Tutorial part 5: Implementing an Ahead-of-Time compiler | |
19 | ------------------------------------------------------- | |
20 | ||
21 | If you have a pre-existing language frontend that's compatible with | |
22 | libgccjit's license, it's possible to hook it up to libgccjit as a | |
23 | backend. In the previous example we showed | |
24 | how to do that for in-memory JIT-compilation, but libgccjit can also | |
25 | compile code directly to a file, allowing you to implement a more | |
26 | traditional ahead-of-time compiler ("JIT" is something of a misnomer | |
27 | for this use-case). | |
28 | ||
29 | The essential difference is to compile the context using | |
30 | :c:func:`gcc_jit_context_compile_to_file` rather than | |
31 | :c:func:`gcc_jit_context_compile`. | |
32 | ||
33 | The "brainf" language | |
34 | ********************* | |
35 | ||
36 | In this example we use libgccjit to construct an ahead-of-time compiler | |
37 | for an esoteric programming language that we shall refer to as "brainf". | |
38 | ||
39 | brainf scripts operate on an array of bytes, with a notional data pointer | |
40 | within the array. | |
41 | ||
42 | brainf is hard for humans to read, but it's trivial to write a parser for | |
43 | it, as there is no lexing; just a stream of bytes. The operations are: | |
44 | ||
85cf5a23 | 45 | .. list-table:: |
46 | :header-rows: 1 | |
47 | ||
48 | * - Character | |
49 | - Meaning | |
50 | ||
51 | * - ``>`` | |
52 | - ``idx += 1`` | |
53 | * - ``<`` | |
54 | - ``idx -= 1`` | |
55 | * - ``+`` | |
56 | - ``data[idx] += 1`` | |
57 | * - ``-`` | |
58 | - ``data[idx] -= 1`` | |
59 | * - ``.`` | |
60 | - ``output (data[idx])`` | |
61 | * - ``,`` | |
62 | - ``data[idx] = input ()`` | |
63 | * - ``[`` | |
64 | - loop until ``data[idx] == 0`` | |
65 | * - ``]`` | |
66 | - end of loop | |
67 | * - Anything else | |
68 | - ignored | |
fdce7209 DM |
69 | |
70 | Unlike the previous example, we'll implement an ahead-of-time compiler, | |
71 | which reads ``.bf`` scripts and outputs executables (though it would | |
72 | be trivial to have it run them JIT-compiled in-process). | |
73 | ||
74 | Here's what a simple ``.bf`` script looks like: | |
75 | ||
76 | .. literalinclude:: ../examples/emit-alphabet.bf | |
77 | :lines: 1- | |
78 | ||
79 | .. note:: | |
80 | ||
81 | This example makes use of whitespace and comments for legibility, but | |
82 | could have been written as:: | |
83 | ||
84 | ++++++++++++++++++++++++++ | |
85 | >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< | |
86 | [>.+<-] | |
87 | ||
88 | It's not a particularly useful language, except for providing | |
89 | compiler-writers with a test case that's easy to parse. The point | |
90 | is that you can use :c:func:`gcc_jit_context_compile_to_file` | |
91 | to use libgccjit as a backend for a pre-existing language frontend | |
92 | (provided that the pre-existing frontend is compatible with libgccjit's | |
93 | license). | |
94 | ||
95 | Converting a brainf script to libgccjit IR | |
96 | ****************************************** | |
97 | ||
85c943f3 | 98 | As before we write simple code to populate a :c:expr:`gcc_jit_context *`. |
fdce7209 DM |
99 | |
100 | .. literalinclude:: ../examples/tut05-bf.c | |
101 | :start-after: #define MAX_OPEN_PARENS 16 | |
102 | :end-before: /* Entrypoint to the compiler. */ | |
103 | :language: c | |
104 | ||
105 | Compiling a context to a file | |
106 | ***************************** | |
107 | ||
108 | Unlike the previous tutorial, this time we'll compile the context | |
109 | directly to an executable, using :c:func:`gcc_jit_context_compile_to_file`: | |
110 | ||
111 | .. code-block:: c | |
112 | ||
113 | gcc_jit_context_compile_to_file (ctxt, | |
114 | GCC_JIT_OUTPUT_KIND_EXECUTABLE, | |
115 | output_file); | |
116 | ||
117 | Here's the top-level of the compiler, which is what actually calls into | |
118 | :c:func:`gcc_jit_context_compile_to_file`: | |
119 | ||
120 | .. literalinclude:: ../examples/tut05-bf.c | |
121 | :start-after: /* Entrypoint to the compiler. */ | |
122 | :end-before: /* Use the built compiler to compile the example to an executable: | |
123 | :language: c | |
124 | ||
125 | Note how once the context is populated you could trivially instead compile | |
126 | it to memory using :c:func:`gcc_jit_context_compile` and run it in-process | |
127 | as in the previous tutorial. | |
128 | ||
129 | To create an executable, we need to export a ``main`` function. Here's | |
130 | how to create one from the JIT API: | |
131 | ||
132 | .. literalinclude:: ../examples/tut05-bf.c | |
133 | :start-after: #include "libgccjit.h" | |
134 | :end-before: #define MAX_OPEN_PARENS 16 | |
135 | :language: c | |
136 | ||
137 | .. note:: | |
138 | ||
139 | The above implementation ignores ``argc`` and ``argv``, but you could | |
140 | make use of them by exposing ``param_argc`` and ``param_argv`` to the | |
141 | caller. | |
142 | ||
143 | Upon compiling this C code, we obtain a bf-to-machine-code compiler; | |
144 | let's call it ``bfc``: | |
145 | ||
146 | .. code-block:: console | |
147 | ||
148 | $ gcc \ | |
149 | tut05-bf.c \ | |
150 | -o bfc \ | |
151 | -lgccjit | |
152 | ||
153 | We can now use ``bfc`` to compile .bf files into machine code executables: | |
154 | ||
155 | .. code-block:: console | |
156 | ||
157 | $ ./bfc \ | |
158 | emit-alphabet.bf \ | |
159 | a.out | |
160 | ||
161 | which we can run directly: | |
162 | ||
163 | .. code-block:: console | |
164 | ||
165 | $ ./a.out | |
166 | ABCDEFGHIJKLMNOPQRSTUVWXYZ | |
167 | ||
168 | Success! | |
169 | ||
170 | We can also inspect the generated executable using standard tools: | |
171 | ||
172 | .. code-block:: console | |
173 | ||
174 | $ objdump -d a.out |less | |
175 | ||
176 | which shows that libgccjit has managed to optimize the function | |
177 | somewhat (for example, the runs of 26 and 65 increment operations | |
178 | have become integer constants 0x1a and 0x41): | |
179 | ||
180 | .. code-block:: console | |
181 | ||
182 | 0000000000400620 <main>: | |
183 | 400620: 80 3d 39 0a 20 00 00 cmpb $0x0,0x200a39(%rip) # 601060 <data | |
184 | 400627: 74 07 je 400630 <main | |
185 | 400629: eb fe jmp 400629 <main+0x9> | |
186 | 40062b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) | |
187 | 400630: 48 83 ec 08 sub $0x8,%rsp | |
188 | 400634: 0f b6 05 26 0a 20 00 movzbl 0x200a26(%rip),%eax # 601061 <data_cells+0x1> | |
189 | 40063b: c6 05 1e 0a 20 00 1a movb $0x1a,0x200a1e(%rip) # 601060 <data_cells> | |
190 | 400642: 8d 78 41 lea 0x41(%rax),%edi | |
191 | 400645: 40 88 3d 15 0a 20 00 mov %dil,0x200a15(%rip) # 601061 <data_cells+0x1> | |
192 | 40064c: 0f 1f 40 00 nopl 0x0(%rax) | |
193 | 400650: 40 0f b6 ff movzbl %dil,%edi | |
194 | 400654: e8 87 fe ff ff callq 4004e0 <putchar@plt> | |
195 | 400659: 0f b6 05 01 0a 20 00 movzbl 0x200a01(%rip),%eax # 601061 <data_cells+0x1> | |
196 | 400660: 80 2d f9 09 20 00 01 subb $0x1,0x2009f9(%rip) # 601060 <data_cells> | |
197 | 400667: 8d 78 01 lea 0x1(%rax),%edi | |
198 | 40066a: 40 88 3d f0 09 20 00 mov %dil,0x2009f0(%rip) # 601061 <data_cells+0x1> | |
199 | 400671: 75 dd jne 400650 <main+0x30> | |
200 | 400673: 31 c0 xor %eax,%eax | |
201 | 400675: 48 83 c4 08 add $0x8,%rsp | |
202 | 400679: c3 retq | |
203 | 40067a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) | |
204 | ||
205 | We also set up debugging information (via | |
206 | :c:func:`gcc_jit_context_new_location` and | |
207 | :c:macro:`GCC_JIT_BOOL_OPTION_DEBUGINFO`), so it's possible to use ``gdb`` | |
208 | to singlestep through the generated binary and inspect the internal | |
209 | state ``idx`` and ``data_cells``: | |
210 | ||
211 | .. code-block:: console | |
212 | ||
213 | (gdb) break main | |
214 | Breakpoint 1 at 0x400790 | |
215 | (gdb) run | |
216 | Starting program: a.out | |
217 | ||
218 | Breakpoint 1, 0x0000000000400790 in main (argc=1, argv=0x7fffffffe448) | |
219 | (gdb) stepi | |
220 | 0x0000000000400797 in main (argc=1, argv=0x7fffffffe448) | |
221 | (gdb) stepi | |
222 | 0x00000000004007a0 in main (argc=1, argv=0x7fffffffe448) | |
223 | (gdb) stepi | |
224 | 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< | |
225 | (gdb) list | |
226 | 4 | |
227 | 5 cell 0 = 26 | |
228 | 6 ++++++++++++++++++++++++++ | |
229 | 7 | |
230 | 8 cell 1 = 65 | |
231 | 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< | |
232 | 10 | |
233 | 11 while cell#0 != 0 | |
234 | 12 [ | |
235 | 13 > | |
236 | (gdb) n | |
237 | 6 ++++++++++++++++++++++++++ | |
238 | (gdb) n | |
239 | 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< | |
240 | (gdb) p idx | |
241 | $1 = 1 | |
242 | (gdb) p data_cells | |
243 | $2 = "\032", '\000' <repeats 29998 times> | |
244 | (gdb) p data_cells[0] | |
245 | $3 = 26 '\032' | |
246 | (gdb) p data_cells[1] | |
247 | $4 = 0 '\000' | |
248 | (gdb) list | |
249 | 4 | |
250 | 5 cell 0 = 26 | |
251 | 6 ++++++++++++++++++++++++++ | |
252 | 7 | |
253 | 8 cell 1 = 65 | |
254 | 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< | |
255 | 10 | |
256 | 11 while cell#0 != 0 | |
257 | 12 [ | |
258 | 13 > | |
259 | ||
260 | ||
261 | Other forms of ahead-of-time-compilation | |
262 | **************************************** | |
263 | ||
85c943f3 | 264 | The above demonstrates compiling a :c:expr:`gcc_jit_context *` directly |
fdce7209 DM |
265 | to an executable. It's also possible to compile it to an object file, |
266 | and to a dynamic library. See the documentation of | |
267 | :c:func:`gcc_jit_context_compile_to_file` for more information. |