Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/regcompiler.c
Line
Count
Source
1
#include "regvm.h"
2
#include "regopcode.h"
3
#include "ast.h"
4
#include <stdlib.h>
5
#include <string.h>
6
#include <stdio.h>
7
8
/* ── Register compiler state ── */
9
10
typedef struct {
11
    char *name;
12
    int   depth;
13
    bool  is_captured;
14
    uint8_t reg;       /* Which register this local lives in */
15
} RegLocal;
16
17
typedef struct {
18
    uint8_t index;
19
    bool    is_local;
20
} RegCompilerUpvalue;
21
22
typedef enum { REG_FUNC_SCRIPT, REG_FUNC_FUNCTION, REG_FUNC_CLOSURE } RegFuncType;
23
24
typedef struct RegCompiler {
25
    struct RegCompiler *enclosing;
26
    RegChunk    *chunk;
27
    RegFuncType  type;
28
    char        *func_name;
29
    int          arity;
30
    RegLocal    *locals;
31
    size_t       local_count;
32
    size_t       local_cap;
33
    RegCompilerUpvalue *upvalues;
34
    size_t       upvalue_count;
35
    int          scope_depth;
36
    uint8_t      next_reg;        /* Next available register */
37
    uint8_t      max_reg;         /* High water mark for register usage */
38
    /* Break/continue tracking */
39
    size_t      *break_patches;   /* Instruction indices needing patching */
40
    size_t       break_count;
41
    size_t       break_cap;
42
    size_t      *continue_patches; /* For-loop continue: forward jump patches */
43
    size_t       continue_count;
44
    size_t       continue_cap;
45
    size_t       loop_start;
46
    int          loop_is_for;     /* 1 if innermost loop is for (continue uses forward jump) */
47
    int          loop_depth;
48
    size_t       loop_break_local_count;
49
    size_t       loop_continue_local_count;
50
    uint8_t      loop_break_reg;  /* Register to restore to on break */
51
    uint8_t      loop_continue_reg;
52
    /* Ensure contracts and return type for postcondition checking */
53
    ContractClause *ensures;
54
    size_t       ensure_count;
55
    char        *return_type;
56
} RegCompiler;
57
58
static RegCompiler *rc = NULL;  /* Current compiler */
59
static char *rc_error = NULL;
60
61
/* Track known enums (same as stack compiler) */
62
static char **rc_known_enums = NULL;
63
static size_t rc_known_enum_count = 0;
64
static size_t rc_known_enum_cap = 0;
65
66
9
static void rc_register_enum(const char *name) {
67
9
    if (rc_known_enum_count >= rc_known_enum_cap) {
68
2
        rc_known_enum_cap = rc_known_enum_cap ? rc_known_enum_cap * 2 : 8;
69
2
        rc_known_enums = realloc(rc_known_enums, rc_known_enum_cap * sizeof(char *));
70
2
    }
71
9
    rc_known_enums[rc_known_enum_count++] = strdup(name);
72
9
}
73
74
548
static bool rc_is_known_enum(const char *name) {
75
4.86k
    for (size_t i = 0; i < rc_known_enum_count; i++)
76
4.33k
        if (strcmp(rc_known_enums[i], name) == 0) return true;
77
534
    return false;
78
548
}
79
80
0
static void rc_free_known_enums(void) {
81
0
    for (size_t i = 0; i < rc_known_enum_count; i++)
82
0
        free(rc_known_enums[i]);
83
0
    free(rc_known_enums);
84
0
    rc_known_enums = NULL;
85
0
    rc_known_enum_count = 0;
86
0
    rc_known_enum_cap = 0;
87
0
}
88
89
/* ── Compiler init/cleanup ── */
90
91
4.24k
static void rc_init(RegCompiler *comp, RegCompiler *enclosing, RegFuncType type) {
92
4.24k
    comp->enclosing = enclosing;
93
4.24k
    comp->chunk = regchunk_new();
94
4.24k
    comp->type = type;
95
4.24k
    comp->func_name = NULL;
96
4.24k
    comp->arity = 0;
97
4.24k
    comp->local_count = 0;
98
4.24k
    comp->local_cap = 256;
99
4.24k
    comp->locals = malloc(comp->local_cap * sizeof(RegLocal));
100
4.24k
    comp->upvalues = NULL;
101
4.24k
    comp->upvalue_count = 0;
102
4.24k
    comp->scope_depth = (type == REG_FUNC_SCRIPT) ? 0 : 1;
103
4.24k
    comp->next_reg = 0;
104
4.24k
    comp->max_reg = 0;
105
4.24k
    comp->break_patches = NULL;
106
4.24k
    comp->break_count = 0;
107
4.24k
    comp->break_cap = 0;
108
4.24k
    comp->continue_patches = NULL;
109
4.24k
    comp->continue_count = 0;
110
4.24k
    comp->continue_cap = 0;
111
4.24k
    comp->loop_start = 0;
112
4.24k
    comp->loop_is_for = 0;
113
4.24k
    comp->loop_depth = 0;
114
4.24k
    comp->loop_break_local_count = 0;
115
4.24k
    comp->loop_continue_local_count = 0;
116
4.24k
    comp->loop_break_reg = 0;
117
4.24k
    comp->loop_continue_reg = 0;
118
4.24k
    comp->ensures = NULL;
119
4.24k
    comp->ensure_count = 0;
120
4.24k
    comp->return_type = NULL;
121
122
    /* Reserve register 0 for function itself (convention) */
123
4.24k
    if (type != REG_FUNC_SCRIPT) {
124
3.28k
        RegLocal *local = &comp->locals[comp->local_count++];
125
3.28k
        local->name = strdup("");
126
3.28k
        local->depth = 0;
127
3.28k
        local->is_captured = false;
128
3.28k
        local->reg = comp->next_reg++;
129
3.28k
    }
130
131
4.24k
    rc = comp;
132
4.24k
}
133
134
4.24k
static void rc_cleanup(RegCompiler *comp) {
135
14.1k
    for (size_t i = 0; i < comp->local_count; i++)
136
9.94k
        free(comp->locals[i].name);
137
4.24k
    free(comp->locals);
138
4.24k
    free(comp->upvalues);
139
4.24k
    free(comp->break_patches);
140
4.24k
    free(comp->continue_patches);
141
4.24k
    free(comp->func_name);
142
4.24k
}
143
144
/* ── Register management ── */
145
146
50.2k
static uint8_t alloc_reg(void) {
147
50.2k
    if (rc->next_reg >= REGVM_REG_MAX - 1) {
148
0
        rc_error = strdup("register overflow (>256 registers)");
149
0
        return 0;
150
0
    }
151
50.2k
    uint8_t r = rc->next_reg++;
152
50.2k
    if (r >= rc->max_reg) rc->max_reg = r + 1;
153
50.2k
    return r;
154
50.2k
}
155
156
22.1k
static void free_reg(uint8_t r) {
157
    /* Only free if it's the top-most register (simple stack-like alloc) */
158
22.1k
    if (r == rc->next_reg - 1)
159
22.0k
        rc->next_reg--;
160
22.1k
}
161
162
12.4k
static void free_regs_to(uint8_t target) {
163
12.4k
    if (rc->next_reg > target)
164
12.4k
        rc->next_reg = target;
165
12.4k
}
166
167
/* ── Emit helpers ── */
168
169
173k
static RegChunk *current_chunk(void) { return rc->chunk; }
170
171
111k
static size_t emit(RegInstr instr, int line) {
172
111k
    return regchunk_write(current_chunk(), instr, line);
173
111k
}
174
175
23.5k
static size_t emit_ABx(uint8_t op, uint8_t a, uint16_t bx, int line) {
176
23.5k
    return emit(REG_ENCODE_ABx(op, a, bx), line);
177
23.5k
}
178
179
58.7k
static size_t emit_ABC(uint8_t op, uint8_t a, uint8_t b, uint8_t c, int line) {
180
58.7k
    return emit(REG_ENCODE_ABC(op, a, b, c), line);
181
58.7k
}
182
183
6.35k
static size_t emit_AsBx(uint8_t op, uint8_t a, int16_t sbx, int line) {
184
6.35k
    return emit(REG_ENCODE_AsBx(op, a, sbx), line);
185
6.35k
}
186
187
/* Emit jump placeholder, return instruction index for patching */
188
3.09k
static size_t emit_jump_placeholder(uint8_t op, uint8_t a, int line) {
189
3.09k
    return emit_AsBx(op, a, 0, line);
190
3.09k
}
191
192
2.67k
static size_t emit_jmp_placeholder(int line) {
193
2.67k
    return emit(REG_ENCODE_sBx(ROP_JMP, 0), line);
194
2.67k
}
195
196
/* Patch a conditional jump (AsBx format) */
197
3.09k
static void patch_jump(size_t instr_idx) {
198
3.09k
    int16_t offset = (int16_t)(current_chunk()->code_len - instr_idx - 1);
199
3.09k
    RegInstr old = current_chunk()->code[instr_idx];
200
3.09k
    uint8_t op = REG_GET_OP(old);
201
3.09k
    uint8_t a = REG_GET_A(old);
202
3.09k
    current_chunk()->code[instr_idx] = REG_ENCODE_AsBx(op, a, offset);
203
3.09k
}
204
205
/* Patch an unconditional jump (sBx24 format) */
206
2.67k
static void patch_jmp(size_t instr_idx) {
207
2.67k
    int32_t offset = (int32_t)(current_chunk()->code_len - instr_idx - 1);
208
    /* Preserve original opcode (may be ROP_JMP, ROP_DEFER_PUSH, etc.) */
209
2.67k
    uint8_t op = REG_GET_OP(current_chunk()->code[instr_idx]);
210
2.67k
    current_chunk()->code[instr_idx] = REG_ENCODE_sBx(op, offset);
211
2.67k
}
212
213
/* Emit backward jump to loop_start */
214
558
static void emit_loop_back(size_t loop_start, int line) {
215
558
    int32_t offset = (int32_t)(loop_start) - (int32_t)(current_chunk()->code_len) - 1;
216
558
    emit(REG_ENCODE_sBx(ROP_JMP, offset), line);
217
558
}
218
219
/* Emit DEFER_RUN + RETURN (use for all returns except inside defer bodies) */
220
7.25k
static void emit_return(uint8_t reg, int line) {
221
7.25k
    emit(REG_ENCODE_ABC(ROP_DEFER_RUN, 0, 0, 0), line);
222
7.25k
    emit(REG_ENCODE_ABC(ROP_RETURN, reg, 1, 0), line);
223
7.25k
}
224
225
/* Forward declaration for ensure checks (defined after compile_expr) */
226
static void emit_postconditions(uint8_t reg, int line);
227
228
/* ── Constant pool ── */
229
230
34.7k
static uint16_t add_constant(LatValue val) {
231
34.7k
    size_t idx = regchunk_add_constant(current_chunk(), val);
232
34.7k
    if (idx >= REGVM_CONST_MAX) {
233
0
        rc_error = strdup("too many constants in one chunk (>65535)");
234
0
        return 0;
235
0
    }
236
34.7k
    return (uint16_t)idx;
237
34.7k
}
238
239
/* ── Scope and local management ── */
240
241
5.89k
static void begin_scope(void) { rc->scope_depth++; }
242
243
5.89k
static void end_scope(int line) {
244
    /* Run defers registered at this scope depth (block-scoped defers) */
245
5.89k
    if (rc->scope_depth > 1) {
246
5.89k
        emit_ABC(ROP_DEFER_RUN, (uint8_t)rc->scope_depth, 0, 0, line);
247
5.89k
    }
248
5.89k
    rc->scope_depth--;
249
8.13k
    while (rc->local_count > 0 &&
250
8.13k
           rc->locals[rc->local_count - 1].depth > rc->scope_depth) {
251
2.24k
        RegLocal *local = &rc->locals[rc->local_count - 1];
252
2.24k
        if (local->is_captured) {
253
174
            emit_ABC(ROP_CLOSEUPVALUE, local->reg, 0, 0, line);
254
174
        }
255
        /* Free the register */
256
2.24k
        free_reg(local->reg);
257
2.24k
        free(local->name);
258
2.24k
        rc->local_count--;
259
2.24k
    }
260
5.89k
}
261
262
8.34k
static uint8_t add_local(const char *name) {
263
8.34k
    if (rc->local_count >= rc->local_cap) {
264
0
        rc->local_cap *= 2;
265
0
        rc->locals = realloc(rc->locals, rc->local_cap * sizeof(RegLocal));
266
0
    }
267
8.34k
    uint8_t reg = alloc_reg();
268
8.34k
    RegLocal *local = &rc->locals[rc->local_count++];
269
8.34k
    local->name = strdup(name);
270
8.34k
    local->depth = rc->scope_depth;
271
8.34k
    local->is_captured = false;
272
8.34k
    local->reg = reg;
273
8.34k
    regchunk_set_local_name(current_chunk(), reg, name);
274
8.34k
    return reg;
275
8.34k
}
276
277
33.8k
static int resolve_local(RegCompiler *comp, const char *name) {
278
97.1k
    for (int i = (int)comp->local_count - 1; i >= 0; i--) {
279
85.9k
        if (strcmp(comp->locals[i].name, name) == 0)
280
22.5k
            return i;
281
85.9k
    }
282
11.2k
    return -1;
283
33.8k
}
284
285
18.4k
static uint8_t local_reg(int local_idx) {
286
18.4k
    return rc->locals[local_idx].reg;
287
18.4k
}
288
289
/* ── Upvalue resolution ── */
290
291
1.59k
static int add_upvalue(RegCompiler *comp, uint8_t index, bool is_local) {
292
2.64k
    for (size_t i = 0; i < comp->upvalue_count; i++) {
293
1.71k
        if (comp->upvalues[i].index == index && comp->upvalues[i].is_local == is_local)
294
667
            return (int)i;
295
1.71k
    }
296
930
    if (comp->upvalue_count >= 256) {
297
0
        rc_error = strdup("too many upvalues in one function");
298
0
        return -1;
299
0
    }
300
930
    comp->upvalues = realloc(comp->upvalues, (comp->upvalue_count + 1) * sizeof(RegCompilerUpvalue));
301
930
    comp->upvalues[comp->upvalue_count].index = index;
302
930
    comp->upvalues[comp->upvalue_count].is_local = is_local;
303
930
    return (int)comp->upvalue_count++;
304
930
}
305
306
11.1k
static int resolve_upvalue(RegCompiler *comp, const char *name) {
307
11.1k
    if (!comp->enclosing) return -1;
308
6.73k
    int local = resolve_local(comp->enclosing, name);
309
6.73k
    if (local != -1) {
310
1.48k
        comp->enclosing->locals[local].is_captured = true;
311
1.48k
        return add_upvalue(comp, comp->enclosing->locals[local].reg, true);
312
1.48k
    }
313
5.25k
    int upvalue = resolve_upvalue(comp->enclosing, name);
314
5.25k
    if (upvalue != -1)
315
116
        return add_upvalue(comp, (uint8_t)upvalue, false);
316
5.13k
    return -1;
317
5.25k
}
318
319
/* ── Break/continue helpers ── */
320
321
98
static void push_break_patch(size_t instr_idx) {
322
98
    if (rc->break_count >= rc->break_cap) {
323
98
        rc->break_cap = rc->break_cap ? rc->break_cap * 2 : 8;
324
98
        rc->break_patches = realloc(rc->break_patches, rc->break_cap * sizeof(size_t));
325
98
    }
326
98
    rc->break_patches[rc->break_count++] = instr_idx;
327
98
}
328
329
1
static void push_continue_patch(size_t instr_idx) {
330
1
    if (rc->continue_count >= rc->continue_cap) {
331
1
        rc->continue_cap = rc->continue_cap ? rc->continue_cap * 2 : 8;
332
1
        rc->continue_patches = realloc(rc->continue_patches, rc->continue_cap * sizeof(size_t));
333
1
    }
334
1
    rc->continue_patches[rc->continue_count++] = instr_idx;
335
1
}
336
337
/* ── Forward declarations ── */
338
339
static void compile_expr(const Expr *e, uint8_t dst, int line);
340
static void compile_stmt(const Stmt *s);
341
342
/* Compile statements into a standalone RegChunk (sub-body for scope/spawn/select).
343
 * The last expression statement becomes the return value; otherwise returns unit. */
344
14
static RegChunk *compile_reg_sub_body(Stmt **stmts, size_t count, int line) {
345
14
    RegCompiler *saved = rc;
346
14
    RegCompiler sub;
347
14
    rc_init(&sub, NULL, REG_FUNC_SCRIPT);
348
14
    sub.scope_depth = 1;  /* treat as local scope */
349
14
    rc = &sub;
350
    /* Slot 0 reserved (convention) */
351
14
    add_local("");
352
353
14
    if (count > 0 && stmts[count - 1]->tag == STMT_EXPR) {
354
15
        for (size_t i = 0; i + 1 < count; i++)
355
2
            compile_stmt(stmts[i]);
356
13
        uint8_t result_reg = alloc_reg();
357
13
        compile_expr(stmts[count - 1]->as.expr, result_reg, line);
358
13
        emit_ABC(ROP_RETURN, result_reg, 1, 0, line);  /* B=1: has return value */
359
13
        free_reg(result_reg);
360
13
    } else {
361
4
        for (size_t i = 0; i < count; i++)
362
3
            compile_stmt(stmts[i]);
363
1
        uint8_t result_reg = alloc_reg();
364
1
        emit_ABC(ROP_LOADUNIT, result_reg, 0, 0, line);
365
1
        emit_ABC(ROP_RETURN, result_reg, 1, 0, line);  /* B=1: has return value */
366
1
        free_reg(result_reg);
367
1
    }
368
369
14
    RegChunk *ch = sub.chunk;
370
14
    sub.chunk = NULL;  /* prevent rc_cleanup from freeing it */
371
14
    rc_cleanup(&sub);
372
14
    rc = saved;
373
14
    return ch;
374
14
}
375
376
/* Compile a single expression into a standalone RegChunk that returns its value. */
377
6
static RegChunk *compile_reg_sub_expr(const Expr *expr, int line) {
378
6
    RegCompiler *saved = rc;
379
6
    RegCompiler sub;
380
6
    rc_init(&sub, NULL, REG_FUNC_SCRIPT);
381
6
    sub.scope_depth = 1;
382
6
    rc = &sub;
383
6
    add_local("");
384
385
6
    uint8_t result_reg = alloc_reg();
386
6
    compile_expr(expr, result_reg, line);
387
6
    emit_ABC(ROP_RETURN, result_reg, 1, 0, line);  /* B=1: has return value */
388
6
    free_reg(result_reg);
389
390
6
    RegChunk *ch = sub.chunk;
391
6
    sub.chunk = NULL;
392
6
    rc_cleanup(&sub);
393
6
    rc = saved;
394
6
    return ch;
395
6
}
396
397
/* Store a sub-RegChunk as a VAL_CLOSURE constant (body=NULL, native_fn=RegChunk*). */
398
20
static uint16_t add_regchunk_constant(RegChunk *ch) {
399
20
    LatValue fn_val;
400
20
    memset(&fn_val, 0, sizeof(fn_val));
401
20
    fn_val.type = VAL_CLOSURE;
402
20
    fn_val.phase = VTAG_UNPHASED;
403
20
    fn_val.region_id = (size_t)-1;
404
20
    fn_val.as.closure.body = NULL;
405
20
    fn_val.as.closure.native_fn = (void *)ch;
406
20
    return add_constant(fn_val);
407
20
}
408
409
/* ── Expression compilation ──
410
 * Each expression compiles its result into register `dst`.
411
 */
