gen.c 108.5 KiB raw
1
#include <assert.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
#include "ast.h"
6
#include "gen.h"
7
#include "gen/data.h"
8
#include "gen/emit.h"
9
#include "io.h"
10
#include "limits.h"
11
#include "module.h"
12
#include "options.h"
13
#include "ralloc.h"
14
#include "riscv.h"
15
16
#include "resolver.h"
17
#include "symtab.h"
18
#include "types.h"
19
20
/* Shorthand: get node pointer array from span in current module's parser. */
21
#define SPAN(g, s) nodespan_ptrs(&(g)->mod->parser, (s))
22
23
static void    gen_assign(gen_t *g, node_t *n);
24
static void    gen_return(gen_t *g, node_t *n);
25
static void    gen_fn(gen_t *g, node_t *n);
26
static value_t gen_array_index(gen_t *g, node_t *n, bool ref);
27
static value_t gen_array_slice(gen_t *g, value_t array_val, node_t *range);
28
static value_t gen_array_literal(gen_t *g, node_t *n);
29
static value_t gen_array_repeat(gen_t *g, node_t *n);
30
static value_t gen_expr(gen_t *g, node_t *n, bool lvalue);
31
static void    gen_expr_stmt(gen_t *g, node_t *n);
32
static void    gen_match(gen_t *g, node_t *n);
33
static void    gen_block(gen_t *g, node_t *n);
34
static void    gen_if(gen_t *g, node_t *n);
35
static void    gen_if_let(gen_t *g, node_t *n);
36
static value_t gen_if_expr(gen_t *g, node_t *n);
37
static void    gen_loop(gen_t *g, node_t *n);
38
static void    gen_break(gen_t *g, node_t *n);
39
static void    gen_var(gen_t *g, node_t *n);
40
static void    gen_const(gen_t *g, node_t *n);
41
static void    gen_static(gen_t *g, node_t *n);
42
static void    gen_nop(gen_t *g, node_t *n);
43
static value_t gen_deref(gen_t *g, node_t *n, value_t ref_val, bool lval);
44
static void    gen_ecall(gen_t *g, node_t *n);
45
static void    gen_ebreak(gen_t *g, node_t *n);
46
static void    gen_panic(gen_t *g, node_t *n);
47
static void    gen_mod(gen_t *g, node_t *n);
48
static void    gen_use(gen_t *g, node_t *n);
49
static value_t gen_as_cast(gen_t *g, node_t *n);
50
static value_t gen_union_constructor(gen_t *g, node_t *n);
51
static value_t gen_record_lit(gen_t *g, node_t *n);
52
static void    gen_throw(gen_t *g, node_t *n);
53
static value_t gen_try(gen_t *g, node_t *n);
54
static value_t gen_union_store(
55
    gen_t *g, type_t *union_type, symbol_t *variant_sym, value_t payload
56
);
57
static void    useval(gen_t *g, value_t val);
58
static void    freeval(gen_t *g, value_t val);
59
static value_t value_none(void);
60
i32            tval_payload_offset(type_t *container);
61
62
/* Convert a value into a tagged value by calculating its offsets. */
63
tval_t tval_from_val(gen_t *g, value_t val) {
64
    /* For unions with payloads, we don't know the value type in advance. */
65
    type_t *val_typ = NULL;
66
67
    if (val.type->cls == TYPE_OPT) {
68
        val_typ = val.type->info.opt.elem;
69
    } else if (val.type->cls == TYPE_RESULT) {
70
        val_typ = val.type->info.res.payload;
71
    }
72
    i32 val_off = tval_payload_offset(val.type);
73
74
    tval_t tval = { 0 };
75
    tval.tag =
76
        (value_t){ .type = g->types->type_u8, .loc = val.loc, .as = val.as };
77
    tval.typ = val.type;
78
    tval.val = (value_t){
79
        .type = val_typ,
80
        .loc  = val.loc,
81
        .as   = val.as,
82
    };
83
84
    if (val.loc == LOC_STACK) {
85
        tval.val.as.off.offset = val.as.off.offset + val_off;
86
    } else if (val.loc == LOC_ADDR) {
87
        tval.val.as.adr.offset = val.as.adr.offset + val_off;
88
    } else if (val.loc == LOC_REG) {
89
        /* Register contains the address of the optional in memory */
90
        tval.tag.loc           = LOC_STACK;
91
        tval.tag.as.off.base   = val.as.reg;
92
        tval.tag.as.off.offset = 0;
93
94
        tval.val.loc           = LOC_STACK;
95
        tval.val.as.off.base   = val.as.reg;
96
        tval.val.as.off.offset = val_off;
97
    } else {
98
        bail("cannot load tagged value from location %d", val.loc);
99
    }
100
    return tval;
101
}
102
103
/* Return the byte offset of the payload within a tagged value. */
104
i32 tval_payload_offset(type_t *container) {
105
    return container->size > TAG_SIZE ? align(TAG_SIZE, container->align)
106
                                      : TAG_SIZE;
107
}
108
109
/* Return the number of payload bytes to zero before writing a new value. */
110
i32 tval_payload_zero_size(type_t *container) {
111
    switch (container->cls) {
112
    case TYPE_OPT:
113
        return container->size - tval_payload_offset(container);
114
    case TYPE_UNION:
115
        return container->size - tval_payload_offset(container);
116
    default:
117
        return 0;
118
    }
119
}
120
121
void tval_store(gen_t *g, value_t dest, value_t value, i32 tag) {
122
    /* Optional values treat tag 0 as nil; everything else always stores a
123
     * payload area. */
124
    bool nil = (dest.type->cls == TYPE_OPT && tag == 0);
125
126
    /* Compute base/offset for tag and payload. For addresses, materialize a
127
     * temporary base register so regstore/memzero can operate safely. */
128
    reg_t base      = ZERO;
129
    i32   tag_off   = 0;
130
    bool  base_temp = false;
131
132
    switch (dest.loc) {
133
    case LOC_STACK:
134
        base    = dest.as.off.base;
135
        tag_off = dest.as.off.offset;
136
        break;
137
    case LOC_ADDR:
138
        base      = nextreg(g);
139
        base_temp = true;
140
        emit_li(g, base, dest.as.adr.base);
141
        tag_off = dest.as.adr.offset;
142
        break;
143
    case LOC_REG: {
144
        /* Register holds the address; copy into a reserved temp. */
145
        base      = nextreg(g);
146
        base_temp = true;
147
        emit_mv(g, base, dest.as.reg);
148
        tag_off = 0;
149
        break;
150
    }
151
    default:
152
        bail("cannot store tagged value at location %d", dest.loc);
153
    }
154
    i32 payload_off = tag_off + tval_payload_offset(dest.type);
155
156
    /* Store tag (1 byte) */
157
    reg_t rd = nextreg(g);
158
    emit_li(g, rd, tag);
159
    emit_regstore(g, rd, base, tag_off, g->types->type_u8);
160
    freereg(g, rd);
161
162
    /* Zero padding between tag byte and payload start so that byte-level
163
     * equality comparisons of tagged values work correctly. */
164
    i32 pad_off  = tag_off + TAG_SIZE;
165
    i32 pad_size = payload_off - pad_off;
166
    if (pad_size > 0) {
167
        emit_memzero(g, OFFSET(base, pad_off), pad_size);
168
    }
169
170
    /* Clear payload region before writing a new value (or when nil). */
171
    i32 payload_size = tval_payload_zero_size(dest.type);
172
    emit_memzero(g, OFFSET(base, payload_off), payload_size);
173
174
    if (!nil && value.type && value.type->cls != TYPE_VOID) {
175
        emit_store(g, value, base, payload_off);
176
    }
177
    if (base_temp)
178
        freereg(g, base);
179
}
180
181
/* Helper function to create an optional value from a primitive immediate. */
182
static value_t optval_from_prim(gen_t *g, type_t *opt_type, value_t prim_val) {
183
    i32     offset  = reserve(g, opt_type);
184
    value_t opt_val = value_stack(OFFSET(FP, offset), opt_type);
185
    tval_store(g, opt_val, prim_val, 1);
186
187
    return opt_val;
188
}
189
190
static value_t optval_from_value(gen_t *g, type_t *opt_type, value_t value) {
191
    if (value.type == opt_type)
192
        return value;
193
194
    i32     offset  = reserve(g, opt_type);
195
    value_t opt_val = value_stack(OFFSET(FP, offset), opt_type);
196
197
    tval_store(g, opt_val, value, 1);
198
199
    return opt_val;
200
}
201
202
/* Load the tag of tagged value into a register. */
203
static reg_t tval_load_tag(gen_t *g, value_t opt_val) {
204
    tval_t opt = tval_from_val(g, opt_val);
205
206
    return emit_load(g, opt.tag);
207
}
208
209
/* Helper to bind a union variant payload to a variable. */
210
static void bind_union_value(gen_t *g, value_t union_src, node_t *bound_var) {
211
    symbol_t *var_sym = bound_var->sym;
212
213
    /* Allocate storage for the bound variable if not already allocated */
214
    if (var_sym->e.var.val.loc == LOC_NONE) {
215
        i32 off = reserve_aligned(g, var_sym->e.var.typ, var_sym->e.var.align);
216
        var_sym->e.var.val = value_stack(OFFSET(FP, off), var_sym->e.var.typ);
217
    }
218
    /* Create a value pointing to the value part of the union (after the tag) */
219
    type_t *union_type = union_src.type;
220
    if (union_type->cls == TYPE_PTR)
221
        union_type = union_type->info.ptr.target;
222
    i32     val_off        = tval_payload_offset(union_type);
223
    value_t union_val_part = union_src;
224
    union_val_part.type    = bound_var->type;
225
226
    if (union_src.loc == LOC_STACK) {
227
        union_val_part.as.off.offset += val_off;
228
    } else if (union_src.loc == LOC_ADDR) {
229
        union_val_part.as.adr.offset += val_off;
230
    } else {
231
        bail("cannot bind union value from this location");
232
    }
233
    /* Copy the union payload to the bound variable */
234
    emit_replace(g, var_sym->e.var.val, union_val_part);
235
}
236
237
/* Copy the value part of an optional to a destination */
238
static void optval_copy_value(gen_t *g, value_t opt_src, value_t value_dest) {
239
    tval_t opt = tval_from_val(g, opt_src);
240
    emit_replace(g, value_dest, opt.val);
241
}
242
243
/* Generate a union constructor call like Expr::number(42) */
244
static value_t gen_union_constructor(gen_t *g, node_t *call) {
245
    type_t *variant_type = call->sym->node->type;
246
    value_t payload      = value_none();
247
248
    if (variant_type->cls != TYPE_VOID) {
249
        node_t *arg_node = SPAN(g, call->val.call.args)[0];
250
        node_t *arg_expr = arg_node->val.call_arg.expr;
251
        payload          = gen_expr(g, arg_expr, false);
252
    }
253
    return gen_union_store(g, call->type, call->sym, payload);
254
}
255
256
static value_t gen_union_store(
257
    gen_t *g, type_t *union_type, symbol_t *variant_sym, value_t payload
258
) {
259
    i32 tag = variant_sym->node->val.union_variant.value;
260
261
    /* Allocate space for the union on the stack */
262
    i32     offset    = reserve(g, union_type);
263
    value_t union_val = value_stack(OFFSET(FP, offset), union_type);
264
265
    /* Store the union value */
266
    useval(g, payload);
267
    tval_store(g, union_val, payload, tag);
268
    freeval(g, payload);
269
270
    return union_val;
271
}
272
273
/* Node type to generator function mapping. */
274
static void (*GENERATORS[])(gen_t *, node_t *) = {
275
    [NODE_TYPE]       = NULL,
276
    [NODE_NUMBER]     = NULL,
277
    [NODE_BOOL]       = NULL,
278
    [NODE_STRING]     = NULL,
279
    [NODE_CHAR]       = NULL,
280
    [NODE_IDENT]      = NULL,
281
    [NODE_BINOP]      = NULL,
282
    [NODE_BLOCK]      = gen_block,
283
    [NODE_CALL]       = NULL,
284
    [NODE_CALL_ARG]   = NULL,
285
    [NODE_VAR]        = gen_var,
286
    [NODE_CONST]      = gen_const,
287
    [NODE_STATIC]     = gen_static,
288
    [NODE_ASSIGN]     = gen_assign,
289
    [NODE_RETURN]     = gen_return,
290
    [NODE_THROW]      = gen_throw,
291
    [NODE_PANIC]      = gen_panic,
292
    [NODE_WHILE]      = NULL,
293
    [NODE_WHILE_LET]  = NULL,
294
    [NODE_FOR]        = NULL,
295
    [NODE_LOOP]       = gen_loop,
296
    [NODE_IF]         = gen_if,
297
    [NODE_IF_LET]     = gen_if_let,
298
    [NODE_IF_CASE]    = NULL,
299
    [NODE_GUARD_CASE] = NULL,
300
    [NODE_GUARD_LET]  = NULL,
301
    [NODE_MATCH]      = gen_match,
302
    [NODE_MATCH_CASE] = gen_nop, /* Cases are handled by gen_match */
303
    [NODE_FN]         = gen_fn,
304
    [NODE_BREAK]      = gen_break,
305
    [NODE_RECORD]     = gen_nop,
306
    [NODE_UNION]      = gen_nop,
307
    [NODE_EXPR_STMT]  = gen_expr_stmt,
308
    [NODE_MOD]        = gen_mod,
309
    [NODE_USE]        = gen_use,
310
};
311
312
/* Built-in functions */
313
static const struct {
314
    const char *name;
315
    usize       length;
316
    void (*gen)(gen_t *, node_t *);
317
} BUILTINS[] = {
318
    { "std::intrinsics::ecall", 22, gen_ecall },
319
    { "std::intrinsics::ebreak", 23, gen_ebreak },
320
    { NULL, 0, NULL },
321
};
322
323
/******************************************************************************/
324
325
value_t value_addr(usize addr, i32 off, type_t *ty) {
326
    return (value_t){
327
        .type          = ty,
328
        .loc           = LOC_ADDR,
329
        .as.adr.base   = addr,
330
        .as.adr.offset = off,
331
    };
332
}
333
334
value_t value_stack(offset_t off, type_t *ty) {
335
    return (value_t){
336
        .type          = ty,
337
        .loc           = LOC_STACK,
338
        .as.off.base   = off.base,
339
        .as.off.offset = off.offset,
340
    };
341
}
342
343
value_t value_reg(reg_t r, type_t *ty) {
344
    return (value_t){
345
        .temp   = true,
346
        .type   = ty,
347
        .loc    = LOC_REG,
348
        .as.reg = r,
349
    };
350
}
351
352
value_t value_imm(imm_t imm, type_t *ty) {
353
    return (value_t){
354
        .type   = ty,
355
        .loc    = LOC_IMM,
356
        .as.imm = imm,
357
    };
358
}
359
360
static value_t value_none(void) {
361
    return (value_t){
362
        .type = NULL,
363
        .loc  = LOC_NONE,
364
    };
365
}
366
367
i32 align_stack(i32 addr, i32 alignment) {
368
    /* Verify alignment is a power of 2. */
369
370
    /* For negative addresses (stack growth downward),
371
     * we round down to the next multiple of alignment. */
372
    return addr & ~(alignment - 1);
373
}
374
375
i32 jump_offset(usize from, usize to) {
376
    return ((i32)to - (i32)from) * INSTR_SIZE;
377
}
378
379
/* Provide a sentinel patch so callers can keep a uniform interface. */
380
static branch_patch_t branch_patch_invalid(void) {
381
    return (branch_patch_t){
382
        .pc       = (usize)-1,
383
        .tramp_pc = (usize)-1,
384
        .op       = I_BEQ,
385
        .rs1      = ZERO,
386
        .rs2      = ZERO,
387
        .valid    = false,
388
    };
389
}
390
391
/* Reserve space for the branch and a fallback trampoline in one call. */
392
static branch_patch_t branch_patch_make(
393
    gen_t *g, iname_t op, reg_t rs1, reg_t rs2
394
) {
395
    branch_patch_t patch = {
396
        .pc       = emit(g, NOP),
397
        .tramp_pc = emit(g, NOP),
398
        .op       = op,
399
        .rs1      = rs1,
400
        .rs2      = rs2,
401
        .valid    = true,
402
    };
403
    return patch;
404
}
405
406
/* Flip a branch opcode so the trampoline executes on the opposite outcome. */
407
static iname_t branch_op_inverse(iname_t op) {
408
    switch (op) {
409
    case I_BEQ:
410
        return I_BNE;
411
    case I_BNE:
412
        return I_BEQ;
413
    case I_BLT:
414
        return I_BGE;
415
    case I_BGE:
416
        return I_BLT;
417
    case I_BLTU:
418
        return I_BGEU;
419
    case I_BGEU:
420
        return I_BLTU;
421
    default:
422
        return 0;
423
    }
424
}
425
426
/* Finalize the branch, rewriting to a long-range form when necessary. */
427
static void branch_patch_apply(gen_t *g, branch_patch_t patch, usize target) {
428
    if (!patch.valid)
429
        return;
430
431
    i32 imm = jump_offset(patch.pc, target);
432
    if (is_branch_imm(imm)) {
433
        g->instrs[patch.pc] = instr(patch.op, ZERO, patch.rs1, patch.rs2, imm);
434
        g->instrs[patch.tramp_pc] = NOP;
435
        return;
436
    }
437
438
    usize fallthrough = patch.tramp_pc + 1;
439
    i32   skip_imm    = jump_offset(patch.pc, fallthrough);
440
441
    iname_t inv         = branch_op_inverse(patch.op);
442
    g->instrs[patch.pc] = instr(inv, ZERO, patch.rs1, patch.rs2, skip_imm);
443
444
    i32 jmp_imm               = jump_offset(patch.tramp_pc, target);
445
    g->instrs[patch.tramp_pc] = JMP(jmp_imm);
446
}
447
448
i32 reserve(gen_t *g, type_t *ty) {
449
    return reserve_aligned(g, ty, ty->align);
450
}
451
452
static void useval(gen_t *g, value_t val) {
453
    if (val.loc == LOC_REG) {
454
        usereg(g, val.as.reg);
455
    } else if (val.loc == LOC_STACK) {
456
        usereg(g, val.as.off.base);
457
    }
458
}
459
460
static void freeval(gen_t *g, value_t val) {
461
    if (val.loc == LOC_REG && val.temp) {
462
        freereg(g, val.as.reg);
463
    }
464
}
465
466
/******************************************************************************/
467
468
/* Patch all break statements for a loop. */
469
static void patch_break_stmts(gen_t *g) {
470
    for (usize i = 0; i < g->fn.nbrkpatches; i++) {
471
        ctpatch_t *p = &g->fn.brkpatches[i];
472
        if (!p->applied && p->loop == g->loop.current) {
473
            /* Calculate jump offset to the loop end, and apply patch. */
474
            i32 offset       = jump_offset(p->pc, g->loop.end);
475
            g->instrs[p->pc] = JAL(ZERO, offset);
476
            p->applied       = true;
477
        }
478
    }
479
}
480
481
/******************************************************************************/
482
483
/* Generate code for a node. */
484
static void gen_node(gen_t *g, node_t *n) {
485
    if (!n)
486
        return;
487
488
    if (!GENERATORS[n->cls])
489
        bail("unsupported node type '%s'", node_names[n->cls]);
490
491
    /* Restore register allocation state between statements to avoid leaking */
492
    bool regs[RALLOC_NREGS] = { false };
493
494
    ralloc_save(&g->regs, regs);
495
    GENERATORS[n->cls](g, n);
496
    ralloc_restore(&g->regs, regs);
497
}
498
499
/* System call (ecall): Takes four arguments (A0, A1, A2, A3) */
500
static void gen_ecall(gen_t *g, node_t *n) {
501
    node_t **cargs = SPAN(g, n->val.call.args);
502
    node_t  *num   = cargs[0];
503
    node_t  *arg0  = cargs[1];
504
    node_t  *arg1  = cargs[2];
505
    node_t  *arg2  = cargs[3];
506
    node_t  *arg3  = cargs[4];
507
508
    value_t numval  = gen_expr(g, num->val.call_arg.expr, false);
509
    value_t arg0val = gen_expr(g, arg0->val.call_arg.expr, false);
510
    value_t arg1val = gen_expr(g, arg1->val.call_arg.expr, false);
511
    value_t arg2val = gen_expr(g, arg2->val.call_arg.expr, false);
512
    value_t arg3val = gen_expr(g, arg3->val.call_arg.expr, false);
513
514
    /* Move the arguments to the appropriate registers. Load higher-numbered
515
     * argument registers first so we don't overwrite values that are still
516
     * needed for lower-numbered arguments (e.g. when the source value lives in
517
     * A0). */
518
    usereg(g, A7);
519
    emit_load_into(g, A7, numval); /* Syscall number is stored in A7 */
520
521
    usereg(g, A3);
522
    emit_load_into(g, A3, arg3val);
523
524
    usereg(g, A2);
525
    emit_load_into(g, A2, arg2val);
526
527
    usereg(g, A1);
528
    emit_load_into(g, A1, arg1val);
529
530
    usereg(g, A0);
531
    emit_load_into(g, A0, arg0val);
532
533
    emit(g, ECALL);
534
535
    freereg(g, A3);
536
    freereg(g, A2);
537
    freereg(g, A1);
538
    freereg(g, A7);
539
}
540
541
/* Emit an EBREAK instruction */
542
static void gen_ebreak(gen_t *g, node_t *n) {
543
    (void)n;
544
    emit(g, EBREAK);
545
}
546
547
/* Generate panic statement */
548
static void gen_panic(gen_t *g, node_t *n) {
549
    (void)n;
550
    emit(g, EBREAK);
551
}
552
553
static void gen_expr_stmt(gen_t *g, node_t *n) {
554
    /* Generate the expression as a statement; result will be discarded. */
555
    value_t result = gen_expr(g, n->val.expr_stmt, false);
556
    /* For non-void expressions, we free any allocated registers */
557
    if (result.loc == LOC_REG) {
558
        freereg(g, result.as.reg);
559
    }
560
}
561
562
/* Generate conditional branch code. */
563
static void gen_branch(gen_t *g, node_t *cond, node_t *lbranch) {
564
    binop_t op    = cond->val.binop.op;
565
    value_t lval  = gen_expr(g, cond->val.binop.left, false);
566
    value_t rval  = gen_expr(g, cond->val.binop.right, false);
567
    reg_t   left  = emit_load(g, lval);
568
    reg_t   right = emit_load(g, rval);
569
570
    iname_t branch_op   = I_BEQ;
571
    reg_t   rs1         = left;
572
    reg_t   rs2         = right;
573
    bool    is_unsigned = false;
574
575
    if (cond->val.binop.left->type) {
576
        is_unsigned = type_is_unsigned(cond->val.binop.left->type->cls);
577
    }
578
579
    /* Select the appropriate branch instruction based on the comparison
580
     * operator. Nb. we're branching if the condition is *false*, so we use the
581
     * opposite branch instruction. */
582
    switch (op) {
583
    case OP_EQ:
584
        branch_op = I_BNE;
585
        break;
586
    case OP_LT:
587
        branch_op = is_unsigned ? I_BGEU : I_BGE;
588
        break;
589
    case OP_GT:
590
        branch_op = is_unsigned ? I_BGEU : I_BGE;
591
        rs1       = right;
592
        rs2       = left;
593
        break;
594
    case OP_LE:
595
        branch_op = is_unsigned ? I_BLTU : I_BLT;
596
        rs1       = right;
597
        rs2       = left;
598
        break;
599
    case OP_GE:
600
        branch_op = is_unsigned ? I_BLTU : I_BLT;
601
        break;
602
    case OP_NE:
603
        /* For not equals, branch if they are equal. */
604
        branch_op = I_BEQ;
605
        break;
606
    case OP_AND:
607
    case OP_OR:
608
    case OP_ADD:
609
    case OP_SUB:
610
    case OP_DIV:
611
    case OP_MUL:
612
    case OP_MOD:
613
    case OP_BAND:
614
    case OP_BOR:
615
    case OP_XOR:
616
    case OP_SHL:
617
    case OP_SHR:
618
        abort();
619
    }
620
621
    branch_patch_t patch = branch_patch_make(g, branch_op, rs1, rs2);
622
623
    freereg(g, left);
624
    freereg(g, right);
625
626
    /* Generate code for the left (true) branch. */
627
    gen_block(g, lbranch);
628
629
    /* Patch the branch to jump past the left branch when false. */
630
    branch_patch_apply(g, patch, g->ninstrs);
631
}
632
633
/* Generate code for an if/else condition with arbitrary condition and branches.
634
 * This function is used both for regular if statements and for match cases. */
