aboutsummaryrefslogtreecommitdiff
path: root/src/compile.c
blob: e5f232ba736c87dc4a18559738ce0d9ca5d839c6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
#include <math.h>
#include <stdio.h>
#include <sys/mman.h>

#include <posthaste/compile.h>
#include <posthaste/lower.h>
#include <posthaste/utils.h>
#include <posthaste/date.h>

#include <lightening/lightening.h>

/* we have a couple assumptions running through this code:
 * 1) For each branch/jump, there's always a corresponding label
 * 2) The global value array is in the virtual register JIT_V0
 * 3) At the start of a function, we immediately allocate f->max_sp slots, and
 * slot indexes are relative to JIT_SP
 *
 * Lightening is a very cool library that provides a kind of virtual assembler,
 * as in there's a number of virtual registers that map to physical registers
 * and each virtual instruction maps to some number of physical instructions.
 * This means that the user is required to handle register allocations, (virtual)
 * code generation and possible optimizations, but with some clever tricks we can
 * pretty much ignore these limitations completely.
 *
 * Each bytecode instruction gets compiled down to some number of virtual
 * Lightening instructions that perform the same task 'as if' the instruction was
 * being interpreted. This means each operation first loads its arguments from
 * memory to virtual registers, does whatever with them, and stores the result into
 * some memory location. This is pretty inefficient but very easy to implement, and
 * even a poor JIT is almost always faster than the best bytecode.
 *
 * As for register allocations, as long as we ensure that each bytecode instruction
 * effectively 'frees' the registers it used once it's been executed, we don't
 * need to worry about it. The values are safely stored in memory where the
 * input/output locs can fetch them later, and there are no register-register
 * dependencies between bytecode instructions.
 *
 * Each operation uses at most two caller-save registers to perform its operation.
 * The only callee-save register we use is JIT_V0 for the global array, so
 * we can ask Lightening to not save the other callee-save registers, saving a bit of
 * overhead when entering/exiting functions.
 *
 * There's no particularly good way to view the generated machine code, as it would
 * require adding something like binutils' BFD library as a dependency. You can
 * always just dump each code arena to a file and look at it later, but I
 * haven't implemented it.
 */

static void compile_fn(struct fn *f);

static void *alloc_arena(size_t size)
{
	return mmap(NULL, size,
	            PROT_EXEC | PROT_READ | PROT_WRITE,
	            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}

static void free_arena(void *arena, size_t size)
{
	munmap(arena, size);
}

static jit_operand_t formal_at(size_t i)
{
	return jit_operand_mem(JIT_OPERAND_ABI_INT64, JIT_SP,
	                       i * sizeof(int64_t));
}

/* load value from global array/stack location l and place it into virtual register r */
static void get(jit_state_t *j, jit_gpr_t r, struct loc l)
{
	if (l.l) {
		jit_ldxi_l(j, r, JIT_SP, l.o * sizeof(int64_t));
		return;
	}

	jit_ldxi_l(j, r, JIT_V0, l.o * sizeof(int64_t));
}

/* store value from virtual register r to global array/stack location l*/
static void put(jit_state_t *j, jit_gpr_t r, struct loc l)
{
	if (l.l) {
		jit_stxi_l(j, l.o * sizeof(int64_t), JIT_SP, r);
		return;
	}

	jit_stxi_l(j, l.o * sizeof(int64_t), JIT_V0, r);
}

static void compile_add(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_addr(j, JIT_R0, JIT_R0, JIT_R1);
	put(j, JIT_R0, i.o);
}

static void compile_sub(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_subr(j, JIT_R0, JIT_R0, JIT_R1);
	put(j, JIT_R0, i.o);
}

static void compile_mul(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_mulr(j, JIT_R0, JIT_R0, JIT_R1);
	put(j, JIT_R0, i.o);
}

static void compile_div(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_divr(j, JIT_R0, JIT_R0, JIT_R1);
	put(j, JIT_R0, i.o);
}

static void compile_move(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	put(j, JIT_R0, i.o);
}

static void compile_const(jit_state_t *j, struct insn i)
{
	jit_movi(j, JIT_R0, i.v);
	put(j, JIT_R0, i.o);
}

static void compile_eq(jit_state_t *j, struct insn i)
{
	/* Lightening doesn't really have any register-register comparison
	 * instructions, so implement them as local branches */
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_reloc_t branch = jit_beqr(j, JIT_R0, JIT_R1);

	jit_movi(j, JIT_R0, 0);
	jit_reloc_t jump = jit_jmp(j);
	jit_patch_there(j, branch, jit_address(j));

	jit_movi(j, JIT_R0, 1);
	jit_patch_there(j, jump, jit_address(j));

	put(j, JIT_R0, i.o);
}

static void compile_lt(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_reloc_t branch = jit_bltr(j, JIT_R0, JIT_R1);

	jit_movi(j, JIT_R0, 0);
	jit_reloc_t jump = jit_jmp(j);
	jit_patch_there(j, branch, jit_address(j));

	jit_movi(j, JIT_R0, 1);
	jit_patch_there(j, jump, jit_address(j));

	put(j, JIT_R0, i.o);
}

/* helper function for compiling PRINT_DATE, Lightening doesn't handle variadics
 * so do the heavy lifting with GCC (or Clang or whatever) */
static void print_date(int64_t date)
{
	char str[11];
	date_to_string(str, (ph_date_t)date);
	printf("%s", str);
}

static void compile_print_date(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, print_date,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));
}