412
413
45.9k
static void compile_expr(const Expr *e, uint8_t dst, int line) {
414
45.9k
    if (rc_error) return;
415
45.9k
    if (e->line > 0) line = e->line;
416
417
45.9k
    switch (e->tag) {
418
2.95k
    case EXPR_INT_LIT: {
419
2.95k
        int64_t v = e->as.int_val;
420
2.95k
        if (v >= -32768 && v <= 32767) {
421
2.95k
            emit_AsBx(ROP_LOADI, dst, (int16_t)v, line);
422
2.95k
        } else {
423
5
            uint16_t ki = add_constant(value_int(v));
424
5
            emit_ABx(ROP_LOADK, dst, ki, line);
425
5
        }
426
2.95k
        break;
427
0
    }
428
429
108
    case EXPR_FLOAT_LIT: {
430
108
        uint16_t ki = add_constant(value_float(e->as.float_val));
431
108
        emit_ABx(ROP_LOADK, dst, ki, line);
432
108
        break;
433
0
    }
434
435
5.93k
    case EXPR_STRING_LIT: {
436
5.93k
        uint16_t ki = add_constant(value_string(e->as.str_val));
437
5.93k
        emit_ABx(ROP_LOADK, dst, ki, line);
438
5.93k
        break;
439
0
    }
440
441
787
    case EXPR_BOOL_LIT:
442
787
        emit_ABC(e->as.bool_val ? ROP_LOADTRUE : ROP_LOADFALSE, dst, 0, 0, line);
443
787
        break;
444
445
339
    case EXPR_NIL_LIT:
446
339
        emit_ABC(ROP_LOADNIL, dst, 0, 0, line);
447
339
        break;
448
449
15.0k
    case EXPR_IDENT: {
450
15.0k
        int local = resolve_local(rc, e->as.str_val);
451
15.0k
        if (local >= 0) {
452
9.42k
            uint8_t src = local_reg(local);
453
9.42k
            if (src != dst)
454
8.98k
                emit_ABC(ROP_MOVE, dst, src, 0, line);
455
9.42k
        } else {
456
5.67k
            int upvalue = resolve_upvalue(rc, e->as.str_val);
457
5.67k
            if (upvalue >= 0) {
458
1.48k
                emit_ABC(ROP_GETUPVALUE, dst, (uint8_t)upvalue, 0, line);
459
4.18k
            } else {
460
4.18k
                uint16_t ki = add_constant(value_string(e->as.str_val));
461
4.18k
                emit_ABx(ROP_GETGLOBAL, dst, ki, line);
462
4.18k
            }
463
5.67k
        }
464
15.0k
        break;
465
0
    }
466
467
3.20k
    case EXPR_BINOP: {
468
        /* Short-circuit AND/OR */
469
3.20k
        if (e->as.binop.op == BINOP_AND) {
470
31
            compile_expr(e->as.binop.left, dst, line);
471
31
            size_t skip = emit_jump_placeholder(ROP_JMPFALSE, dst, line);
472
31
            compile_expr(e->as.binop.right, dst, line);
473
31
            patch_jump(skip);
474
31
            break;
475
31
        }
476
3.17k
        if (e->as.binop.op == BINOP_OR) {
477
2
            compile_expr(e->as.binop.left, dst, line);
478
2
            size_t skip = emit_jump_placeholder(ROP_JMPTRUE, dst, line);
479
2
            compile_expr(e->as.binop.right, dst, line);
480
2
            patch_jump(skip);
481
2
            break;
482
2
        }
483
484
        /* Nil coalescing — use JMPNOTNIL for 2-instruction sequence */
485
3.17k
        if (e->as.binop.op == BINOP_NIL_COALESCE) {
486
6
            compile_expr(e->as.binop.left, dst, line);
487
6
            size_t skip = emit_jump_placeholder(ROP_JMPNOTNIL, dst, line);
488
6
            compile_expr(e->as.binop.right, dst, line);
489
6
            patch_jump(skip);
490
6
            break;
491
6
        }
492
493
        /* Constant folding for integer arithmetic */
494
3.16k
        if ((e->as.binop.left->tag == EXPR_INT_LIT) &&
495
3.16k
            (e->as.binop.right->tag == EXPR_INT_LIT)) {
496
34
            int64_t li = e->as.binop.left->as.int_val;
497
34
            int64_t ri = e->as.binop.right->as.int_val;
498
34
            bool folded = true;
499
34
            LatValue result;
500
34
            switch (e->as.binop.op) {
501
7
                case BINOP_ADD: result = value_int(li + ri); break;
502
0
                case BINOP_SUB: result = value_int(li - ri); break;
503
0
                case BINOP_MUL: result = value_int(li * ri); break;
504
9
                case BINOP_DIV: if (ri != 0) { result = value_int(li / ri); } else { folded = false; } break;
505
1
                case BINOP_MOD: if (ri != 0) { result = value_int(li % ri); } else { folded = false; } break;
506
2
                case BINOP_EQ:  result = value_bool(li == ri); break;
507
2
                case BINOP_NEQ: result = value_bool(li != ri); break;
508
1
                case BINOP_LT:  result = value_bool(li < ri); break;
509
1
                case BINOP_GT:  result = value_bool(li > ri); break;
510
1
                case BINOP_LTEQ: result = value_bool(li <= ri); break;
511
1
                case BINOP_GTEQ: result = value_bool(li >= ri); break;
512
9
                default: folded = false; break;
513
34
            }
514
34
            if (folded) {
515
17
                if (result.type == VAL_INT && result.as.int_val >= -32768 && result.as.int_val <= 32767) {
516
9
                    emit_AsBx(ROP_LOADI, dst, (int16_t)result.as.int_val, line);
517
9
                } else if (result.type == VAL_BOOL) {
518
8
                    emit_ABC(result.as.bool_val ? ROP_LOADTRUE : ROP_LOADFALSE, dst, 0, 0, line);
519
8
                } else {
520
0
                    uint16_t ki = add_constant(result);
521
0
                    emit_ABx(ROP_LOADK, dst, ki, line);
522
0
                }
523
17
                break;
524
17
            }
525
34
        }
526
527
        /* ADDI optimization: x + small_int or small_int + x */
528
3.15k
        if (e->as.binop.op == BINOP_ADD) {
529
1.17k
            if (e->as.binop.right->tag == EXPR_INT_LIT &&
530
1.17k
                e->as.binop.right->as.int_val >= -128 && e->as.binop.right->as.int_val <= 127) {
531
                /* Compile left into dst, then ADDI */
532
370
                compile_expr(e->as.binop.left, dst, line);
533
370
                emit_ABC(ROP_ADDI, dst, dst, (uint8_t)(int8_t)e->as.binop.right->as.int_val, line);
534
370
                break;
535
370
            }
536
801
            if (e->as.binop.left->tag == EXPR_INT_LIT &&
537
801
                e->as.binop.left->as.int_val >= -128 && e->as.binop.left->as.int_val <= 127) {
538
0
                compile_expr(e->as.binop.right, dst, line);
539
0
                emit_ABC(ROP_ADDI, dst, dst, (uint8_t)(int8_t)e->as.binop.left->as.int_val, line);
540
0
                break;
541
0
            }
542
801
        }
543
544
        /* General binary: compile left into dst, right into tmp */
545
2.78k
        compile_expr(e->as.binop.left, dst, line);
546
2.78k
        uint8_t rhs = alloc_reg();
547
2.78k
        compile_expr(e->as.binop.right, rhs, line);
548
549
2.78k
        switch (e->as.binop.op) {
550
801
            case BINOP_ADD:  emit_ABC(ROP_ADD, dst, dst, rhs, line); break;
551
112
            case BINOP_SUB:  emit_ABC(ROP_SUB, dst, dst, rhs, line); break;
552
33
            case BINOP_MUL:  emit_ABC(ROP_MUL, dst, dst, rhs, line); break;
553
12
            case BINOP_DIV:  emit_ABC(ROP_DIV, dst, dst, rhs, line); break;
554
9
            case BINOP_MOD:  emit_ABC(ROP_MOD, dst, dst, rhs, line); break;
555
923
            case BINOP_EQ:   emit_ABC(ROP_EQ, dst, dst, rhs, line); break;
556
267
            case BINOP_NEQ:  emit_ABC(ROP_NEQ, dst, dst, rhs, line); break;
557
188
            case BINOP_LT:   emit_ABC(ROP_LT, dst, dst, rhs, line); break;
558
213
            case BINOP_GT:   emit_ABC(ROP_GT, dst, dst, rhs, line); break;
559
73
            case BINOP_LTEQ: emit_ABC(ROP_LTEQ, dst, dst, rhs, line); break;
560
131
            case BINOP_GTEQ: emit_ABC(ROP_GTEQ, dst, dst, rhs, line); break;
561
5
            case BINOP_BIT_AND: emit_ABC(ROP_BIT_AND, dst, dst, rhs, line); break;
562
4
            case BINOP_BIT_OR:  emit_ABC(ROP_BIT_OR, dst, dst, rhs, line); break;
563
4
            case BINOP_BIT_XOR: emit_ABC(ROP_BIT_XOR, dst, dst, rhs, line); break;
564
3
            case BINOP_LSHIFT:  emit_ABC(ROP_LSHIFT, dst, dst, rhs, line); break;
565
2
            case BINOP_RSHIFT:  emit_ABC(ROP_RSHIFT, dst, dst, rhs, line); break;
566
0
            default:
567
0
                rc_error = strdup("unsupported binary operator in regvm");
568
0
                break;
569
2.78k
        }
570
2.78k
        free_reg(rhs);
571
2.78k
        break;
572
2.78k
    }
573
574
205
    case EXPR_UNARYOP: {
575
205
        compile_expr(e->as.unaryop.operand, dst, line);
576
205
        switch (e->as.unaryop.op) {
577
16
            case UNOP_NEG:     emit_ABC(ROP_NEG, dst, dst, 0, line); break;
578
186
            case UNOP_NOT:     emit_ABC(ROP_NOT, dst, dst, 0, line); break;
579
3
            case UNOP_BIT_NOT: emit_ABC(ROP_BIT_NOT, dst, dst, 0, line); break;
580
205
        }
581
205
        break;
582
205
    }
583
584
2.42k
    case EXPR_IF: {
585
2.42k
        compile_expr(e->as.if_expr.cond, dst, line);
586
2.42k
        size_t else_jump = emit_jump_placeholder(ROP_JMPFALSE, dst, line);
587
588
        /* Then branch */
589
2.42k
        begin_scope();
590
2.42k
        if (e->as.if_expr.then_count > 0) {
591
3.57k
            for (size_t i = 0; i + 1 < e->as.if_expr.then_count; i++)
592
1.15k
                compile_stmt(e->as.if_expr.then_stmts[i]);
593
2.42k
            Stmt *last = e->as.if_expr.then_stmts[e->as.if_expr.then_count - 1];
594
2.42k
            if (last->tag == STMT_EXPR)
595
971
                compile_expr(last->as.expr, dst, line);
596
1.45k
            else {
597
1.45k
                compile_stmt(last);
598
1.45k
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
599
1.45k
            }
600
2.42k
        } else {
601
0
            emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
602
0
        }
603
2.42k
        end_scope(line);
604
605
2.42k
        size_t end_jump = emit_jmp_placeholder(line);
606
2.42k
        patch_jump(else_jump);
607
608
        /* Else branch */
609
2.42k
        begin_scope();
610
2.42k
        if (e->as.if_expr.else_count > 0) {
611
552
            for (size_t i = 0; i + 1 < e->as.if_expr.else_count; i++)
612
123
                compile_stmt(e->as.if_expr.else_stmts[i]);
613
429
            Stmt *last = e->as.if_expr.else_stmts[e->as.if_expr.else_count - 1];
614
429
            if (last->tag == STMT_EXPR)
615
338
                compile_expr(last->as.expr, dst, line);
616
91
            else {
617
91
                compile_stmt(last);
618
91
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
619
91
            }
620
1.99k
        } else {
621
1.99k
            emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
622
1.99k
        }
623
2.42k
        end_scope(line);
624
2.42k
        patch_jmp(end_jump);
625
2.42k
        break;
626
205
    }
627
628
3
    case EXPR_BLOCK: {
629
3
        begin_scope();
630
3
        if (e->as.block.count > 0) {
631
7
            for (size_t i = 0; i + 1 < e->as.block.count; i++)
632
4
                compile_stmt(e->as.block.stmts[i]);
633
3
            Stmt *last = e->as.block.stmts[e->as.block.count - 1];
634
3
            if (last->tag == STMT_EXPR)
635
1
                compile_expr(last->as.expr, dst, line);
636
2
            else {
637
2
                compile_stmt(last);
638
2
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
639
2
            }
640
3
        } else {
641
0
            emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
642
0
        }
643
3
        end_scope(line);
644
3
        break;
645
205
    }
646
647
5.34k
    case EXPR_CALL: {
648
        /* Phase system special forms — intercept by function name */
649
5.34k
        if (e->as.call.func->tag == EXPR_IDENT) {
650
5.34k
            const char *fn = e->as.call.func->as.str_val;
651
652
            /* react(var, callback) → ROP_REACT */
653
5.34k
            if (strcmp(fn, "react") == 0 && e->as.call.arg_count == 2 &&
654
5.34k
                e->as.call.args[0]->tag == EXPR_IDENT) {
655
9
                uint16_t name_ki = add_constant(value_string(e->as.call.args[0]->as.str_val));
656
9
                compile_expr(e->as.call.args[1], dst, line);
657
9
                emit_ABx(ROP_REACT, dst, name_ki, line);
658
9
                break;
659
9
            }
660
            /* unreact(var) → ROP_UNREACT */
661
5.34k
            if (strcmp(fn, "unreact") == 0 && e->as.call.arg_count == 1 &&
662
5.34k
                e->as.call.args[0]->tag == EXPR_IDENT) {
663
1
                uint16_t name_ki = add_constant(value_string(e->as.call.args[0]->as.str_val));
664
1
                emit_ABx(ROP_UNREACT, dst, name_ki, line);
665
1
                break;
666
1
            }
667
            /* bond(target, dep1, ..., [strategy]) → ROP_BOND */
668
5.33k
            if (strcmp(fn, "bond") == 0 && e->as.call.arg_count >= 2 &&
669
5.33k
                e->as.call.args[0]->tag == EXPR_IDENT) {
670
15
                const char *target = e->as.call.args[0]->as.str_val;
671
15
                uint16_t target_ki = add_constant(value_string(target));
672
15
                size_t dep_end = e->as.call.arg_count;
673
15
                const char *strategy = "mirror";
674
15
                Expr *last_arg = e->as.call.args[e->as.call.arg_count - 1];
675
15
                if (last_arg->tag == EXPR_STRING_LIT) {
676
3
                    strategy = last_arg->as.str_val;
677
3
                    dep_end--;
678
3
                }
679
32
                for (size_t i = 1; i < dep_end; i++) {
680
17
                    const char *dep_name = (e->as.call.args[i]->tag == EXPR_IDENT)
681
17
                        ? e->as.call.args[i]->as.str_val : "";
682
17
                    uint8_t dep_reg = alloc_reg();
683
17
                    emit_ABx(ROP_LOADK, dep_reg, add_constant(value_string(dep_name)), line);
684
17
                    uint8_t strat_reg = alloc_reg();
685
17
                    emit_ABx(ROP_LOADK, strat_reg, add_constant(value_string(strategy)), line);
686
17
                    emit_ABC(ROP_BOND, (uint8_t)target_ki, dep_reg, strat_reg, line);
687
17
                    free_reg(strat_reg);
688
17
                    free_reg(dep_reg);
689
17
                }
690
15
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
691
15
                break;
692
15
            }
693
            /* unbond(target, dep1, ...) → ROP_UNBOND */
694
5.32k
            if (strcmp(fn, "unbond") == 0 && e->as.call.arg_count >= 2 &&
695
5.32k
                e->as.call.args[0]->tag == EXPR_IDENT) {
696
1
                const char *target = e->as.call.args[0]->as.str_val;
697
1
                uint16_t target_ki = add_constant(value_string(target));
698
2
                for (size_t i = 1; i < e->as.call.arg_count; i++) {
699
1
                    const char *dep_name = (e->as.call.args[i]->tag == EXPR_IDENT)
700
1
                        ? e->as.call.args[i]->as.str_val : "";
701
1
                    uint8_t dep_reg = alloc_reg();
702
1
                    emit_ABx(ROP_LOADK, dep_reg, add_constant(value_string(dep_name)), line);
703
1
                    emit_ABx(ROP_UNBOND, (uint8_t)target_ki, (uint16_t)dep_reg, line);
704
1
                    free_reg(dep_reg);
705
1
                }
706
1
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
707
1
                break;
708
1
            }
709
            /* seed(var, contract) → ROP_SEED */
710
5.32k
            if (strcmp(fn, "seed") == 0 && e->as.call.arg_count == 2 &&
711
5.32k
                e->as.call.args[0]->tag == EXPR_IDENT) {
712
4
                uint16_t name_ki = add_constant(value_string(e->as.call.args[0]->as.str_val));
713
4
                compile_expr(e->as.call.args[1], dst, line);
714
4
                emit_ABx(ROP_SEED, dst, name_ki, line);
715
4
                break;
716
4
            }
717
            /* unseed(var) → ROP_UNSEED */
718
5.31k
            if (strcmp(fn, "unseed") == 0 && e->as.call.arg_count == 1 &&
719
5.31k
                e->as.call.args[0]->tag == EXPR_IDENT) {
720
1
                uint16_t name_ki = add_constant(value_string(e->as.call.args[0]->as.str_val));
721
1
                emit_ABx(ROP_UNSEED, dst, name_ki, line);
722
1
                break;
723
1
            }
724
            /* grow("varname") → FREEZE_VAR with consume=true (loc_type |= 0x80) */
725
5.31k
            if (strcmp(fn, "grow") == 0 && e->as.call.arg_count == 1 &&
726
5.31k
                e->as.call.args[0]->tag == EXPR_STRING_LIT) {
727
2
                const char *var_name = e->as.call.args[0]->as.str_val;
728
2
                uint16_t name_ki = add_constant(value_string(var_name));
729
2
                int slot = resolve_local(rc, var_name);
730
2
                if (slot >= 0) {
731
2
                    emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 0 | 0x80, local_reg(slot), line);
732
2
                } else {
733
0
                    int uv = resolve_upvalue(rc, var_name);
734
0
                    if (uv >= 0) {
735
0
                        emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 1 | 0x80, (uint8_t)uv, line);
736
0
                    } else {
737
0
                        emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 2 | 0x80, 0, line);
738
0
                    }
739
0
                }
740
                /* Result: load frozen value into dst */
741
2
                if (slot >= 0) {
742
2
                    emit_ABC(ROP_MOVE, dst, local_reg(slot), 0, line);
743
2
                } else {
744
0
                    int uv = resolve_upvalue(rc, var_name);
745
0
                    if (uv >= 0) emit_ABC(ROP_GETUPVALUE, dst, (uint8_t)uv, 0, line);
746
0
                    else emit_ABx(ROP_GETGLOBAL, dst, add_constant(value_string(var_name)), line);
747
0
                }
748
2
                break;
749
2
            }
750
            /* compose(f, g) → build a closure |x| { f(g(x)) } with upvalues */
751
5.31k
            if (strcmp(fn, "compose") == 0 && e->as.call.arg_count == 2) {
752
                /* Compile f and g into local registers */
753
30
                uint8_t f_reg = alloc_reg();
754
30
                uint8_t g_reg = alloc_reg();
755
30
                compile_expr(e->as.call.args[0], f_reg, line);
756
30
                compile_expr(e->as.call.args[1], g_reg, line);
757
758
                /* Build a RegChunk for |x| { f(g(x)) } */
759
30
                RegChunk *fn_chunk = regchunk_new();
760
30
                fn_chunk->name = strdup("<compose>");
761
                /* Constants: none needed (all via upvalues/registers) */
762
                /* Registers: 0=reserved, 1=x (param), 2=temp */
763
                /* Upvalue 0 = f, Upvalue 1 = g */
764
765
                /* Emit: R[2] = GETUPVALUE 0 (f)
766
                 *       R[3] = GETUPVALUE 1 (g)
767
                 *       R[4] = R[1] (x)
768
                 *       CALL R[3], 1, 1 => g(x) -> R[3]
769
                 *       R[4] = R[3] (g_result)
770
                 *       CALL R[2], 1, 1 => f(g_result) -> R[2]
771
                 *       RETURN R[2], 1
772
                 */
773
30
                regchunk_write(fn_chunk, REG_ENCODE_ABC(ROP_GETUPVALUE, 2, 0, 0), line); /* R[2] = f */
774
30
                regchunk_write(fn_chunk, REG_ENCODE_ABC(ROP_GETUPVALUE, 3, 1, 0), line); /* R[3] = g */
775
30
                regchunk_write(fn_chunk, REG_ENCODE_ABC(ROP_MOVE, 4, 1, 0), line);       /* R[4] = x */
776
30
                regchunk_write(fn_chunk, REG_ENCODE_ABC(ROP_CALL, 3, 1, 1), line);       /* R[3] = g(x) */
777
30
                regchunk_write(fn_chunk, REG_ENCODE_ABC(ROP_MOVE, 4, 3, 0), line);       /* R[4] = g_result */
778
30
                regchunk_write(fn_chunk, REG_ENCODE_ABC(ROP_CALL, 2, 1, 1), line);       /* R[2] = f(g_result) */
779
30
                regchunk_write(fn_chunk, REG_ENCODE_ABC(ROP_RETURN, 2, 1, 0), line);     /* return R[2] */
780
781
                /* Store fn_chunk as a closure constant */
782
30
                LatValue closure_val;
783
30
                closure_val.type = VAL_CLOSURE;
784
30
                closure_val.phase = VTAG_UNPHASED;
785
30
                closure_val.region_id = 2; /* upvalue count */
786
30
                closure_val.as.closure.body = NULL;
787
30
                closure_val.as.closure.native_fn = (void *)fn_chunk;
788
30
                closure_val.as.closure.captured_env = NULL;
789
30
                closure_val.as.closure.param_count = 1;
790
30
                closure_val.as.closure.param_names = NULL;
791
30
                closure_val.as.closure.default_values = NULL;
792
30
                closure_val.as.closure.has_variadic = false;
793
794
30
                uint16_t closure_ki = add_constant(closure_val);
795
796
                /* Emit CLOSURE instruction with upvalue capture directives */
797
30
                emit_ABx(ROP_CLOSURE, dst, closure_ki, line);
798
                /* Upvalue 0: f from local f_reg (is_local=1) */
799
30
                emit(REG_ENCODE_ABC(0, 1, f_reg, 0), line);
800
                /* Upvalue 1: g from local g_reg (is_local=1) */
801
30
                emit(REG_ENCODE_ABC(0, 1, g_reg, 0), line);
802
803
30
                free_reg(g_reg);
804
30
                free_reg(f_reg);
805
30
                break;
806
30
            }
807
            /* require("path") → ROP_REQUIRE (compiles module via regcompiler) */
808
5.28k
            if (strcmp(fn, "require") == 0 && e->as.call.arg_count == 1 &&
809
5.28k
                e->as.call.args[0]->tag == EXPR_STRING_LIT) {
810
9
                uint16_t path_ki = add_constant(value_string(e->as.call.args[0]->as.str_val));
811
9
                emit_ABx(ROP_REQUIRE, dst, path_ki, line);
812
9
                break;
813
9
            }
814
            /* track(var), history(var), phases(var): pass var name as string to native */
815
5.27k
            if ((strcmp(fn, "track") == 0 || strcmp(fn, "history") == 0 ||
816
5.27k
                 strcmp(fn, "phases") == 0) &&
817
5.27k
                e->as.call.arg_count == 1 &&
818
5.27k
                e->as.call.args[0]->tag == EXPR_IDENT) {
819
7
                uint8_t base = alloc_reg();
820
7
                compile_expr(e->as.call.func, base, line);
821
7
                uint8_t arg_reg = alloc_reg();
822
7
                emit_ABx(ROP_LOADK, arg_reg, add_constant(value_string(e->as.call.args[0]->as.str_val)), line);
823
7
                emit_ABC(ROP_CALL, base, 1, 1, line);
824
7
                if (base != dst) emit_ABC(ROP_MOVE, dst, base, 0, line);
825
7
                free_regs_to(base);
826
7
                break;
827
7
            }
828
            /* rewind(var, n): pass var name as string, compile second arg normally */
829
5.27k
            if (strcmp(fn, "rewind") == 0 && e->as.call.arg_count == 2 &&
830
5.27k
                e->as.call.args[0]->tag == EXPR_IDENT) {
831
3
                uint8_t base = alloc_reg();
832
3
                compile_expr(e->as.call.func, base, line);
833
3
                uint8_t arg1 = alloc_reg();
834
3
                emit_ABx(ROP_LOADK, arg1, add_constant(value_string(e->as.call.args[0]->as.str_val)), line);
835
3
                uint8_t arg2 = alloc_reg();
836
3
                compile_expr(e->as.call.args[1], arg2, line);
837
3
                emit_ABC(ROP_CALL, base, 2, 1, line);
838
3
                if (base != dst) emit_ABC(ROP_MOVE, dst, base, 0, line);
839
3
                free_regs_to(base);
840
3
                break;
841
3
            }
842
            /* pressurize(var, mode): pass var name as string */
843
5.26k
            if (strcmp(fn, "pressurize") == 0 && e->as.call.arg_count == 2 &&
844
5.26k
                e->as.call.args[0]->tag == EXPR_IDENT) {
845
6
                uint8_t base = alloc_reg();
846
6
                compile_expr(e->as.call.func, base, line);
847
6
                uint8_t arg1 = alloc_reg();
848
6
                emit_ABx(ROP_LOADK, arg1, add_constant(value_string(e->as.call.args[0]->as.str_val)), line);
849
6
                uint8_t arg2 = alloc_reg();
850
6
                compile_expr(e->as.call.args[1], arg2, line);
851
6
                emit_ABC(ROP_CALL, base, 2, 1, line);
852
6
                if (base != dst) emit_ABC(ROP_MOVE, dst, base, 0, line);
853
6
                free_regs_to(base);
854
6
                break;
855
6
            }
856
            /* depressurize(var): pass var name as string */
857
5.26k
            if (strcmp(fn, "depressurize") == 0 && e->as.call.arg_count == 1 &&
858
5.26k
                e->as.call.args[0]->tag == EXPR_IDENT) {
859
1
                uint8_t base = alloc_reg();
860
1
                compile_expr(e->as.call.func, base, line);
861
1
                uint8_t arg1 = alloc_reg();
862
1
                emit_ABx(ROP_LOADK, arg1, add_constant(value_string(e->as.call.args[0]->as.str_val)), line);
863
1
                emit_ABC(ROP_CALL, base, 1, 1, line);
864
1
                if (base != dst) emit_ABC(ROP_MOVE, dst, base, 0, line);
865
1
                free_regs_to(base);
866
1
                break;
867
1
            }
868
5.26k
        }