635
static void gen_if_else(
636
    gen_t  *g,
637
    value_t condition_val, /* Condition value to test */
638
    node_t *lbranch,       /* Code to execute if condition is true */
639
    node_t *rbranch        /* Code to execute if condition is false */
640
) {
641
    /* Load the condition value into a register */
642
    reg_t condreg = emit_load(g, condition_val);
643
644
    /* Emit a conditional branch: if condition is zero (false),
645
     * jump past the left branch. */
646
    branch_patch_t lb_branch = branch_patch_make(g, I_BEQ, condreg, ZERO);
647
    /* Nb. we free this register here even though the register _name_ is used
648
     * lower, because it's only used for patching the instruction above. */
649
    freereg(g, condreg);
650
651
    /* Generate code for the true branch. */
652
    gen_block(g, lbranch);
653
654
    if (rbranch) {
655
        /* If we have an else branch, emit jump to skip over it. */
656
        const usize lb_end   = emit(g, NOP);
657
        const usize rb_start = g->ninstrs;
658
659
        /* Patch the branch instruction to jump to else. */
660
        branch_patch_apply(g, lb_branch, rb_start);
661
662
        /* Generate code for the false branch. */
663
        gen_block(g, rbranch);
664
665
        /* Patch the jump past else. */
666
        const usize rb_end = g->ninstrs;
667
        g->instrs[lb_end]  = JMP(jump_offset(lb_end, rb_end));
668
    } else {
669
        /* No false branch, just patch the conditional branch to jump to the
670
         * end. */
671
        const usize end = g->ninstrs;
672
        branch_patch_apply(g, lb_branch, end);
673
    }
674
}
675
676
/* Generate guard check for a match case. Updates ctrl->guard_branch if guard
677
 * present. */
678
static void gen_case_guard(gen_t *g, node_t *n, match_case_ctrl_t *ctrl) {
679
    if (n->val.match_case.guard) {
680
        value_t guard_val  = gen_expr(g, n->val.match_case.guard, false);
681
        reg_t   guard_reg  = emit_load(g, guard_val);
682
        ctrl->guard_branch = branch_patch_make(g, I_BEQ, guard_reg, ZERO);
683
        freereg(g, guard_reg);
684
    }
685
}
686
687
/* Bind a pattern variable to a record field value. Allocates stack space for
688
 * the variable if needed and copies the field value into it.
689
 * For ref matches (variable type is pointer to field type), stores the address
690
 * of the field instead of copying its value. */
691
static void bind_var_to_field(
692
    gen_t *g, value_t record_val, symbol_t *field_sym, symbol_t *var_sym
693
) {
694
    if (var_sym->e.var.val.loc == LOC_NONE) {
695
        i32 off = reserve_aligned(g, var_sym->e.var.typ, var_sym->e.var.align);
696
        var_sym->e.var.val = value_stack(OFFSET(FP, off), var_sym->e.var.typ);
697
    }
698
    value_t field_val = record_val;
699
    field_val.type    = field_sym->e.field.typ;
700
    if (field_val.loc == LOC_STACK)
701
        field_val.as.off.offset += field_sym->e.field.offset;
702
    else if (field_val.loc == LOC_ADDR)
703
        field_val.as.adr.offset += field_sym->e.field.offset;
704
    else if (field_val.loc == LOC_REG) {
705
        /* Register holds the address of the record. Convert to LOC_STACK
706
         * so that the field offset is applied when loading. */
707
        reg_t base_reg          = field_val.as.reg;
708
        field_val.loc           = LOC_STACK;
709
        field_val.as.off.base   = base_reg;
710
        field_val.as.off.offset = field_sym->e.field.offset;
711
    }
712
713
    /* Check if this is a ref match (variable is pointer to field type) */
714
    type_t *var_typ = var_sym->e.var.typ;
715
    if (var_typ->cls == TYPE_PTR &&
716
        var_typ->info.ptr.target == field_sym->e.field.typ) {
717
        /* Store address of field instead of copying value */
718
        reg_t addr_reg = nextreg(g);
719
        if (field_val.loc == LOC_STACK) {
720
            emit_addr_offset(
721
                g, addr_reg, field_val.as.off.base, field_val.as.off.offset
722
            );
723
        } else if (field_val.loc == LOC_ADDR) {
724
            emit_li(
725
                g, addr_reg, field_val.as.adr.base + field_val.as.adr.offset
726
            );
727
        } else if (field_val.loc == LOC_REG) {
728
            emit(
729
                g, ADDI(addr_reg, field_val.as.reg, field_sym->e.field.offset)
730
            );
731
        } else {
732
            bail("cannot take address of field for ref match");
733
        }
734
        /* Store the address register into the variable's stack location */
735
        emit_regstore(
736
            g,
737
            addr_reg,
738
            var_sym->e.var.val.as.off.base,
739
            var_sym->e.var.val.as.off.offset,
740
            var_sym->e.var.typ
741
        );
742
        freereg(g, addr_reg);
743
    } else {
744
        emit_replace(g, var_sym->e.var.val, field_val);
745
    }
746
}
747
748
/* Bind fields from a record value to pattern variables. Handles both
749
 * tuple-style patterns like `S(x, y)` and labeled patterns like `T { x, y }`.
750
 */
751
static void gen_bind_record_fields(
752
    gen_t *g, value_t record_val, node_t *pattern, type_t *record_type
753
) {
754
    if (pattern->cls == NODE_CALL) {
755
        for (usize i = 0; i < pattern->val.call.args.len; i++) {
756
            node_t *arg_node = SPAN(g, pattern->val.call.args)[i];
757
            node_t *arg      = (arg_node->cls == NODE_CALL_ARG)
758
                                   ? arg_node->val.call_arg.expr
759
                                   : arg_node;
760
            if (arg->cls == NODE_IDENT && arg->sym) {
761
                bind_var_to_field(
762
                    g, record_val, record_type->info.srt.fields[i], arg->sym
763
                );
764
            }
765
        }
766
    } else if (pattern->cls == NODE_RECORD_LIT) {
767
        node_t **fields =
768
            nodespan_ptrs(&g->mod->parser, pattern->val.record_lit.fields);
769
        for (usize i = 0; i < pattern->val.record_lit.fields.len; i++) {
770
            node_t *binding = fields[i]->val.record_lit_field.value;
771
            if (binding->cls == NODE_IDENT && binding->sym) {
772
                bind_var_to_field(g, record_val, fields[i]->sym, binding->sym);
773
            }
774
        }
775
    }
776
}
777
778
static match_case_ctrl_t gen_match_case_union_payload(
779
    gen_t *g, value_t match_val, node_t *n
780
) {
781
    match_case_ctrl_t ctrl = {
782
        .skip_body    = 0,
783
        .guard_branch = branch_patch_invalid(),
784
    };
785
    /* Array to store jumps to body when a pattern matches */
786
    branch_patch_t jumps[MAX_CASE_PATTERNS];
787
    usize          njumps = 0;
788
789
    /* union pattern matching - generate tag comparisons */
790
    node_t **patterns =
791
        nodespan_ptrs(&g->mod->parser, n->val.match_case.patterns);
792
    for (usize p = 0; p < n->val.match_case.patterns.len; p++) {
793
        node_t *patt_node = patterns[p];
794
        node_t *callee    = NULL;
795
796
        if (patt_node->cls == NODE_CALL) {
797
            callee = patt_node->val.call.callee;
798
        } else if (patt_node->cls == NODE_RECORD_LIT) {
799
            callee = patt_node->val.record_lit.type;
800
        } else {
801
            callee = patt_node;
802
        }
803
804
        /* Use the stored variant index */
805
        node_t *variant_ident = callee->val.access.rval;
806
        usize   variant_tag = variant_ident->sym->node->val.union_variant.value;
807
808
        /* Generate tag comparison.
809
         * For ref matching (pointer-to-union), the register holds an address
810
         * we need to load from. */
811
        reg_t tag_reg;
812
        if (match_val.loc == LOC_REG && match_val.type->cls == TYPE_PTR) {
813
            /* Load tag byte from address in register */
814
            tag_reg = nextreg(g);
815
            emit(g, LBU(tag_reg, match_val.as.reg, 0));
816
        } else {
817
            value_t tag_val = match_val;
818
            tag_val.type    = g->types->type_u8;
819
            tag_reg         = emit_load(g, tag_val);
820
        }
821
        reg_t variant_idx_reg = nextreg(g);
822
        emit(g, ADDI(variant_idx_reg, ZERO, variant_tag));
823
        jumps[njumps++] = branch_patch_make(g, I_BEQ, tag_reg, variant_idx_reg);
824
825
        freereg(g, variant_idx_reg);
826
        freereg(g, tag_reg);
827
    }
828
829
    /* If none of the patterns match, jump past the body */
830
    ctrl.skip_body   = emit(g, NOP); /* Will be patched later */
831
    usize body_start = g->ninstrs;   /* Body starts here */
832
833
    /* Patch all the pattern match jumps to point to the body start */
834
    for (usize p = 0; p < njumps; p++) {
835
        branch_patch_apply(g, jumps[p], body_start);
836
    }
837
    /* Set up bound variable for payload binding */
838
    if (n->val.match_case.variable) {
839
        /* If variable doesn't have a symbol, it's likely a placeholder,
840
         * eg. `_`, so we don't bind anything. */
841
        if (n->val.match_case.variable->sym) {
842
            bind_union_value(g, match_val, n->val.match_case.variable);
843
        }
844
    }
845
    /* Handle record literal pattern field bindings */
846
    if (n->val.match_case.patterns.len == 1) {
847
        node_t *patt = patterns[0];
848
        if (patt->cls == NODE_RECORD_LIT) {
849
            node_t *callee       = patt->val.record_lit.type;
850
            node_t *variant_node = callee->val.access.rval;
851
            type_t *payload_type = variant_node->sym->node->type;
852
853
            /* Create a value pointing to the payload (after tag).
854
             * When matching on a reference, match_val.type is a pointer;
855
             * dereference to get the underlying union type. */
856
            type_t *union_type = match_val.type;
857
            if (union_type->cls == TYPE_PTR)
858
                union_type = union_type->info.ptr.target;
859
            i32     val_off = tval_payload_offset(union_type);
860
            value_t payload = match_val;
861
            if (payload.loc == LOC_STACK) {
862
                payload.as.off.offset += val_off;
863
            } else if (payload.loc == LOC_ADDR) {
864
                payload.as.adr.offset += val_off;
865
            } else if (payload.loc == LOC_REG) {
866
                /* Register contains union address; add offset to get payload */
867
                reg_t payload_reg = nextreg(g);
868
                emit(g, ADDI(payload_reg, payload.as.reg, val_off));
869
                payload = value_reg(payload_reg, payload_type);
870
            }
871
            payload.type = payload_type;
872
873
            gen_bind_record_fields(g, payload, patt, payload_type);
874
        }
875
    }
876
    gen_case_guard(g, n, &ctrl);
877
    return ctrl;
878
}
879
880
/* Generate code for a match case with a standalone record pattern.
881
 * Record patterns always match (no tag comparison), so we just bind fields. */
