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/regvm.c
Line
Count
Source
1
#include "regvm.h"
2
#include "regopcode.h"
3
#include "runtime.h"
4
#include "value.h"
5
#include "env.h"
6
#include "stackvm.h"   /* For ObjUpvalue */
7
#include "channel.h"
8
#include "lexer.h"
9
#include "parser.h"
10
#include "ast.h"
11
#include "builtins.h"
12
#include "ext.h"
13
#include "memory.h"
14
#include <stdlib.h>
15
#include <string.h>
16
#include <stdio.h>
17
#include <stdarg.h>
18
#include <limits.h>
19
#ifndef __EMSCRIPTEN__
20
#include <pthread.h>
21
#endif
22
23
/* Native function marker — same sentinel as stack VM (defined in vm.c) */
24
59.9k
#define VM_NATIVE_MARKER ((struct Expr **)(uintptr_t)0x1)
25
/* Extension function marker — same sentinel as stack VM */
26
13.1k
#define VM_EXT_MARKER    ((struct Expr **)(uintptr_t)0x2)
27
28
/* ── RegChunk implementation ── */
29
30
4.27k
RegChunk *regchunk_new(void) {
31
4.27k
    RegChunk *c = calloc(1, sizeof(RegChunk));
32
4.27k
    c->magic = REGCHUNK_MAGIC;
33
4.27k
    c->code_cap = 128;
34
4.27k
    c->code = malloc(c->code_cap * sizeof(RegInstr));
35
4.27k
    c->const_cap = 32;
36
4.27k
    c->constants = malloc(c->const_cap * sizeof(LatValue));
37
4.27k
    c->lines_cap = 128;
38
4.27k
    c->lines = malloc(c->lines_cap * sizeof(int));
39
4.27k
    c->local_name_cap = 0;
40
4.27k
    c->local_names = NULL;
41
4.27k
    c->name = NULL;
42
4.27k
    return c;
43
4.27k
}
44
45
4.27k
void regchunk_free(RegChunk *c) {
46
4.27k
    if (!c) return;
47
    /* Free sub-chunks stored in closure constants */
48
39.0k
    for (size_t i = 0; i < c->const_len; i++) {
49
34.7k
        LatValue *v = &c->constants[i];
50
34.7k
        if (v->type == VAL_CLOSURE && v->as.closure.body == NULL &&
51
34.7k
            v->as.closure.native_fn != NULL &&
52
34.7k
            v->as.closure.default_values != VM_NATIVE_MARKER &&
53
34.7k
            v->as.closure.default_values != VM_EXT_MARKER) {
54
3.33k
            regchunk_free((RegChunk *)v->as.closure.native_fn);
55
3.33k
            v->as.closure.native_fn = NULL;
56
31.4k
        } else {
57
31.4k
            value_free(v);
58
31.4k
        }
59
34.7k
    }
60
4.27k
    free(c->constants);
61
4.27k
    free(c->code);
62
4.27k
    free(c->lines);
63
4.27k
    if (c->local_names) {
64
53.2k
        for (size_t i = 0; i < c->local_name_cap; i++)
65
50.3k
            free(c->local_names[i]);
66
2.92k
        free(c->local_names);
67
2.92k
    }
68
4.27k
    free(c->name);
69
4.27k
    free(c->param_phases);
70
4.27k
    if (c->export_names) {
71
18
        for (size_t i = 0; i < c->export_count; i++)
72
12
            free(c->export_names[i]);
73
6
        free(c->export_names);
74
6
    }
75
4.27k
    free(c);
76
4.27k
}
77
78
112k
size_t regchunk_write(RegChunk *c, RegInstr instr, int line) {
79
112k
    if (c->code_len >= c->code_cap) {
80
90
        c->code_cap *= 2;
81
90
        c->code = realloc(c->code, c->code_cap * sizeof(RegInstr));
82
90
    }
83
112k
    if (c->lines_len >= c->lines_cap) {
84
90
        c->lines_cap *= 2;
85
90
        c->lines = realloc(c->lines, c->lines_cap * sizeof(int));
86
90
    }
87
112k
    size_t offset = c->code_len;
88
112k
    c->code[c->code_len++] = instr;
89
112k
    c->lines[c->lines_len++] = line;
90
112k
    return offset;
91
112k
}
92
93
34.7k
size_t regchunk_add_constant(RegChunk *c, LatValue val) {
94
34.7k
    if (c->const_len >= c->const_cap) {
95
168
        c->const_cap *= 2;
96
168
        c->constants = realloc(c->constants, c->const_cap * sizeof(LatValue));
97
168
    }
98
34.7k
    c->constants[c->const_len] = val;
99
34.7k
    return c->const_len++;
100
34.7k
}
101
102
8.34k
void regchunk_set_local_name(RegChunk *c, size_t reg, const char *name) {
103
8.34k
    if (reg >= c->local_name_cap) {
104
2.96k
        size_t old_cap = c->local_name_cap;
105
2.96k
        c->local_name_cap = reg + 16;
106
2.96k
        c->local_names = realloc(c->local_names, c->local_name_cap * sizeof(char *));
107
53.3k
        for (size_t i = old_cap; i < c->local_name_cap; i++)
108
50.3k
            c->local_names[i] = NULL;
109
2.96k
    }
110
8.34k
    free(c->local_names[reg]);
111
8.34k
    c->local_names[reg] = name ? strdup(name) : NULL;
112
8.34k
}
113
114
/* ── Register VM ── */
115
116
844
void regvm_init(RegVM *vm, LatRuntime *rt) {
117
844
    memset(vm, 0, sizeof(RegVM));
118
844
    vm->fn_chunk_cap = 16;
119
844
    vm->fn_chunks = malloc(vm->fn_chunk_cap * sizeof(RegChunk *));
120
844
    vm->module_cache = NULL;
121
844
    vm->ephemeral = bump_arena_new();
122
844
    vm->rt = rt;
123
844
    vm->env = rt->env;
124
844
    vm->struct_meta = rt->struct_meta;
125
    /* Initialize register stack to nil */
126
13.8M
    for (size_t i = 0; i < REGVM_REG_MAX * REGVM_FRAMES_MAX; i++) {
127
13.8M
        vm->reg_stack[i] = value_nil();
128
13.8M
    }
129
844
}
130
131
844
void regvm_free(RegVM *vm) {
132
    /* Clear thread-local runtime pointer if it still refers to this VM's runtime,
133
     * preventing dangling pointer after the caller's stack-allocated LatRuntime dies. */
134
844
    if (lat_runtime_current() == vm->rt)
135
844
        lat_runtime_set_current(NULL);
136
137
    /* Don't free env/struct_meta — runtime owns them */
138
944
    for (size_t i = 0; i < vm->fn_chunk_count; i++)
139
100
        regchunk_free(vm->fn_chunks[i]);
140
844
    free(vm->fn_chunks);
141
844
    free(vm->error);
142
844
    if (vm->module_cache) {
143
1.46k
        for (size_t i = 0; i < vm->module_cache->cap; i++) {
144
1.37k
            if (vm->module_cache->entries[i].state == MAP_OCCUPIED) {
145
87
                LatValue *v = (LatValue *)vm->module_cache->entries[i].value;
146
87
                value_free(v);
147
87
            }
148
1.37k
        }
149
86
        lat_map_free(vm->module_cache);
150
86
        free(vm->module_cache);
151
86
        vm->module_cache = NULL;
152
86
    }
153
844
    if (vm->ephemeral) {
154
844
        bump_arena_free(vm->ephemeral);
155
844
        vm->ephemeral = NULL;
156
844
    }
157
    /* Free register values */
158
51.0k
    for (size_t i = 0; i < vm->reg_stack_top; i++)
159
50.1k
        value_free_inline(&vm->reg_stack[i]);
160
    /* Free open upvalues */
161
844
    ObjUpvalue *uv = vm->open_upvalues;
162
844
    while (uv) {
163
0
        ObjUpvalue *next = uv->next;
164
0
        value_free(&uv->closed);
165
0
        free(uv);
166
0
        uv = next;
167
0
    }
168
    /* Reactions, bonds, seeds are owned by LatRuntime — not freed here */
169
844
}
170
171
100
void regvm_track_chunk(RegVM *vm, RegChunk *ch) {
172
100
    if (vm->fn_chunk_count >= vm->fn_chunk_cap) {
173
0
        vm->fn_chunk_cap *= 2;
174
0
        vm->fn_chunks = realloc(vm->fn_chunks, vm->fn_chunk_cap * sizeof(RegChunk *));
175
0
    }
176
100
    vm->fn_chunks[vm->fn_chunk_count++] = ch;
177
100
}
178
179
/* ── Value cloning (same fast-path as stack VM) ── */
180
181
40.0k
static inline LatValue rvm_clone(const LatValue *src) {
182
40.0k
    switch (src->type) {
183
10.7k
        case VAL_INT: case VAL_FLOAT: case VAL_BOOL:
184
14.7k
        case VAL_UNIT: case VAL_NIL: case VAL_RANGE: {
185
14.7k
            LatValue v = *src;
186
14.7k
            v.region_id = REGION_NONE;
187
14.7k
            return v;
188
14.7k
        }
189
13.0k
        case VAL_STR: {
190
13.0k
            LatValue v = *src;
191
13.0k
            v.as.str_val = strdup(src->as.str_val);
192
13.0k
            v.region_id = REGION_NONE;
193
13.0k
            return v;
194
14.7k
        }
195
7.81k
        case VAL_CLOSURE: {
196
7.81k
            if (src->as.closure.body == NULL && src->as.closure.native_fn != NULL &&
197
7.81k
                src->as.closure.default_values != VM_NATIVE_MARKER &&
198
7.81k
                src->as.closure.default_values != VM_EXT_MARKER) {
199
6.15k
                LatValue v = *src;
200
6.15k
                if (src->as.closure.param_names) {
201
4.25k
                    v.as.closure.param_names = malloc(src->as.closure.param_count * sizeof(char *));
202
11.2k
                    for (size_t i = 0; i < src->as.closure.param_count; i++)
203
6.94k
                        v.as.closure.param_names[i] = strdup(src->as.closure.param_names[i]);
204
4.25k
                }
205
6.15k
                return v;
206
6.15k
            }
207
1.66k
            return value_deep_clone(src);
208
7.81k
        }
209
1.09k
        case VAL_ARRAY: {
210
1.09k
            LatValue v = *src;
211
1.09k
            size_t len = src->as.array.len;
212
1.09k
            size_t cap = src->as.array.cap > 0 ? src->as.array.cap : (len > 0 ? len : 1);
213
1.09k
            v.as.array.elems = malloc(cap * sizeof(LatValue));
214
1.09k
            v.as.array.cap = cap;
215
9.29k
            for (size_t i = 0; i < len; i++)
216
8.20k
                v.as.array.elems[i] = rvm_clone(&src->as.array.elems[i]);
217
1.09k
            v.region_id = REGION_NONE;
218
1.09k
            return v;
219
7.81k
        }
220
3.38k
        default:
221
3.38k
            return value_deep_clone(src);
222
40.0k
    }
223
40.0k
}
224
225
/* ── Runtime error ── */
226
227
/* Basic error (no exception handler check, for use outside dispatch loop) */
228
0
static RegVMResult rvm_error(RegVM *vm, const char *fmt, ...) {
229
0
    char *msg = NULL;
230
0
    va_list args;
231
0
    va_start(args, fmt);
232
0
    (void)vasprintf(&msg, fmt, args);
233
0
    va_end(args);
234
235
0
    RegCallFrame *f = &vm->frames[vm->frame_count - 1];
236
0
    int line = 0;
237
0
    if (f->ip > f->chunk->code) {
238
0
        size_t offset = (size_t)(f->ip - f->chunk->code - 1);
239
0
        if (offset < f->chunk->lines_len)
240
0
            line = f->chunk->lines[offset];
241
0
    }
242
0
    if (line > 0) {
243
0
        char *full = NULL;
244
0
        (void)asprintf(&full, "[line %d] %s", line, msg);
245
0
        free(msg);
246
0
        vm->error = full;
247
0
    } else {
248
0
        vm->error = msg;
249
0
    }
250
0
    return REGVM_RUNTIME_ERROR;
251
0
}
252
253
/* Forward declaration needed by rvm_handle_error */
254
static inline void reg_set(LatValue *r, LatValue val);
255
256
/* Error handler that routes through exception handlers when available.
257
 * Used inside the dispatch loop via RVM_ERROR macro.
258
 * Returns REGVM_OK if handled (execution continues), error otherwise. */
259
static RegVMResult rvm_handle_error(RegVM *vm, RegCallFrame **frame_ptr,
260
99
                                     LatValue **R_ptr, const char *fmt, ...) {
261
99
    char *inner = NULL;
262
99
    va_list args;
263
99
    va_start(args, fmt);
264
99
    (void)vasprintf(&inner, fmt, args);
265
99
    va_end(args);
266
267
    /* If there's an active handler, pass raw message to catch variable */
268
99
    if (vm->handler_count > 0) {
269
24
        RegHandler h = vm->handlers[--vm->handler_count];
270
271
        /* Unwind frames */
272
32
        while (vm->frame_count - 1 > (int)h.frame_index) {
273
8
            RegCallFrame *uf = &vm->frames[vm->frame_count - 1];
274
2.05k
            for (int i = 0; i < REGVM_REG_MAX; i++)
275
2.04k
                value_free_inline(&uf->regs[i]);
276
8
            vm->frame_count--;
277
8
            vm->reg_stack_top -= REGVM_REG_MAX;
278
8
        }
279
280
24
        *frame_ptr = &vm->frames[vm->frame_count - 1];
281
24
        *R_ptr = (*frame_ptr)->regs;
282
24
        (*frame_ptr)->ip = h.ip;
283
24
        reg_set(&(*R_ptr)[h.error_reg], value_string_owned(inner));
284
24
        return REGVM_OK;
285
24
    }
286
287
    /* Uncaught error: prepend line info */
288
75
    RegCallFrame *f = &vm->frames[vm->frame_count - 1];
289
75
    int line = 0;
290
75
    if (f->ip > f->chunk->code) {
291
75
        size_t offset = (size_t)(f->ip - f->chunk->code - 1);
292
75
        if (offset < f->chunk->lines_len)
293
75
            line = f->chunk->lines[offset];
294
75
    }
295
75
    if (line > 0) {
296
73
        char *msg;
297
73
        (void)asprintf(&msg, "[line %d] %s", line, inner);
298
73
        free(inner);
299
73
        vm->error = msg;
300
73
    } else {
301
2
        vm->error = inner;
302
2
    }
303
75
    return REGVM_RUNTIME_ERROR;
304
99
}
305
306
/* ── Set a register (free old value, then assign) ── */
307
308
33.6k
static inline void reg_set(LatValue *r, LatValue val) {
309
33.6k
    value_free_inline(r);
310
33.6k
    *r = val;
311
33.6k
}
312
313
/* Forward declarations for recursive closure calls */
314
static RegVMResult regvm_dispatch(RegVM *vm, int base_frame, LatValue *result);
315
static LatValue regvm_call_closure(RegVM *vm, LatValue *closure, LatValue *args, int argc);
316
317
/* Run a sub-chunk within the current VM (pushes a new frame, doesn't reset state) */
318
102
static RegVMResult regvm_run_sub(RegVM *vm, RegChunk *chunk, LatValue *result) {
319
102
    if (vm->frame_count >= REGVM_FRAMES_MAX)
320
0
        return rvm_error(vm, "call stack overflow");
321
102
    size_t new_base = vm->reg_stack_top;
322
102
    if (new_base + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX)
323
0
        return rvm_error(vm, "register stack overflow");
324
102
    LatValue *new_regs = &vm->reg_stack[new_base];
325
102
    vm->reg_stack_top += REGVM_REG_MAX;
326
26.2k
    for (int i = 0; i < REGVM_REG_MAX; i++)
327
26.1k
        new_regs[i] = value_nil();
328
329
102
    int saved_base = vm->frame_count;
330
102
    RegCallFrame *new_frame = &vm->frames[vm->frame_count++];
331
102
    new_frame->chunk = chunk;
332
102
    new_frame->ip = chunk->code;
333
102
    new_frame->regs = new_regs;
334
102
    new_frame->reg_count = REGVM_REG_MAX;
335
102
    new_frame->upvalues = NULL;
336
102
    new_frame->upvalue_count = 0;
337
102
    new_frame->caller_result_reg = 0;
338
339
102
    RegVMResult res = regvm_dispatch(vm, saved_base, result);
340
341
    /* Clean up any frames left by HALT (which doesn't pop the frame) */
342
104
    while (vm->frame_count > saved_base) {
343
2
        RegCallFrame *f = &vm->frames[vm->frame_count - 1];
344
514
        for (int i = 0; i < REGVM_REG_MAX; i++)
345
512
            value_free_inline(&f->regs[i]);
346
2
        vm->frame_count--;
347
2
        vm->reg_stack_top -= REGVM_REG_MAX;
348
2
    }
349
350
102
    return res;
351
102
}
352
353
/* ── Invoke builtin method ── */
354
/* Returns true if handled, false if not a builtin */
355
356
static bool rvm_invoke_builtin(RegVM *vm, LatValue *obj, const char *method,
357
2.81k
                                LatValue *args, int arg_count, LatValue *result) {
358
2.81k
    (void)vm;
359
2.81k
    if (obj->type == VAL_ARRAY) {
360
244
        if (strcmp(method, "len") == 0 && arg_count == 0) {
361
21
            *result = value_int((int64_t)obj->as.array.len);
362
21
            return true;
363
21
        }
364
223
        if (strcmp(method, "push") == 0 && arg_count == 1) {
365
131
            if (value_is_crystal(obj)) {
366
0
                vm->error = strdup("cannot push to a crystal array");
367
0
                *result = value_unit();
368
0
                return true;
369
0
            }
370
131
            if (obj->phase == VTAG_SUBLIMATED) {
371
1
                vm->error = strdup("cannot push to a sublimated array");
372
1
                *result = value_unit();
373
1
                return true;
374
1
            }
375
            /* Check pressure constraints */
376
130
            {
377
130
                if (vm->rt && vm->rt->pressure_count > 0) {
378
                    /* Find variable name for this object by checking register names */
379
2
                    RegCallFrame *cf = &vm->frames[vm->frame_count - 1];
380
2
                    if (cf->chunk && cf->chunk->local_names) {
381
4
                        for (size_t r = 0; r < cf->chunk->local_name_cap; r++) {
382
4
                            if (&cf->regs[r] == obj && cf->chunk->local_names[r] && cf->chunk->local_names[r][0]) {
383
2
                                for (size_t pi = 0; pi < vm->rt->pressure_count; pi++) {
384
2
                                    if (strcmp(vm->rt->pressures[pi].name, cf->chunk->local_names[r]) == 0) {
385
2
                                        const char *mode = vm->rt->pressures[pi].mode;
386
2
                                        if (strcmp(mode, "no_grow") == 0 || strcmp(mode, "no_resize") == 0) {
387
2
                                            (void)asprintf(&vm->error, "pressurized (%s): cannot push to '%s'",
388
2
                                                           mode, cf->chunk->local_names[r]);
389
2
                                            *result = value_unit();
390
2
                                            return true;
391
2
                                        }
392
2
                                    }
393
2
                                }
394
0
                                break;
395
2
                            }
396
4
                        }
397
2
                    }
398
2
                }
399
130
            }
400
128
            LatValue val = rvm_clone(&args[0]);
401
128
            if (obj->as.array.len >= obj->as.array.cap) {
402
7
                obj->as.array.cap = obj->as.array.cap ? obj->as.array.cap * 2 : 4;
403
7
                obj->as.array.elems = realloc(obj->as.array.elems,
404
7
                    obj->as.array.cap * sizeof(LatValue));
405
7
            }
406
128
            obj->as.array.elems[obj->as.array.len++] = val;
407
128
            *result = value_unit();
408
128
            return true;
409
130
        }
410
92
        if (strcmp(method, "pop") == 0 && arg_count == 0) {
411
3
            if (value_is_crystal(obj)) {
412
0
                vm->error = strdup("cannot pop from a crystal array");
413
0
                *result = value_unit();
414
0
                return true;
415
0
            }
416
3
            if (obj->phase == VTAG_SUBLIMATED) {
417
1
                vm->error = strdup("cannot pop from a sublimated array");
418
1
                *result = value_unit();
419
1
                return true;
420
1
            }
421
            /* Check pressure constraints */
422
2
            {
423
2
                if (vm->rt && vm->rt->pressure_count > 0) {
424
1
                    RegCallFrame *cf = &vm->frames[vm->frame_count - 1];
425
1
                    if (cf->chunk && cf->chunk->local_names) {
426
2
                        for (size_t r = 0; r < cf->chunk->local_name_cap; r++) {
427
2
                            if (&cf->regs[r] == obj && cf->chunk->local_names[r] && cf->chunk->local_names[r][0]) {
428
1
                                for (size_t pi = 0; pi < vm->rt->pressure_count; pi++) {
429
1
                                    if (strcmp(vm->rt->pressures[pi].name, cf->chunk->local_names[r]) == 0) {
430
1
                                        const char *mode = vm->rt->pressures[pi].mode;
431
1
                                        if (strcmp(mode, "no_shrink") == 0 || strcmp(mode, "no_resize") == 0) {
432
1
                                            (void)asprintf(&vm->error, "pressurized (%s): cannot pop from '%s'",
433
1
                                                           mode, cf->chunk->local_names[r]);
434
1
                                            *result = value_unit();
435
1
                                            return true;
436
1
                                        }
437
1
                                    }
438
1
                                }
439
0
                                break;
440
1
                            }
441
2
                        }
442
1
                    }
443
1
                }
444
2
            }
445
1
            if (obj->as.array.len == 0) {
446
0
                *result = value_nil();
447
1
            } else {
448
1
                *result = obj->as.array.elems[--obj->as.array.len];
449
1
            }
450
1
            return true;
451
2
        }
452
89
        if (strcmp(method, "contains") == 0 && arg_count == 1) {
453
8
            bool found = false;
454
2.61k
            for (size_t i = 0; i < obj->as.array.len; i++) {
455
2.61k
                if (value_eq(&obj->as.array.elems[i], &args[0])) {
456
6
                    found = true;
457
6
                    break;
458
6
                }
459
2.61k
            }
460
8
            *result = value_bool(found);
461
8
            return true;
462
8
        }
463
81
        if (strcmp(method, "reverse") == 0 && arg_count == 0) {
464
1
            LatValue *elems = malloc(obj->as.array.len * sizeof(LatValue));
465
4
            for (size_t i = 0; i < obj->as.array.len; i++)
466
3
                elems[i] = rvm_clone(&obj->as.array.elems[obj->as.array.len - 1 - i]);
467
1
            *result = value_array(elems, obj->as.array.len);
468
1
            free(elems);
469
1
            return true;
470
1
        }
471
80
        if (strcmp(method, "map") == 0 && arg_count == 1) {
472
7
            LatValue *closure = &args[0];
473
7
            size_t len = obj->as.array.len;
474
7
            LatValue *elems = malloc(len * sizeof(LatValue));
475
139
            for (size_t i = 0; i < len; i++) {
476
132
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
477
132
                elems[i] = regvm_call_closure(vm, closure, &arg, 1);
478
132
                value_free(&arg);
479
132
            }
480
7
            *result = value_array(elems, len);
481
7
            free(elems);
482
7
            return true;
483
7
        }
484
73
        if (strcmp(method, "filter") == 0 && arg_count == 1) {
485
3
            LatValue *closure = &args[0];
486
3
            size_t len = obj->as.array.len;
487
3
            size_t cap = len > 0 ? len : 1;
488
3
            LatValue *elems = malloc(cap * sizeof(LatValue));
489
3
            size_t out_len = 0;
490
75
            for (size_t i = 0; i < len; i++) {
491
72
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
492
72
                LatValue pred = regvm_call_closure(vm, closure, &arg, 1);
493
72
                bool keep = (pred.type == VAL_BOOL && pred.as.bool_val);
494
72
                value_free(&pred);
495
72
                if (keep) {
496
33
                    elems[out_len++] = arg;
497
39
                } else {
498
39
                    value_free(&arg);
499
39
                }
500
72
            }
501
3
            *result = value_array(elems, out_len);
502
3
            free(elems);
503
3
            return true;
504
3
        }
505
70
        if (strcmp(method, "join") == 0 && arg_count == 1) {
506
1
            const char *sep = (args[0].type == VAL_STR) ? args[0].as.str_val : "";
507
1
            size_t sep_len = strlen(sep);
508
1
            size_t n = obj->as.array.len;
509
1
            char **parts = malloc(n * sizeof(char *));
510
1
            size_t *lens = malloc(n * sizeof(size_t));
511
1
            size_t total = 0;
512
5
            for (size_t i = 0; i < n; i++) {
513
4
                parts[i] = value_display(&obj->as.array.elems[i]);
514
4
                lens[i] = strlen(parts[i]);
515
4
                total += lens[i];
516
4
            }
517
1
            if (n > 1) total += sep_len * (n - 1);
518
1
            char *buf = malloc(total + 1);
519
1
            size_t pos = 0;
520
5
            for (size_t i = 0; i < n; i++) {
521
4
                if (i > 0) { memcpy(buf + pos, sep, sep_len); pos += sep_len; }
522
4
                memcpy(buf + pos, parts[i], lens[i]); pos += lens[i];
523
4
                free(parts[i]);
524
4
            }
525
1
            buf[pos] = '\0';
526
1
            free(parts); free(lens);
527
1
            *result = value_string_owned(buf);
528
1
            return true;
529
1
        }
530
70
    }
531
532
2.63k
    if (obj->type == VAL_STR) {
533
199
        if (strcmp(method, "len") == 0 && arg_count == 0) {
534
8
            *result = value_int((int64_t)strlen(obj->as.str_val));
535
8
            return true;
536
8
        }
537
191
        if (strcmp(method, "contains") == 0 && arg_count == 1) {
538
5
            if (args[0].type == VAL_STR) {
539
5
                *result = value_bool(strstr(obj->as.str_val, args[0].as.str_val) != NULL);
540
5
            } else {
541
0
                *result = value_bool(false);
542
0
            }
543
5
            return true;
544
5
        }
545
191
    }
546
547
2.62k
    if (obj->type == VAL_MAP) {
548
2.26k
        if (strcmp(method, "len") == 0 && arg_count == 0) {
549
6
            size_t count = 0;
550
102
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
551
96
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED)
552
9
                    count++;
553
96
            }
554
6
            *result = value_int((int64_t)count);
555
6
            return true;
556
6
        }
557
2.25k
        if (strcmp(method, "keys") == 0 && arg_count == 0) {
558
15
            size_t cap = obj->as.map.map->cap;
559
15
            LatValue *keys = malloc(cap * sizeof(LatValue));
560
15
            size_t count = 0;
561
255
            for (size_t i = 0; i < cap; i++) {
562
240
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED)
563
25
                    keys[count++] = value_string(obj->as.map.map->entries[i].key);
564
240
            }
565
15
            *result = value_array(keys, count);
566
15
            free(keys);
567
15
            return true;
568
15
        }
569
2.24k
        if (strcmp(method, "values") == 0 && arg_count == 0) {
570
1
            size_t cap = obj->as.map.map->cap;
571
1
            LatValue *vals = malloc(cap * sizeof(LatValue));
572
1
            size_t count = 0;
573
17
            for (size_t i = 0; i < cap; i++) {
574
16
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED)
575
1
                    vals[count++] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value);
576
16
            }
577
1
            *result = value_array(vals, count);
578
1
            free(vals);
579
1
            return true;
580
1
        }
581
2.24k
        if (strcmp(method, "get") == 0 && arg_count == 1) {
582
1.00k
            if (args[0].type == VAL_STR) {
583
1.00k
                LatValue *val = lat_map_get(obj->as.map.map, args[0].as.str_val);
584
1.00k
                *result = val ? rvm_clone(val) : value_nil();
585
1.00k
            } else {
586
0
                *result = value_nil();
587
0
            }
588
1.00k
            return true;
589
1.00k
        }
590
1.24k
        if (strcmp(method, "set") == 0 && arg_count == 2) {
591
955
            if (args[0].type == VAL_STR) {
592
955
                LatValue cloned = rvm_clone(&args[1]);
593
955
                lat_map_set(obj->as.map.map, args[0].as.str_val, &cloned);
594
955
            }
595
955
            *result = value_unit();
596
955
            return true;
597
955
        }
598
287
        if ((strcmp(method, "has") == 0 || strcmp(method, "contains") == 0) && arg_count == 1) {
599
111
            if (args[0].type == VAL_STR)
600
111
                *result = value_bool(lat_map_get(obj->as.map.map, args[0].as.str_val) != NULL);
601
0
            else
602
0
                *result = value_bool(false);
603
111
            return true;
604
111
        }
605
176
        if (strcmp(method, "entries") == 0 && arg_count == 0) {
606
1
            size_t cap = obj->as.map.map->cap;
607
1
            LatValue *entries = malloc(cap * sizeof(LatValue));
608
1
            size_t count = 0;
609
17
            for (size_t i = 0; i < cap; i++) {
610
16
                if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
611
1
                LatValue pair[2];
612
1
                pair[0] = value_string(obj->as.map.map->entries[i].key);
613
1
                pair[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value);
614
1
                entries[count++] = value_array(pair, 2);
615
1
            }
616
1
            *result = value_array(entries, count);
617
1
            free(entries);
618
1
            return true;
619
1
        }
620
175
        if (strcmp(method, "merge") == 0 && arg_count == 1) {
621
1
            if (args[0].type == VAL_MAP) {
622
                /* Mutate obj in place (like stack VM) */
623
17
                for (size_t i = 0; i < args[0].as.map.map->cap; i++) {
624
16
                    if (args[0].as.map.map->entries[i].state != MAP_OCCUPIED) continue;
625
1
                    LatValue v = rvm_clone((LatValue *)args[0].as.map.map->entries[i].value);
626
1
                    lat_map_set(obj->as.map.map, args[0].as.map.map->entries[i].key, &v);
627
1
                }
628
1
            }
629
1
            *result = value_unit();
630
1
            return true;
631
1
        }
632
174
        if (strcmp(method, "for_each") == 0 && arg_count == 1) {
633
1
            LatValue *closure = &args[0];
634
17
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
635
16
                if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
636
1
                LatValue cb_args[2];
637
1
                cb_args[0] = value_string(obj->as.map.map->entries[i].key);
638
1
                cb_args[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value);
639
1
                LatValue ret = regvm_call_closure(vm, closure, cb_args, 2);
640
1
                value_free(&cb_args[0]);
641
1
                value_free(&cb_args[1]);
642
1
                value_free(&ret);
643
1
            }
644
1
            *result = value_unit();
645
1
            return true;
646
1
        }
647
173
        if (strcmp(method, "filter") == 0 && arg_count == 1) {
648
2
            LatValue *closure = &args[0];
649
2
            LatValue filtered = value_map_new();
650
34
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
651
32
                if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
652
4
                LatValue cb_args[2];
653
4
                cb_args[0] = value_string(obj->as.map.map->entries[i].key);
654
4
                cb_args[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value);
655
4
                LatValue pred = regvm_call_closure(vm, closure, cb_args, 2);
656
4
                if (pred.type == VAL_BOOL && pred.as.bool_val) {
657
2
                    LatValue v = rvm_clone((LatValue *)obj->as.map.map->entries[i].value);
658
2
                    lat_map_set(filtered.as.map.map, obj->as.map.map->entries[i].key, &v);
659
2
                }
660
4
                value_free(&cb_args[0]);
661
4
                value_free(&cb_args[1]);
662
4
                value_free(&pred);
663
4
            }
664
2
            *result = filtered;
665
2
            return true;
666
2
        }
667
173
    }
668
669
    /* ── Array additional methods ── */