869
870
        /* General function call */
871
5.26k
        uint8_t base = alloc_reg();
872
5.26k
        compile_expr(e->as.call.func, base, line);
873
12.3k
        for (size_t i = 0; i < e->as.call.arg_count; i++) {
874
7.06k
            uint8_t arg_reg = alloc_reg();
875
7.06k
            compile_expr(e->as.call.args[i], arg_reg, line);
876
7.06k
        }
877
        /* CALL: A=base (func reg), B=arg count, C=1 (one return value) */
878
5.26k
        emit_ABC(ROP_CALL, base, (uint8_t)e->as.call.arg_count, 1, line);
879
        /* Result lands in base. Move to dst if needed. */
880
5.26k
        if (base != dst)
881
5.26k
            emit_ABC(ROP_MOVE, dst, base, 0, line);
882
        /* Free the window (func + args) */
883
5.26k
        free_regs_to(base);
884
5.26k
        break;
885
5.34k
    }
886
887
74
    case EXPR_FIELD_ACCESS: {
888
        /* Compile object, then GETFIELD */
889
74
        uint8_t obj_reg;
890
74
        bool obj_reg_temp = false;
891
892
74
        if (e->as.field_access.optional) {
893
            /* Optional chaining: need separate obj_reg to avoid LOADNIL clobbering */
894
8
            obj_reg = alloc_reg();
895
8
            obj_reg_temp = true;
896
8
            compile_expr(e->as.field_access.object, obj_reg, line);
897
66
        } else if (e->as.field_access.object->tag == EXPR_IDENT) {
898
61
            int local = resolve_local(rc, e->as.field_access.object->as.str_val);
899
61
            if (local >= 0) {
900
55
                obj_reg = local_reg(local);
901
55
            } else {
902
6
                obj_reg = dst;
903
6
                compile_expr(e->as.field_access.object, obj_reg, line);
904
6
            }
905
61
        } else {
906
5
            obj_reg = dst;
907
5
            compile_expr(e->as.field_access.object, obj_reg, line);
908
5
        }
909
910
        /* Optional chaining: obj?.field → if obj is nil, result is nil */
911
74
        if (e->as.field_access.optional) {
912
            /* If obj is nil, skip the field access and leave nil in dst */
913
8
            emit_ABC(ROP_LOADNIL, dst, 0, 0, line);
914
8
            size_t skip = emit_jump_placeholder(ROP_JMPFALSE, obj_reg, line);
915
8
            uint16_t field_ki = add_constant(value_string(e->as.field_access.field));
916
8
            emit_ABC(ROP_GETFIELD, dst, obj_reg, (uint8_t)field_ki, line);
917
8
            patch_jump(skip);
918
8
            if (obj_reg_temp) free_reg(obj_reg);
919
66
        } else {
920
66
            uint16_t field_ki = add_constant(value_string(e->as.field_access.field));
921
66
            emit_ABC(ROP_GETFIELD, dst, obj_reg, (uint8_t)field_ki, line);
922
66
        }
923
74
        break;
924
5.34k
    }
925
926
978
    case EXPR_INDEX: {
927
978
        uint8_t obj_reg;
928
978
        bool obj_allocated = false;
929
930
978
        if (e->as.index.object->tag == EXPR_IDENT) {
931
936
            int local = resolve_local(rc, e->as.index.object->as.str_val);
932
936
            if (local >= 0) {
933
871
                obj_reg = local_reg(local);
934
871
            } else {
935
65
                obj_reg = alloc_reg();
936
65
                obj_allocated = true;
937
65
                compile_expr(e->as.index.object, obj_reg, line);
938
65
            }
939
936
        } else {
940
42
            obj_reg = alloc_reg();
941
42
            obj_allocated = true;
942
42
            compile_expr(e->as.index.object, obj_reg, line);
943
42
        }
944
945
        /* Optional chaining: obj?[idx] → if obj is nil, result is nil */
946
978
        if (e->as.index.optional) {
947
1
            uint8_t check = alloc_reg();
948
1
            emit_ABC(ROP_MOVE, check, obj_reg, 0, line);
949
1
            emit_ABC(ROP_LOADNIL, dst, 0, 0, line);
950
1
            size_t skip = emit_jump_placeholder(ROP_JMPFALSE, check, line);
951
1
            free_reg(check);
952
1
            uint8_t idx_reg = alloc_reg();
953
1
            compile_expr(e->as.index.index, idx_reg, line);
954
1
            emit_ABC(ROP_GETINDEX, dst, obj_reg, idx_reg, line);
955
1
            free_reg(idx_reg);
956
1
            patch_jump(skip);
957
977
        } else {
958
977
            uint8_t idx_reg = alloc_reg();
959
977
            compile_expr(e->as.index.index, idx_reg, line);
960
977
            emit_ABC(ROP_GETINDEX, dst, obj_reg, idx_reg, line);
961
977
            free_reg(idx_reg);
962
977
        }
963
978
        if (obj_allocated) free_reg(obj_reg);
964
978
        break;
965
5.34k
    }
966
967
623
    case EXPR_ARRAY: {
968
        /* Check for spread elements */
969
623
        bool has_spread = false;
970
1.69k
        for (size_t i = 0; i < e->as.array.count; i++) {
971
1.07k
            if (e->as.array.elems[i]->tag == EXPR_SPREAD) {
972
6
                has_spread = true;
973
6
                break;
974
6
            }
975
1.07k
        }
976
        /* Compile elements into contiguous registers starting at dst */
977
623
        uint8_t base = alloc_reg();
978
1.70k
        for (size_t i = 0; i < e->as.array.count; i++) {
979
1.08k
            uint8_t elem_reg = (i == 0) ? base : alloc_reg();
980
1.08k
            compile_expr(e->as.array.elems[i], elem_reg, line);
981
1.08k
        }
982
623
        if (e->as.array.count == 0) {
983
            /* Empty array: B=base doesn't matter, C=0 */
984
143
            emit_ABC(ROP_NEWARRAY, dst, 0, 0, line);
985
480
        } else {
986
480
            emit_ABC(ROP_NEWARRAY, dst, base, (uint8_t)e->as.array.count, line);
987
480
        }
988
623
        if (has_spread)
989
6
            emit_ABC(ROP_ARRAY_FLATTEN, dst, dst, 0, line);
990
623
        free_regs_to(base);
991
623
        break;
992
5.34k
    }
993
994
46
    case EXPR_STRUCT_LIT: {
995
        /* Compile field values into contiguous registers, then NEWSTRUCT */
996
46
        uint16_t name_ki = add_constant(value_string(e->as.struct_lit.name));
997
998
        /* We need the struct metadata to know field order */
999
46
        uint8_t base = alloc_reg();
1000
134
        for (size_t i = 0; i < e->as.struct_lit.field_count; i++) {
1001
88
            uint8_t freg = (i == 0) ? base : alloc_reg();
1002
88
            compile_expr(e->as.struct_lit.fields[i].value, freg, line);
1003
88
        }
1004
46
        emit_ABC(ROP_NEWSTRUCT, dst, (uint8_t)(name_ki & 0xFF), (uint8_t)e->as.struct_lit.field_count, line);
1005
        /* Store the name constant index in a follow-up instruction for the VM */
1006
46
        emit_ABx(ROP_LOADK, base, name_ki, line);  /* Overloaded: VM reads this as struct name */
1007
46
        free_regs_to(base);
1008
46
        break;
1009
5.34k
    }
1010
1011
10
    case EXPR_RANGE: {
1012
10
        uint8_t start_reg = alloc_reg();
1013
10
        uint8_t end_reg = alloc_reg();
1014
10
        compile_expr(e->as.range.start, start_reg, line);
1015
10
        compile_expr(e->as.range.end, end_reg, line);
1016
10
        emit_ABC(ROP_BUILDRANGE, dst, start_reg, end_reg, line);
1017
10
        free_reg(end_reg);
1018
10
        free_reg(start_reg);
1019
10
        break;
1020
5.34k
    }
1021
1022
1.07k
    case EXPR_PRINT: {
1023
        /* Compile args into contiguous registers */
1024
1.07k
        uint8_t base = alloc_reg();
1025
2.15k
        for (size_t i = 0; i < e->as.print.arg_count; i++) {
1026
1.08k
            uint8_t preg = (i == 0) ? base : alloc_reg();
1027
1.08k
            compile_expr(e->as.print.args[i], preg, line);
1028
1.08k
        }
1029
1.07k
        emit_ABC(ROP_PRINT, base, (uint8_t)e->as.print.arg_count, 0, line);
1030
1.07k
        emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1031
1.07k
        free_regs_to(base);
1032
1.07k
        break;
1033
5.34k
    }
1034
1035
82
    case EXPR_FREEZE: {
1036
        /* ── Partial freeze: freeze(s.field) for struct fields ── */
1037
82
        if (e->as.freeze.except_count == 0 && !e->as.freeze.contract &&
1038
82
            e->as.freeze.expr->tag == EXPR_FIELD_ACCESS) {
1039
2
            Expr *parent = e->as.freeze.expr->as.field_access.object;
1040
2
            const char *field = e->as.freeze.expr->as.field_access.field;
1041
2
            if (parent->tag == EXPR_IDENT) {
1042
2
                const char *pname = parent->as.str_val;
1043
2
                int slot = resolve_local(rc, pname);
1044
2
                uint8_t var_reg = alloc_reg();
1045
2
                if (slot >= 0) {
1046
2
                    emit_ABC(ROP_MOVE, var_reg, local_reg(slot), 0, line);
1047
2
                } else {
1048
0
                    int uv = resolve_upvalue(rc, pname);
1049
0
                    uint16_t pki = add_constant(value_string(pname));
1050
0
                    if (uv >= 0) emit_ABC(ROP_GETUPVALUE, var_reg, (uint8_t)uv, 0, line);
1051
0
                    else emit_ABx(ROP_GETGLOBAL, var_reg, pki, line);
1052
0
                }
1053
2
                uint8_t fk = (uint8_t)add_constant(value_string(field));
1054
2
                emit_ABC(ROP_FREEZE_FIELD, var_reg, fk, 0, line);
1055
                /* Write back */
1056
2
                if (slot >= 0) {
1057
2
                    emit_ABC(ROP_MOVE, local_reg(slot), var_reg, 0, line);
1058
2
                } else {
1059
0
                    int uv2 = resolve_upvalue(rc, pname);
1060
0
                    uint16_t pki2 = add_constant(value_string(pname));
1061
0
                    if (uv2 >= 0) emit_ABC(ROP_SETUPVALUE, var_reg, (uint8_t)uv2, 0, line);
1062
0
                    else emit_ABx(ROP_SETGLOBAL, var_reg, pki2, line);
1063
0
                }
1064
                /* Return the frozen field value */
1065
2
                emit_ABC(ROP_GETFIELD, dst, var_reg, fk, line);
1066
2
                free_reg(var_reg);
1067
2
                break;
1068
2
            }
1069
2
        }
1070
1071
        /* ── Partial freeze: freeze(m["key"]) for map keys ── */
1072
80
        if (e->as.freeze.except_count == 0 && !e->as.freeze.contract &&
1073
80
            e->as.freeze.expr->tag == EXPR_INDEX) {
1074
2
            Expr *parent = e->as.freeze.expr->as.index.object;
1075
2
            if (parent->tag == EXPR_IDENT) {
1076
2
                const char *pname = parent->as.str_val;
1077
2
                uint8_t key_reg = alloc_reg();
1078
2
                compile_expr(e->as.freeze.expr->as.index.index, key_reg, line);
1079
2
                int slot = resolve_local(rc, pname);
1080
2
                uint8_t var_reg = alloc_reg();
1081
2
                if (slot >= 0) {
1082
2
                    emit_ABC(ROP_MOVE, var_reg, local_reg(slot), 0, line);
1083
2
                } else {
1084
0
                    int uv = resolve_upvalue(rc, pname);
1085
0
                    uint16_t pki = add_constant(value_string(pname));
1086
0
                    if (uv >= 0) emit_ABC(ROP_GETUPVALUE, var_reg, (uint8_t)uv, 0, line);
1087
0
                    else emit_ABx(ROP_GETGLOBAL, var_reg, pki, line);
1088
0
                }
1089
                /* For FREEZE_FIELD with map, the key is in a register but opcode takes constant.
1090
                 * Since m["key"] has a string literal key, extract it. */
1091
2
                Expr *idx_expr = e->as.freeze.expr->as.index.index;
1092
2
                if (idx_expr->tag == EXPR_STRING_LIT) {
1093
2
                    uint8_t fk = (uint8_t)add_constant(value_string(idx_expr->as.str_val));
1094
2
                    emit_ABC(ROP_FREEZE_FIELD, var_reg, fk, 0, line);
1095
2
                }
1096
                /* Write back */
1097
2
                if (slot >= 0) {
1098
2
                    emit_ABC(ROP_MOVE, local_reg(slot), var_reg, 0, line);
1099
2
                } else {
1100
0
                    int uv2 = resolve_upvalue(rc, pname);
1101
0
                    uint16_t pki2 = add_constant(value_string(pname));
1102
0
                    if (uv2 >= 0) emit_ABC(ROP_SETUPVALUE, var_reg, (uint8_t)uv2, 0, line);
1103
0
                    else emit_ABx(ROP_SETGLOBAL, var_reg, pki2, line);
1104
0
                }
1105
                /* Return the frozen value */
1106
2
                emit_ABC(ROP_GETINDEX, dst, var_reg, key_reg, line);
1107
2
                free_reg(key_reg);
1108
2
                free_reg(var_reg);
1109
2
                break;
1110
2
            }
1111
2
        }
1112
1113
78
        compile_expr(e->as.freeze.expr, dst, line);
1114
1115
        /* Freeze-except: freeze(x) except ["field1", ...] */
1116
78
        if (e->as.freeze.except_count > 0 && e->as.freeze.expr->tag == EXPR_IDENT) {
1117
            /* Compile except field names into temporary registers */
1118
3
            uint8_t *field_regs = malloc(e->as.freeze.except_count * sizeof(uint8_t));
1119
6
            for (size_t i = 0; i < e->as.freeze.except_count; i++) {
1120
3
                field_regs[i] = alloc_reg();
1121
3
                compile_expr(e->as.freeze.except_fields[i], field_regs[i], line);
1122
3
            }
1123
            /* Freeze the variable */
1124
3
            const char *name = e->as.freeze.expr->as.str_val;
1125
3
            uint16_t name_ki = add_constant(value_string(name));
1126
3
            int slot = resolve_local(rc, name);
1127
3
            if (slot >= 0) {
1128
3
                emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 0, local_reg(slot), line);
1129
3
            } else {
1130
0
                int uv = resolve_upvalue(rc, name);
1131
0
                if (uv >= 0) {
1132
0
                    emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 1, (uint8_t)uv, line);
1133
0
                } else {
1134
0
                    emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 2, 0, line);
1135
0
                }
1136
0
            }
1137
            /* Thaw back the excepted fields using THAW_FIELD opcode */
1138
            /* Read the frozen variable into a working register */
1139
3
            uint8_t var_reg = alloc_reg();
1140
3
            if (slot >= 0) {
1141
3
                emit_ABC(ROP_MOVE, var_reg, local_reg(slot), 0, line);
1142
3
            } else {
1143
0
                int uv2 = resolve_upvalue(rc, name);
1144
0
                if (uv2 >= 0) emit_ABC(ROP_GETUPVALUE, var_reg, (uint8_t)uv2, 0, line);
1145
0
                else emit_ABx(ROP_GETGLOBAL, var_reg, name_ki, line);
1146
0
            }
1147
6
            for (size_t i = 0; i < e->as.freeze.except_count; i++) {
1148
                /* Get the field name as a constant (except_fields are typically string literals) */
1149
3
                Expr *fe = e->as.freeze.except_fields[i];
1150
3
                if (fe->tag == EXPR_STRING_LIT) {
1151
3
                    uint8_t fk = (uint8_t)add_constant(value_string(fe->as.str_val));
1152
3
                    emit_ABC(ROP_THAW_FIELD, var_reg, fk, 0, line);
1153
3
                }
1154
3
                free_reg(field_regs[i]);
1155
3
            }
1156
            /* Write back the modified variable */
1157
3
            if (slot >= 0) {
1158
3
                emit_ABC(ROP_MOVE, local_reg(slot), var_reg, 0, line);
1159
3
            } else {
1160
0
                int uv3 = resolve_upvalue(rc, name);
1161
0
                if (uv3 >= 0) emit_ABC(ROP_SETUPVALUE, var_reg, (uint8_t)uv3, 0, line);
1162
0
                else emit_ABx(ROP_SETGLOBAL, var_reg, name_ki, line);
1163
0
            }
1164
3
            free_reg(var_reg);
1165
3
            free(field_regs);
1166
3
            break;
1167
3
        }
1168
1169
        /* Freeze contract: freeze(x) where |v| { ... }
1170
         * The contract closure is called with the value; errors are wrapped
1171
         * with "freeze contract failed: " prefix. */
1172
75
        if (e->as.freeze.contract && e->as.freeze.expr->tag == EXPR_IDENT) {
1173
            /* Save the value in a temp before handler setup (handler may clobber regs) */
1174
6
            uint8_t saved_val = alloc_reg();
1175
6
            emit_ABC(ROP_MOVE, saved_val, dst, 0, line);
1176
1177
6
            uint8_t err_reg = alloc_reg();
1178
6
            size_t handler_jump = emit_jump_placeholder(ROP_PUSH_HANDLER, err_reg, line);
1179
1180
            /* Compile contract closure and call it with the saved value */
1181
6
            uint8_t base = alloc_reg();
1182
6
            uint8_t arg = alloc_reg();
1183
6
            compile_expr(e->as.freeze.contract, base, line);
1184
6
            emit_ABC(ROP_MOVE, arg, saved_val, 0, line);
1185
6
            emit_ABC(ROP_CALL, base, 1, 1, line);
1186
6
            free_reg(arg);
1187
6
            free_reg(base);
1188
1189
            /* Success — pop handler, jump past catch */
1190
6
            emit_ABC(ROP_POP_HANDLER, 0, 0, 0, line);
1191
6
            size_t skip = emit_jmp_placeholder(line);
1192
1193
            /* Catch: err_reg has error string */
1194
6
            patch_jump(handler_jump);
1195
6
            {
1196
6
                uint8_t pfx = alloc_reg();
1197
6
                uint16_t pfx_ki = add_constant(value_string("freeze contract failed: "));
1198
6
                emit_ABx(ROP_LOADK, pfx, pfx_ki, line);
1199
6
                emit_ABC(ROP_CONCAT, err_reg, pfx, err_reg, line);
1200
6
                free_reg(pfx);
1201
6
            }
1202
6
            emit_ABC(ROP_THROW, err_reg, 0, 0, line);
1203
1204
6
            patch_jmp(skip);
1205
            /* Restore dst from saved value (CALL may have clobbered dst) */
1206
6
            emit_ABC(ROP_MOVE, dst, saved_val, 0, line);
1207
6
            free_reg(err_reg);
1208
6
            free_reg(saved_val);
1209
6
        }