882
static match_case_ctrl_t gen_match_case_record(
883
    gen_t *g, value_t match_val, node_t *n
884
) {
885
    match_case_ctrl_t ctrl = { 0, branch_patch_invalid() };
886
    node_t          **patterns =
887
        nodespan_ptrs(&g->mod->parser, n->val.match_case.patterns);
888
889
    if (n->val.match_case.patterns.len >= 1)
890
        gen_bind_record_fields(g, match_val, patterns[0], match_val.type);
891
892
    gen_case_guard(g, n, &ctrl);
893
    return ctrl;
894
}
895
896
static match_case_ctrl_t gen_match_case(gen_t *g, reg_t match_reg, node_t *n) {
897
    match_case_ctrl_t ctrl = {
898
        .skip_body    = 0,
899
        .guard_branch = branch_patch_invalid(),
900
    };
901
    /* Array to store jumps to body when a pattern matches */
902
    branch_patch_t jumps[MAX_CASE_PATTERNS];
903
    usize          njumps = 0;
904
905
    /* Regular pattern matching (non-payload types) */
906
    node_t **patterns =
907
        nodespan_ptrs(&g->mod->parser, n->val.match_case.patterns);
908
    for (usize p = 0; p < n->val.match_case.patterns.len; p++) {
909
        node_t *patt_node = patterns[p];
910
        value_t patt_val  = gen_expr(g, patt_node, false);
911
        reg_t   patt_reg  = emit_load(g, patt_val);
912
913
        /* If this pattern matches, jump to the body
914
         * (Will be patched later) */
915
        jumps[njumps++] = branch_patch_make(g, I_BEQ, match_reg, patt_reg);
916
        freereg(g, patt_reg);
917
    }
918
    /* If none of the patterns match, jump past the body */
919
    ctrl.skip_body   = emit(g, NOP); /* Will be patched later */
920
    usize body_start = g->ninstrs;   /* Body starts here */
921
922
    /* Patch all the pattern match jumps to point to the body start */
923
    for (usize p = 0; p < njumps; p++) {
924
        branch_patch_apply(g, jumps[p], body_start);
925
    }
926
    gen_case_guard(g, n, &ctrl);
927
    return ctrl;
928
}
929
930
/* Generate code for a match statement by converting it to a series of
931
 * equality comparisons */
932
static void gen_match(gen_t *g, node_t *n) {
933
    /* If there are no cases, nothing to do */
934
    if (n->val.match_stmt.cases.len == 0)
935
        return;
936
937
    /* Generate code for the match operand and load it into a register */
938
    value_t match_val = gen_expr(g, n->val.match_stmt.expr, false);
939
    reg_t   match_reg = emit_load(g, match_val);
940
941
    /* Track jump locations to the end of the match */
942
    usize end_jumps[MAX_SWITCH_CASES];
943
    usize nend_jumps = 0;
944
945
    /* Process each case from first to last */
946
    node_t **cases = nodespan_ptrs(&g->mod->parser, n->val.match_stmt.cases);
947
    for (usize i = 0; i < n->val.match_stmt.cases.len; i++) {
948
        node_t *cn = cases[i];
949
950
        if (!cn->val.match_case.patterns.len) {
951
            /* Default/else case: generate block body */
952
            gen_node(g, cn->val.match_case.body);
953
            break;
954
        }
955
        /* For cases with patterns, we need to:
956
         * 1. Generate pattern tests with jumps to the body if matching
957
         * 2. Jump to the next case if no patterns match
958
         * 3. Generate the body
959
         * 4. Jump to the end of the match after the body */
960
        type_t           *match_type = n->val.match_stmt.expr->type;
961
        match_case_ctrl_t ctrl;
962
963
        /* Check if matching on a pointer to a union */
964
        type_t *union_type = match_type;
965
        if (match_type->cls == TYPE_PTR &&
966
            type_is_union_with_payload(match_type->info.ptr.target)) {
967
            union_type = match_type->info.ptr.target;
968
        }
969
970
        if (type_is_union_with_payload(union_type)) {
971
            ctrl = gen_match_case_union_payload(g, match_val, cn);
972
        } else if (union_type->cls == TYPE_RECORD) {
973
            ctrl = gen_match_case_record(g, match_val, cn);
974
        } else {
975
            ctrl = gen_match_case(g, match_reg, cn);
976
        }
977
        /* Generate the case body */
978
        gen_node(g, cn->val.match_case.body);
979
        /* Jump to end of the match after the body (patched later) */
980
        end_jumps[nend_jumps++] = emit(g, NOP);
981
        /* Patch the jump over the body (skip_body=0 means no patching needed)
982
         */
983
        if (ctrl.guard_branch.valid) {
984
            branch_patch_apply(g, ctrl.guard_branch, g->ninstrs);
985
        }
986
        if (ctrl.skip_body) {
987
            g->instrs[ctrl.skip_body] =
988
                JMP(jump_offset(ctrl.skip_body, g->ninstrs));
989
        }
990
    }
991
992
    /* Patch all jumps to the end of the match */
993
    usize end = g->ninstrs;
994
    for (usize i = 0; i < nend_jumps; i++) {
995
        g->instrs[end_jumps[i]] = JMP(jump_offset(end_jumps[i], end));
996
    }
997
    freeval(g, match_val);
998
}
999
1000
/* Generate code for an `if` statement. */
1001
static void gen_if(gen_t *g, node_t *n) {
1002
    node_t *cond    = n->val.if_stmt.cond;
1003
    node_t *lbranch = n->val.if_stmt.lbranch;
1004
    node_t *rbranch = n->val.if_stmt.rbranch;
1005
1006
    /* Special case for comparison operations. */
1007
    if (node_is_comp(cond)) {
1008
        /* If there's no else branch, use the simple branch generation,
1009
         * but only for primitive types that are compatible with BEQ or BNE. */
1010
        if (!rbranch && type_is_primitive(cond->val.binop.left->type)) {
1011
            gen_branch(g, cond, lbranch);
1012
            return;
1013
        }
1014
    }
1015
    gen_if_else(g, gen_expr(g, cond, false), lbranch, rbranch);
1016
}
1017
1018
/* Generate code for an if expression */
1019
static value_t gen_if_expr(gen_t *g, node_t *n) {
1020
    /* Allocate space for the result value */
1021
    i32     result_off = reserve(g, n->type);
1022
    value_t result_val = value_stack(OFFSET(FP, result_off), n->type);
1023
1024
    /* Generate condition */
1025
    value_t cond_val = gen_expr(g, n->val.if_stmt.cond, false);
1026
    reg_t   cond_reg = emit_load(g, cond_val);
1027
1028
    /* Branch to else if condition is false */
1029
    branch_patch_t else_branch = branch_patch_make(g, I_BEQ, cond_reg, ZERO);
1030
    freereg(g, cond_reg);
1031
1032
    /* Generate then branch and store result */
1033
    value_t then_val = gen_expr(g, n->val.if_stmt.lbranch, false);
1034
    emit_store(g, then_val, result_val.as.off.base, result_val.as.off.offset);
1035
1036
    /* Jump over else branch */
1037
    usize end_jump = emit(g, NOP); /* Placeholder for unconditional jump */
1038
1039
    /* Patch else branch jump */
1040
    usize else_start = g->ninstrs;
1041
    branch_patch_apply(g, else_branch, else_start);
1042
1043
    /* Generate else branch and store result */
1044
    value_t else_val = gen_expr(g, n->val.if_stmt.rbranch, false);
1045
    emit_store(g, else_val, result_val.as.off.base, result_val.as.off.offset);
1046
1047
    /* Patch end jump */
1048
    usize end           = g->ninstrs;
1049
    g->instrs[end_jump] = JMP(jump_offset(end_jump, end));
1050
1051
    return result_val;
1052
}
1053
1054
/* Generate code for an `if let` statement.
1055
 * This checks if an optional value has content and binds it to a variable if
1056
 * so. */
1057
static void gen_if_let(gen_t *g, node_t *n) {
1058
    /* Generate the optional expression */
1059
    value_t opt_val = gen_expr(g, n->val.if_let_stmt.expr, false);
1060
    /* Load the tag to check if optional has a value */
1061
    reg_t tag_reg = tval_load_tag(g, opt_val);
1062
1063
    /* Set up conditional branch: if `exists` is 0, skip the left branch */
1064
    branch_patch_t lb_branch = branch_patch_make(g, I_BEQ, tag_reg, ZERO);
1065
1066
    /* Create and allocate the bound variable (unless it's a placeholder) */
1067
    if (n->val.if_let_stmt.var->cls != NODE_PLACEHOLDER) {
1068
        symbol_t *val_sym = n->val.if_let_stmt.var->sym;
1069
        i32       val_off =
1070
            reserve_aligned(g, val_sym->e.var.typ, val_sym->e.var.align);
1071
        val_sym->e.var.val =
1072
            value_stack(OFFSET(FP, val_off), val_sym->e.var.typ);
1073
1074
        /* Copy the value part from the optional to the local variable */
1075
        optval_copy_value(g, opt_val, val_sym->e.var.val);
1076
    }
1077
1078
    /* If there's a guard condition, evaluate it */
1079
    branch_patch_t guard_branch = branch_patch_invalid();
1080
1081
    if (n->val.if_let_stmt.guard) {
1082
        value_t guard_val = gen_expr(g, n->val.if_let_stmt.guard, false);
1083
        reg_t   guard_reg = emit_load(g, guard_val);
1084
1085
        /* If guard is false, jump to else branch */
1086
        guard_branch =
1087
            branch_patch_make(g, I_BEQ, guard_reg, ZERO); /* Will patch later */
1088
        freereg(g, guard_reg);
1089
    }
1090
1091
    /* Generate code for the left branch */
1092
    gen_block(g, n->val.if_let_stmt.lbranch);
1093
1094
    if (n->val.if_let_stmt.rbranch) {
1095
        /* If we have an else branch, emit jump to skip over it */
1096
        const usize lb_end   = emit(g, NOP);
1097
        const usize rb_start = g->ninstrs;
1098
1099
        /* Patch the branch instruction to jump to else in *none* case */
1100
        branch_patch_apply(g, lb_branch, rb_start);
1101
1102
        /* Patch guard condition branch if it exists */
1103
        if (guard_branch.valid) {
1104
            branch_patch_apply(g, guard_branch, rb_start);
1105
        }
1106
        freereg(g, tag_reg);
1107
1108
        /* Generate code for the else branch */
1109
        gen_block(g, n->val.if_let_stmt.rbranch);
1110
1111
        /* Patch the jump instruction to skip over the else branch */
1112
        usize rb_end      = g->ninstrs;
1113
        g->instrs[lb_end] = JMP(jump_offset(lb_end, rb_end));
1114
    } else {
1115
        /* No else branch, just patch the branch to skip the then branch */
1116
        usize lb_end = g->ninstrs;
1117
        branch_patch_apply(g, lb_branch, lb_end);
1118
1119
        /* Patch guard condition branch if it exists */
1120
        if (guard_branch.valid) {
1121
            branch_patch_apply(g, guard_branch, lb_end);
1122
        }
1123
        freereg(g, tag_reg);
1124
    }
1125
}
1126
1127
/* Generate code for a forever loop. */
1128
static void gen_loop(gen_t *g, node_t *n) {
1129
    /* Save the outer loop context and setup new context with a new loop id. */
1130
    loop_t outer    = g->loop;
1131
    g->loop.current = n;
1132
    g->loop.start   = g->ninstrs;
1133
1134
    /* Generate code for the loop body. */
1135
    gen_block(g, n->val.loop_stmt.body);
1136
    /* Jump back to the beginning of the loop. */
1137
    emit_jump(g, g->loop.start);
1138
1139
    /* Mark this position as the loop end for break statements */
1140
    g->loop.end = g->ninstrs;
1141
    patch_break_stmts(g);
1142
    g->loop = outer;
1143
}
1144
1145
/* Generate code for a break statement. */
1146
static void gen_break(gen_t *g, node_t *n) {
1147
    (void)n;
1148
1149
    if (g->loop.current->cls != NODE_LOOP) {
1150
        bail("`break` statement outside of loop");
1151
    }
1152
    /* Instead of calculating the jump offset now, emit a placeholder
1153
     * instruction that will be patched when we know where the loop ends. */
1154
    usize offset = emit(g, NOP);
1155
1156
    /* Record this location for patching. */
1157
    g->fn.brkpatches[g->fn.nbrkpatches++] = (ctpatch_t){
1158
        .pc      = offset,
1159
        .loop    = g->loop.current,
1160
        .applied = false,
1161
    };
1162
}
1163
1164
static void gen_assign(gen_t *g, node_t *n) {
1165
    node_t *lval = n->val.assign.lval;
1166
    node_t *rval = n->val.assign.rval;
1167
1168
    switch (lval->cls) {
1169
    case NODE_IDENT: { /* Handle normal variable assignment. */
1170
        symbol_t *sym = lval->sym;
1171
1172
        value_t left  = sym->e.var.val;
1173
        value_t right = gen_expr(g, rval, false);
1174
1175
        /* Nb. frees the right value if it's in a register. */
1176
        emit_replace(g, left, right);
1177
        break;
1178
    }
1179
    case NODE_ACCESS: { /* Handle record field assignment (e.g., x.y = 1). */
1180
        value_t left  = gen_expr(g, lval, true);
1181
        value_t right = gen_expr(g, rval, false);
1182
1183
        /* Replace the field value with the right-hand side */
1184
        emit_replace(g, left, right);
1185
        break;
1186
    }
1187
    case NODE_ARRAY_INDEX: { /* Array index assignment (e.g. `arr[0] = 1`). */
1188
        value_t left  = gen_array_index(g, lval, true);
1189
        value_t right = gen_expr(g, rval, false);
1190
        /* Replace the array element value with the right-hand side. */
1191
        emit_replace(g, left, right);
1192
        /* Free the address register from array indexing */
1193
        if (left.loc == LOC_STACK) {
1194
            freereg(g, left.as.off.base);
1195
        }
1196
        break;
1197
    }
1198
    case NODE_UNOP: { /* Handle pointer dereference assignment */
1199
        if (lval->val.unop.op != OP_DEREF) {
1200
            bail("unsupported unary operator in assignment target");
1201
        }
1202
        value_t ptr_val = gen_expr(g, lval->val.unop.expr, true);
1203
        value_t right   = gen_expr(g, rval, false);
1204
        /* `gen_deref` expects an lvalue when the pointer itself is the storage
1205
         * we want to mutate (e.g., `*ptr = ...`). */
1206
        value_t left = gen_deref(g, lval, ptr_val, true);
1207
1208
        emit_replace(g, left, right);
1209
        break;
1210
    }
1211
    default:
1212
        bail("unsupported assignment target %s", node_names[lval->cls]);
1213
    }
1214
}
1215
1216
static void gen_return(gen_t *g, node_t *n) {
1217
    type_t *ret_typ = g->fn.current->node->type->info.fun.ret;
1218
    node_t *value   = n->val.return_stmt.value;
1219
    /* If there's a return value, evaluate the expression.
1220
     * Then, store the expression, in the return register A0,
1221
     * according to the RISC-V calling conventions. */
1222
    if (value) {
1223
        value_t val = gen_expr(g, value, false);
1224
1225
        if (ret_typ->cls == TYPE_RESULT) {
1226
            value_t dest;
1227
            if (type_is_passed_by_ref(ret_typ)) {
1228
                usereg(g, A0);
1229
                dest = value_stack(OFFSET(A0, 0), ret_typ);
1230
            } else {
1231
                dest = value_reg(A0, ret_typ);
1232
            }
1233
            /* Returns are always for the "success" case. */
1234
            emit_result_store_success(g, dest, val);
1235
        } else if (ret_typ->cls == TYPE_OPT &&
1236
                   type_coercible(val.type, ret_typ->info.opt.elem)) {
1237
            /* Wrap value in an optional */
1238
            usereg(g, A0);
1239
            tval_store(g, value_stack(OFFSET(A0, 0), ret_typ), val, 1);
1240
        } else if (ret_typ->cls == TYPE_OPT && val.type->cls == TYPE_OPT) {
1241
            /* Value is already optional, copy it */
1242
            usereg(g, A0);
1243
            emit_replace(g, value_stack(OFFSET(A0, 0), ret_typ), val);
1244
        } else if (type_is_passed_by_ref(val.type)) {
1245
            /* Aggregate returns go through the hidden sret pointer. */
1246
            usereg(g, A0);
1247
            emit_replace(g, value_stack(OFFSET(A0, 0), val.type), val);
1248
        } else {
1249
            emit_load_into(g, A0, val);
1250
        }
1251
        freeval(g, val);
1252
    } else {
1253
        if (ret_typ->cls == TYPE_RESULT) {
1254
            value_t dest;
1255
            if (type_is_passed_by_ref(ret_typ)) {
1256
                usereg(g, A0);
1257
                dest = value_stack(OFFSET(A0, 0), ret_typ);
1258
            } else {
1259
                dest = value_reg(A0, ret_typ);
1260
            }
1261
            emit_result_store_success(g, dest, value_none());
1262
        } else {
1263
            /* If there's no return value, we just store zero in A0. */
1264
            emit_load_into(
1265
                g, A0, value_imm((imm_t){ .i = 0 }, g->types->type_i32)
1266
            );
1267
        }
1268
    }
1269
1270
    /* Instead of returning directly, emit a placeholder jump to the function
1271
     * epilogue that will be patched later. This avoids duplicating epilogue
1272
     * code for each return point. */
1273
    usize pc = emit(g, NOP);
1274
1275
    if (g->fn.nretpatches >= MAX_RET_PATCHES)
1276
        bail("too many return statements in function");
1277
1278
    /* Record this location for patching */
1279
    g->fn.retpatches[g->fn.nretpatches++] = (ctpatch_t){
1280
        .pc      = pc,
1281
        .applied = false,
1282
    };
1283
}
1284
1285
/* Emit the control flow for `throw`
1286
 *
1287
 * 1. Evaluate the error expression
1288
 * 2. Lay it out in the caller-visible result slot (A0 or *A0)
1289
 * 3. Queue a jump to the epilogue so every throw shares the same return path */