670
532
    if (obj->type == VAL_ARRAY) {
671
69
        if (strcmp(method, "enumerate") == 0 && arg_count == 0) {
672
1
            size_t len = obj->as.array.len;
673
1
            LatValue *elems = malloc(len * sizeof(LatValue));
674
4
            for (size_t i = 0; i < len; i++) {
675
3
                LatValue pair[2];
676
3
                pair[0] = value_int((int64_t)i);
677
3
                pair[1] = rvm_clone(&obj->as.array.elems[i]);
678
3
                elems[i] = value_array(pair, 2);
679
3
            }
680
1
            *result = value_array(elems, len);
681
1
            free(elems);
682
1
            return true;
683
1
        }
684
68
        if (strcmp(method, "reduce") == 0 && (arg_count == 1 || arg_count == 2)) {
685
4
            LatValue *closure = &args[0];
686
4
            LatValue acc;
687
4
            size_t start = 0;
688
4
            if (arg_count == 2) {
689
4
                acc = rvm_clone(&args[1]);
690
4
            } else if (obj->as.array.len > 0) {
691
0
                acc = rvm_clone(&obj->as.array.elems[0]);
692
0
                start = 1;
693
0
            } else {
694
0
                *result = value_nil();
695
0
                return true;
696
0
            }
697
14
            for (size_t i = start; i < obj->as.array.len; i++) {
698
10
                LatValue cb_args[2];
699
10
                cb_args[0] = acc;
700
10
                cb_args[1] = rvm_clone(&obj->as.array.elems[i]);
701
10
                acc = regvm_call_closure(vm, closure, cb_args, 2);
702
10
                value_free(&cb_args[0]);
703
10
                value_free(&cb_args[1]);
704
10
            }
705
4
            *result = acc;
706
4
            return true;
707
4
        }
708
64
        if ((strcmp(method, "each") == 0 || strcmp(method, "for_each") == 0) && arg_count == 1) {
709
1
            LatValue *closure = &args[0];
710
4
            for (size_t i = 0; i < obj->as.array.len; i++) {
711
3
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
712
3
                LatValue ret = regvm_call_closure(vm, closure, &arg, 1);
713
3
                value_free(&arg);
714
3
                value_free(&ret);
715
3
            }
716
1
            *result = value_unit();
717
1
            return true;
718
1
        }
719
63
        if (strcmp(method, "sort") == 0 && arg_count <= 1) {
720
5
            size_t len = obj->as.array.len;
721
5
            LatValue *sorted = malloc(len * sizeof(LatValue));
722
16
            for (size_t i = 0; i < len; i++)
723
11
                sorted[i] = rvm_clone(&obj->as.array.elems[i]);
724
            /* Insertion sort */
725
11
            for (size_t i = 1; i < len; i++) {
726
7
                LatValue key = sorted[i];
727
7
                int64_t j = (int64_t)i - 1;
728
13
                while (j >= 0) {
729
10
                    bool swap = false;
730
10
                    if (arg_count == 1) {
731
0
                        LatValue cb_args[2] = { rvm_clone(&sorted[j]), rvm_clone(&key) };
732
0
                        LatValue cmp = regvm_call_closure(vm, &args[0], cb_args, 2);
733
0
                        swap = (cmp.type == VAL_INT && cmp.as.int_val > 0) ||
734
0
                               (cmp.type == VAL_FLOAT && cmp.as.float_val > 0);
735
0
                        value_free(&cmp);
736
0
                        value_free(&cb_args[0]);
737
0
                        value_free(&cb_args[1]);
738
10
                    } else {
739
10
                        if (sorted[j].type == VAL_INT && key.type == VAL_INT)
740
3
                            swap = sorted[j].as.int_val > key.as.int_val;
741
7
                        else if ((sorted[j].type == VAL_FLOAT || sorted[j].type == VAL_INT) &&
742
7
                                 (key.type == VAL_FLOAT || key.type == VAL_INT)) {
743
3
                            double a = sorted[j].type == VAL_FLOAT ? sorted[j].as.float_val : (double)sorted[j].as.int_val;
744
3
                            double b = key.type == VAL_FLOAT ? key.as.float_val : (double)key.as.int_val;
745
3
                            swap = a > b;
746
4
                        } else if (sorted[j].type == VAL_STR && key.type == VAL_STR) {
747
3
                            swap = strcmp(sorted[j].as.str_val, key.as.str_val) > 0;
748
3
                        } else {
749
3
                            for (size_t k = 0; k < len; k++) value_free(&sorted[k]);
750
1
                            free(sorted);
751
1
                            vm->error = strdup("sort: cannot compare values of different types");
752
1
                            *result = value_unit();
753
1
                            return true;
754
1
                        }
755
10
                    }
756
9
                    if (!swap) break;
757
6
                    sorted[j + 1] = sorted[j];
758
6
                    j--;
759
6
                }
760
6
                sorted[j + 1] = key;
761
6
            }
762
4
            *result = value_array(sorted, len);
763
4
            free(sorted);
764
4
            return true;
765
5
        }
766
58
        if (strcmp(method, "sort_by") == 0 && arg_count == 1) {
767
2
            LatValue *closure = &args[0];
768
2
            size_t len = obj->as.array.len;
769
2
            LatValue *buf = malloc((len > 0 ? len : 1) * sizeof(LatValue));
770
12
            for (size_t i = 0; i < len; i++)
771
10
                buf[i] = rvm_clone(&obj->as.array.elems[i]);
772
            /* Insertion sort using comparator: closure(a, b) < 0 means a < b */
773
10
            for (size_t i = 1; i < len; i++) {
774
8
                LatValue key = buf[i];
775
8
                size_t j = i;
776
17
                while (j > 0) {
777
14
                    LatValue ca[2];
778
14
                    ca[0] = rvm_clone(&key);
779
14
                    ca[1] = rvm_clone(&buf[j - 1]);
780
14
                    LatValue cmp = regvm_call_closure(vm, closure, ca, 2);
781
14
                    value_free(&ca[0]); value_free(&ca[1]);
782
14
                    if (cmp.type != VAL_INT || cmp.as.int_val >= 0) { value_free(&cmp); break; }
783
9
                    value_free(&cmp);
784
9
                    buf[j] = buf[j - 1]; j--;
785
9
                }
786
8
                buf[j] = key;
787
8
            }
788
2
            *result = value_array(buf, len);
789
2
            free(buf);
790
2
            return true;
791
2
        }
792
56
        if (strcmp(method, "find") == 0 && arg_count == 1) {
793
2
            LatValue *closure = &args[0];
794
8
            for (size_t i = 0; i < obj->as.array.len; i++) {
795
7
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
796
7
                LatValue pred = regvm_call_closure(vm, closure, &arg, 1);
797
7
                bool found = pred.type == VAL_BOOL && pred.as.bool_val;
798
7
                value_free(&pred);
799
7
                if (found) { *result = arg; return true; }
800
6
                value_free(&arg);
801
6
            }
802
1
            *result = value_unit();
803
1
            return true;
804
2
        }
805
54
        if (strcmp(method, "any") == 0 && arg_count == 1) {
806
2
            LatValue *closure = &args[0];
807
7
            for (size_t i = 0; i < obj->as.array.len; i++) {
808
6
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
809
6
                LatValue pred = regvm_call_closure(vm, closure, &arg, 1);
810
6
                bool yes = pred.type == VAL_BOOL && pred.as.bool_val;
811
6
                value_free(&pred);
812
6
                value_free(&arg);
813
6
                if (yes) { *result = value_bool(true); return true; }
814
6
            }
815
1
            *result = value_bool(false);
816
1
            return true;
817
2
        }
818
52
        if (strcmp(method, "all") == 0 && arg_count == 1) {
819
2
            LatValue *closure = &args[0];
820
5
            for (size_t i = 0; i < obj->as.array.len; i++) {
821
4
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
822
4
                LatValue pred = regvm_call_closure(vm, closure, &arg, 1);
823
4
                bool yes = pred.type == VAL_BOOL && pred.as.bool_val;
824
4
                value_free(&pred);
825
4
                value_free(&arg);
826
4
                if (!yes) { *result = value_bool(false); return true; }
827
4
            }
828
1
            *result = value_bool(true);
829
1
            return true;
830
2
        }
831
50
        if (strcmp(method, "flat_map") == 0 && arg_count == 1) {
832
3
            LatValue *closure = &args[0];
833
3
            size_t cap = obj->as.array.len * 2;
834
3
            LatValue *elems = malloc(cap * sizeof(LatValue));
835
3
            size_t out = 0;
836
9
            for (size_t i = 0; i < obj->as.array.len; i++) {
837
6
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
838
6
                LatValue mapped = regvm_call_closure(vm, closure, &arg, 1);
839
6
                value_free(&arg);
840
6
                if (mapped.type == VAL_ARRAY) {
841
9
                    for (size_t j = 0; j < mapped.as.array.len; j++) {
842
6
                        if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
843
6
                        elems[out++] = rvm_clone(&mapped.as.array.elems[j]);
844
6
                    }
845
3
                    value_free(&mapped);
846
3
                } else {
847
3
                    if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
848
3
                    elems[out++] = mapped;
849
3
                }
850
6
            }
851
3
            *result = value_array(elems, out);
852
3
            free(elems);
853
3
            return true;
854
3
        }
855
47
        if (strcmp(method, "unique") == 0 && arg_count == 0) {
856
1
            size_t len = obj->as.array.len;
857
1
            LatValue *elems = malloc(len * sizeof(LatValue));
858
1
            size_t out = 0;
859
7
            for (size_t i = 0; i < len; i++) {
860
6
                bool dup = false;
861
13
                for (size_t j = 0; j < out; j++) {
862
9
                    if (value_eq(&obj->as.array.elems[i], &elems[j])) { dup = true; break; }
863
9
                }
864
6
                if (!dup) elems[out++] = rvm_clone(&obj->as.array.elems[i]);
865
6
            }
866
1
            *result = value_array(elems, out);
867
1
            free(elems);
868
1
            return true;
869
1
        }
870
46
        if (strcmp(method, "index_of") == 0 && arg_count == 1) {
871
6
            for (size_t i = 0; i < obj->as.array.len; i++) {
872
5
                if (value_eq(&obj->as.array.elems[i], &args[0])) {
873
1
                    *result = value_int((int64_t)i);
874
1
                    return true;
875
1
                }
876
5
            }
877
1
            *result = value_int(-1);
878
1
            return true;
879
2
        }
880
44
        if (strcmp(method, "first") == 0 && arg_count == 0) {
881
9
            *result = obj->as.array.len > 0 ? rvm_clone(&obj->as.array.elems[0]) : value_unit();
882
9
            return true;
883
9
        }
884
35
        if (strcmp(method, "last") == 0 && arg_count == 0) {
885
3
            *result = obj->as.array.len > 0 ? rvm_clone(&obj->as.array.elems[obj->as.array.len - 1]) : value_unit();
886
3
            return true;
887
3
        }
888
32
        if (strcmp(method, "slice") == 0 && (arg_count == 1 || arg_count == 2)) {
889
4
            int64_t start = args[0].type == VAL_INT ? args[0].as.int_val : 0;
890
4
            int64_t end = arg_count == 2 && args[1].type == VAL_INT ? args[1].as.int_val : (int64_t)obj->as.array.len;
891
4
            if (start < 0) start += (int64_t)obj->as.array.len;
892
4
            if (end < 0) end += (int64_t)obj->as.array.len;
893
4
            if (start < 0) start = 0;
894
4
            if (end > (int64_t)obj->as.array.len) end = (int64_t)obj->as.array.len;
895
4
            if (start >= end) { *result = value_array(NULL, 0); return true; }
896
3
            size_t count = (size_t)(end - start);
897
3
            LatValue *elems = malloc(count * sizeof(LatValue));
898
11
            for (size_t i = 0; i < count; i++)
899
8
                elems[i] = rvm_clone(&obj->as.array.elems[start + (int64_t)i]);
900
3
            *result = value_array(elems, count);
901
3
            free(elems);
902
3
            return true;
903
4
        }
904
28
        if (strcmp(method, "take") == 0 && arg_count == 1) {
905
3
            int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0;
906
3
            if (n < 0) n = 0;
907
3
            if (n > (int64_t)obj->as.array.len) n = (int64_t)obj->as.array.len;
908
3
            LatValue *elems = malloc((size_t)n * sizeof(LatValue));
909
8
            for (int64_t i = 0; i < n; i++)
910
5
                elems[i] = rvm_clone(&obj->as.array.elems[i]);
911
3
            *result = value_array(elems, (size_t)n);
912
3
            free(elems);
913
3
            return true;
914
3
        }
915
25
        if (strcmp(method, "drop") == 0 && arg_count == 1) {
916
3
            int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0;
917
3
            if (n < 0) n = 0;
918
3
            if (n > (int64_t)obj->as.array.len) n = (int64_t)obj->as.array.len;
919
3
            size_t count = obj->as.array.len - (size_t)n;
920
3
            LatValue *elems = malloc(count * sizeof(LatValue));
921
9
            for (size_t i = 0; i < count; i++)
922
6
                elems[i] = rvm_clone(&obj->as.array.elems[n + (int64_t)i]);
923
3
            *result = value_array(elems, count);
924
3
            free(elems);
925
3
            return true;
926
3
        }
927
22
        if (strcmp(method, "flatten") == 0 && arg_count == 0) {
928
0
            size_t cap = obj->as.array.len * 2;
929
0
            LatValue *elems = malloc(cap * sizeof(LatValue));
930
0
            size_t out = 0;
931
0
            for (size_t i = 0; i < obj->as.array.len; i++) {
932
0
                if (obj->as.array.elems[i].type == VAL_ARRAY) {
933
0
                    LatValue *inner = &obj->as.array.elems[i];
934
0
                    for (size_t j = 0; j < inner->as.array.len; j++) {
935
0
                        if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
936
0
                        elems[out++] = rvm_clone(&inner->as.array.elems[j]);
937
0
                    }
938
0
                } else {
939
0
                    if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
940
0
                    elems[out++] = rvm_clone(&obj->as.array.elems[i]);
941
0
                }
942
0
            }
943
0
            *result = value_array(elems, out);
944
0
            free(elems);
945
0
            return true;
946
0
        }
947
22
        if (strcmp(method, "zip") == 0 && arg_count == 1) {
948
1
            if (args[0].type != VAL_ARRAY) { *result = value_array(NULL, 0); return true; }
949
1
            size_t len = obj->as.array.len < args[0].as.array.len ? obj->as.array.len : args[0].as.array.len;
950
1
            LatValue *elems = malloc(len * sizeof(LatValue));
951
3
            for (size_t i = 0; i < len; i++) {
952
2
                LatValue pair[2];
953
2
                pair[0] = rvm_clone(&obj->as.array.elems[i]);
954
2
                pair[1] = rvm_clone(&args[0].as.array.elems[i]);
955
2
                elems[i] = value_array(pair, 2);
956
2
            }
957
1
            *result = value_array(elems, len);
958
1
            free(elems);
959
1
            return true;
960
1
        }
961
21
        if (strcmp(method, "sum") == 0 && arg_count == 0) {
962
3
            int64_t isum = 0; double fsum = 0; bool has_float = false;
963
11
            for (size_t i = 0; i < obj->as.array.len; i++) {
964
8
                if (obj->as.array.elems[i].type == VAL_FLOAT) { has_float = true; fsum += obj->as.array.elems[i].as.float_val; }
965
5
                else if (obj->as.array.elems[i].type == VAL_INT) { isum += obj->as.array.elems[i].as.int_val; fsum += (double)obj->as.array.elems[i].as.int_val; }
966
8
            }
967
3
            *result = has_float ? value_float(fsum) : value_int(isum);
968
3
            return true;
969
3
        }
970
18
        if (strcmp(method, "min") == 0 && arg_count == 0) {
971
3
            if (obj->as.array.len == 0) {
972
1
                vm->error = strdup("min() called on empty array");
973
1
                *result = value_unit();
974
1
                return true;
975
1
            }
976
2
            LatValue min_val = obj->as.array.elems[0];
977
8
            for (size_t i = 1; i < obj->as.array.len; i++) {
978
6
                if (obj->as.array.elems[i].type == VAL_INT && min_val.type == VAL_INT) {
979
4
                    if (obj->as.array.elems[i].as.int_val < min_val.as.int_val) min_val = obj->as.array.elems[i];
980
4
                } else if (obj->as.array.elems[i].type == VAL_FLOAT || min_val.type == VAL_FLOAT) {
981
2
                    double a = obj->as.array.elems[i].type == VAL_FLOAT ? obj->as.array.elems[i].as.float_val : (double)obj->as.array.elems[i].as.int_val;
982
2
                    double b = min_val.type == VAL_FLOAT ? min_val.as.float_val : (double)min_val.as.int_val;
983
2
                    if (a < b) min_val = obj->as.array.elems[i];
984
2
                }
985
6
            }
986
2
            *result = rvm_clone(&min_val);
987
2
            return true;
988
3
        }
989
15
        if (strcmp(method, "max") == 0 && arg_count == 0) {
990
3
            if (obj->as.array.len == 0) {
991
1
                vm->error = strdup("max() called on empty array");
992
1
                *result = value_unit();
993
1
                return true;
994
1
            }
995
2
            LatValue max_val = obj->as.array.elems[0];
996
8
            for (size_t i = 1; i < obj->as.array.len; i++) {
997
6
                if (obj->as.array.elems[i].type == VAL_INT && max_val.type == VAL_INT) {
998
4
                    if (obj->as.array.elems[i].as.int_val > max_val.as.int_val) max_val = obj->as.array.elems[i];
999
4
                } else if (obj->as.array.elems[i].type == VAL_FLOAT || max_val.type == VAL_FLOAT) {
1000
2
                    double a = obj->as.array.elems[i].type == VAL_FLOAT ? obj->as.array.elems[i].as.float_val : (double)obj->as.array.elems[i].as.int_val;
1001
2
                    double b = max_val.type == VAL_FLOAT ? max_val.as.float_val : (double)max_val.as.int_val;
1002
2
                    if (a > b) max_val = obj->as.array.elems[i];
1003
2
                }
1004
6
            }
1005
2
            *result = rvm_clone(&max_val);
1006
2
            return true;
1007
3
        }
1008
12
        if (strcmp(method, "insert") == 0 && arg_count == 2) {
1009
1
            if (args[0].type != VAL_INT) { *result = value_unit(); return true; }
1010
1
            int64_t idx = args[0].as.int_val;
1011
1
            if (idx < 0) idx += (int64_t)obj->as.array.len;
1012
1
            if (idx < 0) idx = 0;
1013
1
            if (idx > (int64_t)obj->as.array.len) idx = (int64_t)obj->as.array.len;
1014
1
            if (obj->as.array.len >= obj->as.array.cap) {
1015
0
                obj->as.array.cap = obj->as.array.cap ? obj->as.array.cap * 2 : 4;
1016
0
                obj->as.array.elems = realloc(obj->as.array.elems, obj->as.array.cap * sizeof(LatValue));
1017
0
            }
1018
1
            memmove(&obj->as.array.elems[idx + 1], &obj->as.array.elems[idx],
1019
1
                    (obj->as.array.len - (size_t)idx) * sizeof(LatValue));
1020
1
            obj->as.array.elems[idx] = rvm_clone(&args[1]);
1021
1
            obj->as.array.len++;
1022
1
            *result = value_unit();
1023
1
            return true;
1024
1
        }
1025
11
        if (strcmp(method, "remove_at") == 0 && arg_count == 1) {
1026
1
            if (args[0].type != VAL_INT) { *result = value_nil(); return true; }
1027
1
            int64_t idx = args[0].as.int_val;
1028
1
            if (idx < 0) idx += (int64_t)obj->as.array.len;
1029
1
            if (idx < 0 || (size_t)idx >= obj->as.array.len) { *result = value_nil(); return true; }
1030
1
            *result = obj->as.array.elems[idx];
1031
1
            memmove(&obj->as.array.elems[idx], &obj->as.array.elems[idx + 1],
1032
1
                    (obj->as.array.len - (size_t)idx - 1) * sizeof(LatValue));
1033
1
            obj->as.array.len--;
1034
1
            return true;
1035
1
        }
1036
10
        if (strcmp(method, "chunk") == 0 && arg_count == 1) {
1037
5
            if (args[0].type != VAL_INT || args[0].as.int_val <= 0) { *result = value_array(NULL, 0); return true; }
1038
5
            int64_t sz = args[0].as.int_val;
1039
5
            size_t len = obj->as.array.len;
1040
5
            size_t chunks = (len + (size_t)sz - 1) / (size_t)sz;
1041
5
            LatValue *elems = malloc(chunks * sizeof(LatValue));
1042
14
            for (size_t i = 0; i < chunks; i++) {
1043
9
                size_t start = i * (size_t)sz;
1044
9
                size_t end = start + (size_t)sz;
1045
9
                if (end > len) end = len;
1046
9
                size_t count = end - start;
1047
9
                LatValue *chunk_elems = malloc(count * sizeof(LatValue));
1048
25
                for (size_t j = 0; j < count; j++)
1049
16
                    chunk_elems[j] = rvm_clone(&obj->as.array.elems[start + j]);
1050
9
                elems[i] = value_array(chunk_elems, count);
1051
9
                free(chunk_elems);
1052
9
            }
1053
5
            *result = value_array(elems, chunks);
1054
5
            free(elems);
1055
5
            return true;
1056
5
        }
1057
5
        if (strcmp(method, "group_by") == 0 && arg_count == 1) {
1058
1
            LatValue *closure = &args[0];
1059
1
            LatValue map = value_map_new();
1060
6
            for (size_t i = 0; i < obj->as.array.len; i++) {
1061
5
                LatValue arg = rvm_clone(&obj->as.array.elems[i]);
1062
5
                LatValue key = regvm_call_closure(vm, closure, &arg, 1);
1063
5
                char *key_str = value_display(&key);
1064
5
                LatValue *existing = lat_map_get(map.as.map.map, key_str);
1065
5
                if (existing && existing->type == VAL_ARRAY) {
1066
3
                    if (existing->as.array.len >= existing->as.array.cap) {
1067
0
                        existing->as.array.cap = existing->as.array.cap ? existing->as.array.cap * 2 : 4;
1068
0
                        existing->as.array.elems = realloc(existing->as.array.elems, existing->as.array.cap * sizeof(LatValue));
1069
0
                    }
1070
3
                    existing->as.array.elems[existing->as.array.len++] = arg;
1071
3
                } else {
1072
2
                    LatValue arr = value_array(&arg, 1);
1073
2
                    lat_map_set(map.as.map.map, key_str, &arr);
1074
2
                }
1075
5
                value_free(&key);
1076
5
                free(key_str);
1077
5
            }
1078
1
            *result = map;
1079
1
            return true;
1080
1
        }
1081
5
    }
1082
1083
    /* ── Array additional methods ── */
1084
467
    if (obj->type == VAL_ARRAY) {
1085
4
        if (strcmp(method, "flat") == 0 && arg_count == 0) {
1086
4
            size_t cap = obj->as.array.len * 2;
1087
4
            if (cap == 0) cap = 1;
1088
4
            LatValue *elems = malloc(cap * sizeof(LatValue));
1089
4
            size_t out = 0;
1090
13
            for (size_t i = 0; i < obj->as.array.len; i++) {
1091
9
                LatValue *el = &obj->as.array.elems[i];
1092
9
                if (el->type == VAL_ARRAY) {
1093
13
                    for (size_t j = 0; j < el->as.array.len; j++) {
1094
8
                        if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
1095
8
                        elems[out++] = rvm_clone(&el->as.array.elems[j]);
1096
8
                    }
1097
5
                } else {
1098
4
                    if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
1099
4
                    elems[out++] = rvm_clone(el);
1100
4
                }
1101
9
            }
1102
4
            *result = value_array(elems, out);
1103
4
            free(elems);
1104
4
            return true;
1105
4
        }
1106
0
        if (strcmp(method, "first") == 0 && arg_count == 0) {
1107
0
            *result = (obj->as.array.len > 0) ? rvm_clone(&obj->as.array.elems[0]) : value_unit();
1108
0
            return true;
1109
0
        }
1110
0
        if (strcmp(method, "last") == 0 && arg_count == 0) {
1111
0
            *result = (obj->as.array.len > 0) ? rvm_clone(&obj->as.array.elems[obj->as.array.len - 1]) : value_unit();
1112
0
            return true;
1113
0
        }
1114
0
        if (strcmp(method, "min") == 0 && arg_count == 0) {
1115
0
            if (obj->as.array.len == 0) { *result = value_unit(); return true; }
1116
0
            LatValue best = rvm_clone(&obj->as.array.elems[0]);
1117
0
            for (size_t i = 1; i < obj->as.array.len; i++) {
1118
0
                LatValue *el = &obj->as.array.elems[i];
1119
0
                bool less = false;
1120
0
                if (el->type == VAL_INT && best.type == VAL_INT)
1121
0
                    less = el->as.int_val < best.as.int_val;
1122
0
                else if (el->type == VAL_FLOAT || best.type == VAL_FLOAT) {
1123
0
                    double a = el->type == VAL_FLOAT ? el->as.float_val : (double)el->as.int_val;
1124
0
                    double b = best.type == VAL_FLOAT ? best.as.float_val : (double)best.as.int_val;
1125
0
                    less = a < b;
1126
0
                }
1127
0
                if (less) { value_free(&best); best = rvm_clone(el); }
1128
0
            }
1129
0
            *result = best;
1130
0
            return true;
1131
0
        }
1132
0
        if (strcmp(method, "max") == 0 && arg_count == 0) {
1133
0
            if (obj->as.array.len == 0) { *result = value_unit(); return true; }
1134
0
            LatValue best = rvm_clone(&obj->as.array.elems[0]);
1135
0
            for (size_t i = 1; i < obj->as.array.len; i++) {
1136
0
                LatValue *el = &obj->as.array.elems[i];
1137
0
                bool greater = false;
1138
0
                if (el->type == VAL_INT && best.type == VAL_INT)
1139
0
                    greater = el->as.int_val > best.as.int_val;
1140
0
                else if (el->type == VAL_FLOAT || best.type == VAL_FLOAT) {
1141
0
                    double a = el->type == VAL_FLOAT ? el->as.float_val : (double)el->as.int_val;
1142
0
                    double b = best.type == VAL_FLOAT ? best.as.float_val : (double)best.as.int_val;
1143
0
                    greater = a > b;
1144
0
                }
1145
0
                if (greater) { value_free(&best); best = rvm_clone(el); }
1146
0
            }
1147
0
            *result = best;
1148
0
            return true;
1149
0
        }
1150
0
    }
1151
1152
    /* ── String additional methods ── */
1153
463
    if (obj->type == VAL_STR) {
1154
186
        if (strcmp(method, "split") == 0 && arg_count == 1) {
1155
11
            if (args[0].type != VAL_STR) { *result = value_array(NULL, 0); return true; }
1156
11
            const char *s = obj->as.str_val;
1157
11
            const char *sep = args[0].as.str_val;
1158
11
            size_t sep_len = strlen(sep);
1159
11
            size_t cap = 8;
1160
11
            LatValue *parts = malloc(cap * sizeof(LatValue));
1161
11
            size_t count = 0;
1162
11
            if (sep_len == 0) {
1163
0
                for (size_t i = 0; s[i]; i++) {
1164
0
                    if (count >= cap) { cap *= 2; parts = realloc(parts, cap * sizeof(LatValue)); }
1165
0
                    char c[2] = { s[i], '\0' };
1166
0
                    parts[count++] = value_string(c);
1167
0
                }
1168
11
            } else {
1169
11
                const char *p = s;
1170
16
                while (*p) {
1171
15
                    const char *found = strstr(p, sep);
1172
15
                    if (!found) { if (count >= cap) { cap *= 2; parts = realloc(parts, cap * sizeof(LatValue)); } parts[count++] = value_string(p); break; }
1173
5
                    if (count >= cap) { cap *= 2; parts = realloc(parts, cap * sizeof(LatValue)); }
1174
5
                    char *part = strndup(p, (size_t)(found - p));
1175
5
                    parts[count++] = value_string_owned(part);
1176
5
                    p = found + sep_len;
1177
5
                }
1178
11
            }
1179
11
            *result = value_array(parts, count);
1180
11
            free(parts);
1181
11
            return true;
1182
11
        }
1183
175
        if (strcmp(method, "trim") == 0 && arg_count == 0) {
1184
42
            const char *s = obj->as.str_val;
1185
45
            while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
1186
42
            const char *e = obj->as.str_val + strlen(obj->as.str_val);
1187
49
            while (e > s && (*(e-1) == ' ' || *(e-1) == '\t' || *(e-1) == '\n' || *(e-1) == '\r')) e--;
1188
42
            *result = value_string_owned(strndup(s, (size_t)(e - s)));
1189
42
            return true;
1190
42
        }
1191
133
        if (strcmp(method, "trim_start") == 0 && arg_count == 0) {
1192
1
            const char *s = obj->as.str_val;
1193
3
            while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
1194
1
            *result = value_string(s);
1195
1
            return true;
1196
1
        }
1197
132
        if (strcmp(method, "trim_end") == 0 && arg_count == 0) {
1198
1
            size_t len = strlen(obj->as.str_val);
1199
1
            const char *e = obj->as.str_val + len;
1200
3
            while (e > obj->as.str_val && (*(e-1) == ' ' || *(e-1) == '\t' || *(e-1) == '\n' || *(e-1) == '\r')) e--;
1201
1
            *result = value_string_owned(strndup(obj->as.str_val, (size_t)(e - obj->as.str_val)));
1202
1
            return true;
1203
1
        }
1204
131
        if (strcmp(method, "to_upper") == 0 && arg_count == 0) {
1205
3
            char *s = strdup(obj->as.str_val);
1206
24
            for (char *p = s; *p; p++) if (*p >= 'a' && *p <= 'z') *p -= 32;
1207
3
            *result = value_string_owned(s);
1208
3
            return true;
1209
3
        }
1210
128
        if (strcmp(method, "to_lower") == 0 && arg_count == 0) {
1211
2
            char *s = strdup(obj->as.str_val);
1212
18
            for (char *p = s; *p; p++) if (*p >= 'A' && *p <= 'Z') *p += 32;
1213
2
            *result = value_string_owned(s);
1214
2
            return true;
1215
2
        }
1216
126
        if (strcmp(method, "starts_with") == 0 && arg_count == 1) {
1217
45
            if (args[0].type == VAL_STR)
1218
45
                *result = value_bool(strncmp(obj->as.str_val, args[0].as.str_val, strlen(args[0].as.str_val)) == 0);
1219
0
            else
1220
0
                *result = value_bool(false);
1221
45
            return true;
1222
45
        }
1223
81
        if (strcmp(method, "ends_with") == 0 && arg_count == 1) {
1224
2
            if (args[0].type == VAL_STR) {
1225
2
                size_t slen = strlen(obj->as.str_val);
1226
2
                size_t plen = strlen(args[0].as.str_val);
1227
2
                *result = value_bool(plen <= slen && strcmp(obj->as.str_val + slen - plen, args[0].as.str_val) == 0);
1228
2
            } else {
1229
0
                *result = value_bool(false);
1230
0
            }
1231
2
            return true;
1232
2
        }
1233
79
        if (strcmp(method, "replace") == 0 && arg_count == 2) {
1234
2
            if (args[0].type != VAL_STR || args[1].type != VAL_STR) { *result = rvm_clone(obj); return true; }
1235
2
            const char *s = obj->as.str_val;
1236
2
            const char *from = args[0].as.str_val;
1237
2
            const char *to = args[1].as.str_val;
1238
2
            size_t from_len = strlen(from), to_len = strlen(to);
1239
2
            if (from_len == 0) { *result = rvm_clone(obj); return true; }
1240
2
            size_t cap = strlen(s) + 64;
1241
2
            char *buf = malloc(cap);
1242
2
            size_t pos = 0;
1243
14
            while (*s) {
1244
12
                if (strncmp(s, from, from_len) == 0) {
1245
5
                    while (pos + to_len >= cap) { cap *= 2; buf = realloc(buf, cap); }
1246
5
                    memcpy(buf + pos, to, to_len); pos += to_len; s += from_len;
1247
7
                } else {
1248
7
                    if (pos + 1 >= cap) { cap *= 2; buf = realloc(buf, cap); }
1249
7
                    buf[pos++] = *s++;
1250
7
                }
1251
12
            }
1252
2
            buf[pos] = '\0';
1253
2
            *result = value_string_owned(buf);
1254
2
            return true;
1255
2
        }
1256
77
        if (strcmp(method, "index_of") == 0 && arg_count == 1) {
1257
27
            if (args[0].type == VAL_STR) {
1258
27
                const char *found = strstr(obj->as.str_val, args[0].as.str_val);
1259
27
                *result = found ? value_int((int64_t)(found - obj->as.str_val)) : value_int(-1);
1260
27
            } else {
1261
0
                *result = value_int(-1);
1262
0
            }
1263
27
            return true;
1264
27
        }
1265
50
        if (strcmp(method, "substring") == 0 && (arg_count == 1 || arg_count == 2)) {
1266
31
            size_t slen = strlen(obj->as.str_val);
1267
31
            int64_t start = args[0].type == VAL_INT ? args[0].as.int_val : 0;
1268
31
            int64_t end = arg_count == 2 && args[1].type == VAL_INT ? args[1].as.int_val : (int64_t)slen;
1269
31
            if (start < 0) start += (int64_t)slen;
1270
31
            if (end < 0) end += (int64_t)slen;
1271
31
            if (start < 0) start = 0;
1272
31
            if (end > (int64_t)slen) end = (int64_t)slen;
1273
31
            if (start >= end) { *result = value_string(""); return true; }
1274
31
            *result = value_string_owned(strndup(obj->as.str_val + start, (size_t)(end - start)));
1275
31
            return true;
1276
31
        }
1277
19
        if (strcmp(method, "repeat") == 0 && arg_count == 1) {
1278
2
            if (args[0].type != VAL_INT || args[0].as.int_val < 0) { *result = value_string(""); return true; }
1279
2
            int64_t n = args[0].as.int_val;
1280
2
            size_t slen = strlen(obj->as.str_val);
1281
2
            char *buf = malloc(slen * (size_t)n + 1);
1282
5
            for (int64_t i = 0; i < n; i++)
1283
3
                memcpy(buf + i * (int64_t)slen, obj->as.str_val, slen);
1284
2
            buf[slen * (size_t)n] = '\0';
1285
2
            *result = value_string_owned(buf);
1286
2
            return true;
1287
2
        }
1288
17
        if (strcmp(method, "chars") == 0 && arg_count == 0) {
1289
7
            size_t len = strlen(obj->as.str_val);
1290
7
            LatValue *elems = malloc(len * sizeof(LatValue));
1291
94
            for (size_t i = 0; i < len; i++) {
1292
87
                char c[2] = { obj->as.str_val[i], '\0' };
1293
87
                elems[i] = value_string(c);
1294
87
            }
1295
7
            *result = value_array(elems, len);
1296
7
            free(elems);
1297
7
            return true;
1298
7
        }
1299
10
        if (strcmp(method, "bytes") == 0 && arg_count == 0) {
1300
1
            size_t len = strlen(obj->as.str_val);
1301
1
            LatValue *elems = malloc(len * sizeof(LatValue));
1302
4
            for (size_t i = 0; i < len; i++)
1303
3
                elems[i] = value_int((int64_t)(unsigned char)obj->as.str_val[i]);
1304
1
            *result = value_array(elems, len);
1305
1
            free(elems);
1306
1
            return true;
1307
1
        }
1308
9
        if (strcmp(method, "reverse") == 0 && arg_count == 0) {
1309
2
            size_t len = strlen(obj->as.str_val);
1310
2
            char *buf = malloc(len + 1);
1311
7
            for (size_t i = 0; i < len; i++) buf[i] = obj->as.str_val[len - 1 - i];
1312
2
            buf[len] = '\0';
1313
2
            *result = value_string_owned(buf);
1314
2
            return true;
1315
2
        }
1316
7
        if (strcmp(method, "pad_left") == 0 && (arg_count == 1 || arg_count == 2)) {
1317
1
            int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0;
1318
1
            char pad = (arg_count == 2 && args[1].type == VAL_STR && args[1].as.str_val[0]) ? args[1].as.str_val[0] : ' ';
1319
1
            size_t slen = strlen(obj->as.str_val);
1320
1
            if ((int64_t)slen >= n) { *result = rvm_clone(obj); return true; }
1321
1
            size_t plen = (size_t)n - slen;
1322
1
            char *buf = malloc((size_t)n + 1);
1323
1
            memset(buf, pad, plen);
1324
1
            memcpy(buf + plen, obj->as.str_val, slen);
1325
1
            buf[(size_t)n] = '\0';
1326
1
            *result = value_string_owned(buf);
1327
1
            return true;
1328
1
        }
1329
6
        if (strcmp(method, "pad_right") == 0 && (arg_count == 1 || arg_count == 2)) {
1330
1
            int64_t n = args[0].type == VAL_INT ? args[0].as.int_val : 0;
1331
1
            char pad = (arg_count == 2 && args[1].type == VAL_STR && args[1].as.str_val[0]) ? args[1].as.str_val[0] : ' ';
1332
1
            size_t slen = strlen(obj->as.str_val);
1333
1
            if ((int64_t)slen >= n) { *result = rvm_clone(obj); return true; }
1334
1
            char *buf = malloc((size_t)n + 1);
1335
1
            memcpy(buf, obj->as.str_val, slen);
1336
1
            memset(buf + slen, pad, (size_t)n - slen);
1337
1
            buf[(size_t)n] = '\0';
1338
1
            *result = value_string_owned(buf);
1339
1
            return true;
1340
1
        }
1341
6
    }
1342
1343
    /* ── Enum methods ── */
1344
282
    if (obj->type == VAL_ENUM) {
1345
6
        if (strcmp(method, "tag") == 0 || strcmp(method, "variant_name") == 0) {
1346
1
            *result = value_string(obj->as.enm.variant_name);
1347
1
            return true;
1348
1
        }
1349
5
        if (strcmp(method, "enum_name") == 0) {
1350
1
            *result = value_string(obj->as.enm.enum_name);
1351
1
            return true;
1352
1
        }
1353
4
        if (strcmp(method, "payload") == 0) {
1354
2
            if (obj->as.enm.payload_count > 0) {
1355
2
                LatValue *elems = malloc(obj->as.enm.payload_count * sizeof(LatValue));
1356
5
                for (size_t pi = 0; pi < obj->as.enm.payload_count; pi++)
1357
3
                    elems[pi] = rvm_clone(&obj->as.enm.payload[pi]);
1358
2
                *result = value_array(elems, obj->as.enm.payload_count);
1359
2
                free(elems);
1360
2
            } else {
1361
0
                *result = value_array(NULL, 0);
1362
0
            }
1363
2
            return true;
1364
2
        }
1365
2
        if (strcmp(method, "is_variant") == 0 && arg_count == 1) {
1366
2
            if (args[0].type == VAL_STR)
1367
2
                *result = value_bool(strcmp(obj->as.enm.variant_name, args[0].as.str_val) == 0);
1368
0
            else
1369
0
                *result = value_bool(false);
1370
2
            return true;
1371
2
        }
1372
2
    }
