diff options
author | Bad Diode <bd@badd10de.dev> | 2024-06-21 18:20:35 +0200 |
---|---|---|
committer | Bad Diode <bd@badd10de.dev> | 2024-06-21 18:20:35 +0200 |
commit | 835f4d9f23f55a973d76ae9384b7b9d75da5472b (patch) | |
tree | 8e817452f8437db07688cb6e63a1a73bcce543eb /src/x86asm_compiler.h | |
parent | 5a25eeefd13b0e1988ecaf7e497ebde81e71bb2e (diff) | |
download | bdl-835f4d9f23f55a973d76ae9384b7b9d75da5472b.tar.gz bdl-835f4d9f23f55a973d76ae9384b7b9d75da5472b.zip |
Remove old files no longer needed as reference
Diffstat (limited to 'src/x86asm_compiler.h')
-rw-r--r-- | src/x86asm_compiler.h | 818 |
1 files changed, 818 insertions, 0 deletions
diff --git a/src/x86asm_compiler.h b/src/x86asm_compiler.h new file mode 100644 index 0000000..6ca4467 --- /dev/null +++ b/src/x86asm_compiler.h | |||
@@ -0,0 +1,818 @@ | |||
1 | #ifndef BDL_COMPILER_H | ||
2 | #define BDL_COMPILER_H | ||
3 | |||
4 | #define PRELUDE_FILE "src/x86_64/prelude.asm" | ||
5 | #define POSTLUDE_FILE "src/x86_64/postlude.asm" | ||
6 | |||
7 | #define HEAP_SIZE MB(32) | ||
8 | |||
9 | typedef struct Constant { | ||
10 | Object *obj; | ||
11 | char *label; | ||
12 | } Constant; | ||
13 | |||
14 | static Constant *constants = NULL; | ||
15 | static char **labels = NULL; | ||
16 | static char **procedures = NULL; | ||
17 | |||
18 | static char* current_context = NULL; | ||
19 | #define context_printf(fmt, ...) \ | ||
20 | do { \ | ||
21 | char buf[KB(4)]; \ | ||
22 | int n_chars = sprintf(buf, fmt, ##__VA_ARGS__); \ | ||
23 | array_insert(current_context, buf, n_chars); \ | ||
24 | } while(false); | ||
25 | |||
26 | static Environment *current_env = NULL; | ||
27 | |||
28 | // TODO: Separate c/h files | ||
29 | // TODO: Create a "driver.c" file with the (display) function for external | ||
30 | // linkage or assembly inlining. | ||
31 | // TODO: Ensure we don't compile a function multiple times (for example with | ||
32 | // a function that contains internal functions). | ||
33 | |||
34 | // Immediate constants. | ||
35 | #define NIL_VAL 47LU | ||
36 | #define BOOL_MASK 127LU | ||
37 | #define BOOL_TAG 31LU | ||
38 | #define BOOL_SHIFT 7LU | ||
39 | #define TRUE_VAL ((1 << BOOL_SHIFT) | BOOL_TAG) | ||
40 | #define FALSE_VAL ((0 << BOOL_SHIFT) | BOOL_TAG) | ||
41 | #define FIXNUM_MASK 3LU | ||
42 | #define FIXNUM_TAG 0LU | ||
43 | #define FIXNUM_SHIFT 2LU | ||
44 | |||
45 | // Heap allocated objects. | ||
46 | #define PTR_MASK ~7LU | ||
47 | #define STRING_MASK 7LU | ||
48 | #define STRING_TAG 3LU | ||
49 | #define PAIR_MASK 7LU | ||
50 | #define PAIR_TAG 1LU | ||
51 | #define LAMBDA_MASK 7LU | ||
52 | #define LAMBDA_TAG 6LU | ||
53 | |||
54 | void compile_object(Object *obj); | ||
55 | void compile_fixnum(Object *obj); | ||
56 | void compile_proc_call(Object *obj); | ||
57 | void compile(Program program); | ||
58 | |||
59 | char * | ||
60 | generate_label(char *prefix) { | ||
61 | static size_t label_counter = 0; | ||
62 | char buf[32]; | ||
63 | sprintf(buf, "%s%zu", prefix, label_counter++); | ||
64 | size_t len = strlen(buf); | ||
65 | char * ret = malloc(len + 1); | ||
66 | memcpy(ret, buf, len); | ||
67 | ret[len] = 0; | ||
68 | array_push(labels, ret); | ||
69 | return ret; | ||
70 | } | ||
71 | |||
72 | void | ||
73 | emit_file(char *file_name) { | ||
74 | FILE *file = fopen(file_name, "r"); | ||
75 | if (!file) { | ||
76 | fprintf(stderr, "error: couldn't open input file: %s\n", file_name); | ||
77 | exit(EXIT_FAILURE); | ||
78 | } | ||
79 | char buf[1024]; | ||
80 | size_t n = 0; | ||
81 | while ((n = fread(&buf, 1, 1024, file)) > 0) { | ||
82 | fwrite(buf, 1, n, stdout); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | void | ||
87 | compile_fixnum(Object *obj) { | ||
88 | context_printf(" ;; --> compile_fixnum\n"); | ||
89 | context_printf(" mov rax, %zu\n", (obj->fixnum << FIXNUM_SHIFT) | FIXNUM_TAG); | ||
90 | context_printf(" push rax\n"); | ||
91 | context_printf(" ;; <-- compile_fixnum\n"); | ||
92 | } | ||
93 | |||
94 | void | ||
95 | compile_boolean(Object *obj) { | ||
96 | context_printf(" ;; --> compile_boolean\n"); | ||
97 | int is_true = obj->type == OBJ_TYPE_TRUE; | ||
98 | context_printf(" mov rax, %zu\n", (is_true << BOOL_SHIFT) | BOOL_TAG); | ||
99 | context_printf(" push rax\n"); | ||
100 | context_printf(" ;; <-- compile_boolean\n"); | ||
101 | } | ||
102 | |||
103 | void | ||
104 | compile_nil(void) { | ||
105 | context_printf(" ;; --> compile_nil\n"); | ||
106 | context_printf(" mov rax, NIL_VAL\n"); | ||
107 | context_printf(" push rax\n"); | ||
108 | context_printf(" ;; <-- compile_nil\n"); | ||
109 | } | ||
110 | |||
111 | typedef enum OpType { | ||
112 | // Arithmetic. | ||
113 | OP_ADD, | ||
114 | OP_SUB, | ||
115 | OP_MUL, | ||
116 | OP_DIV, | ||
117 | OP_MOD, | ||
118 | // Type predicates. | ||
119 | OP_IS_NIL, | ||
120 | OP_IS_ZERO, | ||
121 | OP_IS_BOOL, | ||
122 | OP_IS_FIXNUM, | ||
123 | // Logic operations. | ||
124 | OP_EQUAL, | ||
125 | OP_GREATER, | ||
126 | OP_LESS, | ||
127 | OP_GREATER_EQ, | ||
128 | OP_LESS_EQ, | ||
129 | } OpType; | ||
130 | |||
131 | void | ||
132 | compile_type_predicate(OpType op, Object* args) { | ||
133 | context_printf(" ;; --> compile_type_predicate\n"); | ||
134 | compile_object(args->head); | ||
135 | context_printf(" pop rax\n"); | ||
136 | switch (op) { | ||
137 | case OP_IS_NIL: { | ||
138 | context_printf(" cmp rax, NIL_VAL\n"); | ||
139 | } break; | ||
140 | case OP_IS_ZERO: { | ||
141 | context_printf(" cmp rax, 0\n"); | ||
142 | } break; | ||
143 | case OP_IS_BOOL: { | ||
144 | context_printf(" and rax, BOOL_MASK\n"); | ||
145 | context_printf(" cmp rax, BOOL_TAG\n"); | ||
146 | } break; | ||
147 | case OP_IS_FIXNUM: { | ||
148 | context_printf(" and rax, FIXNUM_MASK\n"); | ||
149 | context_printf(" cmp rax, FIXNUM_TAG\n"); | ||
150 | } break; | ||
151 | default: break; | ||
152 | } | ||
153 | context_printf(" mov rax, 0\n"); | ||
154 | context_printf(" sete al\n"); | ||
155 | context_printf(" shl rax, BOOL_SHIFT\n"); | ||
156 | context_printf(" or rax, BOOL_TAG\n"); | ||
157 | context_printf(" push rax\n"); | ||
158 | context_printf(" ;; <-- compile_type_predicate\n"); | ||
159 | } | ||
160 | |||
161 | void | ||
162 | compile_not(Object* args) { | ||
163 | context_printf(" ;; --> compile_not\n"); | ||
164 | compile_object(args->head); | ||
165 | context_printf(" pop rax\n"); | ||
166 | context_printf(" cmp rax, FALSE_VAL\n"); | ||
167 | context_printf(" mov rax, 0\n"); | ||
168 | context_printf(" sete al\n"); | ||
169 | context_printf(" shl rax, BOOL_SHIFT\n"); | ||
170 | context_printf(" or rax, BOOL_TAG\n"); | ||
171 | context_printf(" push rax\n"); | ||
172 | context_printf(" ;; <-- compile_not\n"); | ||
173 | } | ||
174 | |||
175 | void | ||
176 | compile_and(Object *args) { | ||
177 | context_printf(" ;; --> compile_and\n"); | ||
178 | char *lab_false = generate_label("BDLL"); | ||
179 | char *lab_exit = generate_label("BDLL"); | ||
180 | while (args != NULL) { | ||
181 | compile_object(args->head); | ||
182 | args = args->tail; | ||
183 | context_printf(" pop rax\n"); | ||
184 | context_printf(" cmp rax, FALSE_VAL\n"); | ||
185 | context_printf(" je %s\n", lab_false); | ||
186 | } | ||
187 | context_printf(" mov rax, TRUE_VAL\n"); | ||
188 | context_printf(" push rax\n"); | ||
189 | context_printf(" jmp %s\n", lab_exit); | ||
190 | context_printf("%s:\n", lab_false); | ||
191 | context_printf(" mov rax, FALSE_VAL\n"); | ||
192 | context_printf(" push rax\n"); | ||
193 | context_printf("%s:\n", lab_exit); | ||
194 | context_printf(" ;; <-- compile_and\n"); | ||
195 | } | ||
196 | |||
197 | void | ||
198 | compile_or(Object *args) { | ||
199 | context_printf(" ;; --> compile_or\n"); | ||
200 | char *lab_true = generate_label("BDLL"); | ||
201 | char *lab_exit = generate_label("BDLL"); | ||
202 | while (args != NULL) { | ||
203 | compile_object(args->head); | ||
204 | args = args->tail; | ||
205 | context_printf(" pop rax\n"); | ||
206 | context_printf(" cmp rax, FALSE_VAL\n"); | ||
207 | context_printf(" jne %s\n", lab_true); | ||
208 | } | ||
209 | context_printf(" mov rax, FALSE_VAL\n"); | ||
210 | context_printf(" push rax\n"); | ||
211 | context_printf(" jmp %s\n", lab_exit); | ||
212 | context_printf("%s:\n", lab_true); | ||
213 | context_printf(" mov rax, TRUE_VAL\n"); | ||
214 | context_printf(" push rax\n"); | ||
215 | context_printf("%s:\n", lab_exit); | ||
216 | context_printf(" ;; <-- compile_or\n"); | ||
217 | } | ||
218 | |||
219 | void | ||
220 | compile_cmp_list(OpType op, Object* args) { | ||
221 | context_printf(" ;; --> compile_cmp_list\n"); | ||
222 | compile_object(args->head); | ||
223 | char *lab_false = generate_label("BDLL"); | ||
224 | char *lab_exit = generate_label("BDLL"); | ||
225 | args = args->tail; | ||
226 | while (args != NULL) { | ||
227 | compile_object(args->head); | ||
228 | args = args->tail; | ||
229 | |||
230 | // Current value. | ||
231 | context_printf(" pop rcx\n"); | ||
232 | |||
233 | // Previous value. | ||
234 | context_printf(" pop rax\n"); | ||
235 | |||
236 | // Comparison. | ||
237 | context_printf(" cmp rax, rcx\n"); | ||
238 | switch (op) { | ||
239 | case OP_EQUAL: { context_printf(" jne %s\n", lab_false); } break; | ||
240 | case OP_GREATER: { context_printf(" jle %s\n", lab_false); } break; | ||
241 | case OP_LESS: { context_printf(" jge %s\n", lab_false); } break; | ||
242 | case OP_GREATER_EQ: { context_printf(" jl %s\n", lab_false); } break; | ||
243 | case OP_LESS_EQ: { context_printf(" jg %s\n", lab_false); } break; | ||
244 | default: break; | ||
245 | } | ||
246 | context_printf(" push rcx\n"); | ||
247 | } | ||
248 | context_printf(" pop rcx\n"); | ||
249 | context_printf(" mov rax, TRUE_VAL\n"); | ||
250 | context_printf(" push rax\n"); | ||
251 | context_printf(" jmp %s\n", lab_exit); | ||
252 | context_printf("%s:\n", lab_false); | ||
253 | context_printf(" mov rax, FALSE_VAL\n"); | ||
254 | context_printf(" push rax\n"); | ||
255 | context_printf("%s:\n", lab_exit); | ||
256 | context_printf(" ;; <-- compile_cmp_list\n"); | ||
257 | } | ||
258 | |||
259 | void | ||
260 | compile_arithmetic_list(OpType op, Object* args) { | ||
261 | context_printf(" ;; --> compile_arithmetic\n"); | ||
262 | compile_object(args->head); | ||
263 | args = args->tail; | ||
264 | while (args != NULL) { | ||
265 | compile_object(args->head); | ||
266 | args = args->tail; | ||
267 | context_printf(" pop rcx\n"); | ||
268 | context_printf(" pop rax\n"); | ||
269 | switch (op) { | ||
270 | case OP_ADD: { context_printf(" add rax, rcx\n"); } break; | ||
271 | case OP_SUB: { context_printf(" sub rax, rcx\n"); } break; | ||
272 | case OP_MUL: { | ||
273 | context_printf(" sar rax, FIXNUM_SHIFT\n"); | ||
274 | context_printf(" sar rcx, FIXNUM_SHIFT\n"); | ||
275 | context_printf(" mul rcx\n"); | ||
276 | context_printf(" shl rax, FIXNUM_SHIFT\n"); | ||
277 | } break; | ||
278 | case OP_DIV: { | ||
279 | context_printf(" sar rax, FIXNUM_SHIFT\n"); | ||
280 | context_printf(" sar rcx, FIXNUM_SHIFT\n"); | ||
281 | context_printf(" mov rdx, 0\n"); | ||
282 | context_printf(" div rcx\n"); | ||
283 | context_printf(" shl rax, FIXNUM_SHIFT\n"); | ||
284 | } break; | ||
285 | case OP_MOD: { | ||
286 | context_printf(" sar rax, FIXNUM_SHIFT\n"); | ||
287 | context_printf(" sar rcx, FIXNUM_SHIFT\n"); | ||
288 | context_printf(" mov rdx, 0\n"); | ||
289 | context_printf(" div rcx\n"); | ||
290 | context_printf(" mov rax, rdx\n"); | ||
291 | context_printf(" shl rax, FIXNUM_SHIFT\n"); | ||
292 | } break; | ||
293 | default: break; | ||
294 | } | ||
295 | context_printf(" push rax\n"); | ||
296 | } | ||
297 | context_printf(" ;; <-- compile_arithmetic\n"); | ||
298 | } | ||
299 | |||
300 | void | ||
301 | compile_cons(Object *obj) { | ||
302 | context_printf(" ;; --> compile_cons\n"); | ||
303 | // Store objects into the car and cdr. | ||
304 | compile_object(obj->head); | ||
305 | compile_object(obj->tail->head); | ||
306 | context_printf(" pop rdx\n"); | ||
307 | context_printf(" pop rax\n"); | ||
308 | context_printf(" mov [r15], rax\n"); | ||
309 | context_printf(" mov [r15 + 8], rdx\n"); | ||
310 | |||
311 | // Push memory address of cons cell. | ||
312 | context_printf(" mov rax, r15\n"); | ||
313 | context_printf(" or rax, %zu\n", PAIR_TAG); | ||
314 | context_printf(" push rax\n"); | ||
315 | |||
316 | // Bump allocation register. | ||
317 | context_printf(" add r15, 16\n"); | ||
318 | context_printf(" ;; <-- compile_cons\n"); | ||
319 | } | ||
320 | |||
321 | void | ||
322 | compile_car(Object *obj) { | ||
323 | context_printf(" ;; --> compile_car\n"); | ||
324 | compile_object(obj->head); | ||
325 | context_printf(" pop rax\n"); | ||
326 | context_printf(" and rax, %zu\n", ~PAIR_MASK); | ||
327 | context_printf(" mov rdx, [rax]\n"); | ||
328 | context_printf(" push rdx\n"); | ||
329 | context_printf(" ;; <-- compile_car\n"); | ||
330 | } | ||
331 | |||
332 | void | ||
333 | compile_cdr(Object *obj) { | ||
334 | context_printf(" ;; --> compile_cdr\n"); | ||
335 | compile_object(obj->head); | ||
336 | context_printf(" pop rax\n"); | ||
337 | context_printf(" and rax, %zu\n", ~PAIR_MASK); | ||
338 | context_printf(" mov rdx, [rax + 8]\n"); | ||
339 | context_printf(" push rdx\n"); | ||
340 | context_printf(" ;; <-- compile_cdr\n"); | ||
341 | } | ||
342 | |||
343 | size_t | ||
344 | compile_call_body(Object *obj) { | ||
345 | // Compile operator. | ||
346 | compile_object(obj->head); | ||
347 | context_printf(" pop rax\n"); | ||
348 | context_printf(" mov rcx, PTR_MASK\n"); | ||
349 | context_printf(" and rcx, rax\n"); | ||
350 | context_printf(" mov rax, [rcx]\n"); | ||
351 | context_printf(" push rax\n"); | ||
352 | |||
353 | // Get the number of parameters/captured variables for this function. | ||
354 | Object *fun = obj->head; | ||
355 | // FIXME: this is horrible and WILL BREAK. | ||
356 | while (!IS_LAMBDA(fun)) { | ||
357 | if (IS_SYMBOL(fun)) { | ||
358 | fun = symbol_in_env(current_env, fun); | ||
359 | continue; | ||
360 | } | ||
361 | if (IS_PAIR(fun)) { | ||
362 | fun = fun->head; | ||
363 | } | ||
364 | } | ||
365 | |||
366 | size_t n_args = array_size(fun->env->params); | ||
367 | size_t n_cap = array_size(fun->env->captured); | ||
368 | size_t offset = n_args + n_cap; | ||
369 | |||
370 | // Push captured variables. | ||
371 | for (size_t i = 0; i < n_cap; i++) { | ||
372 | context_printf(" mov rax, [rcx + 8 * %zu]\n", i + 1); | ||
373 | context_printf(" push rax\n"); | ||
374 | } | ||
375 | |||
376 | // Compile arguments. | ||
377 | while (obj->tail != NULL) { | ||
378 | obj = obj->tail; | ||
379 | compile_object(obj->head); | ||
380 | } | ||
381 | return offset; | ||
382 | } | ||
383 | |||
384 | void | ||
385 | compile_call(Object *obj) { | ||
386 | context_printf(" ;; --> compile_call\n"); | ||
387 | context_printf(" push rbp\n"); | ||
388 | |||
389 | // Prepare return pointer. | ||
390 | char *lab_end = generate_label("BDLL"); | ||
391 | context_printf(" lea rcx, [%s]\n", lab_end); | ||
392 | context_printf(" push rcx\n"); | ||
393 | |||
394 | // Function call compilation without start/end. | ||
395 | size_t offset = compile_call_body(obj); | ||
396 | |||
397 | // Call function. | ||
398 | context_printf(" mov rdi, [rsp + %zu]\n", 8 * offset); | ||
399 | context_printf(" jmp rdi\n"); | ||
400 | |||
401 | // Restore stack to previous location and store the result on top. | ||
402 | context_printf("%s:\n", lab_end); | ||
403 | context_printf(" add rsp, %zu\n", 8 * (offset + 2)); | ||
404 | context_printf(" pop rbp\n"); | ||
405 | context_printf(" push rax\n"); | ||
406 | context_printf(" ;; <-- compile_call\n"); | ||
407 | } | ||
408 | |||
409 | void | ||
410 | compile_proc_call(Object *obj) { | ||
411 | // TODO: Probably we want to use a hash table for these lookups that is | ||
412 | // initialized at the start of the compilation procedure. | ||
413 | if (sv_equal(&obj->head->text, &STRING("+"))) { | ||
414 | compile_arithmetic_list(OP_ADD, obj->tail); | ||
415 | } else if (sv_equal(&obj->head->text, &STRING("-"))) { | ||
416 | compile_arithmetic_list(OP_SUB, obj->tail); | ||
417 | } else if (sv_equal(&obj->head->text, &STRING("*"))) { | ||
418 | compile_arithmetic_list(OP_MUL, obj->tail); | ||
419 | } else if (sv_equal(&obj->head->text, &STRING("/"))) { | ||
420 | compile_arithmetic_list(OP_DIV, obj->tail); | ||
421 | } else if (sv_equal(&obj->head->text, &STRING("%"))) { | ||
422 | compile_arithmetic_list(OP_MOD, obj->tail); | ||
423 | } else if (sv_equal(&obj->head->text, &STRING("nil?"))) { | ||
424 | compile_type_predicate(OP_IS_NIL, obj->tail); | ||
425 | } else if (sv_equal(&obj->head->text, &STRING("zero?"))) { | ||
426 | compile_type_predicate(OP_IS_ZERO, obj->tail); | ||
427 | } else if (sv_equal(&obj->head->text, &STRING("fixnum?"))) { | ||
428 | compile_type_predicate(OP_IS_FIXNUM, obj->tail); | ||
429 | } else if (sv_equal(&obj->head->text, &STRING("bool?"))) { | ||
430 | compile_type_predicate(OP_IS_BOOL, obj->tail); | ||
431 | } else if (sv_equal(&obj->head->text, &STRING("display"))) { | ||
432 | compile_object(obj->tail->head); | ||
433 | context_printf(" pop rdi\n"); | ||
434 | context_printf(" call display\n"); | ||
435 | } else if (sv_equal(&obj->head->text, &STRING("not"))) { | ||
436 | compile_not(obj->tail); | ||
437 | } else if (sv_equal(&obj->head->text, &STRING("and"))) { | ||
438 | compile_and(obj->tail); | ||
439 | } else if (sv_equal(&obj->head->text, &STRING("or"))) { | ||
440 | compile_or(obj->tail); | ||
441 | } else if (sv_equal(&obj->head->text, &STRING("="))) { | ||
442 | compile_cmp_list(OP_EQUAL, obj->tail); | ||
443 | } else if (sv_equal(&obj->head->text, &STRING(">"))) { | ||
444 | compile_cmp_list(OP_GREATER, obj->tail); | ||
445 | } else if (sv_equal(&obj->head->text, &STRING("<"))) { | ||
446 | compile_cmp_list(OP_LESS, obj->tail); | ||
447 | } else if (sv_equal(&obj->head->text, &STRING(">="))) { | ||
448 | compile_cmp_list(OP_GREATER_EQ, obj->tail); | ||
449 | } else if (sv_equal(&obj->head->text, &STRING("<="))) { | ||
450 | compile_cmp_list(OP_LESS_EQ, obj->tail); | ||
451 | } else if (sv_equal(&obj->head->text, &STRING("cons"))) { | ||
452 | compile_cons(obj->tail); | ||
453 | } else if (sv_equal(&obj->head->text, &STRING("car"))) { | ||
454 | compile_car(obj->tail); | ||
455 | } else if (sv_equal(&obj->head->text, &STRING("cdr"))) { | ||
456 | compile_cdr(obj->tail); | ||
457 | } else { | ||
458 | compile_call(obj); | ||
459 | } | ||
460 | } | ||
461 | |||
462 | void | ||
463 | compile_if(Object *obj) { | ||
464 | context_printf(" ;; --> compile_if\n"); | ||
465 | char *lab_false = generate_label("BDLL"); | ||
466 | compile_object(obj->condition); | ||
467 | context_printf(" pop rax\n"); | ||
468 | context_printf(" cmp rax, FALSE_VAL\n"); | ||
469 | context_printf(" je %s\n", lab_false); | ||
470 | compile_object(obj->expr_true); | ||
471 | if (obj->expr_false != NULL) { | ||
472 | char *lab_exit = generate_label("BDLL"); | ||
473 | context_printf(" jmp %s\n", lab_exit); | ||
474 | context_printf("%s:\n", lab_false); | ||
475 | compile_object(obj->expr_false); | ||
476 | context_printf("%s:\n", lab_exit); | ||
477 | } else { | ||
478 | context_printf("%s:\n", lab_false); | ||
479 | } | ||
480 | context_printf(" ;; <-- compile_if\n"); | ||
481 | } | ||
482 | |||
483 | void | ||
484 | compile_string(Object *obj) { | ||
485 | context_printf(" ;; --> compile_string\n"); | ||
486 | Constant c; | ||
487 | |||
488 | // Check if the string is already stored as a constant. | ||
489 | ssize_t idx = -1; | ||
490 | for (size_t i = 0; i < array_size(constants); i++) { | ||
491 | c = constants[i]; | ||
492 | if (object_equal(c.obj, obj)) { | ||
493 | idx = i; | ||
494 | break; | ||
495 | } | ||
496 | } | ||
497 | if (idx < 0) { | ||
498 | idx = array_size(constants); | ||
499 | c = (Constant){ | ||
500 | .obj = obj, | ||
501 | .label = generate_label("BDLC"), | ||
502 | }; | ||
503 | array_push(constants, c); | ||
504 | } | ||
505 | |||
506 | // Create a tagged pointer to the label. | ||
507 | context_printf(" mov rax, %s\n", c.label); | ||
508 | context_printf(" or rax, STRING_TAG\n"); | ||
509 | context_printf(" push rax\n"); | ||
510 | context_printf(" ;; <-- compile_string\n"); | ||
511 | } | ||
512 | |||
513 | void | ||
514 | compile_lambda(Object *obj) { | ||
515 | context_printf(" ;; --> compile_lambda\n"); | ||
516 | |||
517 | // Create a new compilation context. | ||
518 | char *prev_context = current_context; | ||
519 | Environment *prev_env = current_env; | ||
520 | current_env = obj->env; | ||
521 | current_context = NULL; | ||
522 | array_init(current_context, 0); | ||
523 | |||
524 | char *name = generate_label("BDLP"); | ||
525 | context_printf("alignb 8\n"); | ||
526 | context_printf("%s:\n", name); | ||
527 | |||
528 | // Prepare size vars. | ||
529 | size_t n_locals = array_size(current_env->locals); | ||
530 | size_t n_params = array_size(current_env->params); | ||
531 | size_t n_captured = array_size(current_env->captured); | ||
532 | |||
533 | // Initialize function call frame. | ||
534 | context_printf(" sub rsp, %zu\n", 8 * n_locals); | ||
535 | context_printf(" mov rbp, rsp\n"); | ||
536 | |||
537 | // Procedure body. | ||
538 | // In case the last expression of a function doesn't return anything (e.g. | ||
539 | // a `def` or `display` primitive), we store a sentinel `nil` value at the | ||
540 | // end of the stack. | ||
541 | // | ||
542 | // NOTE: This is probably better handled by a type system that | ||
543 | // allows functions to return void, but right now the caller and function | ||
544 | // creation expect that all functions return values. Failure to comply with | ||
545 | // this convention will result in a corrupted stack. | ||
546 | for (size_t i = 0; i < array_size(obj->body) - 1; i++) { | ||
547 | compile_object(obj->body[i]); | ||
548 | } | ||
549 | Object *last_expr = obj->body[array_size(obj->body) - 1]; | ||
550 | |||
551 | // Tail Call Optimization. | ||
552 | // TODO: also for if statements | ||
553 | // FIXME: only pairs that are not primitives. | ||
554 | if (IS_PAIR(last_expr)) { | ||
555 | // Discard the previous stack frame. | ||
556 | context_printf(" mov rsp, rbp\n"); | ||
557 | |||
558 | size_t old_offset = n_locals + n_captured + n_params; | ||
559 | size_t new_offset = compile_call_body(last_expr); | ||
560 | context_printf(" mov rdi, [rbp - 8]\n"); | ||
561 | for (size_t i = 0; i < new_offset + 1; i++) { | ||
562 | context_printf(" mov rax, [rbp - 8 * %zu]\n", i + 1); | ||
563 | context_printf(" mov [rbp + 8 * %zu], rax\n", old_offset - i); | ||
564 | } | ||
565 | |||
566 | // Set the stack pointer at the end of given parameters. | ||
567 | context_printf(" mov rsp, rbp\n"); | ||
568 | ssize_t offset_diff = old_offset - new_offset; | ||
569 | if (offset_diff > 0) { | ||
570 | context_printf(" add rsp, 8 * %zu\n", offset_diff); | ||
571 | } else { | ||
572 | context_printf(" sub rsp, 8 * %zu\n", offset_diff); | ||
573 | } | ||
574 | |||
575 | context_printf(" jmp rdi\n"); | ||
576 | } else { | ||
577 | compile_nil(); | ||
578 | compile_object(last_expr); | ||
579 | |||
580 | // Return is stored in the `rax`. | ||
581 | context_printf(" pop rax\n"); | ||
582 | |||
583 | // Restore the previous call frame. | ||
584 | size_t rp_offset = (n_locals + n_params + n_captured + 1); | ||
585 | context_printf(" mov rdi, [rbp + %zu]\n", 8 * rp_offset); | ||
586 | context_printf(" mov rsp, rbp\n"); | ||
587 | context_printf(" add rsp, %zu\n", 8 * n_locals); | ||
588 | context_printf(" jmp rdi\n"); | ||
589 | } | ||
590 | |||
591 | context_printf("\n"); | ||
592 | |||
593 | // Restore previous compilation context. | ||
594 | array_push(procedures, current_context); | ||
595 | current_context = prev_context; | ||
596 | current_env = prev_env; | ||
597 | |||
598 | // Add function address. | ||
599 | context_printf(" mov rax, %s\n", name); | ||
600 | context_printf(" mov [r15], rax\n"); | ||
601 | |||
602 | // Add captured variables to the heap. | ||
603 | for (size_t i = 0; i < n_captured; i++) { | ||
604 | ssize_t idx = find_var_index(current_env->locals, obj->env->captured[i]); | ||
605 | context_printf(" mov rax, rbp\n"); | ||
606 | context_printf(" add rax, %ld\n", 8 * idx); | ||
607 | context_printf(" mov [r15 + %ld], rax\n", 8 * (i + 1)); | ||
608 | // TODO: What about capturing captured variables or parameters? | ||
609 | assert(idx != -1 && "unexpected index"); | ||
610 | } | ||
611 | |||
612 | // Create tagged pointer with this lambda procedure. | ||
613 | context_printf(" mov rax, r15\n"); | ||
614 | context_printf(" or rax, %zu\n", LAMBDA_TAG); | ||
615 | |||
616 | // Push compiled object to the stack. | ||
617 | context_printf(" push rax\n"); | ||
618 | |||
619 | // Adjust the heap pointer depending on the number of variables captured. | ||
620 | context_printf(" add r15, %ld\n", 8 * (n_captured + 1)); | ||
621 | |||
622 | context_printf(" ;; <-- compile_lambda\n"); | ||
623 | } | ||
624 | |||
625 | void | ||
626 | compile_def(Object *obj) { | ||
627 | context_printf(" ;; --> compile_def\n"); | ||
628 | compile_object(obj->var_expr); | ||
629 | ssize_t idx = find_var_index(current_env->locals, obj->var_name); | ||
630 | context_printf(" pop rax\n"); | ||
631 | context_printf(" mov [rbp + %ld], rax\n", 8 * idx); | ||
632 | context_printf(" ;; <-- compile_def\n"); | ||
633 | } | ||
634 | |||
635 | void | ||
636 | compile_symbol(Object *obj) { | ||
637 | context_printf(" ;; --> compile_symbol\n"); | ||
638 | ssize_t idx = -1; | ||
639 | |||
640 | // TODO: Is a captured variable? | ||
641 | // FIXME: Order might be an issue, for example if the variable was initially | ||
642 | // captured but then declared as a local? | ||
643 | // (def a 40) | ||
644 | // (fun ext () | ||
645 | // (display a) | ||
646 | // (def a 10) | ||
647 | // (display a)) | ||
648 | idx = find_var_index(current_env->captured, obj); | ||
649 | if (idx != -1) { | ||
650 | size_t n_locals = array_size(current_env->locals); | ||
651 | size_t n_params = array_size(current_env->params); | ||
652 | size_t n_cap = array_size(current_env->captured); | ||
653 | size_t offset = 8 * (n_locals + n_params + n_cap - idx - 1); | ||
654 | context_printf(" mov rcx, [rbp + %ld]\n", offset); | ||
655 | context_printf(" mov rax, [rcx]\n"); | ||
656 | context_printf(" push rax\n"); | ||
657 | context_printf(" ;; <-- compile_symbol\n"); | ||
658 | return; | ||
659 | } | ||
660 | |||
661 | // Is a local variable? | ||
662 | idx = find_var_index(current_env->locals, obj); | ||
663 | if (idx != -1) { | ||
664 | context_printf(" mov rax, [rbp + %ld]\n", 8 * idx); | ||
665 | context_printf(" push rax\n"); | ||
666 | context_printf(" ;; <-- compile_symbol\n"); | ||
667 | return; | ||
668 | } | ||
669 | |||
670 | // Is a function parameter? | ||
671 | idx = find_var_index(current_env->params, obj); | ||
672 | if (idx != -1) { | ||
673 | size_t n_locals = array_size(current_env->locals); | ||
674 | size_t n_params = array_size(current_env->params); | ||
675 | size_t offset = 8 * (n_locals + n_params - idx - 1); | ||
676 | context_printf(" mov rax, [rbp + %ld]\n", offset); | ||
677 | context_printf(" push rax\n"); | ||
678 | context_printf(" ;; <-- compile_symbol\n"); | ||
679 | return; | ||
680 | } | ||
681 | |||
682 | assert(idx != -1 && "unexpected index"); | ||
683 | } | ||
684 | |||
685 | void | ||
686 | compile_object(Object *obj) { | ||
687 | switch (obj->type) { | ||
688 | case OBJ_TYPE_NIL: { compile_nil(); } break; | ||
689 | case OBJ_TYPE_TRUE: | ||
690 | case OBJ_TYPE_FALSE: { compile_boolean(obj); } break; | ||
691 | case OBJ_TYPE_FIXNUM: { compile_fixnum(obj); } break; | ||
692 | case OBJ_TYPE_PAIR: { compile_proc_call(obj); } break; | ||
693 | case OBJ_TYPE_STRING: { compile_string(obj); } break; | ||
694 | case OBJ_TYPE_IF: { compile_if(obj); } break; | ||
695 | case OBJ_TYPE_LAMBDA: { compile_lambda(obj); } break; | ||
696 | case OBJ_TYPE_DEF: { compile_def(obj); } break; | ||
697 | case OBJ_TYPE_SYMBOL: { compile_symbol(obj); } break; | ||
698 | default: break; | ||
699 | } | ||
700 | } | ||
701 | |||
702 | void | ||
703 | emit_bss_section(void) { | ||
704 | printf("section .bss\n"); | ||
705 | printf("bdl_heap:\n"); | ||
706 | printf(" resb HEAP_SIZE\n"); | ||
707 | printf("\n"); | ||
708 | } | ||
709 | |||
710 | void | ||
711 | emit_data_section(void) { | ||
712 | printf("section .data\n"); | ||
713 | printf("true_str:\n db \"true\", 10\n"); | ||
714 | printf(" alignb 8\n"); | ||
715 | printf("false_str:\n db \"false\", 10\n"); | ||
716 | printf(" alignb 8\n"); | ||
717 | printf("lambda_str:\n"); | ||
718 | char lambda_str[] = "#{lambda}"; | ||
719 | printf(" dq %ld\n", sizeof(lambda_str)); | ||
720 | printf(" db \"%s\", 10\n", lambda_str); | ||
721 | printf(" alignb 8\n"); | ||
722 | for (size_t i = 0; i < array_size(constants); i++) { | ||
723 | // NOTE: Only supporting string constants for now. | ||
724 | Constant c = constants[i]; | ||
725 | int n = c.obj->text.n; | ||
726 | // TODO: escape characters maybe? | ||
727 | // TODO: quote all strings maybe? | ||
728 | printf("%s:\n", c.label); | ||
729 | printf(" dq %d\n", n + 1); | ||
730 | printf(" db \"%.*s\", 10\n", n, c.obj->text.start); | ||
731 | printf(" alignb 8\n"); | ||
732 | } | ||
733 | printf("\n"); | ||
734 | } | ||
735 | |||
736 | void | ||
737 | compile(Program program) { | ||
738 | // Prepare compilation variables. | ||
739 | array_init(constants, 0); | ||
740 | array_init(labels, 0); | ||
741 | array_init(procedures, 0); | ||
742 | array_init(current_context, 0); | ||
743 | current_env = program.env; | ||
744 | |||
745 | // Compile program. | ||
746 | for (size_t i = 0; i < array_size(program.roots); i++) { | ||
747 | Object *root = program.roots[i]; | ||
748 | compile_object(root); | ||
749 | } | ||
750 | |||
751 | // Base defines. | ||
752 | printf("%%define NIL_VAL %zu\n", NIL_VAL); | ||
753 | printf("%%define TRUE_VAL %zu\n", TRUE_VAL); | ||
754 | printf("%%define FALSE_VAL %zu\n", FALSE_VAL); | ||
755 | printf("%%define BOOL_MASK %zu\n", BOOL_MASK); | ||
756 | printf("%%define BOOL_TAG %zu\n", BOOL_TAG); | ||
757 | printf("%%define BOOL_SHIFT %zu\n", BOOL_SHIFT); | ||
758 | printf("%%define FIXNUM_MASK %zu\n", FIXNUM_MASK); | ||
759 | printf("%%define FIXNUM_TAG %zu\n", FIXNUM_TAG); | ||
760 | printf("%%define FIXNUM_SHIFT %zu\n", FIXNUM_SHIFT); | ||
761 | printf("%%define PAIR_MASK %zu\n", PAIR_MASK); | ||
762 | printf("%%define PAIR_TAG %zu\n", PAIR_TAG); | ||
763 | printf("%%define PTR_MASK %zu\n", PTR_MASK); | ||
764 | printf("%%define STRING_MASK %zu\n", STRING_MASK); | ||
765 | printf("%%define STRING_TAG %zu\n", STRING_TAG); | ||
766 | printf("%%define LAMBDA_MASK %zu\n", LAMBDA_MASK); | ||
767 | printf("%%define LAMBDA_TAG %zu\n", LAMBDA_TAG); | ||
768 | printf("%%define HEAP_SIZE %zu\n", HEAP_SIZE); | ||
769 | printf("\n"); | ||
770 | |||
771 | // Prelude. | ||
772 | emit_file(PRELUDE_FILE); | ||
773 | printf("\n"); | ||
774 | |||
775 | // Function definitions. | ||
776 | for (size_t i = 0; i < array_size(procedures); i++) { | ||
777 | char *ctx = procedures[i]; | ||
778 | for (size_t i = 0; i < array_size(ctx); i++) { | ||
779 | putchar(ctx[i]); | ||
780 | } | ||
781 | } | ||
782 | |||
783 | // Main context. | ||
784 | printf("alignb 8\n"); | ||
785 | printf("global _start\n"); | ||
786 | printf("_start:\n"); | ||
787 | |||
788 | // Initialize heap pointer. | ||
789 | printf(" mov r15, bdl_heap\n"); | ||
790 | |||
791 | // Initialize main locals. | ||
792 | printf(" sub rsp, %zu\n", 8 * array_size(current_env->locals)); | ||
793 | printf(" mov rbp, rsp\n"); | ||
794 | |||
795 | // Keep the bottom stack value set as NIL_VAL for a default return value. | ||
796 | printf(" push NIL_VAL\n"); | ||
797 | for (size_t i = 0; i < array_size(current_context); i++) { | ||
798 | putchar(current_context[i]); | ||
799 | } | ||
800 | |||
801 | // Postlude. | ||
802 | emit_file(POSTLUDE_FILE); | ||
803 | emit_data_section(); | ||
804 | emit_bss_section(); | ||
805 | |||
806 | // Clean resources. | ||
807 | array_free(constants); | ||
808 | for (size_t i = 0; i < array_size(labels); i++) { | ||
809 | free(labels[i]); | ||
810 | } | ||
811 | array_free(labels); | ||
812 | for (size_t i = 0; i < array_size(procedures); i++) { | ||
813 | array_free(procedures[i]); | ||
814 | } | ||
815 | array_free(procedures); | ||
816 | } | ||
817 | |||
818 | #endif // BDL_COMPILER_H | ||