1210
1211
        /* Normal freeze (or after contract passed) */
1212
75
        if (e->as.freeze.expr->tag == EXPR_IDENT) {
1213
48
            const char *name = e->as.freeze.expr->as.str_val;
1214
48
            uint16_t name_ki = add_constant(value_string(name));
1215
48
            int slot = resolve_local(rc, name);
1216
48
            if (slot >= 0) {
1217
47
                emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 0, local_reg(slot), line);
1218
47
            } else {
1219
1
                int uv = resolve_upvalue(rc, name);
1220
1
                if (uv >= 0) {
1221
0
                    emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 1, (uint8_t)uv, line);
1222
1
                } else {
1223
1
                    emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 2, 0, line);
1224
1
                }
1225
1
            }
1226
48
        } else if (e->as.freeze.expr->tag == EXPR_FIELD_ACCESS) {
1227
            /* Partial freeze: freeze(s.field) */
1228
1
            Expr *parent = e->as.freeze.expr->as.field_access.object;
1229
1
            const char *field = e->as.freeze.expr->as.field_access.field;
1230
1
            if (parent->tag == EXPR_IDENT) {
1231
1
                const char *pname = parent->as.str_val;
1232
1
                uint16_t pname_ki = add_constant(value_string(pname));
1233
1
                uint16_t field_ki = add_constant(value_string(field));
1234
1
                int slot = resolve_local(rc, pname);
1235
1
                uint8_t loc_type, slot_val;
1236
1
                if (slot >= 0) { loc_type = 0; slot_val = local_reg(slot); }
1237
0
                else {
1238
0
                    int uv = resolve_upvalue(rc, pname);
1239
0
                    if (uv >= 0) { loc_type = 1; slot_val = (uint8_t)uv; }
1240
0
                    else { loc_type = 2; slot_val = 0; }
1241
0
                }
1242
                /* Emit a FREEZE_VAR with field_ki encoded — but we don't have an opcode for that.
1243
                 * Instead, freeze the value in the register and write back. */
1244
1
                emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1245
                /* Write the frozen field back: parent.field = frozen_dst */
1246
1
                uint8_t parent_reg = alloc_reg();
1247
1
                if (loc_type == 0) emit_ABC(ROP_MOVE, parent_reg, slot_val, 0, line);
1248
0
                else if (loc_type == 1) emit_ABC(ROP_GETUPVALUE, parent_reg, slot_val, 0, line);
1249
0
                else emit_ABx(ROP_GETGLOBAL, parent_reg, pname_ki, line);
1250
1
                emit_ABC(ROP_SETFIELD, parent_reg, (uint8_t)(field_ki & 0xFF), dst, line);
1251
                /* Write back the modified parent */
1252
1
                if (loc_type == 0) emit_ABC(ROP_MOVE, slot_val, parent_reg, 0, line);
1253
0
                else if (loc_type == 1) emit_ABC(ROP_SETUPVALUE, parent_reg, slot_val, 0, line);
1254
0
                else emit_ABx(ROP_SETGLOBAL, parent_reg, pname_ki, line);
1255
1
                free_reg(parent_reg);
1256
1
            } else {
1257
0
                emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1258
0
            }
1259
26
        } else if (e->as.freeze.expr->tag == EXPR_INDEX) {
1260
            /* Partial freeze: freeze(m["key"]) */
1261
0
            Expr *parent = e->as.freeze.expr->as.index.object;
1262
0
            if (parent->tag == EXPR_IDENT) {
1263
0
                const char *pname = parent->as.str_val;
1264
0
                uint16_t pname_ki = add_constant(value_string(pname));
1265
0
                int slot = resolve_local(rc, pname);
1266
0
                uint8_t loc_type, slot_val;
1267
0
                if (slot >= 0) { loc_type = 0; slot_val = local_reg(slot); }
1268
0
                else {
1269
0
                    int uv = resolve_upvalue(rc, pname);
1270
0
                    if (uv >= 0) { loc_type = 1; slot_val = (uint8_t)uv; }
1271
0
                    else { loc_type = 2; slot_val = 0; }
1272
0
                }
1273
                /* Freeze the indexed value in-register */
1274
0
                emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1275
                /* Write back: parent[key] = frozen */
1276
0
                uint8_t parent_reg = alloc_reg();
1277
0
                uint8_t key_reg = alloc_reg();
1278
0
                if (loc_type == 0) emit_ABC(ROP_MOVE, parent_reg, slot_val, 0, line);
1279
0
                else if (loc_type == 1) emit_ABC(ROP_GETUPVALUE, parent_reg, slot_val, 0, line);
1280
0
                else emit_ABx(ROP_GETGLOBAL, parent_reg, pname_ki, line);
1281
0
                compile_expr(e->as.freeze.expr->as.index.index, key_reg, line);
1282
0
                emit_ABC(ROP_SETINDEX, parent_reg, key_reg, dst, line);
1283
0
                if (loc_type == 0) emit_ABC(ROP_MOVE, slot_val, parent_reg, 0, line);
1284
0
                else if (loc_type == 1) emit_ABC(ROP_SETUPVALUE, parent_reg, slot_val, 0, line);
1285
0
                else emit_ABx(ROP_SETGLOBAL, parent_reg, pname_ki, line);
1286
0
                free_reg(key_reg);
1287
0
                free_reg(parent_reg);
1288
0
            } else {
1289
0
                emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1290
0
            }
1291
26
        } else {
1292
26
            emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1293
26
        }
1294
75
        break;
1295
78
    }
1296
1297
14
    case EXPR_THAW: {
1298
14
        compile_expr(e->as.freeze_expr, dst, line);
1299
14
        if (e->as.freeze_expr->tag == EXPR_IDENT) {
1300
14
            const char *name = e->as.freeze_expr->as.str_val;
1301
14
            uint16_t name_ki = add_constant(value_string(name));
1302
14
            int slot = resolve_local(rc, name);
1303
14
            if (slot >= 0) {
1304
13
                emit_ABC(ROP_THAW_VAR, (uint8_t)(name_ki & 0xFF), 0, local_reg(slot), line);
1305
13
            } else {
1306
1
                int uv = resolve_upvalue(rc, name);
1307
1
                if (uv >= 0) {
1308
0
                    emit_ABC(ROP_THAW_VAR, (uint8_t)(name_ki & 0xFF), 1, (uint8_t)uv, line);
1309
1
                } else {
1310
1
                    emit_ABC(ROP_THAW_VAR, (uint8_t)(name_ki & 0xFF), 2, 0, line);
1311
1
                }
1312
1
            }
1313
14
        } else {
1314
0
            emit_ABC(ROP_THAW, dst, dst, 0, line);
1315
0
        }
1316
14
        break;
1317
78
    }
1318
1319
3
    case EXPR_CLONE: {
1320
3
        compile_expr(e->as.freeze_expr, dst, line);
1321
3
        emit_ABC(ROP_CLONE, dst, dst, 0, line);
1322
3
        break;
1323
78
    }
1324
1325
5.22k
    case EXPR_METHOD_CALL: {
1326
        /* Optional chaining: obj?.method() — if obj is nil, return nil */
1327
5.22k
        size_t opt_skip = 0;
1328
5.22k
        if (e->as.method_call.optional) {
1329
1
            uint8_t check = alloc_reg();
1330
1
            compile_expr(e->as.method_call.object, check, line);
1331
1
            emit_ABC(ROP_LOADNIL, dst, 0, 0, line);
1332
            /* JMPNOTNIL: if check is NOT nil, skip the early return (jump over) */
1333
            /* We want: if nil, skip to end. So use JMPNOTNIL to skip the jump. */
1334
1
            size_t not_nil = emit_jump_placeholder(ROP_JMPNOTNIL, check, line);
1335
            /* Object is nil — jump to end */
1336
1
            opt_skip = emit_jmp_placeholder(line);
1337
1
            patch_jump(not_nil);
1338
1
            free_reg(check);
1339
1
        }
1340
1341
        /* Check if the object is a global variable — needs INVOKE_GLOBAL for
1342
         * in-place mutation (push/pop/etc) to persist on the global. */
1343
5.22k
        bool is_global = false;
1344
5.22k
        const char *global_name = NULL;
1345
5.22k
        if (e->as.method_call.object->tag == EXPR_IDENT) {
1346
5.09k
            int local = resolve_local(rc, e->as.method_call.object->as.str_val);
1347
5.09k
            if (local < 0) {
1348
210
                int uv = resolve_upvalue(rc, e->as.method_call.object->as.str_val);
1349
210
                if (uv < 0) {
1350
210
                    is_global = true;
1351
210
                    global_name = e->as.method_call.object->as.str_val;
1352
210
                }
1353
210
            }
1354
5.09k
        }
1355
1356
5.22k
        if (is_global) {
1357
            /* INVOKE_GLOBAL two-instruction sequence:
1358
             *   INVOKE_GLOBAL dst, name_ki, argc
1359
             *   data:         method_ki, args_base, 0
1360
             * VM gets env_get_ref() for in-place mutation. */
1361
210
            uint8_t args_base = alloc_reg();
1362
478
            for (size_t i = 0; i < e->as.method_call.arg_count; i++) {
1363
268
                uint8_t arg_reg = (i == 0) ? args_base : alloc_reg();
1364
268
                compile_expr(e->as.method_call.args[i], arg_reg, line);
1365
268
            }
1366
1367
210
            uint16_t name_ki = add_constant(value_string(global_name));
1368
210
            uint16_t method_ki = add_constant(value_string(e->as.method_call.method));
1369
1370
210
            emit_ABC(ROP_INVOKE_GLOBAL, dst, (uint8_t)(name_ki & 0xFF),
1371
210
                     (uint8_t)e->as.method_call.arg_count, line);
1372
210
            emit_ABC(ROP_MOVE, (uint8_t)(method_ki & 0xFF), args_base, 0, line);
1373
1374
210
            if (e->as.method_call.arg_count == 0)
1375
41
                free_reg(args_base);
1376
169
            else
1377
169
                free_regs_to(args_base);
1378
210
            if (opt_skip) patch_jmp(opt_skip);
1379
210
            break;
1380
210
        }
1381
1382
        /* Local or expression object: two-instruction INVOKE sequence:
1383
         *   INVOKE dst, method_ki, argc
1384
         *   data:  obj_reg, args_base, 0
1385
         * For local variables, obj_reg IS the local's register (mutation persists). */
1386
5.01k
        uint8_t obj_reg;
1387
5.01k
        bool obj_allocated = false;
1388
1389
5.01k
        if (e->as.method_call.object->tag == EXPR_IDENT) {
1390
4.88k
            int local = resolve_local(rc, e->as.method_call.object->as.str_val);
1391
4.88k
            if (local >= 0) {
1392
4.88k
                obj_reg = local_reg(local);
1393
4.88k
            } else {
1394
                /* Upvalue — compile expression to temp reg */
1395
0
                obj_reg = alloc_reg();
1396
0
                obj_allocated = true;
1397
0
                compile_expr(e->as.method_call.object, obj_reg, line);
1398
0
            }
1399
4.88k
        } else {
1400
133
            obj_reg = alloc_reg();
1401
133
            obj_allocated = true;
1402
133
            compile_expr(e->as.method_call.object, obj_reg, line);
1403
133
        }
1404
1405
        /* Compile args into contiguous temp registers */
1406
5.01k
        uint8_t args_base = alloc_reg();  /* placeholder even if 0 args */
1407
10.8k
        for (size_t i = 0; i < e->as.method_call.arg_count; i++) {
1408
5.78k
            uint8_t arg_reg = (i == 0) ? args_base : alloc_reg();
1409
5.78k
            compile_expr(e->as.method_call.args[i], arg_reg, line);
1410
5.78k
        }
1411
1412
5.01k
        uint16_t method_ki = add_constant(value_string(e->as.method_call.method));
1413
1414
5.01k
        emit_ABC(ROP_INVOKE, dst, (uint8_t)(method_ki & 0xFF),
1415
5.01k
                 (uint8_t)e->as.method_call.arg_count, line);
1416
5.01k
        emit_ABC(ROP_MOVE, obj_reg, args_base, 0, line);  /* data word (not executed as MOVE) */
1417
1418
        /* Free temp registers */
1419
5.01k
        if (e->as.method_call.arg_count == 0)
1420
315
            free_reg(args_base);
1421
4.70k
        else
1422
4.70k
            free_regs_to(args_base);
1423
5.01k
        if (obj_allocated)
1424
133
            free_reg(obj_reg);
1425
5.01k
        if (opt_skip) patch_jmp(opt_skip);
1426
5.01k
        break;
1427
5.22k
    }
1428
1429
637
    case EXPR_CLOSURE: {
1430
637
        RegCompiler func_comp;
1431
637
        rc_init(&func_comp, rc, REG_FUNC_CLOSURE);
1432
1433
        /* Count non-variadic params for arity */
1434
637
        size_t declared_arity = e->as.closure.param_count;
1435
637
        if (e->as.closure.has_variadic && declared_arity > 0)
1436
92
            declared_arity--;
1437
637
        func_comp.arity = (int)declared_arity;
1438
1439
1.33k
        for (size_t i = 0; i < e->as.closure.param_count; i++)
1440
700
            add_local(e->as.closure.params[i]);
1441
1442
        /* Emit default parameter initialization */
1443
637
        if (e->as.closure.default_values) {
1444
199
            for (size_t i = 0; i < e->as.closure.param_count; i++) {
1445
103
                if (e->as.closure.default_values[i]) {
1446
7
                    uint8_t preg = local_reg((int)(i + 1));
1447
7
                    size_t skip = emit_jump_placeholder(ROP_JMPNOTNIL, preg, line);
1448
7
                    compile_expr(e->as.closure.default_values[i], preg, line);
1449
7
                    patch_jump(skip);
1450
7
                }
1451
103
            }
1452
96
        }
1453
1454
        /* Emit variadic collection if needed */
1455
637
        if (e->as.closure.has_variadic) {
1456
92
            uint8_t var_reg = local_reg((int)(declared_arity + 1));
1457
92
            emit_ABC(ROP_COLLECT_VARARGS, var_reg, (uint8_t)(declared_arity + 1), 0, line);
1458
92
        }
1459
1460
        /* Compile body - if block, last expression is the return value */
1461
637
        if (e->as.closure.body->tag == EXPR_BLOCK) {
1462
614
            Expr *block = e->as.closure.body;
1463
614
            if (block->as.block.count > 0 &&
1464
614
                block->as.block.stmts[block->as.block.count - 1]->tag == STMT_EXPR) {
1465
117
                uint8_t result_reg = alloc_reg();
1466
127
                for (size_t i = 0; i + 1 < block->as.block.count; i++)
1467
10
                    compile_stmt(block->as.block.stmts[i]);
1468
117
                compile_expr(block->as.block.stmts[block->as.block.count - 1]->as.expr, result_reg, line);
1469
117
                emit_return(result_reg, line);
1470
117
                free_reg(result_reg);
1471
497
            } else {
1472
1.86k
                for (size_t i = 0; i < block->as.block.count; i++)
1473
1.36k
                    compile_stmt(block->as.block.stmts[i]);
1474
497
                uint8_t result_reg = alloc_reg();
1475
497
                emit_ABC(ROP_LOADUNIT, result_reg, 0, 0, line);
1476
497
                emit_return(result_reg, line);
1477
497
                free_reg(result_reg);
1478
497
            }
1479
614
        } else {
1480
23
            uint8_t result_reg = alloc_reg();
1481
23
            compile_expr(e->as.closure.body, result_reg, line);
1482
23
            emit_return(result_reg, line);
1483
23
            free_reg(result_reg);
1484
23
        }
1485
1486
637
        RegChunk *fn_chunk = func_comp.chunk;
1487
637
        size_t upvalue_count = func_comp.upvalue_count;
1488
637
        RegCompilerUpvalue *upvalues = NULL;
1489
637
        if (upvalue_count > 0) {
1490
524
            upvalues = malloc(upvalue_count * sizeof(RegCompilerUpvalue));
1491
524
            memcpy(upvalues, func_comp.upvalues, upvalue_count * sizeof(RegCompilerUpvalue));
1492
524
        }
1493
637
        rc_cleanup(&func_comp);
1494
637
        rc = func_comp.enclosing;
1495
1496
        /* Store compiled chunk as a closure constant */
1497
637
        LatValue fn_val;
1498
637
        memset(&fn_val, 0, sizeof(fn_val));
1499
637
        fn_val.type = VAL_CLOSURE;
1500
637
        fn_val.phase = VTAG_UNPHASED;
1501
637
        fn_val.region_id = upvalue_count;  /* encode upvalue count for runtime */
1502
637
        fn_val.as.closure.body = NULL;
1503
637
        fn_val.as.closure.native_fn = fn_chunk;
1504
637
        fn_val.as.closure.param_count = e->as.closure.param_count;
1505
637
        if (e->as.closure.param_count > 0) {
1506
637
            fn_val.as.closure.param_names = malloc(e->as.closure.param_count * sizeof(char *));
1507
1.33k
            for (size_t pi = 0; pi < e->as.closure.param_count; pi++)
1508
700
                fn_val.as.closure.param_names[pi] = strdup(e->as.closure.params[pi]);
1509
637
        }
1510
637
        uint16_t fn_ki = add_constant(fn_val);
1511
1512
637
        emit_ABx(ROP_CLOSURE, dst, fn_ki, line);
1513
        /* Emit upvalue descriptors as follow-up instructions */
1514
1.56k
        for (size_t i = 0; i < upvalue_count; i++) {
1515
930
            emit_ABC(ROP_MOVE, upvalues[i].is_local ? 1 : 0, upvalues[i].index, 0, line);
1516
930
        }
1517
637
        free(upvalues);
1518
637
        break;
1519
5.22k
    }
1520
1521
16
    case EXPR_INTERP_STRING: {
1522
        /* Build interpolated string by concatenating parts */
1523
16
        if (e->as.interp.count == 0) {
1524
0
            uint16_t ki = add_constant(value_string(e->as.interp.parts[0]));
1525
0
            emit_ABx(ROP_LOADK, dst, ki, line);
1526
0
            break;
1527
0
        }
1528
1529
        /* Load first part */
1530
16
        uint16_t ki = add_constant(value_string(e->as.interp.parts[0]));
1531
16
        emit_ABx(ROP_LOADK, dst, ki, line);
1532
1533
16
        uint8_t tmp = alloc_reg();
1534
42
        for (size_t i = 0; i < e->as.interp.count; i++) {
1535
            /* Concat with expression result */
1536
26
            compile_expr(e->as.interp.exprs[i], tmp, line);
1537
26
            emit_ABC(ROP_CONCAT, dst, dst, tmp, line);
1538
            /* Concat with next string part */
1539
26
            if (e->as.interp.parts[i + 1][0] != '\0') {
1540
11
                uint16_t pk = add_constant(value_string(e->as.interp.parts[i + 1]));
1541
11
                emit_ABx(ROP_LOADK, tmp, pk, line);
1542
11
                emit_ABC(ROP_CONCAT, dst, dst, tmp, line);
1543
11
            }
1544
26
        }
1545
16
        free_reg(tmp);
1546
16
        break;
1547
16
    }