1373
1374
    /* ── Tuple methods ── */
1375
276
    if (obj->type == VAL_TUPLE) {
1376
1
        if (strcmp(method, "len") == 0 && arg_count == 0) {
1377
1
            *result = value_int((int64_t)obj->as.tuple.len);
1378
1
            return true;
1379
1
        }
1380
1
    }
1381
1382
    /* ── Range methods ── */
1383
275
    if (obj->type == VAL_RANGE) {
1384
0
        if (strcmp(method, "len") == 0 && arg_count == 0) {
1385
0
            int64_t len = obj->as.range.end - obj->as.range.start;
1386
0
            *result = value_int(len > 0 ? len : 0);
1387
0
            return true;
1388
0
        }
1389
0
        if (strcmp(method, "contains") == 0 && arg_count == 1) {
1390
0
            if (args[0].type == VAL_INT) {
1391
0
                int64_t v = args[0].as.int_val;
1392
0
                *result = value_bool(v >= obj->as.range.start && v < obj->as.range.end);
1393
0
            } else {
1394
0
                *result = value_bool(false);
1395
0
            }
1396
0
            return true;
1397
0
        }
1398
0
    }
1399
1400
    /* ── Set methods ── */
1401
275
    if (obj->type == VAL_SET) {
1402
27
        if (strcmp(method, "len") == 0 && arg_count == 0) {
1403
7
            *result = value_int((int64_t)lat_map_len(obj->as.set.map));
1404
7
            return true;
1405
7
        }
1406
20
        if (strcmp(method, "has") == 0 && arg_count == 1) {
1407
7
            char *key = value_display(&args[0]);
1408
7
            bool found = lat_map_contains(obj->as.set.map, key);
1409
7
            free(key);
1410
7
            *result = value_bool(found);
1411
7
            return true;
1412
7
        }
1413
13
        if (strcmp(method, "add") == 0 && arg_count == 1) {
1414
5
            char *key = value_display(&args[0]);
1415
5
            LatValue val = rvm_clone(&args[0]);
1416
5
            lat_map_set(obj->as.set.map, key, &val);
1417
5
            free(key);
1418
5
            *result = value_unit();
1419
5
            return true;
1420
5
        }
1421
8
        if (strcmp(method, "remove") == 0 && arg_count == 1) {
1422
1
            char *key = value_display(&args[0]);
1423
1
            lat_map_remove(obj->as.set.map, key);
1424
1
            free(key);
1425
1
            *result = value_unit();
1426
1
            return true;
1427
1
        }
1428
7
        if (strcmp(method, "to_array") == 0 && arg_count == 0) {
1429
1
            size_t len = lat_map_len(obj->as.set.map);
1430
1
            LatValue *elems = malloc((len > 0 ? len : 1) * sizeof(LatValue));
1431
1
            size_t idx = 0;
1432
17
            for (size_t i = 0; i < obj->as.set.map->cap; i++) {
1433
16
                if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue;
1434
1
                LatValue *v = (LatValue *)obj->as.set.map->entries[i].value;
1435
1
                elems[idx++] = rvm_clone(v);
1436
1
            }
1437
1
            *result = value_array(elems, idx);
1438
1
            free(elems);
1439
1
            return true;
1440
1
        }
1441
6
        if (strcmp(method, "union") == 0 && arg_count == 1 && args[0].type == VAL_SET) {
1442
1
            LatValue result_set = value_set_new();
1443
17
            for (size_t i = 0; i < obj->as.set.map->cap; i++) {
1444
16
                if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue;
1445
2
                LatValue v = rvm_clone((LatValue *)obj->as.set.map->entries[i].value);
1446
2
                lat_map_set(result_set.as.set.map, obj->as.set.map->entries[i].key, &v);
1447
2
            }
1448
17
            for (size_t i = 0; i < args[0].as.set.map->cap; i++) {
1449
16
                if (args[0].as.set.map->entries[i].state != MAP_OCCUPIED) continue;
1450
2
                LatValue v = rvm_clone((LatValue *)args[0].as.set.map->entries[i].value);
1451
2
                lat_map_set(result_set.as.set.map, args[0].as.set.map->entries[i].key, &v);
1452
2
            }
1453
1
            *result = result_set;
1454
1
            return true;
1455
1
        }
1456
5
        if (strcmp(method, "intersection") == 0 && arg_count == 1 && args[0].type == VAL_SET) {
1457
1
            LatValue result_set = value_set_new();
1458
17
            for (size_t i = 0; i < obj->as.set.map->cap; i++) {
1459
16
                if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue;
1460
3
                if (lat_map_contains(args[0].as.set.map, obj->as.set.map->entries[i].key)) {
1461
2
                    LatValue v = rvm_clone((LatValue *)obj->as.set.map->entries[i].value);
1462
2
                    lat_map_set(result_set.as.set.map, obj->as.set.map->entries[i].key, &v);
1463
2
                }
1464
3
            }
1465
1
            *result = result_set;
1466
1
            return true;
1467
1
        }
1468
4
        if (strcmp(method, "difference") == 0 && arg_count == 1 && args[0].type == VAL_SET) {
1469
1
            LatValue result_set = value_set_new();
1470
17
            for (size_t i = 0; i < obj->as.set.map->cap; i++) {
1471
16
                if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue;
1472
3
                if (!lat_map_contains(args[0].as.set.map, obj->as.set.map->entries[i].key)) {
1473
1
                    LatValue v = rvm_clone((LatValue *)obj->as.set.map->entries[i].value);
1474
1
                    lat_map_set(result_set.as.set.map, obj->as.set.map->entries[i].key, &v);
1475
1
                }
1476
3
            }
1477
1
            *result = result_set;
1478
1
            return true;
1479
1
        }
1480
3
        if (strcmp(method, "is_subset") == 0 && arg_count == 1 && args[0].type == VAL_SET) {
1481
2
            bool is = true;
1482
20
            for (size_t i = 0; i < obj->as.set.map->cap; i++) {
1483
19
                if (obj->as.set.map->entries[i].state != MAP_OCCUPIED) continue;
1484
3
                if (!lat_map_contains(args[0].as.set.map, obj->as.set.map->entries[i].key)) {
1485
1
                    is = false; break;
1486
1
                }
1487
3
            }
1488
2
            *result = value_bool(is);
1489
2
            return true;
1490
2
        }
1491
1
        if (strcmp(method, "is_superset") == 0 && arg_count == 1 && args[0].type == VAL_SET) {
1492
1
            bool is = true;
1493
17
            for (size_t i = 0; i < args[0].as.set.map->cap; i++) {
1494
16
                if (args[0].as.set.map->entries[i].state != MAP_OCCUPIED) continue;
1495
2
                if (!lat_map_contains(obj->as.set.map, args[0].as.set.map->entries[i].key)) {
1496
0
                    is = false; break;
1497
0
                }
1498
2
            }
1499
1
            *result = value_bool(is);
1500
1
            return true;
1501
1
        }
1502
1
    }
1503
1504
    /* ── String additional: count, is_empty ── */
1505
248
    if (obj->type == VAL_STR) {
1506
5
        if (strcmp(method, "count") == 0 && arg_count == 1) {
1507
3
            int64_t cnt = 0;
1508
3
            if (args[0].type == VAL_STR && args[0].as.str_val[0]) {
1509
3
                const char *p = obj->as.str_val;
1510
3
                size_t nlen = strlen(args[0].as.str_val);
1511
6
                while ((p = strstr(p, args[0].as.str_val)) != NULL) { cnt++; p += nlen; }
1512
3
            }
1513
3
            *result = value_int(cnt);
1514
3
            return true;
1515
3
        }
1516
2
        if (strcmp(method, "is_empty") == 0 && arg_count == 0) {
1517
2
            *result = value_bool(obj->as.str_val[0] == '\0');
1518
2
            return true;
1519
2
        }
1520
2
    }
1521
1522
    /* ── Map additional: remove/delete ── */
1523
243
    if (obj->type == VAL_MAP) {
1524
171
        if ((strcmp(method, "remove") == 0 || strcmp(method, "delete") == 0) && arg_count == 1) {
1525
1
            if (args[0].type == VAL_STR)
1526
1
                lat_map_remove(obj->as.map.map, args[0].as.str_val);
1527
1
            *result = value_unit();
1528
1
            return true;
1529
1
        }
1530
170
        if (strcmp(method, "map") == 0 && arg_count == 1) {
1531
2
            LatValue *closure = &args[0];
1532
2
            LatValue mapped = value_map_new();
1533
34
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
1534
32
                if (obj->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
1535
4
                LatValue cb_args[2];
1536
4
                cb_args[0] = value_string(obj->as.map.map->entries[i].key);
1537
4
                cb_args[1] = rvm_clone((LatValue *)obj->as.map.map->entries[i].value);
1538
4
                LatValue ret = regvm_call_closure(vm, closure, cb_args, 2);
1539
4
                lat_map_set(mapped.as.map.map, obj->as.map.map->entries[i].key, &ret);
1540
4
                value_free(&cb_args[0]);
1541
4
                value_free(&cb_args[1]);
1542
4
            }
1543
2
            *result = mapped;
1544
2
            return true;
1545
2
        }
1546
170
    }
1547
1548
    /* ── Buffer methods ── */
1549
240
    if (obj->type == VAL_BUFFER) {
1550
25
        if (strcmp(method, "len") == 0 && arg_count == 0) {
1551
10
            *result = value_int((int64_t)obj->as.buffer.len);
1552
10
            return true;
1553
10
        }
1554
15
        if (strcmp(method, "capacity") == 0 && arg_count == 0) {
1555
0
            *result = value_int((int64_t)obj->as.buffer.cap);
1556
0
            return true;
1557
0
        }
1558
15
        if (strcmp(method, "push") == 0 && arg_count == 1) {
1559
2
            if (args[0].type == VAL_INT) {
1560
2
                if (obj->as.buffer.len >= obj->as.buffer.cap) {
1561
0
                    obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8;
1562
0
                    obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap);
1563
0
                }
1564
2
                obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)args[0].as.int_val;
1565
2
            }
1566
2
            *result = value_unit();
1567
2
            return true;
1568
2
        }
1569
13
        if (strcmp(method, "push_u16") == 0 && arg_count == 1) {
1570
1
            if (args[0].type == VAL_INT) {
1571
1
                uint16_t v = (uint16_t)args[0].as.int_val;
1572
1
                while (obj->as.buffer.len + 2 > obj->as.buffer.cap) {
1573
0
                    obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8;
1574
0
                    obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap);
1575
0
                }
1576
1
                obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)(v & 0xFF);
1577
1
                obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF);
1578
1
            }
1579
1
            *result = value_unit();
1580
1
            return true;
1581
1
        }
1582
12
        if (strcmp(method, "push_u32") == 0 && arg_count == 1) {
1583
1
            if (args[0].type == VAL_INT) {
1584
1
                uint32_t v = (uint32_t)args[0].as.int_val;
1585
1
                while (obj->as.buffer.len + 4 > obj->as.buffer.cap) {
1586
0
                    obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8;
1587
0
                    obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap);
1588
0
                }
1589
1
                obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)(v & 0xFF);
1590
1
                obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF);
1591
1
                obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 16) & 0xFF);
1592
1
                obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 24) & 0xFF);
1593
1
            }
1594
1
            *result = value_unit();
1595
1
            return true;
1596
1
        }
1597
11
        if (strcmp(method, "read_u8") == 0 && arg_count == 1) {
1598
0
            if (args[0].type == VAL_INT) {
1599
0
                int64_t idx = args[0].as.int_val;
1600
0
                if (idx < 0 || (size_t)idx >= obj->as.buffer.len) {
1601
0
                    *result = value_nil();
1602
0
                } else {
1603
0
                    *result = value_int((int64_t)obj->as.buffer.data[idx]);
1604
0
                }
1605
0
            } else {
1606
0
                *result = value_nil();
1607
0
            }
1608
0
            return true;
1609
0
        }
1610
11
        if (strcmp(method, "write_u8") == 0 && arg_count == 2) {
1611
0
            if (args[0].type == VAL_INT && args[1].type == VAL_INT) {
1612
0
                int64_t idx = args[0].as.int_val;
1613
0
                if (idx >= 0 && (size_t)idx < obj->as.buffer.len)
1614
0
                    obj->as.buffer.data[idx] = (uint8_t)args[1].as.int_val;
1615
0
            }
1616
0
            *result = value_unit();
1617
0
            return true;
1618
0
        }
1619
11
        if (strcmp(method, "read_u16") == 0 && arg_count == 1) {
1620
1
            if (args[0].type == VAL_INT) {
1621
1
                int64_t idx = args[0].as.int_val;
1622
1
                if (idx < 0 || (size_t)idx + 2 > obj->as.buffer.len) {
1623
0
                    *result = value_nil();
1624
1
                } else {
1625
1
                    uint16_t v = (uint16_t)(obj->as.buffer.data[idx]) |
1626
1
                                 ((uint16_t)(obj->as.buffer.data[idx + 1]) << 8);
1627
1
                    *result = value_int((int64_t)v);
1628
1
                }
1629
1
            } else {
1630
0
                *result = value_nil();
1631
0
            }
1632
1
            return true;
1633
1
        }
1634
10
        if (strcmp(method, "write_u16") == 0 && arg_count == 2) {
1635
1
            if (args[0].type == VAL_INT && args[1].type == VAL_INT) {
1636
1
                int64_t idx = args[0].as.int_val;
1637
1
                uint16_t v = (uint16_t)args[1].as.int_val;
1638
1
                if (idx >= 0 && (size_t)idx + 1 < obj->as.buffer.len) {
1639
1
                    obj->as.buffer.data[idx] = (uint8_t)(v & 0xFF);
1640
1
                    obj->as.buffer.data[idx + 1] = (uint8_t)(v >> 8);
1641
1
                }
1642
1
            }
1643
1
            *result = value_unit();
1644
1
            return true;
1645
1
        }
1646
9
        if (strcmp(method, "read_u32") == 0 && arg_count == 1) {
1647
1
            if (args[0].type == VAL_INT) {
1648
1
                int64_t idx = args[0].as.int_val;
1649
1
                if (idx < 0 || (size_t)idx + 4 > obj->as.buffer.len) {
1650
0
                    *result = value_nil();
1651
1
                } else {
1652
1
                    uint32_t v = (uint32_t)(obj->as.buffer.data[idx]) |
1653
1
                                 ((uint32_t)(obj->as.buffer.data[idx + 1]) << 8) |
1654
1
                                 ((uint32_t)(obj->as.buffer.data[idx + 2]) << 16) |
1655
1
                                 ((uint32_t)(obj->as.buffer.data[idx + 3]) << 24);
1656
1
                    *result = value_int((int64_t)v);
1657
1
                }
1658
1
            } else {
1659
0
                *result = value_nil();
1660
0
            }
1661
1
            return true;
1662
1
        }
1663
8
        if (strcmp(method, "write_u32") == 0 && arg_count == 2) {
1664
1
            if (args[0].type == VAL_INT && args[1].type == VAL_INT) {
1665
1
                int64_t idx = args[0].as.int_val;
1666
1
                uint32_t v = (uint32_t)args[1].as.int_val;
1667
1
                if (idx >= 0 && (size_t)idx + 3 < obj->as.buffer.len) {
1668
1
                    obj->as.buffer.data[idx] = (uint8_t)(v & 0xFF);
1669
1
                    obj->as.buffer.data[idx + 1] = (uint8_t)((v >> 8) & 0xFF);
1670
1
                    obj->as.buffer.data[idx + 2] = (uint8_t)((v >> 16) & 0xFF);
1671
1
                    obj->as.buffer.data[idx + 3] = (uint8_t)((v >> 24) & 0xFF);
1672
1
                }
1673
1
            }
1674
1
            *result = value_unit();
1675
1
            return true;
1676
1
        }
1677
7
        if (strcmp(method, "slice") == 0 && (arg_count == 1 || arg_count == 2)) {
1678
1
            int64_t start = args[0].type == VAL_INT ? args[0].as.int_val : 0;
1679
1
            int64_t end = arg_count == 2 && args[1].type == VAL_INT ? args[1].as.int_val : (int64_t)obj->as.buffer.len;
1680
1
            if (start < 0) start = 0;
1681
1
            if (end > (int64_t)obj->as.buffer.len) end = (int64_t)obj->as.buffer.len;
1682
1
            if (start >= end) { *result = value_buffer(NULL, 0); return true; }
1683
1
            *result = value_buffer(obj->as.buffer.data + start, (size_t)(end - start));
1684
1
            return true;
1685
1
        }
1686
6
        if (strcmp(method, "clear") == 0 && arg_count == 0) {
1687
1
            obj->as.buffer.len = 0;
1688
1
            *result = value_unit();
1689
1
            return true;
1690
1
        }
1691
5
        if (strcmp(method, "fill") == 0 && arg_count == 1) {
1692
1
            if (args[0].type == VAL_INT)
1693
1
                memset(obj->as.buffer.data, (uint8_t)args[0].as.int_val, obj->as.buffer.len);
1694
1
            *result = value_unit();
1695
1
            return true;
1696
1
        }
1697
4
        if (strcmp(method, "resize") == 0 && arg_count == 1) {
1698
1
            if (args[0].type == VAL_INT && args[0].as.int_val >= 0) {
1699
1
                size_t new_len = (size_t)args[0].as.int_val;
1700
1
                if (new_len > obj->as.buffer.cap) {
1701
0
                    obj->as.buffer.cap = new_len;
1702
0
                    obj->as.buffer.data = realloc(obj->as.buffer.data, new_len);
1703
0
                }
1704
1
                if (new_len > obj->as.buffer.len)
1705
1
                    memset(obj->as.buffer.data + obj->as.buffer.len, 0, new_len - obj->as.buffer.len);
1706
1
                obj->as.buffer.len = new_len;
1707
1
            }
1708
1
            *result = value_unit();
1709
1
            return true;
1710
1
        }
1711
3
        if (strcmp(method, "to_string") == 0 && arg_count == 0) {
1712
1
            char *s = malloc(obj->as.buffer.len + 1);
1713
1
            memcpy(s, obj->as.buffer.data, obj->as.buffer.len);
1714
1
            s[obj->as.buffer.len] = '\0';
1715
1
            *result = value_string_owned(s);
1716
1
            return true;
1717
1
        }
1718
2
        if (strcmp(method, "to_array") == 0 && arg_count == 0) {
1719
1
            LatValue *elems = malloc((obj->as.buffer.len > 0 ? obj->as.buffer.len : 1) * sizeof(LatValue));
1720
4
            for (size_t i = 0; i < obj->as.buffer.len; i++)
1721
3
                elems[i] = value_int((int64_t)obj->as.buffer.data[i]);
1722
1
            *result = value_array(elems, obj->as.buffer.len);
1723
1
            free(elems);
1724
1
            return true;
1725
1
        }
1726
1
        if (strcmp(method, "to_hex") == 0 && arg_count == 0) {
1727
1
            char *hex = malloc(obj->as.buffer.len * 2 + 1);
1728
4
            for (size_t i = 0; i < obj->as.buffer.len; i++)
1729
3
                snprintf(hex + i * 2, 3, "%02x", obj->as.buffer.data[i]);
1730
1
            hex[obj->as.buffer.len * 2] = '\0';
1731
1
            *result = value_string_owned(hex);
1732
1
            return true;
1733
1
        }
1734
1
    }
1735
1736
    /* ── Ref methods ── */
1737
215
    if (obj->type == VAL_REF) {
1738
15
        LatRef *ref = obj->as.ref.ref;
1739
15
        if ((strcmp(method, "get") == 0 || strcmp(method, "deref") == 0) && arg_count == 0) {
1740
4
            *result = value_deep_clone(&ref->value);
1741
4
            return true;
1742
4
        }
1743
11
        if (strcmp(method, "set") == 0 && arg_count == 1 && arg_count == 1) {
1744
2
            if (obj->phase == VTAG_CRYSTAL) {
1745
0
                *result = value_unit();
1746
0
                return true;
1747
0
            }
1748
2
            value_free(&ref->value);
1749
2
            ref->value = rvm_clone(&args[0]);
1750
2
            *result = value_unit();
1751
2
            return true;
1752
2
        }
1753
9
        if (strcmp(method, "inner_type") == 0 && arg_count == 0) {
1754
2
            *result = value_string(value_type_name(&ref->value));
1755
2
            return true;
1756
2
        }
1757
        /* Proxy: delegate to inner value's methods if applicable */
1758
7
        if (ref->value.type == VAL_MAP) {
1759
3
            if (strcmp(method, "get") == 0 && arg_count == 1 && args[0].type == VAL_STR) {
1760
0
                LatValue *val = lat_map_get(ref->value.as.map.map, args[0].as.str_val);
1761
0
                *result = val ? value_deep_clone(val) : value_nil();
1762
0
                return true;
1763
0
            }
1764
3
            if (strcmp(method, "set") == 0 && arg_count == 2 && args[0].type == VAL_STR) {
1765
0
                if (obj->phase != VTAG_CRYSTAL) {
1766
0
                    LatValue cloned = rvm_clone(&args[1]);
1767
0
                    lat_map_set(ref->value.as.map.map, args[0].as.str_val, &cloned);
1768
0
                }
1769
0
                *result = value_unit();
1770
0
                return true;
1771
0
            }
1772
3
            if ((strcmp(method, "has") == 0 || strcmp(method, "contains") == 0) && arg_count == 1 && args[0].type == VAL_STR) {
1773
2
                *result = value_bool(lat_map_get(ref->value.as.map.map, args[0].as.str_val) != NULL);
1774
2
                return true;
1775
2
            }
1776
1
            if (strcmp(method, "keys") == 0 && arg_count == 0) {
1777
0
                size_t cap = ref->value.as.map.map->cap;
1778
0
                LatValue *keys = malloc(cap * sizeof(LatValue));
1779
0
                size_t cnt = 0;
1780
0
                for (size_t i = 0; i < cap; i++) {
1781
0
                    if (ref->value.as.map.map->entries[i].state == MAP_OCCUPIED)
1782
0
                        keys[cnt++] = value_string(ref->value.as.map.map->entries[i].key);
1783
0
                }
1784
0
                *result = value_array(keys, cnt);
1785
0
                free(keys);
1786
0
                return true;
1787
0
            }
1788
1
            if (strcmp(method, "values") == 0 && arg_count == 0) {
1789
0
                size_t cap = ref->value.as.map.map->cap;
1790
0
                LatValue *vals = malloc(cap * sizeof(LatValue));
1791
0
                size_t cnt = 0;
1792
0
                for (size_t i = 0; i < cap; i++) {
1793
0
                    if (ref->value.as.map.map->entries[i].state == MAP_OCCUPIED)
1794
0
                        vals[cnt++] = value_deep_clone((LatValue *)ref->value.as.map.map->entries[i].value);
1795
0
                }
1796
0
                *result = value_array(vals, cnt);
1797
0
                free(vals);
1798
0
                return true;
1799
0
            }
1800
1
            if (strcmp(method, "entries") == 0 && arg_count == 0) {
1801
0
                size_t cap = ref->value.as.map.map->cap;
1802
0
                LatValue *entries = malloc(cap * sizeof(LatValue));
1803
0
                size_t cnt = 0;
1804
0
                for (size_t i = 0; i < cap; i++) {
1805
0
                    if (ref->value.as.map.map->entries[i].state != MAP_OCCUPIED) continue;
1806
0
                    LatValue pair[2];
1807
0
                    pair[0] = value_string(ref->value.as.map.map->entries[i].key);
1808
0
                    pair[1] = value_deep_clone((LatValue *)ref->value.as.map.map->entries[i].value);
1809
0
                    entries[cnt++] = value_array(pair, 2);
1810
0
                }
1811
0
                *result = value_array(entries, cnt);
1812
0
                free(entries);
1813
0
                return true;
1814
0
            }
1815
1
            if (strcmp(method, "len") == 0 && arg_count == 0) {
1816
1
                *result = value_int((int64_t)lat_map_len(ref->value.as.map.map));
1817
1
                return true;
1818
1
            }
1819
0
            if (strcmp(method, "merge") == 0 && arg_count == 1 && args[0].type == VAL_MAP) {
1820
0
                if (obj->phase != VTAG_CRYSTAL) {
1821
0
                    for (size_t i = 0; i < args[0].as.map.map->cap; i++) {
1822
0
                        if (args[0].as.map.map->entries[i].state != MAP_OCCUPIED) continue;
1823
0
                        LatValue v = rvm_clone((LatValue *)args[0].as.map.map->entries[i].value);
1824
0
                        lat_map_set(ref->value.as.map.map, args[0].as.map.map->entries[i].key, &v);
1825
0
                    }
1826
0
                }
1827
0
                *result = value_unit();
1828
0
                return true;
1829
0
            }
1830
0
        }
1831
4
        if (ref->value.type == VAL_ARRAY) {
1832
4
            if (strcmp(method, "push") == 0 && arg_count == 1) {
1833
1
                LatValue val = rvm_clone(&args[0]);
1834
1
                if (ref->value.as.array.len >= ref->value.as.array.cap) {
1835
0
                    ref->value.as.array.cap = ref->value.as.array.cap ? ref->value.as.array.cap * 2 : 4;
1836
0
                    ref->value.as.array.elems = realloc(ref->value.as.array.elems,
1837
0
                        ref->value.as.array.cap * sizeof(LatValue));
1838
0
                }
1839
1
                ref->value.as.array.elems[ref->value.as.array.len++] = val;
1840
1
                *result = value_unit();
1841
1
                return true;
1842
1
            }
1843
3
            if (strcmp(method, "pop") == 0 && arg_count == 0) {
1844
1
                if (ref->value.as.array.len == 0) { *result = value_nil(); }
1845
1
                else { *result = ref->value.as.array.elems[--ref->value.as.array.len]; }
1846
1
                return true;
1847
1
            }
1848
2
            if (strcmp(method, "len") == 0 && arg_count == 0) {
1849
2
                *result = value_int((int64_t)ref->value.as.array.len);
1850
2
                return true;
1851
2
            }
1852
0
            if (strcmp(method, "contains") == 0 && arg_count == 1) {
1853
0
                bool found = false;
1854
0
                for (size_t i = 0; i < ref->value.as.array.len; i++) {
1855
0
                    if (value_eq(&ref->value.as.array.elems[i], &args[0])) { found = true; break; }
1856
0
                }
1857
0
                *result = value_bool(found);
1858
0
                return true;
1859
0
            }
1860
0
        }
1861
4
    }
1862
1863
    /* ── Channel methods ── */
1864
200
    if (obj->type == VAL_CHANNEL) {
1865
21
        if (strcmp(method, "send") == 0 && arg_count == 1) {
1866
10
            if (!value_is_crystal(&args[0]) && args[0].phase != VTAG_UNPHASED) {
1867
1
                vm->error = strdup("channel send requires crystal or unphased values");
1868
1
                *result = value_unit();
1869
1
                return true;
1870
1
            }
1871
9
            LatValue val = rvm_clone(&args[0]);
1872
9
            channel_send(obj->as.channel.ch, val);
1873
9
            *result = value_unit();
1874
9
            return true;
1875
10
        }
1876
11
        if (strcmp(method, "recv") == 0 && arg_count == 0) {
1877
8
            bool ok = false;
1878
8
            *result = channel_recv(obj->as.channel.ch, &ok);
1879
8
            if (!ok) *result = value_unit();
1880
8
            return true;
1881
8
        }
1882
3
        if (strcmp(method, "close") == 0 && arg_count == 0) {
1883
3
            channel_close(obj->as.channel.ch);
1884
3
            *result = value_unit();
1885
3
            return true;
1886
3
        }
1887
3
    }
1888
1889
179
    return false;
1890
200
}
1891
1892
/* Phase system functions (regvm_fire_reactions, regvm_freeze_cascade,
1893
 * regvm_validate_seeds, regvm_sync_env_to_register) have been moved to
1894
 * runtime.c as rt_* functions. */
1895
1896
/* ── VM Dispatch Loop ── */
1897
1898
/* Native function type (same as stack VM) */
1899
typedef LatValue (*VMNativeFn)(LatValue *args, int arg_count);
1900
1901
/* Call a closure from within a builtin handler (map, filter, etc.). */
1902
287
static LatValue regvm_call_closure(RegVM *vm, LatValue *closure, LatValue *args, int argc) {
1903
287
    if (closure->type != VAL_CLOSURE) return value_nil();
1904
1905
    /* Check for native C function */
1906
287
    if (closure->as.closure.default_values == VM_NATIVE_MARKER) {
1907
0
        VMNativeFn native = (VMNativeFn)closure->as.closure.native_fn;
1908
0
        LatValue ret = native(args, argc);
1909
        /* Check runtime for native errors — propagate to regvm */
1910
0
        if (vm->rt->error) {
1911
0
            vm->error = vm->rt->error;
1912
0
            vm->rt->error = NULL;
1913
0
            value_free(&ret);
1914
0
            return value_nil();
1915
0
        }
1916
0
        return ret;
1917
0
    }
1918
1919
    /* Extension native function */
1920
287
    if (closure->as.closure.default_values == VM_EXT_MARKER) {
1921
0
        LatValue ret = ext_call_native(closure->as.closure.native_fn, args, (size_t)argc);
1922
0
        if (ret.type == VAL_STR && ret.as.str_val &&
1923
0
            strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) {
1924
0
            vm->error = strdup(ret.as.str_val + 11);
1925
0
            value_free(&ret);
1926
0
            return value_nil();
1927
0
        }
1928
0
        return ret;
1929
0
    }
1930
1931
287
    RegChunk *fn_chunk = (RegChunk *)closure->as.closure.native_fn;
1932
287
    if (!fn_chunk) return value_nil();
1933
1934
    /* Guard: detect stack-VM closures that can't run in regvm */
1935
287
    if (fn_chunk->magic != REGCHUNK_MAGIC) return value_nil();
1936
1937
287
    if (vm->frame_count >= REGVM_FRAMES_MAX) return value_nil();
1938
1939
287
    size_t new_base = vm->reg_stack_top;
1940
287
    if (new_base + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX)
1941
0
        return value_nil();
1942
1943
287
    LatValue *new_regs = &vm->reg_stack[new_base];
1944
287
    vm->reg_stack_top += REGVM_REG_MAX;
1945
73.7k
    for (int i = 0; i < REGVM_REG_MAX; i++)
1946
73.4k
        new_regs[i] = value_nil();
1947
1948
287
    new_regs[0] = value_unit();
1949
616
    for (int i = 0; i < argc; i++) {
1950
329
        value_free(&new_regs[1 + i]);
1951
329
        new_regs[1 + i] = rvm_clone(&args[i]);
1952
329
    }
1953
1954
287
    ObjUpvalue **upvals = (ObjUpvalue **)closure->as.closure.captured_env;
1955
287
    size_t uv_count = closure->region_id != (size_t)-1 ? closure->region_id : 0;
1956
1957
287
    int saved_base = vm->frame_count;
1958
287
    RegCallFrame *new_frame = &vm->frames[vm->frame_count++];
1959
287
    new_frame->chunk = fn_chunk;
1960
287
    new_frame->ip = fn_chunk->code;
1961
287
    new_frame->regs = new_regs;
1962
287
    new_frame->reg_count = REGVM_REG_MAX;
1963
287
    new_frame->upvalues = upvals;
1964
287
    new_frame->upvalue_count = uv_count;
1965
287
    new_frame->caller_result_reg = 0;
1966
1967
287
    LatValue ret;
1968
287
    RegVMResult res = regvm_dispatch(vm, saved_base, &ret);
1969
287
    if (res != REGVM_OK) {
1970
        /* Unwind any frames left by the failed dispatch back to saved_base */
1971
2
        while (vm->frame_count > saved_base) {
1972
1
            RegCallFrame *uf = &vm->frames[vm->frame_count - 1];
1973
257
            for (int i = 0; i < REGVM_REG_MAX; i++)
1974
256
                value_free_inline(&uf->regs[i]);
1975
1
            vm->frame_count--;
1976
1
            vm->reg_stack_top -= REGVM_REG_MAX;
1977
1
        }
1978
        /* Propagate vm->error to rt->error so runtime-level callers
1979
         * (e.g. rt_fire_reactions) can see and wrap the error */
1980
1
        if (vm->error && !vm->rt->error) {
1981
1
            vm->rt->error = vm->error;
1982
1
            vm->error = NULL;
1983
1
        }
1984
1
        return value_nil();
1985
1
    }
1986
286
    return ret;
1987
287
}
1988
1989
1.25k
static RegVMResult regvm_dispatch(RegVM *vm, int base_frame, LatValue *result) {
1990
1.25k
    RegCallFrame *frame = &vm->frames[vm->frame_count - 1];
1991
1992
1.25k
    LatValue *R = frame->regs;  /* Register base pointer */
1993
1994
/* Route runtime errors through exception handlers when possible */
1995
1.25k
#define RVM_ERROR(...) do { \
1996
104
    RegVMResult _err = rvm_handle_error(vm, &frame, &R, __VA_ARGS__); \
1997
104
    if (_err != REGVM_OK) return _err; \
1998
104
    DISPATCH(); \
1999
29
} while(0)
2000
2001
52.6k
#define READ_INSTR()  (*frame->ip++)
2002
1.25k
#define REGS          R
2003
2004
1.25k
#ifdef VM_USE_COMPUTED_GOTO
2005
    /* Computed goto dispatch table */