static void print_int(int64_t i)
{
	printf("%lli", (long long)i);
}

static void compile_print_int(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, print_int,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));
}

static void print_string(const char *s)
{
	printf("%s", s);
}

static void compile_print_string(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, print_string,
	            jit_operand_gpr(JIT_OPERAND_ABI_POINTER, JIT_R0));
}

static void print_bool(int64_t b)
{
	printf("%s", b ? "true" : "false");
}

static void compile_print_bool(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, print_bool,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));
}

static void compile_print_newline(jit_state_t *j, struct insn i)
{
	UNUSED(i);
	jit_calli_1(j, putchar, jit_operand_imm(JIT_OPERAND_ABI_INT8, '\n'));
}

static void compile_print_space(jit_state_t *j, struct insn i)
{
	UNUSED(i);
	jit_calli_1(j, putchar, jit_operand_imm(JIT_OPERAND_ABI_INT8, ' '));
}

static void compile_label(jit_state_t *j, size_t ii, struct vec *labels)
{
	/* add a mapping between instruction index (ii) and current address */
	vect_at(jit_addr_t, *labels, ii) = jit_address(j);
}

struct reloc_helper {
	jit_reloc_t r;
	size_t to;
};

static void compile_j(jit_state_t *j, struct insn i, struct vec *relocs)
{
	jit_reloc_t r = jit_jmp(j);
	struct reloc_helper h = {.r = r, .to = i.v};
	vect_append(struct reloc_helper, *relocs, &h);
}

static void compile_b(jit_state_t *j, struct insn i, struct vec *relocs)
{
	get(j, JIT_R0, i.i0);
	jit_reloc_t r = jit_bnei(j, JIT_R0, 0);
	struct reloc_helper h = {.r = r, .to = i.v};
	vect_append(struct reloc_helper, *relocs, &h);
}

static void compile_bz(jit_state_t *j, struct insn i, struct vec *relocs)
{
	get(j, JIT_R0, i.i0);
	jit_reloc_t r = jit_beqi(j, JIT_R0, 0);
	struct reloc_helper h = {.r = r, .to = i.v};
	vect_append(struct reloc_helper, *relocs, &h);
}

static void compile_arg(struct insn i, struct vec *params)
{
	jit_operand_t operand;
	struct loc l = i.i0;
	/* Lightening allows us to specify memory locations as arguments to
	 * function calls, and will automatically move the value from memory to
	 * the correct register according to the ABI */
	if (l.l) {
		operand = jit_operand_mem(JIT_OPERAND_ABI_INT64, JIT_SP,
		                          l.o * sizeof(int64_t));
	}
	else {
		operand = jit_operand_mem(JIT_OPERAND_ABI_INT64, JIT_V0,
		                          l.o * sizeof(int64_t));
	}

	vect_append(jit_operand_t, *params, &operand);
}

static void compile_call(jit_state_t *j, struct insn i, struct vec *params)
{
	jit_operand_t gp = jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_V0);
	vect_append(jit_operand_t, *params, &gp);

	struct fn *f = find_fn(i.v);
	assert(f);

	/* if we haven't already compiled this function, do it now */
	if (!f->arena)
		compile_fn(f);

	jit_calli(j, f->arena, vec_len(params), params->buf);
	/* argument instructions are only ever directly before a call
	 * instruction, so there's no risk of messing up some other call's
	 * args and we can avoid allocating a new vector */
	vec_reset(params);
}