1548
1549
15
    case EXPR_MATCH: {
1550
        /* Simple match: compile scrutinee, then chain of comparisons */
1551
15
        uint8_t scrutinee = alloc_reg();
1552
15
        compile_expr(e->as.match_expr.scrutinee, scrutinee, line);
1553
1554
15
        size_t *end_jumps = malloc(e->as.match_expr.arm_count * sizeof(size_t));
1555
15
        size_t end_count = 0;
1556
1557
49
        for (size_t i = 0; i < e->as.match_expr.arm_count; i++) {
1558
34
            MatchArm *arm = &e->as.match_expr.arms[i];
1559
34
            size_t next_arm = 0;
1560
1561
34
            if (arm->pattern->tag == PAT_WILDCARD) {
1562
18
                if (arm->pattern->phase_qualifier == PHASE_CRYSTAL) {
1563
                    /* crystal _ => check phase IS crystal */
1564
2
                    uint8_t cmp_reg = alloc_reg();
1565
2
                    emit_ABC(ROP_IS_CRYSTAL, cmp_reg, scrutinee, 0, line);
1566
2
                    next_arm = emit_jump_placeholder(ROP_JMPFALSE, cmp_reg, line);
1567
2
                    free_reg(cmp_reg);
1568
16
                } else if (arm->pattern->phase_qualifier == PHASE_FLUID) {
1569
                    /* fluid _ => check phase is NOT crystal */
1570
3
                    uint8_t cmp_reg = alloc_reg();
1571
3
                    emit_ABC(ROP_IS_CRYSTAL, cmp_reg, scrutinee, 0, line);
1572
3
                    emit_ABC(ROP_NOT, cmp_reg, cmp_reg, 0, line);
1573
3
                    next_arm = emit_jump_placeholder(ROP_JMPFALSE, cmp_reg, line);
1574
3
                    free_reg(cmp_reg);
1575
3
                }
1576
                /* else PHASE_UNSPECIFIED: always matches */
1577
18
            } else if (arm->pattern->tag == PAT_LITERAL) {
1578
9
                uint8_t pat_reg = alloc_reg();
1579
9
                compile_expr(arm->pattern->as.literal, pat_reg, line);
1580
9
                uint8_t cmp_reg = alloc_reg();
1581
9
                emit_ABC(ROP_EQ, cmp_reg, scrutinee, pat_reg, line);
1582
9
                next_arm = emit_jump_placeholder(ROP_JMPFALSE, cmp_reg, line);
1583
9
                free_reg(cmp_reg);
1584
9
                free_reg(pat_reg);
1585
9
            } else if (arm->pattern->tag == PAT_RANGE) {
1586
                /* Range pattern: val >= start && val <= end.
1587
                 * Both fail paths need to skip to next arm. We emit:
1588
                 *   GTEQ cmp, scrutinee, start
1589
                 *   JMPFALSE cmp → next_arm
1590
                 *   LTEQ cmp, scrutinee, end
1591
                 *   JMPFALSE cmp → next_arm
1592
                 * Both JMPFALSE targets are patched later. Since patch_jump only
1593
                 * patches one slot, we handle the first fail by jumping to a trampoline. */
1594
2
                uint8_t start_reg = alloc_reg();
1595
2
                uint8_t cmp_reg = alloc_reg();
1596
2
                compile_expr(arm->pattern->as.range.start, start_reg, line);
1597
2
                emit_ABC(ROP_GTEQ, cmp_reg, scrutinee, start_reg, line);
1598
                /* On fail: load false into cmp_reg so the second JMPFALSE also triggers */
1599
2
                size_t range_fail1 = emit_jump_placeholder(ROP_JMPFALSE, cmp_reg, line);
1600
2
                free_reg(start_reg);
1601
2
                uint8_t end_reg = alloc_reg();
1602
2
                compile_expr(arm->pattern->as.range.end, end_reg, line);
1603
2
                emit_ABC(ROP_LTEQ, cmp_reg, scrutinee, end_reg, line);
1604
2
                free_reg(end_reg);
1605
                /* Skip past the trampoline on success */
1606
2
                size_t skip_tramp = emit_jmp_placeholder(line);
1607
                /* Trampoline: range_fail1 lands here, loads false for the final JMPFALSE */
1608
2
                patch_jump(range_fail1);
1609
2
                emit_ABC(ROP_LOADFALSE, cmp_reg, 0, 0, line);
1610
2
                patch_jmp(skip_tramp);
1611
                /* Final test: if either check failed, cmp_reg is false */
1612
2
                next_arm = emit_jump_placeholder(ROP_JMPFALSE, cmp_reg, line);
1613
2
                free_reg(cmp_reg);
1614
5
            } else if (arm->pattern->tag == PAT_BINDING) {
1615
                /* Bind value to name */
1616
5
                uint8_t bind_reg = add_local(arm->pattern->as.binding_name);
1617
5
                emit_ABC(ROP_MOVE, bind_reg, scrutinee, 0, line);
1618
5
                if (arm->guard) {
1619
2
                    uint8_t guard_reg = alloc_reg();
1620
2
                    compile_expr(arm->guard, guard_reg, line);
1621
2
                    next_arm = emit_jump_placeholder(ROP_JMPFALSE, guard_reg, line);
1622
2
                    free_reg(guard_reg);
1623
2
                }
1624
5
            }
1625
1626
            /* Compile arm body */
1627
34
            begin_scope();
1628
34
            if (arm->body_count > 0) {
1629
35
                for (size_t j = 0; j + 1 < arm->body_count; j++)
1630
1
                    compile_stmt(arm->body[j]);
1631
34
                Stmt *last = arm->body[arm->body_count - 1];
1632
34
                if (last->tag == STMT_EXPR)
1633
34
                    compile_expr(last->as.expr, dst, line);
1634
0
                else {
1635
0
                    compile_stmt(last);
1636
0
                    emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1637
0
                }
1638
34
            } else {
1639
0
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1640
0
            }
1641
34
            end_scope(line);
1642
1643
34
            end_jumps[end_count++] = emit_jmp_placeholder(line);
1644
34
            if (next_arm != 0)
1645
18
                patch_jump(next_arm);
1646
34
        }
1647
1648
        /* Default: nil */
1649
15
        emit_ABC(ROP_LOADNIL, dst, 0, 0, line);
1650
49
        for (size_t i = 0; i < end_count; i++)
1651
34
            patch_jmp(end_jumps[i]);
1652
15
        free_reg(scrutinee);
1653
15
        free(end_jumps);
1654
15
        break;
1655
16
    }
1656
1657
548
    case EXPR_ENUM_VARIANT: {
1658
548
        if (!rc_is_known_enum(e->as.enum_variant.enum_name)) {
1659
            /* Not a declared enum — fall back to global function call
1660
             * e.g. Map::new() calls the "Map::new" builtin */
1661
534
            char key[256];
1662
534
            snprintf(key, sizeof(key), "%s::%s",
1663
534
                     e->as.enum_variant.enum_name, e->as.enum_variant.variant_name);
1664
534
            uint16_t fn_ki = add_constant(value_string(key));
1665
534
            uint8_t fn_reg = alloc_reg();
1666
534
            emit_ABx(ROP_GETGLOBAL, fn_reg, fn_ki, line);
1667
1668
            /* Compile args into contiguous registers after fn_reg */
1669
579
            for (size_t i = 0; i < e->as.enum_variant.arg_count; i++) {
1670
45
                uint8_t arg_reg = alloc_reg();
1671
45
                compile_expr(e->as.enum_variant.args[i], arg_reg, line);
1672
45
            }
1673
534
            emit_ABC(ROP_CALL, fn_reg, (uint8_t)e->as.enum_variant.arg_count, 0, line);
1674
534
            if (dst != fn_reg)
1675
534
                emit_ABC(ROP_MOVE, dst, fn_reg, 0, line);
1676
534
            free_regs_to(fn_reg);
1677
534
            break;
1678
534
        }
1679
        /* For simple enums without args, just load the constant */
1680
14
        if (e->as.enum_variant.arg_count == 0) {
1681
10
            uint16_t ki = add_constant(value_enum(e->as.enum_variant.enum_name,
1682
10
                                                   e->as.enum_variant.variant_name, NULL, 0));
1683
10
            emit_ABx(ROP_LOADK, dst, ki, line);
1684
10
        } else {
1685
            /* Compile payload args into contiguous registers */
1686
4
            uint8_t base = alloc_reg();
1687
10
            for (size_t i = 0; i < e->as.enum_variant.arg_count; i++) {
1688
6
                uint8_t arg_reg = (i == 0) ? base : alloc_reg();
1689
6
                compile_expr(e->as.enum_variant.args[i], arg_reg, line);
1690
6
            }
1691
            /* NEWENUM: A=dst, B=name_ki, C=argc
1692
             * Follow-up data word: A=base, B=variant_ki */
1693
4
            uint16_t name_ki = add_constant(value_string(e->as.enum_variant.enum_name));
1694
4
            uint16_t var_ki = add_constant(value_string(e->as.enum_variant.variant_name));
1695
4
            emit_ABC(ROP_NEWENUM, dst, (uint8_t)(name_ki & 0xFF),
1696
4
                     (uint8_t)e->as.enum_variant.arg_count, line);
1697
4
            emit_ABC(ROP_MOVE, base, (uint8_t)(var_ki & 0xFF), (uint8_t)((name_ki >> 8) & 0xFF), line);
1698
4
            free_regs_to(base);
1699
4
        }
1700
14
        break;
1701
548
    }
1702
1703
5
    case EXPR_SUBLIMATE: {
1704
5
        compile_expr(e->as.freeze_expr, dst, line);
1705
5
        if (e->as.freeze_expr->tag == EXPR_IDENT) {
1706
5
            const char *name = e->as.freeze_expr->as.str_val;
1707
5
            uint16_t name_ki = add_constant(value_string(name));
1708
5
            int slot = resolve_local(rc, name);
1709
5
            if (slot >= 0) {
1710
5
                emit_ABC(ROP_SUBLIMATE_VAR, (uint8_t)(name_ki & 0xFF), 0, local_reg(slot), line);
1711
5
            } else {
1712
0
                int uv = resolve_upvalue(rc, name);
1713
0
                if (uv >= 0) {
1714
0
                    emit_ABC(ROP_SUBLIMATE_VAR, (uint8_t)(name_ki & 0xFF), 1, (uint8_t)uv, line);
1715
0
                } else {
1716
0
                    emit_ABC(ROP_SUBLIMATE_VAR, (uint8_t)(name_ki & 0xFF), 2, 0, line);
1717
0
                }
1718
0
            }
1719
5
        }
1720
        /* For non-ident expressions, the value in dst already has VTAG_SUBLIMATED
1721
         * handled at the value level — no writeback needed */
1722
5
        break;
1723
548
    }
1724
1725
11
    case EXPR_TUPLE: {
1726
        /* Compile elements into contiguous registers, then NEWTUPLE */
1727
11
        if (e->as.tuple.count == 0) {
1728
0
            emit_ABC(ROP_NEWTUPLE, dst, 0, 0, line);
1729
11
        } else {
1730
11
            uint8_t base = alloc_reg();
1731
42
            for (size_t i = 0; i < e->as.tuple.count; i++) {
1732
31
                uint8_t elem_reg = (i == 0) ? base : alloc_reg();
1733
31
                compile_expr(e->as.tuple.elems[i], elem_reg, line);
1734
31
            }
1735
11
            emit_ABC(ROP_NEWTUPLE, dst, base, (uint8_t)e->as.tuple.count, line);
1736
11
            free_regs_to(base);
1737
11
        }
1738
11
        break;
1739
548
    }
1740
1741
9
    case EXPR_SPREAD: {
1742
        /* Compile inner expr, then flatten */
1743
9
        compile_expr(e->as.spread_expr, dst, line);
1744
9
        emit_ABC(ROP_ARRAY_FLATTEN, dst, dst, 0, line);
1745
9
        break;
1746
548
    }
1747
1748
105
    case EXPR_TRY_CATCH: {
1749
        /* PUSH_HANDLER A=error_reg, sBx=offset to catch
1750
         * try body
1751
         * POP_HANDLER
1752
         * JMP past catch
1753
         * catch body */
1754
105
        uint8_t error_reg = alloc_reg();
1755
105
        size_t handler = emit_jump_placeholder(ROP_PUSH_HANDLER, error_reg, line);
1756
1757
        /* Try body */
1758
105
        begin_scope();
1759
105
        if (e->as.try_catch.try_count > 0) {
1760
136
            for (size_t i = 0; i + 1 < e->as.try_catch.try_count; i++)
1761
31
                compile_stmt(e->as.try_catch.try_stmts[i]);
1762
105
            Stmt *last = e->as.try_catch.try_stmts[e->as.try_catch.try_count - 1];
1763
105
            if (last->tag == STMT_EXPR)
1764
105
                compile_expr(last->as.expr, dst, line);
1765
0
            else {
1766
0
                compile_stmt(last);
1767
0
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1768
0
            }
1769
105
        } else {
1770
0
            emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1771
0
        }
1772
105
        end_scope(line);
1773
1774
105
        emit_ABC(ROP_POP_HANDLER, 0, 0, 0, line);
1775
105
        size_t skip_catch = emit_jmp_placeholder(line);
1776
105
        patch_jump(handler);
1777
1778
        /* Catch body — bind error to catch_var */
1779
105
        begin_scope();
1780
105
        if (e->as.try_catch.catch_var) {
1781
105
            uint8_t catch_reg = add_local(e->as.try_catch.catch_var);
1782
105
            emit_ABC(ROP_MOVE, catch_reg, error_reg, 0, line);
1783
105
        }
1784
105
        if (e->as.try_catch.catch_count > 0) {
1785
105
            for (size_t i = 0; i + 1 < e->as.try_catch.catch_count; i++)
1786
0
                compile_stmt(e->as.try_catch.catch_stmts[i]);
1787
105
            Stmt *last = e->as.try_catch.catch_stmts[e->as.try_catch.catch_count - 1];
1788
105
            if (last->tag == STMT_EXPR)
1789
76
                compile_expr(last->as.expr, dst, line);
1790
29
            else {
1791
29
                compile_stmt(last);
1792
29
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1793
29
            }
1794
105
        } else {
1795
0
            emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1796
0
        }
1797
105
        end_scope(line);
1798
105
        free_reg(error_reg);
1799
105
        patch_jmp(skip_catch);
1800
105
        break;
1801
548
    }
1802
1803
6
    case EXPR_TRY_PROPAGATE: {
1804
        /* Compile inner expr, then TRY_UNWRAP */
1805
6
        compile_expr(e->as.try_propagate_expr, dst, line);
1806
6
        emit_ABC(ROP_TRY_UNWRAP, dst, 0, 0, line);
1807
6
        break;
1808
548
    }
1809
1810
1
    case EXPR_FORGE: {
1811
        /* Forge: compile block, then freeze result */
1812
1
        begin_scope();
1813
1
        if (e->as.block.count > 0) {
1814
3
            for (size_t i = 0; i + 1 < e->as.block.count; i++)
1815
2
                compile_stmt(e->as.block.stmts[i]);
1816
1
            Stmt *last = e->as.block.stmts[e->as.block.count - 1];
1817
1
            if (last->tag == STMT_EXPR)
1818
1
                compile_expr(last->as.expr, dst, line);
1819
0
            else {
1820
0
                compile_stmt(last);
1821
0
                emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1822
0
            }
1823
1
        } else {
1824
0
            emit_ABC(ROP_LOADUNIT, dst, 0, 0, line);
1825
0
        }
1826
1
        end_scope(line);
1827
1
        emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1828
1
        break;
1829
548
    }
1830
1831
7
    case EXPR_ANNEAL: {
1832
        /* Anneal: check crystal, thaw, apply closure, refreeze, write-back */
1833
1834
        /* 1. Check: target must be crystal */
1835
7
        compile_expr(e->as.anneal.expr, dst, line);
1836
7
        uint8_t check_reg = alloc_reg();
1837
7
        emit_ABC(ROP_IS_CRYSTAL, check_reg, dst, 0, line);
1838
7
        size_t ok_jump = emit_jump_placeholder(ROP_JMPTRUE, check_reg, line);
1839
7
        free_reg(check_reg);
1840
        /* Not crystal → throw error */
1841
7
        {
1842
7
            uint8_t err_reg2 = alloc_reg();
1843
7
            uint16_t err_ki = add_constant(value_string("anneal requires a crystal value"));
1844
7
            emit_ABx(ROP_LOADK, err_reg2, err_ki, line);
1845
7
            emit_ABC(ROP_THROW, err_reg2, 0, 0, line);
1846
7
            free_reg(err_reg2);
1847
7
        }
1848
7
        patch_jump(ok_jump);
1849
1850
        /* 2. Wrap in try/catch for error prefix */
1851
7
        uint8_t err_reg = alloc_reg();
1852
7
        size_t handler = emit_jump_placeholder(ROP_PUSH_HANDLER, err_reg, line);
1853
1854
        /* 3. Call closure with thawed value */
1855
7
        uint8_t fn_reg = alloc_reg();
1856
7
        uint8_t arg_reg = alloc_reg(); /* must be fn_reg+1 for CALL convention */
1857
7
        compile_expr(e->as.anneal.closure, fn_reg, line);
1858
7
        emit_ABC(ROP_THAW, arg_reg, dst, 0, line);
1859
7
        emit_ABC(ROP_CALL, fn_reg, 1, 1, line);
1860
        /* Result is in fn_reg; move to dst */
1861
7
        emit_ABC(ROP_MOVE, dst, fn_reg, 0, line);
1862
7
        free_reg(arg_reg);
1863
7
        free_reg(fn_reg);
1864
1865
        /* 4. Write-back result to variable register, then freeze */
1866
7
        if (e->as.anneal.expr->tag == EXPR_IDENT) {
1867
6
            const char *name = e->as.anneal.expr->as.str_val;
1868
6
            uint16_t name_ki = add_constant(value_string(name));
1869
6
            int slot = resolve_local(rc, name);
1870
6
            if (slot >= 0) {
1871
                /* Write closure result back to the variable's register */
1872
6
                emit_ABC(ROP_MOVE, local_reg(slot), dst, 0, line);
1873
6
                emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 0, local_reg(slot), line);
1874
6
            } else {
1875
0
                int uv = resolve_upvalue(rc, name);
1876
0
                if (uv >= 0) {
1877
0
                    emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 1, (uint8_t)uv, line);
1878
0
                } else {
1879
0
                    emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), 2, 0, line);
1880
0
                }
1881
0
            }
1882
6
        } else {
1883
1
            emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1884
1
        }
1885
1886
        /* 5. End try — pop handler, jump past catch */
1887
7
        emit_ABC(ROP_POP_HANDLER, 0, 0, 0, line);
1888
7
        size_t past_catch = emit_jmp_placeholder(line);
1889
1890
        /* 6. Catch: wrap with "anneal failed: " and rethrow */
1891
7
        patch_jump(handler);
1892
7
        {
1893
7
            uint8_t prefix_reg = alloc_reg();
1894
7
            uint16_t prefix_ki = add_constant(value_string("anneal failed: "));
1895
7
            emit_ABx(ROP_LOADK, prefix_reg, prefix_ki, line);
1896
7
            emit_ABC(ROP_CONCAT, err_reg, prefix_reg, err_reg, line);
1897
7
            free_reg(prefix_reg);
1898
7
            emit_ABC(ROP_THROW, err_reg, 0, 0, line);
1899
7
        }
1900
7
        free_reg(err_reg);
1901
7
        patch_jmp(past_catch);
1902
7
        break;
1903
548
    }
1904
1905
4
    case EXPR_CRYSTALLIZE: {
1906
        /* crystallize(x) { body } — freeze x, run body, thaw x back (if wasn't crystal) */
1907
4
        if (e->as.crystallize.expr->tag == EXPR_IDENT) {
1908
4
            const char *name = e->as.crystallize.expr->as.str_val;
1909
4
            uint16_t name_ki = add_constant(value_string(name));
1910
4
            int slot = resolve_local(rc, name);
1911
4
            uint8_t loc_type, slot_val;
1912
4
            if (slot >= 0) { loc_type = 0; slot_val = local_reg(slot); }
1913
0
            else {
1914
0
                int uv = resolve_upvalue(rc, name);
1915
0
                if (uv >= 0) { loc_type = 1; slot_val = (uint8_t)uv; }
1916
0
                else { loc_type = 2; slot_val = 0; }
1917
0
            }
1918
            /* Check if already crystal before freezing */
1919
4
            compile_expr(e->as.crystallize.expr, dst, line);
1920
4
            uint8_t was_crystal = alloc_reg();
1921
4
            emit_ABC(ROP_IS_CRYSTAL, was_crystal, dst, 0, line);
1922
            /* Freeze the variable */
1923
4
            compile_expr(e->as.crystallize.expr, dst, line);
1924
4
            emit_ABC(ROP_FREEZE_VAR, (uint8_t)(name_ki & 0xFF), loc_type, slot_val, line);
1925
            /* Execute body */
1926
4
            begin_scope();
1927
10
            for (size_t i = 0; i < e->as.crystallize.body_count; i++)
1928
6
                compile_stmt(e->as.crystallize.body[i]);
1929
4
            end_scope(line);
1930
            /* Conditional thaw: only if was NOT already crystal */
1931
4
            size_t skip_thaw = emit_jump_placeholder(ROP_JMPTRUE, was_crystal, line);
1932
            /* was not crystal → thaw back */
1933
4
            compile_expr(e->as.crystallize.expr, dst, line);
1934
4
            emit_ABC(ROP_THAW_VAR, (uint8_t)(name_ki & 0xFF), loc_type, slot_val, line);
1935
4
            patch_jump(skip_thaw);
1936
4
            free_reg(was_crystal);
1937
4
        } else {
1938
            /* Non-ident: just freeze value, run body, refreeze */
1939
0
            compile_expr(e->as.crystallize.expr, dst, line);
1940
0
            emit_ABC(ROP_FREEZE, dst, dst, 0, line);
1941
0
            begin_scope();
1942
0
            for (size_t i = 0; i < e->as.crystallize.body_count; i++)
1943
0
                compile_stmt(e->as.crystallize.body[i]);
1944
0
            end_scope(line);
1945
0
        }
1946
4
        break;
1947
548
    }
1948
1949
1
    case EXPR_SPAWN: {
1950
        /* Spawn outside scope — compile as ROP_SCOPE with 0 spawns (sub-chunk so return works) */
1951
1
        RegChunk *spawn_ch = compile_reg_sub_body(
1952
1
            e->as.block.stmts, e->as.block.count, line);
1953
1
        uint8_t body_idx = (uint8_t)add_regchunk_constant(spawn_ch);
1954
1955
        /* Emit: ROP_SCOPE dst, then data word with spawn_count=0, sync_idx=body_idx */
1956
1
        emit_ABC(ROP_SCOPE, dst, 0, 0, line);
1957
1
        emit_ABC(0, 0, body_idx, 0, line);  /* spawn_count=0, sync_idx=body_idx */
1958
1
        break;
1959
548
    }
1960
1961
3
    case EXPR_SCOPE: {
1962
3
        size_t total = e->as.block.count;
1963
1964
        /* Count spawns and collect non-spawn stmts */
1965
3
        size_t spawn_count = 0;
1966
9
        for (size_t i = 0; i < total; i++) {
1967
6
            Stmt *s = e->as.block.stmts[i];
1968
6
            if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN)
1969
3
                spawn_count++;
1970
6
        }
1971
3
        size_t sync_count = total - spawn_count;
1972
1973
        /* Compile sync body (all non-spawn stmts together) */
1974
3
        uint8_t sync_idx = 0xFF;
1975
3
        if (sync_count > 0) {
1976
1
            Stmt **sync_stmts = malloc(sync_count * sizeof(Stmt *));
1977
1
            size_t si = 0;
1978
4
            for (size_t i = 0; i < total; i++) {
1979
3
                Stmt *s = e->as.block.stmts[i];
1980
3
                if (!(s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN))
1981
3
                    sync_stmts[si++] = s;
1982
3
            }
1983
1
            RegChunk *sync_ch = compile_reg_sub_body(sync_stmts, sync_count, line);
1984
1
            sync_idx = (uint8_t)add_regchunk_constant(sync_ch);
1985
1
            free(sync_stmts);
1986
1
        }
1987
1988
        /* Compile each spawn body */
1989
3
        uint8_t *spawn_indices = NULL;
1990
3
        if (spawn_count > 0)