2006
1.25k
    static void *dispatch_table[ROP_COUNT] = {
2007
1.25k
        [ROP_MOVE]         = &&L_MOVE,
2008
1.25k
        [ROP_LOADK]        = &&L_LOADK,
2009
1.25k
        [ROP_LOADI]        = &&L_LOADI,
2010
1.25k
        [ROP_LOADNIL]      = &&L_LOADNIL,
2011
1.25k
        [ROP_LOADTRUE]     = &&L_LOADTRUE,
2012
1.25k
        [ROP_LOADFALSE]    = &&L_LOADFALSE,
2013
1.25k
        [ROP_LOADUNIT]     = &&L_LOADUNIT,
2014
1.25k
        [ROP_ADD]          = &&L_ADD,
2015
1.25k
        [ROP_SUB]          = &&L_SUB,
2016
1.25k
        [ROP_MUL]          = &&L_MUL,
2017
1.25k
        [ROP_DIV]          = &&L_DIV,
2018
1.25k
        [ROP_MOD]          = &&L_MOD,
2019
1.25k
        [ROP_NEG]          = &&L_NEG,
2020
1.25k
        [ROP_ADDI]         = &&L_ADDI,
2021
1.25k
        [ROP_CONCAT]       = &&L_CONCAT,
2022
1.25k
        [ROP_EQ]           = &&L_EQ,
2023
1.25k
        [ROP_NEQ]          = &&L_NEQ,
2024
1.25k
        [ROP_LT]           = &&L_LT,
2025
1.25k
        [ROP_LTEQ]         = &&L_LTEQ,
2026
1.25k
        [ROP_GT]           = &&L_GT,
2027
1.25k
        [ROP_GTEQ]         = &&L_GTEQ,
2028
1.25k
        [ROP_NOT]          = &&L_NOT,
2029
1.25k
        [ROP_JMP]          = &&L_JMP,
2030
1.25k
        [ROP_JMPFALSE]     = &&L_JMPFALSE,
2031
1.25k
        [ROP_JMPTRUE]      = &&L_JMPTRUE,
2032
1.25k
        [ROP_GETGLOBAL]    = &&L_GETGLOBAL,
2033
1.25k
        [ROP_SETGLOBAL]    = &&L_SETGLOBAL,
2034
1.25k
        [ROP_DEFINEGLOBAL] = &&L_DEFINEGLOBAL,
2035
1.25k
        [ROP_GETFIELD]     = &&L_GETFIELD,
2036
1.25k
        [ROP_SETFIELD]     = &&L_SETFIELD,
2037
1.25k
        [ROP_GETINDEX]     = &&L_GETINDEX,
2038
1.25k
        [ROP_SETINDEX]     = &&L_SETINDEX,
2039
1.25k
        [ROP_GETUPVALUE]   = &&L_GETUPVALUE,
2040
1.25k
        [ROP_SETUPVALUE]   = &&L_SETUPVALUE,
2041
1.25k
        [ROP_CLOSEUPVALUE] = &&L_CLOSEUPVALUE,
2042
1.25k
        [ROP_CALL]         = &&L_CALL,
2043
1.25k
        [ROP_RETURN]       = &&L_RETURN,
2044
1.25k
        [ROP_CLOSURE]      = &&L_CLOSURE,
2045
1.25k
        [ROP_NEWARRAY]     = &&L_NEWARRAY,
2046
1.25k
        [ROP_NEWSTRUCT]    = &&L_NEWSTRUCT,
2047
1.25k
        [ROP_BUILDRANGE]   = &&L_BUILDRANGE,
2048
1.25k
        [ROP_LEN]          = &&L_LEN,
2049
1.25k
        [ROP_PRINT]        = &&L_PRINT,
2050
1.25k
        [ROP_INVOKE]       = &&L_INVOKE,
2051
1.25k
        [ROP_FREEZE]       = &&L_FREEZE,
2052
1.25k
        [ROP_THAW]         = &&L_THAW,
2053
1.25k
        [ROP_CLONE]        = &&L_CLONE,
2054
1.25k
        [ROP_ITERINIT]     = &&L_ITERINIT,
2055
1.25k
        [ROP_ITERNEXT]     = &&L_ITERNEXT,
2056
1.25k
        [ROP_MARKFLUID]    = &&L_MARKFLUID,
2057
        /* Bitwise */
2058
1.25k
        [ROP_BIT_AND]      = &&L_BIT_AND,
2059
1.25k
        [ROP_BIT_OR]       = &&L_BIT_OR,
2060
1.25k
        [ROP_BIT_XOR]      = &&L_BIT_XOR,
2061
1.25k
        [ROP_BIT_NOT]      = &&L_BIT_NOT,
2062
1.25k
        [ROP_LSHIFT]       = &&L_LSHIFT,
2063
1.25k
        [ROP_RSHIFT]       = &&L_RSHIFT,
2064
        /* Tuple */
2065
1.25k
        [ROP_NEWTUPLE]     = &&L_NEWTUPLE,
2066
        /* Spread/Flatten */
2067
1.25k
        [ROP_ARRAY_FLATTEN] = &&L_ARRAY_FLATTEN,
2068
        /* Enum */
2069
1.25k
        [ROP_NEWENUM]      = &&L_NEWENUM,
2070
        /* Optional chaining */
2071
1.25k
        [ROP_JMPNOTNIL]    = &&L_JMPNOTNIL,
2072
        /* Exception handling */
2073
1.25k
        [ROP_PUSH_HANDLER] = &&L_PUSH_HANDLER,
2074
1.25k
        [ROP_POP_HANDLER]  = &&L_POP_HANDLER,
2075
1.25k
        [ROP_THROW]        = &&L_THROW,
2076
1.25k
        [ROP_TRY_UNWRAP]   = &&L_TRY_UNWRAP,
2077
        /* Defer */
2078
1.25k
        [ROP_DEFER_PUSH]   = &&L_DEFER_PUSH,
2079
1.25k
        [ROP_DEFER_RUN]    = &&L_DEFER_RUN,
2080
        /* Variadic */
2081
1.25k
        [ROP_COLLECT_VARARGS] = &&L_COLLECT_VARARGS,
2082
        /* Advanced phase */
2083
1.25k
        [ROP_FREEZE_VAR]   = &&L_FREEZE_VAR,
2084
1.25k
        [ROP_THAW_VAR]     = &&L_THAW_VAR,
2085
1.25k
        [ROP_SUBLIMATE_VAR] = &&L_SUBLIMATE_VAR,
2086
1.25k
        [ROP_REACT]        = &&L_REACT,
2087
1.25k
        [ROP_UNREACT]      = &&L_UNREACT,
2088
1.25k
        [ROP_BOND]         = &&L_BOND,
2089
1.25k
        [ROP_UNBOND]       = &&L_UNBOND,
2090
1.25k
        [ROP_SEED]         = &&L_SEED,
2091
1.25k
        [ROP_UNSEED]       = &&L_UNSEED,
2092
        /* Module/Import */
2093
1.25k
        [ROP_IMPORT]       = &&L_IMPORT,
2094
        /* Concurrency */
2095
1.25k
        [ROP_SCOPE]        = &&L_SCOPE,
2096
1.25k
        [ROP_SELECT]       = &&L_SELECT,
2097
        /* Ephemeral arena */
2098
1.25k
        [ROP_RESET_EPHEMERAL] = &&L_RESET_EPHEMERAL,
2099
        /* Optimization */
2100
1.25k
        [ROP_ADD_INT]      = &&L_ADD_INT,
2101
1.25k
        [ROP_SUB_INT]      = &&L_SUB_INT,
2102
1.25k
        [ROP_MUL_INT]      = &&L_MUL_INT,
2103
1.25k
        [ROP_LT_INT]       = &&L_LT_INT,
2104
1.25k
        [ROP_LTEQ_INT]     = &&L_LTEQ_INT,
2105
1.25k
        [ROP_INC_REG]      = &&L_INC_REG,
2106
1.25k
        [ROP_DEC_REG]      = &&L_DEC_REG,
2107
1.25k
        [ROP_SETINDEX_LOCAL] = &&L_SETINDEX_LOCAL,
2108
1.25k
        [ROP_INVOKE_GLOBAL] = &&L_INVOKE_GLOBAL,
2109
        /* Phase query */
2110
1.25k
        [ROP_IS_CRYSTAL]   = &&L_IS_CRYSTAL,
2111
        /* Type checking */
2112
1.25k
        [ROP_CHECK_TYPE]   = &&L_CHECK_TYPE,
2113
1.25k
        [ROP_FREEZE_FIELD] = &&L_FREEZE_FIELD,
2114
1.25k
        [ROP_THAW_FIELD]   = &&L_THAW_FIELD,
2115
        /* Require */
2116
1.25k
        [ROP_REQUIRE]      = &&L_REQUIRE,
2117
        /* Misc */
2118
1.25k
        [ROP_HALT]         = &&L_HALT,
2119
1.25k
    };
2120
2121
1.25k
#define DISPATCH() do { \
2122
1.25k
    RegInstr _i = READ_INSTR(); \
2123
1.25k
    goto *dispatch_table[REG_GET_OP(_i)]; \
2124
1.25k
    } while(0)
2125
2126
    /* We need the instruction available after goto. Use a local. */
2127
1.25k
#undef DISPATCH
2128
52.3k
#define DISPATCH() do { \
2129
52.3k
    instr = READ_INSTR(); \
2130
52.3k
    goto *dispatch_table[REG_GET_OP(instr)]; \
2131
52.3k
    } while(0)
2132
2133
1.25k
    RegInstr instr;
2134
1.25k
    DISPATCH();
2135
2136
50.5k
#define CASE(label) L_##label:
2137
2138
#else
2139
    /* Switch-based dispatch */
2140
    for (;;) {
2141
        RegInstr instr = READ_INSTR();
2142
        switch (REG_GET_OP(instr)) {
2143
2144
#define CASE(label) case ROP_##label:
2145
#define DISPATCH() continue
2146
2147
#endif
2148
2149
7.70k
    CASE(MOVE) {
2150
7.70k
        uint8_t a = REG_GET_A(instr);
2151
7.70k
        uint8_t b = REG_GET_B(instr);
2152
7.70k
        reg_set(&R[a], rvm_clone(&R[b]));
2153
        /* Record history for tracked variables */
2154
7.70k
        {
2155
7.70k
            if (vm->rt->tracking_active &&
2156
7.70k
                frame->chunk->local_names && a < frame->chunk->local_name_cap &&
2157
7.70k
                frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) {
2158
23
                rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]);
2159
23
            }
2160
7.70k
        }
2161
7.70k
        DISPATCH();
2162
7.70k
    }
2163
2164
7.70k
    CASE(LOADK) {
2165
3.63k
        uint8_t a = REG_GET_A(instr);
2166
3.63k
        uint16_t bx = REG_GET_Bx(instr);
2167
3.63k
        reg_set(&R[a], rvm_clone(&frame->chunk->constants[bx]));
2168
        /* Record history for tracked variables */
2169
3.63k
        {
2170
3.63k
            if (vm->rt->tracking_active &&
2171
3.63k
                frame->chunk->local_names && a < frame->chunk->local_name_cap &&
2172
3.63k
                frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) {
2173
1
                rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]);
2174
1
            }
2175
3.63k
        }
2176
3.63k
        DISPATCH();
2177
3.63k
    }
2178
2179
3.63k
    CASE(LOADI) {
2180
2.27k
        uint8_t a = REG_GET_A(instr);
2181
2.27k
        int16_t sbx = REG_GET_sBx(instr);
2182
2.27k
        reg_set(&R[a], value_int((int64_t)sbx));
2183
        /* Record history for tracked variables */
2184
2.27k
        {
2185
2.27k
            if (vm->rt->tracking_active &&
2186
2.27k
                frame->chunk->local_names && a < frame->chunk->local_name_cap &&
2187
2.27k
                frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) {
2188
12
                rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]);
2189
12
            }
2190
2.27k
        }
2191
2.27k
        DISPATCH();
2192
2.27k
    }
2193
2194
2.27k
    CASE(LOADNIL) {
2195
96
        uint8_t a = REG_GET_A(instr);
2196
96
        reg_set(&R[a], value_nil());
2197
96
        DISPATCH();
2198
96
    }
2199
2200
98
    CASE(LOADTRUE) {
2201
98
        uint8_t a = REG_GET_A(instr);
2202
98
        reg_set(&R[a], value_bool(true));
2203
98
        {
2204
98
            if (vm->rt->tracking_active &&
2205
98
                frame->chunk->local_names && a < frame->chunk->local_name_cap &&
2206
98
                frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) {
2207
1
                rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]);
2208
1
            }
2209
98
        }
2210
98
        DISPATCH();
2211
98
    }
2212
2213
245
    CASE(LOADFALSE) {
2214
245
        uint8_t a = REG_GET_A(instr);
2215
245
        reg_set(&R[a], value_bool(false));
2216
245
        {
2217
245
            if (vm->rt->tracking_active &&
2218
245
                frame->chunk->local_names && a < frame->chunk->local_name_cap &&
2219
245
                frame->chunk->local_names[a] && frame->chunk->local_names[a][0]) {
2220
0
                rt_record_history(vm->rt, frame->chunk->local_names[a], &R[a]);
2221
0
            }
2222
245
        }
2223
245
        DISPATCH();
2224
245
    }
2225
2226
2.72k
    CASE(LOADUNIT) {
2227
2.72k
        uint8_t a = REG_GET_A(instr);
2228
2.72k
        reg_set(&R[a], value_unit());
2229
2.72k
        DISPATCH();
2230
2.72k
    }
2231
2232
2.72k
    CASE(ADD) {
2233
705
        uint8_t a = REG_GET_A(instr);
2234
705
        uint8_t b = REG_GET_B(instr);
2235
705
        uint8_t c = REG_GET_C(instr);
2236
705
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2237
429
            reg_set(&R[a], value_int(R[b].as.int_val + R[c].as.int_val));
2238
429
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2239
2
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2240
2
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2241
2
            reg_set(&R[a], value_float(lv + rv));
2242
274
        } else if (R[b].type == VAL_STR && R[c].type == VAL_STR) {
2243
274
            size_t lb = strlen(R[b].as.str_val);
2244
274
            size_t lc = strlen(R[c].as.str_val);
2245
274
            char *buf = bump_alloc(vm->ephemeral, lb + lc + 1);
2246
274
            memcpy(buf, R[b].as.str_val, lb);
2247
274
            memcpy(buf + lb, R[c].as.str_val, lc);
2248
274
            buf[lb + lc] = '\0';
2249
274
            LatValue v = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = REGION_EPHEMERAL };
2250
274
            v.as.str_val = buf;
2251
274
            reg_set(&R[a], v);
2252
274
        } else {
2253
0
            RVM_ERROR("cannot add %s and %s",
2254
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2255
0
        }
2256
705
        DISPATCH();
2257
705
    }
2258
2259
44
    CASE(SUB) {
2260
44
        uint8_t a = REG_GET_A(instr);
2261
44
        uint8_t b = REG_GET_B(instr);
2262
44
        uint8_t c = REG_GET_C(instr);
2263
44
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2264
43
            reg_set(&R[a], value_int(R[b].as.int_val - R[c].as.int_val));
2265
43
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2266
1
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2267
1
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2268
1
            reg_set(&R[a], value_float(lv - rv));
2269
1
        } else {
2270
0
            RVM_ERROR("cannot subtract %s from %s",
2271
0
                value_type_name(&R[c]), value_type_name(&R[b]));
2272
0
        }
2273
44
        DISPATCH();
2274
44
    }
2275
2276
213
    CASE(MUL) {
2277
213
        uint8_t a = REG_GET_A(instr);
2278
213
        uint8_t b = REG_GET_B(instr);
2279
213
        uint8_t c = REG_GET_C(instr);
2280
213
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2281
211
            reg_set(&R[a], value_int(R[b].as.int_val * R[c].as.int_val));
2282
211
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2283
2
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2284
2
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2285
2
            reg_set(&R[a], value_float(lv * rv));
2286
2
        } else {
2287
0
            RVM_ERROR("cannot multiply %s and %s",
2288
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2289
0
        }
2290
213
        DISPATCH();
2291
213
    }
2292
2293
12
    CASE(DIV) {
2294
12
        uint8_t a = REG_GET_A(instr);
2295
12
        uint8_t b = REG_GET_B(instr);
2296
12
        uint8_t c = REG_GET_C(instr);
2297
12
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2298
10
            if (R[c].as.int_val == 0)
2299
8
                RVM_ERROR("division by zero");
2300
9
            reg_set(&R[a], value_int(R[b].as.int_val / R[c].as.int_val));
2301
9
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2302
2
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2303
2
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2304
2
            reg_set(&R[a], value_float(lv / rv));  /* float div by zero → Inf/NaN */
2305
2
        } else {
2306
0
            RVM_ERROR("cannot divide %s by %s",
2307
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2308
0
        }
2309
11
        DISPATCH();
2310
11
    }
2311
2312
106
    CASE(MOD) {
2313
106
        uint8_t a = REG_GET_A(instr);
2314
106
        uint8_t b = REG_GET_B(instr);
2315
106
        uint8_t c = REG_GET_C(instr);
2316
106
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2317
106
            if (R[c].as.int_val == 0)
2318
0
                RVM_ERROR("modulo by zero");
2319
106
            reg_set(&R[a], value_int(R[b].as.int_val % R[c].as.int_val));
2320
106
        } else {
2321
0
            RVM_ERROR("cannot modulo %s by %s",
2322
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2323
0
        }
2324
106
        DISPATCH();
2325
106
    }
2326
2327
16
    CASE(NEG) {
2328
16
        uint8_t a = REG_GET_A(instr);
2329
16
        uint8_t b = REG_GET_B(instr);
2330
16
        if (R[b].type == VAL_INT) {
2331
15
            reg_set(&R[a], value_int(-R[b].as.int_val));
2332
15
        } else if (R[b].type == VAL_FLOAT) {
2333
1
            reg_set(&R[a], value_float(-R[b].as.float_val));
2334
1
        } else {
2335
0
            RVM_ERROR("cannot negate %s", value_type_name(&R[b]));
2336
0
        }
2337
16
        DISPATCH();
2338
16
    }
2339
2340
293
    CASE(ADDI) {
2341
293
        uint8_t a = REG_GET_A(instr);
2342
293
        uint8_t b = REG_GET_B(instr);
2343
293
        int8_t  c = (int8_t)REG_GET_C(instr);
2344
293
        if (R[b].type == VAL_INT) {
2345
293
            reg_set(&R[a], value_int(R[b].as.int_val + c));
2346
293
        } else if (R[b].type == VAL_FLOAT) {
2347
0
            reg_set(&R[a], value_float(R[b].as.float_val + c));
2348
0
        } else {
2349
0
            RVM_ERROR("cannot add immediate to %s", value_type_name(&R[b]));
2350
0
        }
2351
293
        DISPATCH();
2352
293
    }
2353
2354
47
    CASE(CONCAT) {
2355
47
        uint8_t a = REG_GET_A(instr);
2356
47
        uint8_t b = REG_GET_B(instr);
2357
47
        uint8_t c = REG_GET_C(instr);
2358
47
        char *ls = value_display(&R[b]);
2359
47
        char *rs = value_display(&R[c]);
2360
47
        size_t ll = strlen(ls), rl = strlen(rs);
2361
47
        char *buf = bump_alloc(vm->ephemeral, ll + rl + 1);
2362
47
        memcpy(buf, ls, ll);
2363
47
        memcpy(buf + ll, rs, rl);
2364
47
        buf[ll + rl] = '\0';
2365
47
        free(ls); free(rs);
2366
47
        LatValue v = { .type = VAL_STR, .phase = VTAG_UNPHASED, .region_id = REGION_EPHEMERAL };
2367
47
        v.as.str_val = buf;
2368
47
        reg_set(&R[a], v);
2369
47
        DISPATCH();
2370
47
    }
2371
2372
605
    CASE(EQ) {
2373
605
        uint8_t a = REG_GET_A(instr);
2374
605
        uint8_t b = REG_GET_B(instr);
2375
605
        uint8_t c = REG_GET_C(instr);
2376
605
        reg_set(&R[a], value_bool(value_eq(&R[b], &R[c])));
2377
605
        DISPATCH();
2378
605
    }
2379
2380
605
    CASE(NEQ) {
2381
52
        uint8_t a = REG_GET_A(instr);
2382
52
        uint8_t b = REG_GET_B(instr);
2383
52
        uint8_t c = REG_GET_C(instr);
2384
52
        reg_set(&R[a], value_bool(!value_eq(&R[b], &R[c])));
2385
52
        DISPATCH();
2386
52
    }
2387
2388
162
    CASE(LT) {
2389
162
        uint8_t a = REG_GET_A(instr);
2390
162
        uint8_t b = REG_GET_B(instr);
2391
162
        uint8_t c = REG_GET_C(instr);
2392
162
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2393
160
            reg_set(&R[a], value_bool(R[b].as.int_val < R[c].as.int_val));
2394
160
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2395
2
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2396
2
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2397
2
            reg_set(&R[a], value_bool(lv < rv));
2398
2
        } else {
2399
0
            RVM_ERROR("cannot compare %s < %s",
2400
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2401
0
        }
2402
162
        DISPATCH();
2403
162
    }
2404
2405
24
    CASE(LTEQ) {
2406
24
        uint8_t a = REG_GET_A(instr);
2407
24
        uint8_t b = REG_GET_B(instr);
2408
24
        uint8_t c = REG_GET_C(instr);
2409
24
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2410
24
            reg_set(&R[a], value_bool(R[b].as.int_val <= R[c].as.int_val));
2411
24
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2412
0
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2413
0
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2414
0
            reg_set(&R[a], value_bool(lv <= rv));
2415
0
        } else {
2416
0
            RVM_ERROR("cannot compare %s <= %s",
2417
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2418
0
        }
2419
24
        DISPATCH();
2420
24
    }
2421
2422
267
    CASE(GT) {
2423
267
        uint8_t a = REG_GET_A(instr);
2424
267
        uint8_t b = REG_GET_B(instr);
2425
267
        uint8_t c = REG_GET_C(instr);
2426
267
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2427
264
            reg_set(&R[a], value_bool(R[b].as.int_val > R[c].as.int_val));
2428
264
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2429
3
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2430
3
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2431
3
            reg_set(&R[a], value_bool(lv > rv));
2432
3
        } else {
2433
0
            RVM_ERROR("cannot compare %s > %s",
2434
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2435
0
        }
2436
267
        DISPATCH();
2437
267
    }
2438
2439
212
    CASE(GTEQ) {
2440
212
        uint8_t a = REG_GET_A(instr);
2441
212
        uint8_t b = REG_GET_B(instr);
2442
212
        uint8_t c = REG_GET_C(instr);
2443
212
        if (R[b].type == VAL_INT && R[c].type == VAL_INT) {
2444
211
            reg_set(&R[a], value_bool(R[b].as.int_val >= R[c].as.int_val));
2445
211
        } else if (R[b].type == VAL_FLOAT || R[c].type == VAL_FLOAT) {
2446
1
            double lv = R[b].type == VAL_FLOAT ? R[b].as.float_val : (double)R[b].as.int_val;
2447
1
            double rv = R[c].type == VAL_FLOAT ? R[c].as.float_val : (double)R[c].as.int_val;
2448
1
            reg_set(&R[a], value_bool(lv >= rv));
2449
1
        } else {
2450
0
            RVM_ERROR("cannot compare %s >= %s",
2451
0
                value_type_name(&R[b]), value_type_name(&R[c]));
2452
0
        }
2453
212
        DISPATCH();
2454
212
    }
2455
2456
32
    CASE(NOT) {
2457
32
        uint8_t a = REG_GET_A(instr);
2458
32
        uint8_t b = REG_GET_B(instr);
2459
32
        reg_set(&R[a], value_bool(!value_is_truthy(&R[b])));
2460
32
        DISPATCH();
2461
32
    }
2462
2463
912
    CASE(JMP) {
2464
912
        int32_t offset = REG_GET_sBx24(instr);
2465
912
        frame->ip += offset;
2466
912
        DISPATCH();
2467
912
    }
2468
2469
1.90k
    CASE(JMPFALSE) {
2470
1.90k
        uint8_t a = REG_GET_A(instr);
2471
1.90k
        int16_t offset = REG_GET_sBx(instr);
2472
1.90k
        if (!value_is_truthy(&R[a]))
2473
1.09k
            frame->ip += offset;
2474
1.90k
        DISPATCH();
2475
1.90k
    }
2476
2477
1.90k
    CASE(JMPTRUE) {
2478
20
        uint8_t a = REG_GET_A(instr);
2479
20
        int16_t offset = REG_GET_sBx(instr);
2480
20
        if (value_is_truthy(&R[a]))
2481
12
            frame->ip += offset;
2482
20
        DISPATCH();
2483
20
    }
2484
2485
2.82k
    CASE(GETGLOBAL) {
2486
2.82k
        uint8_t a = REG_GET_A(instr);
2487
2.82k
        uint16_t bx = REG_GET_Bx(instr);
2488
2.82k
        const char *name = frame->chunk->constants[bx].as.str_val;
2489
2.82k
        LatValue val;
2490
2.82k
        if (!env_get(vm->env, name, &val))
2491
4
            RVM_ERROR("undefined variable '%s'", name);
2492
2.82k
        reg_set(&R[a], rvm_clone(&val));
2493
2.82k
        DISPATCH();
2494
2.82k
    }
2495
2496
11
    CASE(SETGLOBAL) {
2497
11
        uint8_t a = REG_GET_A(instr);
2498
11
        uint16_t bx = REG_GET_Bx(instr);
2499
11
        const char *name = frame->chunk->constants[bx].as.str_val;
2500
11
        if (!env_set(vm->env, name, rvm_clone(&R[a])))
2501
0
            RVM_ERROR("undefined variable '%s'", name);
2502
        /* Record history for tracked globals */
2503
11
        {
2504
11
            if (vm->rt->tracking_active)
2505
0
                rt_record_history(vm->rt, name, &R[a]);
2506
11
        }
2507
11
        DISPATCH();
2508
11
    }
2509
2510
2.86k
    CASE(DEFINEGLOBAL) {
2511
2.86k
        uint8_t a = REG_GET_A(instr);
2512
2.86k
        uint16_t bx = REG_GET_Bx(instr);
2513
2.86k
        const char *name = frame->chunk->constants[bx].as.str_val;
2514
2.86k
        LatValue val = rvm_clone(&R[a]);
2515
2516
        /* Phase-dispatch overloading: if defining a phase-constrained
2517
         * closure and one already exists, create an overload array */
2518
2.86k
        if (val.type == VAL_CLOSURE && val.as.closure.native_fn != NULL &&
2519
2.86k
            val.as.closure.default_values != VM_NATIVE_MARKER) {
2520
2.65k
            uint32_t magic;
2521
2.65k
            memcpy(&magic, val.as.closure.native_fn, sizeof(uint32_t));
2522
2.65k
            if (magic == REGCHUNK_MAGIC) {
2523
2.65k
                RegChunk *ch = (RegChunk *)val.as.closure.native_fn;
2524
2.65k
                if (ch->param_phases) {
2525
13
                    LatValue existing;
2526
13
                    if (env_get(vm->env, name, &existing)) {
2527
2
                        if (existing.type == VAL_CLOSURE && existing.as.closure.native_fn != NULL &&
2528
2
                            existing.as.closure.default_values != VM_NATIVE_MARKER) {
2529
2
                            uint32_t emag;
2530
2
                            memcpy(&emag, existing.as.closure.native_fn, sizeof(uint32_t));
2531
2
                            if (emag == REGCHUNK_MAGIC) {
2532
2
                                RegChunk *ech = (RegChunk *)existing.as.closure.native_fn;
2533
2
                                if (ech->param_phases) {
2534
2
                                    LatValue elems[2] = { value_deep_clone(&existing), val };
2535
2
                                    LatValue arr = value_array(elems, 2);
2536
2
                                    env_define(vm->env, name, arr);
2537
2
                                    DISPATCH();
2538
2
                                }
2539
2
                            }
2540
2
                        } else if (existing.type == VAL_ARRAY) {
2541
0
                            size_t new_len = existing.as.array.len + 1;
2542
0
                            LatValue *new_elems = malloc(new_len * sizeof(LatValue));
2543
0
                            for (size_t i = 0; i < existing.as.array.len; i++)
2544
0
                                new_elems[i] = value_deep_clone(&existing.as.array.elems[i]);
2545
0
                            new_elems[existing.as.array.len] = val;
2546
0
                            LatValue arr = value_array(new_elems, new_len);
2547
0
                            free(new_elems);
2548
0
                            env_define(vm->env, name, arr);
2549
0
                            DISPATCH();
2550
0
                        }
2551
2
                    }
2552
13
                }
2553
2.65k
            }
2554
2.65k
        }
2555
2556
2.86k
        env_define(vm->env, name, val);
2557
2.86k
        DISPATCH();
2558
2.86k
    }
2559
2560
2.86k
    CASE(GETFIELD) {
2561
291
        uint8_t a = REG_GET_A(instr);
2562
291
        uint8_t b = REG_GET_B(instr);
2563
291
        uint8_t c = REG_GET_C(instr);
2564
291
        const char *field_name = frame->chunk->constants[c].as.str_val;
2565
2566
291
        if (R[b].type == VAL_STRUCT) {
2567
260
            bool found = false;
2568
331
            for (size_t i = 0; i < R[b].as.strct.field_count; i++) {
2569
331
                if (strcmp(R[b].as.strct.field_names[i], field_name) == 0) {
2570
260
                    reg_set(&R[a], rvm_clone(&R[b].as.strct.field_values[i]));
2571
260
                    found = true;
2572
260
                    break;
2573
260
                }
2574
331
            }
2575
260
            if (!found)
2576
0
                RVM_ERROR("struct '%s' has no field '%s'",
2577
260
                    R[b].as.strct.name, field_name);
2578
260
        } else if (R[b].type == VAL_MAP) {
2579
25
            LatValue *val = lat_map_get(R[b].as.map.map, field_name);
2580
25
            if (val)
2581
22
                reg_set(&R[a], rvm_clone(val));
2582
3
            else
2583
3
                reg_set(&R[a], value_nil());
2584
25
        } else if (R[b].type == VAL_TUPLE) {
2585
6
            char *endp;
2586
6
            long idx = strtol(field_name, &endp, 10);
2587
6
            if (*endp == '\0' && idx >= 0 && (size_t)idx < R[b].as.tuple.len)
2588
6
                reg_set(&R[a], rvm_clone(&R[b].as.tuple.elems[idx]));
2589
0
            else
2590
0
                RVM_ERROR("tuple has no field '%s'", field_name);
2591
6
        } else if (R[b].type == VAL_ENUM) {
2592
0
            if (strcmp(field_name, "tag") == 0 || strcmp(field_name, "variant_name") == 0)
2593
0
                reg_set(&R[a], value_string(R[b].as.enm.variant_name));
2594
0
            else if (strcmp(field_name, "enum_name") == 0)
2595
0
                reg_set(&R[a], value_string(R[b].as.enm.enum_name));
2596
0
            else if (strcmp(field_name, "payload") == 0) {
2597
0
                if (R[b].as.enm.payload_count > 0) {
2598
0
                    LatValue *elems = malloc(R[b].as.enm.payload_count * sizeof(LatValue));
2599
0
                    for (size_t pi = 0; pi < R[b].as.enm.payload_count; pi++)
2600
0
                        elems[pi] = rvm_clone(&R[b].as.enm.payload[pi]);
2601
0
                    reg_set(&R[a], value_array(elems, R[b].as.enm.payload_count));
2602
0
                    free(elems);
2603
0
                } else {
2604
0
                    reg_set(&R[a], value_array(NULL, 0));
2605
0
                }
2606
0
            } else
2607
0
                RVM_ERROR("enum has no field '%s'", field_name);
2608
0
        } else {
2609
0
            RVM_ERROR("cannot access field '%s' on %s",
2610
0
                field_name, value_type_name(&R[b]));
2611
0
        }
2612
291
        DISPATCH();
2613
291
    }
2614
2615
15
    CASE(SETFIELD) {
2616
15
        uint8_t a = REG_GET_A(instr);  /* object reg */
2617
15
        uint8_t b = REG_GET_B(instr);  /* field name constant */
2618
15
        uint8_t c = REG_GET_C(instr);  /* value reg */
2619
15
        const char *field_name = frame->chunk->constants[b].as.str_val;
2620
2621
        /* Phase checks */
2622
15
        if (R[a].phase == VTAG_CRYSTAL) {
2623
            /* Check per-field phases for structs with partial freeze (freeze except) */
2624
3
            bool blocked = true;
2625
3
            if (R[a].type == VAL_STRUCT && R[a].as.strct.field_phases) {
2626
4
                for (size_t i = 0; i < R[a].as.strct.field_count; i++) {
2627
4
                    if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) {
2628
2
                        if (R[a].as.strct.field_phases[i] != VTAG_CRYSTAL) blocked = false;
2629
2
                        break;
2630
2
                    }
2631
4
                }
2632
2
            }
2633
3
            if (blocked)
2634
2
                RVM_ERROR("cannot set field '%s' on a frozen value", field_name);
2635
3
        }
2636
        /* Also check per-field phases (alloy types) even on non-frozen structs */
2637
13
        if (R[a].type == VAL_STRUCT && R[a].as.strct.field_phases &&
2638
13
            R[a].phase != VTAG_CRYSTAL) {
2639
8
            for (size_t i = 0; i < R[a].as.strct.field_count; i++) {
2640
8
                if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) {
2641
5
                    if (R[a].as.strct.field_phases[i] == VTAG_CRYSTAL)
2642
2
                        RVM_ERROR("cannot assign to frozen field '%s'", field_name);
2643
3
                    break;
2644
5
                }
2645
8
            }
2646
5
        }
2647
2648
11
        if (R[a].type == VAL_STRUCT) {
2649
19
            for (size_t i = 0; i < R[a].as.strct.field_count; i++) {
2650
19
                if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) {
2651
11
                    value_free(&R[a].as.strct.field_values[i]);
2652
11
                    R[a].as.strct.field_values[i] = rvm_clone(&R[c]);
2653
11
                    break;
2654
11
                }
2655
19
            }
2656
11
        } else if (R[a].type == VAL_MAP) {
2657
0
            LatValue cloned = rvm_clone(&R[c]);
2658
0
            lat_map_set(R[a].as.map.map, field_name, &cloned);
2659
0
        }
2660
11
        DISPATCH();
2661
11
    }