static void compile_retval(jit_state_t *j, struct insn i)
{
	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static void compile_neg(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_negr(j, JIT_R0, JIT_R0);
	put(j, JIT_R0, i.o);
}

static void compile_today(jit_state_t *j, struct insn i)
{
	jit_calli_0(j, current_date);
	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

/* lots of these date handling instructions I've implemented as helper functions, mostly
 * just for my own sanity */
static ph_date_t date_add(int64_t i0, int64_t i1)
{
	struct tm time = tm_from_date((ph_date_t)i0);
	time.tm_mday += i1;
	mktime(&time);

	return date_from_tm(time);
}

static void compile_date_add(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_calli_2(j, date_add,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0),
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static ph_date_t date_sub(int64_t i0, int64_t i1)
{
	struct tm time = tm_from_date((ph_date_t)i0);
	time.tm_mday -= i1;
	mktime(&time);

	return date_from_tm(time);
}

static void compile_date_sub(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_calli_2(j, date_sub,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0),
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t date_diff(int64_t i0, int64_t i1)
{
	struct tm tm0 = tm_from_date((ph_date_t)i0);
	struct tm tm1 = tm_from_date((ph_date_t)i1);

	time_t t0 = mktime(&tm0);
	time_t t1 = mktime(&tm1);

	double seconds = difftime(t0, t1);
	return round(seconds / 86400);
}

static void compile_date_diff(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);

	jit_calli_2(j, date_diff,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0),
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t load_year(int64_t i0)
{
	unsigned year = 0;
	date_split((ph_date_t)i0, &year, NULL, NULL);
	return year;
}

static void compile_load_year(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, load_year,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t load_month(int64_t i0)
{
	unsigned month = 0;
	date_split((ph_date_t)i0, NULL, &month, NULL);
	return month;
}

static void compile_load_month(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, load_month,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t load_day(int64_t i0)
{
	unsigned day = 0;
	date_split((ph_date_t)i0, NULL, NULL, &day);
	return day;
}

static void compile_load_day(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, load_day,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t load_weekday(int64_t i0)
{
	struct tm time = tm_from_date((ph_date_t)i0);
	return (int64_t)time.tm_wday;
}

static void compile_load_weekday(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, load_weekday,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t load_weeknum(int64_t i0)
{
	struct tm time = tm_from_date((ph_date_t)i0);
	return time.tm_yday / 7;
}

static void compile_load_weeknum(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	jit_calli_1(j, load_weeknum,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t store_year(int64_t i0, int64_t i1)
{
	unsigned month = 0;
	unsigned day = 0;
	date_split((ph_date_t)i0, NULL, &month, &day);
	return date_from_numbers(i1, month, day);
}

static void compile_store_year(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_calli_2(j, store_year,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0),
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t store_month(int64_t i0, int64_t i1)
{
	unsigned year = 0;
	unsigned day = 0;
	date_split((ph_date_t)i0, &year, NULL, &day);
	return date_from_numbers(year, i1, day);
}

static void compile_store_month(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_calli_2(j, store_month,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0),
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static int64_t store_day(int64_t i0, int64_t i1)
{
	unsigned year = 0;
	unsigned month = 0;
	date_split((ph_date_t)i0, &year, &month, NULL);
	return date_from_numbers(year, month, i1);
}

static void compile_store_day(jit_state_t *j, struct insn i)
{
	get(j, JIT_R0, i.i0);
	get(j, JIT_R1, i.i1);
	jit_calli_2(j, store_day,
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0),
	            jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1));

	jit_retval_l(j, JIT_R0);
	put(j, JIT_R0, i.o);
}

static size_t compile_fn_body(struct fn *f, jit_state_t *j)
{
	jit_begin(j, f->arena, f->size);
	size_t frame = jit_enter_jit_abi(j, 1, 0, 0);
	/* easy off-by-one to miss, damn */
	size_t stack = jit_align_stack(j, (f->max_sp + 1) * sizeof(int64_t));

	/* move parameters to where they belong */
	struct vec params = vec_create(sizeof(jit_operand_t));
	for (size_t i = 0; i < f->params; ++i) {
		jit_operand_t operand = formal_at(i);
		vec_append(&params, &operand);
	}

	jit_operand_t gp = jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_V0);
	vec_append(&params, &gp);
	jit_load_args(j, f->params + 1, params.buf);

	/* reuse vector as argument vector */
	vec_reset(&params);

	struct vec relocs = vec_create(sizeof(struct reloc_helper));
	struct vec labels = vec_create(sizeof(jit_addr_t));
	vec_reserve(&labels, vec_len(&f->insns));

	foreach_vec(ii, f->insns) {
		struct insn i = vect_at(struct insn, f->insns, ii);
		switch (i.k) {
		case LABEL: compile_label(j, ii, &labels); break;
		case J: compile_j(j, i, &relocs); break;
		case B: compile_b(j, i, &relocs); break;
		case BZ: compile_bz(j, i, &relocs); break;
		case ADD: compile_add(j, i); break;
		case SUB: compile_sub(j, i); break;
		case MUL: compile_mul(j, i); break;
		case DIV: compile_div(j, i); break;
		case MOVE: compile_move(j, i); break;
		case CONST: compile_const(j, i); break;
		case EQ: compile_eq(j, i); break;
		case LT: compile_lt(j, i); break;

		case PRINT_DATE: compile_print_date(j, i); break;
		case PRINT_INT: compile_print_int(j, i); break;
		case PRINT_STRING: compile_print_string(j, i); break;
		case PRINT_BOOL: compile_print_bool(j, i); break;
		case PRINT_NEWLINE: compile_print_newline(j, i); break;
		case PRINT_SPACE: compile_print_space(j, i); break;

		case ARG: compile_arg(i, &params); break;
		case CALL: compile_call(j, i, &params); break;
		case RETVAL: compile_retval(j, i); break;

		case NEG: compile_neg(j, i); break;

		case TODAY: compile_today(j, i); break;

		case DATE_ADD: compile_date_add(j, i); break;
		case DATE_SUB: compile_date_sub(j, i); break;
		case DATE_DIFF: compile_date_diff(j, i); break;

		case LOAD_YEAR: compile_load_year(j, i); break;
		case LOAD_MONTH: compile_load_month(j, i); break;
		case LOAD_DAY: compile_load_day(j, i); break;
		case LOAD_WEEKDAY: compile_load_weekday(j, i); break;
		case LOAD_WEEKNUM: compile_load_weeknum(j, i); break;

		case STORE_YEAR: compile_store_year(j, i); break;
		case STORE_MONTH: compile_store_month(j, i); break;
		case STORE_DAY: compile_store_day(j, i); break;

		case RET: {
			get(j, JIT_R0, i.i0);
			jit_shrink_stack(j, stack);
			jit_leave_jit_abi(j, 1, 0, frame);
			jit_retr(j, JIT_R0);
			break;
		}

		case STOP: {
			jit_shrink_stack(j, stack);
			jit_leave_jit_abi(j, 1, 0, frame);
			jit_ret(j);
			break;
		}
		}
	}

	/* fix relocs */
	foreach_vec(ri, relocs) {
		struct reloc_helper h = vect_at(struct reloc_helper, relocs,
		                                ri);
		jit_addr_t a = vect_at(jit_addr_t, labels, h.to);
		jit_reloc_t r = h.r;

		assert(a);
		jit_patch_there(j, r, a);
	}

	vec_destroy(&relocs);
	vec_destroy(&labels);
	vec_destroy(&params);

	size_t size = 0;
	void *p = jit_end(j, &size);
	/* if we had enough room to finish compilation, return 0 to signify that
	 * we don't have to try to compile anything again */
	if (p)
		return 0;

	/* otherwise, return how many bytes lightening estimates that we would
	 * need to succesfully compile this function */
	return size;
}

static void compile_fn(struct fn *f)
{
	init_jit();
	jit_state_t *j = jit_new_state(NULL, NULL);
	assert(j);

	void *arena_base = NULL;
	size_t arena_size = 4096;
	/* technically there should probably be a limit to how many times we
	 * attempt compilation, but in practice we're unlikely to ever need it.
	 */
	while (1) {
		arena_base = alloc_arena(arena_size);
		assert(arena_base &&
		       "failed allocating executable arena, aborting");

		f->arena = arena_base;
		f->size = arena_size;

		size_t required_size = compile_fn_body(f, j);
		if (required_size == 0)
			break;

		free_arena(arena_base, arena_size);
		/* give at least one page more than necessary to be on the safe
		 * side */
		arena_size = required_size + 4096;
	}

	jit_destroy_state(j);
}

void compile()
{
	struct fn *f = find_fn(0);
	compile_fn(f);
}