1991
2
            spawn_indices = malloc(spawn_count * sizeof(uint8_t));
1992
3
        size_t spi = 0;
1993
9
        for (size_t i = 0; i < total; i++) {
1994
6
            Stmt *s = e->as.block.stmts[i];
1995
6
            if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN) {
1996
3
                Expr *spawn_expr = s->as.expr;
1997
3
                RegChunk *spawn_ch = compile_reg_sub_body(
1998
3
                    spawn_expr->as.block.stmts,
1999
3
                    spawn_expr->as.block.count, line);
2000
3
                spawn_indices[spi++] = (uint8_t)add_regchunk_constant(spawn_ch);
2001
3
            }
2002
6
        }
2003
2004
        /* Emit: ROP_SCOPE dst, then data words with spawn_count, sync_idx, spawn_indices */
2005
3
        emit_ABC(ROP_SCOPE, dst, 0, 0, line);
2006
        /* Data word 1: spawn_count in A, sync_idx in B */
2007
3
        emit_ABC(0, (uint8_t)spawn_count, sync_idx, 0, line);
2008
        /* Spawn indices packed 3 per data word */
2009
5
        for (size_t i = 0; i < spawn_count; i += 3) {
2010
2
            uint8_t a = spawn_indices[i];
2011
2
            uint8_t b = (i + 1 < spawn_count) ? spawn_indices[i + 1] : 0;
2012
2
            uint8_t c = (i + 2 < spawn_count) ? spawn_indices[i + 2] : 0;
2013
2
            emit_ABC(0, a, b, c, line);
2014
2
        }
2015
3
        free(spawn_indices);
2016
3
        break;
2017
548
    }
2018
2019
5
    case EXPR_SELECT: {
2020
5
        size_t arm_count = e->as.select_expr.arm_count;
2021
5
        SelectArm *arms = e->as.select_expr.arms;
2022
2023
        /* Emit: ROP_SELECT dst */
2024
5
        emit_ABC(ROP_SELECT, dst, 0, 0, line);
2025
        /* Data word 1: arm_count in A */
2026
5
        emit_ABC(0, (uint8_t)arm_count, 0, 0, line);
2027
2028
14
        for (size_t i = 0; i < arm_count; i++) {
2029
9
            uint8_t flags = 0;
2030
9
            if (arms[i].is_default) flags |= 0x01;
2031
9
            if (arms[i].is_timeout) flags |= 0x02;
2032
9
            if (arms[i].binding_name) flags |= 0x04;
2033
2034
            /* Channel or timeout expression chunk index */
2035
9
            uint8_t chan_idx = 0xFF;
2036
9
            if (arms[i].is_default) {
2037
3
                chan_idx = 0xFF;
2038
6
            } else if (arms[i].is_timeout) {
2039
0
                RegChunk *to_ch = compile_reg_sub_expr(arms[i].timeout_expr, line);
2040
0
                chan_idx = (uint8_t)add_regchunk_constant(to_ch);
2041
6
            } else {
2042
6
                RegChunk *ch_ch = compile_reg_sub_expr(arms[i].channel_expr, line);
2043
6
                chan_idx = (uint8_t)add_regchunk_constant(ch_ch);
2044
6
            }
2045
2046
            /* Body chunk */
2047
9
            RegChunk *body_ch = compile_reg_sub_body(arms[i].body, arms[i].body_count, line);
2048
9
            uint8_t body_idx = (uint8_t)add_regchunk_constant(body_ch);
2049
2050
            /* Binding name constant index */
2051
9
            uint8_t binding_idx = 0xFF;
2052
9
            if (arms[i].binding_name)
2053
6
                binding_idx = (uint8_t)add_constant(value_string(arms[i].binding_name));
2054
2055
            /* Emit arm data: flags, chan_idx, body_idx, binding_idx */
2056
9
            emit_ABC(0, flags, chan_idx, body_idx, line);
2057
9
            emit_ABC(0, binding_idx, 0, 0, line);
2058
9
        }
2059
5
        break;
2060
548
    }
2061
2062
0
    default:
2063
        /* Unsupported expression — silently produce unit rather than error */
2064
0
        if (!rc_error) {
2065
0
            char buf[128];
2066
0
            snprintf(buf, sizeof(buf), "unsupported expression type %d in regvm compiler", e->tag);
2067
0
            rc_error = strdup(buf);
2068
0
        }
2069
0
        break;
2070
45.9k
    }