2662
2663
331
    CASE(GETINDEX) {
2664
331
        uint8_t a = REG_GET_A(instr);
2665
331
        uint8_t b = REG_GET_B(instr);
2666
331
        uint8_t c = REG_GET_C(instr);
2667
2668
331
        if (R[b].type == VAL_ARRAY && R[c].type == VAL_RANGE) {
2669
            /* Array range slicing: arr[start..end] */
2670
3
            int64_t start = R[c].as.range.start;
2671
3
            int64_t end = R[c].as.range.end;
2672
3
            size_t len = R[b].as.array.len;
2673
3
            if (start < 0) start = 0;
2674
3
            if ((size_t)start > len) start = (int64_t)len;
2675
3
            if (end < 0) end = 0;
2676
3
            if ((size_t)end > len) end = (int64_t)len;
2677
3
            if (start >= end) {
2678
1
                reg_set(&R[a], value_array(NULL, 0));
2679
2
            } else {
2680
2
                size_t slice_len = (size_t)(end - start);
2681
2
                LatValue *elems = malloc(slice_len * sizeof(LatValue));
2682
8
                for (size_t i = 0; i < slice_len; i++)
2683
6
                    elems[i] = rvm_clone(&R[b].as.array.elems[start + (int64_t)i]);
2684
2
                reg_set(&R[a], value_array(elems, slice_len));
2685
2
                free(elems);
2686
2
            }
2687
328
        } else if (R[b].type == VAL_STR && R[c].type == VAL_RANGE) {
2688
            /* String range slicing: str[start..end] */
2689
2
            int64_t start = R[c].as.range.start;
2690
2
            int64_t end = R[c].as.range.end;
2691
2
            size_t len = strlen(R[b].as.str_val);
2692
2
            if (start < 0) start = 0;
2693
2
            if ((size_t)start > len) start = (int64_t)len;
2694
2
            if (end < 0) end = 0;
2695
2
            if ((size_t)end > len) end = (int64_t)len;
2696
2
            if (start >= end) {
2697
0
                reg_set(&R[a], value_string(""));
2698
2
            } else {
2699
2
                size_t slice_len = (size_t)(end - start);
2700
2
                char *slice = malloc(slice_len + 1);
2701
2
                memcpy(slice, R[b].as.str_val + start, slice_len);
2702
2
                slice[slice_len] = '\0';
2703
2
                reg_set(&R[a], value_string_owned(slice));
2704
2
            }
2705
326
        } else if (R[b].type == VAL_ARRAY) {
2706
250
            if (R[c].type != VAL_INT)
2707
0
                RVM_ERROR("array index must be an integer");
2708
250
            int64_t idx = R[c].as.int_val;
2709
250
            if (idx < 0) idx += (int64_t)R[b].as.array.len;
2710
250
            if (idx < 0 || (size_t)idx >= R[b].as.array.len)
2711
0
                RVM_ERROR("array index %lld out of bounds (len %zu)",
2712
250
                    (long long)R[c].as.int_val, R[b].as.array.len);
2713
250
            reg_set(&R[a], rvm_clone(&R[b].as.array.elems[idx]));
2714
250
        } else if (R[b].type == VAL_MAP) {
2715
40
            if (R[c].type != VAL_STR)
2716
0
                RVM_ERROR("map key must be a string");
2717
40
            LatValue *val = lat_map_get(R[b].as.map.map, R[c].as.str_val);
2718
40
            if (val)
2719
40
                reg_set(&R[a], rvm_clone(val));
2720
0
            else
2721
0
                reg_set(&R[a], value_nil());
2722
40
        } else if (R[b].type == VAL_STR) {
2723
3
            if (R[c].type != VAL_INT)
2724
0
                RVM_ERROR("string index must be an integer");
2725
3
            int64_t idx = R[c].as.int_val;
2726
3
            size_t len = strlen(R[b].as.str_val);
2727
3
            if (idx < 0) idx += (int64_t)len;
2728
3
            if (idx < 0 || (size_t)idx >= len)
2729
0
                RVM_ERROR("string index out of bounds");
2730
3
            char buf[2] = { R[b].as.str_val[idx], '\0' };
2731
3
            reg_set(&R[a], value_string(buf));
2732
33
        } else if (R[b].type == VAL_BUFFER) {
2733
28
            if (R[c].type != VAL_INT)
2734
0
                RVM_ERROR("buffer index must be an integer");
2735
28
            int64_t idx = R[c].as.int_val;
2736
28
            if (idx < 0 || (size_t)idx >= R[b].as.buffer.len)
2737
0
                RVM_ERROR("buffer index out of bounds");
2738
28
            reg_set(&R[a], value_int((int64_t)R[b].as.buffer.data[idx]));
2739
28
        } else if (R[b].type == VAL_REF) {
2740
            /* Proxy indexing on Ref inner value */
2741
5
            LatRef *ref = R[b].as.ref.ref;
2742
5
            if (ref->value.type == VAL_MAP) {
2743
3
                if (R[c].type != VAL_STR) RVM_ERROR("map key must be a string");
2744
3
                LatValue *val = lat_map_get(ref->value.as.map.map, R[c].as.str_val);
2745
3
                reg_set(&R[a], val ? rvm_clone(val) : value_nil());
2746
3
            } else if (ref->value.type == VAL_ARRAY) {
2747
2
                if (R[c].type != VAL_INT) RVM_ERROR("array index must be an integer");
2748
2
                int64_t idx = R[c].as.int_val;
2749
2
                if (idx < 0) idx += (int64_t)ref->value.as.array.len;
2750
2
                if (idx < 0 || (size_t)idx >= ref->value.as.array.len)
2751
0
                    RVM_ERROR("array index out of bounds");
2752
2
                reg_set(&R[a], rvm_clone(&ref->value.as.array.elems[idx]));
2753
2
            } else {
2754
0
                RVM_ERROR("cannot index Ref(%s)", value_type_name(&ref->value));
2755
0
            }
2756
5
        } else {
2757
0
            RVM_ERROR("cannot index %s", value_type_name(&R[b]));
2758
0
        }
2759
331
        DISPATCH();
2760
331
    }
2761
2762
8
    CASE(SETINDEX) {
2763
8
        uint8_t a = REG_GET_A(instr);  /* object */
2764
8
        uint8_t b = REG_GET_B(instr);  /* index */
2765
8
        uint8_t c = REG_GET_C(instr);  /* value */
2766
2767
        /* Phase checks for mutation */
2768
8
        if (R[a].phase == VTAG_CRYSTAL) {
2769
            /* Allow mutation on maps with per-key phases (freeze except) if key is not frozen */
2770
1
            bool blocked = true;
2771
1
            if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) {
2772
0
                PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val);
2773
0
                if (!kp || *kp != VTAG_CRYSTAL) blocked = false;
2774
0
            }
2775
1
            if (blocked)
2776
1
                RVM_ERROR("cannot modify a frozen value");
2777
1
        }
2778
7
        if (R[a].phase == VTAG_SUBLIMATED && R[a].type == VAL_MAP)
2779
0
            RVM_ERROR("cannot add keys to a sublimated map");
2780
        /* Per-key phase check for non-frozen maps */
2781
7
        if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) {
2782
0
            PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val);
2783
0
            if (kp && *kp == VTAG_CRYSTAL)
2784
0
                RVM_ERROR("cannot modify frozen key '%s'", R[b].as.str_val);
2785
0
        }
2786
2787
7
        if (R[a].type == VAL_ARRAY) {
2788
0
            if (R[b].type != VAL_INT)
2789
0
                RVM_ERROR("array index must be an integer");
2790
0
            int64_t idx = R[b].as.int_val;
2791
0
            if (idx < 0) idx += (int64_t)R[a].as.array.len;
2792
0
            if (idx < 0 || (size_t)idx >= R[a].as.array.len)
2793
0
                RVM_ERROR("array index out of bounds");
2794
0
            value_free(&R[a].as.array.elems[idx]);
2795
0
            R[a].as.array.elems[idx] = rvm_clone(&R[c]);
2796
7
        } else if (R[a].type == VAL_MAP) {
2797
0
            if (R[b].type != VAL_STR)
2798
0
                RVM_ERROR("map key must be a string");
2799
0
            LatValue cloned = rvm_clone(&R[c]);
2800
0
            lat_map_set(R[a].as.map.map, R[b].as.str_val, &cloned);
2801
7
        } else if (R[a].type == VAL_BUFFER) {
2802
2
            if (R[b].type != VAL_INT) RVM_ERROR("buffer index must be an integer");
2803
2
            int64_t idx = R[b].as.int_val;
2804
2
            if (idx < 0 || (size_t)idx >= R[a].as.buffer.len)
2805
0
                RVM_ERROR("buffer index out of bounds");
2806
2
            if (R[c].type != VAL_INT) RVM_ERROR("buffer value must be an integer");
2807
2
            R[a].as.buffer.data[(size_t)idx] = (uint8_t)(R[c].as.int_val & 0xFF);
2808
5
        } else if (R[a].type == VAL_REF) {
2809
            /* Proxy: set index on inner value */
2810
5
            LatRef *ref = R[a].as.ref.ref;
2811
5
            if (ref->value.type == VAL_MAP) {
2812
4
                if (R[b].type != VAL_STR) RVM_ERROR("map key must be a string");
2813
4
                LatValue cloned = rvm_clone(&R[c]);
2814
4
                lat_map_set(ref->value.as.map.map, R[b].as.str_val, &cloned);
2815
4
            } else if (ref->value.type == VAL_ARRAY) {
2816
1
                if (R[b].type != VAL_INT) RVM_ERROR("array index must be an integer");
2817
1
                int64_t idx = R[b].as.int_val;
2818
1
                if (idx < 0) idx += (int64_t)ref->value.as.array.len;
2819
1
                if (idx < 0 || (size_t)idx >= ref->value.as.array.len)
2820
0
                    RVM_ERROR("array index out of bounds");
2821
1
                value_free(&ref->value.as.array.elems[idx]);
2822
1
                ref->value.as.array.elems[idx] = rvm_clone(&R[c]);
2823
1
            } else {
2824
0
                RVM_ERROR("cannot set index on Ref(%s)", value_type_name(&ref->value));
2825
0
            }
2826
5
        } else {
2827
0
            RVM_ERROR("cannot set index on %s", value_type_name(&R[a]));
2828
0
        }
2829
7
        DISPATCH();
2830
7
    }
2831
2832
696
    CASE(GETUPVALUE) {
2833
696
        uint8_t a = REG_GET_A(instr);
2834
696
        uint8_t b = REG_GET_B(instr);
2835
696
        if (frame->upvalues && b < frame->upvalue_count) {
2836
696
            reg_set(&R[a], rvm_clone(frame->upvalues[b]->location));
2837
696
        }
2838
696
        DISPATCH();
2839
696
    }
2840
2841
696
    CASE(SETUPVALUE) {
2842
0
        uint8_t a = REG_GET_A(instr);
2843
0
        uint8_t b = REG_GET_B(instr);
2844
0
        if (frame->upvalues && b < frame->upvalue_count) {
2845
0
            value_free(frame->upvalues[b]->location);
2846
0
            *frame->upvalues[b]->location = rvm_clone(&R[a]);
2847
0
        }
2848
0
        DISPATCH();
2849
0
    }
2850
2851
0
    CASE(CLOSEUPVALUE) {
2852
        /* Close upvalue at register A */
2853
0
        uint8_t a = REG_GET_A(instr);
2854
0
        ObjUpvalue *prev = NULL;
2855
0
        ObjUpvalue *uv = vm->open_upvalues;
2856
0
        while (uv) {
2857
0
            if (uv->location == &R[a]) {
2858
0
                uv->closed = rvm_clone(&R[a]);
2859
0
                uv->location = &uv->closed;
2860
0
                if (prev) prev->next = uv->next;
2861
0
                else vm->open_upvalues = uv->next;
2862
0
                break;
2863
0
            }
2864
0
            prev = uv;
2865
0
            uv = uv->next;
2866
0
        }
2867
0
        DISPATCH();
2868
0
    }
2869
2870
3.14k
    CASE(CALL) {
2871
3.14k
        uint8_t a = REG_GET_A(instr);     /* func register */
2872
3.14k
        uint8_t b = REG_GET_B(instr);     /* arg count */
2873
3.14k
        uint8_t c = REG_GET_C(instr);     /* return count (1 for now) */
2874
3.14k
        (void)c;
2875
2876
3.14k
        LatValue *func = &R[a];
2877
2878
        /* Phase-dispatch overload resolution: VAL_ARRAY of closures */
2879
3.14k
        if (func->type == VAL_ARRAY) {
2880
2
            int best_score = -1;
2881
2
            int best_idx = -1;
2882
6
            for (size_t ci = 0; ci < func->as.array.len; ci++) {
2883
4
                LatValue *cand = &func->as.array.elems[ci];
2884
4
                if (cand->type != VAL_CLOSURE || cand->as.closure.native_fn == NULL) continue;
2885
4
                if (cand->as.closure.default_values == VM_NATIVE_MARKER) continue;
2886
4
                uint32_t cmag;
2887
4
                memcpy(&cmag, cand->as.closure.native_fn, sizeof(uint32_t));
2888
4
                if (cmag != REGCHUNK_MAGIC) continue;
2889
4
                RegChunk *ch = (RegChunk *)cand->as.closure.native_fn;
2890
4
                if (!ch->param_phases) continue;
2891
4
                bool compatible = true;
2892
4
                int score = 0;
2893
6
                for (int j = 0; j < ch->param_phase_count && j < (int)b; j++) {
2894
4
                    uint8_t pp = ch->param_phases[j];
2895
4
                    LatValue *arg = &R[a + 1 + j];
2896
4
                    if (pp == PHASE_FLUID) {
2897
2
                        if (arg->phase == VTAG_CRYSTAL) { compatible = false; break; }
2898
1
                        if (arg->phase == VTAG_FLUID) score += 3;
2899
0
                        else score += 1;
2900
2
                    } else if (pp == PHASE_CRYSTAL) {
2901
2
                        if (arg->phase == VTAG_FLUID) { compatible = false; break; }
2902
1
                        if (arg->phase == VTAG_CRYSTAL) score += 3;
2903
0
                        else score += 1;
2904
1
                    } else {
2905
0
                        if (arg->phase == VTAG_UNPHASED) score += 2;
2906
0
                        else score += 1;
2907
0
                    }
2908
4
                }
2909
4
                if (compatible && score > best_score) {
2910
2
                    best_score = score;
2911
2
                    best_idx = (int)ci;
2912
2
                }
2913
4
            }
2914
2
            if (best_idx >= 0) {
2915
2
                LatValue matched = value_deep_clone(&func->as.array.elems[best_idx]);
2916
2
                reg_set(func, matched);
2917
2
            } else {
2918
0
                RVM_ERROR("no matching overload for given argument phases");
2919
0
            }
2920
2
        }
2921
2922
3.14k
        if (func->type != VAL_CLOSURE)
2923
0
            RVM_ERROR("attempt to call a non-function (%s)",
2924
3.14k
                value_type_name(func));
2925
2926
        /* Check for native function */
2927
3.14k
        if (func->as.closure.default_values == VM_NATIVE_MARKER) {
2928
1.49k
            VMNativeFn native = (VMNativeFn)func->as.closure.native_fn;
2929
2930
            /* Sync named locals from current call chain to env.
2931
             * Needed for natives that access variables by name (track, react, bond, seed, etc.)
2932
             * and for phase system operations that read env. */
2933
1.49k
            {
2934
5.77k
                for (int fi = 0; fi < vm->frame_count; fi++) {
2935
4.28k
                    RegCallFrame *sf = &vm->frames[fi];
2936
4.28k
                    if (!sf->chunk || !sf->chunk->local_names) continue;
2937
46.6k
                    for (size_t li = 0; li < sf->chunk->local_name_cap; li++) {
2938
44.2k
                        if (sf->chunk->local_names[li] && sf->chunk->local_names[li][0]) {
2939
9.62k
                            LatValue clone = value_deep_clone(&sf->regs[li]);
2940
9.62k
                            if (!env_set(vm->env, sf->chunk->local_names[li], clone))
2941
1.32k
                                env_define(vm->env, sf->chunk->local_names[li], clone);
2942
9.62k
                        }
2943
44.2k
                    }
2944
2.40k
                }
2945
1.49k
            }
2946
            /* Collect args */
2947
1.49k
            LatValue args[16];
2948
2.67k
            for (int i = 0; i < b; i++)
2949
1.18k
                args[i] = rvm_clone(&R[a + 1 + i]);
2950
1.49k
            LatValue ret = native(args, b);
2951
2.67k
            for (int i = 0; i < b; i++)
2952
1.18k
                value_free(&args[i]);
2953
            /* Check runtime for native errors */
2954
1.49k
            if (vm->rt->error) {
2955
60
                char *err = vm->rt->error;
2956
60
                vm->rt->error = NULL;
2957
60
                value_free(&ret);
2958
60
                RVM_ERROR("%s", err);
2959
60
            }
2960
            /* Also check if regvm itself got an error (from re-entrant dispatch callbacks) */
2961
1.44k
            if (vm->error) {
2962
0
                value_free(&ret);
2963
0
                return REGVM_RUNTIME_ERROR;
2964
0
            }
2965
            /* Note: no reverse env→locals sync here — it's too broad and can break
2966
             * closure-captured values. grow() and similar natives that modify variables
2967
             * by name will need specialized handling if needed. */
2968
1.44k
            reg_set(&R[a], ret);
2969
1.44k
            DISPATCH();
2970
1.44k
        }
2971
2972
        /* Extension native function (loaded via require_ext) */
2973
3.09k
        if (func->as.closure.default_values == VM_EXT_MARKER) {
2974
64
            LatValue args[16];
2975
166
            for (int i = 0; i < b; i++)
2976
102
                args[i] = rvm_clone(&R[a + 1 + i]);
2977
64
            LatValue ret = ext_call_native(func->as.closure.native_fn, args, (size_t)b);
2978
166
            for (int i = 0; i < b; i++)
2979
102
                value_free(&args[i]);
2980
            /* Extension errors return strings prefixed with "EVAL_ERROR:" */
2981
64
            if (ret.type == VAL_STR && ret.as.str_val &&
2982
64
                strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) {
2983
3
                char *msg = strdup(ret.as.str_val + 11);
2984
3
                value_free(&ret);
2985
3
                RVM_ERROR("%s", msg);
2986
3
            }
2987
61
            reg_set(&R[a], ret);
2988
61
            DISPATCH();
2989
61
        }
2990
2991
        /* Compiled function call */
2992
3.09k
        RegChunk *fn_chunk = (RegChunk *)func->as.closure.native_fn;
2993
3.09k
        if (!fn_chunk)
2994
0
            RVM_ERROR("attempt to call a closure with NULL chunk");
2995
2996
        /* Guard: detect stack-VM closures (from require()) that can't run in regvm.
2997
         * RegChunks have a magic header; stack-VM Chunks don't.
2998
         * Use memcpy to avoid misaligned read when fn_chunk is actually a stack Chunk. */
2999
3.09k
        {
3000
3.09k
            uint32_t magic;
3001
3.09k
            memcpy(&magic, fn_chunk, sizeof(uint32_t));
3002
3.09k
            if (magic != REGCHUNK_MAGIC)
3003
0
                RVM_ERROR("cannot call stack-VM closure from register VM "
3004
3.09k
                          "(use 'import' instead of 'require')");
3005
3.09k
        }
3006
3007
        /* Phase constraint check on parameters */
3008
3.09k
        if (fn_chunk->param_phases) {
3009
17
            for (int i = 0; i < fn_chunk->param_phase_count && i < (int)b; i++) {
3010
10
                uint8_t pp = fn_chunk->param_phases[i];
3011
10
                if (pp == PHASE_UNSPECIFIED) continue;
3012
10
                LatValue *arg = &R[a + 1 + i];
3013
10
                if (pp == PHASE_FLUID && arg->phase == VTAG_CRYSTAL) {
3014
2
                    RVM_ERROR("phase constraint violation in function '%s'",
3015
2
                        fn_chunk->name ? fn_chunk->name : "<anonymous>");
3016
2
                }
3017
8
                if (pp == PHASE_CRYSTAL && arg->phase == VTAG_FLUID) {
3018
1
                    RVM_ERROR("phase constraint violation in function '%s'",
3019
1
                        fn_chunk->name ? fn_chunk->name : "<anonymous>");
3020
1
                }
3021
8
            }
3022
10
        }
3023
3024
3.08k
        if (vm->frame_count >= REGVM_FRAMES_MAX)
3025
0
            RVM_ERROR("call stack overflow");
3026
3027
        /* Allocate new register window */
3028
3.08k
        size_t new_base = vm->reg_stack_top;
3029
3.08k
        if (new_base + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX)
3030
0
            RVM_ERROR("register stack overflow");
3031
3032
3.08k
        LatValue *new_regs = &vm->reg_stack[new_base];
3033
3.08k
        vm->reg_stack_top += REGVM_REG_MAX;
3034
3035
        /* Initialize new registers to nil */
3036
408k
        for (int i = 0; i < REGVM_REG_MAX; i++)
3037
405k
            new_regs[i] = value_nil();
3038
3039
        /* Copy arguments: R[0] = reserved, R[1..n] = args */
3040
3.08k
        new_regs[0] = value_unit();  /* Reserved slot */
3041
4.18k
        for (int i = 0; i < b; i++) {
3042
1.09k
            value_free(&new_regs[1 + i]);
3043
1.09k
            new_regs[1 + i] = rvm_clone(&R[a + 1 + i]);
3044
1.09k
        }
3045
3046
        /* Set up upvalues */
3047
3.08k
        ObjUpvalue **upvals = (ObjUpvalue **)func->as.closure.captured_env;
3048
3.08k
        size_t uv_count = func->region_id != (size_t)-1 ? func->region_id : 0;
3049
3050
        /* Push new frame */
3051
3.08k
        RegCallFrame *new_frame = &vm->frames[vm->frame_count++];
3052
3.08k
        new_frame->chunk = fn_chunk;
3053
3.08k
        new_frame->ip = fn_chunk->code;
3054
3.08k
        new_frame->regs = new_regs;
3055
3.08k
        new_frame->reg_count = REGVM_REG_MAX;
3056
3.08k
        new_frame->upvalues = upvals;
3057
3.08k
        new_frame->upvalue_count = uv_count;
3058
3.08k
        new_frame->caller_result_reg = a;  /* RETURN puts result here */
3059
3.08k
        frame = new_frame;
3060
3.08k
        R = new_regs;
3061
3.08k
        DISPATCH();
3062
3.08k
    }
3063
3064
2.79k
    CASE(RETURN) {
3065
2.79k
        uint8_t a = REG_GET_A(instr);
3066
2.79k
        uint8_t b = REG_GET_B(instr);
3067
3068
2.79k
        LatValue ret_val = (b > 0) ? rvm_clone(&R[a]) : value_unit();
3069
2.79k
        uint8_t dest_reg = frame->caller_result_reg;
3070
3071
        /* Close any open upvalues that point into this frame's registers */
3072
2.79k
        {
3073
2.79k
            LatValue *frame_base = frame->regs;
3074
2.79k
            LatValue *frame_end = frame_base + REGVM_REG_MAX;
3075
2.79k
            ObjUpvalue **prev = &vm->open_upvalues;
3076
2.94k
            while (*prev) {
3077
151
                ObjUpvalue *uv = *prev;
3078
151
                if (uv->location >= frame_base && uv->location < frame_end) {
3079
                    /* Close this upvalue: move value to uv->closed */
3080
52
                    uv->closed = *uv->location;
3081
52
                    *uv->location = value_nil(); /* prevent double-free */
3082
52
                    uv->location = &uv->closed;
3083
52
                    *prev = uv->next;
3084
99
                } else {
3085
99
                    prev = &uv->next;
3086
99
                }
3087
151
            }
3088
2.79k
        }
3089
3090
        /* Clean up current frame's registers */
3091
719k
        for (int i = 0; i < REGVM_REG_MAX; i++)
3092
716k
            value_free_inline(&frame->regs[i]);
3093
3094
2.79k
        vm->frame_count--;
3095
2.79k
        vm->reg_stack_top -= REGVM_REG_MAX;
3096
3097
2.79k
        if (vm->frame_count == base_frame) {
3098
            /* Return to caller (or top-level) */
3099
1.14k
            *result = ret_val;
3100
1.14k
            return REGVM_OK;
3101
1.14k
        }
3102
3103
        /* Restore caller frame */
3104
1.65k
        frame = &vm->frames[vm->frame_count - 1];
3105
1.65k
        R = frame->regs;
3106
3107
1.65k
        reg_set(&R[dest_reg], ret_val);
3108
1.65k
        DISPATCH();
3109
1.65k
    }
3110
3111
2.78k
    CASE(CLOSURE) {
3112
2.78k
        uint8_t a = REG_GET_A(instr);
3113
2.78k
        uint16_t bx = REG_GET_Bx(instr);
3114
2.78k
        LatValue fn_proto = frame->chunk->constants[bx];
3115
3116
        /* Create closure from prototype */
3117
2.78k
        LatValue closure;
3118
2.78k
        memset(&closure, 0, sizeof(closure));
3119
2.78k
        closure.type = VAL_CLOSURE;
3120
2.78k
        closure.phase = VTAG_UNPHASED;
3121
2.78k
        closure.region_id = (size_t)-1;
3122
2.78k
        closure.as.closure.body = NULL;
3123
2.78k
        closure.as.closure.native_fn = fn_proto.as.closure.native_fn;
3124
2.78k
        closure.as.closure.param_count = fn_proto.as.closure.param_count;
3125
        /* Copy param_names from prototype */
3126
2.78k
        if (fn_proto.as.closure.param_names && fn_proto.as.closure.param_count > 0) {
3127
1.83k
            closure.as.closure.param_names = malloc(fn_proto.as.closure.param_count * sizeof(char *));
3128
4.86k
            for (size_t pi = 0; pi < fn_proto.as.closure.param_count; pi++)
3129
3.02k
                closure.as.closure.param_names[pi] = fn_proto.as.closure.param_names[pi]
3130
3.02k
                    ? strdup(fn_proto.as.closure.param_names[pi]) : NULL;
3131
1.83k
        } else {
3132
944
            closure.as.closure.param_names = NULL;
3133
944
        }
3134
2.78k
        closure.as.closure.default_values = NULL;
3135
2.78k
        closure.as.closure.has_variadic = fn_proto.as.closure.has_variadic;
3136
2.78k
        closure.as.closure.captured_env = NULL;
3137
3138
        /* Process upvalue descriptors that follow the CLOSURE instruction */
3139
        /* Each upvalue descriptor is encoded as a MOVE instruction:
3140
         * A=1 means local, A=0 means upvalue; B=index */
3141
2.78k
        size_t uv_count = fn_proto.region_id;  /* upvalue count stored by compiler */
3142
2.78k
        ObjUpvalue **upvals = NULL;
3143
3144
2.78k
        if (uv_count > 0) {
3145
32
            upvals = malloc(uv_count * sizeof(ObjUpvalue *));
3146
84
            for (size_t i = 0; i < uv_count; i++) {
3147
52
                RegInstr desc = READ_INSTR();
3148
52
                uint8_t is_local = REG_GET_A(desc);
3149
52
                uint8_t index = REG_GET_B(desc);
3150
3151
52
                if (is_local) {
3152
                    /* Capture from current frame's register */
3153
52
                    ObjUpvalue *uv = malloc(sizeof(ObjUpvalue));
3154
52
                    uv->location = &R[index];
3155
52
                    uv->closed = value_nil();
3156
52
                    uv->next = vm->open_upvalues;
3157
52
                    vm->open_upvalues = uv;
3158
52
                    upvals[i] = uv;
3159
52
                } else {
3160
                    /* Capture from enclosing upvalue */
3161
0
                    if (frame->upvalues && index < frame->upvalue_count)
3162
0
                        upvals[i] = frame->upvalues[index];
3163
0
                    else
3164
0
                        upvals[i] = NULL;
3165
0
                }
3166
52
            }
3167
32
            closure.as.closure.captured_env = (Env *)upvals;
3168
32
            closure.region_id = uv_count;
3169
32
        }
3170
3171
2.78k
        reg_set(&R[a], closure);
3172
2.78k
        DISPATCH();
3173
2.78k
    }
3174
3175
2.78k
    CASE(NEWARRAY) {
3176
256
        uint8_t a = REG_GET_A(instr);
3177
256
        uint8_t b = REG_GET_B(instr);  /* base register */
3178
256
        uint8_t c = REG_GET_C(instr);  /* count */
3179
3180
256
        if (c == 0) {
3181
61
            reg_set(&R[a], value_array(NULL, 0));
3182
195
        } else {
3183
195
            LatValue *elems = malloc(c * sizeof(LatValue));
3184
733
            for (int i = 0; i < c; i++)
3185
538
                elems[i] = rvm_clone(&R[b + i]);
3186
195
            reg_set(&R[a], value_array(elems, c));
3187
195
            free(elems);
3188
195
        }
3189
256
        DISPATCH();
3190
256
    }
3191
3192
256
    CASE(NEWSTRUCT) {
3193
194
        uint8_t a = REG_GET_A(instr);
3194
        /* b is unused; name constant comes from follow-up LOADK instruction */
3195
194
        uint8_t c = REG_GET_C(instr);  /* field count */
3196
3197
        /* Read the follow-up LOADK instruction to get the full constant index */
3198
194
        RegInstr name_instr = READ_INSTR();
3199
194
        uint16_t name_ki = REG_GET_Bx(name_instr);
3200
194
        const char *struct_name = frame->chunk->constants[name_ki].as.str_val;
3201
3202
        /* Look up field names from struct metadata */
3203
194
        char meta_name[256];
3204
194
        snprintf(meta_name, sizeof(meta_name), "__struct_%s", struct_name);
3205
194
        LatValue meta;
3206
194
        if (!env_get(vm->env, meta_name, &meta)) {
3207
0
            RVM_ERROR("unknown struct '%s'", struct_name);
3208
0
        }
3209
3210
194
        if (meta.type != VAL_ARRAY || (int)meta.as.array.len != c) {
3211
0
            RVM_ERROR("struct '%s' field count mismatch", struct_name);
3212
0
        }
3213
3214
        /* Build field names array */
3215
194
        char **field_names = malloc(c * sizeof(char *));
3216
194
        LatValue *field_values = malloc(c * sizeof(LatValue));
3217
578
        for (int i = 0; i < c; i++) {
3218
384
            field_names[i] = strdup(meta.as.array.elems[i].as.str_val);
3219
            /* Field values are in registers a+1..a+c */
3220
            /* Actually, the compiler puts them starting at base (which is free'd by the compiler,
3221
             * but the values are still in registers we can read) */
3222
            /* The register where fields were compiled: they're before `a` in the temp window.
3223
             * But we don't know the exact base. Let's use the meta field names to find them
3224
             * from the struct literal compilation. Actually, let me revisit the compiler... */
3225
            /* The compiler compiles fields into base..base+c-1, then emits NEWSTRUCT into dst(a).
3226
             * So fields are in R[a+1..a+c] if base = a+1... But the compiler uses alloc_reg
3227
             * for base, so base = next_reg. The fields are contiguous starting at base.
3228
             * After NEWSTRUCT, the compiler calls free_regs_to(base). But we haven't freed yet.
3229
             * Actually, base was allocated, then fields at base..base+c-1.
3230
             * After NEWSTRUCT is emitted, the LOADK follow-up uses base (which was the first alloc'd reg).
3231
             * So the fields are at the register given by REG_GET_A(name_instr) and following. */
3232
            /* Wait, looking at the compiler: base = alloc_reg(), fields compiled into base..base+c-1.
3233
             * Then NEWSTRUCT A=dst, then LOADK A=base. So base is in REG_GET_A(name_instr). */
3234
384
        }
3235
194
        uint8_t field_base = REG_GET_A(name_instr);
3236
578
        for (int i = 0; i < c; i++) {
3237
384
            field_values[i] = rvm_clone(&R[field_base + i]);
3238
384
        }
3239
3240
194
        LatValue strct = value_struct(struct_name, field_names, field_values, c);
3241
578
        for (int i = 0; i < c; i++)
3242
384
            free(field_names[i]);
3243
194
        free(field_names);
3244
194
        free(field_values);
3245
3246
        /* Alloy enforcement: apply per-field phase from struct declaration */
3247
194
        {
3248
194
            char phase_key[256];
3249
194
            snprintf(phase_key, sizeof(phase_key), "__struct_phases_%s", struct_name);
3250
194
            LatValue *phase_ref = env_get_ref(vm->env, phase_key);
3251
194
            if (phase_ref &&
3252
194
                phase_ref->type == VAL_ARRAY && (int)phase_ref->as.array.len == c) {
3253
3
                strct.as.strct.field_phases = calloc(c, sizeof(PhaseTag));
3254
9
                for (int i = 0; i < c; i++) {
3255
6
                    int64_t p = phase_ref->as.array.elems[i].as.int_val;
3256
6
                    if (p == 1) { /* PHASE_CRYSTAL */
3257
3
                        strct.as.strct.field_values[i] = value_freeze(strct.as.strct.field_values[i]);
3258
3
                        strct.as.strct.field_phases[i] = VTAG_CRYSTAL;
3259
3
                    } else if (p == 0) { /* PHASE_FLUID */
3260
3
                        strct.as.strct.field_phases[i] = VTAG_FLUID;
3261
3
                    } else {
3262
0
                        strct.as.strct.field_phases[i] = strct.phase;
3263
0
                    }
3264
6
                }
3265
3
            }
3266
194
        }
3267
3268
194
        reg_set(&R[a], strct);
3269
194
        DISPATCH();
3270
194
    }
3271
3272
13
    CASE(BUILDRANGE) {
3273
13
        uint8_t a = REG_GET_A(instr);
3274
13
        uint8_t b = REG_GET_B(instr);
3275
13
        uint8_t c = REG_GET_C(instr);
3276
13
        if (R[b].type != VAL_INT || R[c].type != VAL_INT)
3277
0
            RVM_ERROR("range bounds must be integers");
3278
13
        reg_set(&R[a], value_range(R[b].as.int_val, R[c].as.int_val));
3279
13
        DISPATCH();
3280
13
    }
3281
3282
356
    CASE(LEN) {
3283
356
        uint8_t a = REG_GET_A(instr);
3284
356
        uint8_t b = REG_GET_B(instr);
3285
356
        if (R[b].type == VAL_ARRAY) {
3286
119
            reg_set(&R[a], value_int((int64_t)R[b].as.array.len));
3287
237
        } else if (R[b].type == VAL_STR) {
3288
0
            reg_set(&R[a], value_int((int64_t)strlen(R[b].as.str_val)));
3289
237
        } else if (R[b].type == VAL_RANGE) {
3290
237
            int64_t len = R[b].as.range.end - R[b].as.range.start;
3291
237
            if (len < 0) len = 0;
3292
237
            reg_set(&R[a], value_int(len));
3293
237
        } else if (R[b].type == VAL_MAP) {
3294
0
            reg_set(&R[a], value_int((int64_t)lat_map_len(R[b].as.map.map)));
3295
0
        } else if (R[b].type == VAL_SET) {
3296
0
            reg_set(&R[a], value_int((int64_t)lat_map_len(R[b].as.set.map)));
3297
0
        } else if (R[b].type == VAL_TUPLE) {
3298
0
            reg_set(&R[a], value_int((int64_t)R[b].as.tuple.len));
3299
0
        } else if (R[b].type == VAL_BUFFER) {
3300
0
            reg_set(&R[a], value_int((int64_t)R[b].as.buffer.len));
3301
0
        } else {
3302
0
            RVM_ERROR("cannot get length of %s", value_type_name(&R[b]));
3303
0
        }
3304
356
        DISPATCH();
3305
356
    }
3306
3307
999
    CASE(PRINT) {
3308
999
        uint8_t a = REG_GET_A(instr);
3309
999
        uint8_t b = REG_GET_B(instr);  /* count */
3310
2.00k
        for (int i = 0; i < b; i++) {
3311
1.00k
            if (i > 0) printf(" ");
3312
1.00k
            value_print(&R[a + i], stdout);
3313
1.00k
        }
3314
999
        printf("\n");
3315
999
        DISPATCH();
3316
999
    }