1290
static void gen_throw(gen_t *g, node_t *n) {
1291
    type_t *fn_ret = g->fn.current->node->type->info.fun.ret;
1292
1293
    value_t err_val = gen_expr(g, n->val.throw_stmt.expr, false);
1294
    value_t dest;
1295
1296
    if (type_is_passed_by_ref(fn_ret)) {
1297
        usereg(g, A0);
1298
        dest = value_stack(OFFSET(A0, 0), fn_ret);
1299
    } else {
1300
        dest = value_reg(A0, fn_ret);
1301
    }
1302
    emit_result_store_error(g, dest, err_val);
1303
    freeval(g, err_val);
1304
1305
    /* Jump to function end (patch) */
1306
    usize pc = emit(g, NOP);
1307
1308
    if (g->fn.nretpatches >= MAX_RET_PATCHES)
1309
        bail("too many return statements in function");
1310
1311
    /* Patch to jump to function epilogue */
1312
    g->fn.retpatches[g->fn.nretpatches++] = (ctpatch_t){
1313
        .pc      = pc,
1314
        .applied = false,
1315
    };
1316
}
1317
1318
/* Emit `try`
1319
 *
1320
 * 1. Evaluate the expression result
1321
 * 2. Load its tag and branch past the error path when the tag is zero
1322
 * 3. On error, normalize the tag/value into the function result slot and
1323
 *    enqueue a jump to the epilogue (mirroring an early return)
1324
 * 4. On success, expose the payload location for the caller
1325
 *
1326
 * With catch block:
1327
 * 1. Evaluate the expression result
1328
 * 2. Load its tag and branch based on success/error
1329
 * 3. On error: execute catch block (must diverge or return void)
1330
 * 4. On success: use payload value
1331
 */
1332
static value_t gen_try(gen_t *g, node_t *n) {
1333
    /* 1. */
1334
    value_t res_val = gen_expr(g, n->val.try_expr.expr, false);
1335
    tval_t  res     = tval_from_val(g, res_val);
1336
1337
    /* Inspect the tag to determine whether the result is success or error. */
1338
    reg_t tag = nextreg(g);
1339
    emit_regload(
1340
        g, tag, res.tag.as.off.base, res.tag.as.off.offset, g->types->type_u8
1341
    );
1342
    type_t *payload     = res_val.type->info.res.payload;
1343
    type_t *result_type = n->type ? n->type : payload;
1344
1345
    /* Handle `try?` expressions */
1346
    if (n->val.try_expr.optional) {
1347
        /* Allocate stack space for the optional result. */
1348
        i32     result_offset = reserve(g, result_type);
1349
        value_t result = value_stack(OFFSET(FP, result_offset), result_type);
1350
1351
        /* Branch over the error path when the tag is zero (success). */
1352
        branch_patch_t success_branch = branch_patch_make(g, I_BEQ, tag, ZERO);
1353
        freereg(g, tag);
1354
1355
        /* Error path: store nil (tag = 0). */
1356
        tval_store(g, result, (value_t){ 0 }, 0);
1357
1358
        /* Jump over success path. */
1359
        usize end_patch = emit(g, JMP(0));
1360
1361
        /* Success path: store Some(payload) (tag = 1). */
1362
        branch_patch_apply(g, success_branch, g->ninstrs);
1363
1364
        value_t payload_val = res.val;
1365
        payload_val.type    = payload;
1366
        tval_store(g, result, payload_val, 1);
1367
1368
        /* End: both paths converge here. */
1369
        g->instrs[end_patch] = JMP(jump_offset(end_patch, g->ninstrs));
1370
1371
        return result;
1372
    }
1373
1374
    /* Handle catch block */
1375
    if (n->val.try_expr.catch_expr) {
1376
        node_t *catch_node = n->val.try_expr.catch_expr;
1377
1378
        node_t *catch_binding = catch_node->val.catch_clause.binding;
1379
        node_t *catch_body    = catch_node->val.catch_clause.body;
1380
1381
        /* Branch over the error path when the tag is zero (success). */
1382
        branch_patch_t success_branch = branch_patch_make(g, I_BEQ, tag, ZERO);
1383
        freereg(g, tag);
1384
1385
        /* If there's a binding, store the error value to the variable. */
1386
        if (catch_binding && catch_binding->sym) {
1387
            symbol_t *err_sym  = catch_binding->sym;
1388
            type_t   *err_type = res_val.type->info.res.err;
1389
            i32 err_off = reserve_aligned(g, err_type, err_sym->e.var.align);
1390
            err_sym->e.var.val = value_stack(OFFSET(FP, err_off), err_type);
1391
1392
            /* Create a value pointing to the error slot (same as payload). */
1393
            value_t err_slot = res.val;
1394
            err_slot.type    = err_type;
1395
1396
            /* Copy the error to the bound variable. */
1397
            emit_replace(g, err_sym->e.var.val, err_slot);
1398
        }
1399
        gen_block(g, catch_body);
1400
        branch_patch_apply(g, success_branch, g->ninstrs);
1401
1402
        if (catch_body->type && catch_body->type->cls == TYPE_NEVER) {
1403
            value_t payload_val = res.val;
1404
            payload_val.type    = payload;
1405
            return payload_val;
1406
        }
1407
        return value_none();
1408
    }
1409
1410
    /* Branch over the error path when the tag is zero (success). */
1411
    branch_patch_t success_branch = branch_patch_make(g, I_BEQ, tag, ZERO);
1412
    if (n->val.try_expr.panic) {
1413
        emit(g, EBREAK);
1414
        branch_patch_apply(g, success_branch, g->ninstrs);
1415
        freereg(g, tag);
1416
1417
        if (n->val.try_expr.handlers.len > 0)
1418
            bail("catch clauses not supported in code generation");
1419
1420
        if (!payload->size) {
1421
            return value_none();
1422
        }
1423
        value_t result = res.val;
1424
        result.type    = payload;
1425
1426
        return result;
1427
    }
1428
1429
    type_t *fn_ret = g->fn.current->node->type->info.fun.ret;
1430
1431
    /* Prepare the function result slot so we can store an error in-place. */
1432
    value_t dest;
1433
    if (type_is_passed_by_ref(fn_ret)) {
1434
        usereg(g, A0);
1435
        dest = value_stack(OFFSET(A0, 0), fn_ret);
1436
    } else {
1437
        dest = value_reg(A0, fn_ret);
1438
    }
1439
    /* Copy the error payload into the function result slot. */
1440
    value_t err_slot = res.val;
1441
    err_slot.type    = res_val.type->info.res.err;
1442
    emit_result_store_error(g, dest, err_slot);
1443
1444
    usize ret_pc = emit(g, NOP);
1445
1446
    if (g->fn.nretpatches >= MAX_RET_PATCHES)
1447
        bail("too many return statements in function");
1448
1449
    g->fn.retpatches[g->fn.nretpatches++] = (ctpatch_t){
1450
        .pc      = ret_pc,
1451
        .applied = false,
1452
    };
1453
    branch_patch_apply(g, success_branch, g->ninstrs);
1454
1455
    freereg(g, tag);
1456
1457
    if (n->val.try_expr.handlers.len > 0)
1458
        bail("catch clauses not supported in code generation");
1459
1460
    if (!payload->size) {
1461
        return value_none();
1462
    }
1463
    value_t result = res.val;
1464
    result.type    = payload; /* Unwrap payload */
1465
1466
    return result;
1467
}
1468
1469
static value_t gen_binop(gen_t *g, node_t *n) {
1470
    value_t lval = gen_expr(g, n->val.binop.left, false);
1471
    reg_t   left = emit_load(g, lval);
1472
1473
    /* Ensure generation for the rval does not overwrite the lval. */
1474
    usereg(g, left);
1475
1476
    value_t rval   = gen_expr(g, n->val.binop.right, false);
1477
    reg_t   right  = emit_load(g, rval);
1478
    reg_t   result = left;
1479
1480
    switch (n->val.binop.op) {
1481
    case OP_ADD:
1482
        if (type_is_int(lval.type->cls)) {
1483
            emit(g, ADDW(left, left, right));
1484
        } else {
1485
            emit(g, ADD(left, left, right));
1486
        }
1487
        break;
1488
    case OP_SUB:
1489
        if (type_is_int(lval.type->cls)) {
1490
            emit(g, SUBW(left, left, right));
1491
        } else {
1492
            emit(g, SUB(left, left, right));
1493
        }
1494
        break;
1495
    case OP_MUL:
1496
        if (type_is_int(lval.type->cls)) {
1497
            emit(g, MULW(left, left, right));
1498
        } else {
1499
            emit(g, MUL(left, left, right));
1500
        }
1501
        break;
1502
    case OP_DIV:
1503
        /* Check for division by zero (node is already set by gen_node) */
1504
        emit(g, BNE(right, ZERO, INSTR_SIZE * 2));
1505
        emit(g, EBREAK);
1506
        if (type_is_unsigned(lval.type->cls)) {
1507
            emit(g, DIVUW(left, left, right));
1508
        } else {
1509
            emit(g, DIVW(left, left, right));
1510
        }
1511
        break;
1512
    case OP_MOD:
1513
        if (type_is_int(lval.type->cls)) {
1514
            /* Check for division by zero (node is already set by gen_node) */
1515
            emit(g, BNE(right, ZERO, INSTR_SIZE * 2));
1516
            emit(g, EBREAK);
1517
            if (type_is_unsigned(lval.type->cls)) {
1518
                emit(g, REMUW(left, left, right));
1519
            } else {
1520
                emit(g, REMW(left, left, right));
1521
            }
1522
        } else {
1523
            bail("modulo operator is only supported for integers");
1524
        }
1525
        break;
1526
    case OP_EQ:
1527
    case OP_NE: {
1528
        bool invert    = (n->val.binop.op == OP_NE);
1529
        bool opt_left  = (lval.type->cls == TYPE_OPT);
1530
        bool opt_right = (rval.type->cls == TYPE_OPT);
1531
        bool left_nil  = (n->val.binop.left->cls == NODE_NIL);
1532
        bool right_nil = (n->val.binop.right->cls == NODE_NIL);
1533
1534
        /* Fast-path for comparisons with `nil`. */
1535
        if (opt_left && opt_right && (left_nil || right_nil)) {
1536
            if (left_nil && right_nil) {
1537
                freereg(g, left);
1538
                freereg(g, right);
1539
1540
                reg_t result_reg = nextreg(g);
1541
                emit_li(g, result_reg, invert ? 0 : 1);
1542
1543
                return value_reg(result_reg, n->type);
1544
            }
1545
            reg_t opt_reg = left_nil ? right : left;
1546
            reg_t nil_reg = left_nil ? left : right;
1547
1548
            freereg(g, nil_reg);
1549
1550
            reg_t tag_reg = nextreg(g);
1551
            emit(g, LBU(tag_reg, opt_reg, 0));
1552
            emit(g, SLTIU(tag_reg, tag_reg, 1));
1553
1554
            if (invert)
1555
                emit(g, XORI(tag_reg, tag_reg, 1));
1556
1557
            freereg(g, opt_reg);
1558
1559
            return value_reg(tag_reg, n->type);
1560
        }
1561
1562
        if (opt_left != opt_right) {
1563
            type_t *opt_type   = opt_left ? lval.type : rval.type;
1564
            value_t value_expr = opt_left ? rval : lval;
1565
            value_t wrapped    = optval_from_value(g, opt_type, value_expr);
1566
            reg_t   target_reg = opt_left ? right : left;
1567
1568
            emit_load_into(g, target_reg, wrapped);
1569
            result = nextreg(g);
1570
            emit_memequal(g, left, right, opt_type, result);
1571
1572
            if (invert)
1573
                emit(g, XORI(result, result, 1));
1574
        } else if (type_is_primitive(lval.type)) {
1575
            if (invert) {
1576
                /* XOR will be non-zero if values differ. */
1577
                emit(g, XOR(left, left, right));
1578
                /* Set to 1 if result is non-zero (different). */
1579
                emit(g, SLTU(left, ZERO, left));
1580
            } else {
1581
                /* Emits `result = left - right` */
1582
                if (type_is_int(lval.type->cls)) {
1583
                    emit(g, SUBW(left, left, right));
1584
                } else {
1585
                    emit(g, SUB(left, left, right));
1586
                }
1587
                /* Emits `result = (result < 1) ? 1 : 0` */
1588
                emit(g, SLTIU(left, left, 1));
1589
            }
1590
        } else {
1591
            result = nextreg(g);
1592
            emit_memequal(g, left, right, lval.type, result);
1593
            if (invert)
1594
                emit(g, XORI(result, result, 1));
1595
        }
1596
        break;
1597
    }
1598
    case OP_LT:
1599
        /* Emits `result = (left < right) ? 1 : 0` */
1600
        if (type_is_unsigned(lval.type->cls)) {
1601
            emit(g, SLTU(left, left, right));
1602
        } else {
1603
            emit(g, SLT(left, left, right));
1604
        }
1605
        break;
1606
    case OP_GT:
1607
        /* Emits `result = (right < left) ? 1 : 0` */
1608
        if (type_is_unsigned(lval.type->cls)) {
1609
            emit(g, SLTU(left, right, left));
1610
        } else {
1611
            emit(g, SLT(left, right, left));
1612
        }
1613
        break;
1614
    case OP_LE:
1615
        /* For `x <= y`, we can compute `!(x > y)`, which is `!(y < x)`, */
1616
        if (type_is_unsigned(lval.type->cls)) {
1617
            emit(g, SLTU(left, right, left));
1618
        } else {
1619
            emit(g, SLT(left, right, left));
1620
        }
1621
        emit(g, XORI(left, left, 1));
1622
        break;
1623
    case OP_GE:
1624
        /* For `x >= y`, we can compute `!(x < y)`. */
1625
        if (type_is_unsigned(lval.type->cls)) {
1626
            emit(g, SLTU(left, left, right));
1627
        } else {
1628
            emit(g, SLT(left, left, right));
1629
        }
1630
        emit(g, XORI(left, left, 1));
1631
        break;
1632
    case OP_AND:
1633
        /* Logical AND; both values must be 1 for the result to be 1. */
1634
        emit(g, AND(left, left, right));
1635
        break;
1636
    case OP_OR:
1637
        /* Logical OR; if either value is 1, the result is 1. */
1638
        emit(g, OR(left, left, right));
1639
        break;
1640
    case OP_BAND:
1641
        /* Bitwise AND */
1642
        emit(g, AND(left, left, right));
1643
        break;
1644
    case OP_BOR:
1645
        /* Bitwise OR */
1646
        emit(g, OR(left, left, right));
1647
        break;
1648
    case OP_XOR:
1649
        /* Bitwise XOR */
1650
        emit(g, XOR(left, left, right));
1651
        break;
1652
    case OP_SHL:
1653
        /* Left shift */
1654
        if (type_is_int(lval.type->cls)) {
1655
            emit(g, SLLW(left, left, right));
1656
        } else {
1657
            emit(g, SLL(left, left, right));
1658
        }
1659
        break;
1660
    case OP_SHR:
1661
        /* Right shift */
1662
        if (type_is_int(lval.type->cls)) {
1663
            emit(g, SRLW(left, left, right));
1664
        } else {
1665
            emit(g, SRL(left, left, right));
1666
        }
1667
        break;
1668
    }
1669
    /* Check if result needs to be coerced to optional type */
1670
    if (n->type->cls == TYPE_OPT) {
1671
        i32     offset     = reserve(g, n->type);
1672
        value_t opt_val    = value_stack(OFFSET(FP, offset), n->type);
1673
        value_t result_val = value_reg(result, n->type->info.opt.elem);
1674
1675
        tval_store(g, opt_val, result_val, 1);
1676
        lval = opt_val;
1677
1678
        /* Can free all registers since result is stored on stack */
1679
        freereg(g, left);
1680
        freereg(g, right);
1681
        freereg(g, result);
1682
    } else {
1683
        lval = value_reg(result, n->type);
1684
1685
        if (left != result)
1686
            freereg(g, left);
1687
        if (right != result)
1688
            freereg(g, right);
1689
    }
1690
    return lval;
1691
}
1692
1693
/* Generate code for record construction. Handles both labeled syntax like
1694
 * `Point { x: 1, y: 2 }` (NODE_RECORD_LIT) and tuple syntax like `Pair(1, 2)`
1695
 * (NODE_CALL with tuple record type). */