2071
45.9k
}
2072
2073
/* ── Statement compilation ── */
2074
2075
12.4k
static void compile_stmt(const Stmt *s) {
2076
12.4k
    if (rc_error) return;
2077
12.4k
    int line = s->line;
2078
2079
12.4k
    switch (s->tag) {
2080
3.34k
    case STMT_EXPR: {
2081
        /* Compile expression, result goes into a temporary that we free */
2082
3.34k
        uint8_t tmp = alloc_reg();
2083
3.34k
        compile_expr(s->as.expr, tmp, line);
2084
3.34k
        free_reg(tmp);
2085
3.34k
        break;
2086
0
    }
2087
2088
4.43k
    case STMT_BINDING: {
2089
4.43k
        if (rc->scope_depth > 0) {
2090
            /* Local variable: allocate a register */
2091
4.36k
            uint8_t reg = add_local(s->as.binding.name);
2092
4.36k
            if (s->as.binding.value)
2093
4.36k
                compile_expr(s->as.binding.value, reg, line);
2094
0
            else
2095
0
                emit_ABC(ROP_LOADNIL, reg, 0, 0, line);
2096
2097
4.36k
            if (s->as.binding.phase == PHASE_FLUID)
2098
1.09k
                emit_ABC(ROP_MARKFLUID, reg, 0, 0, line);
2099
3.26k
            else if (s->as.binding.phase == PHASE_CRYSTAL)
2100
15
                emit_ABC(ROP_FREEZE, reg, reg, 0, line);
2101
4.36k
        } else {
2102
            /* Global variable */
2103
71
            uint8_t tmp = alloc_reg();
2104
71
            if (s->as.binding.value)
2105
71
                compile_expr(s->as.binding.value, tmp, line);
2106
0
            else
2107
0
                emit_ABC(ROP_LOADNIL, tmp, 0, 0, line);
2108
2109
71
            if (s->as.binding.phase == PHASE_FLUID)
2110
4
                emit_ABC(ROP_MARKFLUID, tmp, 0, 0, line);
2111
67
            else if (s->as.binding.phase == PHASE_CRYSTAL)
2112
0
                emit_ABC(ROP_FREEZE, tmp, tmp, 0, line);
2113
2114
71
            uint16_t name_ki = add_constant(value_string(s->as.binding.name));
2115
71
            emit_ABx(ROP_DEFINEGLOBAL, tmp, name_ki, line);
2116
71
            free_reg(tmp);
2117
71
        }
2118
4.43k
        break;
2119
0
    }
2120
2121
917
    case STMT_ASSIGN: {
2122
917
        if (s->as.assign.target->tag == EXPR_IDENT) {
2123
868
            const char *name = s->as.assign.target->as.str_val;
2124
868
            int local = resolve_local(rc, name);
2125
868
            if (local >= 0) {
2126
863
                compile_expr(s->as.assign.value, local_reg(local), line);
2127
863
            } else {
2128
5
                int upvalue = resolve_upvalue(rc, name);
2129
5
                if (upvalue >= 0) {
2130
0
                    uint8_t tmp = alloc_reg();
2131
0
                    compile_expr(s->as.assign.value, tmp, line);
2132
0
                    emit_ABC(ROP_SETUPVALUE, tmp, (uint8_t)upvalue, 0, line);
2133
0
                    free_reg(tmp);
2134
5
                } else {
2135
5
                    uint8_t tmp = alloc_reg();
2136
5
                    compile_expr(s->as.assign.value, tmp, line);
2137
5
                    uint16_t name_ki = add_constant(value_string(name));
2138
5
                    emit_ABx(ROP_SETGLOBAL, tmp, name_ki, line);
2139
5
                    free_reg(tmp);
2140
5
                }
2141
5
            }
2142
868
        } else if (s->as.assign.target->tag == EXPR_FIELD_ACCESS) {
2143
14
            Expr *target = s->as.assign.target;
2144
14
            uint8_t val_reg = alloc_reg();
2145
14
            compile_expr(s->as.assign.value, val_reg, line);
2146
2147
            /* Get the object register */
2148
14
            uint8_t obj_reg;
2149
14
            bool obj_is_local = false;
2150
14
            int local = -1;
2151
14
            if (target->as.field_access.object->tag == EXPR_IDENT) {
2152
12
                local = resolve_local(rc, target->as.field_access.object->as.str_val);
2153
12
                if (local >= 0) {
2154
12
                    obj_reg = local_reg(local);
2155
12
                    obj_is_local = true;
2156
12
                } else {
2157
0
                    obj_reg = alloc_reg();
2158
0
                    compile_expr(target->as.field_access.object, obj_reg, line);
2159
0
                }
2160
12
            } else {
2161
2
                obj_reg = alloc_reg();
2162
2
                compile_expr(target->as.field_access.object, obj_reg, line);
2163
2
            }
2164
2165
14
            uint16_t field_ki = add_constant(value_string(target->as.field_access.field));
2166
14
            emit_ABC(ROP_SETFIELD, obj_reg, (uint8_t)(field_ki & 0xFF), val_reg, line);
2167
2168
            /* Write back if it's a global/upvalue */
2169
14
            if (!obj_is_local && target->as.field_access.object->tag == EXPR_IDENT) {
2170
0
                const char *name = target->as.field_access.object->as.str_val;
2171
0
                int uv = resolve_upvalue(rc, name);
2172
0
                if (uv >= 0) {
2173
0
                    emit_ABC(ROP_SETUPVALUE, obj_reg, (uint8_t)uv, 0, line);
2174
0
                } else {
2175
0
                    uint16_t nki = add_constant(value_string(name));
2176
0
                    emit_ABx(ROP_SETGLOBAL, obj_reg, nki, line);
2177
0
                }
2178
0
                free_reg(obj_reg);
2179
0
            }
2180
14
            free_reg(val_reg);
2181
35
        } else if (s->as.assign.target->tag == EXPR_INDEX) {
2182
35
            Expr *target = s->as.assign.target;
2183
35
            uint8_t val_reg = alloc_reg();
2184
35
            compile_expr(s->as.assign.value, val_reg, line);
2185
2186
35
            uint8_t obj_reg;
2187
35
            bool obj_is_local = false;
2188
35
            if (target->as.index.object->tag == EXPR_IDENT) {
2189
35
                int local = resolve_local(rc, target->as.index.object->as.str_val);
2190
35
                if (local >= 0) {
2191
27
                    obj_reg = local_reg(local);
2192
27
                    obj_is_local = true;
2193
27
                } else {
2194
8
                    obj_reg = alloc_reg();
2195
8
                    compile_expr(target->as.index.object, obj_reg, line);
2196
8
                }
2197
35
            } else {
2198
0
                obj_reg = alloc_reg();
2199
0
                compile_expr(target->as.index.object, obj_reg, line);
2200
0
            }
2201
2202
35
            uint8_t idx_reg = alloc_reg();
2203
35
            compile_expr(target->as.index.index, idx_reg, line);
2204
35
            emit_ABC(obj_is_local ? ROP_SETINDEX_LOCAL : ROP_SETINDEX,
2205
35
                     obj_reg, idx_reg, val_reg, line);
2206
2207
35
            if (!obj_is_local && target->as.index.object->tag == EXPR_IDENT) {
2208
8
                const char *name = target->as.index.object->as.str_val;
2209
8
                int uv = resolve_upvalue(rc, name);
2210
8
                if (uv >= 0) {
2211
0
                    emit_ABC(ROP_SETUPVALUE, obj_reg, (uint8_t)uv, 0, line);
2212
8
                } else {
2213
8
                    uint16_t nki = add_constant(value_string(name));
2214
8
                    emit_ABx(ROP_SETGLOBAL, obj_reg, nki, line);
2215
8
                }
2216
8
                free_reg(obj_reg);
2217
8
            }
2218
35
            free_reg(idx_reg);
2219
35
            free_reg(val_reg);
2220
35
        }
2221
917
        break;
2222
0
    }
2223
2224
3.02k
    case STMT_RETURN: {
2225
3.02k
        uint8_t ret_reg = alloc_reg();
2226
3.02k
        if (s->as.return_expr)
2227
3.02k
            compile_expr(s->as.return_expr, ret_reg, line);
2228
1
        else
2229
1
            emit_ABC(ROP_LOADUNIT, ret_reg, 0, 0, line);
2230
3.02k
        emit_postconditions(ret_reg, line);
2231
3.02k
        emit_return(ret_reg, line);
2232
3.02k
        free_reg(ret_reg);
2233
3.02k
        break;
2234
0
    }
2235
2236
120
    case STMT_WHILE: {
2237
120
        size_t saved_break_count = rc->break_count;
2238
120
        size_t saved_loop_start = rc->loop_start;
2239
120
        int saved_loop_is_for = rc->loop_is_for;
2240
120
        int saved_loop_depth = rc->loop_depth;
2241
120
        uint8_t saved_break_reg = rc->loop_break_reg;
2242
120
        uint8_t saved_continue_reg = rc->loop_continue_reg;
2243
120
        size_t saved_break_lc = rc->loop_break_local_count;
2244
120
        size_t saved_continue_lc = rc->loop_continue_local_count;
2245
2246
120
        rc->loop_break_local_count = rc->local_count;
2247
120
        rc->loop_continue_local_count = rc->local_count;
2248
120
        rc->loop_break_reg = rc->next_reg;
2249
120
        rc->loop_continue_reg = rc->next_reg;
2250
120
        rc->loop_start = current_chunk()->code_len;
2251
120
        rc->loop_is_for = 0;
2252
120
        rc->loop_depth++;
2253
2254
120
        uint8_t cond_reg = alloc_reg();
2255
120
        compile_expr(s->as.while_loop.cond, cond_reg, line);
2256
120
        size_t exit_jump = emit_jump_placeholder(ROP_JMPFALSE, cond_reg, line);
2257
120
        free_reg(cond_reg);
2258
2259
120
        begin_scope();
2260
517
        for (size_t i = 0; i < s->as.while_loop.body_count; i++)
2261
397
            compile_stmt(s->as.while_loop.body[i]);
2262
120
        end_scope(line);
2263
2264
120
        emit_loop_back(rc->loop_start, line);
2265
120
        patch_jump(exit_jump);
2266
2267
131
        for (size_t i = saved_break_count; i < rc->break_count; i++)
2268
11
            patch_jmp(rc->break_patches[i]);
2269
120
        rc->break_count = saved_break_count;
2270
120
        rc->loop_start = saved_loop_start;
2271
120
        rc->loop_is_for = saved_loop_is_for;
2272
120
        rc->loop_depth = saved_loop_depth;
2273
120
        rc->loop_break_reg = saved_break_reg;
2274
120
        rc->loop_continue_reg = saved_continue_reg;
2275
120
        rc->loop_break_local_count = saved_break_lc;
2276
120
        rc->loop_continue_local_count = saved_continue_lc;
2277
120
        break;
2278
0
    }
2279
2280
116
    case STMT_LOOP: {
2281
116
        size_t saved_break_count = rc->break_count;
2282
116
        size_t saved_loop_start = rc->loop_start;
2283
116
        int saved_loop_is_for = rc->loop_is_for;
2284
116
        int saved_loop_depth = rc->loop_depth;
2285
116
        uint8_t saved_break_reg = rc->loop_break_reg;
2286
116
        uint8_t saved_continue_reg = rc->loop_continue_reg;
2287
116
        size_t saved_break_lc = rc->loop_break_local_count;
2288
116
        size_t saved_continue_lc = rc->loop_continue_local_count;
2289
2290
116
        rc->loop_break_local_count = rc->local_count;
2291
116
        rc->loop_continue_local_count = rc->local_count;
2292
116
        rc->loop_break_reg = rc->next_reg;
2293
116
        rc->loop_continue_reg = rc->next_reg;
2294
116
        rc->loop_start = current_chunk()->code_len;
2295
116
        rc->loop_is_for = 0;
2296
116
        rc->loop_depth++;
2297
2298
116
        begin_scope();
2299
580
        for (size_t i = 0; i < s->as.loop.body_count; i++)
2300
464
            compile_stmt(s->as.loop.body[i]);
2301
116
        end_scope(line);
2302
2303
116
        emit_loop_back(rc->loop_start, line);
2304
2305
203
        for (size_t i = saved_break_count; i < rc->break_count; i++)
2306
87
            patch_jmp(rc->break_patches[i]);
2307
116
        rc->break_count = saved_break_count;
2308
116
        rc->loop_start = saved_loop_start;
2309
116
        rc->loop_is_for = saved_loop_is_for;
2310
116
        rc->loop_depth = saved_loop_depth;
2311
116
        rc->loop_break_reg = saved_break_reg;
2312
116
        rc->loop_continue_reg = saved_continue_reg;
2313
116
        rc->loop_break_local_count = saved_break_lc;
2314
116
        rc->loop_continue_local_count = saved_continue_lc;
2315
116
        break;
2316
0
    }
2317
2318
282
    case STMT_FOR: {
2319
282
        size_t saved_break_count = rc->break_count;
2320
282
        size_t saved_continue_count = rc->continue_count;
2321
282
        size_t saved_loop_start = rc->loop_start;
2322
282
        int saved_loop_is_for = rc->loop_is_for;
2323
282
        int saved_loop_depth = rc->loop_depth;
2324
282
        uint8_t saved_break_reg = rc->loop_break_reg;
2325
282
        uint8_t saved_continue_reg = rc->loop_continue_reg;
2326
282
        size_t saved_break_lc = rc->loop_break_local_count;
2327
282
        size_t saved_continue_lc = rc->loop_continue_local_count;
2328
2329
282
        rc->loop_break_local_count = rc->local_count;
2330
2331
282
        begin_scope();
2332
2333
        /* Compile iterator and init */
2334
282
        uint8_t iter_reg = alloc_reg();  /* collection/range */
2335
282
        compile_expr(s->as.for_loop.iter, iter_reg, line);
2336
282
        uint8_t idx_reg = alloc_reg();   /* index counter */
2337
282
        emit_ABC(ROP_ITERINIT, iter_reg, iter_reg, 0, line);
2338
282
        emit_AsBx(ROP_LOADI, idx_reg, 0, line);  /* idx = 0 */
2339
2340
        /* Hidden locals for iter state */
2341
282
        {
2342
282
            RegLocal *l1 = &rc->locals[rc->local_count++];
2343
282
            l1->name = strdup(""); l1->depth = rc->scope_depth;
2344
282
            l1->is_captured = false; l1->reg = iter_reg;
2345
282
            RegLocal *l2 = &rc->locals[rc->local_count++];
2346
282
            l2->name = strdup(""); l2->depth = rc->scope_depth;
2347
282
            l2->is_captured = false; l2->reg = idx_reg;
2348
282
        }
2349
2350
282
        rc->loop_continue_local_count = rc->local_count;
2351
282
        rc->loop_continue_reg = rc->next_reg;
2352
282
        rc->loop_start = current_chunk()->code_len;
2353
282
        rc->loop_is_for = 1;
2354
282
        rc->loop_depth++;
2355
2356
        /* Loop variable */
2357
282
        uint8_t var_reg = add_local(s->as.for_loop.var);
2358
2359
        /* Emit length check: compare idx against collection length */
2360
282
        uint8_t len_reg = alloc_reg();
2361
282
        emit_ABC(ROP_LEN, len_reg, iter_reg, 0, line);
2362
282
        uint8_t cmp_reg = alloc_reg();
2363
282
        emit_ABC(ROP_LT_INT, cmp_reg, idx_reg, len_reg, line);
2364
282
        size_t exit_jmp = emit_jump_placeholder(ROP_JMPFALSE, cmp_reg, line);
2365
282
        free_reg(cmp_reg);
2366
282
        free_reg(len_reg);
2367
2368
        /* Get current element */
2369
282
        emit_ABC(ROP_ITERNEXT, var_reg, iter_reg, idx_reg, line);
2370
2371
282
        rc->loop_break_reg = rc->next_reg;
2372
2373
        /* Compile body */
2374
282
        begin_scope();
2375
924
        for (size_t i = 0; i < s->as.for_loop.body_count; i++)
2376
642
            compile_stmt(s->as.for_loop.body[i]);
2377
282
        end_scope(line);
2378
2379
        /* Increment index — continue forward-jumps land here.
2380
         * Patch continue jumps before emitting INC so code_len == inc addr. */
2381
283
        for (size_t i = saved_continue_count; i < rc->continue_count; i++)
2382
1
            patch_jmp(rc->continue_patches[i]);
2383
282
        rc->continue_count = saved_continue_count;
2384
282
        emit_ABC(ROP_INC_REG, idx_reg, 0, 0, line);
2385
2386
282
        emit_loop_back(rc->loop_start, line);
2387
282
        patch_jump(exit_jmp);
2388
2389
282
        end_scope(line);
2390
2391
282
        for (size_t i = saved_break_count; i < rc->break_count; i++)
2392
0
            patch_jmp(rc->break_patches[i]);
2393
282
        rc->break_count = saved_break_count;
2394
282
        rc->loop_start = saved_loop_start;
2395
282
        rc->loop_is_for = saved_loop_is_for;
2396
282
        rc->loop_depth = saved_loop_depth;
2397
282
        rc->loop_break_reg = saved_break_reg;
2398
282
        rc->loop_continue_reg = saved_continue_reg;
2399
282
        rc->loop_break_local_count = saved_break_lc;
2400
282
        rc->loop_continue_local_count = saved_continue_lc;
2401
282
        break;
2402
0
    }
2403
2404
98
    case STMT_BREAK: {
2405
98
        if (rc->loop_depth == 0) {
2406
0
            rc_error = strdup("break outside of loop");
2407
0
            return;
2408
0
        }
2409
98
        size_t jmp = emit_jmp_placeholder(line);
2410
98
        push_break_patch(jmp);
2411
98
        break;
2412
98
    }
2413
2414
41
    case STMT_CONTINUE: {
2415
41
        if (rc->loop_depth == 0) {
2416
0
            rc_error = strdup("continue outside of loop");
2417
0
            return;
2418
0
        }
2419
41
        if (rc->loop_is_for) {
2420
            /* For-loop: forward jump to increment, patched later */
2421
1
            size_t jmp = emit_jmp_placeholder(line);
2422
1
            push_continue_patch(jmp);
2423
40
        } else {
2424
            /* While/loop: backward jump to condition check */
2425
40
            emit_loop_back(rc->loop_start, line);
2426
40
        }
2427
41
        break;
2428
41
    }
2429
2430
8
    case STMT_DESTRUCTURE: {
2431
        /* Compile source expression */
2432
8
        uint8_t src_reg = alloc_reg();
2433
8
        compile_expr(s->as.destructure.value, src_reg, line);
2434
2435
8
        if (s->as.destructure.kind == DESTRUCT_ARRAY) {
2436
            /* Array destructuring: let [a, b, c] = expr */
2437
18
            for (size_t i = 0; i < s->as.destructure.name_count; i++) {
2438
12
                if (s->as.destructure.names[i][0] == '\0') continue; /* skip blank */
2439
12
                uint8_t idx_reg = alloc_reg();
2440
12
                emit_AsBx(ROP_LOADI, idx_reg, (int16_t)i, line);
2441
12
                if (rc->scope_depth > 0) {
2442
12
                    uint8_t var_reg = add_local(s->as.destructure.names[i]);
2443
12
                    emit_ABC(ROP_GETINDEX, var_reg, src_reg, idx_reg, line);
2444
12
                } else {
2445
0
                    uint8_t val_reg = alloc_reg();
2446
0
                    emit_ABC(ROP_GETINDEX, val_reg, src_reg, idx_reg, line);
2447
0
                    uint16_t nki = add_constant(value_string(s->as.destructure.names[i]));
2448
0
                    emit_ABx(ROP_DEFINEGLOBAL, val_reg, nki, line);
2449
0
                    free_reg(val_reg);
2450
0
                }
2451
12
                free_reg(idx_reg);
2452
12
            }
2453
            /* Handle ...rest — slice tail of array using BUILDRANGE + GETINDEX */
2454
6
            if (s->as.destructure.rest_name) {
2455
3
                uint8_t rest_start = alloc_reg();
2456
3
                emit_AsBx(ROP_LOADI, rest_start, (int16_t)s->as.destructure.name_count, line);
2457
3
                uint8_t rest_len = alloc_reg();
2458
3
                emit_ABC(ROP_LEN, rest_len, src_reg, 0, line);
2459
3
                uint8_t range_reg = alloc_reg();
2460
3
                emit_ABC(ROP_BUILDRANGE, range_reg, rest_start, rest_len, line);
2461
3
                if (rc->scope_depth > 0) {
2462
3
                    uint8_t var_reg = add_local(s->as.destructure.rest_name);
2463
3
                    emit_ABC(ROP_GETINDEX, var_reg, src_reg, range_reg, line);
2464
3
                } else {
2465
0
                    uint8_t val_reg = alloc_reg();
2466
0
                    emit_ABC(ROP_GETINDEX, val_reg, src_reg, range_reg, line);
2467
0
                    uint16_t nki = add_constant(value_string(s->as.destructure.rest_name));
2468
0
                    emit_ABx(ROP_DEFINEGLOBAL, val_reg, nki, line);
2469
0
                    free_reg(val_reg);
2470
0
                }
2471
3
                free_reg(range_reg);
2472
3
                free_reg(rest_len);
2473
3
                free_reg(rest_start);
2474
3
            }
2475
6
        } else {
2476
            /* Struct destructuring: let { x, y } = expr */
2477
6
            for (size_t i = 0; i < s->as.destructure.name_count; i++) {
2478
4
                uint16_t field_ki = add_constant(value_string(s->as.destructure.names[i]));
2479
4
                if (rc->scope_depth > 0) {
2480
4
                    uint8_t var_reg = add_local(s->as.destructure.names[i]);
2481
4
                    emit_ABC(ROP_GETFIELD, var_reg, src_reg, (uint8_t)field_ki, line);
2482
4
                } else {
2483
0
                    uint8_t val_reg = alloc_reg();
2484
0
                    emit_ABC(ROP_GETFIELD, val_reg, src_reg, (uint8_t)field_ki, line);
2485
0
                    uint16_t nki = add_constant(value_string(s->as.destructure.names[i]));
2486
0
                    emit_ABx(ROP_DEFINEGLOBAL, val_reg, nki, line);
2487
0
                    free_reg(val_reg);
2488
0
                }
2489
4
            }
2490
2
        }
2491
8
        free_reg(src_reg);
2492
8
        break;
2493
41
    }
2494
2495
6
    case STMT_DEFER: {
2496
        /* DEFER_PUSH A=scope_depth, sBx=offset past defer body
2497
         * defer body (compile stmts + RETURN)
2498
         * ...continues after jump... */
2499
6
        size_t defer_jmp = emit(REG_ENCODE_AsBx(ROP_DEFER_PUSH, (uint8_t)rc->scope_depth, 0), line);
2500
2501
        /* Compile defer body */
2502
12
        for (size_t i = 0; i < s->as.defer.body_count; i++)
2503
6
            compile_stmt(s->as.defer.body[i]);
2504
        /* Emit HALT to end defer body (not RETURN — DEFER_RUN handles cleanup) */
2505
6
        emit_ABC(ROP_HALT, 0, 0, 0, line);
2506
2507
        /* Patch the jump to skip past defer body (AsBx format, preserves A=scope_depth) */
2508
6
        patch_jump(defer_jmp);
2509
6
        break;
2510
41
    }
2511
2512
94
    case STMT_IMPORT: {
2513
        /* ROP_IMPORT A=dst, Bx=path_ki */
2514
94
        uint8_t tmp = alloc_reg();
2515
94
        uint16_t path_ki = add_constant(value_string(s->as.import.module_path));
2516
94
        emit_ABx(ROP_IMPORT, tmp, path_ki, line);
2517
2518
94
        if (s->as.import.alias) {
2519
            /* import "path" as alias → define alias = module */
2520
77
            uint16_t alias_ki = add_constant(value_string(s->as.import.alias));
2521
77
            emit_ABx(ROP_DEFINEGLOBAL, tmp, alias_ki, line);
2522
77
        } else if (s->as.import.selective_names && s->as.import.selective_count > 0) {
2523
            /* import { a, b } from "path" → extract each name */
2524
37
            for (size_t i = 0; i < s->as.import.selective_count; i++) {
2525
20
                uint16_t field_ki = add_constant(value_string(s->as.import.selective_names[i]));
2526
20
                uint8_t val_reg = alloc_reg();
2527
20
                emit_ABC(ROP_GETFIELD, val_reg, tmp, (uint8_t)field_ki, line);
2528
                /* Check that the export exists (not nil) */
2529
20
                size_t ok = emit_jump_placeholder(ROP_JMPNOTNIL, val_reg, line);
2530
20
                {
2531
20
                    uint8_t err_reg = alloc_reg();
2532
20
                    char errmsg[512];
2533
20
                    snprintf(errmsg, sizeof(errmsg), "module '%s' does not export '%s'",
2534
20
                             s->as.import.module_path, s->as.import.selective_names[i]);
2535
20
                    uint16_t err_ki = add_constant(value_string(errmsg));
2536
20
                    emit_ABx(ROP_LOADK, err_reg, err_ki, line);
2537
20
                    emit_ABC(ROP_THROW, err_reg, 0, 0, line);
2538
20
                    free_reg(err_reg);
2539
20
                }
2540
20
                patch_jump(ok);
2541
20
                uint16_t name_ki = add_constant(value_string(s->as.import.selective_names[i]));
2542
20
                emit_ABx(ROP_DEFINEGLOBAL, val_reg, name_ki, line);
2543
20
                free_reg(val_reg);
2544
20
            }
2545
17
        }
2546
94
        free_reg(tmp);
2547
94
        break;
2548
41
    }
2549
2550
0
    default:
2551
0
        break;
2552
12.4k
    }
2553
12.4k
}
2554
2555
/* Emit ensure (postcondition) checks and return type checks before return */
2556
5.66k
static void emit_postconditions(uint8_t reg, int line) {
2557
    /* Return type check */
2558
5.66k
    if (rc && rc->return_type) {
2559
3.42k
        uint16_t type_ki = add_constant(value_string(rc->return_type));
2560
3.42k
        emit_ABx(ROP_CHECK_TYPE, reg, type_ki, line);
2561
        /* Second word: error message constant index */
2562
3.42k
        char err_msg[512];
2563
3.42k
        snprintf(err_msg, sizeof(err_msg), "return type expects %s, got %%s", rc->return_type);
2564
3.42k
        uint32_t err_ki = (uint32_t)add_constant(value_string(err_msg));
2565
3.42k
        emit(err_ki, line);
2566
3.42k
    }
2567
5.66k
    if (!rc || !rc->ensures) return;
2568
10
    for (size_t ei = 0; ei < rc->ensure_count; ei++) {
2569
5
        if (!rc->ensures[ei].is_ensure) continue;
2570
5
        uint8_t fn_reg = alloc_reg();
2571
5
        uint8_t arg_reg = alloc_reg(); /* Must be fn_reg+1 */
2572
5
        compile_expr(rc->ensures[ei].condition, fn_reg, line);
2573
5
        emit_ABC(ROP_MOVE, arg_reg, reg, 0, line);
2574
5
        emit_ABC(ROP_CALL, fn_reg, 1, 1, line);
2575
        /* fn_reg now has the result */
2576
5
        size_t ok = emit_jump_placeholder(ROP_JMPTRUE, fn_reg, line);
2577
5
        uint8_t err_reg = alloc_reg();
2578
5
        const char *user_msg = rc->ensures[ei].message ? rc->ensures[ei].message : "postcondition not met";
2579
5
        char full_msg[512];
2580
5
        snprintf(full_msg, sizeof(full_msg), "ensure failed in '%s': %s",
2581
5
                 rc->func_name ? rc->func_name : "<anonymous>", user_msg);
2582
5
        uint16_t msg_ki = add_constant(value_string(full_msg));
2583
5
        emit_ABx(ROP_LOADK, err_reg, msg_ki, line);
2584
5
        emit_ABC(ROP_THROW, err_reg, 0, 0, line);
2585
5
        free_reg(err_reg);
2586
5
        patch_jump(ok);
2587
5
        free_reg(arg_reg);
2588
5
        free_reg(fn_reg);
2589
5
    }
2590
5
}
2591
2592
/* ── Function body compilation ── */
2593
2594
static void compile_function_body(RegFuncType type, const char *name,
2595
                                  Param *params, size_t param_count,
2596
                                  Stmt **body, size_t body_count, int line,
2597
                                  ContractClause *contracts, size_t contract_count,
2598
2.63k
                                  TypeExpr *return_type) {
2599
2.63k
    RegCompiler func_comp;
2600
2.63k
    rc_init(&func_comp, rc, type);
2601
2.63k
    func_comp.func_name = name ? strdup(name) : NULL;
2602
2.63k
    func_comp.chunk->name = name ? strdup(name) : NULL;
2603
2.63k
    if (return_type && return_type->name)
2604
1.53k
        func_comp.return_type = return_type->name;
2605
2606
    /* Count non-variadic params for arity */
2607
2.63k
    size_t declared_arity = param_count;
2608
2.63k
    bool has_variadic = false;
2609
5.44k
    for (size_t i = 0; i < param_count; i++) {
2610
2.84k
        if (params[i].is_variadic) {
2611
32
            declared_arity = i;
2612
32
            has_variadic = true;
2613
32
            break;
2614
32
        }
2615
2.84k
    }
2616
2.63k
    func_comp.arity = (int)declared_arity;
2617
2618
    /* Add params as locals (they occupy R1..Rn) */
2619
5.48k
    for (size_t i = 0; i < param_count; i++)
2620
2.84k
        add_local(params[i].name);
2621
2622
    /* Emit default parameter initialization */
2623
5.48k
    for (size_t i = 0; i < param_count; i++) {
2624
2.84k
        if (params[i].default_value && !params[i].is_variadic) {
2625
            /* If param is nil, use default value */
2626
32
            uint8_t preg = local_reg((int)(i + 1)); /* +1 because slot 0 is reserved */
2627
32
            size_t skip = emit_jump_placeholder(ROP_JMPNOTNIL, preg, line);
2628
32
            compile_expr(params[i].default_value, preg, line);
2629
32
            patch_jump(skip);
2630
32
        }
2631
2.84k
    }
2632
2633
    /* Emit variadic collection if needed */
2634
2.63k
    if (has_variadic) {
2635
32
        uint8_t var_reg = local_reg((int)(declared_arity + 1));
2636
32
        emit_ABC(ROP_COLLECT_VARARGS, var_reg, (uint8_t)(declared_arity + 1), 0, line);
2637
32
    }
2638
2639
    /* Emit runtime parameter type checks */
2640
5.44k
    for (size_t i = 0; i < param_count; i++) {
2641
2.84k
        if (params[i].is_variadic) break;
2642
2.81k
        if (!params[i].ty.name || strcmp(params[i].ty.name, "Any") == 0 || strcmp(params[i].ty.name, "any") == 0) continue;
2643
2.02k
        uint8_t preg = local_reg((int)(i + 1));
2644
2.02k
        uint16_t type_ki = add_constant(value_string(params[i].ty.name));
2645
2.02k
        emit_ABx(ROP_CHECK_TYPE, preg, type_ki, line);
2646
2.02k
        char err_msg[512];
2647
2.02k
        snprintf(err_msg, sizeof(err_msg), "function '%s' parameter '%s' expects type %s, got %%s",
2648
2.02k
                 name ? name : "<anonymous>", params[i].name, params[i].ty.name);
2649
2.02k
        uint32_t err_ki = (uint32_t)add_constant(value_string(err_msg));
2650
2.02k
        emit(err_ki, line);
2651
2.02k
    }
2652
2653
    /* Emit require contracts (preconditions) */
2654
2.63k
    if (contracts) {
2655
14
        for (size_t ci = 0; ci < contract_count; ci++) {
2656
8
            if (contracts[ci].is_ensure) continue;
2657
6
            uint8_t cond_reg = alloc_reg();
2658
6
            compile_expr(contracts[ci].condition, cond_reg, line);
2659
6
            size_t ok = emit_jump_placeholder(ROP_JMPTRUE, cond_reg, line);
2660
            /* Condition is false — throw error */
2661
6
            const char *user_msg = contracts[ci].message ? contracts[ci].message : "condition not met";
2662
6
            char full_msg[512];
2663
6
            snprintf(full_msg, sizeof(full_msg), "require failed in '%s': %s",
2664
6
                     name ? name : "<anonymous>", user_msg);
2665
6
            uint8_t err_reg = alloc_reg();
2666
6
            uint16_t msg_ki = add_constant(value_string(full_msg));
2667
6
            emit_ABx(ROP_LOADK, err_reg, msg_ki, line);
2668
6
            emit_ABC(ROP_THROW, err_reg, 0, 0, line);
2669
6
            free_reg(err_reg);
2670
6
            patch_jump(ok);
2671
6
            free_reg(cond_reg);
2672
6
        }
2673
6
    }
2674
2675
    /* Set ensure contracts and return type on compiler state for emit_return */
2676
2.63k
    if (contracts) {
2677
6
        size_t ec = 0;
2678
14
        for (size_t ci = 0; ci < contract_count; ci++)
2679
8
            if (contracts[ci].is_ensure) ec++;
2680
6
        if (ec > 0) {
2681
2
            rc->ensures = contracts;
2682
2
            rc->ensure_count = contract_count; /* emit_return filters by is_ensure */
2683
2
        }
2684
6
    }
2685
2686
2687
    /* Compile body — last expression is implicit return value */
2688
2.63k
    if (body_count > 0 && body[body_count - 1]->tag == STMT_EXPR) {
2689
2.73k
        for (size_t i = 0; i + 1 < body_count; i++)
2690
1.64k
            compile_stmt(body[i]);
2691
1.09k
        uint8_t ret_reg = alloc_reg();
2692
1.09k
        compile_expr(body[body_count - 1]->as.expr, ret_reg, line);
2693
1.09k
        emit_postconditions(ret_reg, line);
2694
1.09k
        emit_return(ret_reg, line);
2695
1.09k
        free_reg(ret_reg);
2696
1.54k
    } else {
2697
6.33k
        for (size_t i = 0; i < body_count; i++)
2698
4.79k
            compile_stmt(body[i]);
2699
        /* Implicit unit return */
2700
1.54k
        uint8_t ret_reg = alloc_reg();
2701
1.54k
        emit_ABC(ROP_LOADUNIT, ret_reg, 0, 0, line);
2702
1.54k
        emit_postconditions(ret_reg, line);
2703
1.54k
        emit_return(ret_reg, line);
2704
1.54k
        free_reg(ret_reg);
2705
1.54k
    }
2706
2707
2.63k
    RegChunk *fn_chunk = func_comp.chunk;
2708
2709
    /* Store parameter phase constraints on chunk for runtime checking */
2710
2.63k
    {
2711
2.63k
        bool has_phase_constraints = false;
2712
5.43k
        for (size_t i = 0; i < param_count; i++) {
2713
2.84k
            if (params[i].is_variadic) break;
2714
2.81k
            if (params[i].ty.phase != PHASE_UNSPECIFIED) {
2715
13
                has_phase_constraints = true;
2716
13
                break;
2717
13
            }
2718
2.81k
        }
2719
2.63k
        if (has_phase_constraints) {
2720
13
            fn_chunk->param_phases = calloc(param_count, sizeof(uint8_t));
2721
13
            fn_chunk->param_phase_count = (int)param_count;
2722
26
            for (size_t i = 0; i < param_count; i++) {
2723
13
                if (params[i].is_variadic) break;
2724
13
                fn_chunk->param_phases[i] = (uint8_t)params[i].ty.phase;
2725
13
            }
2726
13
        }
2727
2.63k
    }
2728
2729
2.63k
    size_t upvalue_count = func_comp.upvalue_count;
2730
2.63k
    RegCompilerUpvalue *upvalues = NULL;
2731
2.63k
    if (upvalue_count > 0) {
2732
0
        upvalues = malloc(upvalue_count * sizeof(RegCompilerUpvalue));
2733
0
        memcpy(upvalues, func_comp.upvalues, upvalue_count * sizeof(RegCompilerUpvalue));
2734
0
    }
2735
2.63k
    rc_cleanup(&func_comp);
2736
2.63k
    rc = func_comp.enclosing;
2737
2738
    /* Store as a closure constant */
2739
2.63k
    LatValue fn_val;
2740
2.63k
    memset(&fn_val, 0, sizeof(fn_val));
2741
2.63k
    fn_val.type = VAL_CLOSURE;
2742
2.63k
    fn_val.phase = VTAG_UNPHASED;
2743
2.63k
    fn_val.region_id = upvalue_count;  /* encode upvalue count for runtime */
2744
2.63k
    fn_val.as.closure.body = NULL;
2745
2.63k
    fn_val.as.closure.native_fn = fn_chunk;
2746
2.63k
    fn_val.as.closure.param_count = param_count;
2747
2.63k
    if (param_count > 0 && params) {
2748
1.69k
        fn_val.as.closure.param_names = malloc(param_count * sizeof(char *));
2749
4.53k
        for (size_t pi = 0; pi < param_count; pi++)
2750
2.84k
            fn_val.as.closure.param_names[pi] = strdup(params[pi].name);
2751
1.69k
    }
2752
2.63k
    uint16_t fn_ki = add_constant(fn_val);
2753
2754
2.63k
    uint8_t dst = alloc_reg();
2755
2.63k
    emit_ABx(ROP_CLOSURE, dst, fn_ki, line);
2756
2.63k
    for (size_t i = 0; i < upvalue_count; i++) {
2757
0
        emit_ABC(ROP_MOVE, upvalues[i].is_local ? 1 : 0, upvalues[i].index, 0, line);
2758
0
    }
2759
2.63k
    free(upvalues);
2760
2761
    /* Define as global */
2762
2.63k
    uint16_t name_ki = add_constant(value_string(name));
2763
2.63k
    emit_ABx(ROP_DEFINEGLOBAL, dst, name_ki, line);
2764
2.63k
    free_reg(dst);
2765
2.63k
}
2766
2767
/* ── Top-level compilation ── */
2768
2769
844
RegChunk *reg_compile(const Program *prog, char **error) {
2770
844
    RegCompiler top;
2771
844
    rc_init(&top, NULL, REG_FUNC_SCRIPT);
2772
844
    *error = NULL;
2773
2774
2.02k
    for (size_t i = 0; i < prog->item_count; i++) {
2775
1.18k
        if (rc_error) break;
2776
2777
1.18k
        switch (prog->items[i].tag) {
2778
238
        case ITEM_STMT:
2779
238
            compile_stmt(prog->items[i].as.stmt);
2780
238
            break;
2781
2782
878
        case ITEM_FUNCTION: {
2783
878
            FnDecl *fn = &prog->items[i].as.fn_decl;
2784
878
            compile_function_body(REG_FUNC_FUNCTION, fn->name,
2785
878
                                  fn->params, fn->param_count,
2786
878
                                  fn->body, fn->body_count, 0,
2787
878
                                  fn->contracts, fn->contract_count,
2788
878
                                  fn->return_type);
2789
878
            break;
2790
0
        }
2791
2792
44
        case ITEM_STRUCT: {
2793
44
            StructDecl *sd = &prog->items[i].as.struct_decl;
2794
            /* Store struct metadata as "__struct_<name>" global */
2795
44
            LatValue *field_names = malloc(sd->field_count * sizeof(LatValue));
2796
128
            for (size_t j = 0; j < sd->field_count; j++)
2797
84
                field_names[j] = value_string(sd->fields[j].name);
2798
44
            LatValue arr = value_array(field_names, sd->field_count);
2799
44
            free(field_names);
2800
2801
44
            char meta_name[256];
2802
44
            snprintf(meta_name, sizeof(meta_name), "__struct_%s", sd->name);
2803
44
            uint8_t tmp = alloc_reg();
2804
44
            uint16_t arr_ki = add_constant(arr);
2805
44
            emit_ABx(ROP_LOADK, tmp, arr_ki, 0);
2806
44
            uint16_t name_ki = add_constant(value_string(meta_name));
2807
44
            emit_ABx(ROP_DEFINEGLOBAL, tmp, name_ki, 0);
2808
44
            free_reg(tmp);
2809
2810
            /* Alloy: emit per-field phase metadata if any field has a phase annotation */
2811
44
            {
2812
44
                bool has_phase_ann = false;
2813
122
                for (size_t j = 0; j < sd->field_count; j++) {
2814
81
                    if (sd->fields[j].ty.phase != PHASE_UNSPECIFIED) { has_phase_ann = true; break; }
2815
81
                }
2816
44
                if (has_phase_ann) {
2817
3
                    LatValue *phases = malloc(sd->field_count * sizeof(LatValue));
2818
9
                    for (size_t j = 0; j < sd->field_count; j++)
2819
6
                        phases[j] = value_int((int64_t)sd->fields[j].ty.phase);
2820
3
                    LatValue phase_arr = value_array(phases, sd->field_count);
2821
3
                    free(phases);
2822
3
                    char phase_meta[256];
2823
3
                    snprintf(phase_meta, sizeof(phase_meta), "__struct_phases_%s", sd->name);
2824
3
                    uint8_t pt = alloc_reg();
2825
3
                    uint16_t pi = add_constant(phase_arr);
2826
3
                    emit_ABx(ROP_LOADK, pt, pi, 0);
2827
3
                    uint16_t pn = add_constant(value_string(phase_meta));
2828
3
                    emit_ABx(ROP_DEFINEGLOBAL, pt, pn, 0);
2829
3
                    free_reg(pt);
2830
3
                }
2831
44
            }
2832
44
            break;
2833
0
        }
2834
2835
9
        case ITEM_ENUM: {
2836
9
            EnumDecl *ed = &prog->items[i].as.enum_decl;
2837
9
            rc_register_enum(ed->name);
2838
9
            char meta_name[256];
2839
9
            snprintf(meta_name, sizeof(meta_name), "__enum_%s", ed->name);
2840
9
            uint8_t tmp = alloc_reg();
2841
9
            emit_ABC(ROP_LOADTRUE, tmp, 0, 0, 0);
2842
9
            uint16_t name_ki = add_constant(value_string(meta_name));
2843
9
            emit_ABx(ROP_DEFINEGLOBAL, tmp, name_ki, 0);
2844
9
            free_reg(tmp);
2845
9
            break;
2846
0
        }
2847
2848
5
        case ITEM_IMPL: {
2849
5
            ImplBlock *ib = &prog->items[i].as.impl_block;
2850
11
            for (size_t j = 0; j < ib->method_count; j++) {
2851
6
                FnDecl *method = &ib->methods[j];
2852
6
                char key[256];
2853
6
                snprintf(key, sizeof(key), "%s::%s", ib->type_name, method->name);
2854
2855
                /* Compile method body */
2856
6
                RegCompiler func_comp;
2857
6
                rc_init(&func_comp, rc, REG_FUNC_FUNCTION);
2858
6
                func_comp.func_name = strdup(key);
2859
6
                func_comp.chunk->name = strdup(key);
2860
2861
                /* self is slot 0 */
2862
6
                size_t first_param = 0;
2863
6
                if (method->param_count > 0 && strcmp(method->params[0].name, "self") == 0) {
2864
6
                    free(func_comp.locals[0].name);
2865
6
                    func_comp.locals[0].name = strdup("self");
2866
6
                    first_param = 1;
2867
6
                }
2868
2869
                /* Count non-variadic params for arity */
2870
6
                size_t m_declared_arity = method->param_count - first_param;
2871
6
                bool m_has_variadic = false;
2872
7
                for (size_t k = first_param; k < method->param_count; k++) {
2873
1
                    if (method->params[k].is_variadic) {
2874
0
                        m_declared_arity = k - first_param;
2875
0
                        m_has_variadic = true;
2876
0
                        break;
2877
0
                    }
2878
1
                }
2879
6
                func_comp.arity = (int)m_declared_arity;
2880
7
                for (size_t k = first_param; k < method->param_count; k++)
2881
1
                    add_local(method->params[k].name);
2882
2883
                /* Emit default parameter initialization.
2884
                 * With self: locals[0]=self, locals[1..]=params[1..].
2885
                 * Without self (shouldn't happen for impl): locals[0]="", locals[1..]=params[0..].
2886
                 * In both cases, params[k] is at locals[k]. */
2887
7
                for (size_t k = first_param; k < method->param_count; k++) {
2888
1
                    if (method->params[k].default_value && !method->params[k].is_variadic) {
2889
0
                        uint8_t preg = local_reg((int)k);
2890
0
                        size_t skip = emit_jump_placeholder(ROP_JMPNOTNIL, preg, 0);
2891
0
                        compile_expr(method->params[k].default_value, preg, 0);
2892
0
                        patch_jump(skip);
2893
0
                    }
2894
1
                }
2895
2896
                /* Emit variadic collection if needed */
2897
6
                if (m_has_variadic) {
2898
0
                    size_t var_idx = first_param + m_declared_arity;
2899
0
                    uint8_t var_reg = local_reg((int)var_idx);
2900
0
                    emit_ABC(ROP_COLLECT_VARARGS, var_reg, (uint8_t)(m_declared_arity + 1), 0, 0);
2901
0
                }
2902
2903
12
                for (size_t k = 0; k < method->body_count; k++)
2904
6
                    compile_stmt(method->body[k]);
2905
2906
6
                uint8_t ret_reg = alloc_reg();
2907
6
                emit_ABC(ROP_LOADUNIT, ret_reg, 0, 0, 0);
2908
6
                emit_return(ret_reg, 0);
2909
6
                free_reg(ret_reg);
2910
2911
6
                RegChunk *fn_chunk = func_comp.chunk;
2912
6
                size_t upvalue_count = func_comp.upvalue_count;
2913
6
                RegCompilerUpvalue *upvalues = NULL;
2914
6
                if (upvalue_count > 0) {
2915
0
                    upvalues = malloc(upvalue_count * sizeof(RegCompilerUpvalue));
2916
0
                    memcpy(upvalues, func_comp.upvalues, upvalue_count * sizeof(RegCompilerUpvalue));
2917
0
                }
2918
6
                rc_cleanup(&func_comp);
2919
6
                rc = func_comp.enclosing;
2920
2921
6
                LatValue fn_val;
2922
6
                memset(&fn_val, 0, sizeof(fn_val));
2923
6
                fn_val.type = VAL_CLOSURE;
2924
6
                fn_val.phase = VTAG_UNPHASED;
2925
6
                fn_val.region_id = upvalue_count;  /* encode upvalue count for runtime */
2926
6
                fn_val.as.closure.body = NULL;
2927
6
                fn_val.as.closure.native_fn = fn_chunk;
2928
6
                fn_val.as.closure.param_count = method->param_count;
2929
6
                if (method->param_count > 0 && method->params) {
2930
6
                    fn_val.as.closure.param_names = malloc(method->param_count * sizeof(char *));
2931
13
                    for (size_t pi = 0; pi < method->param_count; pi++)
2932
7
                        fn_val.as.closure.param_names[pi] = strdup(method->params[pi].name);
2933
6
                }
2934
6
                uint16_t fn_ki = add_constant(fn_val);
2935
2936
6
                uint8_t dst = alloc_reg();
2937
6
                emit_ABx(ROP_CLOSURE, dst, fn_ki, 0);
2938
6
                for (size_t k = 0; k < upvalue_count; k++)
2939
0
                    emit_ABC(ROP_MOVE, upvalues[k].is_local ? 1 : 0, upvalues[k].index, 0, 0);
2940
6
                free(upvalues);
2941
2942
6
                uint16_t key_ki = add_constant(value_string(key));
2943
6
                emit_ABx(ROP_DEFINEGLOBAL, dst, key_ki, 0);
2944
6
                free_reg(dst);
2945
6
            }
2946
5
            break;
2947
0
        }
2948
2949
4
        case ITEM_TRAIT:
2950
6
        case ITEM_TEST:
2951
6
            break;
2952
1.18k
        }
2953
1.18k
    }
2954
2955
844
    if (rc_error) {
2956
0
        *error = rc_error;
2957
0
        rc_error = NULL;
2958
0
        regchunk_free(top.chunk);
2959
0
        rc_cleanup(&top);
2960
0
        rc = NULL;
2961
0
        rc_free_known_enums();
2962
0
        return NULL;
2963
0
    }
2964
2965
    /* Auto-call main() if defined */
2966
844
    bool has_main = false;
2967
1.20k
    for (size_t i = 0; i < prog->item_count; i++) {
2968
1.17k
        if (prog->items[i].tag == ITEM_FUNCTION &&
2969
1.17k
            strcmp(prog->items[i].as.fn_decl.name, "main") == 0) {
2970
816
            has_main = true;
2971
816
            break;
2972
816
        }
2973
1.17k
    }
2974
844
    if (has_main) {
2975
816
        uint8_t func_reg = alloc_reg();
2976
816
        uint16_t main_ki = add_constant(value_string("main"));
2977
816
        emit_ABx(ROP_GETGLOBAL, func_reg, main_ki, 0);
2978
816
        emit_ABC(ROP_CALL, func_reg, 0, 1, 0);
2979
816
        free_reg(func_reg);
2980
816
    }
2981
2982
    /* Final halt + return */
2983
844
    uint8_t ret_reg = alloc_reg();
2984
844
    emit_ABC(ROP_LOADUNIT, ret_reg, 0, 0, 0);
2985
844
    emit_return(ret_reg, 0);
2986
844
    free_reg(ret_reg);
2987
2988
844
    RegChunk *result = top.chunk;
2989
844
    rc_cleanup(&top);
2990
844
    rc = NULL;
2991
844
    return result;
2992
844
}
2993
2994
/* Compile for REPL — keeps the last expression value in a known register */
2995
static RegChunk *reg_compile_internal(const Program *prog, char **error,
2996
100
                                      bool auto_main, bool keep_last_expr) {
2997
100
    RegCompiler top;
2998
100
    rc_init(&top, NULL, REG_FUNC_SCRIPT);
2999
100
    *error = NULL;
3000
3001
1.88k
    for (size_t i = 0; i < prog->item_count; i++) {
3002
1.79k
        if (rc_error) break;
3003
3004
1.79k
        switch (prog->items[i].tag) {
3005
30
        case ITEM_STMT:
3006
            /* For REPL mode, if this is the last item and it's an expression,
3007
             * keep the result instead of discarding */
3008
30
            if (keep_last_expr && i + 1 == prog->item_count &&
3009
30
                prog->items[i].as.stmt->tag == STMT_EXPR) {
3010
6
                uint8_t result_reg = alloc_reg();
3011
6
                compile_expr(prog->items[i].as.stmt->as.expr, result_reg,
3012
6
                             prog->items[i].as.stmt->line);
3013
6
                emit_return(result_reg, 0);
3014
6
                free_reg(result_reg);
3015
6
                goto finish;
3016
6
            }
3017
24
            compile_stmt(prog->items[i].as.stmt);
3018
24
            break;
3019
3020
1.75k
        case ITEM_FUNCTION: {
3021
1.75k
            FnDecl *fn = &prog->items[i].as.fn_decl;
3022
1.75k
            compile_function_body(REG_FUNC_FUNCTION, fn->name,
3023
1.75k
                                  fn->params, fn->param_count,
3024
1.75k
                                  fn->body, fn->body_count, 0,
3025
1.75k
                                  fn->contracts, fn->contract_count,
3026
1.75k
                                  fn->return_type);
3027
1.75k
            break;
3028
30
        }
3029
3030
4
        case ITEM_STRUCT: {
3031
4
            StructDecl *sd = &prog->items[i].as.struct_decl;
3032
4
            LatValue *field_names = malloc(sd->field_count * sizeof(LatValue));
3033
11
            for (size_t j = 0; j < sd->field_count; j++)
3034
7
                field_names[j] = value_string(sd->fields[j].name);
3035
4
            LatValue arr = value_array(field_names, sd->field_count);
3036
4
            free(field_names);
3037
4
            char meta_name[256];
3038
4
            snprintf(meta_name, sizeof(meta_name), "__struct_%s", sd->name);
3039
4
            uint8_t tmp = alloc_reg();
3040
4
            uint16_t arr_ki = add_constant(arr);
3041
4
            emit_ABx(ROP_LOADK, tmp, arr_ki, 0);
3042
4
            uint16_t name_ki = add_constant(value_string(meta_name));
3043
4
            emit_ABx(ROP_DEFINEGLOBAL, tmp, name_ki, 0);
3044
4
            free_reg(tmp);
3045
            /* Alloy: per-field phase metadata */
3046
4
            {
3047
4
                bool has_phase_ann = false;
3048
11
                for (size_t j = 0; j < sd->field_count; j++) {
3049
7
                    if (sd->fields[j].ty.phase != PHASE_UNSPECIFIED) { has_phase_ann = true; break; }
3050
7
                }
3051
4
                if (has_phase_ann) {
3052
0
                    LatValue *phases = malloc(sd->field_count * sizeof(LatValue));
3053
0
                    for (size_t j = 0; j < sd->field_count; j++)
3054
0
                        phases[j] = value_int((int64_t)sd->fields[j].ty.phase);
3055
0
                    LatValue phase_arr = value_array(phases, sd->field_count);
3056
0
                    free(phases);
3057
0
                    char phase_meta[256];
3058
0
                    snprintf(phase_meta, sizeof(phase_meta), "__struct_phases_%s", sd->name);
3059
0
                    uint8_t pt = alloc_reg();
3060
0
                    uint16_t pi = add_constant(phase_arr);
3061
0
                    emit_ABx(ROP_LOADK, pt, pi, 0);
3062
0
                    uint16_t pn = add_constant(value_string(phase_meta));
3063
0
                    emit_ABx(ROP_DEFINEGLOBAL, pt, pn, 0);
3064
0
                    free_reg(pt);
3065
0
                }
3066
4
            }
3067
4
            break;
3068
30
        }
3069
3070
0
        case ITEM_ENUM: {
3071
0
            EnumDecl *ed = &prog->items[i].as.enum_decl;
3072
0
            rc_register_enum(ed->name);
3073
0
            char meta_name[256];
3074
0
            snprintf(meta_name, sizeof(meta_name), "__enum_%s", ed->name);
3075
0
            uint8_t tmp = alloc_reg();
3076
0
            emit_ABC(ROP_LOADTRUE, tmp, 0, 0, 0);
3077
0
            uint16_t nk = add_constant(value_string(meta_name));
3078
0
            emit_ABx(ROP_DEFINEGLOBAL, tmp, nk, 0);
3079
0
            free_reg(tmp);
3080
0
            break;
3081
30
        }
3082
3083
0
        case ITEM_IMPL: {
3084
0
            ImplBlock *ib = &prog->items[i].as.impl_block;
3085
0
            for (size_t j = 0; j < ib->method_count; j++) {
3086
0
                FnDecl *method = &ib->methods[j];
3087
0
                char key[256];
3088
0
                snprintf(key, sizeof(key), "%s::%s", ib->type_name, method->name);
3089
0
                RegCompiler func_comp;
3090
0
                rc_init(&func_comp, rc, REG_FUNC_FUNCTION);
3091
0
                func_comp.func_name = strdup(key);
3092
0
                func_comp.chunk->name = strdup(key);
3093
0
                size_t first_param = 0;
3094
0
                if (method->param_count > 0 && strcmp(method->params[0].name, "self") == 0) {
3095
0
                    free(func_comp.locals[0].name);
3096
0
                    func_comp.locals[0].name = strdup("self");
3097
0
                    first_param = 1;
3098
0
                }
3099
0
                func_comp.arity = (int)(method->param_count - first_param);
3100
0
                for (size_t k = first_param; k < method->param_count; k++)
3101
0
                    add_local(method->params[k].name);
3102
0
                for (size_t k = 0; k < method->body_count; k++)
3103
0
                    compile_stmt(method->body[k]);
3104
0
                uint8_t rr = alloc_reg();
3105
0
                emit_ABC(ROP_LOADUNIT, rr, 0, 0, 0);
3106
0
                emit_return(rr, 0);
3107
0
                free_reg(rr);
3108
0
                RegChunk *fn_chunk = func_comp.chunk;
3109
0
                size_t uvc = func_comp.upvalue_count;
3110
0
                RegCompilerUpvalue *uvs = NULL;
3111
0
                if (uvc > 0) {
3112
0
                    uvs = malloc(uvc * sizeof(RegCompilerUpvalue));
3113
0
                    memcpy(uvs, func_comp.upvalues, uvc * sizeof(RegCompilerUpvalue));
3114
0
                }
3115
0
                rc_cleanup(&func_comp);
3116
0
                rc = func_comp.enclosing;
3117
0
                LatValue fn_val;
3118
0
                memset(&fn_val, 0, sizeof(fn_val));
3119
0
                fn_val.type = VAL_CLOSURE;
3120
0
                fn_val.phase = VTAG_UNPHASED;
3121
0
                fn_val.region_id = uvc;  /* encode upvalue count for runtime */
3122
0
                fn_val.as.closure.body = NULL;
3123
0
                fn_val.as.closure.native_fn = fn_chunk;
3124
0
                fn_val.as.closure.param_count = method->param_count;
3125
0
                if (method->param_count > 0 && method->params) {
3126
0
                    fn_val.as.closure.param_names = malloc(method->param_count * sizeof(char *));
3127
0
                    for (size_t pi = 0; pi < method->param_count; pi++)
3128
0
                        fn_val.as.closure.param_names[pi] = strdup(method->params[pi].name);
3129
0
                }
3130
0
                uint16_t fn_ki = add_constant(fn_val);
3131
0
                uint8_t dst = alloc_reg();
3132
0
                emit_ABx(ROP_CLOSURE, dst, fn_ki, 0);
3133
0
                for (size_t k = 0; k < uvc; k++)
3134
0
                    emit_ABC(ROP_MOVE, uvs[k].is_local ? 1 : 0, uvs[k].index, 0, 0);
3135
0
                free(uvs);
3136
0
                uint16_t key_ki = add_constant(value_string(key));
3137
0
                emit_ABx(ROP_DEFINEGLOBAL, dst, key_ki, 0);
3138
0
                free_reg(dst);
3139
0
            }
3140
0
            break;
3141
30
        }
3142
3143
0
        case ITEM_TRAIT:
3144
0
        case ITEM_TEST:
3145
0
            break;
3146
1.79k
        }
3147
1.79k
    }
3148
3149
94
    if (rc_error) {
3150
0
        *error = rc_error;
3151
0
        rc_error = NULL;
3152
0
        regchunk_free(top.chunk);
3153
0
        rc_cleanup(&top);
3154
0
        rc = NULL;
3155
0
        return NULL;
3156
0
    }
3157
3158
    /* Auto-call main() if requested */
3159
94
    if (auto_main) {
3160
0
        bool has_main = false;
3161
0
        for (size_t i = 0; i < prog->item_count; i++) {
3162
0
            if (prog->items[i].tag == ITEM_FUNCTION &&
3163
0
                strcmp(prog->items[i].as.fn_decl.name, "main") == 0) {
3164
0
                has_main = true;
3165
0
                break;
3166
0
            }
3167
0
        }
3168
0
        if (has_main) {
3169
0
            uint8_t func_reg = alloc_reg();
3170
0
            uint16_t main_ki = add_constant(value_string("main"));
3171
0
            emit_ABx(ROP_GETGLOBAL, func_reg, main_ki, 0);
3172
0
            emit_ABC(ROP_CALL, func_reg, 0, 1, 0);
3173
0
            free_reg(func_reg);
3174
0
        }
3175
0
    }
3176
3177
100
finish:
3178
100
    {
3179
        /* Final return */
3180
100
        uint8_t ret_reg = alloc_reg();
3181
100
        emit_ABC(ROP_LOADUNIT, ret_reg, 0, 0, 0);
3182
100
        emit_return(ret_reg, 0);
3183
100
        free_reg(ret_reg);
3184
100
    }
3185
3186
100
    RegChunk *result = top.chunk;
3187
100
    rc_cleanup(&top);
3188
100
    rc = NULL;
3189
100
    return result;
3190
94
}
3191
3192
13
RegChunk *reg_compile_repl(const Program *prog, char **error) {
3193
13
    return reg_compile_internal(prog, error, false, true);
3194
13
}
3195
3196
87
RegChunk *reg_compile_module(const Program *prog, char **error) {
3197
87
    RegChunk *result = reg_compile_internal(prog, error, false, false);
3198
    /* Copy export metadata from Program to RegChunk */
3199
87
    if (result && prog->has_exports) {
3200
6
        result->has_exports = true;
3201
6
        result->export_count = prog->export_count;
3202
6
        result->export_names = malloc(prog->export_count * sizeof(char *));
3203
18
        for (size_t i = 0; i < prog->export_count; i++)
3204
12
            result->export_names[i] = strdup(prog->export_names[i]);
3205
6
    }
3206
87
    return result;
3207
87
}
3208
3209
0
void reg_compiler_free_known_enums(void) {
3210
0
    rc_free_known_enums();
3211
0
}