3317
3318
2.60k
    CASE(INVOKE) {
3319
        /* Two-instruction sequence:
3320
         *   INVOKE A=dst, B=method_ki, C=argc
3321
         *   data:  A=obj_reg, B=args_base, C=0
3322
         * Object is mutated in-place at R[obj_reg] (for push/pop).
3323
         * Return value goes into R[dst]. */
3324
2.60k
        uint8_t dst = REG_GET_A(instr);
3325
2.60k
        uint8_t method_ki = REG_GET_B(instr);
3326
2.60k
        uint8_t argc = REG_GET_C(instr);
3327
3328
        /* Read data word */
3329
2.60k
        RegInstr data = *frame->ip++;
3330
2.60k
        uint8_t obj_reg = REG_GET_A(data);
3331
2.60k
        uint8_t args_base = REG_GET_B(data);
3332
3333
2.60k
        const char *method_name = frame->chunk->constants[method_ki].as.str_val;
3334
3335
        /* Try builtin */
3336
2.60k
        LatValue invoke_result;
3337
2.60k
        LatValue *invoke_args = (argc > 0) ? &R[args_base] : NULL;
3338
2.60k
        if (rvm_invoke_builtin(vm, &R[obj_reg], method_name, invoke_args, argc, &invoke_result)) {
3339
2.59k
            if (vm->error)
3340
9
                return REGVM_RUNTIME_ERROR;
3341
            /* Object was mutated in-place at R[obj_reg]; result goes to R[dst] */
3342
2.58k
            reg_set(&R[dst], invoke_result);
3343
2.58k
            DISPATCH();
3344
2.58k
        }
3345
3346
        /* Check for callable closure field in map */
3347
2.59k
        if (R[obj_reg].type == VAL_MAP) {
3348
0
            LatValue *field = lat_map_get(R[obj_reg].as.map.map, method_name);
3349
0
            if (field && field->type == VAL_CLOSURE) {
3350
                /* Native C function in map */
3351
0
                if (field->as.closure.default_values == VM_NATIVE_MARKER) {
3352
0
                    VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
3353
0
                    LatValue *call_args = (argc > 0) ? &R[args_base] : NULL;
3354
0
                    LatValue ret = native(call_args, argc);
3355
0
                    if (vm->rt->error) {
3356
0
                        vm->error = vm->rt->error;
3357
0
                        vm->rt->error = NULL;
3358
0
                        value_free(&ret);
3359
0
                        return REGVM_RUNTIME_ERROR;
3360
0
                    }
3361
0
                    reg_set(&R[dst], ret);
3362
0
                    DISPATCH();
3363
0
                }
3364
                /* Extension native function in map */
3365
0
                if (field->as.closure.default_values == VM_EXT_MARKER) {
3366
0
                    LatValue *call_args = (argc > 0) ? &R[args_base] : NULL;
3367
0
                    LatValue ret = ext_call_native(field->as.closure.native_fn,
3368
0
                                                   call_args, (size_t)argc);
3369
0
                    if (ret.type == VAL_STR && ret.as.str_val &&
3370
0
                        strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) {
3371
0
                        vm->error = strdup(ret.as.str_val + 11);
3372
0
                        value_free(&ret);
3373
0
                        return REGVM_RUNTIME_ERROR;
3374
0
                    }
3375
0
                    reg_set(&R[dst], ret);
3376
0
                    DISPATCH();
3377
0
                }
3378
                /* RegChunk closure in map */
3379
0
                RegChunk *fn_chunk = (RegChunk *)field->as.closure.native_fn;
3380
0
                if (fn_chunk && fn_chunk->magic == REGCHUNK_MAGIC) {
3381
0
                    if (vm->frame_count >= REGVM_FRAMES_MAX)
3382
0
                        RVM_ERROR("call stack overflow");
3383
3384
0
                    size_t new_base = vm->reg_stack_top;
3385
0
                    LatValue *new_regs = &vm->reg_stack[new_base];
3386
0
                    vm->reg_stack_top += REGVM_REG_MAX;
3387
0
                    for (int i = 0; i < REGVM_REG_MAX; i++)
3388
0
                        new_regs[i] = value_nil();
3389
3390
                    /* Slot 0 = reserved, slots 1+ = args (no self for map closures) */
3391
0
                    new_regs[0] = value_unit();
3392
0
                    for (int i = 0; i < argc; i++) {
3393
0
                        value_free(&new_regs[1 + i]);
3394
0
                        new_regs[1 + i] = rvm_clone(&R[args_base + i]);
3395
0
                    }
3396
3397
0
                    ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
3398
0
                    size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
3399
3400
0
                    RegCallFrame *new_frame = &vm->frames[vm->frame_count++];
3401
0
                    new_frame->chunk = fn_chunk;
3402
0
                    new_frame->ip = fn_chunk->code;
3403
0
                    new_frame->regs = new_regs;
3404
0
                    new_frame->reg_count = REGVM_REG_MAX;
3405
0
                    new_frame->upvalues = upvals;
3406
0
                    new_frame->upvalue_count = uv_count;
3407
0
                    new_frame->caller_result_reg = dst;
3408
0
                    frame = new_frame;
3409
0
                    R = new_regs;
3410
0
                    DISPATCH();
3411
0
                }
3412
                /* Stack-VM closure in map — use regvm bridge */
3413
0
                if (field->as.closure.native_fn) {
3414
0
                    LatValue *call_args = (argc > 0) ? &R[args_base] : NULL;
3415
0
                    LatValue ret = regvm_call_closure(vm, field, call_args, argc);
3416
0
                    if (vm->error) return REGVM_RUNTIME_ERROR;
3417
0
                    reg_set(&R[dst], ret);
3418
0
                    DISPATCH();
3419
0
                }
3420
0
            }
3421
0
        }
3422
3423
        /* Check for callable closure field in struct */
3424
2.59k
        if (R[obj_reg].type == VAL_STRUCT) {
3425
24
            for (size_t fi = 0; fi < R[obj_reg].as.strct.field_count; fi++) {
3426
14
                if (strcmp(R[obj_reg].as.strct.field_names[fi], method_name) != 0) continue;
3427
4
                LatValue *field = &R[obj_reg].as.strct.field_values[fi];
3428
4
                if (field->type == VAL_CLOSURE && field->as.closure.native_fn) {
3429
4
                    RegChunk *fn_chunk = (RegChunk *)field->as.closure.native_fn;
3430
4
                    if (vm->frame_count >= REGVM_FRAMES_MAX)
3431
0
                        RVM_ERROR("call stack overflow");
3432
3433
4
                    size_t new_base = vm->reg_stack_top;
3434
4
                    LatValue *new_regs = &vm->reg_stack[new_base];
3435
4
                    vm->reg_stack_top += REGVM_REG_MAX;
3436
1.02k
                    for (int i = 0; i < REGVM_REG_MAX; i++)
3437
1.02k
                        new_regs[i] = value_nil();
3438
3439
                    /* Slot 0 = reserved, slot 1 = self, slots 2+ = args */
3440
4
                    new_regs[0] = value_unit();
3441
4
                    new_regs[1] = rvm_clone(&R[obj_reg]);  /* self = first param */
3442
6
                    for (int i = 0; i < argc; i++) {
3443
2
                        value_free(&new_regs[2 + i]);
3444
2
                        new_regs[2 + i] = rvm_clone(&R[args_base + i]);
3445
2
                    }
3446
3447
4
                    ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
3448
4
                    size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
3449
3450
4
                    RegCallFrame *new_frame = &vm->frames[vm->frame_count++];
3451
4
                    new_frame->chunk = fn_chunk;
3452
4
                    new_frame->ip = fn_chunk->code;
3453
4
                    new_frame->regs = new_regs;
3454
4
                    new_frame->reg_count = REGVM_REG_MAX;
3455
4
                    new_frame->upvalues = upvals;
3456
4
                    new_frame->upvalue_count = uv_count;
3457
4
                    new_frame->caller_result_reg = dst;
3458
4
                    frame = new_frame;
3459
4
                    R = new_regs;
3460
4
                    DISPATCH();
3461
4
                }
3462
4
            }
3463
10
        }
3464
3465
        /* Check for impl method (TypeName::method) */
3466
2.59k
        if (R[obj_reg].type == VAL_STRUCT) {
3467
6
            char key[256];
3468
6
            snprintf(key, sizeof(key), "%s::%s", R[obj_reg].as.strct.name, method_name);
3469
6
            LatValue impl_fn;
3470
6
            if (env_get(vm->env, key, &impl_fn) && impl_fn.type == VAL_CLOSURE) {
3471
6
                RegChunk *fn_chunk = (RegChunk *)impl_fn.as.closure.native_fn;
3472
6
                if (!fn_chunk) goto invoke_fail;
3473
3474
6
                if (vm->frame_count >= REGVM_FRAMES_MAX)
3475
0
                    RVM_ERROR("call stack overflow");
3476
3477
6
                size_t new_base = vm->reg_stack_top;
3478
6
                LatValue *new_regs = &vm->reg_stack[new_base];
3479
6
                vm->reg_stack_top += REGVM_REG_MAX;
3480
1.54k
                for (int i = 0; i < REGVM_REG_MAX; i++)
3481
1.53k
                    new_regs[i] = value_nil();
3482
3483
                /* ITEM_IMPL compiles self at slot 0, other params at slot 1+ */
3484
6
                new_regs[0] = rvm_clone(&R[obj_reg]);  /* self */
3485
7
                for (int i = 0; i < argc; i++) {
3486
1
                    value_free(&new_regs[1 + i]);
3487
1
                    new_regs[1 + i] = rvm_clone(&R[args_base + i]);
3488
1
                }
3489
3490
6
                ObjUpvalue **upvals = (ObjUpvalue **)impl_fn.as.closure.captured_env;
3491
6
                size_t uv_count = impl_fn.region_id != (size_t)-1 ? impl_fn.region_id : 0;
3492
3493
6
                RegCallFrame *new_frame = &vm->frames[vm->frame_count++];
3494
6
                new_frame->chunk = fn_chunk;
3495
6
                new_frame->ip = fn_chunk->code;
3496
6
                new_frame->regs = new_regs;
3497
6
                new_frame->reg_count = REGVM_REG_MAX;
3498
6
                new_frame->upvalues = upvals;
3499
6
                new_frame->upvalue_count = uv_count;
3500
6
                new_frame->caller_result_reg = dst;
3501
6
                frame = new_frame;
3502
6
                R = new_regs;
3503
6
                DISPATCH();
3504
6
            }
3505
6
        }
3506
3507
2.59k
        invoke_fail:
3508
1
        RVM_ERROR("no method '%s' on %s", method_name, value_type_name(&R[obj_reg]));
3509
1
    }
3510
3511
44
    CASE(FREEZE) {
3512
44
        uint8_t a = REG_GET_A(instr);
3513
44
        uint8_t b = REG_GET_B(instr);
3514
44
        if (R[b].type == VAL_CHANNEL)
3515
0
            RVM_ERROR("cannot freeze a channel");
3516
44
        LatValue frozen = value_freeze(rvm_clone(&R[b]));
3517
44
        reg_set(&R[a], frozen);
3518
44
        DISPATCH();
3519
44
    }
3520
3521
6
    CASE(THAW) {
3522
6
        uint8_t a = REG_GET_A(instr);
3523
6
        uint8_t b = REG_GET_B(instr);
3524
6
        LatValue thawed = value_thaw(&R[b]);
3525
6
        reg_set(&R[a], thawed);
3526
6
        DISPATCH();
3527
6
    }
3528
3529
6
    CASE(CLONE) {
3530
3
        uint8_t a = REG_GET_A(instr);
3531
3
        uint8_t b = REG_GET_B(instr);
3532
3
        reg_set(&R[a], value_deep_clone(&R[b]));
3533
3
        DISPATCH();
3534
3
    }
3535
3536
47
    CASE(ITERINIT) {
3537
        /* A = destination (collection stays in A), B = source */
3538
47
        uint8_t a = REG_GET_A(instr);
3539
47
        uint8_t b = REG_GET_B(instr);
3540
47
        if (R[b].type == VAL_MAP) {
3541
            /* Convert map to array of [key, value] pairs for uniform iteration */
3542
1
            LatMap *m = R[b].as.map.map;
3543
1
            size_t cap = m->cap;
3544
1
            size_t count = lat_map_len(m);
3545
1
            LatValue *entries = malloc((count > 0 ? count : 1) * sizeof(LatValue));
3546
1
            size_t idx = 0;
3547
17
            for (size_t i = 0; i < cap; i++) {
3548
16
                if (m->entries[i].state != MAP_OCCUPIED) continue;
3549
1
                LatValue pair[2];
3550
1
                pair[0] = value_string(m->entries[i].key);
3551
1
                pair[1] = rvm_clone((LatValue *)m->entries[i].value);
3552
1
                entries[idx++] = value_array(pair, 2);
3553
1
            }
3554
1
            reg_set(&R[a], value_array(entries, idx));
3555
1
            free(entries);
3556
46
        } else if (R[b].type == VAL_SET) {
3557
            /* Convert set to array of values for uniform iteration */
3558
1
            LatMap *m = R[b].as.set.map;
3559
1
            size_t cap = m->cap;
3560
1
            size_t count = lat_map_len(m);
3561
1
            LatValue *elems = malloc((count > 0 ? count : 1) * sizeof(LatValue));
3562
1
            size_t idx = 0;
3563
17
            for (size_t i = 0; i < cap; i++) {
3564
16
                if (m->entries[i].state != MAP_OCCUPIED) continue;
3565
1
                elems[idx++] = rvm_clone((LatValue *)m->entries[i].value);
3566
1
            }
3567
1
            reg_set(&R[a], value_array(elems, idx));
3568
1
            free(elems);
3569
45
        } else if (R[b].type == VAL_STR) {
3570
            /* Convert string to array of characters for uniform iteration */
3571
0
            size_t len = strlen(R[b].as.str_val);
3572
0
            LatValue *chars = malloc((len > 0 ? len : 1) * sizeof(LatValue));
3573
0
            for (size_t i = 0; i < len; i++) {
3574
0
                char ch[2] = { R[b].as.str_val[i], '\0' };
3575
0
                chars[i] = value_string(ch);
3576
0
            }
3577
0
            reg_set(&R[a], value_array(chars, len));
3578
0
            free(chars);
3579
45
        } else {
3580
45
            if (a != b)
3581
0
                reg_set(&R[a], rvm_clone(&R[b]));
3582
45
        }
3583
        /* The collection stays in R[a]. Index starts at 0 (set by compiler). */
3584
47
        DISPATCH();
3585
47
    }
3586
3587
306
    CASE(ITERNEXT) {
3588
        /* A = result (loop var), B = collection, C = index register */
3589
306
        uint8_t a = REG_GET_A(instr);
3590
306
        uint8_t b = REG_GET_B(instr);
3591
306
        uint8_t c = REG_GET_C(instr);
3592
3593
306
        if (R[b].type == VAL_RANGE) {
3594
229
            int64_t idx = R[c].as.int_val;
3595
229
            int64_t start = R[b].as.range.start;
3596
229
            int64_t end = R[b].as.range.end;
3597
229
            int64_t current_val = start + idx;
3598
229
            if (current_val >= end) {
3599
                /* Done — set result to nil (falsy, triggers JMPFALSE) */
3600
0
                reg_set(&R[a], value_nil());
3601
229
            } else {
3602
229
                reg_set(&R[a], value_int(current_val));
3603
229
            }
3604
229
        } else if (R[b].type == VAL_ARRAY) {
3605
77
            int64_t idx = R[c].as.int_val;
3606
77
            if ((size_t)idx >= R[b].as.array.len) {
3607
0
                reg_set(&R[a], value_nil());
3608
77
            } else {
3609
77
                reg_set(&R[a], rvm_clone(&R[b].as.array.elems[idx]));
3610
77
            }
3611
77
        } else {
3612
0
            RVM_ERROR("cannot iterate over %s", value_type_name(&R[b]));
3613
0
        }
3614
306
        DISPATCH();
3615
306
    }
3616
3617
343
    CASE(MARKFLUID) {
3618
343
        uint8_t a = REG_GET_A(instr);
3619
343
        R[a].phase = VTAG_FLUID;
3620
343
        DISPATCH();
3621
343
    }
3622
3623
    /* ── Bitwise operations ── */
3624
3625
343
    CASE(BIT_AND) {
3626
5
        uint8_t a = REG_GET_A(instr);
3627
5
        uint8_t b = REG_GET_B(instr);
3628
5
        uint8_t c = REG_GET_C(instr);
3629
5
        if (R[b].type != VAL_INT || R[c].type != VAL_INT)
3630
0
            RVM_ERROR("bitwise AND requires integers");
3631
5
        reg_set(&R[a], value_int(R[b].as.int_val & R[c].as.int_val));
3632
5
        DISPATCH();
3633
5
    }
3634
3635
4
    CASE(BIT_OR) {
3636
4
        uint8_t a = REG_GET_A(instr);
3637
4
        uint8_t b = REG_GET_B(instr);
3638
4
        uint8_t c = REG_GET_C(instr);
3639
4
        if (R[b].type != VAL_INT || R[c].type != VAL_INT)
3640
0
            RVM_ERROR("bitwise OR requires integers");
3641
4
        reg_set(&R[a], value_int(R[b].as.int_val | R[c].as.int_val));
3642
4
        DISPATCH();
3643
4
    }
3644
3645
4
    CASE(BIT_XOR) {
3646
4
        uint8_t a = REG_GET_A(instr);
3647
4
        uint8_t b = REG_GET_B(instr);
3648
4
        uint8_t c = REG_GET_C(instr);
3649
4
        if (R[b].type != VAL_INT || R[c].type != VAL_INT)
3650
0
            RVM_ERROR("bitwise XOR requires integers");
3651
4
        reg_set(&R[a], value_int(R[b].as.int_val ^ R[c].as.int_val));
3652
4
        DISPATCH();
3653
4
    }
3654
3655
3
    CASE(BIT_NOT) {
3656
3
        uint8_t a = REG_GET_A(instr);
3657
3
        uint8_t b = REG_GET_B(instr);
3658
3
        if (R[b].type != VAL_INT)
3659
0
            RVM_ERROR("bitwise NOT requires integer");
3660
3
        reg_set(&R[a], value_int(~R[b].as.int_val));
3661
3
        DISPATCH();
3662
3
    }
3663
3664
3
    CASE(LSHIFT) {
3665
3
        uint8_t a = REG_GET_A(instr);
3666
3
        uint8_t b = REG_GET_B(instr);
3667
3
        uint8_t c = REG_GET_C(instr);
3668
3
        if (R[b].type != VAL_INT || R[c].type != VAL_INT)
3669
0
            RVM_ERROR("left shift requires integers");
3670
3
        if (R[c].as.int_val < 0 || R[c].as.int_val > 63)
3671
1
            RVM_ERROR("shift amount out of range (0..63)");
3672
3
        reg_set(&R[a], value_int(R[b].as.int_val << R[c].as.int_val));
3673
3
        DISPATCH();
3674
3
    }
3675
3676
2
    CASE(RSHIFT) {
3677
2
        uint8_t a = REG_GET_A(instr);
3678
2
        uint8_t b = REG_GET_B(instr);
3679
2
        uint8_t c = REG_GET_C(instr);
3680
2
        if (R[b].type != VAL_INT || R[c].type != VAL_INT)
3681
0
            RVM_ERROR("right shift requires integers");
3682
2
        reg_set(&R[a], value_int(R[b].as.int_val >> R[c].as.int_val));
3683
2
        DISPATCH();
3684
2
    }
3685
3686
    /* ── Tuple ── */
3687
3688
11
    CASE(NEWTUPLE) {
3689
11
        uint8_t a = REG_GET_A(instr);
3690
11
        uint8_t b = REG_GET_B(instr);
3691
11
        uint8_t c = REG_GET_C(instr);  /* count */
3692
11
        LatValue *elems = c > 0 ? malloc(c * sizeof(LatValue)) : NULL;
3693
42
        for (int i = 0; i < c; i++)
3694
31
            elems[i] = rvm_clone(&R[b + i]);
3695
11
        LatValue tup;
3696
11
        tup.type = VAL_TUPLE;
3697
11
        tup.phase = VTAG_CRYSTAL;
3698
11
        tup.region_id = REGION_NONE;
3699
11
        tup.as.tuple.elems = elems;
3700
11
        tup.as.tuple.len = c;
3701
11
        reg_set(&R[a], tup);
3702
11
        DISPATCH();
3703
11
    }
3704
3705
    /* ── Spread/Flatten ── */
3706
3707
15
    CASE(ARRAY_FLATTEN) {
3708
15
        uint8_t a = REG_GET_A(instr);
3709
15
        uint8_t b = REG_GET_B(instr);
3710
15
        if (R[b].type != VAL_ARRAY) {
3711
0
            reg_set(&R[a], rvm_clone(&R[b]));
3712
0
            DISPATCH();
3713
0
        }
3714
        /* One-level flatten */
3715
15
        size_t cap = R[b].as.array.len * 2;
3716
15
        if (cap == 0) cap = 1;
3717
15
        LatValue *elems = malloc(cap * sizeof(LatValue));
3718
15
        size_t out = 0;
3719
46
        for (size_t i = 0; i < R[b].as.array.len; i++) {
3720
31
            if (R[b].as.array.elems[i].type == VAL_ARRAY) {
3721
9
                LatValue *inner = &R[b].as.array.elems[i];
3722
27
                for (size_t j = 0; j < inner->as.array.len; j++) {
3723
18
                    if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
3724
18
                    elems[out++] = rvm_clone(&inner->as.array.elems[j]);
3725
18
                }
3726
22
            } else {
3727
22
                if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
3728
22
                elems[out++] = rvm_clone(&R[b].as.array.elems[i]);
3729
22
            }
3730
31
        }
3731
15
        reg_set(&R[a], value_array(elems, out));
3732
15
        free(elems);
3733
15
        DISPATCH();
3734
15
    }
3735
3736
    /* ── Enum with payload ── */
3737
3738
15
    CASE(NEWENUM) {
3739
4
        uint8_t dst = REG_GET_A(instr);
3740
4
        uint8_t name_ki_lo = REG_GET_B(instr);
3741
4
        uint8_t argc = REG_GET_C(instr);
3742
3743
        /* Read data word: A=base, B=variant_ki, C=name_ki_hi */
3744
4
        RegInstr data = READ_INSTR();
3745
4
        uint8_t base = REG_GET_A(data);
3746
4
        uint8_t var_ki = REG_GET_B(data);
3747
4
        uint8_t name_ki_hi = REG_GET_C(data);
3748
3749
4
        uint16_t name_ki = (uint16_t)name_ki_lo | ((uint16_t)name_ki_hi << 8);
3750
4
        const char *enum_name = frame->chunk->constants[name_ki].as.str_val;
3751
4
        const char *variant_name = frame->chunk->constants[var_ki].as.str_val;
3752
3753
4
        if (argc == 0) {
3754
0
            reg_set(&R[dst], value_enum(enum_name, variant_name, NULL, 0));
3755
4
        } else {
3756
4
            LatValue *payload = malloc(argc * sizeof(LatValue));
3757
10
            for (int i = 0; i < argc; i++)
3758
6
                payload[i] = rvm_clone(&R[base + i]);
3759
4
            reg_set(&R[dst], value_enum(enum_name, variant_name, payload, argc));
3760
4
            free(payload);
3761
4
        }
3762
4
        DISPATCH();
3763
4
    }
3764
3765
    /* ── Optional chaining ── */
3766
3767
50
    CASE(JMPNOTNIL) {
3768
50
        uint8_t a = REG_GET_A(instr);
3769
50
        int16_t offset = REG_GET_sBx(instr);
3770
50
        if (R[a].type != VAL_NIL)
3771
24
            frame->ip += offset;
3772
50
        DISPATCH();
3773
50
    }
3774
3775
    /* ── Exception handling ── */
3776
3777
50
    CASE(PUSH_HANDLER) {
3778
38
        uint8_t a = REG_GET_A(instr);
3779
38
        int16_t offset = REG_GET_sBx(instr);
3780
38
        if (vm->handler_count >= REGVM_HANDLER_MAX)
3781
0
            RVM_ERROR("exception handler stack overflow");
3782
38
        RegHandler *h = &vm->handlers[vm->handler_count++];
3783
38
        h->ip = frame->ip + offset;
3784
38
        h->chunk = frame->chunk;
3785
38
        h->frame_index = (size_t)(vm->frame_count - 1);
3786
38
        h->reg_stack_top = vm->reg_stack_top;
3787
38
        h->error_reg = a;
3788
38
        DISPATCH();
3789
38
    }
3790
3791
14
    CASE(POP_HANDLER) {
3792
14
        if (vm->handler_count > 0)
3793
14
            vm->handler_count--;
3794
14
        DISPATCH();
3795
14
    }
3796
3797
14
    CASE(THROW) {
3798
10
        uint8_t a = REG_GET_A(instr);
3799
10
        LatValue thrown = rvm_clone(&R[a]);
3800
3801
10
        if (vm->handler_count == 0) {
3802
            /* Match stack VM behavior: string exceptions pass directly,
3803
             * non-string exceptions get "unhandled exception:" wrapper.
3804
             * No [line N] prefix for thrown exceptions. */
3805
10
            if (thrown.type == VAL_STR) {
3806
10
                vm->error = strdup(thrown.as.str_val);
3807
10
            } else {
3808
0
                char *repr = value_display(&thrown);
3809
0
                (void)asprintf(&vm->error, "unhandled exception: %s", repr);
3810
0
                free(repr);
3811
0
            }
3812
10
            value_free(&thrown);
3813
10
            return REGVM_RUNTIME_ERROR;
3814
10
        }
3815
3816
        /* Unwind to handler */
3817
0
        RegHandler h = vm->handlers[--vm->handler_count];
3818
3819
        /* Clean up frames between current and handler frame */
3820
0
        while (vm->frame_count - 1 > (int)h.frame_index) {
3821
0
            RegCallFrame *f = &vm->frames[vm->frame_count - 1];
3822
0
            for (int i = 0; i < REGVM_REG_MAX; i++)
3823
0
                value_free_inline(&f->regs[i]);
3824
0
            vm->frame_count--;
3825
0
            vm->reg_stack_top -= REGVM_REG_MAX;
3826
0
        }
3827
3828
0
        frame = &vm->frames[vm->frame_count - 1];
3829
0
        R = frame->regs;
3830
0
        frame->ip = h.ip;
3831
3832
0
        reg_set(&R[h.error_reg], thrown);
3833
0
        DISPATCH();
3834
0
    }
3835
3836
6
    CASE(TRY_UNWRAP) {
3837
6
        uint8_t a = REG_GET_A(instr);
3838
        /* R[a] should be a Result map: {tag: "ok", value: ...} or {tag: "err", value: ...} */
3839
6
        if (R[a].type == VAL_MAP) {
3840
5
            LatValue *tag = lat_map_get(R[a].as.map.map, "tag");
3841
5
            if (tag && tag->type == VAL_STR) {
3842
5
                if (strcmp(tag->as.str_val, "ok") == 0) {
3843
3
                    LatValue *val = lat_map_get(R[a].as.map.map, "value");
3844
3
                    LatValue unwrapped = val ? rvm_clone(val) : value_nil();
3845
3
                    reg_set(&R[a], unwrapped);
3846
3
                    DISPATCH();
3847
3
                } else if (strcmp(tag->as.str_val, "err") == 0) {
3848
                    /* Propagate error: return the error map */
3849
2
                    LatValue err_val = rvm_clone(&R[a]);
3850
2
                    uint8_t dest_reg = frame->caller_result_reg;
3851
3852
514
                    for (int i = 0; i < REGVM_REG_MAX; i++)
3853
512
                        value_free_inline(&frame->regs[i]);
3854
2
                    vm->frame_count--;
3855
2
                    vm->reg_stack_top -= REGVM_REG_MAX;
3856
3857
2
                    if (vm->frame_count == base_frame) {
3858
0
                        *result = err_val;
3859
0
                        return REGVM_OK;
3860
0
                    }
3861
2
                    frame = &vm->frames[vm->frame_count - 1];
3862
2
                    R = frame->regs;
3863
2
                    reg_set(&R[dest_reg], err_val);
3864
2
                    DISPATCH();
3865
2
                }
3866
5
            }
3867
5
        }
3868
        /* If it's an enum Result */
3869
6
        if (R[a].type == VAL_ENUM) {
3870
0
            if (strcmp(R[a].as.enm.variant_name, "Ok") == 0) {
3871
0
                LatValue unwrapped = R[a].as.enm.payload_count > 0
3872
0
                    ? rvm_clone(&R[a].as.enm.payload[0]) : value_nil();
3873
0
                reg_set(&R[a], unwrapped);
3874
0
                DISPATCH();
3875
0
            } else if (strcmp(R[a].as.enm.variant_name, "Err") == 0) {
3876
0
                LatValue err_val = rvm_clone(&R[a]);
3877
0
                uint8_t dest_reg = frame->caller_result_reg;
3878
0
                for (int i = 0; i < REGVM_REG_MAX; i++)
3879
0
                    value_free_inline(&frame->regs[i]);
3880
0
                vm->frame_count--;
3881
0
                vm->reg_stack_top -= REGVM_REG_MAX;
3882
0
                if (vm->frame_count == base_frame) {
3883
0
                    *result = err_val;
3884
0
                    return REGVM_OK;
3885
0
                }
3886
0
                frame = &vm->frames[vm->frame_count - 1];
3887
0
                R = frame->regs;
3888
0
                reg_set(&R[dest_reg], err_val);
3889
0
                DISPATCH();
3890
0
            }
3891
0
        }
3892
        /* Not a Result — error */
3893
6
        RVM_ERROR("'?' operator requires a Result value, got %s", value_type_name(&R[a]));
3894
6
    }
3895
3896
    /* ── Defer ── */
3897
3898
8
    CASE(DEFER_PUSH) {
3899
        /* A = scope_depth, sBx = offset to jump past the defer body */
3900
8
        uint8_t scope_d = REG_GET_A(instr);
3901
8
        int16_t offset = REG_GET_sBx(instr);
3902
8
        if (vm->defer_count >= REGVM_DEFER_MAX)
3903
0
            RVM_ERROR("defer stack overflow");
3904
8
        RegDefer *d = &vm->defers[vm->defer_count++];
3905
8
        d->ip = frame->ip;       /* Points to start of defer body */
3906
8
        d->chunk = frame->chunk;
3907
8
        d->frame_index = (size_t)(vm->frame_count - 1);
3908
8
        d->regs = frame->regs;
3909
8
        d->scope_depth = (int)scope_d;
3910
        /* Skip past the defer body */
3911
8
        frame->ip += offset;
3912
8
        DISPATCH();
3913
8
    }
3914
3915
4.69k
    CASE(DEFER_RUN) {
3916
        /* Execute defers for the current frame in LIFO order.
3917
         * A=min_scope_depth: only run defers with scope_depth >= A.
3918
         * A=0 runs all defers for this frame (used at function return).
3919
         * After each defer body runs, copy modified registers back to
3920
         * the original frame so deferred mutations are visible. */
3921
4.69k
        uint8_t min_scope = REG_GET_A(instr);
3922
4.69k
        size_t frame_idx = (size_t)(vm->frame_count - 1);
3923
4.69k
        while (vm->defer_count > 0) {
3924
8
            RegDefer *d = &vm->defers[vm->defer_count - 1];
3925
8
            if (d->frame_index != frame_idx) break;
3926
8
            if (min_scope > 0 && d->scope_depth < (int)min_scope) break;
3927
8
            vm->defer_count--;
3928
3929
            /* Push a new frame for the defer body */
3930
8
            if (vm->frame_count >= REGVM_FRAMES_MAX ||
3931
8
                vm->reg_stack_top + REGVM_REG_MAX > REGVM_REG_MAX * REGVM_FRAMES_MAX) {
3932
0
                continue; /* Skip defer if stack is full */
3933
0
            }
3934
3935
8
            LatValue *new_regs = &vm->reg_stack[vm->reg_stack_top];
3936
8
            vm->reg_stack_top += REGVM_REG_MAX;
3937
3938
            /* Copy current registers so defer body can access locals */
3939
2.05k
            for (int i = 0; i < REGVM_REG_MAX; i++)
3940
2.04k
                new_regs[i] = rvm_clone(&R[i]);
3941
3942
8
            RegCallFrame *df = &vm->frames[vm->frame_count++];
3943
8
            df->chunk = d->chunk;
3944
8
            df->ip = d->ip;
3945
8
            df->regs = new_regs;
3946
8
            df->reg_count = REGVM_REG_MAX;
3947
8
            df->upvalues = frame->upvalues;
3948
8
            df->upvalue_count = frame->upvalue_count;
3949
8
            df->caller_result_reg = 0;
3950
3951
8
            LatValue defer_result;
3952
8
            int saved_frame = (int)(vm->frame_count - 1);
3953
8
            RegVMResult dr = regvm_dispatch(vm, saved_frame, &defer_result);
3954
8
            value_free(&defer_result);
3955
8
            (void)dr;
3956
3957
            /* HALT leaves the defer frame on the stack with registers intact.
3958
             * Copy modified registers back to the original frame, then pop. */
3959
8
            frame = &vm->frames[frame_idx];
3960
8
            R = frame->regs;
3961
            /* The defer frame may still be on the stack (HALT) or popped (error) */
3962
8
            if (vm->frame_count > (int)frame_idx + 1) {
3963
8
                RegCallFrame *defer_frame = &vm->frames[vm->frame_count - 1];
3964
8
                LatValue *defer_regs = defer_frame->regs;
3965
2.05k
                for (int i = 0; i < REGVM_REG_MAX; i++) {
3966
2.04k
                    value_free(&R[i]);
3967
2.04k
                    R[i] = defer_regs[i];
3968
2.04k
                    defer_regs[i] = value_nil(); /* prevent double-free on cleanup */
3969
2.04k
                }
3970
                /* Pop the defer frame */
3971
2.05k
                for (int i = 0; i < REGVM_REG_MAX; i++)
3972
2.04k
                    value_free_inline(&defer_frame->regs[i]);
3973
8
                vm->frame_count--;
3974
8
                vm->reg_stack_top -= REGVM_REG_MAX;
3975
8
            }
3976
            /* Restore frame/R to the original frame */
3977
8
            frame = &vm->frames[frame_idx];
3978
8
            R = frame->regs;
3979
8
        }
3980
4.69k
        DISPATCH();
3981
4.69k
    }
3982
3983
    /* ── Variadic ── */
3984
3985
4.69k
    CASE(COLLECT_VARARGS) {
3986
12
        uint8_t a = REG_GET_A(instr);  /* destination register */
3987
12
        uint8_t b = REG_GET_B(instr);  /* start position (declared_arity + 1) */
3988
        /* Collect excess args into an array */
3989
12
        size_t cap = 8;
3990
12
        LatValue *elems = malloc(cap * sizeof(LatValue));
3991
12
        size_t count = 0;
3992
29
        for (int i = b; i < REGVM_REG_MAX; i++) {
3993
29
            if (R[i].type == VAL_NIL || R[i].type == VAL_UNIT) break;
3994
17
            if (count >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
3995
17
            elems[count++] = rvm_clone(&R[i]);
3996
17
        }
3997
12
        reg_set(&R[a], value_array(elems, count));
3998
12
        free(elems);
3999
12
        DISPATCH();
4000
12
    }
4001
4002
    /* ── Advanced phase system ── */
4003
4004
206
    CASE(FREEZE_VAR) {
4005
        /* A=name constant index, B=loc_type (0=local, 1=upvalue, 2=global; high bit=consume seeds), C=slot */
4006
206
        uint8_t name_ki = REG_GET_A(instr);
4007
206
        uint8_t raw_loc = REG_GET_B(instr);
4008
206
        uint8_t slot = REG_GET_C(instr);
4009
206
        bool consume_seeds = (raw_loc & 0x80) != 0;
4010
206
        uint8_t loc_type = raw_loc & 0x7F;
4011
206
        const char *var_name = frame->chunk->constants[name_ki].as.str_val;
4012
206
        LatValue *target = NULL;
4013
206
        if (loc_type == 0) {
4014
205
            if (R[slot].type == VAL_CHANNEL)
4015
1
                RVM_ERROR("cannot freeze a channel");
4016
204
            target = &R[slot];
4017
204
        } else if (loc_type == 1 && frame->upvalues && slot < frame->upvalue_count) {
4018
0
            target = frame->upvalues[slot]->location;
4019
1
        } else if (loc_type == 2) {
4020
1
            LatValue gval;
4021
1
            if (env_get(vm->env, var_name, &gval)) {
4022
                /* Validate seed contracts */
4023
1
                char *seed_err = rt_validate_seeds(vm->rt, var_name, &gval, consume_seeds);
4024
1
                if (seed_err) {
4025
0
                    value_free(&gval);
4026
0
                    RVM_ERROR("%s", seed_err);
4027
0
                }
4028
1
                LatValue frozen = value_freeze(rvm_clone(&gval));
4029
1
                value_free(&gval);
4030
1
                env_set(vm->env, var_name, frozen);
4031
1
                rt_freeze_cascade(vm->rt, var_name);
4032
1
                if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR;
4033
1
                rt_fire_reactions(vm->rt, var_name, "crystal");
4034
1
                if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR;
4035
                /* Record history for tracked globals */
4036
1
                {
4037
1
                    if (vm->rt->tracking_active)
4038
0
                        rt_record_history(vm->rt, var_name, &frozen);
4039
1
                }
4040
1
            }
4041
1
            DISPATCH();
4042
1
        }
4043
205
        if (target) {
4044
            /* Validate seed contracts */
4045
204
            char *seed_err = rt_validate_seeds(vm->rt, var_name, target, consume_seeds);
4046
204
            if (seed_err) {
4047
2
                RVM_ERROR("%s", seed_err);
4048
2
            }
4049
202
            LatValue frozen = value_freeze(rvm_clone(target));
4050
202
            value_free(target);
4051
202
            *target = frozen;
4052
            /* Sync to env for cascade/reactions (locals aren't in env) */
4053
202
            if (loc_type != 2) { /* not already global */
4054
202
                if (!env_set(vm->env, var_name, value_deep_clone(&frozen)))
4055
39
                    env_define(vm->env, var_name, value_deep_clone(&frozen));
4056
202
            }
4057
202
            rt_freeze_cascade(vm->rt, var_name);
4058
202
            if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR;
4059
201
            rt_fire_reactions(vm->rt, var_name, "crystal");
4060
201
            if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; return REGVM_RUNTIME_ERROR; } if (vm->error) return REGVM_RUNTIME_ERROR;
4061
            /* Record history for tracked variables after phase change */
4062
200
            {
4063
200
                if (vm->rt->tracking_active)
4064
2
                    rt_record_history(vm->rt, var_name, target);
4065
200
            }
4066
200
        }
4067
201
        DISPATCH();
4068
201
    }