1696
static value_t gen_record_lit(gen_t *g, node_t *n) {
1697
    type_t *stype     = n->type;
1698
    int     strct_off = reserve(g, stype);
1699
1700
    usize nfields = (n->cls == NODE_RECORD_LIT) ? n->val.record_lit.fields.len
1701
                                                : n->val.call.args.len;
1702
    node_t **fields =
1703
        (n->cls == NODE_RECORD_LIT)
1704
            ? nodespan_ptrs(&g->mod->parser, n->val.record_lit.fields)
1705
            : NULL;
1706
1707
    for (usize i = 0; i < nfields; i++) {
1708
        symbol_t *field;
1709
        node_t   *expr;
1710
1711
        if (n->cls == NODE_RECORD_LIT) {
1712
            node_t *arg = fields[i];
1713
            field       = arg->sym ? arg->sym : stype->info.srt.fields[i];
1714
            expr        = arg->val.call_arg.expr;
1715
        } else {
1716
            node_t *arg = SPAN(g, n->val.call.args)[i];
1717
            field       = stype->info.srt.fields[i];
1718
            expr = (arg->cls == NODE_CALL_ARG) ? arg->val.call_arg.expr : arg;
1719
        }
1720
1721
        value_t argval = gen_expr(g, expr, false);
1722
        emit_record_field_set(g, argval, FP, strct_off, field);
1723
        freeval(g, argval);
1724
    }
1725
    return value_stack(OFFSET(FP, strct_off), stype);
1726
}
1727
1728
static value_t gen_call_intrinsic(
1729
    gen_t *g, node_t *n, void (*gen_intrinsic)(gen_t *, node_t *)
1730
) {
1731
    node_t *fn = n->sym->node;
1732
    type_t *ret =
1733
        fn->val.fn_decl.return_type ? fn->val.fn_decl.return_type->type : NULL;
1734
    /* Call the specialized generator for this intrinsic.
1735
     * It will handle argument processing in its own way. */
1736
    (*gen_intrinsic)(g, n);
1737
1738
    /* For void functions, return a void value */
1739
    if (!ret) {
1740
        return (value_t){ .type = NULL, .loc = LOC_NONE };
1741
    }
1742
    return value_reg(A0, ret);
1743
}
1744
1745
static value_t gen_call(gen_t *g, node_t *n) {
1746
    symbol_t   *sym  = n->sym;
1747
    const char *name = sym->qualified;
1748
1749
    /* Get the return type. Fall back to the call node type when the symbol
1750
     * does not carry a resolved function signature (eg. indirect calls). */
1751
    type_t *return_type = sym->node->type->info.fun.ret;
1752
    if (!return_type && n->type) {
1753
        return_type = n->type;
1754
    }
1755
1756
    /* Keep track of registers we saved before the call. */
1757
    i32       saved_regs[REGISTERS] = { 0 };
1758
    value_t   saved_vals[REGISTERS] = { 0 };
1759
    symbol_t *saved_syms[REGISTERS] = { 0 };
1760
1761
    /* Save live registers to the stack, in case they get clobbered by
1762
     * the callee. */
1763
    for (usize i = 0; i < RALLOC_NREGS; i++) {
1764
        reg_t r = ralloc_regs[i];
1765
1766
        /* Don't save registers that aren't caller-saved. */
1767
        if (!caller_saved_registers[r])
1768
            continue;
1769
1770
        /* Don't save registers that aren't in use. */
1771
        if (ralloc_is_free(&g->regs, r))
1772
            continue;
1773
1774
        /* Use a pointer-sized type for saving registers to the stack. */
1775
        static type_t dword = { .cls   = TYPE_PTR,
1776
                                .size  = WORD_SIZE,
1777
                                .align = WORD_SIZE };
1778
        saved_regs[r]       = emit_regpush(g, r, &dword);
1779
        /* We can free the register since it's on the stack. */
1780
        freereg(g, r);
1781
1782
        /* Parameters arrive in caller-saved registers; if we let the allocator
1783
         * reuse that register (e.g. in emit_memzero), the parameter value gets
1784
         * clobbered. When we spill the register here, rewrite the symbol to
1785
         * point at the spill slot so later loads grab the preserved copy. */
1786
        node_t *fn_node = g->fn.current->node;
1787
1788
        for (usize p = 0; p < fn_node->val.fn_decl.params.len; p++) {
1789
            node_t   *param     = SPAN(g, fn_node->val.fn_decl.params)[p];
1790
            symbol_t *param_sym = param->sym;
1791
            value_t  *param_val = &param_sym->e.var.val;
1792
1793
            if (param_val->loc == LOC_REG && param_val->as.reg == r) {
1794
                saved_syms[r] = param_sym;
1795
                saved_vals[r] = *param_val;
1796
1797
                param_sym->e.var.val =
1798
                    value_stack(OFFSET(FP, saved_regs[r]), param_val->type);
1799
                param_sym->e.var.val.temp = false;
1800
1801
                break;
1802
            }
1803
        }
1804
    }
1805
1806
    bool  sret           = type_is_passed_by_ref(return_type);
1807
    reg_t arg0           = sret ? A1 : A0;
1808
    usize avail_arg_regs = (usize)((A7 - arg0) + 1);
1809
1810
    if (n->val.call.args.len > avail_arg_regs) {
1811
        bail(
1812
            "function call '%s' requires %zu argument registers but only %zu "
1813
            "are available",
1814
            name,
1815
            n->val.call.args.len,
1816
            avail_arg_regs
1817
        );
1818
    }
1819
1820
    /* Setup arguments in argument registers (A0..A7), shifting when a hidden
1821
     * return pointer occupies A0. */
1822
    for (usize i = 0; i < n->val.call.args.len; i++) {
1823
        /* Generate code for the expression part of the argument. */
1824
        node_t *arg    = SPAN(g, n->val.call.args)[i];
1825
        value_t argval = gen_expr(g, arg->val.call_arg.expr, false);
1826
1827
        type_t *param_type = sym->node->type->info.fun.params[i];
1828
        if (param_type->cls == TYPE_OPT && argval.type->cls != TYPE_OPT) {
1829
            argval = optval_from_value(g, param_type, argval);
1830
        }
1831
        /* Mark this register as in use for the duration of the call. */
1832
        reg_t arg_reg = arg0 + (reg_t)i;
1833
        emit_load_into(g, usereg(g, arg_reg), argval);
1834
    }
1835
    /* Return value is in A0, by convention, whether or not an address was
1836
     * passed into A0 by the caller. */
1837
    reg_t return_reg = A0;
1838
    /* Return stack offset if we store it on the stack. */
1839
    i32  return_off         = 0;
1840
    i32  return_stack_off   = 0;
1841
    bool return_is_on_stack = false;
1842
1843
    /* For types that are passed by reference, allocate space in this
1844
     * stack frame, and pass the address via A0, as a hidden first parameter.
1845
     * Nb. The return record address is setup *after* the call arguments
1846
     * are generated, to not clobber A0 in case one of the arguments is a
1847
     * call, eg. `f(g())` where `f` is the current function call. */
1848
    if (return_type->cls == TYPE_VOID) {
1849
        /* For void functions, no need to allocate space for return value */
1850
    } else if (sret) {
1851
        return_off = reserve(g, return_type);
1852
        /* Result-returning callees can legitimately skip rewriting the tag on
1853
         * a fast-path success, so ensure the caller-visible slot starts zeroed.
1854
         * Other pass-by-ref aggregates are always fully overwritten by the
1855
         * callee, making a pre-emptive memset unnecessary work. */
1856
        if (return_type->cls == TYPE_RESULT) {
1857
            emit_memzero(g, OFFSET(FP, return_off), return_type->size);
1858
        }
1859
        /* Store return address in return address register. */
1860
        usereg(g, return_reg);
1861
        emit_addr_offset(g, return_reg, FP, return_off);
1862
    }
1863
1864
    /* Call the function. */
1865
    if (sym->kind == SYM_VARIABLE) {
1866
        /* Function pointer call: load address into S2 and call via JALR */
1867
        value_t fn_ptr_val = sym->e.var.val;
1868
1869
        if (fn_ptr_val.loc == LOC_REG && saved_regs[fn_ptr_val.as.reg]) {
1870
            value_t spill = value_stack(
1871
                OFFSET(FP, saved_regs[fn_ptr_val.as.reg]), fn_ptr_val.type
1872
            );
1873
            emit_load_into(g, S2, spill);
1874
        } else if (fn_ptr_val.loc == LOC_REG) {
1875
            emit_mv(g, S2, fn_ptr_val.as.reg);
1876
        } else {
1877
            emit_load_into(g, S2, fn_ptr_val);
1878
        }
1879
        emit(g, JALR(RA, S2, 0));
1880
    } else if (sym->e.fn.attribs & ATTRIB_EXTERN) {
1881
        /* External function. */
1882
    } else if (sym->e.fn.addr) {
1883
        /* Direct call, address is already known. */
1884
        emit_call(g, sym->e.fn.addr);
1885
    } else {
1886
        if (g->nfnpatches >= MAX_FN_PATCHES)
1887
            bail("too many function call patches");
1888
1889
        /* Indirect call with patch later, address is not yet known. */
1890
1891
        reg_t scratch = nextreg(g);
1892
        usize pc      = emit(g, NOP);
1893
        usize tramp   = emit(g, NOP);
1894
1895
        g->fnpatches[g->nfnpatches++] = (fnpatch_t){
1896
            .fn_name     = sym->qualified,
1897
            .pc          = pc,
1898
            .tramp_pc    = tramp,
1899
            .patch_type  = PATCH_CALL,
1900
            .target_reg  = 0,
1901
            .scratch_reg = scratch,
1902
        };
1903
        freereg(g, scratch);
1904
    }
1905
    /* If the return register (A0) was in use before the function call, move the
1906
     * return value to a fresh register so restored caller values do not wipe it
1907
     * out. */
1908
    bool is_reg_return =
1909
        (return_type->cls != TYPE_VOID) && !type_is_passed_by_ref(return_type);
1910
    bool is_return_reg_saved = saved_regs[return_reg] != 0;
1911
1912
    if (is_reg_return && is_return_reg_saved) {
1913
        return_stack_off   = emit_regpush(g, return_reg, return_type);
1914
        return_is_on_stack = true;
1915
    }
1916
1917
    /* Restore all saved registers. */
1918
    for (usize i = 0; i < RALLOC_NREGS; i++) {
1919
        reg_t dst    = ralloc_regs[i];
1920
        i32   offset = saved_regs[dst];
1921
1922
        if (!offset)
1923
            continue;
1924
1925
        static type_t dword = { .cls   = TYPE_PTR,
1926
                                .size  = WORD_SIZE,
1927
                                .align = WORD_SIZE };
1928
        emit_regload(g, dst, FP, offset, &dword);
1929
        usereg(g, dst);
1930
1931
        /* Undo the temporary rebinding so the parameter once again refers to
1932
         * its original register value now that the spill has been reloaded. */
1933
        if (saved_syms[dst]) {
1934
            saved_syms[dst]->e.var.val      = saved_vals[dst];
1935
            saved_syms[dst]->e.var.val.temp = false;
1936
        }
1937
    }
1938
1939
    /* Restore argument registers that weren't in use before the call. */
1940
    for (usize i = 0; i < n->val.call.args.len; i++) {
1941
        reg_t arg = arg0 + (reg_t)i;
1942
        if (!saved_regs[arg])
1943
            freereg(g, arg);
1944
    }
1945
1946
    /* For records, the return value is stored on the stack, and the return
1947
     * register holds the address. For everything else, it's in a register. */
1948
    if (return_type->cls == TYPE_VOID) {
1949
        /* For void functions, we don't return a value */
1950
        if (!is_return_reg_saved)
1951
            freereg(g, return_reg);
1952
        return (value_t){ .type = return_type, .loc = LOC_NONE };
1953
    } else if (type_is_passed_by_ref(return_type)) {
1954
        return value_stack(OFFSET(FP, return_off), return_type);
1955
    } else {
1956
        if (return_is_on_stack) {
1957
            if (!is_return_reg_saved)
1958
                freereg(g, return_reg);
1959
            return value_stack(OFFSET(FP, return_stack_off), return_type);
1960
        }
1961
        /* The return value is marked as temp, so the caller is responsible
1962
         * for freeing the register when done with the value. Mark the register
1963
         * as in use to prevent reallocation before the value is consumed. */
1964
        usereg(g, return_reg);
1965
        return value_reg(return_reg, return_type);
1966
    }
1967
}
1968
1969
/* Generate code to access a slice field (len or ptr) given the slice value. */
1970
static value_t gen_slice_field(
1971
    gen_t *g, value_t slice_val, node_t *field, type_t *result_type
1972
) {
1973
    if (memcmp(field->val.ident.name, LEN_FIELD, LEN_FIELD_LEN) == 0) {
1974
        reg_t len = emit_load_offset(g, slice_val, SLICE_FIELD_LEN_OFFSET);
1975
        /* Slice lengths are stored as full dwords but typed as u32.
1976
         * Zero-extend to clear any upper 32 bits so that 64-bit
1977
         * comparisons (SLTU etc.) produce correct results on RV64. */
1978
        if (WORD_SIZE == 8) {
1979
            emit(g, SLLI(len, len, 32));
1980
            emit(g, SRLI(len, len, 32));
1981
        }
1982
        return value_reg(len, result_type);
1983
    }
1984
    if (memcmp(field->val.ident.name, PTR_FIELD, PTR_FIELD_LEN) == 0) {
1985
        reg_t ptr = emit_load_offset(g, slice_val, SLICE_FIELD_PTR_OFFSET);
1986
        return value_reg(ptr, result_type);
1987
    }
1988
    bail("unknown slice field");
1989
}
1990
1991
static value_t gen_access_ref(gen_t *g, node_t *n) {
1992
    node_t *expr     = n->val.access.lval;
1993
    type_t *expr_typ = expr->type;
1994
1995
    type_t *target_type = deref_type(expr_typ);
1996
1997
    switch (target_type->cls) {
1998
    case TYPE_RECORD: {
1999
        value_t   ptr_val = gen_expr(g, expr, true);
2000
        symbol_t *field   = n->sym;
2001
        useval(g, ptr_val);
2002
2003
        /* For pointer access like ptr.field, we need to dereference first */
2004
        /* Create a temporary node for dereferencing */
2005
        node_t deref_node = {
2006
            .cls           = NODE_UNOP,
2007
            .type          = target_type,
2008
            .val.unop.op   = OP_DEREF,
2009
            .val.unop.expr = expr,
2010
        };
2011
        /* For pointer-to-record field access, keep the pointed-to record as an
2012
         * lvalue so the field setter sees the original storage address. */
2013
        value_t record_val = gen_deref(g, &deref_node, ptr_val, true);
2014
        freeval(g, ptr_val);
2015
2016
        return emit_record_field_get(record_val, field);
2017
    }
2018
    case TYPE_SLICE: {
2019
        node_t *field = n->val.access.rval;
2020
2021
        /* Dereference to get the slice value, then access the field */
2022
        value_t ptr_val = gen_expr(g, expr, true);
2023
        useval(g, ptr_val);
2024
2025
        node_t deref_node = {
2026
            .cls           = NODE_UNOP,
2027
            .type          = target_type,
2028
            .val.unop.op   = OP_DEREF,
2029
            .val.unop.expr = expr,
2030
        };
2031
        value_t slice_val = gen_deref(g, &deref_node, ptr_val, true);
2032
        freeval(g, ptr_val);
2033
2034
        return gen_slice_field(g, slice_val, field, n->type);
2035
    }
2036
    case TYPE_ARRAY: {
2037
        /* For pointer access like ptr[index], create a temporary array index
2038
         * node */
2039
        /* and let gen_array_index handle the pointer dereferencing */
2040
        node_t array_index_node = { .cls             = NODE_ARRAY_INDEX,
2041
                                    .type            = n->type,
2042
                                    .val.access.lval = expr,
2043
                                    .val.access.rval = n->val.access.rval };
2044
2045
        return gen_array_index(g, &array_index_node, true);
2046
    }
2047
    default:
2048
        bail(
2049
            "cannot access field of reference to %s",
2050
            type_names[target_type->cls]
2051
        );
2052
    }
2053
}
2054
2055
static value_t gen_access(gen_t *g, node_t *n, bool lval) {
2056
    node_t *expr     = n->val.access.lval;
2057
    type_t *expr_typ = expr->type;
2058
    node_t *field    = n->val.access.rval;
2059
2060
    /* Handle non-reference types. */
2061
    switch (expr_typ->cls) {
2062
    case TYPE_PTR:
2063
        return gen_access_ref(g, n);
2064
    case TYPE_RECORD: {
2065
        /* Struct value and type. */
2066
        value_t   sval  = gen_expr(g, expr, lval);
2067
        symbol_t *field = n->sym;
2068
2069
        return emit_record_field_get(sval, field);
2070
    }
2071
    case TYPE_SLICE: {
2072
        value_t slice_val = gen_expr(g, expr, lval);
2073
        return gen_slice_field(g, slice_val, field, n->type);
2074
    }
2075
    /* Fall through */
2076
    default:
2077
        abort();
2078
    }
2079
}
2080
2081
/* Generate code to obtain a function pointer for the given symbol */
2082
static value_t gen_fn_ptr(gen_t *g, symbol_t *sym, type_t *type) {
2083
    reg_t reg = nextreg(g);
2084
2085
    if (sym->e.fn.addr) {
2086
        /* Direct function address is known - use AUIPC+ADDI for PC-relative
2087
         * addressing since the program may be loaded at a non-zero base. */
2088
        emit_pc_rel_addr(g, reg, sym->e.fn.addr);
2089
        return value_reg(reg, type);
2090
    }
2091
2092
    /* Function address will be patched later - generate AUIPC+ADDI sequence */
2093
    usize pc = emit(g, NOP); /* Placeholder - will be patched with AUIPC */
2094
    emit(g, NOP); /* Second placeholder - will be patched with ADDI */
2095
2096
    if (g->nfnpatches >= MAX_FN_PATCHES)
2097
        bail("too many function address patches");
2098
2099
    g->fnpatches[g->nfnpatches++] = (fnpatch_t){
2100
        .fn_name     = sym->qualified,
2101
        .pc          = pc,
2102
        .tramp_pc    = pc + 1,
2103
        .patch_type  = PATCH_ADDRESS,
2104
        .target_reg  = reg,
2105
        .scratch_reg = ZERO,
2106
    };
2107
    return value_reg(reg, type);
2108
}
2109
2110
static value_t gen_scope(gen_t *g, node_t *n) {
2111
    symbol_t *sym = n->sym;
2112
2113
    /* Generate code based on the symbol type, not the lval */
2114
    switch (sym->kind) {
2115
    case SYM_VARIABLE:
2116
        break;
2117
    case SYM_CONSTANT:
2118
        if (sym->e.var.val.loc == LOC_NONE) {
2119
            gen_const(g, sym->node);
2120
        }
2121
        return sym->e.var.val;
2122
    case SYM_VARIANT:
2123
        if (n->type->cls == TYPE_UNION) {
2124
            if (type_is_union_with_payload(n->type)) {
2125
                return gen_union_store(g, n->type, sym, value_none());
2126
            }
2127
            return value_imm(
2128
                (imm_t){ .i = sym->node->val.union_variant.value }, n->type
2129
            );
2130
        } else {
2131
            bail("variant of type %s is invalid", type_names[n->type->cls]);
2132
        }
2133
        break;
2134
    case SYM_FUNCTION:
2135
        return gen_fn_ptr(g, sym, n->type);
2136
    default:
2137
        break;
2138
    }
2139
    bail(
2140
        "unhandled scope case for symbol kind %d, node kind %s",
2141
        sym->kind,
2142
        node_names[n->cls]
2143
    );
2144
}
2145
2146
static value_t gen_ref(gen_t *g, node_t *n) {
2147
    /* Slice literal */
2148
    if (n->val.ref.target->cls == NODE_ARRAY_LIT) {
2149
        value_t ary = gen_array_literal(g, n->val.ref.target);
2150
        return gen_array_slice(g, ary, NULL);
2151
    }
2152
2153
    /* Ask for an lvalue so we get back the actual storage location. */
2154
    value_t target_val = gen_expr(g, n->val.ref.target, true);
2155
2156
    /* If the value is in a register, we need its address.
2157
     * This requires the value to be moved to the stack first. */
2158
    if (target_val.loc == LOC_REG) {
2159
        target_val = emit_push(g, target_val);
2160
    }
2161
    if (target_val.loc == LOC_STACK) {
2162
        /* Turn the stack location into an address held in a register. */
2163
        reg_t addr = nextreg(g);
2164
        emit_addr_offset(
2165
            g, addr, target_val.as.off.base, target_val.as.off.offset
2166
        );
2167
2168
        return value_reg(addr, n->type);
2169
    }
2170
    if (target_val.loc == LOC_ADDR) {
2171
        reg_t addr = nextreg(g);
2172
        emit_li(g, addr, target_val.as.adr.base + target_val.as.adr.offset);
2173
        return value_reg(addr, n->type);
2174
    }
2175
    /* For immediates and other types, we can't take a reference. */
2176
    bail("cannot take a reference to the target expression");
2177
}
2178
2179
static value_t gen_deref(gen_t *g, node_t *n, value_t ref_val, bool lval) {
2180
    reg_t addr           = ZERO;
2181
    bool  addr_from_load = false;
2182
2183
    /* Resolve the pointer value into a register. */
2184
    if (ref_val.loc == LOC_REG) {
2185
        addr = ref_val.as.reg;
2186
    } else if (ref_val.loc == LOC_STACK || ref_val.loc == LOC_ADDR) {
2187
        addr           = emit_load(g, ref_val);
2188
        addr_from_load = true;
2189
    } else {
2190
        bail("cannot dereference expression at this location");
2191
    }
2192
    value_t location = value_stack(OFFSET(addr, 0), n->type);
2193
2194
    if (lval || type_is_passed_by_ref(n->type)) {
2195
        return location;
2196
    }
2197
    reg_t val_reg = emit_load(g, location);
2198
2199
    if (addr_from_load)
2200
        freereg(g, addr);
2201
2202
    return value_reg(val_reg, n->type);
2203
}
2204
2205
/* Generate an array literal.
2206
 *
2207
 * This function handles array literals like `[1, 2, 3]`. It allocates
2208
 * space for the array on the stack, evaluates each element, and initializes
2209
 * the array elements in memory. */