4069
4070
165
    CASE(THAW_VAR) {
4071
165
        uint8_t name_ki = REG_GET_A(instr);
4072
165
        uint8_t loc_type = REG_GET_B(instr);
4073
165
        uint8_t slot = REG_GET_C(instr);
4074
165
        const char *var_name = frame->chunk->constants[name_ki].as.str_val;
4075
4076
165
        LatValue *target = NULL;
4077
165
        if (loc_type == 0) {
4078
164
            target = &R[slot];
4079
164
        } else if (loc_type == 1 && frame->upvalues && slot < frame->upvalue_count) {
4080
0
            target = frame->upvalues[slot]->location;
4081
1
        } else if (loc_type == 2) {
4082
1
            LatValue gval;
4083
1
            if (env_get(vm->env, var_name, &gval)) {
4084
1
                LatValue thawed = value_thaw(&gval);
4085
1
                value_free(&gval);
4086
1
                env_set(vm->env, var_name, thawed);
4087
1
                rt_fire_reactions(vm->rt, var_name, "fluid");
4088
                /* Record history for tracked globals */
4089
1
                {
4090
1
                    if (vm->rt->tracking_active)
4091
0
                        rt_record_history(vm->rt, var_name, &thawed);
4092
1
                }
4093
1
            }
4094
1
            DISPATCH();
4095
1
        }
4096
165
        if (target) {
4097
164
            LatValue thawed = value_thaw(target);
4098
164
            value_free(target);
4099
164
            *target = thawed;
4100
            /* Sync to env for cascade/reactions */
4101
164
            if (loc_type != 2) {
4102
164
                if (!env_set(vm->env, var_name, value_deep_clone(&thawed)))
4103
7
                    env_define(vm->env, var_name, value_deep_clone(&thawed));
4104
164
            }
4105
164
            rt_fire_reactions(vm->rt, var_name, "fluid");
4106
            /* Record history for tracked variables after phase change */
4107
164
            {
4108
164
                if (vm->rt->tracking_active)
4109
1
                    rt_record_history(vm->rt, var_name, target);
4110
164
            }
4111
164
        }
4112
165
        DISPATCH();
4113
165
    }
4114
4115
165
    CASE(SUBLIMATE_VAR) {
4116
5
        uint8_t name_ki = REG_GET_A(instr);
4117
5
        uint8_t loc_type = REG_GET_B(instr);
4118
5
        uint8_t slot = REG_GET_C(instr);
4119
5
        const char *var_name = frame->chunk->constants[name_ki].as.str_val;
4120
4121
5
        LatValue *target = NULL;
4122
5
        if (loc_type == 0) {
4123
5
            target = &R[slot];
4124
5
        } else if (loc_type == 1 && frame->upvalues && slot < frame->upvalue_count) {
4125
0
            target = frame->upvalues[slot]->location;
4126
0
        } else if (loc_type == 2) {
4127
0
            LatValue gval;
4128
0
            if (env_get(vm->env, var_name, &gval)) {
4129
0
                gval.phase = VTAG_SUBLIMATED;
4130
0
                env_set(vm->env, var_name, gval);
4131
0
                rt_fire_reactions(vm->rt, var_name, "sublimated");
4132
0
            }
4133
0
            DISPATCH();
4134
0
        }
4135
5
        if (target) {
4136
5
            target->phase = VTAG_SUBLIMATED;
4137
            /* Sync to env */
4138
5
            if (loc_type != 2) {
4139
5
                if (!env_set(vm->env, var_name, value_deep_clone(target)))
4140
4
                    env_define(vm->env, var_name, value_deep_clone(target));
4141
5
            }
4142
5
            rt_fire_reactions(vm->rt, var_name, "sublimated");
4143
5
        }
4144
5
        DISPATCH();
4145
5
    }
4146
4147
9
    CASE(REACT) {
4148
        /* Compiler emits: emit_ABx(ROP_REACT, dst, name_ki) → A=cb_reg, Bx=name_ki */
4149
9
        uint8_t cb_reg = REG_GET_A(instr);
4150
9
        uint16_t name_ki = REG_GET_Bx(instr);
4151
9
        const char *var_name = frame->chunk->constants[name_ki].as.str_val;
4152
9
        if (R[cb_reg].type != VAL_CLOSURE) DISPATCH();
4153
        /* Find or create reaction entry */
4154
9
        size_t ri = vm->rt->reaction_count;
4155
9
        for (size_t i = 0; i < vm->rt->reaction_count; i++) {
4156
1
            if (strcmp(vm->rt->reactions[i].var_name, var_name) == 0) { ri = i; break; }
4157
1
        }
4158
9
        if (ri == vm->rt->reaction_count) {
4159
8
            if (vm->rt->reaction_count >= vm->rt->reaction_cap) {
4160
8
                vm->rt->reaction_cap = vm->rt->reaction_cap ? vm->rt->reaction_cap * 2 : 4;
4161
8
                vm->rt->reactions = realloc(vm->rt->reactions, vm->rt->reaction_cap * sizeof(*vm->rt->reactions));
4162
8
            }
4163
8
            vm->rt->reactions[ri].var_name = strdup(var_name);
4164
8
            vm->rt->reactions[ri].callbacks = NULL;
4165
8
            vm->rt->reactions[ri].cb_count = 0;
4166
8
            vm->rt->reactions[ri].cb_cap = 0;
4167
8
            vm->rt->reaction_count++;
4168
8
        }
4169
9
        if (vm->rt->reactions[ri].cb_count >= vm->rt->reactions[ri].cb_cap) {
4170
8
            vm->rt->reactions[ri].cb_cap = vm->rt->reactions[ri].cb_cap ? vm->rt->reactions[ri].cb_cap * 2 : 4;
4171
8
            vm->rt->reactions[ri].callbacks = realloc(vm->rt->reactions[ri].callbacks,
4172
8
                vm->rt->reactions[ri].cb_cap * sizeof(LatValue));
4173
8
        }
4174
9
        vm->rt->reactions[ri].callbacks[vm->rt->reactions[ri].cb_count++] = value_deep_clone(&R[cb_reg]);
4175
9
        DISPATCH();
4176
9
    }
4177
4178
9
    CASE(UNREACT) {
4179
        /* Compiler emits: emit_ABx(ROP_UNREACT, dst, name_ki) → A=dst, Bx=name_ki */
4180
1
        uint16_t name_ki = REG_GET_Bx(instr);
4181
1
        const char *var_name = frame->chunk->constants[name_ki].as.str_val;
4182
1
        for (size_t i = 0; i < vm->rt->reaction_count; i++) {
4183
1
            if (strcmp(vm->rt->reactions[i].var_name, var_name) != 0) continue;
4184
1
            free(vm->rt->reactions[i].var_name);
4185
2
            for (size_t j = 0; j < vm->rt->reactions[i].cb_count; j++)
4186
1
                value_free(&vm->rt->reactions[i].callbacks[j]);
4187
1
            free(vm->rt->reactions[i].callbacks);
4188
1
            vm->rt->reactions[i] = vm->rt->reactions[--vm->rt->reaction_count];
4189
1
            break;
4190
1
        }
4191
1
        DISPATCH();
4192
1
    }
4193
4194
17
    CASE(BOND) {
4195
        /* Compiler emits: emit_ABC(ROP_BOND, target_ki, dep_reg, strat_reg) → A=target_ki, B=dep_reg, C=strat_reg */
4196
17
        uint8_t target_ki = REG_GET_A(instr);
4197
17
        uint8_t dep_reg = REG_GET_B(instr);
4198
17
        uint8_t strat_reg = REG_GET_C(instr);
4199
17
        const char *target_name = frame->chunk->constants[target_ki].as.str_val;
4200
17
        const char *dep_name = (R[dep_reg].type == VAL_STR) ? R[dep_reg].as.str_val : "";
4201
17
        const char *strategy = (R[strat_reg].type == VAL_STR) ? R[strat_reg].as.str_val : "mirror";
4202
17
        if (dep_name[0] == '\0') {
4203
1
            RVM_ERROR("bond() requires variable names for dependencies");
4204
1
        }
4205
        /* Validate: check variables exist and target is not already frozen */
4206
16
        {
4207
            /* Find target variable's phase */
4208
16
            PhaseTag target_phase = VTAG_UNPHASED;
4209
16
            LatValue tval;
4210
16
            bool t_env = env_get(vm->env, target_name, &tval);
4211
16
            if (t_env) { target_phase = tval.phase; value_free(&tval); }
4212
15
            else {
4213
30
                for (int fi = 0; fi < (int)vm->frame_count; fi++) {
4214
30
                    RegCallFrame *f = &vm->frames[fi];
4215
30
                    if (!f->chunk || !f->chunk->local_names) continue;
4216
31
                    for (size_t r = 0; r < f->chunk->local_name_cap; r++) {
4217
31
                        if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], target_name) == 0) {
4218
15
                            target_phase = f->regs[r].phase;
4219
15
                            goto found_target;
4220
15
                        }
4221
31
                    }
4222
15
                }
4223
15
                found_target:;
4224
15
            }
4225
16
            if (target_phase == VTAG_CRYSTAL)
4226
1
                RVM_ERROR("bond: variable '%s' is already frozen", target_name);
4227
4228
            /* Check dep variable exists */
4229
15
            LatValue dep_val;
4230
15
            bool dep_found = env_get(vm->env, dep_name, &dep_val);
4231
15
            if (dep_found) { value_free(&dep_val); }
4232
14
            else {
4233
14
                bool found_local = false;
4234
29
                for (int fi = 0; fi < (int)vm->frame_count; fi++) {
4235
28
                    RegCallFrame *f = &vm->frames[fi];
4236
28
                    if (!f->chunk || !f->chunk->local_names) continue;
4237
60
                    for (size_t r = 0; r < f->chunk->local_name_cap; r++) {
4238
59
                        if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], dep_name) == 0) {
4239
13
                            found_local = true; break;
4240
13
                        }
4241
59
                    }
4242
14
                    if (found_local) break;
4243
14
                }
4244
14
                if (!found_local)
4245
1
                    RVM_ERROR("bond: undefined variable '%s'", dep_name);
4246
14
            }
4247
15
        }
4248
        /* Find or create bond entry */
4249
14
        size_t bi = vm->rt->bond_count;
4250
15
        for (size_t i = 0; i < vm->rt->bond_count; i++) {
4251
3
            if (strcmp(vm->rt->bonds[i].target, target_name) == 0) { bi = i; break; }
4252
3
        }
4253
14
        if (bi == vm->rt->bond_count) {
4254
12
            if (vm->rt->bond_count >= vm->rt->bond_cap) {
4255
11
                vm->rt->bond_cap = vm->rt->bond_cap ? vm->rt->bond_cap * 2 : 4;
4256
11
                vm->rt->bonds = realloc(vm->rt->bonds, vm->rt->bond_cap * sizeof(*vm->rt->bonds));
4257
11
            }
4258
12
            vm->rt->bonds[bi].target = strdup(target_name);
4259
12
            vm->rt->bonds[bi].deps = NULL;
4260
12
            vm->rt->bonds[bi].dep_strategies = NULL;
4261
12
            vm->rt->bonds[bi].dep_count = 0;
4262
12
            vm->rt->bonds[bi].dep_cap = 0;
4263
12
            vm->rt->bond_count++;
4264
12
        }
4265
14
        if (vm->rt->bonds[bi].dep_count >= vm->rt->bonds[bi].dep_cap) {
4266
12
            vm->rt->bonds[bi].dep_cap = vm->rt->bonds[bi].dep_cap ? vm->rt->bonds[bi].dep_cap * 2 : 4;
4267
12
            vm->rt->bonds[bi].deps = realloc(vm->rt->bonds[bi].deps,
4268
12
                vm->rt->bonds[bi].dep_cap * sizeof(char *));
4269
12
            vm->rt->bonds[bi].dep_strategies = realloc(vm->rt->bonds[bi].dep_strategies,
4270
12
                vm->rt->bonds[bi].dep_cap * sizeof(char *));
4271
12
        }
4272
14
        vm->rt->bonds[bi].deps[vm->rt->bonds[bi].dep_count] = strdup(dep_name);
4273
14
        vm->rt->bonds[bi].dep_strategies[vm->rt->bonds[bi].dep_count] = strdup(strategy);
4274
14
        vm->rt->bonds[bi].dep_count++;
4275
14
        DISPATCH();
4276
14
    }
4277
4278
1
    CASE(UNBOND) {
4279
        /* Compiler emits: emit_ABx(ROP_UNBOND, target_ki, dep_reg) → A=target_ki, Bx=dep_reg */
4280
1
        uint8_t target_ki = REG_GET_A(instr);
4281
1
        uint8_t dep_reg = (uint8_t)REG_GET_Bx(instr);
4282
1
        const char *target_name = frame->chunk->constants[target_ki].as.str_val;
4283
1
        const char *dep_name = (R[dep_reg].type == VAL_STR) ? R[dep_reg].as.str_val : "";
4284
1
        for (size_t i = 0; i < vm->rt->bond_count; i++) {
4285
1
            if (strcmp(vm->rt->bonds[i].target, target_name) != 0) continue;
4286
1
            for (size_t j = 0; j < vm->rt->bonds[i].dep_count; j++) {
4287
1
                if (strcmp(vm->rt->bonds[i].deps[j], dep_name) != 0) continue;
4288
1
                free(vm->rt->bonds[i].deps[j]);
4289
1
                if (vm->rt->bonds[i].dep_strategies)
4290
1
                    free(vm->rt->bonds[i].dep_strategies[j]);
4291
1
                vm->rt->bonds[i].deps[j] = vm->rt->bonds[i].deps[vm->rt->bonds[i].dep_count - 1];
4292
1
                if (vm->rt->bonds[i].dep_strategies)
4293
1
                    vm->rt->bonds[i].dep_strategies[j] = vm->rt->bonds[i].dep_strategies[vm->rt->bonds[i].dep_count - 1];
4294
1
                vm->rt->bonds[i].dep_count--;
4295
1
                break;
4296
1
            }
4297
1
            if (vm->rt->bonds[i].dep_count == 0) {
4298
1
                free(vm->rt->bonds[i].target);
4299
1
                free(vm->rt->bonds[i].deps);
4300
1
                free(vm->rt->bonds[i].dep_strategies);
4301
1
                vm->rt->bonds[i] = vm->rt->bonds[--vm->rt->bond_count];
4302
1
            }
4303
1
            break;
4304
1
        }
4305
1
        DISPATCH();
4306
1
    }
4307
4308
4
    CASE(SEED) {
4309
        /* Compiler emits: emit_ABx(ROP_SEED, dst, name_ki) → A=contract_reg, Bx=name_ki */
4310
4
        uint8_t contract_reg = REG_GET_A(instr);
4311
4
        uint16_t name_ki = REG_GET_Bx(instr);
4312
4
        const char *var_name = frame->chunk->constants[name_ki].as.str_val;
4313
4
        if (R[contract_reg].type != VAL_CLOSURE) DISPATCH();
4314
4
        if (vm->rt->seed_count >= vm->rt->seed_cap) {
4315
4
            vm->rt->seed_cap = vm->rt->seed_cap ? vm->rt->seed_cap * 2 : 4;
4316
4
            vm->rt->seeds = realloc(vm->rt->seeds, vm->rt->seed_cap * sizeof(*vm->rt->seeds));
4317
4
        }
4318
4
        vm->rt->seeds[vm->rt->seed_count].var_name = strdup(var_name);
4319
4
        vm->rt->seeds[vm->rt->seed_count].contract = value_deep_clone(&R[contract_reg]);
4320
4
        vm->rt->seed_count++;
4321
4
        DISPATCH();
4322
4
    }
4323
4324
4
    CASE(UNSEED) {
4325
        /* Compiler emits: emit_ABx(ROP_UNSEED, dst, name_ki) → A=dst, Bx=name_ki */
4326
1
        uint16_t name_ki = REG_GET_Bx(instr);
4327
1
        const char *var_name = frame->chunk->constants[name_ki].as.str_val;
4328
1
        for (size_t i = 0; i < vm->rt->seed_count; i++) {
4329
1
            if (strcmp(vm->rt->seeds[i].var_name, var_name) != 0) continue;
4330
1
            free(vm->rt->seeds[i].var_name);
4331
1
            value_free(&vm->rt->seeds[i].contract);
4332
1
            vm->rt->seeds[i] = vm->rt->seeds[--vm->rt->seed_count];
4333
1
            break;
4334
1
        }
4335
1
        DISPATCH();
4336
1
    }
4337
4338
    /* ── Module/Import ── */
4339
4340
94
    CASE(IMPORT) {
4341
94
        uint8_t a = REG_GET_A(instr);
4342
94
        uint16_t bx = REG_GET_Bx(instr);
4343
94
        const char *raw_path = frame->chunk->constants[bx].as.str_val;
4344
4345
        /* Check for built-in stdlib module */
4346
94
        LatValue builtin_mod;
4347
94
        if (rt_try_builtin_import(raw_path, &builtin_mod)) {
4348
11
            reg_set(&R[a], builtin_mod);
4349
11
            DISPATCH();
4350
11
        }
4351
4352
        /* Resolve file path: append .lat if not present */
4353
94
        size_t plen = strlen(raw_path);
4354
94
        char *file_path;
4355
94
        if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) {
4356
0
            file_path = strdup(raw_path);
4357
94
        } else {
4358
94
            file_path = malloc(plen + 5);
4359
94
            memcpy(file_path, raw_path, plen);
4360
94
            memcpy(file_path + plen, ".lat", 5);
4361
94
        }
4362
4363
        /* Resolve to absolute path */
4364
94
        char resolved[PATH_MAX];
4365
94
        if (!realpath(file_path, resolved)) {
4366
1
            char *emsg = NULL;
4367
1
            (void)asprintf(&emsg, "import: cannot find '%s'", file_path);
4368
1
            free(file_path);
4369
            /* Set error directly without [line N] prefix for import errors */
4370
1
            vm->error = emsg;
4371
1
            return REGVM_RUNTIME_ERROR;
4372
1
        }
4373
93
        free(file_path);
4374
4375
        /* Check module cache */
4376
93
        if (vm->module_cache) {
4377
1
            LatValue *cached = lat_map_get(vm->module_cache, resolved);
4378
1
            if (cached) {
4379
1
                reg_set(&R[a], rvm_clone(cached));
4380
1
                DISPATCH();
4381
1
            }
4382
1
        }
4383
4384
        /* Read the file */
4385
93
        char *source = builtin_read_file(resolved);
4386
93
        if (!source)
4387
0
            RVM_ERROR("import: cannot read '%s'", resolved);
4388
4389
        /* Lex */
4390
93
        Lexer mod_lex = lexer_new(source);
4391
93
        char *lex_err = NULL;
4392
93
        LatVec mod_toks = lexer_tokenize(&mod_lex, &lex_err);
4393
93
        free(source);
4394
93
        if (lex_err) {
4395
0
            char errmsg[1024];
4396
0
            snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved, lex_err);
4397
0
            free(lex_err);
4398
0
            lat_vec_free(&mod_toks);
4399
0
            RVM_ERROR("%s", errmsg);
4400
0
        }
4401
4402
        /* Parse */
4403
93
        Parser mod_parser = parser_new(&mod_toks);
4404
93
        char *parse_err = NULL;
4405
93
        Program mod_prog = parser_parse(&mod_parser, &parse_err);
4406
93
        if (parse_err) {
4407
0
            char errmsg[1024];
4408
0
            snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved, parse_err);
4409
0
            free(parse_err);
4410
0
            program_free(&mod_prog);
4411
0
            for (size_t ti = 0; ti < mod_toks.len; ti++)
4412
0
                token_free(lat_vec_get(&mod_toks, ti));
4413
0
            lat_vec_free(&mod_toks);
4414
0
            RVM_ERROR("%s", errmsg);
4415
0
        }
4416
4417
        /* Compile as module */
4418
93
        char *comp_err = NULL;
4419
93
        RegChunk *mod_chunk = reg_compile_module(&mod_prog, &comp_err);
4420
4421
        /* Free parse artifacts */
4422
93
        program_free(&mod_prog);
4423
117k
        for (size_t ti = 0; ti < mod_toks.len; ti++)
4424
117k
            token_free(lat_vec_get(&mod_toks, ti));
4425
93
        lat_vec_free(&mod_toks);
4426
4427
93
        if (!mod_chunk) {
4428
0
            char errmsg[1024];
4429
0
            snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved,
4430
0
                     comp_err ? comp_err : "compile error");
4431
0
            free(comp_err);
4432
0
            RVM_ERROR("%s", errmsg);
4433
0
        }
4434
4435
        /* Track chunk */
4436
93
        regvm_track_chunk(vm, mod_chunk);
4437
4438
        /* Push module scope */
4439
93
        env_push_scope(vm->env);
4440
4441
        /* Run module by pushing a new frame */
4442
93
        LatValue mod_result;
4443
93
        RegVMResult mod_r = regvm_run_sub(vm, mod_chunk, &mod_result);
4444
        /* Restore frame/R pointers after dispatch */
4445
93
        frame = &vm->frames[vm->frame_count - 1];
4446
93
        R = frame->regs;
4447
4448
93
        if (mod_r != REGVM_OK) {
4449
0
            env_pop_scope(vm->env);
4450
0
            reg_set(&R[a], value_nil());
4451
0
            DISPATCH();
4452
0
        }
4453
93
        value_free(&mod_result);
4454
4455
        /* Build module Map from the module scope */
4456
93
        LatValue module_map = value_map_new();
4457
93
        Scope *mod_scope = &vm->env->scopes[vm->env->count - 1];
4458
3.22k
        for (size_t mi = 0; mi < mod_scope->cap; mi++) {
4459
3.13k
            if (mod_scope->entries[mi].state != MAP_OCCUPIED) continue;
4460
1.77k
            const char *name = mod_scope->entries[mi].key;
4461
1.77k
            LatValue *val_ptr = (LatValue *)mod_scope->entries[mi].value;
4462
4463
            /* Copy all module bindings to base scope for closures */
4464
1.77k
            env_define_at(vm->env, 0, name, value_deep_clone(val_ptr));
4465
4466
            /* Filter based on export declarations */
4467
1.77k
            if (!module_should_export(name,
4468
1.77k
                    (const char **)mod_chunk->export_names,
4469
1.77k
                    mod_chunk->export_count, mod_chunk->has_exports))
4470
12
                continue;
4471
4472
1.76k
            LatValue exported = rvm_clone(val_ptr);
4473
1.76k
            lat_map_set(module_map.as.map.map, name, &exported);
4474
1.76k
        }
4475
4476
93
        env_pop_scope(vm->env);
4477
4478
        /* Cache */
4479
93
        if (!vm->module_cache) {
4480
81
            vm->module_cache = malloc(sizeof(LatMap));
4481
81
            *vm->module_cache = lat_map_new(sizeof(LatValue));
4482
81
        }
4483
93
        LatValue cache_copy = value_deep_clone(&module_map);
4484
93
        lat_map_set(vm->module_cache, resolved, &cache_copy);
4485
4486
93
        reg_set(&R[a], module_map);
4487
93
        DISPATCH();
4488
93
    }
4489
4490
9
    CASE(REQUIRE) {
4491
9
        uint8_t a = REG_GET_A(instr);
4492
9
        uint16_t bx = REG_GET_Bx(instr);
4493
9
        const char *raw_path = frame->chunk->constants[bx].as.str_val;
4494
4495
        /* Resolve file path: append .lat if not present */
4496
9
        size_t plen = strlen(raw_path);
4497
9
        char *file_path;
4498
9
        if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) {
4499
2
            file_path = strdup(raw_path);
4500
7
        } else {
4501
7
            file_path = malloc(plen + 5);
4502
7
            memcpy(file_path, raw_path, plen);
4503
7
            memcpy(file_path + plen, ".lat", 5);
4504
7
        }
4505
4506
        /* Resolve to absolute path: try CWD first, then script_dir */
4507
9
        char resolved[PATH_MAX];
4508
9
        bool found = (realpath(file_path, resolved) != NULL);
4509
9
        if (!found && vm->rt->script_dir && file_path[0] != '/') {
4510
0
            char script_rel[PATH_MAX];
4511
0
            snprintf(script_rel, sizeof(script_rel), "%s/%s",
4512
0
                     vm->rt->script_dir, file_path);
4513
0
            found = (realpath(script_rel, resolved) != NULL);
4514
0
        }
4515
9
        if (!found) {
4516
1
            char *emsg = NULL;
4517
1
            (void)asprintf(&emsg, "require: cannot find '%s'", raw_path);
4518
1
            free(file_path);
4519
            /* Set error directly without [line N] prefix, matching native_require */
4520
1
            vm->error = emsg;
4521
1
            return REGVM_RUNTIME_ERROR;
4522
1
        }
4523
8
        free(file_path);
4524
4525
        /* Dedup: skip if already required */
4526
8
        if (vm->module_cache) {
4527
3
            LatValue *cached = lat_map_get(vm->module_cache, resolved);
4528
3
            if (cached) {
4529
2
                reg_set(&R[a], value_bool(true));
4530
2
                DISPATCH();
4531
2
            }
4532
3
        }
4533
4534
        /* Read the file */
4535
8
        char *source = builtin_read_file(resolved);
4536
8
        if (!source)
4537
0
            RVM_ERROR("require: cannot read '%s'", resolved);
4538
4539
        /* Lex */
4540
8
        Lexer req_lex = lexer_new(source);
4541
8
        char *lex_err = NULL;
4542
8
        LatVec req_toks = lexer_tokenize(&req_lex, &lex_err);
4543
8
        free(source);
4544
8
        if (lex_err) {
4545
0
            char errmsg[1024];
4546
0
            snprintf(errmsg, sizeof(errmsg), "require '%s': %s", resolved, lex_err);
4547
0
            free(lex_err);
4548
0
            lat_vec_free(&req_toks);
4549
0
            RVM_ERROR("%s", errmsg);
4550
0
        }
4551
4552
        /* Parse */
4553
8
        Parser req_parser = parser_new(&req_toks);
4554
8
        char *parse_err = NULL;
4555
8
        Program req_prog = parser_parse(&req_parser, &parse_err);
4556
8
        if (parse_err) {
4557
0
            char errmsg[1024];
4558
0
            snprintf(errmsg, sizeof(errmsg), "require '%s': %s", resolved, parse_err);
4559
0
            free(parse_err);
4560
0
            program_free(&req_prog);
4561
0
            for (size_t ti = 0; ti < req_toks.len; ti++)
4562
0
                token_free(lat_vec_get(&req_toks, ti));
4563
0
            lat_vec_free(&req_toks);
4564
0
            RVM_ERROR("%s", errmsg);
4565
0
        }
4566
4567
        /* Compile as module (via regcompiler, not stack VM compiler) */
4568
8
        char *comp_err = NULL;
4569
8
        RegChunk *req_chunk = reg_compile_module(&req_prog, &comp_err);
4570
4571
        /* Free parse artifacts */
4572
8
        program_free(&req_prog);
4573
109
        for (size_t ti = 0; ti < req_toks.len; ti++)
4574
101
            token_free(lat_vec_get(&req_toks, ti));
4575
8
        lat_vec_free(&req_toks);
4576
4577
8
        if (!req_chunk) {
4578
0
            char errmsg[1024];
4579
0
            snprintf(errmsg, sizeof(errmsg), "require '%s': %s", resolved,
4580
0
                     comp_err ? comp_err : "compile error");
4581
0
            free(comp_err);
4582
0
            RVM_ERROR("%s", errmsg);
4583
0
        }
4584
4585
        /* Track chunk */
4586
8
        regvm_track_chunk(vm, req_chunk);
4587
4588
        /* Mark as loaded (for dedup) before execution */
4589
8
        if (!vm->module_cache) {
4590
5
            vm->module_cache = malloc(sizeof(LatMap));
4591
5
            *vm->module_cache = lat_map_new(sizeof(LatValue));
4592
5
        }
4593
8
        LatValue loaded_marker = value_bool(true);
4594
8
        lat_map_set(vm->module_cache, resolved, &loaded_marker);
4595
4596
        /* Run module — NO scope isolation, defs go directly to global env */
4597
8
        LatValue req_result;
4598
8
        RegVMResult req_r = regvm_run_sub(vm, req_chunk, &req_result);
4599
        /* Restore frame/R pointers after dispatch */
4600
8
        frame = &vm->frames[vm->frame_count - 1];
4601
8
        R = frame->regs;
4602
4603
8
        if (req_r != REGVM_OK) {
4604
            /* Propagate the error */
4605
0
            return REGVM_RUNTIME_ERROR;
4606
0
        }
4607
8
        value_free(&req_result);
4608
4609
8
        reg_set(&R[a], value_bool(true));
4610
8
        DISPATCH();
4611
8
    }
4612
4613
    /* ── Concurrency ── */
4614
4615
4
    CASE(SCOPE) {
4616
4
        uint8_t dst_reg = REG_GET_A(instr);
4617
        /* Variable-length: read spawn_count, sync_idx, spawn_indices */
4618
4
        RegInstr data1 = READ_INSTR();
4619
4
        uint8_t spawn_count = REG_GET_A(data1);
4620
4
        uint8_t sync_idx = REG_GET_B(data1);
4621
4
        uint8_t spawn_indices[256];
4622
        /* Read spawn indices from follow-up data words (3 per word: A, B, C) */
4623
6
        for (uint8_t i = 0; i < spawn_count; i += 3) {
4624
2
            RegInstr sp = READ_INSTR();
4625
2
            spawn_indices[i] = REG_GET_A(sp);
4626
2
            if (i + 1 < spawn_count) spawn_indices[i + 1] = REG_GET_B(sp);
4627
2
            if (i + 2 < spawn_count) spawn_indices[i + 2] = REG_GET_C(sp);
4628
2
        }
4629
4630
        /* Export locals to env for sub-chunk access */
4631
4
        env_push_scope(vm->env);
4632
12
        for (int fi2 = 0; fi2 < vm->frame_count; fi2++) {
4633
8
            RegCallFrame *f2 = &vm->frames[fi2];
4634
8
            if (!f2->chunk) continue;
4635
59
            for (size_t sl = 0; sl < f2->chunk->local_name_cap; sl++) {
4636
51
                if (f2->chunk->local_names[sl])
4637
6
                    env_define(vm->env, f2->chunk->local_names[sl],
4638
6
                               rvm_clone(&f2->regs[sl]));
4639
51
            }
4640
8
        }
4641
4642
        /* Run sync body — its return value becomes the scope result */
4643
4
        LatValue scope_result = value_unit();
4644
4
        if (sync_idx != 0xFF) {
4645
2
            RegChunk *sync_body = (RegChunk *)frame->chunk->constants[sync_idx].as.closure.native_fn;
4646
2
            if (sync_body) {
4647
2
                RegVMResult sr = regvm_run_sub(vm, sync_body, &scope_result);
4648
                /* Restore frame/R pointers */
4649
2
                frame = &vm->frames[vm->frame_count - 1];
4650
2
                R = frame->regs;
4651
2
                if (sr != REGVM_OK) {
4652
0
                    env_pop_scope(vm->env);
4653
0
                    RVM_ERROR("%s", vm->error ? vm->error : "scope error");
4654
0
                }
4655
2
            }
4656
2
        }
4657
4658
        /* Run spawns synchronously (threading TBD) */
4659
6
        for (uint8_t i = 0; i < spawn_count; i++) {
4660
3
            RegChunk *sp_chunk = (RegChunk *)frame->chunk->constants[spawn_indices[i]].as.closure.native_fn;
4661
3
            if (sp_chunk) {
4662
3
                LatValue sp_result;
4663
3
                RegVMResult sr = regvm_run_sub(vm, sp_chunk, &sp_result);
4664
                /* Restore frame/R pointers */
4665
3
                frame = &vm->frames[vm->frame_count - 1];
4666
3
                R = frame->regs;
4667
3
                value_free(&sp_result);
4668
3
                if (sr != REGVM_OK) {
4669
1
                    value_free(&scope_result);
4670
1
                    env_pop_scope(vm->env);
4671
1
                    RVM_ERROR("%s", vm->error ? vm->error : "spawn error");
4672
1
                }
4673
3
            }
4674
3
        }
4675
4676
3
        env_pop_scope(vm->env);
4677
3
        reg_set(&R[dst_reg], scope_result);
4678
3
        DISPATCH();
4679
3
    }
4680
4681
5
    CASE(SELECT) {
4682
5
        uint8_t dst_reg = REG_GET_A(instr);
4683
        /* Variable-length: arm_count, per-arm data */
4684
5
        RegInstr data1 = READ_INSTR();
4685
5
        uint8_t arm_count = REG_GET_A(data1);
4686
4687
        /* Read all arm descriptors (2 data words per arm) */
4688
5
        typedef struct { uint8_t flags, chan_idx, body_idx, binding_idx; } RSelArm;
4689
5
        RSelArm sel_arms[64];
4690
14
        for (uint8_t i = 0; i < arm_count && i < 64; i++) {
4691
9
            RegInstr d1 = READ_INSTR();
4692
9
            RegInstr d2 = READ_INSTR();
4693
9
            sel_arms[i].flags = REG_GET_A(d1);
4694
9
            sel_arms[i].chan_idx = REG_GET_B(d1);
4695
9
            sel_arms[i].body_idx = REG_GET_C(d1);
4696
9
            sel_arms[i].binding_idx = REG_GET_A(d2);
4697
9
        }
4698
4699
        /* Export locals to env for sub-chunk visibility */
4700
5
        env_push_scope(vm->env);
4701
15
        for (int fi2 = 0; fi2 < vm->frame_count; fi2++) {
4702
10
            RegCallFrame *f2 = &vm->frames[fi2];
4703
10
            if (!f2->chunk) continue;
4704
95
            for (size_t sl = 0; sl < f2->chunk->local_name_cap; sl++) {
4705
85
                if (f2->chunk->local_names[sl])
4706
11
                    env_define(vm->env, f2->chunk->local_names[sl],
4707
11
                               rvm_clone(&f2->regs[sl]));
4708
85
            }
4709
10
        }
4710
4711
        /* Find default arm */
4712
5
        int default_arm = -1;
4713
11
        for (uint8_t i = 0; i < arm_count; i++) {
4714
9
            if (sel_arms[i].flags & 0x01) { default_arm = (int)i; break; }
4715
9
        }
4716
4717
        /* Evaluate channel expressions */
4718
5
        LatChannel **channels = calloc(arm_count, sizeof(LatChannel *));
4719
14
        for (uint8_t i = 0; i < arm_count; i++) {
4720
9
            if (sel_arms[i].flags & 0x03) continue;  /* skip default/timeout */
4721
6
            RegChunk *ch_chunk = (RegChunk *)frame->chunk->constants[sel_arms[i].chan_idx].as.closure.native_fn;
4722
6
            LatValue ch_val;
4723
6
            RegVMResult cr = regvm_run_sub(vm, ch_chunk, &ch_val);
4724
6
            frame = &vm->frames[vm->frame_count - 1];
4725
6
            R = frame->regs;
4726
6
            if (cr != REGVM_OK || ch_val.type != VAL_CHANNEL) {
4727
0
                value_free(&ch_val);
4728
0
                for (uint8_t j = 0; j < i; j++)
4729
0
                    if (channels[j]) channel_release(channels[j]);
4730
0
                free(channels);
4731
0
                env_pop_scope(vm->env);
4732
0
                RVM_ERROR("select arm: expression is not a Channel");
4733
0
            }
4734
6
            channels[i] = ch_val.as.channel.ch;
4735
6
            channel_retain(channels[i]);
4736
6
            value_free(&ch_val);
4737
6
        }
4738
4739
        /* Try non-blocking recv on each channel arm */
4740
5
        LatValue select_result = value_unit();
4741
5
        bool select_found = false;
4742
11
        for (uint8_t i = 0; i < arm_count; i++) {
4743
8
            if (sel_arms[i].flags & 0x03) continue;
4744
6
            LatValue recv_val;
4745
6
            bool closed = false;
4746
6
            if (channel_try_recv(channels[i], &recv_val, &closed)) {
4747
                /* Got a value — bind in env, run body */
4748
2
                env_push_scope(vm->env);
4749
2
                if (sel_arms[i].flags & 0x04) {
4750
2
                    const char *binding = frame->chunk->constants[sel_arms[i].binding_idx].as.str_val;
4751
2
                    if (binding)
4752
2
                        env_define(vm->env, binding, recv_val);
4753
0
                    else
4754
0
                        value_free(&recv_val);
4755
2
                } else {
4756
0
                    value_free(&recv_val);
4757
0
                }
4758
2
                RegChunk *body_chunk = (RegChunk *)frame->chunk->constants[sel_arms[i].body_idx].as.closure.native_fn;
4759
2
                LatValue arm_result;
4760
2
                RegVMResult ar = regvm_run_sub(vm, body_chunk, &arm_result);
4761
2
                frame = &vm->frames[vm->frame_count - 1];
4762
2
                R = frame->regs;
4763
2
                env_pop_scope(vm->env);
4764
2
                if (ar == REGVM_OK) {
4765
2
                    value_free(&select_result);
4766
2
                    select_result = arm_result;
4767
2
                }
4768
2
                select_found = true;
4769
2
                break;
4770
2
            }
4771
4
            (void)closed;
4772
4
        }
4773
4774
        /* If no channel was ready, execute default arm if present */
4775
5
        if (!select_found && default_arm >= 0) {
4776
2
            env_push_scope(vm->env);
4777
2
            RegChunk *def_chunk = (RegChunk *)frame->chunk->constants[sel_arms[default_arm].body_idx].as.closure.native_fn;
4778
2
            LatValue def_result;
4779
2
            RegVMResult dr = regvm_run_sub(vm, def_chunk, &def_result);
4780
2
            frame = &vm->frames[vm->frame_count - 1];
4781
2
            R = frame->regs;
4782
2
            env_pop_scope(vm->env);
4783
2
            if (dr == REGVM_OK) {
4784
2
                value_free(&select_result);
4785
2
                select_result = def_result;
4786
2
            }
4787
2
        }
4788
4789
14
        for (uint8_t i = 0; i < arm_count; i++)
4790
9
            if (channels[i]) channel_release(channels[i]);
4791
5
        free(channels);
4792
5
        env_pop_scope(vm->env);
4793
4794
5
        reg_set(&R[dst_reg], select_result);
4795
5
        DISPATCH();
4796
5
    }
4797
4798
    /* ── Ephemeral arena ── */
4799
4800
0
    CASE(RESET_EPHEMERAL) {
4801
0
        if (vm->ephemeral)
4802
0
            bump_arena_reset(vm->ephemeral);
4803
0
        DISPATCH();
4804
0
    }
4805
4806
    /* ── Optimization opcodes ── */
4807
4808
0
    CASE(ADD_INT) {
4809
0
        uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr);
4810
0
        R[a].type = VAL_INT;
4811
0
        R[a].as.int_val = R[b].as.int_val + R[c].as.int_val;
4812
0
        DISPATCH();
4813
0
    }
4814
4815
0
    CASE(SUB_INT) {
4816
0
        uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr);
4817
0
        R[a].type = VAL_INT;
4818
0
        R[a].as.int_val = R[b].as.int_val - R[c].as.int_val;
4819
0
        DISPATCH();
4820
0
    }
4821
4822
0
    CASE(MUL_INT) {
4823
0
        uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr);
4824
0
        R[a].type = VAL_INT;
4825
0
        R[a].as.int_val = R[b].as.int_val * R[c].as.int_val;
4826
0
        DISPATCH();
4827
0
    }
4828
4829
353
    CASE(LT_INT) {
4830
353
        uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr);
4831
353
        R[a].type = VAL_BOOL;
4832
353
        R[a].as.bool_val = R[b].as.int_val < R[c].as.int_val;
4833
353
        DISPATCH();
4834
353
    }
4835
4836
353
    CASE(LTEQ_INT) {
4837
0
        uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr);
4838
0
        R[a].type = VAL_BOOL;
4839
0
        R[a].as.bool_val = R[b].as.int_val <= R[c].as.int_val;
4840
0
        DISPATCH();
4841
0
    }
4842
4843
306
    CASE(INC_REG) {
4844
306
        uint8_t a = REG_GET_A(instr);
4845
306
        R[a].as.int_val++;
4846
306
        DISPATCH();
4847
306
    }
4848
4849
306
    CASE(DEC_REG) {
4850
0
        uint8_t a = REG_GET_A(instr);
4851
0
        R[a].as.int_val--;
4852
0
        DISPATCH();
4853
0
    }
4854
4855
27
    CASE(SETINDEX_LOCAL) {
4856
        /* R[A][R[B]] = R[C] — in-place array/map mutation */
4857
27
        uint8_t a = REG_GET_A(instr), b = REG_GET_B(instr), c = REG_GET_C(instr);
4858
        /* Phase checks */
4859
27
        if (R[a].phase == VTAG_CRYSTAL) {
4860
1
            bool blocked = true;
4861
1
            if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) {
4862
1
                PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val);
4863
1
                if (!kp || *kp != VTAG_CRYSTAL) blocked = false;
4864
1
            }
4865
1
            if (blocked)
4866
0
                RVM_ERROR("cannot modify a frozen value");
4867
1
        }
4868
27
        if (R[a].phase == VTAG_SUBLIMATED && R[a].type == VAL_MAP)
4869
1
            RVM_ERROR("cannot add keys to a sublimated map");
4870
26
        if (R[a].type == VAL_MAP && R[b].type == VAL_STR && R[a].as.map.key_phases) {
4871
3
            PhaseTag *kp = lat_map_get(R[a].as.map.key_phases, R[b].as.str_val);
4872
3
            if (kp && *kp == VTAG_CRYSTAL)
4873
1
                RVM_ERROR("cannot modify frozen key '%s'", R[b].as.str_val);
4874
3
        }
4875
25
        if (R[a].type == VAL_ARRAY) {
4876
2
            if (R[b].type == VAL_INT) {
4877
2
                int64_t idx = R[b].as.int_val;
4878
2
                if (idx < 0) idx += (int64_t)R[a].as.array.len;
4879
2
                if (idx >= 0 && (size_t)idx < R[a].as.array.len) {
4880
2
                    value_free(&R[a].as.array.elems[idx]);
4881
2
                    R[a].as.array.elems[idx] = rvm_clone(&R[c]);
4882
2
                }
4883
2
            }
4884
23
        } else if (R[a].type == VAL_MAP) {
4885
22
            if (R[b].type == VAL_STR) {
4886
22
                LatValue cloned = rvm_clone(&R[c]);
4887
22
                lat_map_set(R[a].as.map.map, R[b].as.str_val, &cloned);
4888
22
            }
4889
22
        } else if (R[a].type == VAL_REF) {
4890
            /* Proxy: set index on inner value */
4891
1
            LatRef *ref = R[a].as.ref.ref;
4892
1
            if (value_is_crystal(&R[a]))
4893
0
                RVM_ERROR("cannot mutate a frozen Ref");
4894
1
            if (ref->value.type == VAL_MAP) {
4895
1
                if (R[b].type == VAL_STR) {
4896
1
                    LatValue cloned = rvm_clone(&R[c]);
4897
1
                    lat_map_set(ref->value.as.map.map, R[b].as.str_val, &cloned);
4898
1
                }
4899
1
            } else if (ref->value.type == VAL_ARRAY) {
4900
0
                if (R[b].type == VAL_INT) {
4901
0
                    int64_t idx = R[b].as.int_val;
4902
0
                    if (idx < 0) idx += (int64_t)ref->value.as.array.len;
4903
0
                    if (idx >= 0 && (size_t)idx < ref->value.as.array.len) {
4904
0
                        value_free(&ref->value.as.array.elems[idx]);
4905
0
                        ref->value.as.array.elems[idx] = rvm_clone(&R[c]);
4906
0
                    }
4907
0
                }
4908
0
            }
4909
1
        }
4910
25
        DISPATCH();
4911
25
    }
4912
4913
209
    CASE(INVOKE_GLOBAL) {
4914
        /* Two-instruction sequence:
4915
         *   INVOKE_GLOBAL dst, name_ki, argc
4916
         *   data:         method_ki, args_base, 0
4917
         * Mutates the global value in-place (for push/pop/etc). */
4918
209
        uint8_t dst = REG_GET_A(instr);
4919
209
        uint8_t name_ki = REG_GET_B(instr);
4920
209
        uint8_t argc = REG_GET_C(instr);
4921
4922
209
        RegInstr data = *frame->ip++;
4923
209
        uint8_t method_ki = REG_GET_A(data);
4924
209
        uint8_t args_base = REG_GET_B(data);
4925
4926
209
        const char *global_name = frame->chunk->constants[name_ki].as.str_val;
4927
209
        const char *method_name = frame->chunk->constants[method_ki].as.str_val;
4928
4929
        /* Get a direct reference to the global value */
4930
209
        LatValue *obj_ref = env_get_ref(vm->env, global_name);
4931
209
        if (!obj_ref) {
4932
0
            RVM_ERROR("undefined variable '%s'", global_name);
4933
0
            DISPATCH();
4934
0
        }
4935
4936
        /* Try builtin — mutates obj_ref in-place */
4937
209
        LatValue invoke_result;
4938
209
        LatValue *invoke_args = (argc > 0) ? &R[args_base] : NULL;
4939
209
        if (rvm_invoke_builtin(vm, obj_ref, method_name, invoke_args, argc, &invoke_result)) {
4940
41
            if (vm->error)
4941
0
                return REGVM_RUNTIME_ERROR;
4942
41
            reg_set(&R[dst], invoke_result);
4943
41
            DISPATCH();
4944
41
        }
4945
4946
        /* Check for callable closure field in struct */
4947
209
        if (obj_ref->type == VAL_STRUCT) {
4948
0
            for (size_t fi = 0; fi < obj_ref->as.strct.field_count; fi++) {
4949
0
                if (strcmp(obj_ref->as.strct.field_names[fi], method_name) != 0)
4950
0
                    continue;
4951
0
                LatValue *field = &obj_ref->as.strct.field_values[fi];
4952
0
                if (field->type == VAL_CLOSURE) {
4953
                    /* Copy object into a temp register, then proceed like normal INVOKE */
4954
0
                    uint8_t tmp = args_base > 0 ? args_base - 1 : dst;
4955
0
                    reg_set(&R[tmp], rvm_clone(obj_ref));
4956
                    /* Fall through to closure call logic similar to INVOKE */
4957
0
                    LatValue closure = rvm_clone(field);
4958
0
                    if (closure.as.closure.body == NULL &&
4959
0
                        closure.as.closure.native_fn != NULL &&
4960
0
                        closure.as.closure.default_values != VM_NATIVE_MARKER &&
4961
0
                        closure.as.closure.default_values != VM_EXT_MARKER) {
4962
                        /* Compiled closure */
4963
0
                        RegChunk *fn_chunk = (RegChunk *)closure.as.closure.native_fn;
4964
0
                        if (vm->frame_count >= REGVM_FRAMES_MAX) {
4965
0
                            value_free(&closure);
4966
0
                            RVM_ERROR("stack overflow");
4967
0
                            DISPATCH();
4968
0
                        }
4969
0
                        LatValue *new_regs = &vm->reg_stack[vm->reg_stack_top];
4970
0
                        vm->reg_stack_top += REGVM_REG_MAX;
4971
0
                        for (int ri = 0; ri < REGVM_REG_MAX; ri++)
4972
0
                            new_regs[ri] = value_nil();
4973
4974
                        /* Slot 0 = reserved, slot 1 = self, slots 2+ = args */
4975
0
                        new_regs[0] = value_unit();
4976
0
                        new_regs[1] = rvm_clone(obj_ref);  /* self = first param */
4977
0
                        value_free(&closure);
4978
                        /* Copy args into param slots */
4979
0
                        for (int ai = 0; ai < argc && ai + 2 < REGVM_REG_MAX; ai++)
4980
0
                            new_regs[ai + 2] = rvm_clone(&R[args_base + ai]);
4981
4982
0
                        RegCallFrame *nf = &vm->frames[vm->frame_count++];
4983
0
                        nf->chunk = fn_chunk;
4984
0
                        nf->ip = fn_chunk->code;
4985
0
                        nf->regs = new_regs;
4986
0
                        nf->reg_count = REGVM_REG_MAX;
4987
0
                        nf->caller_result_reg = dst;
4988
4989
0
                        ObjUpvalue **upvals = (ObjUpvalue **)closure.as.closure.captured_env;
4990
0
                        size_t uv_count = closure.region_id;
4991
0
                        nf->upvalues = upvals;
4992
0
                        nf->upvalue_count = uv_count;
4993
4994
0
                        frame = nf;
4995
0
                        R = frame->regs;
4996
0
                        DISPATCH();
4997
0
                    }
4998
0
                    value_free(&closure);
4999
0
                }
5000
0
                break;
5001
0
            }
5002
0
        }
5003
5004
        /* Check for callable closure field in map (global) */
5005
209
        if (obj_ref->type == VAL_MAP) {
5006
168
            LatValue *field = lat_map_get(obj_ref->as.map.map, method_name);
5007
168
            if (field && field->type == VAL_CLOSURE) {
5008
168
                if (field->as.closure.default_values == VM_NATIVE_MARKER) {
5009
2
                    VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
5010
2
                    LatValue *call_args = (argc > 0) ? &R[args_base] : NULL;
5011
2
                    LatValue ret = native(call_args, argc);
5012
2
                    if (vm->rt->error) {
5013
0
                        vm->error = vm->rt->error;
5014
0
                        vm->rt->error = NULL;
5015
0
                        value_free(&ret);
5016
0
                        return REGVM_RUNTIME_ERROR;
5017
0
                    }
5018
2
                    reg_set(&R[dst], ret);
5019
2
                    DISPATCH();
5020
2
                }
5021
168
                if (field->as.closure.default_values == VM_EXT_MARKER) {
5022
0
                    LatValue *call_args = (argc > 0) ? &R[args_base] : NULL;
5023
0
                    LatValue ret = ext_call_native(field->as.closure.native_fn,
5024
0
                                                   call_args, (size_t)argc);
5025
0
                    if (ret.type == VAL_STR && ret.as.str_val &&
5026
0
                        strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) {
5027
0
                        vm->error = strdup(ret.as.str_val + 11);
5028
0
                        value_free(&ret);
5029
0
                        return REGVM_RUNTIME_ERROR;
5030
0
                    }
5031
0
                    reg_set(&R[dst], ret);
5032
0
                    DISPATCH();
5033
0
                }
5034
168
                RegChunk *fn_chunk = (RegChunk *)field->as.closure.native_fn;
5035
168
                if (fn_chunk) {
5036
166
                    uint32_t magic;
5037
166
                    memcpy(&magic, fn_chunk, sizeof(uint32_t));
5038
166
                    if (magic == REGCHUNK_MAGIC) {
5039
166
                        if (vm->frame_count >= REGVM_FRAMES_MAX)
5040
0
                            RVM_ERROR("call stack overflow");
5041
166
                        LatValue *new_regs = &vm->reg_stack[vm->reg_stack_top];
5042
166
                        vm->reg_stack_top += REGVM_REG_MAX;
5043
42.6k
                        for (int ri = 0; ri < REGVM_REG_MAX; ri++)
5044
42.4k
                            new_regs[ri] = value_nil();
5045
166
                        new_regs[0] = value_unit();
5046
410
                        for (int ai = 0; ai < argc; ai++)
5047
244
                            new_regs[1 + ai] = rvm_clone(&R[args_base + ai]);
5048
5049
166
                        ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
5050
166
                        size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
5051
5052
166
                        RegCallFrame *nf = &vm->frames[vm->frame_count++];
5053
166
                        nf->chunk = fn_chunk;
5054
166
                        nf->ip = fn_chunk->code;
5055
166
                        nf->regs = new_regs;
5056
166
                        nf->reg_count = REGVM_REG_MAX;
5057
166
                        nf->upvalues = upvals;
5058
166
                        nf->upvalue_count = uv_count;
5059
166
                        nf->caller_result_reg = dst;
5060
166
                        frame = nf;
5061
166
                        R = frame->regs;
5062
166
                        DISPATCH();
5063
166
                    }
5064
166
                }
5065
168
            }
5066
168
        }
5067
5068
        /* Fallback: copy global into temp, do regular invoke */
5069
209
        {
5070
209
            LatValue obj_copy = rvm_clone(obj_ref);
5071
209
            LatValue fb_result;
5072
209
            LatValue *fb_args = (argc > 0) ? &R[args_base] : NULL;
5073
209
            if (rvm_invoke_builtin(vm, &obj_copy, method_name, fb_args, argc, &fb_result)) {
5074
                /* Write back the mutated copy to the global */
5075
0
                value_free(obj_ref);
5076
0
                *obj_ref = obj_copy;
5077
0
                reg_set(&R[dst], fb_result);
5078
209
            } else {
5079
209
                value_free(&obj_copy);
5080
209
                reg_set(&R[dst], value_nil());
5081
209
            }
5082
209
        }
5083
209
        DISPATCH();
5084
209
    }
5085
5086
16
    CASE(IS_CRYSTAL) {
5087
16
        uint8_t a = REG_GET_A(instr);
5088
16
        uint8_t b = REG_GET_B(instr);
5089
16
        reg_set(&R[a], value_bool(R[b].phase == VTAG_CRYSTAL));
5090
16
        DISPATCH();
5091
16
    }
5092
5093
900
    CASE(CHECK_TYPE) {
5094
        /* 2-word instruction:
5095
         * word 1: A=value reg, Bx=expected type name constant index
5096
         * word 2: error message constant index (raw 32-bit, 0xFFFFFFFF = default) */
5097
900
        uint8_t a = REG_GET_A(instr);
5098
900
        uint16_t bx = REG_GET_Bx(instr);
5099
900
        uint32_t err_word = *frame->ip++;
5100
900
        const char *expected = frame->chunk->constants[bx].as.str_val;
5101
        /* Type matching logic (mirrors vm_type_matches from stack VM) */
5102
900
        bool type_ok = false;
5103
900
        if (!expected || strcmp(expected, "Any") == 0 || strcmp(expected, "any") == 0) {
5104
7
            type_ok = true;
5105
893
        } else if (strcmp(expected, "Int") == 0) {
5106
126
            type_ok = R[a].type == VAL_INT;
5107
767
        } else if (strcmp(expected, "Float") == 0) {
5108
0
            type_ok = R[a].type == VAL_FLOAT;
5109
767
        } else if (strcmp(expected, "String") == 0) {
5110
73
            type_ok = R[a].type == VAL_STR;
5111
694
        } else if (strcmp(expected, "Bool") == 0) {
5112
27
            type_ok = R[a].type == VAL_BOOL;
5113
667
        } else if (strcmp(expected, "Nil") == 0) {
5114
0
            type_ok = R[a].type == VAL_NIL;
5115
667
        } else if (strcmp(expected, "Map") == 0) {
5116
537
            type_ok = R[a].type == VAL_MAP;
5117
537
        } else if (strcmp(expected, "Array") == 0) {
5118
62
            type_ok = R[a].type == VAL_ARRAY;
5119
68
        } else if (strcmp(expected, "Fn") == 0 || strcmp(expected, "Closure") == 0) {
5120
57
            type_ok = R[a].type == VAL_CLOSURE;
5121
57
        } else if (strcmp(expected, "Channel") == 0) {
5122
0
            type_ok = R[a].type == VAL_CHANNEL;
5123
11
        } else if (strcmp(expected, "Range") == 0) {
5124
0
            type_ok = R[a].type == VAL_RANGE;
5125
11
        } else if (strcmp(expected, "Set") == 0) {
5126
0
            type_ok = R[a].type == VAL_SET;
5127
11
        } else if (strcmp(expected, "Tuple") == 0) {
5128
0
            type_ok = R[a].type == VAL_TUPLE;
5129
11
        } else if (strcmp(expected, "Buffer") == 0) {
5130
0
            type_ok = R[a].type == VAL_BUFFER;
5131
11
        } else if (strcmp(expected, "Ref") == 0) {
5132
0
            type_ok = R[a].type == VAL_REF;
5133
11
        } else if (strcmp(expected, "Number") == 0) {
5134
5
            type_ok = R[a].type == VAL_INT || R[a].type == VAL_FLOAT;
5135
6
        } else if (R[a].type == VAL_STRUCT && R[a].as.strct.name) {
5136
5
            type_ok = strcmp(R[a].as.strct.name, expected) == 0;
5137
5
        } else if (R[a].type == VAL_ENUM && R[a].as.enm.enum_name) {
5138
1
            type_ok = strcmp(R[a].as.enm.enum_name, expected) == 0;
5139
1
        }
5140
900
        if (!type_ok) {
5141
4
            const char *display;
5142
4
            if (R[a].type == VAL_STRUCT && R[a].as.strct.name)
5143
1
                display = R[a].as.strct.name;
5144
3
            else if (R[a].type == VAL_ENUM && R[a].as.enm.enum_name)
5145
0
                display = R[a].as.enm.enum_name;
5146
3
            else if (R[a].type == VAL_CLOSURE)
5147
0
                display = "Fn";
5148
3
            else
5149
3
                display = value_type_name(&R[a]);
5150
4
            if (err_word != 0xFFFFFFFF) {
5151
                /* Custom error format with %s placeholder for actual type */
5152
4
                const char *fmt = frame->chunk->constants[err_word].as.str_val;
5153
4
                RVM_ERROR(fmt, display);
5154
4
            } else {
5155
0
                RVM_ERROR("return type expects %s, got %s", expected, display);
5156
0
            }
5157
4
        }
5158
896
        DISPATCH();
5159
896
    }
5160
5161
4
    CASE(FREEZE_FIELD) {
5162
        /* A=var reg, B=field name constant */
5163
4
        uint8_t a = REG_GET_A(instr);
5164
4
        uint8_t b_ki = REG_GET_B(instr);
5165
4
        const char *field_name = frame->chunk->constants[b_ki].as.str_val;
5166
5167
4
        if (R[a].type == VAL_STRUCT) {
5168
2
            size_t fi = (size_t)-1;
5169
2
            for (size_t i = 0; i < R[a].as.strct.field_count; i++) {
5170
2
                if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) { fi = i; break; }
5171
2
            }
5172
2
            if (fi == (size_t)-1) RVM_ERROR("struct has no field '%s'", field_name);
5173
2
            R[a].as.strct.field_values[fi] = value_freeze(R[a].as.strct.field_values[fi]);
5174
2
            if (!R[a].as.strct.field_phases)
5175
2
                R[a].as.strct.field_phases = calloc(R[a].as.strct.field_count, sizeof(PhaseTag));
5176
2
            R[a].as.strct.field_phases[fi] = VTAG_CRYSTAL;
5177
2
        } else if (R[a].type == VAL_MAP) {
5178
2
            LatValue *val_ptr = (LatValue *)lat_map_get(R[a].as.map.map, field_name);
5179
2
            if (val_ptr) *val_ptr = value_freeze(*val_ptr);
5180
2
            if (!R[a].as.map.key_phases) {
5181
2
                R[a].as.map.key_phases = calloc(1, sizeof(LatMap));
5182
2
                *R[a].as.map.key_phases = lat_map_new(sizeof(PhaseTag));
5183
2
            }
5184
2
            PhaseTag phase = VTAG_CRYSTAL;
5185
2
            lat_map_set(R[a].as.map.key_phases, field_name, &phase);
5186
2
        }
5187
4
        DISPATCH();
5188
4
    }
5189
5190
3
    CASE(THAW_FIELD) {
5191
        /* A=var reg, B=field name constant */
5192
3
        uint8_t a = REG_GET_A(instr);
5193
3
        uint8_t b_ki = REG_GET_B(instr);
5194
3
        const char *field_name = frame->chunk->constants[b_ki].as.str_val;
5195
5196
3
        if (R[a].type == VAL_STRUCT) {
5197
2
            if (!R[a].as.strct.field_phases) {
5198
2
                R[a].as.strct.field_phases = calloc(R[a].as.strct.field_count, sizeof(PhaseTag));
5199
8
                for (size_t i = 0; i < R[a].as.strct.field_count; i++)
5200
6
                    R[a].as.strct.field_phases[i] = R[a].phase;
5201
2
            }
5202
6
            for (size_t i = 0; i < R[a].as.strct.field_count; i++) {
5203
6
                if (strcmp(R[a].as.strct.field_names[i], field_name) == 0) {
5204
2
                    R[a].as.strct.field_phases[i] = VTAG_FLUID;
5205
2
                    break;
5206
2
                }
5207
6
            }
5208
2
        } else if (R[a].type == VAL_MAP) {
5209
1
            if (!R[a].as.map.key_phases) {
5210
1
                R[a].as.map.key_phases = calloc(1, sizeof(LatMap));
5211
1
                *R[a].as.map.key_phases = lat_map_new(sizeof(PhaseTag));
5212
1
            }
5213
1
            PhaseTag phase = VTAG_FLUID;
5214
1
            lat_map_set(R[a].as.map.key_phases, field_name, &phase);
5215
1
        }
5216
3
        DISPATCH();
5217
3
    }
5218
5219
8
    CASE(HALT) {
5220
8
        *result = value_unit();
5221
8
        return REGVM_OK;
5222
3
    }
5223
5224
0
#ifdef VM_USE_COMPUTED_GOTO
5225
    /* End of computed goto block - should never reach here */
5226
0
    RVM_ERROR("unreachable: fell through computed goto dispatch");
5227
#else
5228
        default:
5229
            RVM_ERROR("unknown register opcode %d", REG_GET_OP(instr));
5230
        } /* end switch */
5231
    } /* end for */
5232
#endif
5233
0
}
5234
5235
/* ── Dispatch adapters for LatRuntime ── */
5236
5237
/* call_closure: delegates to the register VM's closure call */
5238
19
static LatValue regvm_dispatch_call_closure(void *opaque_vm, LatValue *closure, LatValue *args, int argc) {
5239
19
    return regvm_call_closure((RegVM *)opaque_vm, closure, args, argc);
5240
19
}
5241
5242
/* find_local_value: searches register frame locals for a named variable */
5243
1
static bool regvm_dispatch_find_local_value(void *opaque_vm, const char *name, LatValue *out) {
5244
1
    RegVM *rvm = (RegVM *)opaque_vm;
5245
3
    for (int fi = 0; fi < rvm->frame_count; fi++) {
5246
2
        RegCallFrame *f = &rvm->frames[fi];
5247
2
        if (!f->chunk || !f->chunk->local_names) continue;
5248
0
        for (size_t r = 0; r < f->chunk->local_name_cap; r++) {
5249
0
            if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], name) == 0) {
5250
0
                *out = value_deep_clone(&f->regs[r]);
5251
0
                return true;
5252
0
            }
5253
0
        }
5254
0
    }
5255
1
    return false;
5256
1
}
5257
5258
/* current_line: returns the source line of the current instruction */
5259
30
static int regvm_dispatch_current_line(void *opaque_vm) {
5260
30
    RegVM *rvm = (RegVM *)opaque_vm;
5261
30
    if (rvm->frame_count <= 0) return 0;
5262
30
    RegCallFrame *f = &rvm->frames[rvm->frame_count - 1];
5263
30
    if (f->ip > f->chunk->code) {
5264
30
        size_t offset = (size_t)(f->ip - f->chunk->code - 1);
5265
30
        if (offset < f->chunk->lines_len)
5266
30
            return f->chunk->lines[offset];
5267
30
    }
5268
0
    return 0;
5269
30
}
5270
5271
/* get_var_by_name: searches locals then globals */
5272
21
static bool regvm_dispatch_get_var_by_name(void *opaque_vm, const char *name, LatValue *out) {
5273
21
    RegVM *rvm = (RegVM *)opaque_vm;
5274
    /* Search frame locals first */
5275
42
    for (int fi = 0; fi < rvm->frame_count; fi++) {
5276
42
        RegCallFrame *f = &rvm->frames[fi];
5277
42
        if (!f->chunk || !f->chunk->local_names) continue;
5278
59
        for (size_t r = 0; r < f->chunk->local_name_cap; r++) {
5279
59
            if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], name) == 0) {
5280
21
                *out = value_deep_clone(&f->regs[r]);
5281
21
                return true;
5282
21
            }
5283
59
        }
5284
21
    }
5285
    /* Fall back to globals */
5286
0
    return env_get(rvm->env, name, out);
5287
21
}
5288
5289
/* set_var_by_name: sets in locals (if found) or globals */
5290
11
static bool regvm_dispatch_set_var_by_name(void *opaque_vm, const char *name, LatValue val) {
5291
11
    RegVM *rvm = (RegVM *)opaque_vm;
5292
    /* Try frame locals first */
5293
22
    for (int fi = 0; fi < rvm->frame_count; fi++) {
5294
22
        RegCallFrame *f = &rvm->frames[fi];
5295
22
        if (!f->chunk || !f->chunk->local_names) continue;
5296
36
        for (size_t r = 0; r < f->chunk->local_name_cap; r++) {
5297
36
            if (f->chunk->local_names[r] && strcmp(f->chunk->local_names[r], name) == 0) {
5298
11
                value_free(&f->regs[r]);
5299
11
                f->regs[r] = val;
5300
                /* Also sync to env */
5301
11
                LatValue clone = value_deep_clone(&val);
5302
11
                if (!env_set(rvm->env, name, clone))
5303
9
                    env_define(rvm->env, name, clone);
5304
11
                return true;
5305
11
            }
5306
36
        }
5307
11
    }
5308
    /* Fall back to globals */
5309
0
    if (env_set(rvm->env, name, val))
5310
0
        return true;
5311
0
    env_define(rvm->env, name, val);
5312
0
    return true;
5313
0
}
5314
5315
/* Set up runtime dispatch pointers for the register VM */
5316
857
static void regvm_setup_dispatch(RegVM *vm) {
5317
857
    vm->rt->backend = RT_BACKEND_REG_VM;
5318
857
    vm->rt->active_vm = vm;
5319
857
    vm->rt->call_closure = regvm_dispatch_call_closure;
5320
857
    vm->rt->find_local_value = regvm_dispatch_find_local_value;
5321
857
    vm->rt->current_line = regvm_dispatch_current_line;
5322
857
    vm->rt->get_var_by_name = regvm_dispatch_get_var_by_name;
5323
857
    vm->rt->set_var_by_name = regvm_dispatch_set_var_by_name;
5324
857
    lat_runtime_set_current(vm->rt);
5325
857
}
5326
5327
857
RegVMResult regvm_run(RegVM *vm, RegChunk *chunk, LatValue *result) {
5328
    /* Set up runtime dispatch so native functions can call back into regvm */
5329
857
    regvm_setup_dispatch(vm);
5330
5331
    /* Reentrant: push a new frame on top of the existing stack.
5332
     * This is safe for recursive calls (e.g. native_lat_eval, native_require)
5333
     * because we don't clobber existing frames. */
5334
857
    int base_frame = vm->frame_count;
5335
857
    if (vm->frame_count >= REGVM_FRAMES_MAX) {
5336
0
        vm->error = strdup("regvm_run: frame overflow");
5337
0
        return REGVM_RUNTIME_ERROR;
5338
0
    }
5339
857
    RegCallFrame *frame = &vm->frames[vm->frame_count++];
5340
857
    frame->chunk = chunk;
5341
857
    frame->ip = chunk->code;
5342
857
    frame->regs = &vm->reg_stack[vm->reg_stack_top];
5343
857
    frame->reg_count = REGVM_REG_MAX;
5344
857
    frame->upvalues = NULL;
5345
857
    frame->upvalue_count = 0;
5346
857
    frame->caller_result_reg = 0;
5347
857
    vm->reg_stack_top += REGVM_REG_MAX;
5348
5349
    /* Zero the new register window */
5350
857
    memset(frame->regs, 0, REGVM_REG_MAX * sizeof(LatValue));
5351
5352
857
    return regvm_dispatch(vm, base_frame, result);
5353
857
}
5354
5355
/* REPL variant: reuses existing frame 0 registers (preserves globals/locals) */
5356
0
RegVMResult regvm_run_repl(RegVM *vm, RegChunk *chunk, LatValue *result) {
5357
    /* Set up runtime dispatch so native functions can call back into regvm */
5358
0
    regvm_setup_dispatch(vm);
5359
0
    RegCallFrame *frame = &vm->frames[0];
5360
0
    frame->chunk = chunk;
5361
0
    frame->ip = chunk->code;
5362
    /* Reuse existing regs — don't reset reg_stack */
5363
0
    frame->regs = vm->reg_stack;
5364
0
    frame->reg_count = REGVM_REG_MAX;
5365
    /* Preserve upvalues from previous iterations */
5366
0
    frame->caller_result_reg = 0;
5367
0
    vm->frame_count = 1;
5368
0
    vm->reg_stack_top = REGVM_REG_MAX;
5369
5370
0
    return regvm_dispatch(vm, 0, result);
5371
0
}