2210
static value_t gen_array_literal(gen_t *g, node_t *n) {
2211
    type_t *array_type = n->type;
2212
    type_t *elem_type  = array_type->info.ary.elem;
2213
    usize   length     = array_type->info.ary.length;
2214
2215
    /* Reserve stack space for the array in the current frame. */
2216
    int array_off = reserve(g, array_type);
2217
2218
    /* Evaluate and store each element of the array. */
2219
    node_t **elems = nodespan_ptrs(&g->mod->parser, n->val.array_lit.elems);
2220
    for (usize i = 0; i < length; i++) {
2221
        node_t  *elem     = elems[i];
2222
        frame_t *frame    = &g->fn.current->e.fn.frame;
2223
        i32      saved_sp = frame->sp;
2224
        value_t  elem_val = gen_expr(g, elem, false);
2225
2226
        /* Calculate the offset for this element in the array. */
2227
        i32 elem_off = array_off + (i32)(i * elem_type->size);
2228
2229
        /* Store the element value at the calculated offset. */
2230
        emit_store(g, elem_val, FP, elem_off);
2231
        freeval(g, elem_val);
2232
2233
        /* Only reclaim stack space if the element type doesn't contain
2234
         * pointers. Slices and pointers may reference stack-allocated
2235
         * temporaries that must remain live. */
2236
        if (!type_is_address(elem_type->cls)) {
2237
            frame->sp = saved_sp;
2238
        }
2239
    }
2240
    /* The initialized array is on the stack at the computed offset. */
2241
    return value_stack(OFFSET(FP, array_off), array_type);
2242
}
2243
2244
/* Generate code for an array repeat literal (e.g. [0; 24]). */
2245
static value_t gen_array_repeat(gen_t *g, node_t *n) {
2246
    type_t *array_type = n->type;
2247
    type_t *elem_type  = array_type->info.ary.elem;
2248
    usize   length     = array_type->info.ary.length;
2249
    usize   array_off  = reserve(g, array_type);
2250
    value_t elem_val   = gen_expr(g, n->val.array_repeat_lit.value, false);
2251
2252
    /* Store the same value at each array position */
2253
    for (usize i = 0; i < length; i++) {
2254
        i32 elem_off = array_off + (i32)(i * elem_type->size);
2255
        emit_store(g, elem_val, FP, elem_off);
2256
    }
2257
    if (elem_val.loc == LOC_REG)
2258
        freereg(g, elem_val.as.reg);
2259
2260
    return value_stack(OFFSET(FP, array_off), array_type);
2261
}
2262
2263
/* Generate code for a slice with a range expression. */
2264
static value_t gen_array_slice(gen_t *g, value_t array_val, node_t *range) {
2265
    static type_t dword_type = { .cls = TYPE_PTR };
2266
2267
    type_t *slice_type, *elem_type;
2268
    if (array_val.type->cls == TYPE_ARRAY) {
2269
        slice_type = array_val.type->slice;
2270
        elem_type  = slice_type->info.slc.elem;
2271
    } else { /* TYPE_SLICE */
2272
        slice_type = array_val.type;
2273
        elem_type  = array_val.type->info.slc.elem;
2274
    }
2275
2276
    /* Reserve stack space for the slice (pointer + length) */
2277
    i32     slice_off   = reserve(g, slice_type);
2278
    value_t slice_val   = value_stack(OFFSET(FP, slice_off), slice_type);
2279
    reg_t   slice_start = ZERO; /* Start index */
2280
2281
    /* 1. Store array pointer at slice offset `0`.
2282
     * 2. Update slice offset `0` with slice start range.
2283
     * 3. Compute slice length, based on range.
2284
     * 4. Store slice length at slice offset `4`. */
2285
2286
    /* Emit slice address information */
2287
    if (range && range->val.range.start) {
2288
        /* Generate start expression and bounds check */
2289
        reg_t   r         = nextreg(g);
2290
        value_t start_val = gen_expr(g, range->val.range.start, false);
2291
        reg_t   start_reg = emit_load(g, start_val);
2292
        reg_t   slice_adr = ZERO;
2293
2294
        if (array_val.type->cls == TYPE_ARRAY) {
2295
            slice_adr = emit_load(g, array_val);
2296
        } else {
2297
            /* Load data pointer from slice (first word) */
2298
            slice_adr = emit_load_dword(g, array_val);
2299
        }
2300
        offset_t slice_off = slice_val.as.off;
2301
2302
        emit_li(g, r, elem_type->size);
2303
        emit(g, MUL(r, r, start_reg)); /* Offset from array address */
2304
        emit(g, ADD(r, r, slice_adr)); /* Full address */
2305
        emit_regstore(
2306
            g, r, slice_off.base, slice_off.offset, &dword_type
2307
        ); /* Save */
2308
2309
        slice_start = start_reg;
2310
2311
        /* Don't free start_reg yet - still needed as slice_start */
2312
        if (array_val.type->cls == TYPE_SLICE) {
2313
            freereg(g, slice_adr);
2314
        }
2315
        freereg(g, r);
2316
    } else {
2317
        if (array_val.type->cls == TYPE_ARRAY) {
2318
            /* For arrays, copy the array address */
2319
            emit_copy_by_ref(g, array_val, slice_val);
2320
        } else { /* TYPE_SLICE */
2321
            /* For slices, copy the slice fat pointer */
2322
            emit_memcopy(g, array_val.as.off, slice_val.as.off, array_val.type);
2323
        }
2324
    }
2325
2326
    /* Emit slice length information */
2327
    if (range && range->val.range.end) {
2328
        /* Generate end value */
2329
        value_t end_val = gen_expr(g, range->val.range.end, false);
2330
        reg_t   end_reg = emit_load(g, end_val);
2331
2332
        offset_t slice_off = slice_val.as.off;
2333
        if (slice_start != ZERO) {
2334
            /* Use SUBW on RV64 so the result is properly sign-extended
2335
             * to 64 bits, keeping the upper 32 bits clean. */
2336
            emit(g, SUBW(end_reg, end_reg, slice_start));
2337
        }
2338
        emit_regstore(
2339
            g,
2340
            end_reg,
2341
            slice_off.base,
2342
            slice_off.offset + WORD_SIZE,
2343
            &dword_type
2344
        );
2345
2346
        freereg(g, end_reg);
2347
    } else {
2348
        reg_t r = nextreg(g);
2349
        if (array_val.type->cls == TYPE_ARRAY) {
2350
            emit_li(g, r, array_val.type->info.ary.length);
2351
        } else { /* Slice */
2352
            /* Load length from slice (second word) */
2353
            r = emit_load_offset(g, array_val, SLICE_FIELD_LEN_OFFSET);
2354
        }
2355
        /* Slice length = array length - slice start */
2356
        offset_t slice_off = slice_val.as.off;
2357
        if (slice_start != ZERO) {
2358
            /* Use SUBW on RV64 so the result is properly sign-extended
2359
             * to 64 bits, keeping the upper 32 bits clean. */
2360
            emit(g, SUBW(r, r, slice_start));
2361
        }
2362
        emit_regstore(
2363
            g, r, slice_off.base, slice_off.offset + WORD_SIZE, &dword_type
2364
        );
2365
2366
        freereg(g, r);
2367
    }
2368
    freereg(g, slice_start);
2369
2370
    return slice_val;
2371
}
2372
2373
/* Generate array indexing.
2374
 *
2375
 * This function handles array indexing operations like `arr[i]` or `slice[i]`,
2376
 * as well as slicing operations using ranges like `arr[..]` or `arr[0..5]`. */
2377
static value_t gen_array_index(gen_t *g, node_t *n, bool lval) {
2378
    /* Generate code for the array/slice expression. */
2379
    value_t array_val  = gen_expr(g, n->val.access.lval, lval);
2380
    type_t *array_type = array_val.type;
2381
2382
    if (array_type->cls == TYPE_PTR) {
2383
        array_type = deref_type(array_type);
2384
    }
2385
2386
    /* Check if this is a range expression (for slicing) */
2387
    node_t *idx_node = n->val.access.rval;
2388
    if (idx_node->cls == NODE_RANGE) {
2389
        return gen_array_slice(g, array_val, idx_node);
2390
    } else {
2391
        return emit_array_index(
2392
            g, array_val, gen_expr(g, idx_node, false), lval
2393
        );
2394
    }
2395
}
2396
2397
static value_t gen_unop(gen_t *g, node_t *n, bool lval) {
2398
    value_t expr_val = gen_expr(g, n->val.unop.expr, lval);
2399
2400
    switch (n->val.unop.op) {
2401
    case OP_NOT: {
2402
        /* Logical NOT; invert the boolean value. */
2403
        reg_t expr_reg = emit_load(g, expr_val);
2404
        emit(g, NOT(expr_reg, expr_reg));
2405
        return value_reg(expr_reg, expr_val.type);
2406
    }
2407
    case OP_NEG: {
2408
        /* Numerical negation. */
2409
        reg_t expr_reg = emit_load(g, expr_val);
2410
        emit(g, NEG(expr_reg, expr_reg));
2411
        return value_reg(expr_reg, expr_val.type);
2412
    }
2413
    case OP_BNOT: {
2414
        /* Bitwise NOT; invert all bits. */
2415
        reg_t expr_reg = emit_load(g, expr_val);
2416
        emit(g, XORI(expr_reg, expr_reg, -1));
2417
        return value_reg(expr_reg, expr_val.type);
2418
    }
2419
    case OP_DEREF:
2420
        return gen_deref(g, n, expr_val, lval);
2421
    default:
2422
        abort();
2423
    }
2424
}
2425
2426
static value_t gen_string(gen_t *g, node_t *n) {
2427
    /* Add the string to the data section and get its offset */
2428
    usize str_len = n->val.string_lit.length;
2429
    usize str_off = data_string(&g->data, n->val.string_lit.data, str_len);
2430
2431
    /* Create a stack space for the string slice */
2432
    i32 slice_off = reserve(g, n->type);
2433
2434
    return emit_slice_lit(g, slice_off, str_off, str_len, n->type);
2435
}
2436
2437
static value_t gen_expr(gen_t *g, node_t *n, bool lvalue) {
2438
    assert(n->type);
2439
2440
    value_t val = (value_t){ .type = n->type };
2441
2442
    switch (n->cls) {
2443
    case NODE_UNOP:
2444
        return gen_unop(g, n, lvalue);
2445
    case NODE_BINOP:
2446
        return gen_binop(g, n);
2447
    case NODE_BOOL:
2448
        if (n->type->cls == TYPE_OPT) {
2449
            value_t inner_val = (value_t){
2450
                .type     = n->type->info.opt.elem,
2451
                .loc      = LOC_IMM,
2452
                .as.imm.b = n->val.bool_lit,
2453
            };
2454
            return optval_from_prim(g, n->type, inner_val);
2455
        } else {
2456
            val.loc      = LOC_IMM;
2457
            val.as.imm.b = n->val.bool_lit;
2458
        }
2459
        break;
2460
    case NODE_STRING:
2461
        return gen_string(g, n);
2462
    case NODE_CHAR:
2463
        if (n->type->cls == TYPE_OPT) {
2464
            value_t inner_val = (value_t){
2465
                .type     = n->type->info.opt.elem,
2466
                .loc      = LOC_IMM,
2467
                .as.imm.u = (u8)n->val.char_lit,
2468
            };
2469
            return optval_from_prim(g, n->type, inner_val);
2470
        } else {
2471
            val.loc      = LOC_IMM;
2472
            val.as.imm.u = (u8)n->val.char_lit;
2473
        }
2474
        break;
2475
    case NODE_NUMBER:
2476
        val.loc = LOC_IMM;
2477
2478
        switch (n->type->cls) {
2479
        case TYPE_I8:
2480
        case TYPE_I16:
2481
        case TYPE_I32:
2482
            val.as.imm.i = n->val.number.value.i;
2483
            break;
2484
        case TYPE_U8:
2485
        case TYPE_U16:
2486
        case TYPE_U32:
2487
            val.as.imm.u = n->val.number.value.u;
2488
            break;
2489
        case TYPE_OPT: {
2490
            /* Number coerced to optional - create some(number) on stack */
2491
            type_t *elem_type = n->type->info.opt.elem;
2492
            value_t inner_val = (value_t){ .type = elem_type, .loc = LOC_IMM };
2493
2494
            switch (elem_type->cls) {
2495
            case TYPE_I8:
2496
            case TYPE_I16:
2497
            case TYPE_I32:
2498
                inner_val.as.imm.i = n->val.number.value.i;
2499
                break;
2500
            case TYPE_U8:
2501
            case TYPE_U16:
2502
            case TYPE_U32:
2503
                inner_val.as.imm.u = n->val.number.value.u;
2504
                break;
2505
            default:
2506
                break;
2507
            }
2508
            return optval_from_prim(g, n->type, inner_val);
2509
        }
2510
        default:
2511
            break;
2512
        }
2513
        break;
2514
    case NODE_ACCESS:
2515
        return gen_access(g, n, lvalue);
2516
    case NODE_SCOPE:
2517
        return gen_scope(g, n);
2518
    case NODE_TRY:
2519
        return gen_try(g, n);
2520
    case NODE_IDENT:
2521
2522
        if (n->sym->kind == SYM_FUNCTION) {
2523
            /* Function identifier used as a value (function pointer) */
2524
            return gen_fn_ptr(g, n->sym, n->type);
2525
        }
2526
2527
        /* For types that are passed by reference and held in registers
2528
         * (function parameters), dereference the pointer to get the data */
2529
        if ((type_is_passed_by_ref(n->type)) &&
2530
            n->sym->e.var.val.loc == LOC_REG) {
2531
            return value_stack(OFFSET(n->sym->e.var.val.as.reg, 0), n->type);
2532
        }
2533
        return n->sym->e.var.val;
2534
    case NODE_CALL: {
2535
        /* Check if this is a tuple record constructor call */
2536
        if (!n->sym && n->type && n->type->cls == TYPE_RECORD &&
2537
            n->type->info.srt.tuple) {
2538
            return gen_record_lit(g, n);
2539
        }
2540
        assert(n->sym);
2541
        /* Check if this is a union constructor call */
2542
        if (n->sym->kind == SYM_VARIANT &&
2543
            type_is_union_with_payload(n->type)) {
2544
            return gen_union_constructor(g, n);
2545
        }
2546
        /* Function pointer call */
2547
        if (n->sym->kind == SYM_VARIABLE) {
2548
            return gen_call(g, n);
2549
        }
2550
        /* Regular function call */
2551
2552
        if (n->sym->e.fn.attribs & ATTRIB_EXTERN) {
2553
            /* Check if it's a built-in function. */
2554
            for (usize i = 0; BUILTINS[i].name; i++) {
2555
                if (strcmp(n->sym->qualified, BUILTINS[i].name) == 0) {
2556
                    return gen_call_intrinsic(g, n, BUILTINS[i].gen);
2557
                }
2558
            }
2559
        }
2560
        return gen_call(g, n);
2561
    }
2562
    case NODE_CALL_ARG:
2563
        /* Unreachable. This is handled inside `NODE_CALL`. */
2564
    case NODE_RECORD_LIT:
2565
        if (type_is_union_with_payload(n->type)) {
2566
            type_t *payload_type = n->sym->node->type;
2567
2568
            node_t payload_lit = *n;
2569
            payload_lit.type   = payload_type;
2570
            payload_lit.sym    = NULL;
2571
2572
            value_t payload = gen_record_lit(g, &payload_lit);
2573
2574
            return gen_union_store(g, n->type, n->sym, payload);
2575
        }
2576
        return gen_record_lit(g, n);
2577
    case NODE_ARRAY_LIT:
2578
        return gen_array_literal(g, n);
2579
    case NODE_ARRAY_REPEAT_LIT:
2580
        return gen_array_repeat(g, n);
2581
    case NODE_ARRAY_INDEX:
2582
        return gen_array_index(g, n, lvalue);
2583
    case NODE_REF:
2584
        return gen_ref(g, n);
2585
    case NODE_NIL: {
2586
        /* Allocate space for the optional value and initialize as nil */
2587
        i32 off = reserve(g, n->type);
2588
        val     = value_stack(OFFSET(FP, off), n->type);
2589
        tval_store(g, val, (value_t){ 0 }, 0);
2590
2591
        return val;
2592
    }
2593
    case NODE_UNDEF: {
2594
        i32 off = reserve(g, n->type);
2595
        val     = value_stack(OFFSET(FP, off), n->type);
2596
2597
        return val;
2598
    }
2599
    case NODE_AS:
2600
        return gen_as_cast(g, n);
2601
    case NODE_IF:
2602
        if (n->type->cls != TYPE_VOID) {
2603
            return gen_if_expr(g, n);
2604
        } else {
2605
            gen_if(g, n);
2606
            return value_none();
2607
        }
2608
    case NODE_BUILTIN: {
2609
        builtin_kind_t kind = n->val.builtin.kind;
2610
        node_t **args = nodespan_ptrs(&g->mod->parser, n->val.builtin.args);
2611
2612
        switch (kind) {
2613
        case BUILTIN_SLICE_OF: {
2614
            /* @sliceOf(ptr, len) - construct a slice from a pointer and length.
2615
             * Slices are fat pointers: 4 bytes for ptr, 4 bytes for len. */
2616
            node_t *ptr_expr = args[0];
2617
            node_t *len_expr = args[1];
2618
2619
            /* Generate code for pointer and length expressions */
2620
            value_t ptr_val = gen_expr(g, ptr_expr, false);
2621
            value_t len_val = gen_expr(g, len_expr, false);
2622
2623
            /* Reserve stack space for the slice */
2624
            i32 off = reserve(g, n->type);
2625
            val     = value_stack(OFFSET(FP, off), n->type);
2626
2627
            /* Store pointer at offset+0, length at offset+WORD_SIZE */
2628
            emit_store(g, ptr_val, FP, off + SLICE_FIELD_PTR_OFFSET);
2629
            /* Force len to be stored as a dword (WORD_SIZE bytes) */
2630
            static type_t dword = { .cls = TYPE_PTR };
2631
            len_val.type        = &dword;
2632
            emit_store(g, len_val, FP, off + SLICE_FIELD_LEN_OFFSET);
2633
2634
            return val;
2635
        }
2636
        case BUILTIN_SIZE_OF:
2637
        case BUILTIN_ALIGN_OF:
2638
            /* These are compile-time constants and should have been
2639
             * folded during type checking. */
2640
            bail("@sizeOf/@alignOf should be folded at compile time");
2641
        }
2642
        break;
2643
    }
2644
    default:
2645
        bail("unsupported expression node type %s", node_names[n->cls]);
2646
    }
2647
    return val;
2648
}
2649
2650
static void gen_fn_param(gen_t *g, node_t *param, usize ix) {
2651
    node_t *fn = g->fn.current->node;
2652
2653
    type_t *ret  = fn->type->info.fun.ret;
2654
    bool    sret = type_is_passed_by_ref(ret);
2655
    reg_t   base = sret ? A1 : A0;
2656
    reg_t   a    = base + (reg_t)ix;
2657
2658
    /* We're going to simply track the register in which our parameter is
2659
     * held, and mark it as in use. */
2660
    param->sym->e.var.val      = value_reg(a, param->type);
2661
    param->sym->e.var.val.temp = false;
2662
    usereg(g, a);
2663
2664
    /* If the type was passed by reference, we need to copy it to avoid
2665
     * modifying the original copy. */
2666
    if (type_is_passed_by_ref(param->type)) {
2667
        param->sym->e.var.val = emit_push(g, param->sym->e.var.val);
2668
        freereg(g, a);
2669
    }
2670
    /* Nb. If code takes the address of a parameter (`&param`), that parameter
2671
     * typically must be spilled to memory since registers don't have
2672
     * addresses. */
2673
}
2674
2675
/* Detect literal initializers that reside in a dedicated temporary and
2676
 * therefore can be bound directly without creating a defensive copy. */
2677
static bool is_unaliased(node_t *init) {
2678
    switch (init->cls) {
2679
    case NODE_ARRAY_LIT:
2680
    case NODE_ARRAY_REPEAT_LIT:
2681
    case NODE_RECORD_LIT:
2682
    case NODE_STRING:
2683
    case NODE_NIL:
2684
    case NODE_CALL:
2685
        return true;
2686
    default:
2687
        /* Nb. all immediates return `false`, because they do not occupy a
2688
         * stack location and therefore are not considered aliasable. */
2689
        return false;
2690
    }
2691
}
2692
2693
static void gen_var(gen_t *g, node_t *n) {
2694
    node_t *lval = n->val.var.ident;
2695
    node_t *rval = n->val.var.value;
2696
2697
    /* For placeholders, just evaluate the rvalue for side effects */
2698
    if (lval->cls == NODE_PLACEHOLDER) {
2699
        if (rval->cls != NODE_UNDEF) {
2700
            gen_expr(g, rval, false);
2701
        }
2702
        return;
2703
    }
2704
2705
    i32 align = n->sym->e.var.align;
2706
2707
    if (rval->cls == NODE_UNDEF) {
2708
        i32 offset        = reserve_aligned(g, n->type, align);
2709
        n->sym->e.var.val = value_stack(OFFSET(FP, offset), n->type);
2710
        return;
2711
    }
2712
2713
    value_t val = gen_expr(g, rval, false);
2714
    bool    reuse =
2715
        align <= n->type->align && val.loc == LOC_STACK && is_unaliased(rval);
2716
2717
    if (reuse) {
2718
        n->sym->e.var.val = val;
2719
        return;
2720
    }
2721
    i32     offset    = reserve_aligned(g, n->type, align);
2722
    value_t dest      = value_stack(OFFSET(FP, offset), n->type);
2723
    n->sym->e.var.val = dest;
2724
2725
    emit_replace(g, dest, val);
2726
}
2727
2728
static void gen_const(gen_t *g, node_t *n) {
2729
    /* Don't re-generate if it already has a location. */
2730
    if (n->sym->e.var.val.loc != LOC_NONE)
2731
        return;
2732
2733
    node_t     *value    = n->val.constant.value;
2734
    const char *name     = n->sym->qualified;
2735
    usize       name_len = strlen(name);
2736
    usize addr = data_node(&g->data, &g->mod->parser, value, name, name_len);
2737
2738
    /* Store the constant address in the symbol table */
2739
    n->sym->e.var.val   = value_addr(addr, 0, n->type);
2740
    n->sym->e.var.align = n->type->align;
2741
}
2742
2743
static void gen_static(gen_t *g, node_t *n) {
2744
    /* Don't re-generate if it already has a location. */
2745
    if (n->sym->e.var.val.loc != LOC_NONE)
2746
        return;
2747
2748
    node_t     *value    = n->val.static_decl.value;
2749
    const char *name     = n->sym->qualified;
2750
    usize       name_len = strlen(n->sym->qualified);
2751
    usize addr = data_node(&g->data, &g->mod->parser, value, name, name_len);
2752
2753
    n->sym->e.var.val   = value_addr(addr, 0, n->type);
2754
    n->sym->e.var.align = n->type->align;
2755
}
2756
2757
/* Generate code for a block of code. */
2758
static void gen_block(gen_t *g, node_t *n) {
2759
    frame_t *frame = &g->fn.current->e.fn.frame;
2760
2761
    /* Record the stack pointer before entering the block
2762
     * to restore it when exiting. */
2763
    i32 sp = frame->sp;
2764
2765
    /* Generate code for each statement in the block. */
2766
    node_t **stmts = nodespan_ptrs(&g->mod->parser, n->val.block.stmts);
2767
    for (usize i = 0; i < n->val.block.stmts.len; i++) {
2768
        gen_node(g, stmts[i]);
2769
    }
2770
    if (-frame->sp > frame->size) {
2771
        /* Keep track of the maximum stack space used. */
2772
        frame->size = -frame->sp;
2773
    }
2774
    /* De-allocate stack space. */
2775
    frame->sp = sp;
2776
}
2777
2778
/* Generate code for a function. */
2779
static void gen_fn(gen_t *g, node_t *n) {
2780
    /* Skip unused functions (dead code elimination) */
2781
    if (!n->sym->e.fn.used) {
2782
        return;
2783
    }
2784
2785
    /* Check if this is an extern function */
2786
    if (n->sym->e.fn.attribs & ATTRIB_EXTERN) {
2787
        /* For extern functions, we don't generate any code since they are
2788
         * implemented externally or are built-ins. */
2789
        return;
2790
    }
2791
    /* Check if it's a test function, and skip if not in test mode. */
2792
    if (n->sym->e.fn.attribs & ATTRIB_TEST && !(g->flags & FLAG_TEST)) {
2793
        return;
2794
    }
2795
2796
    type_t *ret  = n->type->info.fun.ret;
2797
    bool    sret = type_is_passed_by_ref(ret);
2798
2799
    /* For types that are returned by reference, keep hidden return pointer
2800
     * alive */
2801
    if (sret) {
2802
        usereg(g, A0);
2803
    }
2804
2805
    /* Set current function. */
2806
    g->fn.current     = n->sym;
2807
    g->fn.nretpatches = 0;
2808
2809
    symbol_t *sym = n->sym;
2810
2811
    /* Store the current instruction address as the function's address. */
2812
    sym->e.fn.addr = g->ninstrs;
2813
    node_t *body   = n->val.fn_decl.body;
2814
2815
    /* Functions should have non-zero address, unless it's the default */
2816
2817
    frame_t *f = &sym->e.fn.frame;
2818
2819
    /* Offsets for RA and previous FP. */
2820
    const i32 fp_off = -WORD_SIZE - WORD_SIZE;
2821
2822
    f->sp   = fp_off;
2823
    f->size = 0; /* Will be patched once we know the frame size. */
2824
2825
    /* Function prologue. Track prologue address for patching. */
2826
    usize prologue = sym->e.fn.addr;
2827
2828
    /* Generate placeholder instructions that will be patched at the end. */
2829
    /* This is the maximum prologue size, if we need to create a big
2830
     * stack frame. */
2831
    emit(g, NOP);
2832
    emit(g, NOP);
2833
    emit(g, NOP);
2834
    emit(g, NOP);
2835
    emit(g, NOP);
2836
    emit(g, NOP);
2837
    emit(g, NOP);
2838
2839
    /* Reserve all argument registers up-front so they are not used as
2840
     * temporaries while we spill each parameter. */
2841
    reg_t param_base = sret ? A1 : A0;
2842
2843
    for (usize i = 0; i < n->val.fn_decl.params.len; i++) {
2844
        reg_t a = param_base + (reg_t)i;
2845
2846
        if (a > A7) {
2847
            bail(
2848
                "function '%s' parameter %zu exceeds available register "
2849
                "arguments",
2850
                g->fn.current->qualified,
2851
                i + 1
2852
            );
2853
        }
2854
        usereg(g, a);
2855
    }
2856
2857
    /*
2858
     * Save parameters on the stack.
2859
     */
2860
2861
    for (usize i = 0; i < n->val.fn_decl.params.len; i++) {
2862
        gen_fn_param(g, SPAN(g, n->val.fn_decl.params)[i], i);
2863
    }
2864
2865
    /*
2866
     * Generate body.
2867
     */
2868
    gen_block(g, body);
2869
2870
    /* Ensure fallible functions that reach the end
2871
     * implicitly return success. */
2872
    if (ret->cls == TYPE_RESULT) {
2873
        if (!ret->info.res.payload->size) {
2874
            value_t dest;
2875
2876
            if (type_is_passed_by_ref(ret)) {
2877
                usereg(g, A0);
2878
                dest = value_stack(OFFSET(A0, 0), ret);
2879
            } else {
2880
                dest = value_reg(A0, ret);
2881
            }
2882
            emit_result_store_success(g, dest, value_none());
2883
2884
            if (!type_is_passed_by_ref(ret)) {
2885
                freereg(g, A0);
2886
            }
2887
        }
2888
    }
2889
    /* Align the frame size according to the RISCV ABI. */
2890
    f->size = align(f->size, STACK_ALIGNMENT);
2891
2892
    instr_t  *ins    = &g->instrs[prologue];
2893
    usize     slot   = 0;
2894
    const i32 locals = f->size - WORD_SIZE * 2;
2895
2896
    ins[slot++] = ADDI(SP, SP, -WORD_SIZE * 2);
2897
    ins[slot++] = SD(FP, SP, 0);
2898
    ins[slot++] = SD(RA, SP, WORD_SIZE);
2899
    ins[slot++] = ADDI(FP, SP, WORD_SIZE * 2);
2900
2901
    if (locals != 0) {
2902
        if (is_small(-locals)) {
2903
            ins[slot++] = ADDI(SP, SP, -locals);
2904
        } else {
2905
            i32 hi = 0, lo = 0;
2906
            split_imm(locals, &hi, &lo);
2907
2908
            ins[slot++] = LUI(T0, hi);
2909
2910
            if (lo != 0)
2911
                ins[slot++] = ADDI(T0, T0, lo);
2912
2913
            ins[slot++] = SUB(SP, SP, T0);
2914
        }
2915
    }
2916
    while (slot < 7)
2917
        ins[slot++] = NOP;
2918
2919
    /* Mark the epilogue position and patch all return statements
2920
     * to jump to this epilogue. */
2921
    usize epilogue = g->ninstrs;
2922
2923
    for (usize i = 0; i < g->fn.nretpatches; i++) {
2924
        ctpatch_t *p = &g->fn.retpatches[i];
2925
2926
        if (!p->applied) {
2927
            /* Calculate jump offset to the epilogue. */
2928
            i32 offset = jump_offset(p->pc, epilogue);
2929
2930
            /* A word-size offset basically means jumping to the next
2931
             * instruction, which is a redundant. We leave it as a NOP in
2932
             * that case. */
2933
            if (offset != INSTR_SIZE) {
2934
                /* Update the jump instruction with the correct offset. */
2935
                g->instrs[p->pc] = JMP(offset);
2936
            }
2937
            p->applied = true;
2938
        }
2939
    }
2940
    /*
2941
     * Function epilogue.
2942
     */
2943
    if (locals != 0) {
2944
        if (is_small(locals)) {
2945
            emit(g, ADDI(SP, SP, locals));
2946
        } else {
2947
            emit_li(g, T0, locals);
2948
            emit(g, ADD(SP, SP, T0));
2949
        }
2950
    }
2951
    emit(g, LD(FP, SP, 0));
2952
    emit(g, LD(RA, SP, WORD_SIZE));
2953
    emit(g, ADDI(SP, SP, WORD_SIZE * 2));
2954
    emit(g, RET);
2955
2956
    /* Release parameter and temporary registers */
2957
    for (reg_t r = A0; r <= A7; r++)
2958
        freereg(g, r);
2959
2960
    for (usize i = 0; i < sizeof(temp_registers) / sizeof(reg_t); i++)
2961
        freereg(g, temp_registers[i]);
2962
2963
    /* Patch function call locations. */
2964
    for (usize i = 0; i < g->nfnpatches; i++) {
2965
        fnpatch_t *p = &g->fnpatches[i];
2966
2967
        if (!p->applied && strcmp(p->fn_name, sym->qualified) == 0) {
2968
            if (p->patch_type == PATCH_CALL) {
2969
                i32 offset = jump_offset(p->pc, sym->e.fn.addr);
2970
2971
                if (is_jump_imm(offset)) {
2972
                    g->instrs[p->pc] = JAL(RA, offset);
2973
                    if (p->tramp_pc != (usize)-1)
2974
                        g->instrs[p->tramp_pc] = NOP;
2975
                } else {
2976
                    i32 target_addr  = (i32)(sym->e.fn.addr * INSTR_SIZE);
2977
                    i32 current_addr = (i32)(p->pc * INSTR_SIZE);
2978
                    i32 rel          = target_addr - current_addr;
2979
2980
                    i32 hi, lo;
2981
                    split_imm(rel, &hi, &lo);
2982
2983
                    reg_t scratch    = p->scratch_reg ? p->scratch_reg : T0;
2984
                    g->instrs[p->pc] = AUIPC(scratch, hi);
2985
                    g->instrs[p->tramp_pc] = JALR(RA, scratch, lo);
2986
                }
2987
            } else if (p->patch_type == PATCH_ADDRESS) {
2988
                /* For function address patches, replace the NOPs with AUIPC +
2989
                 * ADDI for PC-relative addressing. Calculate target -
2990
                 * current_pc. */
2991
                i32 target_addr  = sym->e.fn.addr * INSTR_SIZE;
2992
                i32 current_addr = p->pc * INSTR_SIZE;
2993
                i32 offset       = target_addr - current_addr;
2994
2995
                /* Split offset into upper 20 bits and lower 12 bits */
2996
                i32 hi, lo;
2997
                split_imm(offset, &hi, &lo);
2998
2999
                /* Emit AUIPC + ADDI sequence */
3000
                g->instrs[p->pc]       = AUIPC(p->target_reg, hi);
3001
                g->instrs[p->tramp_pc] = ADDI(p->target_reg, p->target_reg, lo);
3002
            }
3003
            /* Mark as applied so we don't patch it again. */
3004
            p->applied = true;
3005
        }
3006
    }
3007
}
3008
3009
/* Generate code for a module. */
3010
static void gen_module(gen_t *g, module_t *m) {
3011
    node_t *n = m->ast;
3012
3013
    if (m->compiled)
3014
        return;
3015
3016
    /* Set the current module for span access */
3017
    module_t *prev_mod = g->mod;
3018
    g->mod             = m;
3019
3020
    /* Don't compile test modules unless we are in test mode. */
3021
    if (m->attribs & ATTRIB_TEST && !(g->flags & FLAG_TEST)) {
3022
        g->mod = prev_mod;
3023
        return;
3024
    }
3025
    /* Generate all constants to ensure they're available */
3026
    node_t **stmts_const = nodespan_ptrs(&m->parser, n->val.block.stmts);
3027
    for (usize i = 0; i < n->val.block.stmts.len; i++) {
3028
        node_t *stmt = stmts_const[i];
3029
        if (stmt->cls == NODE_CONST) {
3030
            gen_const(g, stmt);
3031
        } else if (stmt->cls == NODE_STATIC) {
3032
            gen_static(g, stmt);
3033
        }
3034
    }
3035
    /* Generate code for module entry point. */
3036
    /* Must be at address _zero_ of the module. */
3037
    if (n->sym->e.mod->default_fn) {
3038
        gen_fn(g, n->sym->e.mod->default_fn->node);
3039
    }
3040
3041
    /* Generate all declared modules */
3042
    node_t **stmts_sub = nodespan_ptrs(&m->parser, n->val.block.stmts);
3043
    for (usize i = 0; i < n->val.block.stmts.len; i++) {
3044
        node_t *stmt = stmts_sub[i];
3045
        if (stmt->cls == NODE_MOD) {
3046
            gen_mod(g, stmt);
3047
        }
3048
        if (stmt->cls == NODE_USE) {
3049
            gen_use(g, stmt);
3050
        }
3051
    }
3052
    /* Generate code for everything else. */
3053
    node_t **stmts = nodespan_ptrs(&m->parser, n->val.block.stmts);
3054
    for (usize i = 0; i < n->val.block.stmts.len; i++) {
3055
        node_t *stmt = stmts[i];
3056
        if (stmt->cls == NODE_CONST)
3057
            continue;
3058
        if (stmt->cls == NODE_FN && stmt->sym->e.fn.attribs & ATTRIB_DEFAULT)
3059
            continue;
3060
        gen_node(g, stmt);
3061
    }
3062
    m->compiled = true;
3063
    g->mod      = prev_mod;
3064
}
3065
3066
/* Generate code for a module declaration. */
3067
static void gen_mod(gen_t *g, node_t *n) {
3068
    if (!n->sym) { /* Skip modules that aren't loaded like test modules. */
3069
        return;
3070
    }
3071
    module_t *mod = n->sym->e.mod;
3072
3073
    gen_module(g, mod);
3074
}
3075
3076
/* Generate code for a use declaration. */
3077
/* For function/variable imports, this generates the parent module. */
3078
static void gen_use(gen_t *g, node_t *n) {
3079
    /* For wildcard re-exports, n->sym is NULL since we're not binding
3080
     * the module itself, just re-exporting its symbols. */
3081
    if (!n->sym)
3082
        return;
3083
3084
    module_t *mod = n->sym->scope->mod;
3085
3086
    gen_module(g, mod);
3087
}
3088
3089
/* Generating nothing. This is used eg. for type declaration nodes
3090
 * which don't have any associated code. */
3091
static void gen_nop(gen_t *g, node_t *n) {
3092
    (void)g;
3093
    (void)n;
3094
}
3095
3096
/* Generate code from AST. */
3097
/* Pre-size the initialized data region by summing the aligned sizes of all
3098
 * initialized (non-BSS) constants and statics across every module. */
3099
static void data_presize(gen_t *g) {
3100
    usize total = 0;
3101
3102
    for (usize m = 0; m < g->mm->nmodules; m++) {
3103
        module_t *mod = &g->mm->modules[m];
3104
3105
        if (!mod->ast)
3106
            continue;
3107
        if (mod->attribs & ATTRIB_TEST && !(g->flags & FLAG_TEST))
3108
            continue;
3109
3110
        node_t  *n     = mod->ast;
3111
        node_t **stmts = nodespan_ptrs(&mod->parser, n->val.block.stmts);
3112
3113
        for (usize i = 0; i < n->val.block.stmts.len; i++) {
3114
            node_t *stmt  = stmts[i];
3115
            node_t *value = NULL;
3116
3117
            if (stmt->cls == NODE_CONST)
3118
                value = stmt->val.constant.value;
3119
            else if (stmt->cls == NODE_STATIC)
3120
                value = stmt->val.static_decl.value;
3121
            else
3122
                continue;
3123
3124
            if (value->cls == NODE_UNDEF)
3125
                continue;
3126
3127
            total  = align(total, WORD_SIZE);
3128
            total += stmt->type->size;
3129
        }
3130
    }
3131
    g->data.rw_init_total = align(total, WORD_SIZE);
3132
}
3133
3134
int gen_emit(gen_t *g, module_t *root) {
3135
    /* Pre-size the initialized data region so that BSS items are placed
3136
     * after all initialized data in the rw section. */
3137
    data_presize(g);
3138
3139
    /* Generate root module. This has to have address zero, as it has
3140
     * the entry point. */
3141
    gen_module(g, root);
3142
3143
    /* Generate `std` module if available. */
3144
    module_t *std = module_manager_lookup_by_qualified_name(g->mm, "std");
3145
    if (std) {
3146
        gen_module(g, std);
3147
    }
3148
    /* Check that all patches have been applied. */
3149
    for (usize i = 0; i < g->nfnpatches; i++) {
3150
        if (!g->fnpatches[i].applied)
3151
            bail(
3152
                "jump for function '%s' was not patched",
3153
                g->fnpatches[i].fn_name
3154
            );
3155
    }
3156
    /* Check that all return patches have been applied. */
3157
    for (usize i = 0; i < g->fn.nretpatches; i++) {
3158
        if (!g->fn.retpatches[i].applied)
3159
            bail("return statement was not properly patched");
3160
    }
3161
    /* Check that all break patches have been applied. */
3162
    for (usize i = 0; i < g->fn.nbrkpatches; i++) {
3163
        if (!g->fn.brkpatches[i].applied)
3164
            bail("break statement was not properly patched");
3165
    }
3166
    /* Keep root module reference for data emission. */
3167
    g->mod = root;
3168
3169
    return 0;
3170
}
3171
3172
static value_t gen_as_cast(gen_t *g, node_t *n) {
3173
    node_t *expr = n->val.as_expr.expr;
3174
    value_t val  = gen_expr(g, expr, false);
3175
3176
    /* If casting to the same type, no conversion needed */
3177
    if (val.type == n->type)
3178
        return val;
3179
3180
    /* For casts between different primitive types, we need to handle
3181
     * size changes properly (e.g., u8 -> i32 requires zero extension) */
3182
    /* If the types are the same size, just change the type metadata */
3183
    if (val.type->size == n->type->size) {
3184
        val.type = n->type;
3185
        return val;
3186
    }
3187
    /* For size changes, we need to properly load and re-store the value
3188
     * to ensure correct zero/sign extension */
3189
    if (val.loc == LOC_STACK) {
3190
        /* Load the value using the source type (proper sized load) */
3191
        reg_t rd = emit_load(g, val);
3192
        /* Push to stack using the target type (proper sized store) */
3193
        i32 offset = emit_regpush(g, rd, n->type);
3194
        freereg(g, rd);
3195
3196
        return value_stack(OFFSET(FP, offset), n->type);
3197
    }
3198
    /* For non-stack values (registers, immediates), just change the
3199
     * type */
3200
    val.type = n->type;
3201
3202
    return val;
3203
}
3204
3205
void gen_dump_bin(gen_t *g, FILE *text, FILE *data_ro, FILE *data_rw) {
3206
    /* Write instructions */
3207
    fwrite(g->instrs, sizeof(u32), g->ninstrs, text);
3208
    /* Write data */
3209
    data_emit_rw(&g->data, data_rw);
3210
    data_emit_ro(&g->data, data_ro);
3211
3212
    fflush(text);
3213
    fflush(data_ro);
3214
    fflush(data_rw);
3215
}
3216
3217
/* Initialize a `gen` object. */
3218
void gen_init(gen_t *g, types_t *t, module_manager_t *mm, u32 flags) {
3219
    g->ninstrs        = 0;
3220
    g->nfnpatches     = 0;
3221
    g->fn.current     = NULL;
3222
    g->fn.nretpatches = 0;
3223
    g->fn.nbrkpatches = 0;
3224
    g->regs           = ralloc();
3225
    g->types          = t;
3226
    g->loop.current   = NULL;
3227
    g->loop.end       = 0;
3228
    g->mm             = mm;
3229
    g->flags          = flags;
3230
3231
    /* Initialize data section */
3232
    data_init(&g->data);
3233
}