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/stackvm.c
Line
Count
Source
1
#include "stackvm.h"
2
#include "runtime.h"
3
#include "stackopcode.h"
4
#include "stackcompiler.h"  /* for AstPhase (PHASE_FLUID, PHASE_CRYSTAL, PHASE_UNSPECIFIED) */
5
#include "intern.h"
6
#include "builtins.h"
7
#include "lattice.h"
8
#include "channel.h"
9
#include "ext.h"
10
#include "lexer.h"
11
#include "parser.h"
12
#include "latc.h"
13
#include "string_ops.h"
14
#include "array_ops.h"
15
#include <stdlib.h>
16
#include <limits.h>
17
#include <libgen.h>
18
#include <string.h>
19
#include <stdio.h>
20
#include <stdarg.h>
21
#ifndef __EMSCRIPTEN__
22
#include <pthread.h>
23
#endif
24
#include "memory.h"
25
26
/* In the StackVM dispatch loop, use the inline fast-path for value_free
27
 * to avoid function call overhead on primitives (int, float, bool, etc.) */
28
27.0k
#define value_free(v) value_free_inline(v)
29
30
/* Native function pointer for StackVM builtins.
31
 * Args array is arg_count elements. Returns a LatValue result. */
32
typedef LatValue (*VMNativeFn)(LatValue *args, int arg_count);
33
34
/* Sentinel to distinguish native C functions from compiled closures. */
35
27.7k
#define VM_NATIVE_MARKER ((struct Expr **)(uintptr_t)0x1)
36
12.4k
#define VM_EXT_MARKER    ((struct Expr **)(uintptr_t)0x2)
37
38
/* ── Stack operations ── */
39
40
40.5k
static void push(StackVM *vm, LatValue val) {
41
40.5k
    if (vm->stack_top - vm->stack >= STACKVM_STACK_MAX) {
42
0
        fprintf(stderr, "fatal: StackVM stack overflow\n");
43
0
        exit(1);
44
0
    }
45
40.5k
    *vm->stack_top = val;
46
40.5k
    vm->stack_top++;
47
40.5k
}
48
49
24.4k
static LatValue pop(StackVM *vm) {
50
24.4k
    vm->stack_top--;
51
24.4k
    return *vm->stack_top;
52
24.4k
}
53
54
8.45k
static LatValue *stackvm_peek(StackVM *vm, int distance) {
55
8.45k
    return &vm->stack_top[-1 - distance];
56
8.45k
}
57
58
/* Get the source line for the current instruction in the topmost frame. */
59
30
static int stackvm_current_line(StackVM *vm) {
60
30
    if (vm->frame_count == 0) return 0;
61
30
    StackCallFrame *f = &vm->frames[vm->frame_count - 1];
62
30
    if (!f->chunk || !f->chunk->lines || f->chunk->lines_len == 0) return 0;
63
30
    size_t offset = (size_t)(f->ip - f->chunk->code);
64
30
    if (offset > 0) offset--;  /* ip already advanced past the opcode */
65
30
    if (offset >= f->chunk->lines_len) offset = f->chunk->lines_len - 1;
66
30
    return f->chunk->lines[offset];
67
30
}
68
69
11
static StackVMResult runtime_error(StackVM *vm, const char *fmt, ...) {
70
11
    char *inner = NULL;
71
11
    va_list args;
72
11
    va_start(args, fmt);
73
11
    (void)vasprintf(&inner, fmt, args);
74
11
    va_end(args);
75
11
    vm->error = inner;
76
11
    return STACKVM_RUNTIME_ERROR;
77
11
}
78
79
/* Try to route a runtime error through exception handlers.
80
 * If a handler exists, unwinds to it, pushes the error string, and returns STACKVM_OK
81
 * (caller should `break` to continue the StackVM loop).
82
 * If no handler, returns STACKVM_RUNTIME_ERROR (caller should `return` the result). */
83
31
static StackVMResult stackvm_handle_error(StackVM *vm, StackCallFrame **frame_ptr, const char *fmt, ...) {
84
31
    char *inner = NULL;
85
31
    va_list args;
86
31
    va_start(args, fmt);
87
31
    (void)vasprintf(&inner, fmt, args);
88
31
    va_end(args);
89
90
31
    if (vm->handler_count > 0) {
91
        /* Caught by try/catch — pass raw error message without line prefix */
92
11
        StackExceptionHandler h = vm->handlers[--vm->handler_count];
93
17
        while (vm->frame_count - 1 > h.frame_index)
94
6
            vm->frame_count--;
95
11
        *frame_ptr = &vm->frames[vm->frame_count - 1];
96
11
        vm->stack_top = h.stack_top;
97
11
        (*frame_ptr)->ip = h.ip;
98
11
        push(vm, value_string(inner));
99
11
        free(inner);
100
11
        return STACKVM_OK;
101
11
    }
102
103
    /* Uncaught — store raw error (stack trace provides line info) */
104
20
    vm->error = inner;
105
20
    return STACKVM_RUNTIME_ERROR;
106
31
}
107
108
/* Like stackvm_handle_error but for native function errors that already have
109
 * a message in vm->error.  Does NOT prepend [line N] to match tree-walker
110
 * behaviour (native errors carry their own descriptive messages). */
111
78
static StackVMResult stackvm_handle_native_error(StackVM *vm, StackCallFrame **frame_ptr) {
112
78
    if (vm->handler_count > 0) {
113
13
        StackExceptionHandler h = vm->handlers[--vm->handler_count];
114
15
        while (vm->frame_count - 1 > h.frame_index)
115
2
            vm->frame_count--;
116
13
        *frame_ptr = &vm->frames[vm->frame_count - 1];
117
13
        vm->stack_top = h.stack_top;
118
13
        (*frame_ptr)->ip = h.ip;
119
13
        push(vm, value_string(vm->error));
120
13
        free(vm->error);
121
13
        vm->error = NULL;
122
13
        return STACKVM_OK;
123
13
    }
124
    /* Uncaught — vm->error already set by native function, no line prefix */
125
65
    return STACKVM_RUNTIME_ERROR;
126
78
}
127
128
2.13k
static bool is_falsy(LatValue *v) {
129
2.13k
    return v->type == VAL_NIL ||
130
2.13k
           (v->type == VAL_BOOL && !v->as.bool_val) ||
131
2.13k
           v->type == VAL_UNIT;
132
2.13k
}
133
134
/* ── Upvalue management ── */
135
136
48
static ObjUpvalue *new_upvalue(LatValue *slot) {
137
48
    ObjUpvalue *uv = calloc(1, sizeof(ObjUpvalue));
138
48
    uv->location = slot;
139
48
    uv->closed = value_nil();
140
48
    uv->next = NULL;
141
48
    return uv;
142
48
}
143
144
48
static ObjUpvalue *capture_upvalue(StackVM *vm, LatValue *local) {
145
48
    ObjUpvalue *prev = NULL;
146
48
    ObjUpvalue *uv = vm->open_upvalues;
147
148
64
    while (uv != NULL && uv->location > local) {
149
16
        prev = uv;
150
16
        uv = uv->next;
151
16
    }
152
153
48
    if (uv != NULL && uv->location == local)
154
0
        return uv;
155
156
48
    ObjUpvalue *created = new_upvalue(local);
157
48
    created->next = uv;
158
159
48
    if (prev == NULL)
160
32
        vm->open_upvalues = created;
161
16
    else
162
16
        prev->next = created;
163
164
48
    return created;
165
48
}
166
167
/* VM_NATIVE_MARKER and VM_EXT_MARKER defined at top of file */
168
169
/* Fast-path clone: flat copy for primitives, strdup for strings,
170
 * full deep clone only for compound types. */
171
26.0k
static inline LatValue value_clone_fast(const LatValue *src) {
172
26.0k
    switch (src->type) {
173
8.75k
        case VAL_INT: case VAL_FLOAT: case VAL_BOOL:
174
8.91k
        case VAL_UNIT: case VAL_NIL: case VAL_RANGE: {
175
8.91k
            LatValue v = *src;
176
8.91k
            v.region_id = REGION_NONE;
177
8.91k
            return v;
178
8.91k
        }
179
9.31k
        case VAL_STR: {
180
9.31k
            LatValue v = *src;
181
9.31k
            v.as.str_val = strdup(src->as.str_val);
182
9.31k
            v.region_id = REGION_NONE;
183
9.31k
            return v;
184
8.91k
        }
185
40
        case VAL_BUFFER: {
186
40
            LatValue v = *src;
187
40
            v.as.buffer.data = malloc(src->as.buffer.cap);
188
40
            memcpy(v.as.buffer.data, src->as.buffer.data, src->as.buffer.len);
189
40
            v.region_id = REGION_NONE;
190
40
            return v;
191
8.91k
        }
192
26
        case VAL_REF: {
193
26
            ref_retain(src->as.ref.ref);
194
26
            LatValue v = *src;
195
26
            v.region_id = REGION_NONE;
196
26
            return v;
197
8.91k
        }
198
5.56k
        case VAL_CLOSURE: {
199
5.56k
            if (src->as.closure.body == NULL && src->as.closure.native_fn != NULL &&
200
5.56k
                src->as.closure.default_values != VM_NATIVE_MARKER &&
201
5.56k
                src->as.closure.default_values != VM_EXT_MARKER) {
202
                /* Bytecode closure: shallow copy + strdup param_names */
203
5.37k
                LatValue v = *src;
204
5.37k
                if (src->as.closure.param_names) {
205
2.99k
                    v.as.closure.param_names = malloc(src->as.closure.param_count * sizeof(char *));
206
7.61k
                    for (size_t i = 0; i < src->as.closure.param_count; i++)
207
4.62k
                        v.as.closure.param_names[i] = strdup(src->as.closure.param_names[i]);
208
2.99k
                }
209
5.37k
                return v;
210
5.37k
            }
211
189
            return value_deep_clone(src);
212
5.56k
        }
213
603
        case VAL_STRUCT: {
214
603
            LatValue v = *src;
215
603
            size_t fc = src->as.strct.field_count;
216
603
            v.as.strct.name = strdup(src->as.strct.name);
217
603
            v.as.strct.field_names = malloc(fc * sizeof(char *));
218
603
            v.as.strct.field_values = malloc(fc * sizeof(LatValue));
219
1.81k
            for (size_t i = 0; i < fc; i++) {
220
1.21k
                v.as.strct.field_names[i] = strdup(src->as.strct.field_names[i]);
221
1.21k
                v.as.strct.field_values[i] = value_clone_fast(&src->as.strct.field_values[i]);
222
1.21k
            }
223
603
            if (src->as.strct.field_phases) {
224
18
                v.as.strct.field_phases = malloc(fc * sizeof(PhaseTag));
225
18
                memcpy(v.as.strct.field_phases, src->as.strct.field_phases, fc * sizeof(PhaseTag));
226
18
            }
227
603
            v.region_id = REGION_NONE;
228
603
            return v;
229
5.56k
        }
230
826
        case VAL_ARRAY: {
231
826
            LatValue v = *src;
232
826
            size_t len = src->as.array.len;
233
826
            size_t cap = src->as.array.cap;
234
826
            v.as.array.elems = malloc(cap * sizeof(LatValue));
235
5.81k
            for (size_t i = 0; i < len; i++)
236
4.99k
                v.as.array.elems[i] = value_clone_fast(&src->as.array.elems[i]);
237
826
            v.region_id = REGION_NONE;
238
826
            return v;
239
5.56k
        }
240
15
        case VAL_TUPLE: {
241
15
            LatValue v = *src;
242
15
            size_t len = src->as.tuple.len;
243
15
            v.as.tuple.elems = malloc(len * sizeof(LatValue));
244
54
            for (size_t i = 0; i < len; i++)
245
39
                v.as.tuple.elems[i] = value_clone_fast(&src->as.tuple.elems[i]);
246
15
            v.region_id = REGION_NONE;
247
15
            return v;
248
5.56k
        }
249
710
        case VAL_MAP: {
250
710
            LatValue v = value_map_new();
251
710
            v.phase = src->phase;  /* Preserve phase tag */
252
710
            LatMap *sm = src->as.map.map;
253
12.1k
            for (size_t i = 0; i < sm->cap; i++) {
254
11.4k
                if (sm->entries[i].state == MAP_OCCUPIED) {
255
1.88k
                    LatValue cloned = value_clone_fast((LatValue *)sm->entries[i].value);
256
1.88k
                    lat_map_set(v.as.map.map, sm->entries[i].key, &cloned);
257
1.88k
                }
258
11.4k
            }
259
710
            if (src->as.map.key_phases) {
260
2
                LatMap *ksrc = src->as.map.key_phases;
261
2
                v.as.map.key_phases = malloc(sizeof(LatMap));
262
2
                *v.as.map.key_phases = lat_map_new(sizeof(PhaseTag));
263
34
                for (size_t i = 0; i < ksrc->cap; i++) {
264
32
                    if (ksrc->entries[i].state == MAP_OCCUPIED)
265
4
                        lat_map_set(v.as.map.key_phases, ksrc->entries[i].key, ksrc->entries[i].value);
266
32
                }
267
2
            }
268
710
            v.region_id = REGION_NONE;
269
710
            return v;
270
5.56k
        }
271
28
        default:
272
28
            return value_deep_clone(src);
273
26.0k
    }
274
26.0k
}
275
276
3.67k
static void close_upvalues(StackVM *vm, LatValue *last) {
277
3.71k
    while (vm->open_upvalues != NULL && vm->open_upvalues->location >= last) {
278
48
        ObjUpvalue *uv = vm->open_upvalues;
279
48
        uv->closed = value_clone_fast(uv->location);
280
48
        uv->location = &uv->closed;
281
48
        vm->open_upvalues = uv->next;
282
48
    }
283
3.67k
}
284
285
/* Create a string value allocated in the ephemeral arena.
286
 * The caller passes a malloc'd string; it's copied into the arena and the original is freed. */
287
__attribute__((unused))
288
0
static inline LatValue stackvm_ephemeral_string(StackVM *vm, char *s) {
289
0
    if (vm->ephemeral) {
290
0
        char *arena_str = bump_strdup(vm->ephemeral, s);
291
0
        free(s);
292
0
        LatValue v;
293
0
        v.type = VAL_STR;
294
0
        v.phase = VTAG_UNPHASED;
295
0
        v.region_id = REGION_EPHEMERAL;
296
0
        v.as.str_val = arena_str;
297
0
        vm->ephemeral_on_stack = true;
298
0
        return v;
299
0
    }
300
0
    return value_string_owned(s);
301
0
}
302
303
/* Concatenate two strings directly into the ephemeral arena (avoids malloc+free). */
304
static inline LatValue stackvm_ephemeral_concat(StackVM *vm, const char *a, size_t la,
305
308
                                            const char *b, size_t lb) {
306
308
    size_t total = la + lb + 1;
307
308
    if (vm->ephemeral) {
308
308
        char *buf = bump_alloc(vm->ephemeral, total);
309
308
        memcpy(buf, a, la);
310
308
        memcpy(buf + la, b, lb);
311
308
        buf[la + lb] = '\0';
312
308
        vm->ephemeral_on_stack = true;
313
308
        LatValue v;
314
308
        v.type = VAL_STR;
315
308
        v.phase = VTAG_UNPHASED;
316
308
        v.region_id = REGION_EPHEMERAL;
317
308
        v.as.str_val = buf;
318
308
        return v;
319
308
    }
320
0
    char *buf = malloc(total);
321
0
    memcpy(buf, a, la);
322
0
    memcpy(buf + la, b, lb);
323
0
    buf[la + lb] = '\0';
324
0
    return value_string_owned(buf);
325
308
}
326
327
/* If value is ephemeral, deep-clone to malloc. */
328
6.92k
static inline void stackvm_promote_value(LatValue *v) {
329
6.92k
    if (v->region_id == REGION_EPHEMERAL) {
330
133
        *v = value_deep_clone(v);
331
133
    }
332
6.92k
}
333
334
/* Promote all ephemeral values in the current frame before entering a new
335
 * bytecode frame, so the callee's OP_RESET_EPHEMERAL won't invalidate
336
 * anything in the caller's frame. */
337
2.60k
static inline void stackvm_promote_frame_ephemerals(StackVM *vm, StackCallFrame *frame) {
338
2.60k
    if (vm->ephemeral_on_stack) {
339
2
        for (LatValue *slot = frame->slots; slot < vm->stack_top; slot++)
340
0
            stackvm_promote_value(slot);
341
2
        vm->ephemeral_on_stack = false;
342
2
    }
343
2.60k
}
344
345
/* ── Closure invocation helper for builtins ──
346
 * Calls a compiled closure from within the StackVM using a temporary wrapper chunk.
347
 * Returns the closure's return value. */
348
287
static LatValue stackvm_call_closure(StackVM *vm, LatValue *closure, LatValue *args, int arg_count) {
349
287
    if (closure->type != VAL_CLOSURE || closure->as.closure.native_fn == NULL ||
350
287
        closure->as.closure.default_values == VM_NATIVE_MARKER) {
351
0
        return value_nil();
352
0
    }
353
354
    /* Reuse the pre-built wrapper chunk, patching the arg count */
355
287
    vm->call_wrapper.code[1] = (uint8_t)arg_count;
356
357
    /* Push closure + args onto the stack for the wrapper to invoke */
358
287
    push(vm, value_clone_fast(closure));
359
616
    for (int i = 0; i < arg_count; i++)
360
329
        push(vm, value_clone_fast(&args[i]));
361
362
287
    LatValue result;
363
287
    stackvm_run(vm, &vm->call_wrapper, &result);
364
287
    return result;
365
287
}
366
367
45
static bool stackvm_find_local_value(StackVM *vm, const char *name, LatValue *out) {
368
45
    if (vm->frame_count == 0) return false;
369
45
    StackCallFrame *frame = &vm->frames[vm->frame_count - 1];
370
45
    Chunk *chunk = frame->chunk;
371
45
    if (!chunk->local_names) return false;
372
113
    for (size_t i = 0; i < chunk->local_name_cap; i++) {
373
112
        if (chunk->local_names[i] && strcmp(chunk->local_names[i], name) == 0) {
374
43
            *out = value_deep_clone(&frame->slots[i]);
375
43
            return true;
376
43
        }
377
112
    }
378
1
    return false;
379
44
}
380
381
/* Thin wrapper: delegate to runtime's record_history */
382
391
static inline void stackvm_record_history(StackVM *vm, const char *name, LatValue *val) {
383
391
    rt_record_history(vm->rt, name, val);
384
391
}
385
/* ── Phase system: variable access by name helpers ── */
386
387
23
static bool stackvm_get_var_by_name(StackVM *vm, const char *name, LatValue *out) {
388
    /* Check current frame's locals first */
389
23
    if (vm->frame_count > 0) {
390
23
        StackCallFrame *frame = &vm->frames[vm->frame_count - 1];
391
23
        Chunk *chunk = frame->chunk;
392
23
        if (chunk->local_names) {
393
63
            for (size_t i = 0; i < chunk->local_name_cap; i++) {
394
63
                if (chunk->local_names[i] && strcmp(chunk->local_names[i], name) == 0) {
395
23
                    *out = value_deep_clone(&frame->slots[i]);
396
23
                    return true;
397
23
                }
398
63
            }
399
23
        }
400
23
    }
401
0
    return env_get(vm->env, name, out);
402
23
}
403
404
12
static bool stackvm_set_var_by_name(StackVM *vm, const char *name, LatValue val) {
405
    /* Check current frame's locals first */
406
12
    if (vm->frame_count > 0) {
407
12
        StackCallFrame *frame = &vm->frames[vm->frame_count - 1];
408
12
        Chunk *chunk = frame->chunk;
409
12
        if (chunk->local_names) {
410
38
            for (size_t i = 0; i < chunk->local_name_cap; i++) {
411
38
                if (chunk->local_names[i] && strcmp(chunk->local_names[i], name) == 0) {
412
12
                    value_free(&frame->slots[i]);
413
12
                    frame->slots[i] = val;
414
12
                    return true;
415
12
                }
416
38
            }
417
12
        }
418
12
    }
419
0
    env_set(vm->env, name, val);
420
0
    return true;
421
12
}
422
423
/* Write back a value to a variable location (local/upvalue/global) and record history */
424
377
static void stackvm_write_back(StackVM *vm, StackCallFrame *frame, uint8_t loc_type, uint8_t loc_slot, const char *name, LatValue val) {
425
377
    switch (loc_type) {
426
375
        case 0: /* local */
427
375
            value_free(&frame->slots[loc_slot]);
428
375
            frame->slots[loc_slot] = value_deep_clone(&val);
429
375
            break;
430
0
        case 1: /* upvalue */
431
0
            if (frame->upvalues && loc_slot < frame->upvalue_count && frame->upvalues[loc_slot]) {
432
0
                value_free(frame->upvalues[loc_slot]->location);
433
0
                *frame->upvalues[loc_slot]->location = value_deep_clone(&val);
434
0
            }
435
0
            break;
436
2
        case 2: /* global */
437
2
            env_set(vm->env, name, value_deep_clone(&val));
438
2
            break;
439
377
    }
440
377
    stackvm_record_history(vm, name, &val);
441
377
}
442
443
/* ── Phase system wrappers: delegate to runtime, bridge errors to StackVM ── */
444
445
368
static StackVMResult stackvm_fire_reactions(StackVM *vm, StackCallFrame **frame_ptr, const char *name, const char *phase) {
446
368
    (void)frame_ptr;
447
368
    rt_fire_reactions(vm->rt, name, phase);
448
368
    if (vm->rt->error) {
449
1
        vm->error = vm->rt->error;
450
1
        vm->rt->error = NULL;
451
1
        return STACKVM_RUNTIME_ERROR;
452
1
    }
453
367
    return STACKVM_OK;
454
368
}
455
456
199
static StackVMResult stackvm_freeze_cascade(StackVM *vm, StackCallFrame **frame_ptr, const char *target_name) {
457
199
    (void)frame_ptr;
458
199
    rt_freeze_cascade(vm->rt, target_name);
459
199
    if (vm->rt->error) {
460
1
        vm->error = vm->rt->error;
461
1
        vm->rt->error = NULL;
462
1
        return STACKVM_RUNTIME_ERROR;
463
1
    }
464
198
    return STACKVM_OK;
465
199
}
466
467
200
static char *stackvm_validate_seeds(StackVM *vm, const char *name, LatValue *val, bool consume) {
468
200
    return rt_validate_seeds(vm->rt, name, val, consume);
469
200
}
470
/* ── StackVM lifecycle ── */
471
472
862
void stackvm_init(StackVM *vm, LatRuntime *rt) {
473
862
    memset(vm, 0, sizeof(StackVM));
474
862
    vm->rt = rt;
475
862
    vm->stack_top = vm->stack;
476
862
    vm->env = rt->env;          /* cached pointer — runtime owns the env */
477
862
    vm->error = NULL;
478
862
    vm->open_upvalues = NULL;
479
862
    vm->handler_count = 0;
480
862
    vm->defer_count = 0;
481
862
    vm->struct_meta = rt->struct_meta; /* cached pointer — runtime owns struct_meta */
482
862
    vm->fn_chunks = NULL;
483
862
    vm->fn_chunk_count = 0;
484
862
    vm->fn_chunk_cap = 0;
485
862
    vm->module_cache = lat_map_new(sizeof(LatValue));
486
862
    vm->ephemeral = bump_arena_new();
487
488
    /* Pre-build the call wrapper chunk: [OP_CALL, 0, OP_RETURN] */
489
862
    memset(&vm->call_wrapper, 0, sizeof(Chunk));
490
862
    vm->call_wrapper.code = malloc(3);
491
862
    vm->call_wrapper.code[0] = OP_CALL;
492
862
    vm->call_wrapper.code[1] = 0;
493
862
    vm->call_wrapper.code[2] = OP_RETURN;
494
862
    vm->call_wrapper.code_len = 3;
495
862
    vm->call_wrapper.code_cap = 3;
496
862
    vm->call_wrapper.lines = calloc(3, sizeof(int));
497
862
    vm->call_wrapper.lines_len = 3;
498
862
    vm->call_wrapper.lines_cap = 3;
499
500
862
}
501
502
862
void stackvm_free(StackVM *vm) {
503
    /* Clear thread-local runtime pointer if it still refers to this VM's runtime,
504
     * preventing dangling pointer after the caller's stack-allocated LatRuntime dies. */
505
862
    if (lat_runtime_current() == vm->rt)
506
862
        lat_runtime_set_current(NULL);
507
508
    /* Free any remaining stack values */
509
1.05k
    while (vm->stack_top > vm->stack) {
510
188
        vm->stack_top--;
511
188
        value_free(vm->stack_top);
512
188
    }
513
    /* env and struct_meta are owned by the runtime — don't free here */
514
862
    free(vm->error);
515
516
    /* Free open upvalues */
517
862
    ObjUpvalue *uv = vm->open_upvalues;
518
862
    while (uv) {
519
0
        ObjUpvalue *next = uv->next;
520
0
        if (uv->location == &uv->closed)
521
0
            value_free(&uv->closed);
522
0
        free(uv);
523
0
        uv = next;
524
0
    }
525
526
    /* Free upvalue arrays in frames */
527
1.06k
    for (size_t i = 0; i < vm->frame_count; i++) {
528
198
        StackCallFrame *f = &vm->frames[i];
529
198
        for (size_t j = 0; j < f->upvalue_count; j++) {
530
0
            if (f->upvalues[j] && f->upvalues[j]->location == &f->upvalues[j]->closed)
531
0
                value_free(&f->upvalues[j]->closed);
532
0
            free(f->upvalues[j]);
533
0
        }
534
198
        free(f->upvalues);
535
198
    }
536
537
    /* Free function chunks */
538
964
    for (size_t i = 0; i < vm->fn_chunk_count; i++)
539
102
        chunk_free(vm->fn_chunks[i]);
540
862
    free(vm->fn_chunks);
541
542
    /* Free per-StackVM module cache */
543
14.6k
    for (size_t i = 0; i < vm->module_cache.cap; i++) {
544
13.7k
        if (vm->module_cache.entries[i].state == MAP_OCCUPIED) {
545
81
            LatValue *v = (LatValue *)vm->module_cache.entries[i].value;
546
81
            value_free(v);
547
81
        }
548
13.7k
    }
549
862
    lat_map_free(&vm->module_cache);
550
551
    /* Phase system arrays are owned by the runtime — don't free here */
552
553
862
    bump_arena_free(vm->ephemeral);
554
555
    /* Free call wrapper (inline Chunk, not heap-allocated) */
556
862
    free(vm->call_wrapper.code);
557
862
    free(vm->call_wrapper.lines);
558
862
}
559
560
0
void stackvm_print_stack_trace(StackVM *vm) {
561
0
    if (vm->frame_count <= 1) return;  /* No trace for top-level errors */
562
0
    fprintf(stderr, "stack trace (most recent call last):\n");
563
0
    for (size_t i = 0; i < vm->frame_count; i++) {
564
0
        StackCallFrame *f = &vm->frames[i];
565
0
        if (!f->chunk) continue;
566
0
        size_t offset = (size_t)(f->ip - f->chunk->code);
567
0
        if (offset > 0) offset--;
568
0
        int line = 0;
569
0
        if (f->chunk->lines && offset < f->chunk->lines_len)
570
0
            line = f->chunk->lines[offset];
571
0
        const char *name = f->chunk->name;
572
0
        if (name && name[0])
573
0
            fprintf(stderr, "  [line %d] in %s()\n", line, name);
574
0
        else if (i == 0)
575
0
            fprintf(stderr, "  [line %d] in <script>\n", line);
576
0
        else
577
0
            fprintf(stderr, "  [line %d] in <closure>\n", line);
578
0
    }
579
0
}
580
581
/* ── Concurrency infrastructure ── */
582
583
0
void stackvm_track_chunk(StackVM *vm, Chunk *ch) {
584
0
    if (vm->fn_chunk_count >= vm->fn_chunk_cap) {
585
0
        vm->fn_chunk_cap = vm->fn_chunk_cap ? vm->fn_chunk_cap * 2 : 8;
586
0
        vm->fn_chunks = realloc(vm->fn_chunks, vm->fn_chunk_cap * sizeof(Chunk *));
587
0
    }
588
0
    vm->fn_chunks[vm->fn_chunk_count++] = ch;
589
0
}
590
591
#ifndef __EMSCRIPTEN__
592
593
typedef struct {
594
    Chunk     *chunk;       /* compiled spawn body (parent owns via fn_chunks) */
595
    StackVM        *child_vm;    /* independent StackVM for thread */
596
    char      *error;       /* NULL on success */
597
    pthread_t  thread;
598
} VMSpawnTask;
599
600
3
StackVM *stackvm_clone_for_thread(StackVM *parent) {
601
    /* Create a child runtime with cloned env + fresh phase arrays */
602
3
    LatRuntime *child_rt = calloc(1, sizeof(LatRuntime));
603
3
    child_rt->env = env_clone(parent->rt->env);
604
3
    child_rt->struct_meta = parent->rt->struct_meta; /* shared read-only */
605
3
    child_rt->script_dir = parent->rt->script_dir ? strdup(parent->rt->script_dir) : NULL;
606
3
    child_rt->prog_argc = parent->rt->prog_argc;
607
3
    child_rt->prog_argv = parent->rt->prog_argv;
608
3
    child_rt->module_cache = lat_map_new(sizeof(LatValue));
609
3
    child_rt->required_files = lat_map_new(sizeof(bool));
610
3
    child_rt->loaded_extensions = lat_map_new(sizeof(LatValue));
611
612
3
    StackVM *child = calloc(1, sizeof(StackVM));
613
3
    child->rt = child_rt;
614
3
    child->stack_top = child->stack;
615
3
    child->env = child_rt->env;
616
3
    child->error = NULL;
617
3
    child->open_upvalues = NULL;
618
3
    child->handler_count = 0;
619
3
    child->defer_count = 0;
620
3
    child->struct_meta = child_rt->struct_meta;
621
3
    child->fn_chunks = NULL;
622
3
    child->fn_chunk_count = 0;
623
3
    child->fn_chunk_cap = 0;
624
3
    child->module_cache = lat_map_new(sizeof(LatValue));
625
3
    child->ephemeral = bump_arena_new();
626
627
    /* Pre-build the call wrapper chunk */
628
3
    memset(&child->call_wrapper, 0, sizeof(Chunk));
629
3
    child->call_wrapper.code = malloc(3);
630
3
    child->call_wrapper.code[0] = OP_CALL;
631
3
    child->call_wrapper.code[1] = 0;
632
3
    child->call_wrapper.code[2] = OP_RETURN;
633
3
    child->call_wrapper.code_len = 3;
634
3
    child->call_wrapper.code_cap = 3;
635
3
    child->call_wrapper.lines = calloc(3, sizeof(int));
636
3
    child->call_wrapper.lines_len = 3;
637
3
    child->call_wrapper.lines_cap = 3;
638
639
3
    return child;
640
3
}
641
642
3
void stackvm_free_child(StackVM *child) {
643
    /* Free stack values */
644
4
    while (child->stack_top > child->stack) {
645
1
        child->stack_top--;
646
1
        value_free(child->stack_top);
647
1
    }
648
    /* env is a cached pointer to child->rt->env — freed by runtime below */
649
3
    free(child->error);
650
651
    /* Free open upvalues */
652
3
    ObjUpvalue *uv = child->open_upvalues;
653
3
    while (uv) {
654
0
        ObjUpvalue *next = uv->next;
655
0
        if (uv->location == &uv->closed)
656
0
            value_free(&uv->closed);
657
0
        free(uv);
658
0
        uv = next;
659
0
    }
660
661
    /* Free upvalue arrays in frames */
662
5
    for (size_t i = 0; i < child->frame_count; i++) {
663
2
        StackCallFrame *f = &child->frames[i];
664
2
        for (size_t j = 0; j < f->upvalue_count; j++) {
665
0
            if (f->upvalues[j] && f->upvalues[j]->location == &f->upvalues[j]->closed)
666
0
                value_free(&f->upvalues[j]->closed);
667
0
            free(f->upvalues[j]);
668
0
        }
669
2
        free(f->upvalues);
670
2
    }
671
672
    /* Free child-owned fn_chunks */
673
3
    for (size_t i = 0; i < child->fn_chunk_count; i++)
674
0
        chunk_free(child->fn_chunks[i]);
675
3
    free(child->fn_chunks);
676
677
    /* Free per-StackVM module cache */
678
3
    lat_map_free(&child->module_cache);
679
    /* struct_meta is shared — parent runtime owns it */
680
3
    bump_arena_free(child->ephemeral);
681
682
    /* Free call wrapper */
683
3
    free(child->call_wrapper.code);
684
3
    free(child->call_wrapper.lines);
685
686
    /* Free child runtime (env + caches) */
687
3
    LatRuntime *crt = child->rt;
688
3
    if (crt) {
689
3
        if (crt->env) env_free(crt->env);
690
3
        lat_map_free(&crt->module_cache);
691
3
        lat_map_free(&crt->required_files);
692
3
        lat_map_free(&crt->loaded_extensions);
693
3
        free(crt->script_dir);
694
3
        free(crt);
695
3
    }
696
697
3
    free(child);
698
3
}
699
700
/* Export current frame's live locals into child's env as globals,
701
 * so re-compiled code can access them via OP_GET_GLOBAL. */
702
3
static void stackvm_export_locals_to_env(StackVM *parent, StackVM *child) {
703
9
    for (size_t fi = 0; fi < parent->frame_count; fi++) {
704
6
        StackCallFrame *f = &parent->frames[fi];
705
6
        if (!f->chunk) continue;
706
6
        size_t local_count = (size_t)(parent->stack_top - f->slots);
707
6
        if (fi + 1 < parent->frame_count)
708
3
            local_count = (size_t)(parent->frames[fi + 1].slots - f->slots);
709
13
        for (size_t slot = 0; slot < local_count; slot++) {
710
7
            if (slot < f->chunk->local_name_cap && f->chunk->local_names[slot]) {
711
4
                env_define(child->env, f->chunk->local_names[slot],
712
4
                           value_deep_clone(&f->slots[slot]));
713
4
            }
714
7
        }
715
6
    }
716
3
}
717
718
3
static void *stackvm_spawn_thread_fn(void *arg) {
719
3
    VMSpawnTask *task = arg;
720
3
    lat_runtime_set_current(task->child_vm->rt);
721
3
    task->child_vm->rt->active_vm = task->child_vm;
722
723
    /* Set up thread-local heap */
724
3
    DualHeap *heap = dual_heap_new();
725
3
    value_set_heap(heap);
726
3
    value_set_arena(NULL);
727
728
3
    LatValue result;
729
3
    StackVMResult r = stackvm_run(task->child_vm, task->chunk, &result);
730
3
    if (r != STACKVM_OK) {
731
1
        task->error = task->child_vm->error;
732
1
        task->child_vm->error = NULL;
733
2
    } else {
734
2
        value_free(&result);
735
2
    }
736
737
3
    dual_heap_free(heap);
738
3
    return NULL;
739
3
}
740
741
#endif /* __EMSCRIPTEN__ */
742
743
/* ── Builtin method helpers ── */
744
745
/* Check if a pressure mode blocks growth (push/insert) */
746
131
static bool pressure_blocks_grow(const char *mode) {
747
131
    return mode && (strcmp(mode, "no_grow") == 0 || strcmp(mode, "no_resize") == 0);
748
131
}
749
750
/* Check if a pressure mode blocks shrinkage (pop/remove) */
751
3
static bool pressure_blocks_shrink(const char *mode) {
752
3
    return mode && (strcmp(mode, "no_shrink") == 0 || strcmp(mode, "no_resize") == 0);
753
3
}
754
755
/* Find pressure mode for a variable name, or NULL if none */
756
134
static const char *stackvm_find_pressure(StackVM *vm, const char *name) {
757
134
    if (!name) return NULL;
758
134
    for (size_t i = 0; i < vm->rt->pressure_count; i++) {
759
3
        if (strcmp(vm->rt->pressures[i].name, name) == 0)
760
3
            return vm->rt->pressures[i].mode;
761
3
    }
762
131
    return NULL;
763
134
}
764
765
/* ── Pre-computed djb2 hashes for builtin method names ── */
766
317
#define MHASH_all              0x0b885ddeu
767
530
#define MHASH_any              0x0b885e2du
768
28
#define MHASH_bytes            0x0f30b64cu
769
42
#define MHASH_chars            0x0f392d36u
770
66
#define MHASH_chunk            0x0f3981beu
771
6
#define MHASH_close            0x0f3b9a5bu
772
1.47k
#define MHASH_contains         0x42aa8264u
773
10
#define MHASH_count            0x0f3d586eu
774
8
#define MHASH_difference       0x52a92470u
775
82
#define MHASH_drop             0x7c95d91au
776
546
#define MHASH_each             0x7c961b96u
777
166
#define MHASH_ends_with        0x9079bb6au
778
688
#define MHASH_entries          0x6b84747fu
779
6
#define MHASH_enum_name        0x9f13be1au
780
162
#define MHASH_enumerate        0x9f82838bu
781
1.24k
#define MHASH_filter           0xfd7675abu
782
534
#define MHASH_find             0x7c96cb66u
783
38
#define MHASH_first            0x0f704b8du
784
104
#define MHASH_flat             0x7c96d68cu
785
14
#define MHASH_flat_map         0x022d3129u
786
800
#define MHASH_for_each         0x0f4aaefcu
787
4.91k
#define MHASH_get              0x0b887685u
788
4
#define MHASH_group_by         0xdd0fdaecu
789
970
#define MHASH_has              0x0b887a41u
790
234
#define MHASH_index_of         0x66e4af51u
791
2
#define MHASH_insert           0x04d4029au
792
10
#define MHASH_intersection     0x40c04d3cu
793
4
#define MHASH_is_empty         0xdc1854cfu
794
6
#define MHASH_is_subset        0x805437d6u
795
2
#define MHASH_is_superset      0x05f3913bu
796
4
#define MHASH_is_variant       0x443eb735u
797
158
#define MHASH_join             0x7c9915d5u
798
2.85k
#define MHASH_keys             0x7c9979c1u
799
20
#define MHASH_last             0x7c99f459u
800
5.84k
#define MHASH_len              0x0b888bc4u
801
1.25k
#define MHASH_map              0x0b888f83u
802
44
#define MHASH_max              0x0b888f8bu
803
684
#define MHASH_merge            0x0fecc3f5u
804
50
#define MHASH_min              0x0b889089u
805
14
#define MHASH_pad_left         0xf3895c84u
806
12
#define MHASH_pad_right        0x6523b4b7u
807
12
#define MHASH_payload          0x9c4949cfu
808
190
#define MHASH_pop              0x0b889e14u
809
484
#define MHASH_push             0x7c9c7ae5u
810
22
#define MHASH_recv             0x7c9d4d95u
811
554
#define MHASH_reduce           0x19279c1du
812
40
#define MHASH_add              0x0b885cceu
813
718
#define MHASH_remove           0x192c7473u
814
68
#define MHASH_remove_at        0xd988a4a7u
815
22
#define MHASH_repeat           0x192dec66u
816
162
#define MHASH_replace          0x3eef4e01u
817
186
#define MHASH_reverse          0x3f5854c1u
818
42
#define MHASH_send             0x7c9ddb4fu
819
2.84k
#define MHASH_set              0x0b88a991u
820
110
#define MHASH_slice            0x105d06d5u
821
546
#define MHASH_sort             0x7c9e066du
822
8
#define MHASH_sort_by          0xa365ac87u
823
372
#define MHASH_split            0x105f45f1u
824
256
#define MHASH_starts_with      0xf5ef8361u
825
104
#define MHASH_substring        0xcc998606u
826
56
#define MHASH_sum              0x0b88ab9au
827
12
#define MHASH_tag              0x0b88ad41u
828
88
#define MHASH_take             0x7c9e564au
829
18
#define MHASH_to_array         0xcebde966u
830
260
#define MHASH_to_lower         0xcf836790u
831
266
#define MHASH_to_upper         0xd026b2b3u
832
350
#define MHASH_trim             0x7c9e9e61u
833
16
#define MHASH_trim_end         0xcdcebb17u
834
18
#define MHASH_trim_start       0x7d6a808eu
835
12
#define MHASH_union            0x1082522eu
836
70
#define MHASH_unique           0x20cca1bcu
837
2.82k
#define MHASH_values           0x22383ff5u
838
8
#define MHASH_variant_name     0xb2b2b8bau
839
72
#define MHASH_zip              0x0b88c7d8u
840
/* Ref methods */
841
24
#define MHASH_deref            0x0f49e72bu
842
18
#define MHASH_inner_type       0xdf644222u
843
/* Buffer methods */
844
26
#define MHASH_push_u16         0x1aaf75a0u
845
24
#define MHASH_push_u32         0x1aaf75deu
846
22
#define MHASH_read_u8          0x3ddb750du
847
22
#define MHASH_write_u8         0x931616bcu
848
22
#define MHASH_read_u16         0xf94a15fcu
849
20
#define MHASH_write_u16        0xf5d8ed8bu
850
18
#define MHASH_read_u32         0xf94a163au
851
16
#define MHASH_write_u32        0xf5d8edc9u
852
12
#define MHASH_clear            0x0f3b6d8cu
853
10
#define MHASH_fill             0x7c96cb2cu
854
8
#define MHASH_resize           0x192fa5b7u
855
6
#define MHASH_to_string        0xd09c437eu
856
2
#define MHASH_to_hex           0x1e83ed8cu
857
30
#define MHASH_capacity         0x104ec913u
858
859
3.19k
static inline uint32_t method_hash(const char *s) {
860
3.19k
    uint32_t h = 5381;
861
16.3k
    while (*s) h = h * 33 + (unsigned char)*s++;
862
3.19k
    return h;
863
3.19k
}
864
865
/* Returns true if the builtin method is "simple" — no user closures executed,
866
 * safe for direct-pointer mutation without clone-mutate-writeback. */
867
209
static inline bool stackvm_invoke_builtin_is_simple(uint32_t mhash) {
868
209
    return !(mhash == MHASH_map || mhash == MHASH_filter ||
869
209
             mhash == MHASH_reduce || mhash == MHASH_each ||
870
209
             mhash == MHASH_sort || mhash == MHASH_find ||
871
209
             mhash == MHASH_any || mhash == MHASH_all);
872
209
}
873
874
2.98k
static bool stackvm_invoke_builtin(StackVM *vm, LatValue *obj, const char *method, int arg_count, const char *var_name) {
875
2.98k
    uint32_t mhash = method_hash(method);
876
877
2.98k
    switch (obj->type) {
878
    /* Array methods */
879
247
    case VAL_ARRAY: {
880
247
        if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
881
24
            push(vm, value_int((int64_t)obj->as.array.len));
882
24
            return true;
883
24
        }
884
223
        if (mhash == MHASH_push && strcmp(method, "push") == 0 && arg_count == 1) {
885
            /* Check phase: crystal and sublimated values are immutable */
886
131
            if (obj->phase == VTAG_CRYSTAL || obj->phase == VTAG_SUBLIMATED) {
887
1
                LatValue val = pop(vm);
888
1
                value_free(&val);
889
1
                const char *phase_name = obj->phase == VTAG_CRYSTAL ? "crystal" : "sublimated";
890
1
                char *err = NULL;
891
1
                (void)asprintf(&err, "cannot push to %s array", phase_name);
892
1
                vm->error = err;
893
1
                push(vm, value_unit());
894
1
                return true;
895
1
            }
896
            /* Check pressure constraint */
897
130
            const char *pmode = stackvm_find_pressure(vm, var_name);
898
130
            if (pressure_blocks_grow(pmode)) {
899
2
                LatValue val = pop(vm);
900
2
                value_free(&val);
901
2
                char *err = NULL;
902
2
                (void)asprintf(&err, "pressurized (%s): cannot push to '%s'", pmode, var_name);
903
2
                vm->error = err;
904
2
                push(vm, value_unit());
905
2
                return true;
906
2
            }
907
128
            LatValue val = pop(vm);
908
128
            stackvm_promote_value(&val);
909
            /* Mutate the array in-place */
910
128
            if (obj->as.array.len >= obj->as.array.cap) {
911
7
                obj->as.array.cap = obj->as.array.cap ? obj->as.array.cap * 2 : 4;
912
7
                obj->as.array.elems = realloc(obj->as.array.elems,
913
7
                    obj->as.array.cap * sizeof(LatValue));
914
7
            }
915
128
            obj->as.array.elems[obj->as.array.len++] = val;
916
128
            push(vm, value_unit());
917
128
            return true;
918
130
        }
919
92
        if (mhash == MHASH_pop && strcmp(method, "pop") == 0 && arg_count == 0) {
920
            /* Check phase: crystal and sublimated values are immutable */
921
3
            if (obj->phase == VTAG_CRYSTAL || obj->phase == VTAG_SUBLIMATED) {
922
1
                const char *phase_name = obj->phase == VTAG_CRYSTAL ? "crystal" : "sublimated";
923
1
                char *err = NULL;
924
1
                (void)asprintf(&err, "cannot pop from %s array", phase_name);
925
1
                vm->error = err;
926
1
                push(vm, value_unit());
927
1
                return true;
928
1
            }
929
            /* Check pressure constraint */
930
2
            const char *pmode = stackvm_find_pressure(vm, var_name);
931
2
            if (pressure_blocks_shrink(pmode)) {
932
1
                char *err = NULL;
933
1
                (void)asprintf(&err, "pressurized (%s): cannot pop from '%s'", pmode, var_name);
934
1
                vm->error = err;
935
1
                push(vm, value_unit());
936
1
                return true;
937
1
            }
938
1
            if (obj->as.array.len == 0) {
939
0
                push(vm, value_nil());
940
1
            } else {
941
1
                push(vm, obj->as.array.elems[--obj->as.array.len]);
942
1
            }
943
1
            return true;
944
2
        }
945
89
        if (mhash == MHASH_contains && strcmp(method, "contains") == 0 && arg_count == 1) {
946
8
            LatValue needle = pop(vm);
947
8
            bool found = false;
948
2.61k
            for (size_t i = 0; i < obj->as.array.len; i++) {
949
2.61k
                if (value_eq(&obj->as.array.elems[i], &needle)) {
950
6
                    found = true;
951
6
                    break;
952
6
                }
953
2.61k
            }
954
8
            value_free(&needle);
955
8
            push(vm, value_bool(found));
956
8
            return true;
957
8
        }
958
81
        if (mhash == MHASH_enumerate && strcmp(method, "enumerate") == 0 && arg_count == 0) {
959
            /* Build array of [index, element] pairs */
960
1
            LatValue *pairs = malloc(obj->as.array.len * sizeof(LatValue));
961
4
            for (size_t i = 0; i < obj->as.array.len; i++) {
962
3
                LatValue pair_elems[2];
963
3
                pair_elems[0] = value_int((int64_t)i);
964
3
                pair_elems[1] = value_clone_fast(&obj->as.array.elems[i]);
965
3
                pairs[i] = value_array(pair_elems, 2);
966
3
            }
967
1
            LatValue result = value_array(pairs, obj->as.array.len);
968
1
            free(pairs);
969
1
            push(vm, result);
970
1
            return true;
971
1
        }
972
80
        if (mhash == MHASH_reverse && strcmp(method, "reverse") == 0 && arg_count == 0) {
973
1
            LatValue *elems = malloc(obj->as.array.len * sizeof(LatValue));
974
4
            for (size_t i = 0; i < obj->as.array.len; i++)
975
3
                elems[i] = value_deep_clone(&obj->as.array.elems[obj->as.array.len - 1 - i]);
976
1
            LatValue result = value_array(elems, obj->as.array.len);
977
1
            free(elems);
978
1
            push(vm, result);
979
1
            return true;
980
1
        }
981
79
        if (mhash == MHASH_join && strcmp(method, "join") == 0 && arg_count == 1) {
982
1
            LatValue sep = pop(vm);
983
1
            const char *sep_str = (sep.type == VAL_STR) ? sep.as.str_val : "";
984
1
            size_t sep_len = strlen(sep_str);
985
1
            size_t n = obj->as.array.len;
986
1
            char **parts = malloc(n * sizeof(char *));
987
1
            size_t *lens = malloc(n * sizeof(size_t));
988
1
            size_t total = 0;
989
5
            for (size_t i = 0; i < n; i++) {
990
4
                parts[i] = value_display(&obj->as.array.elems[i]);
991
4
                lens[i] = strlen(parts[i]);
992
4
                total += lens[i];
993
4
            }
994
1
            if (n > 1) total += sep_len * (n - 1);
995
1
            char *buf = malloc(total + 1);
996
1
            size_t pos = 0;
997
5
            for (size_t i = 0; i < n; i++) {
998
4
                if (i > 0) { memcpy(buf + pos, sep_str, sep_len); pos += sep_len; }
999
4
                memcpy(buf + pos, parts[i], lens[i]); pos += lens[i];
1000
4
                free(parts[i]);
1001
4
            }
1002
1
            buf[pos] = '\0';
1003
1
            free(parts); free(lens);
1004
1
            value_free(&sep);
1005
1
            push(vm, value_string_owned(buf));
1006
1
            return true;
1007
1
        }
1008
78
        if (mhash == MHASH_map && strcmp(method, "map") == 0 && arg_count == 1) {
1009
7
            LatValue closure = pop(vm);
1010
7
            size_t len = obj->as.array.len;
1011
7
            LatValue *elems = malloc(len * sizeof(LatValue));
1012
139
            for (size_t i = 0; i < len; i++) {
1013
132
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1014
132
                elems[i] = stackvm_call_closure(vm, &closure, &arg, 1);
1015
132
                value_free(&arg);
1016
132
            }
1017
7
            LatValue result = value_array(elems, len);
1018
7
            free(elems);
1019
7
            value_free(&closure);
1020
7
            push(vm, result);
1021
7
            return true;
1022
7
        }
1023
71
        if (mhash == MHASH_filter && strcmp(method, "filter") == 0 && arg_count == 1) {
1024
3
            LatValue closure = pop(vm);
1025
3
            size_t len = obj->as.array.len;
1026
3
            size_t cap = len > 0 ? len : 1;
1027
3
            LatValue *elems = malloc(cap * sizeof(LatValue));
1028
3
            size_t out_len = 0;
1029
75
            for (size_t i = 0; i < len; i++) {
1030
72
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1031
72
                LatValue pred = stackvm_call_closure(vm, &closure, &arg, 1);
1032
72
                bool keep = (pred.type == VAL_BOOL && pred.as.bool_val);
1033
72
                value_free(&pred);
1034
72
                if (keep) {
1035
33
                    elems[out_len++] = arg;
1036
39
                } else {
1037
39
                    value_free(&arg);
1038
39
                }
1039
72
            }
1040
3
            LatValue result = value_array(elems, out_len);
1041
3
            free(elems);
1042
3
            value_free(&closure);
1043
3
            push(vm, result);
1044
3
            return true;
1045
3
        }
1046
68
        if (mhash == MHASH_reduce && strcmp(method, "reduce") == 0 && arg_count == 2) {
1047
4
            LatValue acc = pop(vm);       /* second arg: initial value (TOS) */
1048
4
            LatValue closure = pop(vm);   /* first arg: closure */
1049
4
            size_t len = obj->as.array.len;
1050
14
            for (size_t i = 0; i < len; i++) {
1051
10
                LatValue elem = value_deep_clone(&obj->as.array.elems[i]);
1052
10
                LatValue args[2] = { acc, elem };
1053
10
                acc = stackvm_call_closure(vm, &closure, args, 2);
1054
10
                value_free(&args[0]);
1055
10
                value_free(&args[1]);
1056
10
            }
1057
4
            value_free(&closure);
1058
4
            push(vm, acc);
1059
4
            return true;
1060
4
        }
1061
64
        if (mhash == MHASH_each && strcmp(method, "each") == 0 && arg_count == 1) {
1062
0
            LatValue closure = pop(vm);
1063
0
            size_t len = obj->as.array.len;
1064
0
            for (size_t i = 0; i < len; i++) {
1065
0
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1066
0
                LatValue r = stackvm_call_closure(vm, &closure, &arg, 1);
1067
0
                value_free(&arg);
1068
0
                value_free(&r);
1069
0
            }
1070
0
            value_free(&closure);
1071
0
            push(vm, value_nil());
1072
0
            return true;
1073
0
        }
1074
64
        if (mhash == MHASH_sort && strcmp(method, "sort") == 0 && arg_count <= 1) {
1075
5
            LatValue closure;
1076
5
            bool has_cmp = (arg_count == 1);
1077
5
            if (has_cmp) closure = pop(vm);
1078
1079
            /* Deep clone the array for sorting */
1080
5
            size_t len = obj->as.array.len;
1081
5
            LatValue *elems = malloc(len * sizeof(LatValue));
1082
16
            for (size_t i = 0; i < len; i++)
1083
11
                elems[i] = value_deep_clone(&obj->as.array.elems[i]);
1084
1085
            /* Simple insertion sort (stable, fine for typical sizes) */
1086
11
            for (size_t i = 1; i < len; i++) {
1087
7
                LatValue key = elems[i];
1088
7
                size_t j = i;
1089
13
                while (j > 0) {
1090
10
                    bool should_swap;
1091
10
                    if (has_cmp) {
1092
0
                        LatValue args[2];
1093
0
                        args[0] = value_clone_fast(&elems[j - 1]);
1094
0
                        args[1] = value_clone_fast(&key);
1095
0
                        LatValue cmp = stackvm_call_closure(vm, &closure, args, 2);
1096
0
                        should_swap = (cmp.type == VAL_INT && cmp.as.int_val > 0) ||
1097
0
                                      (cmp.type == VAL_BOOL && !cmp.as.bool_val);
1098
0
                        value_free(&args[0]);
1099
0
                        value_free(&args[1]);
1100
0
                        value_free(&cmp);
1101
10
                    } else {
1102
                        /* Default: ascending for ints, floats, strings */
1103
10
                        if (elems[j - 1].type == VAL_INT && key.type == VAL_INT) {
1104
3
                            should_swap = elems[j - 1].as.int_val > key.as.int_val;
1105
7
                        } else if (elems[j - 1].type == VAL_FLOAT && key.type == VAL_FLOAT) {
1106
3
                            should_swap = elems[j - 1].as.float_val > key.as.float_val;
1107
4
                        } else if ((elems[j - 1].type == VAL_INT || elems[j - 1].type == VAL_FLOAT) &&
1108
4
                                   (key.type == VAL_INT || key.type == VAL_FLOAT)) {
1109
0
                            double a_d = elems[j - 1].type == VAL_INT ?
1110
0
                                (double)elems[j - 1].as.int_val : elems[j - 1].as.float_val;
1111
0
                            double b_d = key.type == VAL_INT ? (double)key.as.int_val : key.as.float_val;
1112
0
                            should_swap = a_d > b_d;
1113
4
                        } else if (elems[j - 1].type == VAL_STR && key.type == VAL_STR) {
1114
3
                            should_swap = strcmp(elems[j - 1].as.str_val, key.as.str_val) > 0;
1115
3
                        } else {
1116
                            /* Mixed non-numeric types — error */
1117
3
                            for (size_t fi = 0; fi < len; fi++) value_free(&elems[fi]);
1118
1
                            free(elems);
1119
1
                            vm->error = strdup("sort: cannot compare mixed types");
1120
1
                            push(vm, value_unit());
1121
1
                            return true;
1122
1
                        }
1123
10
                    }
1124
9
                    if (!should_swap) break;
1125
6
                    elems[j] = elems[j - 1];
1126
6
                    j--;
1127
6
                }
1128
6
                elems[j] = key;
1129
6
            }
1130
1131
4
            LatValue result = value_array(elems, len);
1132
4
            free(elems);
1133
4
            if (has_cmp) value_free(&closure);
1134
4
            push(vm, result);
1135
4
            return true;
1136
5
        }
1137
59
        if (mhash == MHASH_for_each && strcmp(method, "for_each") == 0 && arg_count == 1) {
1138
1
            LatValue closure = pop(vm);
1139
1
            size_t len = obj->as.array.len;
1140
4
            for (size_t i = 0; i < len; i++) {
1141
3
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1142
3
                LatValue r = stackvm_call_closure(vm, &closure, &arg, 1);
1143
3
                value_free(&arg); value_free(&r);
1144
3
            }
1145
1
            value_free(&closure);
1146
1
            push(vm, value_unit());
1147
1
            return true;
1148
1
        }
1149
58
        if (mhash == MHASH_find && strcmp(method, "find") == 0 && arg_count == 1) {
1150
2
            LatValue closure = pop(vm);
1151
8
            for (size_t i = 0; i < obj->as.array.len; i++) {
1152
7
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1153
7
                LatValue pred = stackvm_call_closure(vm, &closure, &arg, 1);
1154
7
                bool match = (pred.type == VAL_BOOL && pred.as.bool_val);
1155
7
                value_free(&arg); value_free(&pred);
1156
7
                if (match) {
1157
1
                    value_free(&closure);
1158
1
                    push(vm, value_clone_fast(&obj->as.array.elems[i]));
1159
1
                    return true;
1160
1
                }
1161
7
            }
1162
1
            value_free(&closure);
1163
1
            push(vm, value_unit());
1164
1
            return true;
1165
2
        }
1166
56
        if (mhash == MHASH_any && strcmp(method, "any") == 0 && arg_count == 1) {
1167
2
            LatValue closure = pop(vm);
1168
7
            for (size_t i = 0; i < obj->as.array.len; i++) {
1169
6
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1170
6
                LatValue pred = stackvm_call_closure(vm, &closure, &arg, 1);
1171
6
                bool match = (pred.type == VAL_BOOL && pred.as.bool_val);
1172
6
                value_free(&arg); value_free(&pred);
1173
6
                if (match) { value_free(&closure); push(vm, value_bool(true)); return true; }
1174
6
            }
1175
1
            value_free(&closure); push(vm, value_bool(false)); return true;
1176
2
        }
1177
54
        if (mhash == MHASH_all && strcmp(method, "all") == 0 && arg_count == 1) {
1178
2
            LatValue closure = pop(vm);
1179
5
            for (size_t i = 0; i < obj->as.array.len; i++) {
1180
4
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1181
4
                LatValue pred = stackvm_call_closure(vm, &closure, &arg, 1);
1182
4
                bool match = (pred.type == VAL_BOOL && pred.as.bool_val);
1183
4
                value_free(&arg); value_free(&pred);
1184
4
                if (!match) { value_free(&closure); push(vm, value_bool(false)); return true; }
1185
4
            }
1186
1
            value_free(&closure); push(vm, value_bool(true)); return true;
1187
2
        }
1188
52
        if (mhash == MHASH_flat && strcmp(method, "flat") == 0 && arg_count == 0) {
1189
4
            push(vm, array_flat(obj));
1190
4
            return true;
1191
4
        }
1192
48
        if (mhash == MHASH_slice && strcmp(method, "slice") == 0 && arg_count == 2) {
1193
4
            LatValue end_v = pop(vm); LatValue start_v = pop(vm);
1194
4
            char *err = NULL;
1195
4
            LatValue r = array_slice(obj, start_v.as.int_val, end_v.as.int_val, &err);
1196
4
            value_free(&start_v); value_free(&end_v);
1197
4
            if (err) { free(err); push(vm, value_array(NULL, 0)); }
1198
4
            else push(vm, r);
1199
4
            return true;
1200
4
        }
1201
44
        if (mhash == MHASH_take && strcmp(method, "take") == 0 && arg_count == 1) {
1202
3
            LatValue n_v = pop(vm);
1203
3
            int64_t n = n_v.as.int_val;
1204
3
            value_free(&n_v);
1205
3
            if (n <= 0) { push(vm, value_array(NULL, 0)); return true; }
1206
2
            size_t take_n = (size_t)n;
1207
2
            if (take_n > obj->as.array.len) take_n = obj->as.array.len;
1208
2
            LatValue *elems = malloc((take_n > 0 ? take_n : 1) * sizeof(LatValue));
1209
7
            for (size_t i = 0; i < take_n; i++)
1210
5
                elems[i] = value_deep_clone(&obj->as.array.elems[i]);
1211
2
            LatValue r = value_array(elems, take_n); free(elems);
1212
2
            push(vm, r); return true;
1213
3
        }
1214
41
        if (mhash == MHASH_drop && strcmp(method, "drop") == 0 && arg_count == 1) {
1215
3
            LatValue n_v = pop(vm);
1216
3
            int64_t n = n_v.as.int_val;
1217
3
            value_free(&n_v);
1218
3
            size_t start = (n > 0) ? (size_t)n : 0;
1219
3
            if (start >= obj->as.array.len) { push(vm, value_array(NULL, 0)); return true; }
1220
2
            size_t cnt = obj->as.array.len - start;
1221
2
            LatValue *elems = malloc(cnt * sizeof(LatValue));
1222
8
            for (size_t i = 0; i < cnt; i++)
1223
6
                elems[i] = value_deep_clone(&obj->as.array.elems[start + i]);
1224
2
            LatValue r = value_array(elems, cnt); free(elems);
1225
2
            push(vm, r); return true;
1226
3
        }
1227
38
        if (mhash == MHASH_index_of && strcmp(method, "index_of") == 0 && arg_count == 1) {
1228
2
            LatValue needle = pop(vm);
1229
2
            int64_t idx = -1;
1230
6
            for (size_t i = 0; i < obj->as.array.len; i++) {
1231
5
                if (value_eq(&obj->as.array.elems[i], &needle)) { idx = (int64_t)i; break; }
1232
5
            }
1233
2
            value_free(&needle);
1234
2
            push(vm, value_int(idx)); return true;
1235
2
        }
1236
36
        if (mhash == MHASH_zip && strcmp(method, "zip") == 0 && arg_count == 1) {
1237
1
            LatValue other = pop(vm);
1238
1
            if (other.type != VAL_ARRAY) { value_free(&other); push(vm, value_nil()); return true; }
1239
1
            size_t n = obj->as.array.len < other.as.array.len ? obj->as.array.len : other.as.array.len;
1240
1
            LatValue *pairs = malloc((n > 0 ? n : 1) * sizeof(LatValue));
1241
3
            for (size_t i = 0; i < n; i++) {
1242
2
                LatValue pe[2];
1243
2
                pe[0] = value_deep_clone(&obj->as.array.elems[i]);
1244
2
                pe[1] = value_deep_clone(&other.as.array.elems[i]);
1245
2
                pairs[i] = value_array(pe, 2);
1246
2
            }
1247
1
            value_free(&other);
1248
1
            LatValue r = value_array(pairs, n); free(pairs);
1249
1
            push(vm, r); return true;
1250
1
        }
1251
35
        if (mhash == MHASH_unique && strcmp(method, "unique") == 0 && arg_count == 0) {
1252
1
            size_t n = obj->as.array.len;
1253
1
            LatValue *res = malloc((n > 0 ? n : 1) * sizeof(LatValue));
1254
1
            size_t rc = 0;
1255
7
            for (size_t i = 0; i < n; i++) {
1256
6
                bool dup = false;
1257
13
                for (size_t j = 0; j < rc; j++)
1258
9
                    if (value_eq(&obj->as.array.elems[i], &res[j])) { dup = true; break; }
1259
6
                if (!dup) res[rc++] = value_deep_clone(&obj->as.array.elems[i]);
1260
6
            }
1261
1
            LatValue r = value_array(res, rc); free(res);
1262
1
            push(vm, r); return true;
1263
1
        }
1264
34
        if (mhash == MHASH_remove_at && strcmp(method, "remove_at") == 0 && arg_count == 1) {
1265
1
            const char *pmode = stackvm_find_pressure(vm, var_name);
1266
1
            if (pressure_blocks_shrink(pmode)) {
1267
0
                LatValue idx_v = pop(vm); value_free(&idx_v);
1268
0
                char *err = NULL;
1269
0
                (void)asprintf(&err, "pressurized (%s): cannot remove_at from '%s'", pmode, var_name);
1270
0
                vm->error = err;
1271
0
                push(vm, value_unit()); return true;
1272
0
            }
1273
1
            LatValue idx_v = pop(vm);
1274
1
            int64_t idx = idx_v.as.int_val;
1275
1
            value_free(&idx_v);
1276
1
            if (idx < 0 || (size_t)idx >= obj->as.array.len) { push(vm, value_nil()); return true; }
1277
1
            LatValue removed = obj->as.array.elems[(size_t)idx];
1278
1
            memmove(&obj->as.array.elems[(size_t)idx],
1279
1
                    &obj->as.array.elems[(size_t)idx + 1],
1280
1
                    (obj->as.array.len - (size_t)idx - 1) * sizeof(LatValue));
1281
1
            obj->as.array.len--;
1282
1
            push(vm, removed);
1283
1
            return true;
1284
1
        }
1285
33
        if (mhash == MHASH_chunk && strcmp(method, "chunk") == 0 && arg_count == 1) {
1286
5
            LatValue cs_v = pop(vm);
1287
5
            int64_t cs = cs_v.as.int_val;
1288
5
            value_free(&cs_v);
1289
5
            if (cs <= 0) { push(vm, value_array(NULL, 0)); return true; }
1290
5
            size_t n = obj->as.array.len;
1291
5
            size_t nc = (n > 0) ? (n + (size_t)cs - 1) / (size_t)cs : 0;
1292
5
            LatValue *chunks = malloc((nc > 0 ? nc : 1) * sizeof(LatValue));
1293
14
            for (size_t ci = 0; ci < nc; ci++) {
1294
9
                size_t s = ci * (size_t)cs, e = s + (size_t)cs;
1295
9
                if (e > n) e = n;
1296
9
                size_t cl = e - s;
1297
9
                LatValue *ce = malloc(cl * sizeof(LatValue));
1298
25
                for (size_t j = 0; j < cl; j++)
1299
16
                    ce[j] = value_deep_clone(&obj->as.array.elems[s + j]);
1300
9
                chunks[ci] = value_array(ce, cl); free(ce);
1301
9
            }
1302
5
            LatValue r = value_array(chunks, nc); free(chunks);
1303
5
            push(vm, r); return true;
1304
5
        }
1305
28
        if (mhash == MHASH_sum && strcmp(method, "sum") == 0 && arg_count == 0) {
1306
3
            bool has_float = false;
1307
3
            int64_t isum = 0; double fsum = 0.0;
1308
11
            for (size_t i = 0; i < obj->as.array.len; i++) {
1309
8
                if (obj->as.array.elems[i].type == VAL_INT) {
1310
5
                    isum += obj->as.array.elems[i].as.int_val;
1311
5
                    fsum += (double)obj->as.array.elems[i].as.int_val;
1312
5
                } else if (obj->as.array.elems[i].type == VAL_FLOAT) {
1313
3
                    has_float = true;
1314
3
                    fsum += obj->as.array.elems[i].as.float_val;
1315
3
                }
1316
8
            }
1317
3
            push(vm, has_float ? value_float(fsum) : value_int(isum));
1318
3
            return true;
1319
3
        }
1320
25
        if (mhash == MHASH_min && strcmp(method, "min") == 0 && arg_count == 0) {
1321
3
            if (obj->as.array.len == 0) { vm->error = strdup("min() called on empty array"); push(vm, value_nil()); return true; }
1322
3
            bool hf = false;
1323
10
            for (size_t i = 0; i < obj->as.array.len; i++)
1324
8
                if (obj->as.array.elems[i].type == VAL_FLOAT) hf = true;
1325
2
            if (hf) {
1326
1
                double fm = (obj->as.array.elems[0].type == VAL_FLOAT)
1327
1
                    ? obj->as.array.elems[0].as.float_val : (double)obj->as.array.elems[0].as.int_val;
1328
3
                for (size_t i = 1; i < obj->as.array.len; i++) {
1329
2
                    double v = (obj->as.array.elems[i].type == VAL_FLOAT)
1330
2
                        ? obj->as.array.elems[i].as.float_val : (double)obj->as.array.elems[i].as.int_val;
1331
2
                    if (v < fm) fm = v;
1332
2
                }
1333
1
                push(vm, value_float(fm));
1334
1
            } else {
1335
1
                int64_t im = obj->as.array.elems[0].as.int_val;
1336
5
                for (size_t i = 1; i < obj->as.array.len; i++)
1337
4
                    if (obj->as.array.elems[i].as.int_val < im) im = obj->as.array.elems[i].as.int_val;
1338
1
                push(vm, value_int(im));
1339
1
            }
1340
2
            return true;
1341
3
        }
1342
22
        if (mhash == MHASH_max && strcmp(method, "max") == 0 && arg_count == 0) {
1343
3
            if (obj->as.array.len == 0) { vm->error = strdup("max() called on empty array"); push(vm, value_nil()); return true; }
1344
3
            bool hf = false;
1345
10
            for (size_t i = 0; i < obj->as.array.len; i++)
1346
8
                if (obj->as.array.elems[i].type == VAL_FLOAT) hf = true;
1347
2
            if (hf) {
1348
1
                double fm = (obj->as.array.elems[0].type == VAL_FLOAT)
1349
1
                    ? obj->as.array.elems[0].as.float_val : (double)obj->as.array.elems[0].as.int_val;
1350
3
                for (size_t i = 1; i < obj->as.array.len; i++) {
1351
2
                    double v = (obj->as.array.elems[i].type == VAL_FLOAT)
1352
2
                        ? obj->as.array.elems[i].as.float_val : (double)obj->as.array.elems[i].as.int_val;
1353
2
                    if (v > fm) fm = v;
1354
2
                }
1355
1
                push(vm, value_float(fm));
1356
1
            } else {
1357
1
                int64_t im = obj->as.array.elems[0].as.int_val;
1358
5
                for (size_t i = 1; i < obj->as.array.len; i++)
1359
4
                    if (obj->as.array.elems[i].as.int_val > im) im = obj->as.array.elems[i].as.int_val;
1360
1
                push(vm, value_int(im));
1361
1
            }
1362
2
            return true;
1363
3
        }
1364
19
        if (mhash == MHASH_first && strcmp(method, "first") == 0 && arg_count == 0) {
1365
9
            push(vm, obj->as.array.len > 0 ? value_deep_clone(&obj->as.array.elems[0]) : value_unit());
1366
9
            return true;
1367
9
        }
1368
10
        if (mhash == MHASH_last && strcmp(method, "last") == 0 && arg_count == 0) {
1369
3
            push(vm, obj->as.array.len > 0 ? value_deep_clone(&obj->as.array.elems[obj->as.array.len - 1]) : value_unit());
1370
3
            return true;
1371
3
        }
1372
7
        if (mhash == MHASH_flat_map && strcmp(method, "flat_map") == 0 && arg_count == 1) {
1373
3
            LatValue closure = pop(vm);
1374
3
            size_t n = obj->as.array.len;
1375
3
            LatValue *mapped = malloc((n > 0 ? n : 1) * sizeof(LatValue));
1376
9
            for (size_t i = 0; i < n; i++) {
1377
6
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1378
6
                mapped[i] = stackvm_call_closure(vm, &closure, &arg, 1);
1379
6
                value_free(&arg);
1380
6
            }
1381
3
            size_t total = 0;
1382
9
            for (size_t i = 0; i < n; i++)
1383
6
                total += (mapped[i].type == VAL_ARRAY) ? mapped[i].as.array.len : 1;
1384
3
            LatValue *buf = malloc((total > 0 ? total : 1) * sizeof(LatValue));
1385
3
            size_t pos = 0;
1386
9
            for (size_t i = 0; i < n; i++) {
1387
6
                if (mapped[i].type == VAL_ARRAY) {
1388
9
                    for (size_t j = 0; j < mapped[i].as.array.len; j++)
1389
6
                        buf[pos++] = mapped[i].as.array.elems[j];
1390
3
                    mapped[i].as.array.len = 0; /* prevent double-free of moved elements */
1391
3
                } else {
1392
3
                    buf[pos++] = mapped[i];
1393
3
                    mapped[i].type = VAL_NIL; /* prevent double-free */
1394
3
                }
1395
6
            }
1396
9
            for (size_t i = 0; i < n; i++) value_free(&mapped[i]);
1397
3
            free(mapped);
1398
3
            value_free(&closure);
1399
3
            LatValue r = value_array(buf, pos); free(buf);
1400
3
            push(vm, r); return true;
1401
3
        }
1402
4
        if (mhash == MHASH_sort_by && strcmp(method, "sort_by") == 0 && arg_count == 1) {
1403
2
            LatValue closure = pop(vm);
1404
2
            size_t n = obj->as.array.len;
1405
2
            LatValue *buf = malloc((n > 0 ? n : 1) * sizeof(LatValue));
1406
12
            for (size_t i = 0; i < n; i++)
1407
10
                buf[i] = value_deep_clone(&obj->as.array.elems[i]);
1408
10
            for (size_t i = 1; i < n; i++) {
1409
8
                LatValue key = buf[i];
1410
8
                size_t j = i;
1411
17
                while (j > 0) {
1412
14
                    LatValue ca[2];
1413
14
                    ca[0] = value_clone_fast(&key);
1414
14
                    ca[1] = value_clone_fast(&buf[j - 1]);
1415
14
                    LatValue cmp = stackvm_call_closure(vm, &closure, ca, 2);
1416
14
                    value_free(&ca[0]); value_free(&ca[1]);
1417
14
                    if (cmp.type != VAL_INT || cmp.as.int_val >= 0) { value_free(&cmp); break; }
1418
9
                    value_free(&cmp);
1419
9
                    buf[j] = buf[j - 1]; j--;
1420
9
                }
1421
8
                buf[j] = key;
1422
8
            }
1423
2
            value_free(&closure);
1424
2
            LatValue r = value_array(buf, n); free(buf);
1425
2
            push(vm, r); return true;
1426
2
        }
1427
2
        if (mhash == MHASH_group_by && strcmp(method, "group_by") == 0 && arg_count == 1) {
1428
1
            LatValue closure = pop(vm);
1429
1
            LatValue grp = value_map_new();
1430
6
            for (size_t i = 0; i < obj->as.array.len; i++) {
1431
5
                LatValue arg = value_deep_clone(&obj->as.array.elems[i]);
1432
5
                LatValue key_v = stackvm_call_closure(vm, &closure, &arg, 1);
1433
5
                value_free(&arg);
1434
5
                char *gk = value_display(&key_v);
1435
5
                value_free(&key_v);
1436
5
                LatValue *existing = lat_map_get(grp.as.map.map, gk);
1437
5
                if (existing) {
1438
3
                    size_t ol = existing->as.array.len;
1439
3
                    LatValue *ne = malloc((ol + 1) * sizeof(LatValue));
1440
7
                    for (size_t j = 0; j < ol; j++)
1441
4
                        ne[j] = value_deep_clone(&existing->as.array.elems[j]);
1442
3
                    ne[ol] = value_deep_clone(&obj->as.array.elems[i]);
1443
3
                    LatValue na = value_array(ne, ol + 1); free(ne);
1444
3
                    lat_map_set(grp.as.map.map, gk, &na);
1445
3
                } else {
1446
2
                    LatValue cl = value_deep_clone(&obj->as.array.elems[i]);
1447
2
                    LatValue na = value_array(&cl, 1);
1448
2
                    lat_map_set(grp.as.map.map, gk, &na);
1449
2
                }
1450
5
                free(gk);
1451
5
            }
1452
1
            value_free(&closure);
1453
1
            push(vm, grp); return true;
1454
1
        }
1455
1
        if (mhash == MHASH_insert && strcmp(method, "insert") == 0 && arg_count == 2) {
1456
1
            const char *pmode = stackvm_find_pressure(vm, var_name);
1457
1
            if (pressure_blocks_grow(pmode)) {
1458
0
                LatValue val = pop(vm); LatValue idx_v = pop(vm);
1459
0
                value_free(&val); value_free(&idx_v);
1460
0
                char *err = NULL;
1461
0
                (void)asprintf(&err, "pressurized (%s): cannot insert into '%s'", pmode, var_name);
1462
0
                vm->error = err;
1463
0
                push(vm, value_unit()); return true;
1464
0
            }
1465
1
            LatValue val = pop(vm); LatValue idx_v = pop(vm);
1466
1
            int64_t idx = idx_v.as.int_val;
1467
1
            value_free(&idx_v);
1468
1
            if (idx < 0 || (size_t)idx > obj->as.array.len) {
1469
0
                value_free(&val);
1470
0
                vm->error = strdup(".insert() index out of bounds");
1471
0
                push(vm, value_unit()); return true;
1472
0
            }
1473
            /* Grow if needed */
1474
1
            if (obj->as.array.len >= obj->as.array.cap) {
1475
0
                size_t new_cap = obj->as.array.cap < 4 ? 4 : obj->as.array.cap * 2;
1476
0
                obj->as.array.elems = realloc(obj->as.array.elems, new_cap * sizeof(LatValue));
1477
0
                obj->as.array.cap = new_cap;
1478
0
            }
1479
            /* Shift elements right */
1480
1
            memmove(&obj->as.array.elems[(size_t)idx + 1],
1481
1
                    &obj->as.array.elems[(size_t)idx],
1482
1
                    (obj->as.array.len - (size_t)idx) * sizeof(LatValue));
1483
1
            obj->as.array.elems[(size_t)idx] = val;
1484
1
            obj->as.array.len++;
1485
1
            push(vm, value_unit()); return true;
1486
1
        }
1487
1
    } break;
1488
1489
    /* String methods */
1490
199
    case VAL_STR: {
1491
199
        if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
1492
8
            push(vm, value_int((int64_t)strlen(obj->as.str_val)));
1493
8
            return true;
1494
8
        }
1495
191
        if (mhash == MHASH_contains && strcmp(method, "contains") == 0 && arg_count == 1) {
1496
5
            LatValue needle = pop(vm);
1497
5
            if (needle.type == VAL_STR) {
1498
5
                push(vm, value_bool(strstr(obj->as.str_val, needle.as.str_val) != NULL));
1499
5
            } else {
1500
0
                push(vm, value_bool(false));
1501
0
            }
1502
5
            value_free(&needle);
1503
5
            return true;
1504
5
        }
1505
186
        if (mhash == MHASH_split && strcmp(method, "split") == 0 && arg_count == 1) {
1506
11
            LatValue delim = pop(vm);
1507
11
            if (delim.type == VAL_STR) {
1508
                /* Split string */
1509
11
                LatValue arr = value_array(NULL, 0);
1510
11
                char *str = strdup(obj->as.str_val);
1511
11
                char *tok = strtok(str, delim.as.str_val);
1512
26
                while (tok) {
1513
15
                    LatValue elem = value_string(tok);
1514
15
                    if (arr.as.array.len >= arr.as.array.cap) {
1515
0
                        arr.as.array.cap = arr.as.array.cap ? arr.as.array.cap * 2 : 4;
1516
0
                        arr.as.array.elems = realloc(arr.as.array.elems,
1517
0
                            arr.as.array.cap * sizeof(LatValue));
1518
0
                    }
1519
15
                    arr.as.array.elems[arr.as.array.len++] = elem;
1520
15
                    tok = strtok(NULL, delim.as.str_val);
1521
15
                }
1522
11
                free(str);
1523
11
                push(vm, arr);
1524
11
            } else {
1525
0
                push(vm, value_nil());
1526
0
            }
1527
11
            value_free(&delim);
1528
11
            return true;
1529
11
        }
1530
175
        if (mhash == MHASH_trim && strcmp(method, "trim") == 0 && arg_count == 0) {
1531
42
            const char *s = obj->as.str_val;
1532
45
            while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
1533
42
            const char *e = s + strlen(s);
1534
49
            while (e > s && (e[-1] == ' ' || e[-1] == '\t' || e[-1] == '\n' || e[-1] == '\r')) e--;
1535
42
            char *trimmed = malloc((size_t)(e - s) + 1);
1536
42
            memcpy(trimmed, s, (size_t)(e - s));
1537
42
            trimmed[e - s] = '\0';
1538
42
            push(vm, value_string_owned(trimmed));
1539
42
            return true;
1540
42
        }
1541
133
        if (mhash == MHASH_to_upper && strcmp(method, "to_upper") == 0 && arg_count == 0) {
1542
3
            char *s = strdup(obj->as.str_val);
1543
24
            for (size_t i = 0; s[i]; i++)
1544
21
                if (s[i] >= 'a' && s[i] <= 'z') s[i] -= 32;
1545
3
            push(vm, value_string_owned(s));
1546
3
            return true;
1547
3
        }
1548
130
        if (mhash == MHASH_to_lower && strcmp(method, "to_lower") == 0 && arg_count == 0) {
1549
2
            char *s = strdup(obj->as.str_val);
1550
18
            for (size_t i = 0; s[i]; i++)
1551
16
                if (s[i] >= 'A' && s[i] <= 'Z') s[i] += 32;
1552
2
            push(vm, value_string_owned(s));
1553
2
            return true;
1554
2
        }
1555
128
        if (mhash == MHASH_starts_with && strcmp(method, "starts_with") == 0 && arg_count == 1) {
1556
45
            LatValue prefix = pop(vm);
1557
45
            if (prefix.type == VAL_STR) {
1558
45
                size_t plen = strlen(prefix.as.str_val);
1559
45
                push(vm, value_bool(strncmp(obj->as.str_val, prefix.as.str_val, plen) == 0));
1560
45
            } else {
1561
0
                push(vm, value_bool(false));
1562
0
            }
1563
45
            value_free(&prefix);
1564
45
            return true;
1565
45
        }
1566
83
        if (mhash == MHASH_ends_with && strcmp(method, "ends_with") == 0 && arg_count == 1) {
1567
2
            LatValue suffix = pop(vm);
1568
2
            if (suffix.type == VAL_STR) {
1569
2
                size_t slen = strlen(obj->as.str_val);
1570
2
                size_t plen = strlen(suffix.as.str_val);
1571
2
                if (plen <= slen)
1572
2
                    push(vm, value_bool(strcmp(obj->as.str_val + slen - plen, suffix.as.str_val) == 0));
1573
0
                else
1574
0
                    push(vm, value_bool(false));
1575
2
            } else {
1576
0
                push(vm, value_bool(false));
1577
0
            }
1578
2
            value_free(&suffix);
1579
2
            return true;
1580
2
        }
1581
81
        if (mhash == MHASH_replace && strcmp(method, "replace") == 0 && arg_count == 2) {
1582
2
            LatValue replacement = pop(vm);
1583
2
            LatValue pattern = pop(vm);
1584
2
            if (pattern.type == VAL_STR && replacement.type == VAL_STR) {
1585
2
                push(vm, value_string_owned(lat_str_replace(obj->as.str_val, pattern.as.str_val, replacement.as.str_val)));
1586
2
            } else {
1587
0
                push(vm, value_nil());
1588
0
            }
1589
2
            value_free(&pattern);
1590
2
            value_free(&replacement);
1591
2
            return true;
1592
2
        }
1593
79
        if (mhash == MHASH_index_of && strcmp(method, "index_of") == 0 && arg_count == 1) {
1594
27
            LatValue needle = pop(vm);
1595
27
            if (needle.type == VAL_STR)
1596
27
                push(vm, value_int(lat_str_index_of(obj->as.str_val, needle.as.str_val)));
1597
0
            else
1598
0
                push(vm, value_int(-1));
1599
27
            value_free(&needle); return true;
1600
27
        }
1601
52
        if (mhash == MHASH_substring && strcmp(method, "substring") == 0 && arg_count == 2) {
1602
31
            LatValue end_v = pop(vm); LatValue start_v = pop(vm);
1603
31
            int64_t s = (start_v.type == VAL_INT) ? start_v.as.int_val : 0;
1604
31
            int64_t e = (end_v.type == VAL_INT) ? end_v.as.int_val : (int64_t)strlen(obj->as.str_val);
1605
31
            push(vm, value_string_owned(lat_str_substring(obj->as.str_val, s, e)));
1606
31
            value_free(&start_v); value_free(&end_v); return true;
1607
31
        }
1608
21
        if (mhash == MHASH_chars && strcmp(method, "chars") == 0 && arg_count == 0) {
1609
7
            size_t slen = strlen(obj->as.str_val);
1610
7
            LatValue *elems = malloc((slen > 0 ? slen : 1) * sizeof(LatValue));
1611
94
            for (size_t i = 0; i < slen; i++) {
1612
87
                char ch[2] = { obj->as.str_val[i], '\0' };
1613
87
                elems[i] = value_string(ch);
1614
87
            }
1615
7
            LatValue r = value_array(elems, slen); free(elems);
1616
7
            push(vm, r); return true;
1617
7
        }
1618
14
        if (mhash == MHASH_bytes && strcmp(method, "bytes") == 0 && arg_count == 0) {
1619
1
            size_t slen = strlen(obj->as.str_val);
1620
1
            LatValue *elems = malloc((slen > 0 ? slen : 1) * sizeof(LatValue));
1621
4
            for (size_t i = 0; i < slen; i++)
1622
3
                elems[i] = value_int((int64_t)(unsigned char)obj->as.str_val[i]);
1623
1
            LatValue r = value_array(elems, slen); free(elems);
1624
1
            push(vm, r); return true;
1625
1
        }
1626
13
        if (mhash == MHASH_reverse && strcmp(method, "reverse") == 0 && arg_count == 0) {
1627
2
            push(vm, value_string_owned(lat_str_reverse(obj->as.str_val)));
1628
2
            return true;
1629
2
        }
1630
11
        if (mhash == MHASH_repeat && strcmp(method, "repeat") == 0 && arg_count == 1) {
1631
2
            LatValue n_v = pop(vm);
1632
2
            size_t n = (n_v.type == VAL_INT && n_v.as.int_val > 0) ? (size_t)n_v.as.int_val : 0;
1633
2
            value_free(&n_v);
1634
2
            push(vm, value_string_owned(lat_str_repeat(obj->as.str_val, n)));
1635
2
            return true;
1636
2
        }
1637
9
        if (mhash == MHASH_trim_start && strcmp(method, "trim_start") == 0 && arg_count == 0) {
1638
1
            const char *s = obj->as.str_val;
1639
3
            while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
1640
1
            push(vm, value_string(s)); return true;
1641
1
        }
1642
8
        if (mhash == MHASH_trim_end && strcmp(method, "trim_end") == 0 && arg_count == 0) {
1643
1
            const char *s = obj->as.str_val;
1644
1
            size_t slen = strlen(s);
1645
3
            while (slen > 0 && (s[slen-1] == ' ' || s[slen-1] == '\t' || s[slen-1] == '\n' || s[slen-1] == '\r')) slen--;
1646
1
            char *r = malloc(slen + 1);
1647
1
            memcpy(r, s, slen); r[slen] = '\0';
1648
1
            push(vm, value_string_owned(r)); return true;
1649
1
        }
1650
7
        if (mhash == MHASH_pad_left && strcmp(method, "pad_left") == 0 && arg_count == 2) {
1651
1
            LatValue ch_v = pop(vm); LatValue n_v = pop(vm);
1652
1
            int64_t n = (n_v.type == VAL_INT) ? n_v.as.int_val : 0;
1653
1
            char pad = (ch_v.type == VAL_STR && ch_v.as.str_val[0]) ? ch_v.as.str_val[0] : ' ';
1654
1
            value_free(&n_v); value_free(&ch_v);
1655
1
            size_t slen = strlen(obj->as.str_val);
1656
1
            if ((int64_t)slen >= n) { push(vm, value_clone_fast(obj)); return true; }
1657
1
            size_t pad_n = (size_t)n - slen;
1658
1
            char *r = malloc((size_t)n + 1);
1659
1
            memset(r, pad, pad_n);
1660
1
            memcpy(r + pad_n, obj->as.str_val, slen);
1661
1
            r[(size_t)n] = '\0';
1662
1
            push(vm, value_string_owned(r)); return true;
1663
1
        }
1664
6
        if (mhash == MHASH_pad_right && strcmp(method, "pad_right") == 0 && arg_count == 2) {
1665
1
            LatValue ch_v = pop(vm); LatValue n_v = pop(vm);
1666
1
            int64_t n = (n_v.type == VAL_INT) ? n_v.as.int_val : 0;
1667
1
            char pad = (ch_v.type == VAL_STR && ch_v.as.str_val[0]) ? ch_v.as.str_val[0] : ' ';
1668
1
            value_free(&n_v); value_free(&ch_v);
1669
1
            size_t slen = strlen(obj->as.str_val);
1670
1
            if ((int64_t)slen >= n) { push(vm, value_clone_fast(obj)); return true; }
1671
1
            size_t pad_n = (size_t)n - slen;
1672
1
            char *r = malloc((size_t)n + 1);
1673
1
            memcpy(r, obj->as.str_val, slen);
1674
1
            memset(r + slen, pad, pad_n);
1675
1
            r[(size_t)n] = '\0';
1676
1
            push(vm, value_string_owned(r)); return true;
1677
1
        }
1678
5
        if (mhash == MHASH_count && strcmp(method, "count") == 0 && arg_count == 1) {
1679
3
            LatValue needle = pop(vm);
1680
3
            int64_t cnt = 0;
1681
3
            if (needle.type == VAL_STR && needle.as.str_val[0]) {
1682
3
                const char *p = obj->as.str_val;
1683
3
                size_t nlen = strlen(needle.as.str_val);
1684
6
                while ((p = strstr(p, needle.as.str_val)) != NULL) { cnt++; p += nlen; }
1685
3
            }
1686
3
            value_free(&needle);
1687
3
            push(vm, value_int(cnt)); return true;
1688
3
        }
1689
2
        if (mhash == MHASH_is_empty && strcmp(method, "is_empty") == 0 && arg_count == 0) {
1690
2
            push(vm, value_bool(obj->as.str_val[0] == '\0'));
1691
2
            return true;
1692
2
        }
1693
2
    } break;
1694
1695
    /* Map methods */
1696
2.43k
    case VAL_MAP: {
1697
2.43k
        if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
1698
6
            push(vm, value_int((int64_t)lat_map_len(obj->as.map.map)));
1699
6
            return true;
1700
6
        }
1701
2.42k
        if (mhash == MHASH_get && strcmp(method, "get") == 0 && arg_count == 1) {
1702
1.00k
            LatValue key = pop(vm);
1703
1.00k
            if (key.type == VAL_STR) {
1704
1.00k
                LatValue *found = lat_map_get(obj->as.map.map, key.as.str_val);
1705
1.00k
                if (found)
1706
998
                    push(vm, value_deep_clone(found));
1707
3
                else
1708
3
                    push(vm, value_nil());
1709
1.00k
            } else {
1710
0
                push(vm, value_nil());
1711
0
            }
1712
1.00k
            value_free(&key);
1713
1.00k
            return true;
1714
1.00k
        }
1715
1.42k
        if (mhash == MHASH_keys && strcmp(method, "keys") == 0 && arg_count == 0) {
1716
15
            size_t len = lat_map_len(obj->as.map.map);
1717
15
            LatValue *keys = malloc((len ? len : 1) * sizeof(LatValue));
1718
15
            size_t key_idx = 0;
1719
255
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
1720
240
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) {
1721
25
                    keys[key_idx++] = value_string(obj->as.map.map->entries[i].key);
1722
25
                }
1723
240
            }
1724
15
            LatValue arr = value_array(keys, key_idx);
1725
15
            free(keys);
1726
15
            push(vm, arr);
1727
15
            return true;
1728
15
        }
1729
1.41k
        if (mhash == MHASH_values && strcmp(method, "values") == 0 && arg_count == 0) {
1730
1
            size_t len = lat_map_len(obj->as.map.map);
1731
1
            LatValue *vals = malloc((len ? len : 1) * sizeof(LatValue));
1732
1
            size_t val_idx = 0;
1733
17
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
1734
16
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) {
1735
1
                    LatValue *stored = (LatValue *)obj->as.map.map->entries[i].value;
1736
1
                    vals[val_idx++] = value_deep_clone(stored);
1737
1
                }
1738
16
            }
1739
1
            LatValue arr = value_array(vals, val_idx);
1740
1
            free(vals);
1741
1
            push(vm, arr);
1742
1
            return true;
1743
1
        }
1744
1.41k
        if (mhash == MHASH_set && strcmp(method, "set") == 0 && arg_count == 2) {
1745
            /* Check phase: crystal and sublimated values are immutable */
1746
955
            if (obj->phase == VTAG_CRYSTAL || obj->phase == VTAG_SUBLIMATED) {
1747
0
                LatValue val = pop(vm);
1748
0
                LatValue key = pop(vm);
1749
0
                value_free(&val);
1750
0
                value_free(&key);
1751
0
                const char *phase_name = obj->phase == VTAG_CRYSTAL ? "crystal" : "sublimated";
1752
0
                char *err = NULL;
1753
0
                (void)asprintf(&err, "cannot set on %s map", phase_name);
1754
0
                vm->error = err;
1755
0
                push(vm, value_unit());
1756
0
                return true;
1757
0
            }
1758
955
            LatValue val = pop(vm);
1759
955
            LatValue key = pop(vm);
1760
955
            if (key.type == VAL_STR) {
1761
955
                lat_map_set(obj->as.map.map, key.as.str_val, &val);
1762
955
            } else {
1763
0
                value_free(&val);
1764
0
            }
1765
955
            value_free(&key);
1766
955
            push(vm, value_unit());
1767
955
            return true;
1768
955
        }
1769
455
        if (mhash == MHASH_contains && strcmp(method, "contains") == 0 && arg_count == 1) {
1770
0
            LatValue key = pop(vm);
1771
0
            if (key.type == VAL_STR) {
1772
0
                push(vm, value_bool(lat_map_get(obj->as.map.map, key.as.str_val) != NULL));
1773
0
            } else {
1774
0
                push(vm, value_bool(false));
1775
0
            }
1776
0
            value_free(&key);
1777
0
            return true;
1778
0
        }
1779
455
        if (mhash == MHASH_has && strcmp(method, "has") == 0 && arg_count == 1) {
1780
111
            LatValue key = pop(vm);
1781
111
            if (key.type == VAL_STR)
1782
111
                push(vm, value_bool(lat_map_get(obj->as.map.map, key.as.str_val) != NULL));
1783
0
            else
1784
0
                push(vm, value_bool(false));
1785
111
            value_free(&key); return true;
1786
111
        }
1787
344
        if (mhash == MHASH_remove && strcmp(method, "remove") == 0 && arg_count == 1) {
1788
1
            LatValue key = pop(vm);
1789
1
            if (key.type == VAL_STR) {
1790
1
                lat_map_remove(obj->as.map.map, key.as.str_val);
1791
1
            }
1792
1
            value_free(&key);
1793
1
            push(vm, value_unit()); return true;
1794
1
        }
1795
343
        if (mhash == MHASH_entries && strcmp(method, "entries") == 0 && arg_count == 0) {
1796
1
            size_t n = lat_map_len(obj->as.map.map);
1797
1
            LatValue *entries = malloc((n > 0 ? n : 1) * sizeof(LatValue));
1798
1
            size_t ei = 0;
1799
17
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
1800
16
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) {
1801
1
                    LatValue pe[2];
1802
1
                    pe[0] = value_string(obj->as.map.map->entries[i].key);
1803
1
                    pe[1] = value_deep_clone((LatValue *)obj->as.map.map->entries[i].value);
1804
1
                    entries[ei++] = value_array(pe, 2);
1805
1
                }
1806
16
            }
1807
1
            LatValue r = value_array(entries, ei); free(entries);
1808
1
            push(vm, r); return true;
1809
1
        }
1810
342
        if (mhash == MHASH_merge && strcmp(method, "merge") == 0 && arg_count == 1) {
1811
1
            LatValue other = pop(vm);
1812
1
            if (other.type == VAL_MAP) {
1813
17
                for (size_t i = 0; i < other.as.map.map->cap; i++) {
1814
16
                    if (other.as.map.map->entries[i].state == MAP_OCCUPIED) {
1815
1
                        LatValue cloned = value_deep_clone((LatValue *)other.as.map.map->entries[i].value);
1816
1
                        lat_map_set(obj->as.map.map, other.as.map.map->entries[i].key, &cloned);
1817
1
                    }
1818
16
                }
1819
1
            }
1820
1
            value_free(&other);
1821
1
            push(vm, value_unit()); return true;
1822
1
        }
1823
341
        if (mhash == MHASH_for_each && strcmp(method, "for_each") == 0 && arg_count == 1) {
1824
1
            LatValue closure = pop(vm);
1825
17
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
1826
16
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) {
1827
1
                    LatValue ca[2];
1828
1
                    ca[0] = value_string(obj->as.map.map->entries[i].key);
1829
1
                    ca[1] = value_deep_clone((LatValue *)obj->as.map.map->entries[i].value);
1830
1
                    LatValue r = stackvm_call_closure(vm, &closure, ca, 2);
1831
1
                    value_free(&ca[0]); value_free(&ca[1]); value_free(&r);
1832
1
                }
1833
16
            }
1834
1
            value_free(&closure);
1835
1
            push(vm, value_unit()); return true;
1836
1
        }
1837
340
        if (mhash == MHASH_filter && strcmp(method, "filter") == 0 && arg_count == 1) {
1838
2
            LatValue closure = pop(vm);
1839
2
            LatValue result = value_map_new();
1840
34
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
1841
32
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) {
1842
4
                    LatValue ca[2];
1843
4
                    ca[0] = value_string(obj->as.map.map->entries[i].key);
1844
4
                    ca[1] = value_deep_clone((LatValue *)obj->as.map.map->entries[i].value);
1845
4
                    LatValue r = stackvm_call_closure(vm, &closure, ca, 2);
1846
4
                    bool keep = (r.type == VAL_BOOL && r.as.bool_val);
1847
4
                    value_free(&ca[0]); value_free(&r);
1848
4
                    if (keep) {
1849
2
                        lat_map_set(result.as.map.map, obj->as.map.map->entries[i].key, &ca[1]);
1850
2
                    } else {
1851
2
                        value_free(&ca[1]);
1852
2
                    }
1853
4
                }
1854
32
            }
1855
2
            value_free(&closure);
1856
2
            push(vm, result); return true;
1857
2
        }
1858
338
        if (mhash == MHASH_map && strcmp(method, "map") == 0 && arg_count == 1) {
1859
2
            LatValue closure = pop(vm);
1860
2
            LatValue result = value_map_new();
1861
34
            for (size_t i = 0; i < obj->as.map.map->cap; i++) {
1862
32
                if (obj->as.map.map->entries[i].state == MAP_OCCUPIED) {
1863
4
                    LatValue ca[2];
1864
4
                    ca[0] = value_string(obj->as.map.map->entries[i].key);
1865
4
                    ca[1] = value_deep_clone((LatValue *)obj->as.map.map->entries[i].value);
1866
4
                    LatValue r = stackvm_call_closure(vm, &closure, ca, 2);
1867
4
                    value_free(&ca[0]); value_free(&ca[1]);
1868
4
                    lat_map_set(result.as.map.map, obj->as.map.map->entries[i].key, &r);
1869
4
                }
1870
32
            }
1871
2
            value_free(&closure);
1872
2
            push(vm, result); return true;
1873
2
        }
1874
338
    } break;
1875
1876
    /* Struct methods */
1877
336
    case VAL_STRUCT: {
1878
10
        if (mhash == MHASH_get && strcmp(method, "get") == 0 && arg_count == 1) {
1879
0
            LatValue key = pop(vm);
1880
0
            if (key.type == VAL_STR) {
1881
0
                bool found = false;
1882
0
                for (size_t i = 0; i < obj->as.strct.field_count; i++) {
1883
0
                    if (strcmp(obj->as.strct.field_names[i], key.as.str_val) == 0) {
1884
0
                        push(vm, value_deep_clone(&obj->as.strct.field_values[i]));
1885
0
                        found = true; break;
1886
0
                    }
1887
0
                }
1888
0
                if (!found) push(vm, value_nil());
1889
0
            } else {
1890
0
                push(vm, value_nil());
1891
0
            }
1892
0
            value_free(&key); return true;
1893
0
        }
1894
        /* Struct field that is callable */
1895
20
        for (size_t i = 0; i < obj->as.strct.field_count; i++) {
1896
14
            if (strcmp(obj->as.strct.field_names[i], method) == 0) {
1897
4
                LatValue *field_val = &obj->as.strct.field_values[i];
1898
4
                if (field_val->type == VAL_CLOSURE && field_val->as.closure.native_fn) {
1899
                    /* It's a compiled function - invoke it */
1900
                    /* This will be handled by the main call path */
1901
4
                    return false;
1902
4
                }
1903
0
                return false;
1904
4
            }
1905
14
        }
1906
10
    } break;
1907
1908
    /* Range methods */
1909
6
    case VAL_RANGE: {
1910
0
        if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
1911
0
            int64_t len = obj->as.range.end - obj->as.range.start;
1912
0
            push(vm, value_int(len > 0 ? len : 0));
1913
0
            return true;
1914
0
        }
1915
0
        if (mhash == MHASH_contains && strcmp(method, "contains") == 0 && arg_count == 1) {
1916
0
            LatValue val = pop(vm);
1917
0
            if (val.type == VAL_INT) {
1918
0
                push(vm, value_bool(val.as.int_val >= obj->as.range.start &&
1919
0
                                    val.as.int_val < obj->as.range.end));
1920
0
            } else {
1921
0
                push(vm, value_bool(false));
1922
0
            }
1923
0
            value_free(&val);
1924
0
            return true;
1925
0
        }
1926
0
    } break;
1927
1928
    /* Tuple methods */
1929
1
    case VAL_TUPLE: {
1930
1
        if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
1931
1
            push(vm, value_int((int64_t)obj->as.tuple.len));
1932
1
            return true;
1933
1
        }
1934
1
    } break;
1935
1936
    /* Enum methods */
1937
6
    case VAL_ENUM: {
1938
6
        if (mhash == MHASH_tag && strcmp(method, "tag") == 0 && arg_count == 0) {
1939
0
            push(vm, value_string(obj->as.enm.variant_name));
1940
0
            return true;
1941
0
        }
1942
6
        if (mhash == MHASH_payload && strcmp(method, "payload") == 0 && arg_count == 0) {
1943
            /* Always return an array of all payloads */
1944
2
            if (obj->as.enm.payload_count > 0) {
1945
2
                LatValue *elems = malloc(obj->as.enm.payload_count * sizeof(LatValue));
1946
5
                for (size_t pi = 0; pi < obj->as.enm.payload_count; pi++)
1947
3
                    elems[pi] = value_deep_clone(&obj->as.enm.payload[pi]);
1948
2
                push(vm, value_array(elems, obj->as.enm.payload_count));
1949
2
                free(elems);
1950
2
            } else {
1951
0
                push(vm, value_array(NULL, 0));
1952
0
            }
1953
2
            return true;
1954
2
        }
1955
4
        if (mhash == MHASH_variant_name && strcmp(method, "variant_name") == 0 && arg_count == 0) {
1956
1
            push(vm, value_string(obj->as.enm.variant_name));
1957
1
            return true;
1958
1
        }
1959
3
        if (mhash == MHASH_enum_name && strcmp(method, "enum_name") == 0 && arg_count == 0) {
1960
1
            push(vm, value_string(obj->as.enm.enum_name));
1961
1
            return true;
1962
1
        }
1963
2
        if (mhash == MHASH_is_variant && strcmp(method, "is_variant") == 0 && arg_count == 1) {
1964
2
            LatValue name = pop(vm);
1965
2
            bool match = (name.type == VAL_STR && strcmp(obj->as.enm.variant_name, name.as.str_val) == 0);
1966
2
            value_free(&name);
1967
2
            push(vm, value_bool(match)); return true;
1968
2
        }
1969
2
    } break;
1970
1971
    /* ── Set methods ── */
1972
27
    case VAL_SET: {
1973
27
        if (mhash == MHASH_has && strcmp(method, "has") == 0 && arg_count == 1) {
1974
7
            LatValue val = pop(vm);
1975
7
            char *key = value_display(&val);
1976
7
            bool found = lat_map_contains(obj->as.set.map, key);
1977
7
            free(key);
1978
7
            value_free(&val);
1979
7
            push(vm, value_bool(found)); return true;
1980
7
        }
1981
20
        if (mhash == MHASH_add && strcmp(method, "add") == 0 && arg_count == 1) {
1982
5
            LatValue val = pop(vm);
1983
5
            char *key = value_display(&val);
1984
5
            LatValue clone = value_deep_clone(&val);
1985
5
            lat_map_set(obj->as.set.map, key, &clone);
1986
5
            free(key);
1987
5
            value_free(&val);
1988
5
            push(vm, value_unit()); return true;
1989
5
        }
1990
15
        if (mhash == MHASH_remove && strcmp(method, "remove") == 0 && arg_count == 1) {
1991
1
            LatValue val = pop(vm);
1992
1
            char *key = value_display(&val);
1993
1
            lat_map_remove(obj->as.set.map, key);
1994
1
            free(key);
1995
1
            value_free(&val);
1996
1
            push(vm, value_unit()); return true;
1997
1
        }
1998
14
        if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
1999
7
            push(vm, value_int((int64_t)lat_map_len(obj->as.set.map))); return true;
2000
7
        }
2001
7
        if (mhash == MHASH_to_array && strcmp(method, "to_array") == 0 && arg_count == 0) {
2002
1
            size_t len = lat_map_len(obj->as.set.map);
2003
1
            LatValue *elems = malloc(len * sizeof(LatValue));
2004
1
            size_t idx = 0;
2005
17
            for (size_t i = 0; i < obj->as.set.map->cap; i++) {
2006
16
                if (obj->as.set.map->entries[i].state == MAP_OCCUPIED) {
2007
1
                    LatValue *v = (LatValue *)obj->as.set.map->entries[i].value;
2008
1
                    elems[idx++] = value_deep_clone(v);
2009
1
                }
2010
16
            }
2011
1
            LatValue arr = value_array(elems, len);
2012
1
            free(elems);
2013
1
            push(vm, arr); return true;
2014
1
        }
2015
6
        if (mhash == MHASH_union && strcmp(method, "union") == 0 && arg_count == 1) {
2016
1
            LatValue other = pop(vm);
2017
1
            LatValue result = value_set_new();
2018
17
            for (size_t i = 0; i < obj->as.set.map->cap; i++) {
2019
16
                if (obj->as.set.map->entries[i].state == MAP_OCCUPIED) {
2020
2
                    LatValue *v = (LatValue *)obj->as.set.map->entries[i].value;
2021
2
                    LatValue c = value_deep_clone(v);
2022
2
                    lat_map_set(result.as.set.map, obj->as.set.map->entries[i].key, &c);
2023
2
                }
2024
16
            }
2025
1
            if (other.type == VAL_SET) {
2026
17
                for (size_t i = 0; i < other.as.set.map->cap; i++) {
2027
16
                    if (other.as.set.map->entries[i].state == MAP_OCCUPIED) {
2028
2
                        LatValue *v = (LatValue *)other.as.set.map->entries[i].value;
2029
2
                        LatValue c = value_deep_clone(v);
2030
2
                        lat_map_set(result.as.set.map, other.as.set.map->entries[i].key, &c);
2031
2
                    }
2032
16
                }
2033
1
            }
2034
1
            value_free(&other);
2035
1
            push(vm, result); return true;
2036
1
        }
2037
5
        if (mhash == MHASH_intersection && strcmp(method, "intersection") == 0 && arg_count == 1) {
2038
1
            LatValue other = pop(vm);
2039
1
            LatValue result = value_set_new();
2040
1
            if (other.type == VAL_SET) {
2041
17
                for (size_t i = 0; i < obj->as.set.map->cap; i++) {
2042
16
                    if (obj->as.set.map->entries[i].state == MAP_OCCUPIED &&
2043
16
                        lat_map_contains(other.as.set.map, obj->as.set.map->entries[i].key)) {
2044
2
                        LatValue *v = (LatValue *)obj->as.set.map->entries[i].value;
2045
2
                        LatValue c = value_deep_clone(v);
2046
2
                        lat_map_set(result.as.set.map, obj->as.set.map->entries[i].key, &c);
2047
2
                    }
2048
16
                }
2049
1
            }
2050
1
            value_free(&other);
2051
1
            push(vm, result); return true;
2052
1
        }
2053
4
        if (mhash == MHASH_difference && strcmp(method, "difference") == 0 && arg_count == 1) {
2054
1
            LatValue other = pop(vm);
2055
1
            LatValue result = value_set_new();
2056
1
            if (other.type == VAL_SET) {
2057
17
                for (size_t i = 0; i < obj->as.set.map->cap; i++) {
2058
16
                    if (obj->as.set.map->entries[i].state == MAP_OCCUPIED &&
2059
16
                        !lat_map_contains(other.as.set.map, obj->as.set.map->entries[i].key)) {
2060
1
                        LatValue *v = (LatValue *)obj->as.set.map->entries[i].value;
2061
1
                        LatValue c = value_deep_clone(v);
2062
1
                        lat_map_set(result.as.set.map, obj->as.set.map->entries[i].key, &c);
2063
1
                    }
2064
16
                }
2065
1
            }
2066
1
            value_free(&other);
2067
1
            push(vm, result); return true;
2068
1
        }
2069
3
        if (mhash == MHASH_is_subset && strcmp(method, "is_subset") == 0 && arg_count == 1) {
2070
2
            LatValue other = pop(vm);
2071
2
            bool result = true;
2072
2
            if (other.type == VAL_SET) {
2073
20
                for (size_t i = 0; i < obj->as.set.map->cap; i++) {
2074
19
                    if (obj->as.set.map->entries[i].state == MAP_OCCUPIED &&
2075
19
                        !lat_map_contains(other.as.set.map, obj->as.set.map->entries[i].key)) {
2076
1
                        result = false; break;
2077
1
                    }
2078
19
                }
2079
2
            } else result = false;
2080
2
            value_free(&other);
2081
2
            push(vm, value_bool(result)); return true;
2082
2
        }
2083
1
        if (mhash == MHASH_is_superset && strcmp(method, "is_superset") == 0 && arg_count == 1) {
2084
1
            LatValue other = pop(vm);
2085
1
            bool result = true;
2086
1
            if (other.type == VAL_SET) {
2087
17
                for (size_t i = 0; i < other.as.set.map->cap; i++) {
2088
16
                    if (other.as.set.map->entries[i].state == MAP_OCCUPIED &&
2089
16
                        !lat_map_contains(obj->as.set.map, other.as.set.map->entries[i].key)) {
2090
0
                        result = false; break;
2091
0
                    }
2092
16
                }
2093
1
            } else result = false;
2094
1
            value_free(&other);
2095
1
            push(vm, value_bool(result)); return true;
2096
1
        }
2097
1
    } break;
2098
2099
    /* ── Channel methods ── */
2100
21
    case VAL_CHANNEL: {
2101
21
        if (mhash == MHASH_send && strcmp(method, "send") == 0 && arg_count == 1) {
2102
10
            LatValue val = pop(vm);
2103
10
            if (val.phase == VTAG_FLUID) {
2104
1
                value_free(&val);
2105
1
                vm->error = strdup("channel.send: can only send crystal (immutable) values");
2106
1
                push(vm, value_unit()); return true;
2107
1
            }
2108
9
            channel_send(obj->as.channel.ch, val);
2109
9
            push(vm, value_unit()); return true;
2110
10
        }
2111
11
        if (mhash == MHASH_recv && strcmp(method, "recv") == 0 && arg_count == 0) {
2112
8
            bool ok;
2113
8
            LatValue val = channel_recv(obj->as.channel.ch, &ok);
2114
8
            if (!ok) { push(vm, value_unit()); return true; }
2115
7
            push(vm, val); return true;
2116
8
        }
2117
3
        if (mhash == MHASH_close && strcmp(method, "close") == 0 && arg_count == 0) {
2118
3
            channel_close(obj->as.channel.ch);
2119
3
            push(vm, value_unit()); return true;
2120
3
        }
2121
3
    } break;
2122
2123
    /* ── Buffer methods ── */
2124
25
    case VAL_BUFFER: {
2125
25
        if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
2126
10
            push(vm, value_int((int64_t)obj->as.buffer.len));
2127
10
            return true;
2128
10
        }
2129
15
        if (mhash == MHASH_capacity && strcmp(method, "capacity") == 0 && arg_count == 0) {
2130
0
            push(vm, value_int((int64_t)obj->as.buffer.cap));
2131
0
            return true;
2132
0
        }
2133
15
        if (mhash == MHASH_push && strcmp(method, "push") == 0 && arg_count == 1) {
2134
2
            LatValue val = pop(vm);
2135
2
            if (val.type != VAL_INT) { value_free(&val); push(vm, value_unit()); return true; }
2136
2
            if (obj->as.buffer.len >= obj->as.buffer.cap) {
2137
0
                obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8;
2138
0
                obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap);
2139
0
            }
2140
2
            obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)(val.as.int_val & 0xFF);
2141
2
            push(vm, value_unit());
2142
2
            return true;
2143
2
        }
2144
13
        if (mhash == MHASH_push_u16 && strcmp(method, "push_u16") == 0 && arg_count == 1) {
2145
1
            LatValue val = pop(vm);
2146
1
            if (val.type != VAL_INT) { value_free(&val); push(vm, value_unit()); return true; }
2147
1
            uint16_t v = (uint16_t)(val.as.int_val & 0xFFFF);
2148
1
            while (obj->as.buffer.len + 2 > obj->as.buffer.cap) {
2149
0
                obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8;
2150
0
                obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap);
2151
0
            }
2152
1
            obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)(v & 0xFF);
2153
1
            obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF);
2154
1
            push(vm, value_unit());
2155
1
            return true;
2156
1
        }
2157
12
        if (mhash == MHASH_push_u32 && strcmp(method, "push_u32") == 0 && arg_count == 1) {
2158
1
            LatValue val = pop(vm);
2159
1
            if (val.type != VAL_INT) { value_free(&val); push(vm, value_unit()); return true; }
2160
1
            uint32_t v = (uint32_t)(val.as.int_val & 0xFFFFFFFF);
2161
1
            while (obj->as.buffer.len + 4 > obj->as.buffer.cap) {
2162
0
                obj->as.buffer.cap = obj->as.buffer.cap ? obj->as.buffer.cap * 2 : 8;
2163
0
                obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap);
2164
0
            }
2165
1
            obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)(v & 0xFF);
2166
1
            obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF);
2167
1
            obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 16) & 0xFF);
2168
1
            obj->as.buffer.data[obj->as.buffer.len++] = (uint8_t)((v >> 24) & 0xFF);
2169
1
            push(vm, value_unit());
2170
1
            return true;
2171
1
        }
2172
11
        if (mhash == MHASH_read_u8 && strcmp(method, "read_u8") == 0 && arg_count == 1) {
2173
0
            LatValue idx = pop(vm);
2174
0
            if (idx.type != VAL_INT || idx.as.int_val < 0 || (size_t)idx.as.int_val >= obj->as.buffer.len) {
2175
0
                value_free(&idx);
2176
0
                vm->error = strdup("Buffer.read_u8: index out of bounds");
2177
0
                push(vm, value_int(0));
2178
0
                return true;
2179
0
            }
2180
0
            push(vm, value_int(obj->as.buffer.data[idx.as.int_val]));
2181
0
            return true;
2182
0
        }
2183
11
        if (mhash == MHASH_write_u8 && strcmp(method, "write_u8") == 0 && arg_count == 2) {
2184
0
            LatValue val = pop(vm);
2185
0
            LatValue idx = pop(vm);
2186
0
            if (idx.type != VAL_INT || idx.as.int_val < 0 || (size_t)idx.as.int_val >= obj->as.buffer.len) {
2187
0
                value_free(&idx); value_free(&val);
2188
0
                vm->error = strdup("Buffer.write_u8: index out of bounds");
2189
0
                push(vm, value_unit());
2190
0
                return true;
2191
0
            }
2192
0
            obj->as.buffer.data[idx.as.int_val] = (uint8_t)(val.as.int_val & 0xFF);
2193
0
            push(vm, value_unit());
2194
0
            return true;
2195
0
        }
2196
11
        if (mhash == MHASH_read_u16 && strcmp(method, "read_u16") == 0 && arg_count == 1) {
2197
1
            LatValue idx = pop(vm);
2198
1
            if (idx.type != VAL_INT || idx.as.int_val < 0 || (size_t)idx.as.int_val + 2 > obj->as.buffer.len) {
2199
0
                value_free(&idx);
2200
0
                vm->error = strdup("Buffer.read_u16: index out of bounds");
2201
0
                push(vm, value_int(0));
2202
0
                return true;
2203
0
            }
2204
1
            size_t i = (size_t)idx.as.int_val;
2205
1
            uint16_t v = (uint16_t)(obj->as.buffer.data[i] | (obj->as.buffer.data[i+1] << 8));
2206
1
            push(vm, value_int(v));
2207
1
            return true;
2208
1
        }
2209
10
        if (mhash == MHASH_write_u16 && strcmp(method, "write_u16") == 0 && arg_count == 2) {
2210
1
            LatValue val = pop(vm);
2211
1
            LatValue idx = pop(vm);
2212
1
            if (idx.type != VAL_INT || idx.as.int_val < 0 || (size_t)idx.as.int_val + 2 > obj->as.buffer.len) {
2213
0
                value_free(&idx); value_free(&val);
2214
0
                vm->error = strdup("Buffer.write_u16: index out of bounds");
2215
0
                push(vm, value_unit());
2216
0
                return true;
2217
0
            }
2218
1
            size_t i = (size_t)idx.as.int_val;
2219
1
            uint16_t v = (uint16_t)(val.as.int_val & 0xFFFF);
2220
1
            obj->as.buffer.data[i] = (uint8_t)(v & 0xFF);
2221
1
            obj->as.buffer.data[i+1] = (uint8_t)((v >> 8) & 0xFF);
2222
1
            push(vm, value_unit());
2223
1
            return true;
2224
1
        }
2225
9
        if (mhash == MHASH_read_u32 && strcmp(method, "read_u32") == 0 && arg_count == 1) {
2226
1
            LatValue idx = pop(vm);
2227
1
            if (idx.type != VAL_INT || idx.as.int_val < 0 || (size_t)idx.as.int_val + 4 > obj->as.buffer.len) {
2228
0
                value_free(&idx);
2229
0
                vm->error = strdup("Buffer.read_u32: index out of bounds");
2230
0
                push(vm, value_int(0));
2231
0
                return true;
2232
0
            }
2233
1
            size_t i = (size_t)idx.as.int_val;
2234
1
            uint32_t v = (uint32_t)obj->as.buffer.data[i]
2235
1
                       | ((uint32_t)obj->as.buffer.data[i+1] << 8)
2236
1
                       | ((uint32_t)obj->as.buffer.data[i+2] << 16)
2237
1
                       | ((uint32_t)obj->as.buffer.data[i+3] << 24);
2238
1
            push(vm, value_int((int64_t)v));
2239
1
            return true;
2240
1
        }
2241
8
        if (mhash == MHASH_write_u32 && strcmp(method, "write_u32") == 0 && arg_count == 2) {
2242
1
            LatValue val = pop(vm);
2243
1
            LatValue idx = pop(vm);
2244
1
            if (idx.type != VAL_INT || idx.as.int_val < 0 || (size_t)idx.as.int_val + 4 > obj->as.buffer.len) {
2245
0
                value_free(&idx); value_free(&val);
2246
0
                vm->error = strdup("Buffer.write_u32: index out of bounds");
2247
0
                push(vm, value_unit());
2248
0
                return true;
2249
0
            }
2250
1
            size_t i = (size_t)idx.as.int_val;
2251
1
            uint32_t v = (uint32_t)(val.as.int_val & 0xFFFFFFFF);
2252
1
            obj->as.buffer.data[i]   = (uint8_t)(v & 0xFF);
2253
1
            obj->as.buffer.data[i+1] = (uint8_t)((v >> 8) & 0xFF);
2254
1
            obj->as.buffer.data[i+2] = (uint8_t)((v >> 16) & 0xFF);
2255
1
            obj->as.buffer.data[i+3] = (uint8_t)((v >> 24) & 0xFF);
2256
1
            push(vm, value_unit());
2257
1
            return true;
2258
1
        }
2259
7
        if (mhash == MHASH_slice && strcmp(method, "slice") == 0 && arg_count == 2) {
2260
1
            LatValue end_v = pop(vm);
2261
1
            LatValue start_v = pop(vm);
2262
1
            if (start_v.type != VAL_INT || end_v.type != VAL_INT) {
2263
0
                value_free(&start_v); value_free(&end_v);
2264
0
                vm->error = strdup("Buffer.slice: expected Int arguments");
2265
0
                push(vm, value_buffer(NULL, 0));
2266
0
                return true;
2267
0
            }
2268
1
            int64_t s = start_v.as.int_val, e = end_v.as.int_val;
2269
1
            if (s < 0) s = 0;
2270
1
            if (e > (int64_t)obj->as.buffer.len) e = (int64_t)obj->as.buffer.len;
2271
1
            if (s >= e) { push(vm, value_buffer(NULL, 0)); return true; }
2272
1
            push(vm, value_buffer(obj->as.buffer.data + s, (size_t)(e - s)));
2273
1
            return true;
2274
1
        }
2275
6
        if (mhash == MHASH_clear && strcmp(method, "clear") == 0 && arg_count == 0) {
2276
1
            obj->as.buffer.len = 0;
2277
1
            push(vm, value_unit());
2278
1
            return true;
2279
1
        }
2280
5
        if (mhash == MHASH_fill && strcmp(method, "fill") == 0 && arg_count == 1) {
2281
1
            LatValue val = pop(vm);
2282
1
            uint8_t byte = (val.type == VAL_INT) ? (uint8_t)(val.as.int_val & 0xFF) : 0;
2283
1
            memset(obj->as.buffer.data, byte, obj->as.buffer.len);
2284
1
            push(vm, value_unit());
2285
1
            return true;
2286
1
        }
2287
4
        if (mhash == MHASH_resize && strcmp(method, "resize") == 0 && arg_count == 1) {
2288
1
            LatValue val = pop(vm);
2289
1
            if (val.type != VAL_INT || val.as.int_val < 0) { value_free(&val); push(vm, value_unit()); return true; }
2290
1
            size_t new_len = (size_t)val.as.int_val;
2291
1
            if (new_len > obj->as.buffer.cap) {
2292
0
                obj->as.buffer.cap = new_len;
2293
0
                obj->as.buffer.data = realloc(obj->as.buffer.data, obj->as.buffer.cap);
2294
0
            }
2295
1
            if (new_len > obj->as.buffer.len)
2296
1
                memset(obj->as.buffer.data + obj->as.buffer.len, 0, new_len - obj->as.buffer.len);
2297
1
            obj->as.buffer.len = new_len;
2298
1
            push(vm, value_unit());
2299
1
            return true;
2300
1
        }
2301
3
        if (mhash == MHASH_to_string && strcmp(method, "to_string") == 0 && arg_count == 0) {
2302
1
            char *s = malloc(obj->as.buffer.len + 1);
2303
1
            memcpy(s, obj->as.buffer.data, obj->as.buffer.len);
2304
1
            s[obj->as.buffer.len] = '\0';
2305
1
            push(vm, value_string_owned(s));
2306
1
            return true;
2307
1
        }
2308
2
        if (mhash == MHASH_to_array && strcmp(method, "to_array") == 0 && arg_count == 0) {
2309
1
            size_t len = obj->as.buffer.len;
2310
1
            LatValue *elems = malloc((len > 0 ? len : 1) * sizeof(LatValue));
2311
4
            for (size_t i = 0; i < len; i++)
2312
3
                elems[i] = value_int(obj->as.buffer.data[i]);
2313
1
            LatValue arr = value_array(elems, len);
2314
1
            free(elems);
2315
1
            push(vm, arr);
2316
1
            return true;
2317
1
        }
2318
1
        if (mhash == MHASH_to_hex && strcmp(method, "to_hex") == 0 && arg_count == 0) {
2319
1
            size_t len = obj->as.buffer.len;
2320
1
            char *hex = malloc(len * 2 + 1);
2321
4
            for (size_t i = 0; i < len; i++)
2322
3
                snprintf(hex + i * 2, 3, "%02x", obj->as.buffer.data[i]);
2323
1
            hex[len * 2] = '\0';
2324
1
            push(vm, value_string_owned(hex));
2325
1
            return true;
2326
1
        }
2327
1
    } break;
2328
2329
    /* ── Ref methods ── */
2330
15
    case VAL_REF: {
2331
15
        LatRef *ref = obj->as.ref.ref;
2332
15
        LatValue *inner = &ref->value;
2333
2334
        /* Ref-specific: get() with 0 args */
2335
15
        if (mhash == MHASH_get && strcmp(method, "get") == 0 && arg_count == 0) {
2336
3
            push(vm, value_deep_clone(inner));
2337
3
            return true;
2338
3
        }
2339
        /* Ref-specific: deref() alias for get() */
2340
12
        if (mhash == MHASH_deref && strcmp(method, "deref") == 0 && arg_count == 0) {
2341
1
            push(vm, value_deep_clone(inner));
2342
1
            return true;
2343
1
        }
2344
        /* Ref-specific: set(v) with 1 arg */
2345
11
        if (mhash == MHASH_set && strcmp(method, "set") == 0 && arg_count == 1) {
2346
2
            if (obj->phase == VTAG_CRYSTAL) {
2347
0
                runtime_error(vm, "cannot set on a frozen Ref");
2348
0
                return true;
2349
0
            }
2350
2
            value_free(inner);
2351
2
            *inner = stackvm_peek(vm, 0)[0];
2352
2
            vm->stack_top--;
2353
2
            *inner = value_deep_clone(inner);
2354
2
            push(vm, value_unit());
2355
2
            return true;
2356
2
        }
2357
        /* Ref-specific: inner_type() */
2358
9
        if (mhash == MHASH_inner_type && strcmp(method, "inner_type") == 0 && arg_count == 0) {
2359
2
            push(vm, value_string(value_type_name(inner)));
2360
2
            return true;
2361
2
        }
2362
2363
        /* Map proxy (when inner is VAL_MAP) */
2364
7
        if (inner->type == VAL_MAP) {
2365
            /* get(key) with 1 arg -> Map proxy */
2366
3
            if (mhash == MHASH_get && strcmp(method, "get") == 0 && arg_count == 1) {
2367
0
                LatValue key = stackvm_peek(vm, 0)[0];
2368
0
                if (key.type != VAL_STR) { push(vm, value_nil()); return true; }
2369
0
                LatValue *found = lat_map_get(inner->as.map.map, key.as.str_val);
2370
0
                value_free(&key); vm->stack_top--;
2371
0
                push(vm, found ? value_deep_clone(found) : value_nil());
2372
0
                return true;
2373
0
            }
2374
            /* set(k, v) with 2 args -> Map proxy */
2375
3
            if (mhash == MHASH_set && strcmp(method, "set") == 0 && arg_count == 2) {
2376
0
                if (obj->phase == VTAG_CRYSTAL) {
2377
0
                    runtime_error(vm, "cannot set on a frozen Ref");
2378
0
                    return true;
2379
0
                }
2380
0
                LatValue val2 = stackvm_peek(vm, 0)[0]; vm->stack_top--;
2381
0
                LatValue key = stackvm_peek(vm, 0)[0]; vm->stack_top--;
2382
0
                if (key.type == VAL_STR) {
2383
0
                    LatValue *old = (LatValue *)lat_map_get(inner->as.map.map, key.as.str_val);
2384
0
                    if (old) value_free(old);
2385
0
                    lat_map_set(inner->as.map.map, key.as.str_val, &val2);
2386
0
                } else {
2387
0
                    value_free(&val2);
2388
0
                }
2389
0
                value_free(&key);
2390
0
                push(vm, value_unit());
2391
0
                return true;
2392
0
            }
2393
3
            if (mhash == MHASH_has && strcmp(method, "has") == 0 && arg_count == 1) {
2394
2
                LatValue key = stackvm_peek(vm, 0)[0]; vm->stack_top--;
2395
2
                bool found = key.type == VAL_STR && lat_map_contains(inner->as.map.map, key.as.str_val);
2396
2
                value_free(&key);
2397
2
                push(vm, value_bool(found));
2398
2
                return true;
2399
2
            }
2400
1
            if (mhash == MHASH_contains && strcmp(method, "contains") == 0 && arg_count == 1) {
2401
0
                LatValue needle = stackvm_peek(vm, 0)[0]; vm->stack_top--;
2402
0
                bool found = false;
2403
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
2404
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
2405
0
                    LatValue *mv = (LatValue *)inner->as.map.map->entries[i].value;
2406
0
                    if (value_eq(mv, &needle)) { found = true; break; }
2407
0
                }
2408
0
                value_free(&needle);
2409
0
                push(vm, value_bool(found));
2410
0
                return true;
2411
0
            }
2412
1
            if (mhash == MHASH_keys && strcmp(method, "keys") == 0 && arg_count == 0) {
2413
0
                size_t n = lat_map_len(inner->as.map.map);
2414
0
                LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue));
2415
0
                size_t ei = 0;
2416
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
2417
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
2418
0
                    elems[ei++] = value_string(inner->as.map.map->entries[i].key);
2419
0
                }
2420
0
                LatValue arr = value_array(elems, ei); free(elems);
2421
0
                push(vm, arr);
2422
0
                return true;
2423
0
            }
2424
1
            if (mhash == MHASH_values && strcmp(method, "values") == 0 && arg_count == 0) {
2425
0
                size_t n = lat_map_len(inner->as.map.map);
2426
0
                LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue));
2427
0
                size_t ei = 0;
2428
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
2429
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
2430
0
                    LatValue *mv = (LatValue *)inner->as.map.map->entries[i].value;
2431
0
                    elems[ei++] = value_deep_clone(mv);
2432
0
                }
2433
0
                LatValue arr = value_array(elems, ei); free(elems);
2434
0
                push(vm, arr);
2435
0
                return true;
2436
0
            }
2437
1
            if (mhash == MHASH_entries && strcmp(method, "entries") == 0 && arg_count == 0) {
2438
0
                size_t n = lat_map_len(inner->as.map.map);
2439
0
                LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue));
2440
0
                size_t ei = 0;
2441
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
2442
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
2443
0
                    LatValue pair[2];
2444
0
                    pair[0] = value_string(inner->as.map.map->entries[i].key);
2445
0
                    pair[1] = value_deep_clone((LatValue *)inner->as.map.map->entries[i].value);
2446
0
                    elems[ei++] = value_array(pair, 2);
2447
0
                }
2448
0
                LatValue arr = value_array(elems, ei); free(elems);
2449
0
                push(vm, arr);
2450
0
                return true;
2451
0
            }
2452
1
            if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
2453
1
                push(vm, value_int((int64_t)lat_map_len(inner->as.map.map)));
2454
1
                return true;
2455
1
            }
2456
0
            if (mhash == MHASH_merge && strcmp(method, "merge") == 0 && arg_count == 1) {
2457
0
                if (obj->phase == VTAG_CRYSTAL) {
2458
0
                    runtime_error(vm, "cannot merge into a frozen Ref");
2459
0
                    return true;
2460
0
                }
2461
0
                LatValue other = stackvm_peek(vm, 0)[0]; vm->stack_top--;
2462
0
                if (other.type == VAL_MAP) {
2463
0
                    for (size_t i = 0; i < other.as.map.map->cap; i++) {
2464
0
                        if (other.as.map.map->entries[i].state != MAP_OCCUPIED) continue;
2465
0
                        LatValue cloned = value_deep_clone((LatValue *)other.as.map.map->entries[i].value);
2466
0
                        LatValue *old = (LatValue *)lat_map_get(inner->as.map.map, other.as.map.map->entries[i].key);
2467
0
                        if (old) value_free(old);
2468
0
                        lat_map_set(inner->as.map.map, other.as.map.map->entries[i].key, &cloned);
2469
0
                    }
2470
0
                }
2471
0
                value_free(&other);
2472
0
                push(vm, value_unit());
2473
0
                return true;
2474
0
            }
2475
0
        }
2476
2477
        /* Array proxy (when inner is VAL_ARRAY) */
2478
4
        if (inner->type == VAL_ARRAY) {
2479
4
            if (mhash == MHASH_push && strcmp(method, "push") == 0 && arg_count == 1) {
2480
1
                if (obj->phase == VTAG_CRYSTAL) {
2481
0
                    runtime_error(vm, "cannot push to a frozen Ref");
2482
0
                    return true;
2483
0
                }
2484
1
                LatValue val2 = stackvm_peek(vm, 0)[0]; vm->stack_top--;
2485
1
                if (inner->as.array.len >= inner->as.array.cap) {
2486
0
                    size_t old_cap = inner->as.array.cap;
2487
0
                    inner->as.array.cap = old_cap < 4 ? 4 : old_cap * 2;
2488
0
                    inner->as.array.elems = realloc(inner->as.array.elems, inner->as.array.cap * sizeof(LatValue));
2489
0
                }
2490
1
                inner->as.array.elems[inner->as.array.len++] = val2;
2491
1
                push(vm, value_unit());
2492
1
                return true;
2493
1
            }
2494
3
            if (mhash == MHASH_pop && strcmp(method, "pop") == 0 && arg_count == 0) {
2495
1
                if (obj->phase == VTAG_CRYSTAL) {
2496
0
                    runtime_error(vm, "cannot pop from a frozen Ref");
2497
0
                    return true;
2498
0
                }
2499
1
                if (inner->as.array.len == 0) {
2500
0
                    runtime_error(vm, "pop on empty array");
2501
0
                    return true;
2502
0
                }
2503
1
                LatValue popped = inner->as.array.elems[--inner->as.array.len];
2504
1
                push(vm, popped);
2505
1
                return true;
2506
1
            }
2507
2
            if (mhash == MHASH_len && strcmp(method, "len") == 0 && arg_count == 0) {
2508
2
                push(vm, value_int((int64_t)inner->as.array.len));
2509
2
                return true;
2510
2
            }
2511
0
            if (mhash == MHASH_contains && strcmp(method, "contains") == 0 && arg_count == 1) {
2512
0
                LatValue needle = stackvm_peek(vm, 0)[0]; vm->stack_top--;
2513
0
                bool found = false;
2514
0
                for (size_t i = 0; i < inner->as.array.len; i++) {
2515
0
                    if (value_eq(&inner->as.array.elems[i], &needle)) { found = true; break; }
2516
0
                }
2517
0
                value_free(&needle);
2518
0
                push(vm, value_bool(found));
2519
0
                return true;
2520
0
            }
2521
0
        }
2522
4
    } break;
2523
2524
1
    default: break;
2525
2.98k
    } /* end switch */
2526
2527
343
    return false;
2528
2.98k
}
2529
2530
/* ── Runtime type checking (mirrors eval.c type_matches_value) ── */
2531
2532
900
static bool stackvm_type_matches(const LatValue *val, const char *type_name) {
2533
900
    if (!type_name || strcmp(type_name, "Any") == 0 || strcmp(type_name, "any") == 0) return true;
2534
900
    if (strcmp(type_name, "Int") == 0)     return val->type == VAL_INT;
2535
771
    if (strcmp(type_name, "Float") == 0)   return val->type == VAL_FLOAT;
2536
771
    if (strcmp(type_name, "String") == 0)  return val->type == VAL_STR;
2537
694
    if (strcmp(type_name, "Bool") == 0)    return val->type == VAL_BOOL;
2538
667
    if (strcmp(type_name, "Nil") == 0)     return val->type == VAL_NIL;
2539
667
    if (strcmp(type_name, "Map") == 0)     return val->type == VAL_MAP;
2540
130
    if (strcmp(type_name, "Array") == 0)   return val->type == VAL_ARRAY;
2541
68
    if (strcmp(type_name, "Fn") == 0 || strcmp(type_name, "Closure") == 0)
2542
57
        return val->type == VAL_CLOSURE;
2543
11
    if (strcmp(type_name, "Channel") == 0) return val->type == VAL_CHANNEL;
2544
11
    if (strcmp(type_name, "Range") == 0)   return val->type == VAL_RANGE;
2545
11
    if (strcmp(type_name, "Set") == 0)     return val->type == VAL_SET;
2546
11
    if (strcmp(type_name, "Tuple") == 0)   return val->type == VAL_TUPLE;
2547
11
    if (strcmp(type_name, "Buffer") == 0)  return val->type == VAL_BUFFER;
2548
11
    if (strcmp(type_name, "Ref") == 0)     return val->type == VAL_REF;
2549
11
    if (strcmp(type_name, "Number") == 0)
2550
5
        return val->type == VAL_INT || val->type == VAL_FLOAT;
2551
    /* Struct name check */
2552
6
    if (val->type == VAL_STRUCT && val->as.strct.name)
2553
5
        return strcmp(val->as.strct.name, type_name) == 0;
2554
    /* Enum name check */
2555
1
    if (val->type == VAL_ENUM && val->as.enm.enum_name)
2556
1
        return strcmp(val->as.enm.enum_name, type_name) == 0;
2557
0
    return false;
2558
1
}
2559
2560
4
static const char *stackvm_value_type_display(const LatValue *val) {
2561
4
    switch (val->type) {
2562
0
        case VAL_INT:     return "Int";
2563
0
        case VAL_FLOAT:   return "Float";
2564
0
        case VAL_BOOL:    return "Bool";
2565
3
        case VAL_STR:     return "String";
2566
0
        case VAL_ARRAY:   return "Array";
2567
1
        case VAL_STRUCT:  return val->as.strct.name ? val->as.strct.name : "Struct";
2568
0
        case VAL_CLOSURE: return "Fn";
2569
0
        case VAL_UNIT:    return "Unit";
2570
0
        case VAL_NIL:     return "Nil";
2571
0
        case VAL_RANGE:   return "Range";
2572
0
        case VAL_MAP:     return "Map";
2573
0
        case VAL_CHANNEL: return "Channel";
2574
0
        case VAL_ENUM:    return val->as.enm.enum_name ? val->as.enm.enum_name : "Enum";
2575
0
        case VAL_SET:     return "Set";
2576
0
        case VAL_TUPLE:   return "Tuple";
2577
0
        case VAL_BUFFER:  return "Buffer";
2578
0
        case VAL_REF:     return "Ref";
2579
4
    }
2580
0
    return "Unknown";
2581
4
}
2582
2583
/* ── Execution ── */
2584
2585
119k
#define READ_BYTE() (*frame->ip++)
2586
3.72k
#define READ_U16() (frame->ip += 2, (uint16_t)((frame->ip[-2] << 8) | frame->ip[-1]))
2587
2588
/* Route runtime errors through exception handlers when possible.
2589
 * Use in place of 'return runtime_error(vm, ...)' inside the dispatch loop. */
2590
31
#define VM_ERROR(...) do { \
2591
31
    StackVMResult _err = stackvm_handle_error(vm, &frame, __VA_ARGS__); \
2592
31
    if (_err != STACKVM_OK) return _err; \
2593
31
} while(0)
2594
2595
/* Adjust stack for default parameters and variadic arguments before a compiled
2596
 * closure call. Returns the adjusted arg_count, or -1 on error. On error,
2597
 * vm->error is set with a heap-allocated message. */
2598
/* Convert a Map or Set on the stack (pointed to by iter) to a key/value array in-place. */
2599
2
static void stackvm_iter_convert_to_array(LatValue *iter) {
2600
2
    bool is_map = (iter->type == VAL_MAP);
2601
2
    LatMap *hm = is_map ? iter->as.map.map : iter->as.set.map;
2602
2
    size_t len = lat_map_len(hm);
2603
2
    LatValue *elms = malloc((len ? len : 1) * sizeof(LatValue));
2604
2
    size_t ei = 0;
2605
34
    for (size_t i = 0; i < hm->cap; i++) {
2606
32
        if (hm->entries[i].state == MAP_OCCUPIED) {
2607
2
            if (is_map)
2608
1
                elms[ei++] = value_string(hm->entries[i].key);
2609
1
            else
2610
1
                elms[ei++] = value_deep_clone((LatValue *)hm->entries[i].value);
2611
2
        }
2612
32
    }
2613
2
    LatValue arr = value_array(elms, ei);
2614
2
    free(elms);
2615
2
    value_free(iter);
2616
2
    *iter = arr;
2617
2
}
2618
2619
2.59k
static int stackvm_adjust_call_args(StackVM *vm, Chunk *fn_chunk, int arity, int arg_count) {
2620
2.59k
    int dc = fn_chunk->default_count;
2621
2.59k
    bool vd = fn_chunk->fn_has_variadic;
2622
2.59k
    if (dc == 0 && !vd) {
2623
2.56k
        if (arg_count != arity) {
2624
0
            (void)asprintf(&vm->error, "expected %d arguments but got %d", arity, arg_count);
2625
0
            return -1;
2626
0
        }
2627
2.56k
        return arg_count;
2628
2.56k
    }
2629
29
    int required = arity - dc - (vd ? 1 : 0);
2630
29
    int non_variadic = vd ? arity - 1 : arity;
2631
2632
29
    if (arg_count < required || (!vd && arg_count > arity)) {
2633
0
        if (vd)
2634
0
            (void)asprintf(&vm->error, "expected at least %d arguments but got %d", required, arg_count);
2635
0
        else if (dc > 0)
2636
0
            (void)asprintf(&vm->error, "expected %d to %d arguments but got %d", required, arity, arg_count);
2637
0
        else
2638
0
            (void)asprintf(&vm->error, "expected %d arguments but got %d", arity, arg_count);
2639
0
        return -1;
2640
0
    }
2641
2642
    /* Push defaults for missing non-variadic params */
2643
29
    if (arg_count < non_variadic && fn_chunk->default_values) {
2644
34
        for (int i = arg_count; i < non_variadic; i++) {
2645
18
            int def_idx = i - required;
2646
18
            push(vm, value_clone_fast(&fn_chunk->default_values[def_idx]));
2647
18
        }
2648
16
        arg_count = non_variadic;
2649
16
    }
2650
2651
    /* Bundle variadic args into array */
2652
29
    if (vd) {
2653
12
        int extra = arg_count - non_variadic;
2654
12
        if (extra < 0) extra = 0;
2655
12
        LatValue *elems = NULL;
2656
12
        if (extra > 0) {
2657
8
            elems = malloc(extra * sizeof(LatValue));
2658
25
            for (int i = extra - 1; i >= 0; i--)
2659
17
                elems[i] = pop(vm);
2660
8
        }
2661
12
        push(vm, value_array(elems, extra));
2662
12
        free(elems);
2663
12
        arg_count = arity;
2664
12
    }
2665
2666
29
    return arg_count;
2667
29
}
2668
2669
/* Dispatch pointer adapters for LatRuntime */
2670
19
static LatValue stackvm_dispatch_call_closure(void *vm_ptr, LatValue *closure, LatValue *args, int argc) {
2671
19
    StackVM *vm = (StackVM *)vm_ptr;
2672
19
    LatValue result = stackvm_call_closure(vm, closure, args, argc);
2673
    /* Bridge StackVM errors to runtime for phase system functions */
2674
19
    if (vm->error) {
2675
1
        vm->rt->error = vm->error;
2676
1
        vm->error = NULL;
2677
1
    }
2678
19
    return result;
2679
19
}
2680
2681
1.27k
StackVMResult stackvm_run(StackVM *vm, Chunk *chunk, LatValue *result) {
2682
    /* Set up runtime dispatch pointers so native functions can call back */
2683
1.27k
    vm->rt->backend = RT_BACKEND_STACK_VM;
2684
1.27k
    vm->rt->active_vm = vm;
2685
1.27k
    vm->rt->call_closure = stackvm_dispatch_call_closure;
2686
1.27k
    vm->rt->find_local_value = (bool (*)(void *, const char *, LatValue *))stackvm_find_local_value;
2687
1.27k
    vm->rt->current_line = (int (*)(void *))stackvm_current_line;
2688
1.27k
    vm->rt->get_var_by_name = (bool (*)(void *, const char *, LatValue *))stackvm_get_var_by_name;
2689
1.27k
    vm->rt->set_var_by_name = (bool (*)(void *, const char *, LatValue))stackvm_set_var_by_name;
2690
1.27k
    lat_runtime_set_current(vm->rt);
2691
1.27k
    size_t base_frame = vm->frame_count;
2692
1.27k
    StackCallFrame *frame = &vm->frames[vm->frame_count++];
2693
1.27k
    frame->chunk = chunk;
2694
1.27k
    frame->ip = chunk->code;
2695
1.27k
    if (vm->next_frame_slots) {
2696
8
        frame->slots = vm->next_frame_slots;
2697
8
        frame->cleanup_base = vm->stack_top;  /* Only free above this point on return */
2698
8
        vm->next_frame_slots = NULL;
2699
1.26k
    } else {
2700
1.26k
        frame->slots = vm->stack_top;
2701
1.26k
        frame->cleanup_base = NULL;
2702
1.26k
    }
2703
1.27k
    frame->upvalues = NULL;
2704
1.27k
    frame->upvalue_count = 0;
2705
2706
1.27k
#ifdef VM_USE_COMPUTED_GOTO
2707
1.27k
    static void *dispatch_table[] = {
2708
1.27k
        [OP_CONSTANT] = &&lbl_OP_CONSTANT,
2709
1.27k
        [OP_NIL] = &&lbl_OP_NIL, [OP_TRUE] = &&lbl_OP_TRUE,
2710
1.27k
        [OP_FALSE] = &&lbl_OP_FALSE, [OP_UNIT] = &&lbl_OP_UNIT,
2711
1.27k
        [OP_POP] = &&lbl_OP_POP, [OP_DUP] = &&lbl_OP_DUP, [OP_SWAP] = &&lbl_OP_SWAP,
2712
1.27k
        [OP_ADD] = &&lbl_OP_ADD, [OP_SUB] = &&lbl_OP_SUB,
2713
1.27k
        [OP_MUL] = &&lbl_OP_MUL, [OP_DIV] = &&lbl_OP_DIV, [OP_MOD] = &&lbl_OP_MOD,
2714
1.27k
        [OP_NEG] = &&lbl_OP_NEG, [OP_NOT] = &&lbl_OP_NOT,
2715
1.27k
        [OP_BIT_AND] = &&lbl_OP_BIT_AND, [OP_BIT_OR] = &&lbl_OP_BIT_OR,
2716
1.27k
        [OP_BIT_XOR] = &&lbl_OP_BIT_XOR, [OP_BIT_NOT] = &&lbl_OP_BIT_NOT,
2717
1.27k
        [OP_LSHIFT] = &&lbl_OP_LSHIFT, [OP_RSHIFT] = &&lbl_OP_RSHIFT,
2718
1.27k
        [OP_EQ] = &&lbl_OP_EQ, [OP_NEQ] = &&lbl_OP_NEQ,
2719
1.27k
        [OP_LT] = &&lbl_OP_LT, [OP_GT] = &&lbl_OP_GT,
2720
1.27k
        [OP_LTEQ] = &&lbl_OP_LTEQ, [OP_GTEQ] = &&lbl_OP_GTEQ,
2721
1.27k
        [OP_CONCAT] = &&lbl_OP_CONCAT,
2722
1.27k
        [OP_GET_LOCAL] = &&lbl_OP_GET_LOCAL, [OP_SET_LOCAL] = &&lbl_OP_SET_LOCAL,
2723
1.27k
        [OP_GET_GLOBAL] = &&lbl_OP_GET_GLOBAL, [OP_SET_GLOBAL] = &&lbl_OP_SET_GLOBAL,
2724
1.27k
        [OP_DEFINE_GLOBAL] = &&lbl_OP_DEFINE_GLOBAL,
2725
1.27k
        [OP_GET_UPVALUE] = &&lbl_OP_GET_UPVALUE, [OP_SET_UPVALUE] = &&lbl_OP_SET_UPVALUE,
2726
1.27k
        [OP_CLOSE_UPVALUE] = &&lbl_OP_CLOSE_UPVALUE,
2727
1.27k
        [OP_JUMP] = &&lbl_OP_JUMP, [OP_JUMP_IF_FALSE] = &&lbl_OP_JUMP_IF_FALSE,
2728
1.27k
        [OP_JUMP_IF_TRUE] = &&lbl_OP_JUMP_IF_TRUE,
2729
1.27k
        [OP_JUMP_IF_NOT_NIL] = &&lbl_OP_JUMP_IF_NOT_NIL, [OP_LOOP] = &&lbl_OP_LOOP,
2730
1.27k
        [OP_CALL] = &&lbl_OP_CALL, [OP_CLOSURE] = &&lbl_OP_CLOSURE,
2731
1.27k
        [OP_RETURN] = &&lbl_OP_RETURN,
2732
1.27k
        [OP_ITER_INIT] = &&lbl_OP_ITER_INIT, [OP_ITER_NEXT] = &&lbl_OP_ITER_NEXT,
2733
1.27k
        [OP_BUILD_ARRAY] = &&lbl_OP_BUILD_ARRAY, [OP_ARRAY_FLATTEN] = &&lbl_OP_ARRAY_FLATTEN,
2734
1.27k
        [OP_BUILD_MAP] = &&lbl_OP_BUILD_MAP, [OP_BUILD_TUPLE] = &&lbl_OP_BUILD_TUPLE,
2735
1.27k
        [OP_BUILD_STRUCT] = &&lbl_OP_BUILD_STRUCT, [OP_BUILD_RANGE] = &&lbl_OP_BUILD_RANGE,
2736
1.27k
        [OP_BUILD_ENUM] = &&lbl_OP_BUILD_ENUM,
2737
1.27k
        [OP_INDEX] = &&lbl_OP_INDEX, [OP_SET_INDEX] = &&lbl_OP_SET_INDEX,
2738
1.27k
        [OP_GET_FIELD] = &&lbl_OP_GET_FIELD, [OP_SET_FIELD] = &&lbl_OP_SET_FIELD,
2739
1.27k
        [OP_INVOKE] = &&lbl_OP_INVOKE, [OP_INVOKE_LOCAL] = &&lbl_OP_INVOKE_LOCAL,
2740
1.27k
        [OP_INVOKE_GLOBAL] = &&lbl_OP_INVOKE_GLOBAL,
2741
1.27k
        [OP_SET_INDEX_LOCAL] = &&lbl_OP_SET_INDEX_LOCAL,
2742
1.27k
        [OP_PUSH_EXCEPTION_HANDLER] = &&lbl_OP_PUSH_EXCEPTION_HANDLER,
2743
1.27k
        [OP_POP_EXCEPTION_HANDLER] = &&lbl_OP_POP_EXCEPTION_HANDLER,
2744
1.27k
        [OP_THROW] = &&lbl_OP_THROW, [OP_TRY_UNWRAP] = &&lbl_OP_TRY_UNWRAP,
2745
1.27k
        [OP_DEFER_PUSH] = &&lbl_OP_DEFER_PUSH, [OP_DEFER_RUN] = &&lbl_OP_DEFER_RUN,
2746
1.27k
        [OP_FREEZE] = &&lbl_OP_FREEZE, [OP_THAW] = &&lbl_OP_THAW,
2747
1.27k
        [OP_CLONE] = &&lbl_OP_CLONE, [OP_MARK_FLUID] = &&lbl_OP_MARK_FLUID,
2748
1.27k
        [OP_REACT] = &&lbl_OP_REACT, [OP_UNREACT] = &&lbl_OP_UNREACT,
2749
1.27k
        [OP_BOND] = &&lbl_OP_BOND, [OP_UNBOND] = &&lbl_OP_UNBOND,
2750
1.27k
        [OP_SEED] = &&lbl_OP_SEED, [OP_UNSEED] = &&lbl_OP_UNSEED,
2751
1.27k
        [OP_FREEZE_VAR] = &&lbl_OP_FREEZE_VAR, [OP_THAW_VAR] = &&lbl_OP_THAW_VAR,
2752
1.27k
        [OP_SUBLIMATE_VAR] = &&lbl_OP_SUBLIMATE_VAR, [OP_SUBLIMATE] = &&lbl_OP_SUBLIMATE,
2753
1.27k
        [OP_PRINT] = &&lbl_OP_PRINT, [OP_IMPORT] = &&lbl_OP_IMPORT,
2754
1.27k
        [OP_SCOPE] = &&lbl_OP_SCOPE, [OP_SELECT] = &&lbl_OP_SELECT,
2755
1.27k
        [OP_INC_LOCAL] = &&lbl_OP_INC_LOCAL, [OP_DEC_LOCAL] = &&lbl_OP_DEC_LOCAL,
2756
1.27k
        [OP_ADD_INT] = &&lbl_OP_ADD_INT, [OP_SUB_INT] = &&lbl_OP_SUB_INT,
2757
1.27k
        [OP_MUL_INT] = &&lbl_OP_MUL_INT, [OP_LT_INT] = &&lbl_OP_LT_INT,
2758
1.27k
        [OP_LTEQ_INT] = &&lbl_OP_LTEQ_INT, [OP_LOAD_INT8] = &&lbl_OP_LOAD_INT8,
2759
1.27k
        [OP_CONSTANT_16] = &&lbl_OP_CONSTANT_16,
2760
1.27k
        [OP_GET_GLOBAL_16] = &&lbl_OP_GET_GLOBAL_16,
2761
1.27k
        [OP_SET_GLOBAL_16] = &&lbl_OP_SET_GLOBAL_16,
2762
1.27k
        [OP_DEFINE_GLOBAL_16] = &&lbl_OP_DEFINE_GLOBAL_16,
2763
1.27k
        [OP_CLOSURE_16] = &&lbl_OP_CLOSURE_16,
2764
1.27k
        [OP_RESET_EPHEMERAL] = &&lbl_OP_RESET_EPHEMERAL,
2765
1.27k
        [OP_SET_LOCAL_POP] = &&lbl_OP_SET_LOCAL_POP,
2766
1.27k
        [OP_CHECK_TYPE] = &&lbl_OP_CHECK_TYPE,
2767
1.27k
        [OP_CHECK_RETURN_TYPE] = &&lbl_OP_CHECK_RETURN_TYPE,
2768
1.27k
        [OP_IS_CRYSTAL] = &&lbl_OP_IS_CRYSTAL,
2769
1.27k
        [OP_FREEZE_EXCEPT] = &&lbl_OP_FREEZE_EXCEPT,
2770
1.27k
        [OP_FREEZE_FIELD] = &&lbl_OP_FREEZE_FIELD,
2771
1.27k
        [OP_HALT] = &&lbl_OP_HALT,
2772
1.27k
    };
2773
1.27k
#endif
2774
2775
70.7k
    for (;;) {
2776
70.7k
        uint8_t op = READ_BYTE();
2777
2778
70.7k
#ifdef VM_USE_COMPUTED_GOTO
2779
70.7k
        goto *dispatch_table[op];
2780
70.7k
#endif
2781
70.7k
        switch (op) {
2782
0
#ifdef VM_USE_COMPUTED_GOTO
2783
3.69k
            lbl_OP_CONSTANT:
2784
3.69k
#endif
2785
3.69k
            case OP_CONSTANT: {
2786
3.69k
                uint8_t idx = READ_BYTE();
2787
3.69k
                push(vm, value_clone_fast(&frame->chunk->constants[idx]));
2788
3.69k
                break;
2789
3.69k
            }
2790
2791
0
#ifdef VM_USE_COMPUTED_GOTO
2792
0
            lbl_OP_CONSTANT_16:
2793
0
#endif
2794
0
            case OP_CONSTANT_16: {
2795
0
                uint16_t idx = READ_U16();
2796
0
                push(vm, value_clone_fast(&frame->chunk->constants[idx]));
2797
0
                break;
2798
0
            }
2799
2800
0
#ifdef VM_USE_COMPUTED_GOTO
2801
860
            lbl_OP_NIL:
2802
860
#endif
2803
860
            case OP_NIL:   push(vm, value_nil()); break;
2804
0
#ifdef VM_USE_COMPUTED_GOTO
2805
95
            lbl_OP_TRUE:
2806
95
#endif
2807
95
            case OP_TRUE:  push(vm, value_bool(true)); break;
2808
0
#ifdef VM_USE_COMPUTED_GOTO
2809
245
            lbl_OP_FALSE:
2810
245
#endif
2811
245
            case OP_FALSE: push(vm, value_bool(false)); break;
2812
0
#ifdef VM_USE_COMPUTED_GOTO
2813
3.37k
            lbl_OP_UNIT:
2814
3.37k
#endif
2815
3.37k
            case OP_UNIT:  push(vm, value_unit()); break;
2816
2817
0
#ifdef VM_USE_COMPUTED_GOTO
2818
9.24k
            lbl_OP_POP:
2819
9.24k
#endif
2820
9.24k
            case OP_POP: {
2821
9.24k
                vm->stack_top--;
2822
9.24k
                value_free(vm->stack_top);
2823
9.24k
                break;
2824
9.24k
            }
2825
2826
0
#ifdef VM_USE_COMPUTED_GOTO
2827
87
            lbl_OP_DUP:
2828
87
#endif
2829
87
            case OP_DUP: {
2830
87
                push(vm, value_clone_fast(stackvm_peek(vm, 0)));
2831
87
                break;
2832
87
            }
2833
2834
0
#ifdef VM_USE_COMPUTED_GOTO
2835
27
            lbl_OP_SWAP:
2836
27
#endif
2837
27
            case OP_SWAP: {
2838
27
                LatValue a = vm->stack_top[-1];
2839
27
                vm->stack_top[-1] = vm->stack_top[-2];
2840
27
                vm->stack_top[-2] = a;
2841
27
                break;
2842
27
            }
2843
2844
0
#ifdef VM_USE_COMPUTED_GOTO
2845
1.07k
            lbl_OP_ADD:
2846
1.07k
#endif
2847
1.07k
            case OP_ADD: {
2848
1.07k
                LatValue b = pop(vm), a = pop(vm);
2849
1.07k
                if (a.type == VAL_INT && b.type == VAL_INT) {
2850
768
                    push(vm, value_int(a.as.int_val + b.as.int_val));
2851
768
                } else if (a.type == VAL_FLOAT && b.type == VAL_FLOAT) {
2852
2
                    push(vm, value_float(a.as.float_val + b.as.float_val));
2853
304
                } else if (a.type == VAL_INT && b.type == VAL_FLOAT) {
2854
0
                    push(vm, value_float((double)a.as.int_val + b.as.float_val));
2855
304
                } else if (a.type == VAL_FLOAT && b.type == VAL_INT) {
2856
0
                    push(vm, value_float(a.as.float_val + (double)b.as.int_val));
2857
304
                } else if (a.type == VAL_STR || b.type == VAL_STR) {
2858
304
                    const char *pa = (a.type == VAL_STR) ? a.as.str_val : NULL;
2859
304
                    const char *pb = (b.type == VAL_STR) ? b.as.str_val : NULL;
2860
304
                    char *ra = pa ? NULL : value_repr(&a);
2861
304
                    char *rb = pb ? NULL : value_repr(&b);
2862
304
                    if (!pa) pa = ra;
2863
304
                    if (!pb) pb = rb;
2864
304
                    size_t la = strlen(pa), lb = strlen(pb);
2865
304
                    LatValue result = stackvm_ephemeral_concat(vm, pa, la, pb, lb);
2866
304
                    free(ra); free(rb);
2867
304
                    value_free(&a); value_free(&b);
2868
304
                    push(vm, result);
2869
304
                    break;
2870
304
                } else {
2871
0
                    value_free(&a); value_free(&b);
2872
0
                    VM_ERROR("operands must be numbers for '+'"); break;
2873
0
                }
2874
770
                break;
2875
1.07k
            }
2876
2877
770
#ifdef VM_USE_COMPUTED_GOTO
2878
770
            lbl_OP_SUB:
2879
569
#endif
2880
569
            case OP_SUB: {
2881
569
                LatValue b = pop(vm), a = pop(vm);
2882
569
                if (a.type == VAL_INT && b.type == VAL_INT)
2883
568
                    push(vm, value_int(a.as.int_val - b.as.int_val));
2884
1
                else if (a.type == VAL_FLOAT && b.type == VAL_FLOAT)
2885
1
                    push(vm, value_float(a.as.float_val - b.as.float_val));
2886
0
                else if (a.type == VAL_INT && b.type == VAL_FLOAT)
2887
0
                    push(vm, value_float((double)a.as.int_val - b.as.float_val));
2888
0
                else if (a.type == VAL_FLOAT && b.type == VAL_INT)
2889
0
                    push(vm, value_float(a.as.float_val - (double)b.as.int_val));
2890
0
                else {
2891
0
                    value_free(&a); value_free(&b);
2892
0
                    VM_ERROR("operands must be numbers for '-'"); break;
2893
0
                }
2894
569
                break;
2895
569
            }
2896
2897
569
#ifdef VM_USE_COMPUTED_GOTO
2898
569
            lbl_OP_MUL:
2899
216
#endif
2900
216
            case OP_MUL: {
2901
216
                LatValue b = pop(vm), a = pop(vm);
2902
216
                if (a.type == VAL_INT && b.type == VAL_INT)
2903
214
                    push(vm, value_int(a.as.int_val * b.as.int_val));
2904
2
                else if (a.type == VAL_FLOAT && b.type == VAL_FLOAT)
2905
1
                    push(vm, value_float(a.as.float_val * b.as.float_val));
2906
1
                else if (a.type == VAL_INT && b.type == VAL_FLOAT)
2907
0
                    push(vm, value_float((double)a.as.int_val * b.as.float_val));
2908
1
                else if (a.type == VAL_FLOAT && b.type == VAL_INT)
2909
1
                    push(vm, value_float(a.as.float_val * (double)b.as.int_val));
2910
0
                else {
2911
0
                    value_free(&a); value_free(&b);
2912
0
                    VM_ERROR("operands must be numbers for '*'"); break;
2913
0
                }
2914
216
                break;
2915
216
            }
2916
2917
216
#ifdef VM_USE_COMPUTED_GOTO
2918
216
            lbl_OP_DIV:
2919
12
#endif
2920
12
            case OP_DIV: {
2921
12
                LatValue b = pop(vm), a = pop(vm);
2922
12
                if (a.type == VAL_INT && b.type == VAL_INT) {
2923
10
                    if (b.as.int_val == 0) { VM_ERROR("division by zero"); break; }
2924
2
                    push(vm, value_int(a.as.int_val / b.as.int_val));
2925
2
                } else if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) {
2926
2
                    double fa = a.type == VAL_INT ? (double)a.as.int_val : a.as.float_val;
2927
2
                    double fb = b.type == VAL_INT ? (double)b.as.int_val : b.as.float_val;
2928
                    /* Let IEEE 754 produce NaN/Inf for float division by zero */
2929
2
                    push(vm, value_float(fa / fb));
2930
2
                } else {
2931
0
                    value_free(&a); value_free(&b);
2932
0
                    VM_ERROR("operands must be numbers for '/'"); break;
2933
0
                }
2934
4
                break;
2935
12
            }
2936
2937
4
#ifdef VM_USE_COMPUTED_GOTO
2938
106
            lbl_OP_MOD:
2939
106
#endif
2940
106
            case OP_MOD: {
2941
106
                LatValue b = pop(vm), a = pop(vm);
2942
106
                if (a.type == VAL_INT && b.type == VAL_INT) {
2943
106
                    if (b.as.int_val == 0) { VM_ERROR("modulo by zero"); break; }
2944
106
                    push(vm, value_int(a.as.int_val % b.as.int_val));
2945
106
                } else {
2946
0
                    value_free(&a); value_free(&b);
2947
0
                    VM_ERROR("operands must be integers for '%%'"); break;
2948
0
                }
2949
106
                break;
2950
106
            }
2951
2952
106
#ifdef VM_USE_COMPUTED_GOTO
2953
106
            lbl_OP_NEG:
2954
1
#endif
2955
1
            case OP_NEG: {
2956
1
                LatValue a = pop(vm);
2957
1
                if (a.type == VAL_INT) push(vm, value_int(-a.as.int_val));
2958
0
                else if (a.type == VAL_FLOAT) push(vm, value_float(-a.as.float_val));
2959
0
                else {
2960
0
                    value_free(&a);
2961
0
                    VM_ERROR("operand must be a number for unary '-'"); break;
2962
0
                }
2963
1
                break;
2964
1
            }
2965
2966
1
#ifdef VM_USE_COMPUTED_GOTO
2967
32
            lbl_OP_NOT:
2968
32
#endif
2969
32
            case OP_NOT: {
2970
32
                LatValue a = pop(vm);
2971
32
                bool falsy_val = is_falsy(&a);
2972
32
                value_free(&a);
2973
32
                push(vm, value_bool(falsy_val));
2974
32
                break;
2975
32
            }
2976
2977
0
#ifdef VM_USE_COMPUTED_GOTO
2978
605
            lbl_OP_EQ:
2979
605
#endif
2980
605
            case OP_EQ: {
2981
605
                LatValue b = pop(vm), a = pop(vm);
2982
605
                bool eq = value_eq(&a, &b);
2983
605
                value_free(&a); value_free(&b);
2984
605
                push(vm, value_bool(eq));
2985
605
                break;
2986
605
            }
2987
0
#ifdef VM_USE_COMPUTED_GOTO
2988
52
            lbl_OP_NEQ:
2989
52
#endif
2990
52
            case OP_NEQ: {
2991
52
                LatValue b = pop(vm), a = pop(vm);
2992
52
                bool eq = value_eq(&a, &b);
2993
52
                value_free(&a); value_free(&b);
2994
52
                push(vm, value_bool(!eq));
2995
52
                break;
2996
52
            }
2997
2998
0
#ifdef VM_USE_COMPUTED_GOTO
2999
693
            lbl_OP_LT:
3000
693
#endif
3001
693
            case OP_LT: {
3002
693
                LatValue b = pop(vm), a = pop(vm);
3003
693
                if (a.type == VAL_INT && b.type == VAL_INT)
3004
691
                    push(vm, value_bool(a.as.int_val < b.as.int_val));
3005
2
                else if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) {
3006
2
                    double fa = a.type == VAL_INT ? (double)a.as.int_val : a.as.float_val;
3007
2
                    double fb = b.type == VAL_INT ? (double)b.as.int_val : b.as.float_val;
3008
2
                    push(vm, value_bool(fa < fb));
3009
2
                } else {
3010
0
                    value_free(&a); value_free(&b);
3011
0
                    VM_ERROR("operands must be numbers for '<'"); break;
3012
0
                }
3013
693
                break;
3014
693
            }
3015
693
#ifdef VM_USE_COMPUTED_GOTO
3016
693
            lbl_OP_GT:
3017
267
#endif
3018
267
            case OP_GT: {
3019
267
                LatValue b = pop(vm), a = pop(vm);
3020
267
                if (a.type == VAL_INT && b.type == VAL_INT)
3021
264
                    push(vm, value_bool(a.as.int_val > b.as.int_val));
3022
3
                else if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) {
3023
3
                    double fa = a.type == VAL_INT ? (double)a.as.int_val : a.as.float_val;
3024
3
                    double fb = b.type == VAL_INT ? (double)b.as.int_val : b.as.float_val;
3025
3
                    push(vm, value_bool(fa > fb));
3026
3
                } else {
3027
0
                    value_free(&a); value_free(&b);
3028
0
                    VM_ERROR("operands must be numbers for '>'"); break;
3029
0
                }
3030
267
                break;
3031
267
            }
3032
267
#ifdef VM_USE_COMPUTED_GOTO
3033
267
            lbl_OP_LTEQ:
3034
24
#endif
3035
24
            case OP_LTEQ: {
3036
24
                LatValue b = pop(vm), a = pop(vm);
3037
24
                if (a.type == VAL_INT && b.type == VAL_INT)
3038
24
                    push(vm, value_bool(a.as.int_val <= b.as.int_val));
3039
0
                else if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) {
3040
0
                    double fa = a.type == VAL_INT ? (double)a.as.int_val : a.as.float_val;
3041
0
                    double fb = b.type == VAL_INT ? (double)b.as.int_val : b.as.float_val;
3042
0
                    push(vm, value_bool(fa <= fb));
3043
0
                } else {
3044
0
                    value_free(&a); value_free(&b);
3045
0
                    VM_ERROR("operands must be numbers for '<='"); break;
3046
0
                }
3047
24
                break;
3048
24
            }
3049
24
#ifdef VM_USE_COMPUTED_GOTO
3050
212
            lbl_OP_GTEQ:
3051
212
#endif
3052
212
            case OP_GTEQ: {
3053
212
                LatValue b = pop(vm), a = pop(vm);
3054
212
                if (a.type == VAL_INT && b.type == VAL_INT)
3055
211
                    push(vm, value_bool(a.as.int_val >= b.as.int_val));
3056
1
                else if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) {
3057
1
                    double fa = a.type == VAL_INT ? (double)a.as.int_val : a.as.float_val;
3058
1
                    double fb = b.type == VAL_INT ? (double)b.as.int_val : b.as.float_val;
3059
1
                    push(vm, value_bool(fa >= fb));
3060
1
                } else {
3061
0
                    value_free(&a); value_free(&b);
3062
0
                    VM_ERROR("operands must be numbers for '>='"); break;
3063
0
                }
3064
212
                break;
3065
212
            }
3066
3067
            /* ── Bitwise operations ── */
3068
212
#ifdef VM_USE_COMPUTED_GOTO
3069
212
            lbl_OP_BIT_AND:
3070
5
#endif
3071
5
            case OP_BIT_AND: {
3072
5
                LatValue b = pop(vm), a = pop(vm);
3073
5
                if (a.type == VAL_INT && b.type == VAL_INT) {
3074
5
                    push(vm, value_int(a.as.int_val & b.as.int_val));
3075
5
                } else {
3076
0
                    value_free(&a); value_free(&b);
3077
0
                    VM_ERROR("operands must be integers for '&'"); break;
3078
0
                }
3079
5
                break;
3080
5
            }
3081
5
#ifdef VM_USE_COMPUTED_GOTO
3082
5
            lbl_OP_BIT_OR:
3083
4
#endif
3084
4
            case OP_BIT_OR: {
3085
4
                LatValue b = pop(vm), a = pop(vm);
3086
4
                if (a.type == VAL_INT && b.type == VAL_INT) {
3087
4
                    push(vm, value_int(a.as.int_val | b.as.int_val));
3088
4
                } else {
3089
0
                    value_free(&a); value_free(&b);
3090
0
                    VM_ERROR("operands must be integers for '|'"); break;
3091
0
                }
3092
4
                break;
3093
4
            }
3094
4
#ifdef VM_USE_COMPUTED_GOTO
3095
4
            lbl_OP_BIT_XOR:
3096
4
#endif
3097
4
            case OP_BIT_XOR: {
3098
4
                LatValue b = pop(vm), a = pop(vm);
3099
4
                if (a.type == VAL_INT && b.type == VAL_INT) {
3100
4
                    push(vm, value_int(a.as.int_val ^ b.as.int_val));
3101
4
                } else {
3102
0
                    value_free(&a); value_free(&b);
3103
0
                    VM_ERROR("operands must be integers for '^'"); break;
3104
0
                }
3105
4
                break;
3106
4
            }
3107
4
#ifdef VM_USE_COMPUTED_GOTO
3108
4
            lbl_OP_BIT_NOT:
3109
3
#endif
3110
3
            case OP_BIT_NOT: {
3111
3
                LatValue a = pop(vm);
3112
3
                if (a.type == VAL_INT) {
3113
3
                    push(vm, value_int(~a.as.int_val));
3114
3
                } else {
3115
0
                    value_free(&a);
3116
0
                    VM_ERROR("operand must be an integer for '~'"); break;
3117
0
                }
3118
3
                break;
3119
3
            }
3120
3
#ifdef VM_USE_COMPUTED_GOTO
3121
3
            lbl_OP_LSHIFT:
3122
3
#endif
3123
3
            case OP_LSHIFT: {
3124
3
                LatValue b = pop(vm), a = pop(vm);
3125
3
                if (a.type == VAL_INT && b.type == VAL_INT) {
3126
3
                    if (b.as.int_val < 0 || b.as.int_val > 63) {
3127
1
                        VM_ERROR("shift amount out of range (0..63)"); break;
3128
1
                    }
3129
2
                    push(vm, value_int(a.as.int_val << b.as.int_val));
3130
2
                } else {
3131
0
                    value_free(&a); value_free(&b);
3132
0
                    VM_ERROR("operands must be integers for '<<'"); break;
3133
0
                }
3134
2
                break;
3135
3
            }
3136
2
#ifdef VM_USE_COMPUTED_GOTO
3137
2
            lbl_OP_RSHIFT:
3138
2
#endif
3139
2
            case OP_RSHIFT: {
3140
2
                LatValue b = pop(vm), a = pop(vm);
3141
2
                if (a.type == VAL_INT && b.type == VAL_INT) {
3142
2
                    if (b.as.int_val < 0 || b.as.int_val > 63) {
3143
0
                        VM_ERROR("shift amount out of range (0..63)"); break;
3144
0
                    }
3145
2
                    push(vm, value_int(a.as.int_val >> b.as.int_val));
3146
2
                } else {
3147
0
                    value_free(&a); value_free(&b);
3148
0
                    VM_ERROR("operands must be integers for '>>'"); break;
3149
0
                }
3150
2
                break;
3151
2
            }
3152
3153
2
#ifdef VM_USE_COMPUTED_GOTO
3154
4
            lbl_OP_CONCAT:
3155
4
#endif
3156
4
            case OP_CONCAT: {
3157
4
                LatValue b = pop(vm), a = pop(vm);
3158
4
                const char *pa = (a.type == VAL_STR) ? a.as.str_val : NULL;
3159
4
                const char *pb = (b.type == VAL_STR) ? b.as.str_val : NULL;
3160
4
                char *ra = pa ? NULL : value_repr(&a);
3161
4
                char *rb = pb ? NULL : value_repr(&b);
3162
4
                if (!pa) pa = ra;
3163
4
                if (!pb) pb = rb;
3164
4
                size_t la = strlen(pa), lb = strlen(pb);
3165
4
                LatValue result = stackvm_ephemeral_concat(vm, pa, la, pb, lb);
3166
4
                free(ra); free(rb);
3167
4
                value_free(&a); value_free(&b);
3168
4
                push(vm, result);
3169
4
                break;
3170
4
            }
3171
3172
            /* ── Variables ── */
3173
0
#ifdef VM_USE_COMPUTED_GOTO
3174
7.48k
            lbl_OP_GET_LOCAL:
3175
7.48k
#endif
3176
7.48k
            case OP_GET_LOCAL: {
3177
7.48k
                uint8_t slot = READ_BYTE();
3178
7.48k
                push(vm, value_clone_fast(&frame->slots[slot]));
3179
7.48k
                break;
3180
7.48k
            }
3181
3182
0
#ifdef VM_USE_COMPUTED_GOTO
3183
27
            lbl_OP_SET_LOCAL:
3184
27
#endif
3185
27
            case OP_SET_LOCAL: {
3186
27
                uint8_t slot = READ_BYTE();
3187
27
                value_free(&frame->slots[slot]);
3188
27
                frame->slots[slot] = value_clone_fast(stackvm_peek(vm, 0));
3189
                /* Record history for tracked variables */
3190
27
                if (vm->rt->tracking_active && frame->chunk->local_names &&
3191
27
                    slot < frame->chunk->local_name_cap && frame->chunk->local_names[slot]) {
3192
0
                    stackvm_record_history(vm, frame->chunk->local_names[slot], &frame->slots[slot]);
3193
0
                }
3194
27
                break;
3195
27
            }
3196
3197
0
#ifdef VM_USE_COMPUTED_GOTO
3198
439
            lbl_OP_SET_LOCAL_POP:
3199
439
#endif
3200
439
            case OP_SET_LOCAL_POP: {
3201
439
                uint8_t slot = READ_BYTE();
3202
439
                LatValue *dest = &frame->slots[slot];
3203
439
                value_free(dest);
3204
439
                *dest = value_clone_fast(stackvm_peek(vm, 0));
3205
439
                if (vm->rt->tracking_active && frame->chunk->local_names &&
3206
439
                    slot < frame->chunk->local_name_cap && frame->chunk->local_names[slot]) {
3207
14
                    stackvm_record_history(vm, frame->chunk->local_names[slot], dest);
3208
14
                }
3209
439
                vm->stack_top--;
3210
439
                break;
3211
439
            }
3212
3213
0
#ifdef VM_USE_COMPUTED_GOTO
3214
3.40k
            lbl_OP_GET_GLOBAL:
3215
3.40k
#endif
3216
3.40k
            case OP_GET_GLOBAL: {
3217
3.40k
                uint8_t idx = READ_BYTE();
3218
3.40k
                const char *name = frame->chunk->constants[idx].as.str_val;
3219
3.40k
                size_t hash = frame->chunk->const_hashes[idx];
3220
3.40k
                LatValue *ref = env_get_ref_prehashed(vm->env, name, hash);
3221
3.40k
                if (!ref) {
3222
4
                    VM_ERROR("undefined variable '%s'", name); break;
3223
4
                }
3224
3.39k
                if (ref->type == VAL_CLOSURE && ref->as.closure.native_fn != NULL &&
3225
3.39k
                    (ref->as.closure.default_values == VM_NATIVE_MARKER ||
3226
3.30k
                     ref->as.closure.default_values == VM_EXT_MARKER)) {
3227
1.50k
                    push(vm, *ref);
3228
1.89k
                } else {
3229
1.89k
                    push(vm, value_clone_fast(ref));
3230
1.89k
                }
3231
3.39k
                break;
3232
3.40k
            }
3233
3234
0
#ifdef VM_USE_COMPUTED_GOTO
3235
0
            lbl_OP_GET_GLOBAL_16:
3236
0
#endif
3237
0
            case OP_GET_GLOBAL_16: {
3238
0
                uint16_t idx = READ_U16();
3239
0
                const char *name = frame->chunk->constants[idx].as.str_val;
3240
0
                size_t hash = frame->chunk->const_hashes[idx];
3241
0
                LatValue *ref = env_get_ref_prehashed(vm->env, name, hash);
3242
0
                if (!ref) {
3243
0
                    VM_ERROR("undefined variable '%s'", name); break;
3244
0
                }
3245
0
                if (ref->type == VAL_CLOSURE && ref->as.closure.native_fn != NULL &&
3246
0
                    (ref->as.closure.default_values == VM_NATIVE_MARKER ||
3247
0
                     ref->as.closure.default_values == VM_EXT_MARKER)) {
3248
0
                    push(vm, *ref);
3249
0
                } else {
3250
0
                    push(vm, value_clone_fast(ref));
3251
0
                }
3252
0
                break;
3253
0
            }
3254
3255
0
#ifdef VM_USE_COMPUTED_GOTO
3256
11
            lbl_OP_SET_GLOBAL:
3257
11
#endif
3258
11
            case OP_SET_GLOBAL: {
3259
11
                uint8_t idx = READ_BYTE();
3260
11
                const char *name = frame->chunk->constants[idx].as.str_val;
3261
11
                LatValue *val = stackvm_peek(vm, 0);
3262
11
                env_set(vm->env, name, value_clone_fast(val));
3263
11
                if (vm->rt->tracking_active) {
3264
0
                    stackvm_record_history(vm, name, val);
3265
0
                }
3266
11
                break;
3267
11
            }
3268
3269
0
#ifdef VM_USE_COMPUTED_GOTO
3270
0
            lbl_OP_SET_GLOBAL_16:
3271
0
#endif
3272
0
            case OP_SET_GLOBAL_16: {
3273
0
                uint16_t idx = READ_U16();
3274
0
                const char *name = frame->chunk->constants[idx].as.str_val;
3275
0
                LatValue *val = stackvm_peek(vm, 0);
3276
0
                env_set(vm->env, name, value_clone_fast(val));
3277
0
                if (vm->rt->tracking_active) {
3278
0
                    stackvm_record_history(vm, name, val);
3279
0
                }
3280
0
                break;
3281
0
            }
3282
3283
0
#ifdef VM_USE_COMPUTED_GOTO
3284
2.89k
            lbl_OP_DEFINE_GLOBAL:
3285
2.89k
#endif
3286
2.89k
            case OP_DEFINE_GLOBAL: {
3287
2.89k
                uint8_t idx = READ_BYTE();
3288
2.89k
                const char *name = frame->chunk->constants[idx].as.str_val;
3289
2.89k
                LatValue val = pop(vm);
3290
2.89k
                stackvm_promote_value(&val);
3291
3292
                /* Phase-dispatch overloading: if defining a phase-constrained
3293
                 * closure and one already exists, create an overload array */
3294
2.89k
                if (val.type == VAL_CLOSURE && val.as.closure.native_fn != NULL &&
3295
2.89k
                    val.as.closure.default_values != VM_NATIVE_MARKER &&
3296
2.89k
                    val.as.closure.default_values != VM_EXT_MARKER) {
3297
2.68k
                    Chunk *ch = (Chunk *)val.as.closure.native_fn;
3298
2.68k
                    if (ch->param_phases) {
3299
13
                        LatValue existing;
3300
13
                        if (env_get(vm->env, name, &existing)) {
3301
2
                            if (existing.type == VAL_CLOSURE && existing.as.closure.native_fn != NULL &&
3302
2
                                existing.as.closure.default_values != VM_NATIVE_MARKER &&
3303
2
                                existing.as.closure.default_values != VM_EXT_MARKER) {
3304
2
                                Chunk *ech = (Chunk *)existing.as.closure.native_fn;
3305
2
                                if (ech->param_phases) {
3306
2
                                    LatValue elems[2] = { existing, val };
3307
2
                                    LatValue arr = value_array(elems, 2);
3308
2
                                    value_free(&existing);
3309
2
                                    value_free(&val);
3310
2
                                    env_define(vm->env, name, arr);
3311
2
                                    break;
3312
2
                                }
3313
2
                            } else if (existing.type == VAL_ARRAY) {
3314
                                /* Append to existing overload set */
3315
0
                                size_t new_len = existing.as.array.len + 1;
3316
0
                                LatValue *new_elems = malloc(new_len * sizeof(LatValue));
3317
0
                                for (size_t i = 0; i < existing.as.array.len; i++)
3318
0
                                    new_elems[i] = existing.as.array.elems[i];
3319
0
                                new_elems[existing.as.array.len] = val;
3320
0
                                LatValue arr = value_array(new_elems, new_len);
3321
0
                                free(new_elems);
3322
0
                                value_free(&existing);
3323
0
                                value_free(&val);
3324
0
                                env_define(vm->env, name, arr);
3325
0
                                break;
3326
0
                            }
3327
0
                            value_free(&existing);
3328
0
                        }
3329
13
                    }
3330
2.68k
                }
3331
3332
2.89k
                env_define(vm->env, name, val);
3333
2.89k
                break;
3334
2.89k
            }
3335
3336
0
#ifdef VM_USE_COMPUTED_GOTO
3337
0
            lbl_OP_DEFINE_GLOBAL_16:
3338
0
#endif
3339
0
            case OP_DEFINE_GLOBAL_16: {
3340
0
                uint16_t idx = READ_U16();
3341
0
                const char *name = frame->chunk->constants[idx].as.str_val;
3342
0
                LatValue val = pop(vm);
3343
0
                stackvm_promote_value(&val);
3344
0
                env_define(vm->env, name, val);
3345
0
                break;
3346
0
            }
3347
3348
0
#ifdef VM_USE_COMPUTED_GOTO
3349
692
            lbl_OP_GET_UPVALUE:
3350
692
#endif
3351
692
            case OP_GET_UPVALUE: {
3352
692
                uint8_t slot = READ_BYTE();
3353
692
                if (frame->upvalues && slot < frame->upvalue_count && frame->upvalues[slot]) {
3354
692
                    push(vm, value_clone_fast(frame->upvalues[slot]->location));
3355
692
                } else {
3356
0
                    push(vm, value_nil());
3357
0
                }
3358
692
                break;
3359
692
            }
3360
3361
0
#ifdef VM_USE_COMPUTED_GOTO
3362
0
            lbl_OP_SET_UPVALUE:
3363
0
#endif
3364
0
            case OP_SET_UPVALUE: {
3365
0
                uint8_t slot = READ_BYTE();
3366
0
                if (frame->upvalues && slot < frame->upvalue_count && frame->upvalues[slot]) {
3367
0
                    value_free(frame->upvalues[slot]->location);
3368
0
                    *frame->upvalues[slot]->location = value_clone_fast(stackvm_peek(vm, 0));
3369
0
                }
3370
0
                break;
3371
0
            }
3372
3373
0
#ifdef VM_USE_COMPUTED_GOTO
3374
0
            lbl_OP_CLOSE_UPVALUE:
3375
0
#endif
3376
0
            case OP_CLOSE_UPVALUE: {
3377
0
                close_upvalues(vm, vm->stack_top - 1);
3378
0
                LatValue v = pop(vm);
3379
0
                value_free(&v);
3380
0
                break;
3381
0
            }
3382
3383
            /* ── Jumps ── */
3384
0
#ifdef VM_USE_COMPUTED_GOTO
3385
601
            lbl_OP_JUMP:
3386
601
#endif
3387
601
            case OP_JUMP: {
3388
601
                uint16_t offset = READ_U16();
3389
601
                frame->ip += offset;
3390
601
                break;
3391
601
            }
3392
3393
0
#ifdef VM_USE_COMPUTED_GOTO
3394
2.09k
            lbl_OP_JUMP_IF_FALSE:
3395
2.09k
#endif
3396
2.09k
            case OP_JUMP_IF_FALSE: {
3397
2.09k
                uint16_t offset = READ_U16();
3398
2.09k
                if (is_falsy(stackvm_peek(vm, 0)))
3399
1.30k
                    frame->ip += offset;
3400
2.09k
                break;
3401
2.09k
            }
3402
3403
0
#ifdef VM_USE_COMPUTED_GOTO
3404
9
            lbl_OP_JUMP_IF_TRUE:
3405
9
#endif
3406
9
            case OP_JUMP_IF_TRUE: {
3407
9
                uint16_t offset = READ_U16();
3408
9
                if (!is_falsy(stackvm_peek(vm, 0)))
3409
5
                    frame->ip += offset;
3410
9
                break;
3411
9
            }
3412
3413
0
#ifdef VM_USE_COMPUTED_GOTO
3414
36
            lbl_OP_JUMP_IF_NOT_NIL:
3415
36
#endif
3416
36
            case OP_JUMP_IF_NOT_NIL: {
3417
36
                uint16_t offset = READ_U16();
3418
36
                if (stackvm_peek(vm, 0)->type != VAL_NIL)
3419
22
                    frame->ip += offset;
3420
36
                break;
3421
36
            }
3422
3423
0
#ifdef VM_USE_COMPUTED_GOTO
3424
586
            lbl_OP_LOOP:
3425
586
#endif
3426
586
            case OP_LOOP: {
3427
586
                uint16_t offset = READ_U16();
3428
586
                frame->ip -= offset;
3429
586
                break;
3430
586
            }
3431
3432
            /* ── Functions/closures ── */
3433
0
#ifdef VM_USE_COMPUTED_GOTO
3434
4.00k
            lbl_OP_CALL:
3435
4.00k
#endif
3436
4.00k
            case OP_CALL: {
3437
4.00k
                uint8_t arg_count = READ_BYTE();
3438
4.00k
                LatValue *callee = stackvm_peek(vm, arg_count);
3439
3440
4.00k
                if (callee->type == VAL_CLOSURE && callee->as.closure.native_fn != NULL
3441
4.00k
                    && callee->as.closure.default_values == VM_NATIVE_MARKER) {
3442
                    /* StackVM native builtin function */
3443
1.50k
                    VMNativeFn native = (VMNativeFn)callee->as.closure.native_fn;
3444
1.50k
                    LatValue *args = (arg_count <= 16) ? vm->fast_args
3445
1.50k
                                   : malloc(arg_count * sizeof(LatValue));
3446
2.70k
                    for (int i = arg_count - 1; i >= 0; i--)
3447
1.19k
                        args[i] = pop(vm);
3448
1.50k
                    LatValue callee_val = pop(vm); /* pop callee */
3449
1.50k
                    LatValue ret = native(args, arg_count);
3450
                    /* Bridge: native errors from runtime to StackVM */
3451
1.50k
                    if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; }
3452
2.70k
                    for (int i = 0; i < arg_count; i++)
3453
1.19k
                        value_free(&args[i]);
3454
1.50k
                    if (arg_count > 16) free(args);
3455
1.50k
                    value_free(&callee_val);
3456
                    /* Check if native set an error (e.g. grow() seed failure) */
3457
1.50k
                    if (vm->error) {
3458
62
                        value_free(&ret);
3459
62
                        StackVMResult r = stackvm_handle_native_error(vm, &frame);
3460
62
                        if (r != STACKVM_OK) return r;
3461
13
                        break;
3462
62
                    }
3463
1.44k
                    push(vm, ret);
3464
1.44k
                    break;
3465
1.50k
                }
3466
3467
2.49k
                if (callee->type == VAL_CLOSURE && callee->as.closure.native_fn != NULL
3468
2.49k
                    && callee->as.closure.default_values == VM_EXT_MARKER) {
3469
                    /* Extension native function — call via ext_call_native() */
3470
64
                    LatValue *args = (arg_count <= 16) ? vm->fast_args
3471
64
                                   : malloc(arg_count * sizeof(LatValue));
3472
166
                    for (int i = arg_count - 1; i >= 0; i--)
3473
102
                        args[i] = pop(vm);
3474
64
                    LatValue callee_val = pop(vm);
3475
64
                    LatValue ret = ext_call_native(callee_val.as.closure.native_fn,
3476
64
                                                   args, (size_t)arg_count);
3477
166
                    for (int i = 0; i < arg_count; i++)
3478
102
                        value_free(&args[i]);
3479
64
                    if (arg_count > 16) free(args);
3480
64
                    value_free(&callee_val);
3481
                    /* Extension errors return strings prefixed with "EVAL_ERROR:" */
3482
64
                    if (ret.type == VAL_STR && strncmp(ret.as.str_val, "EVAL_ERROR:", 11) == 0) {
3483
3
                        vm->error = strdup(ret.as.str_val + 11);
3484
3
                        value_free(&ret);
3485
3
                        StackVMResult r = stackvm_handle_native_error(vm, &frame);
3486
3
                        if (r != STACKVM_OK) return r;
3487
0
                        break;
3488
3
                    }
3489
61
                    push(vm, ret);
3490
61
                    break;
3491
64
                }
3492
3493
                /* Phase-dispatch overload resolution: VAL_ARRAY of closures */
3494
2.43k
                if (callee->type == VAL_ARRAY) {
3495
2
                    int best_score = -1;
3496
2
                    int best_idx = -1;
3497
6
                    for (size_t ci = 0; ci < callee->as.array.len; ci++) {
3498
4
                        LatValue *cand = &callee->as.array.elems[ci];
3499
4
                        if (cand->type != VAL_CLOSURE || cand->as.closure.native_fn == NULL) continue;
3500
4
                        if (cand->as.closure.default_values == VM_NATIVE_MARKER) continue;
3501
4
                        if (cand->as.closure.default_values == VM_EXT_MARKER) continue;
3502
4
                        Chunk *ch = (Chunk *)cand->as.closure.native_fn;
3503
4
                        if (!ch->param_phases) continue;
3504
4
                        bool compatible = true;
3505
4
                        int score = 0;
3506
6
                        for (int j = 0; j < ch->param_phase_count && j < (int)arg_count; j++) {
3507
4
                            uint8_t pp = ch->param_phases[j];
3508
4
                            LatValue *arg = stackvm_peek(vm, arg_count - 1 - j);
3509
4
                            if (pp == PHASE_FLUID) {
3510
2
                                if (arg->phase == VTAG_CRYSTAL) { compatible = false; break; }
3511
1
                                if (arg->phase == VTAG_FLUID) score += 3;
3512
0
                                else score += 1;
3513
2
                            } else if (pp == PHASE_CRYSTAL) {
3514
2
                                if (arg->phase == VTAG_FLUID) { compatible = false; break; }
3515
1
                                if (arg->phase == VTAG_CRYSTAL) score += 3;
3516
0
                                else score += 1;
3517
1
                            } else {
3518
0
                                if (arg->phase == VTAG_UNPHASED) score += 2;
3519
0
                                else score += 1;
3520
0
                            }
3521
4
                        }
3522
4
                        if (compatible && score > best_score) {
3523
2
                            best_score = score;
3524
2
                            best_idx = (int)ci;
3525
2
                        }
3526
4
                    }
3527
2
                    if (best_idx >= 0) {
3528
2
                        LatValue matched = value_clone_fast(&callee->as.array.elems[best_idx]);
3529
2
                        value_free(callee);
3530
2
                        *callee = matched;
3531
                        /* Fall through to compiled closure call below */
3532
2
                    } else {
3533
0
                        VM_ERROR("no matching overload for given argument phases"); break;
3534
0
                    }
3535
2
                }
3536
3537
2.43k
                if (callee->type == VAL_CLOSURE && callee->as.closure.native_fn != NULL) {
3538
                    /* Compiled function call */
3539
2.43k
                    Chunk *fn_chunk = (Chunk *)callee->as.closure.native_fn;
3540
2.43k
                    int arity = (int)callee->as.closure.param_count;
3541
3542
                    /* Phase constraint check */
3543
2.43k
                    if (fn_chunk->param_phases) {
3544
10
                        bool phase_mismatch = false;
3545
17
                        for (int i = 0; i < fn_chunk->param_phase_count && i < (int)arg_count; i++) {
3546
10
                            uint8_t pp = fn_chunk->param_phases[i];
3547
10
                            if (pp == PHASE_UNSPECIFIED) continue;
3548
10
                            LatValue *arg = stackvm_peek(vm, arg_count - 1 - i);
3549
10
                            if (pp == PHASE_FLUID && arg->phase == VTAG_CRYSTAL) { phase_mismatch = true; break; }
3550
8
                            if (pp == PHASE_CRYSTAL && arg->phase == VTAG_FLUID) { phase_mismatch = true; break; }
3551
8
                        }
3552
10
                        if (phase_mismatch) {
3553
3
                            VM_ERROR("phase constraint violation in function '%s'",
3554
3
                                fn_chunk->name ? fn_chunk->name : "<anonymous>");
3555
0
                            break;
3556
3
                        }
3557
10
                    }
3558
3559
2.43k
                    int adjusted = stackvm_adjust_call_args(vm, fn_chunk, arity, (int)arg_count);
3560
2.43k
                    if (adjusted < 0) {
3561
0
                        char *err = vm->error; vm->error = NULL;
3562
0
                        VM_ERROR("%s", err); free(err); break;
3563
0
                    }
3564
2.43k
                    (void)adjusted;
3565
3566
2.43k
                    if (vm->frame_count >= STACKVM_FRAMES_MAX) {
3567
0
                        VM_ERROR("stack overflow (too many nested calls)"); break;
3568
0
                    }
3569
3570
2.43k
                    stackvm_promote_frame_ephemerals(vm, frame);
3571
3572
                    /* Get upvalues from the callee closure */
3573
2.43k
                    ObjUpvalue **callee_upvalues = (ObjUpvalue **)callee->as.closure.captured_env;
3574
2.43k
                    size_t callee_upvalue_count = callee->region_id != REGION_NONE ?
3575
2.42k
                        callee->region_id : 0;
3576
3577
2.43k
                    StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
3578
2.43k
                    new_frame->chunk = fn_chunk;
3579
2.43k
                    new_frame->ip = fn_chunk->code;
3580
2.43k
                    new_frame->slots = callee;
3581
2.43k
                    new_frame->upvalues = callee_upvalues;
3582
2.43k
                    new_frame->upvalue_count = callee_upvalue_count;
3583
2.43k
                    frame = new_frame;
3584
2.43k
                    break;
3585
2.43k
                }
3586
3587
                /* Unknown callee type - pop args and callee, push nil */
3588
0
                for (int i = 0; i < arg_count; i++) {
3589
0
                    LatValue v = pop(vm);
3590
0
                    value_free(&v);
3591
0
                }
3592
0
                LatValue callee_val = pop(vm);
3593
0
                value_free(&callee_val);
3594
0
                push(vm, value_nil());
3595
0
                break;
3596
2.43k
            }
3597
3598
0
#ifdef VM_USE_COMPUTED_GOTO
3599
2.80k
            lbl_OP_CLOSURE:
3600
2.80k
#endif
3601
2.80k
            case OP_CLOSURE: {
3602
2.80k
                uint8_t fn_idx = READ_BYTE();
3603
2.80k
                uint8_t upvalue_count = READ_BYTE();
3604
2.80k
                LatValue fn_val = value_clone_fast(&frame->chunk->constants[fn_idx]);
3605
3606
                /* Read upvalue descriptors and capture */
3607
2.80k
                ObjUpvalue **upvalues = NULL;
3608
2.80k
                if (upvalue_count > 0) {
3609
30
                    upvalues = calloc(upvalue_count, sizeof(ObjUpvalue *));
3610
78
                    for (uint8_t i = 0; i < upvalue_count; i++) {
3611
48
                        uint8_t is_local = READ_BYTE();
3612
48
                        uint8_t index = READ_BYTE();
3613
48
                        if (is_local) {
3614
48
                            upvalues[i] = capture_upvalue(vm, &frame->slots[index]);
3615
48
                        } else {
3616
0
                            if (frame->upvalues && index < frame->upvalue_count)
3617
0
                                upvalues[i] = frame->upvalues[index];
3618
0
                            else
3619
0
                                upvalues[i] = new_upvalue(&frame->slots[0]); /* fallback */
3620
0
                        }
3621
48
                    }
3622
30
                }
3623
3624
                /* Store upvalues in the closure value for later OP_CALL.
3625
                 * We pack them into the closure's captured_env as a hack. */
3626
2.80k
                fn_val.as.closure.captured_env = (Env *)upvalues;
3627
2.80k
                fn_val.as.closure.has_variadic = (upvalue_count > 0);
3628
2.80k
                fn_val.region_id = (size_t)upvalue_count;
3629
3630
                /* Track the chunk so it gets freed */
3631
2.80k
                if (fn_val.as.closure.native_fn) {
3632
                    /* Don't double-track - the chunk is in the constant pool already */
3633
2.80k
                }
3634
3635
2.80k
                push(vm, fn_val);
3636
2.80k
                break;
3637
2.80k
            }
3638
3639
0
#ifdef VM_USE_COMPUTED_GOTO
3640
0
            lbl_OP_CLOSURE_16:
3641
0
#endif
3642
0
            case OP_CLOSURE_16: {
3643
0
                uint16_t fn_idx = READ_U16();
3644
0
                uint8_t upvalue_count = READ_BYTE();
3645
0
                LatValue fn_val = value_clone_fast(&frame->chunk->constants[fn_idx]);
3646
3647
0
                ObjUpvalue **upvalues = NULL;
3648
0
                if (upvalue_count > 0) {
3649
0
                    upvalues = calloc(upvalue_count, sizeof(ObjUpvalue *));
3650
0
                    for (uint8_t i = 0; i < upvalue_count; i++) {
3651
0
                        uint8_t is_local = READ_BYTE();
3652
0
                        uint8_t index = READ_BYTE();
3653
0
                        if (is_local) {
3654
0
                            upvalues[i] = capture_upvalue(vm, &frame->slots[index]);
3655
0
                        } else {
3656
0
                            if (frame->upvalues && index < frame->upvalue_count)
3657
0
                                upvalues[i] = frame->upvalues[index];
3658
0
                            else
3659
0
                                upvalues[i] = new_upvalue(&frame->slots[0]);
3660
0
                        }
3661
0
                    }
3662
0
                }
3663
3664
0
                fn_val.as.closure.captured_env = (Env *)upvalues;
3665
0
                fn_val.as.closure.has_variadic = (upvalue_count > 0);
3666
0
                fn_val.region_id = (size_t)upvalue_count;
3667
0
                push(vm, fn_val);
3668
0
                break;
3669
0
            }
3670
3671
0
#ifdef VM_USE_COMPUTED_GOTO
3672
3.66k
            lbl_OP_RETURN:
3673
3.66k
#endif
3674
3.66k
            case OP_RETURN: {
3675
3.66k
                LatValue ret = pop(vm);
3676
                /* For defer frames (cleanup_base != NULL), only close upvalues
3677
                 * and free stack values above the entry point, preserving
3678
                 * the parent frame's locals that we share via next_frame_slots. */
3679
3.66k
                LatValue *base = frame->cleanup_base ? frame->cleanup_base : frame->slots;
3680
3.66k
                close_upvalues(vm, base);
3681
3.66k
                vm->frame_count--;
3682
3.66k
                if (vm->frame_count == base_frame) {
3683
                    /* Returned to the entry frame of this stackvm_run invocation.
3684
                     * Free remaining stack values down to our entry point. */
3685
1.17k
                    while (vm->stack_top > base) {
3686
0
                        vm->stack_top--;
3687
0
                        value_free(vm->stack_top);
3688
0
                    }
3689
1.17k
                    *result = ret;
3690
1.17k
                    return STACKVM_OK;
3691
1.17k
                }
3692
                /* Free the callee and args/locals from this frame */
3693
8.76k
                while (vm->stack_top > base) {
3694
6.27k
                    vm->stack_top--;
3695
6.27k
                    value_free(vm->stack_top);
3696
6.27k
                }
3697
2.49k
                push(vm, ret);
3698
2.49k
                frame = &vm->frames[vm->frame_count - 1];
3699
2.49k
                break;
3700
3.66k
            }
3701
3702
            /* ── Iterators ── */
3703
0
#ifdef VM_USE_COMPUTED_GOTO
3704
47
            lbl_OP_ITER_INIT:
3705
47
#endif
3706
47
            case OP_ITER_INIT: {
3707
                /* TOS is a range, array, map, or set. Push index 0 on top. */
3708
47
                LatValue *iter = stackvm_peek(vm, 0);
3709
47
                if (iter->type == VAL_MAP || iter->type == VAL_SET) {
3710
                    /* Convert map/set to array for iteration */
3711
2
                    stackvm_iter_convert_to_array(iter);
3712
2
                }
3713
47
                if (iter->type != VAL_RANGE && iter->type != VAL_ARRAY) {
3714
0
                    VM_ERROR("cannot iterate over %s", value_type_name(iter)); break;
3715
0
                }
3716
47
                push(vm, value_int(0)); /* index */
3717
47
                break;
3718
47
            }
3719
3720
0
#ifdef VM_USE_COMPUTED_GOTO
3721
353
            lbl_OP_ITER_NEXT:
3722
353
#endif
3723
353
            case OP_ITER_NEXT: {
3724
353
                uint16_t offset = READ_U16();
3725
353
                LatValue *idx_val = stackvm_peek(vm, 0);  /* index */
3726
353
                LatValue *iter = stackvm_peek(vm, 1);      /* collection */
3727
353
                int64_t idx = idx_val->as.int_val;
3728
3729
353
                if (iter->type == VAL_RANGE) {
3730
237
                    if (idx >= iter->as.range.end - iter->as.range.start) {
3731
                        /* Exhausted */
3732
8
                        frame->ip += offset;
3733
8
                        break;
3734
8
                    }
3735
                    /* Push next value */
3736
229
                    push(vm, value_int(iter->as.range.start + idx));
3737
                    /* Increment index */
3738
229
                    idx_val->as.int_val = idx + 1;
3739
229
                } else if (iter->type == VAL_ARRAY) {
3740
116
                    if ((size_t)idx >= iter->as.array.len) {
3741
39
                        frame->ip += offset;
3742
39
                        break;
3743
39
                    }
3744
77
                    push(vm, value_clone_fast(&iter->as.array.elems[idx]));
3745
77
                    idx_val->as.int_val = idx + 1;
3746
77
                } else {
3747
0
                    frame->ip += offset;
3748
0
                }
3749
306
                break;
3750
353
            }
3751
3752
            /* ── Data structures ── */
3753
306
#ifdef VM_USE_COMPUTED_GOTO
3754
306
            lbl_OP_BUILD_ARRAY:
3755
256
#endif
3756
256
            case OP_BUILD_ARRAY: {
3757
256
                uint8_t count = READ_BYTE();
3758
256
                LatValue *elems = NULL;
3759
256
                LatValue elem_buf[16];
3760
256
                if (count > 0) {
3761
195
                    elems = (count <= 16) ? elem_buf : malloc(count * sizeof(LatValue));
3762
733
                    for (int i = count - 1; i >= 0; i--)
3763
538
                        elems[i] = pop(vm);
3764
733
                    for (int i = 0; i < count; i++)
3765
538
                        stackvm_promote_value(&elems[i]);
3766
195
                }
3767
256
                LatValue arr = value_array(elems, count);
3768
256
                if (count > 16) free(elems);
3769
256
                push(vm, arr);
3770
256
                break;
3771
256
            }
3772
3773
0
#ifdef VM_USE_COMPUTED_GOTO
3774
6
            lbl_OP_ARRAY_FLATTEN:
3775
6
#endif
3776
6
            case OP_ARRAY_FLATTEN: {
3777
                /* One-level flatten for spread operator: [1, [2, 3], 4] → [1, 2, 3, 4] */
3778
6
                LatValue arr = pop(vm);
3779
6
                if (arr.type != VAL_ARRAY) {
3780
0
                    push(vm, arr);
3781
0
                    break;
3782
0
                }
3783
6
                size_t total = 0;
3784
19
                for (size_t i = 0; i < arr.as.array.len; i++) {
3785
13
                    if (arr.as.array.elems[i].type == VAL_ARRAY)
3786
9
                        total += arr.as.array.elems[i].as.array.len;
3787
4
                    else
3788
4
                        total++;
3789
13
                }
3790
6
                LatValue *flat = malloc(total * sizeof(LatValue));
3791
6
                size_t pos = 0;
3792
19
                for (size_t i = 0; i < arr.as.array.len; i++) {
3793
13
                    if (arr.as.array.elems[i].type == VAL_ARRAY) {
3794
9
                        LatValue *inner = arr.as.array.elems[i].as.array.elems;
3795
27
                        for (size_t j = 0; j < arr.as.array.elems[i].as.array.len; j++)
3796
18
                            flat[pos++] = value_deep_clone(&inner[j]);
3797
9
                    } else {
3798
4
                        flat[pos++] = value_deep_clone(&arr.as.array.elems[i]);
3799
4
                    }
3800
13
                }
3801
6
                value_free(&arr);
3802
6
                LatValue result_arr = value_array(flat, total);
3803
6
                free(flat);
3804
6
                push(vm, result_arr);
3805
6
                break;
3806
6
            }
3807
3808
0
#ifdef VM_USE_COMPUTED_GOTO
3809
0
            lbl_OP_BUILD_MAP:
3810
0
#endif
3811
0
            case OP_BUILD_MAP: {
3812
0
                uint8_t pair_count = READ_BYTE();
3813
0
                LatValue map = value_map_new();
3814
                /* Pop pairs in reverse */
3815
0
                LatValue *pairs = NULL;
3816
0
                if (pair_count > 0) {
3817
0
                    pairs = malloc(pair_count * 2 * sizeof(LatValue));
3818
0
                    for (int i = pair_count * 2 - 1; i >= 0; i--)
3819
0
                        pairs[i] = pop(vm);
3820
0
                    for (uint8_t i = 0; i < pair_count; i++) {
3821
0
                        LatValue key = pairs[i * 2];
3822
0
                        LatValue val = pairs[i * 2 + 1];
3823
0
                        stackvm_promote_value(&val);
3824
0
                        if (key.type == VAL_STR) {
3825
0
                            lat_map_set(map.as.map.map, key.as.str_val, &val);
3826
0
                        }
3827
0
                        value_free(&key);
3828
0
                    }
3829
0
                    free(pairs);
3830
0
                }
3831
0
                push(vm, map);
3832
0
                break;
3833
0
            }
3834
3835
0
#ifdef VM_USE_COMPUTED_GOTO
3836
11
            lbl_OP_BUILD_TUPLE:
3837
11
#endif
3838
11
            case OP_BUILD_TUPLE: {
3839
11
                uint8_t count = READ_BYTE();
3840
11
                LatValue *elems = NULL;
3841
11
                LatValue elem_buf[16];
3842
11
                if (count > 0) {
3843
11
                    elems = (count <= 16) ? elem_buf : malloc(count * sizeof(LatValue));
3844
42
                    for (int i = count - 1; i >= 0; i--)
3845
31
                        elems[i] = pop(vm);
3846
42
                    for (int i = 0; i < count; i++)
3847
31
                        stackvm_promote_value(&elems[i]);
3848
11
                }
3849
11
                LatValue tup = value_tuple(elems, count);
3850
11
                if (count > 16) free(elems);
3851
11
                push(vm, tup);
3852
11
                break;
3853
11
            }
3854
3855
0
#ifdef VM_USE_COMPUTED_GOTO
3856
194
            lbl_OP_BUILD_STRUCT:
3857
194
#endif
3858
194
            case OP_BUILD_STRUCT: {
3859
194
                uint8_t name_idx = READ_BYTE();
3860
194
                uint8_t field_count = READ_BYTE();
3861
194
                const char *struct_name = frame->chunk->constants[name_idx].as.str_val;
3862
3863
                /* Borrow field name pointers from constant pool (no strdup here) */
3864
194
                const char *fn_buf[16];
3865
194
                LatValue fv_buf[16];
3866
194
                const char **field_names = (field_count <= 16) ? fn_buf : malloc(field_count * sizeof(const char *));
3867
194
                LatValue *field_values = (field_count <= 16) ? fv_buf : malloc(field_count * sizeof(LatValue));
3868
3869
194
                size_t base_const = name_idx + 1;
3870
578
                for (uint8_t i = 0; i < field_count; i++)
3871
384
                    field_names[i] = frame->chunk->constants[base_const + i].as.str_val;
3872
3873
                /* Pop field values in reverse */
3874
578
                for (int i = field_count - 1; i >= 0; i--)
3875
384
                    field_values[i] = pop(vm);
3876
578
                for (int i = 0; i < field_count; i++)
3877
384
                    stackvm_promote_value(&field_values[i]);
3878
3879
194
                LatValue s = value_struct_vm(struct_name, field_names, field_values, field_count);
3880
3881
                /* Alloy enforcement: apply per-field phase from struct declaration */
3882
194
                char phase_key[256];
3883
194
                snprintf(phase_key, sizeof(phase_key), "__struct_phases_%s", struct_name);
3884
194
                LatValue *phase_ref = env_get_ref(vm->env, phase_key);
3885
194
                if (phase_ref &&
3886
194
                    phase_ref->type == VAL_ARRAY && phase_ref->as.array.len == field_count) {
3887
3
                    s.as.strct.field_phases = calloc(field_count, sizeof(PhaseTag));
3888
9
                    for (uint8_t i = 0; i < field_count; i++) {
3889
6
                        int64_t p = phase_ref->as.array.elems[i].as.int_val;
3890
6
                        if (p == 1) { /* PHASE_CRYSTAL */
3891
3
                            s.as.strct.field_values[i] = value_freeze(s.as.strct.field_values[i]);
3892
3
                            s.as.strct.field_phases[i] = VTAG_CRYSTAL;
3893
3
                        } else if (p == 0) { /* PHASE_FLUID */
3894
3
                            s.as.strct.field_phases[i] = VTAG_FLUID;
3895
3
                        } else {
3896
0
                            s.as.strct.field_phases[i] = s.phase;
3897
0
                        }
3898
6
                    }
3899
3
                }
3900
3901
194
                if (field_count > 16) { free(field_names); free(field_values); }
3902
194
                push(vm, s);
3903
194
                break;
3904
194
            }
3905
3906
0
#ifdef VM_USE_COMPUTED_GOTO
3907
13
            lbl_OP_BUILD_RANGE:
3908
13
#endif
3909
13
            case OP_BUILD_RANGE: {
3910
13
                LatValue end = pop(vm), start = pop(vm);
3911
13
                if (start.type == VAL_INT && end.type == VAL_INT) {
3912
13
                    push(vm, value_range(start.as.int_val, end.as.int_val));
3913
13
                } else {
3914
0
                    value_free(&start); value_free(&end);
3915
0
                    VM_ERROR("range bounds must be integers"); break;
3916
0
                }
3917
13
                break;
3918
13
            }
3919
3920
13
#ifdef VM_USE_COMPUTED_GOTO
3921
14
            lbl_OP_BUILD_ENUM:
3922
14
#endif
3923
14
            case OP_BUILD_ENUM: {
3924
14
                uint8_t enum_idx = READ_BYTE();
3925
14
                uint8_t var_idx = READ_BYTE();
3926
14
                uint8_t payload_count = READ_BYTE();
3927
14
                const char *enum_name = frame->chunk->constants[enum_idx].as.str_val;
3928
14
                const char *variant_name = frame->chunk->constants[var_idx].as.str_val;
3929
3930
14
                LatValue *payload = NULL;
3931
14
                if (payload_count > 0) {
3932
4
                    payload = malloc(payload_count * sizeof(LatValue));
3933
10
                    for (int i = payload_count - 1; i >= 0; i--)
3934
6
                        payload[i] = pop(vm);
3935
4
                }
3936
14
                LatValue e = value_enum(enum_name, variant_name, payload, payload_count);
3937
14
                free(payload);
3938
14
                push(vm, e);
3939
14
                break;
3940
14
            }
3941
3942
0
#ifdef VM_USE_COMPUTED_GOTO
3943
329
            lbl_OP_INDEX:
3944
329
#endif
3945
329
            case OP_INDEX: {
3946
329
                LatValue idx = pop(vm);
3947
329
                LatValue obj = pop(vm);
3948
                /* Ref proxy: delegate indexing to inner value */
3949
329
                if (obj.type == VAL_REF) {
3950
5
                    LatValue *inner = &obj.as.ref.ref->value;
3951
5
                    if (inner->type == VAL_ARRAY && idx.type == VAL_INT) {
3952
2
                        int64_t i = idx.as.int_val;
3953
2
                        if (i < 0 || (size_t)i >= inner->as.array.len) {
3954
0
                            value_free(&obj);
3955
0
                            VM_ERROR("array index out of bounds: %lld (len %zu)",
3956
0
                                     (long long)i, inner->as.array.len); break;
3957
0
                        }
3958
2
                        LatValue elem = value_deep_clone(&inner->as.array.elems[i]);
3959
2
                        value_free(&obj);
3960
2
                        push(vm, elem);
3961
2
                        break;
3962
2
                    }
3963
3
                    if (inner->type == VAL_MAP && idx.type == VAL_STR) {
3964
3
                        LatValue *found = lat_map_get(inner->as.map.map, idx.as.str_val);
3965
3
                        if (found)
3966
3
                            push(vm, value_deep_clone(found));
3967
0
                        else
3968
0
                            push(vm, value_nil());
3969
3
                        value_free(&obj);
3970
3
                        value_free(&idx);
3971
3
                        break;
3972
3
                    }
3973
0
                    const char *it = value_type_name(&idx);
3974
0
                    const char *innert = value_type_name(inner);
3975
0
                    value_free(&obj); value_free(&idx);
3976
0
                    VM_ERROR("invalid index operation: Ref<%s>[%s]", innert, it); break;
3977
0
                }
3978
324
                if (obj.type == VAL_ARRAY && idx.type == VAL_INT) {
3979
250
                    int64_t i = idx.as.int_val;
3980
250
                    if (i < 0 || (size_t)i >= obj.as.array.len) {
3981
0
                        value_free(&obj);
3982
0
                        VM_ERROR("array index out of bounds: %lld (len %zu)",
3983
0
                                 (long long)i, obj.as.array.len); break;
3984
0
                    }
3985
250
                    LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
3986
250
                    value_free(&obj);
3987
250
                    push(vm, elem);
3988
250
                } else if (obj.type == VAL_MAP && idx.type == VAL_STR) {
3989
38
                    LatValue *found = lat_map_get(obj.as.map.map, idx.as.str_val);
3990
38
                    if (found)
3991
38
                        push(vm, value_deep_clone(found));
3992
0
                    else
3993
0
                        push(vm, value_nil());
3994
38
                    value_free(&obj);
3995
38
                    value_free(&idx);
3996
38
                } else if (obj.type == VAL_STR && idx.type == VAL_INT) {
3997
3
                    int64_t i = idx.as.int_val;
3998
3
                    size_t len = strlen(obj.as.str_val);
3999
3
                    if (i < 0 || (size_t)i >= len) {
4000
0
                        value_free(&obj);
4001
0
                        VM_ERROR("string index out of bounds"); break;
4002
0
                    }
4003
3
                    char ch[2] = { obj.as.str_val[i], '\0' };
4004
3
                    value_free(&obj);
4005
3
                    push(vm, value_string(ch));
4006
33
                } else if (obj.type == VAL_TUPLE && idx.type == VAL_INT) {
4007
0
                    int64_t i = idx.as.int_val;
4008
0
                    if (i < 0 || (size_t)i >= obj.as.tuple.len) {
4009
0
                        value_free(&obj);
4010
0
                        VM_ERROR("tuple index out of bounds"); break;
4011
0
                    }
4012
0
                    LatValue elem = value_deep_clone(&obj.as.tuple.elems[i]);
4013
0
                    value_free(&obj);
4014
0
                    push(vm, elem);
4015
33
                } else if (obj.type == VAL_STR && idx.type == VAL_RANGE) {
4016
                    /* String range slicing: "hello"[1..4] → "ell" */
4017
2
                    int64_t start = idx.as.range.start;
4018
2
                    int64_t end = idx.as.range.end;
4019
2
                    size_t len = strlen(obj.as.str_val);
4020
2
                    if (start < 0) start = 0;
4021
2
                    if (end < 0) end = 0;
4022
2
                    if ((size_t)start > len) start = (int64_t)len;
4023
2
                    if ((size_t)end > len) end = (int64_t)len;
4024
2
                    if (start >= end) {
4025
0
                        value_free(&obj);
4026
0
                        push(vm, value_string(""));
4027
2
                    } else {
4028
2
                        size_t slice_len = (size_t)(end - start);
4029
2
                        char *slice = malloc(slice_len + 1);
4030
2
                        memcpy(slice, obj.as.str_val + start, slice_len);
4031
2
                        slice[slice_len] = '\0';
4032
2
                        value_free(&obj);
4033
2
                        push(vm, value_string_owned(slice));
4034
2
                    }
4035
31
                } else if (obj.type == VAL_ARRAY && idx.type == VAL_RANGE) {
4036
                    /* Array range slicing: [1,2,3,4][1..3] → [2,3] */
4037
3
                    int64_t start = idx.as.range.start;
4038
3
                    int64_t end = idx.as.range.end;
4039
3
                    size_t len = obj.as.array.len;
4040
3
                    if (start < 0) start = 0;
4041
3
                    if ((size_t)start > len) start = (int64_t)len;
4042
3
                    if (end < 0) end = 0;
4043
3
                    if ((size_t)end > len) end = (int64_t)len;
4044
3
                    if (start >= end) {
4045
1
                        value_free(&obj);
4046
1
                        push(vm, value_array(NULL, 0));
4047
2
                    } else {
4048
2
                        size_t slice_len = (size_t)(end - start);
4049
2
                        LatValue *elems = malloc(slice_len * sizeof(LatValue));
4050
8
                        for (size_t i = 0; i < slice_len; i++)
4051
6
                            elems[i] = value_deep_clone(&obj.as.array.elems[start + i]);
4052
2
                        value_free(&obj);
4053
2
                        push(vm, value_array(elems, slice_len));
4054
2
                        free(elems);
4055
2
                    }
4056
28
                } else if (obj.type == VAL_BUFFER && idx.type == VAL_INT) {
4057
28
                    int64_t i = idx.as.int_val;
4058
28
                    if (i < 0 || (size_t)i >= obj.as.buffer.len) {
4059
0
                        value_free(&obj);
4060
0
                        VM_ERROR("buffer index out of bounds: %lld (len %zu)",
4061
0
                                 (long long)i, obj.as.buffer.len); break;
4062
0
                    }
4063
28
                    LatValue elem = value_int(obj.as.buffer.data[i]);
4064
28
                    value_free(&obj);
4065
28
                    push(vm, elem);
4066
28
                } else {
4067
0
                    const char *ot = value_type_name(&obj);
4068
0
                    const char *it = value_type_name(&idx);
4069
0
                    value_free(&obj); value_free(&idx);
4070
0
                    VM_ERROR("invalid index operation: %s[%s]", ot, it); break;
4071
0
                }
4072
324
                break;
4073
324
            }
4074
4075
324
#ifdef VM_USE_COMPUTED_GOTO
4076
324
            lbl_OP_SET_INDEX:
4077
8
#endif
4078
8
            case OP_SET_INDEX: {
4079
8
                LatValue idx = pop(vm);
4080
8
                LatValue obj = pop(vm);
4081
8
                LatValue val = pop(vm);
4082
                /* Ref proxy: delegate set-index to inner value */
4083
8
                if (obj.type == VAL_REF) {
4084
6
                    if (obj.phase == VTAG_CRYSTAL) {
4085
1
                        value_free(&obj); value_free(&idx); value_free(&val);
4086
1
                        VM_ERROR("cannot assign index on a frozen Ref"); break;
4087
1
                    }
4088
5
                    LatValue *inner = &obj.as.ref.ref->value;
4089
5
                    if (inner->type == VAL_ARRAY && idx.type == VAL_INT) {
4090
1
                        int64_t i = idx.as.int_val;
4091
1
                        if (i < 0 || (size_t)i >= inner->as.array.len) {
4092
0
                            value_free(&obj); value_free(&val);
4093
0
                            VM_ERROR("array index out of bounds in assignment"); break;
4094
0
                        }
4095
1
                        value_free(&inner->as.array.elems[i]);
4096
1
                        inner->as.array.elems[i] = val;
4097
1
                        push(vm, obj);
4098
1
                        break;
4099
1
                    }
4100
4
                    if (inner->type == VAL_MAP && idx.type == VAL_STR) {
4101
4
                        LatValue *old = (LatValue *)lat_map_get(inner->as.map.map, idx.as.str_val);
4102
4
                        if (old) value_free(old);
4103
4
                        lat_map_set(inner->as.map.map, idx.as.str_val, &val);
4104
4
                        value_free(&idx);
4105
4
                        push(vm, obj);
4106
4
                        break;
4107
4
                    }
4108
0
                    value_free(&obj); value_free(&idx); value_free(&val);
4109
0
                    VM_ERROR("invalid index assignment on Ref"); break;
4110
0
                }
4111
2
                if (obj.type == VAL_ARRAY && idx.type == VAL_INT) {
4112
0
                    int64_t i = idx.as.int_val;
4113
0
                    if (i < 0 || (size_t)i >= obj.as.array.len) {
4114
0
                        value_free(&obj); value_free(&val);
4115
0
                        VM_ERROR("array index out of bounds in assignment"); break;
4116
0
                    }
4117
0
                    value_free(&obj.as.array.elems[i]);
4118
0
                    obj.as.array.elems[i] = val;
4119
0
                    push(vm, obj);
4120
2
                } else if (obj.type == VAL_MAP && idx.type == VAL_STR) {
4121
0
                    lat_map_set(obj.as.map.map, idx.as.str_val, &val);
4122
0
                    value_free(&idx);
4123
0
                    push(vm, obj);
4124
2
                } else if (obj.type == VAL_BUFFER && idx.type == VAL_INT) {
4125
2
                    int64_t i = idx.as.int_val;
4126
2
                    if (i < 0 || (size_t)i >= obj.as.buffer.len) {
4127
0
                        value_free(&obj); value_free(&val);
4128
0
                        VM_ERROR("buffer index out of bounds in assignment"); break;
4129
0
                    }
4130
2
                    obj.as.buffer.data[i] = (uint8_t)(val.as.int_val & 0xFF);
4131
2
                    value_free(&val);
4132
2
                    push(vm, obj);
4133
2
                } else {
4134
0
                    value_free(&obj); value_free(&idx); value_free(&val);
4135
0
                    VM_ERROR("invalid index assignment"); break;
4136
0
                }
4137
2
                break;
4138
2
            }
4139
4140
2
#ifdef VM_USE_COMPUTED_GOTO
4141
288
            lbl_OP_GET_FIELD:
4142
288
#endif
4143
288
            case OP_GET_FIELD: {
4144
288
                uint8_t name_idx = READ_BYTE();
4145
288
                const char *field_name = frame->chunk->constants[name_idx].as.str_val;
4146
288
                LatValue obj = pop(vm);
4147
4148
288
                if (obj.type == VAL_STRUCT) {
4149
257
                    bool found = false;
4150
328
                    for (size_t i = 0; i < obj.as.strct.field_count; i++) {
4151
328
                        if (strcmp(obj.as.strct.field_names[i], field_name) == 0) {
4152
                            /* Steal the value from the dying struct */
4153
257
                            LatValue stolen = obj.as.strct.field_values[i];
4154
257
                            obj.as.strct.field_values[i] = (LatValue){.type = VAL_NIL};
4155
257
                            push(vm, stolen);
4156
257
                            found = true;
4157
257
                            break;
4158
257
                        }
4159
328
                    }
4160
257
                    if (!found) {
4161
0
                        value_free(&obj);
4162
0
                        VM_ERROR("struct has no field '%s'", field_name); break;
4163
0
                    }
4164
257
                    value_free(&obj);
4165
257
                } else if (obj.type == VAL_MAP) {
4166
25
                    LatValue *val = lat_map_get(obj.as.map.map, field_name);
4167
25
                    if (val) {
4168
                        /* Steal the value from the dying map */
4169
22
                        LatValue stolen = *val;
4170
22
                        val->type = VAL_NIL;
4171
22
                        push(vm, stolen);
4172
22
                    } else {
4173
3
                        push(vm, value_nil());
4174
3
                    }
4175
25
                    value_free(&obj);
4176
25
                } else if (obj.type == VAL_TUPLE) {
4177
                    /* Tuple field access: t.0, t.1, etc. */
4178
6
                    char *end;
4179
6
                    long idx = strtol(field_name, &end, 10);
4180
6
                    if (*end == '\0' && idx >= 0 && (size_t)idx < obj.as.tuple.len) {
4181
6
                        LatValue stolen = obj.as.tuple.elems[idx];
4182
6
                        obj.as.tuple.elems[idx] = (LatValue){.type = VAL_NIL};
4183
6
                        push(vm, stolen);
4184
6
                    } else {
4185
0
                        value_free(&obj);
4186
0
                        VM_ERROR("tuple has no field '%s'", field_name); break;
4187
0
                    }
4188
6
                    value_free(&obj);
4189
6
                } else if (obj.type == VAL_ENUM) {
4190
0
                    if (strcmp(field_name, "tag") == 0) {
4191
0
                        push(vm, value_string(obj.as.enm.variant_name));
4192
0
                    } else if (strcmp(field_name, "payload") == 0) {
4193
                        /* Always return an array of all payloads */
4194
0
                        if (obj.as.enm.payload_count > 0) {
4195
0
                            LatValue *elems = malloc(obj.as.enm.payload_count * sizeof(LatValue));
4196
0
                            for (size_t i = 0; i < obj.as.enm.payload_count; i++) {
4197
0
                                elems[i] = obj.as.enm.payload[i];
4198
0
                                obj.as.enm.payload[i] = (LatValue){.type = VAL_NIL};
4199
0
                            }
4200
0
                            push(vm, value_array(elems, obj.as.enm.payload_count));
4201
0
                            free(elems);
4202
0
                        } else {
4203
0
                            push(vm, value_array(NULL, 0));
4204
0
                        }
4205
0
                    } else {
4206
0
                        push(vm, value_nil());
4207
0
                    }
4208
0
                    value_free(&obj);
4209
0
                } else {
4210
0
                    const char *tn = value_type_name(&obj);
4211
0
                    value_free(&obj);
4212
0
                    VM_ERROR("cannot access field '%s' on %s", field_name, tn); break;
4213
0
                }
4214
288
                break;
4215
288
            }
4216
4217
288
#ifdef VM_USE_COMPUTED_GOTO
4218
288
            lbl_OP_SET_FIELD:
4219
14
#endif
4220
14
            case OP_SET_FIELD: {
4221
14
                uint8_t name_idx = READ_BYTE();
4222
14
                const char *field_name = frame->chunk->constants[name_idx].as.str_val;
4223
14
                LatValue obj = pop(vm);
4224
14
                LatValue val = pop(vm);
4225
14
                stackvm_promote_value(&val);
4226
4227
14
                if (obj.type == VAL_STRUCT) {
4228
                    /* Check overall struct phase (only if no per-field phases set) */
4229
14
                    if ((obj.phase == VTAG_CRYSTAL || obj.phase == VTAG_SUBLIMATED) &&
4230
14
                        !obj.as.strct.field_phases) {
4231
1
                        value_free(&obj); value_free(&val);
4232
1
                        VM_ERROR("cannot assign to field '%s' on a %s struct", field_name,
4233
1
                            obj.phase == VTAG_CRYSTAL ? "frozen" : "sublimated"); break;
4234
1
                    }
4235
                    /* Check per-field phase constraints (alloy types) */
4236
14
                    bool field_frozen = false;
4237
13
                    if (obj.as.strct.field_phases) {
4238
12
                        for (size_t i = 0; i < obj.as.strct.field_count; i++) {
4239
12
                            if (strcmp(obj.as.strct.field_names[i], field_name) == 0) {
4240
7
                                if (obj.as.strct.field_phases[i] == VTAG_CRYSTAL)
4241
3
                                    field_frozen = true;
4242
7
                                break;
4243
7
                            }
4244
12
                        }
4245
7
                    }
4246
13
                    if (field_frozen) {
4247
3
                        value_free(&obj); value_free(&val);
4248
3
                        VM_ERROR("cannot assign to frozen field '%s'", field_name); break;
4249
3
                    }
4250
13
                    bool found = false;
4251
18
                    for (size_t i = 0; i < obj.as.strct.field_count; i++) {
4252
18
                        if (strcmp(obj.as.strct.field_names[i], field_name) == 0) {
4253
10
                            value_free(&obj.as.strct.field_values[i]);
4254
10
                            obj.as.strct.field_values[i] = val;
4255
10
                            found = true;
4256
10
                            break;
4257
10
                        }
4258
18
                    }
4259
10
                    if (!found) {
4260
0
                        value_free(&obj); value_free(&val);
4261
0
                        VM_ERROR("struct has no field '%s'", field_name); break;
4262
0
                    }
4263
10
                    push(vm, obj);
4264
10
                } else if (obj.type == VAL_MAP) {
4265
0
                    lat_map_set(obj.as.map.map, field_name, &val);
4266
0
                    push(vm, obj);
4267
0
                } else {
4268
0
                    value_free(&obj); value_free(&val);
4269
0
                    VM_ERROR("cannot set field on non-struct/map value"); break;
4270
0
                }
4271
10
                break;
4272
14
            }
4273
4274
10
#ifdef VM_USE_COMPUTED_GOTO
4275
109
            lbl_OP_INVOKE:
4276
109
#endif
4277
109
            case OP_INVOKE: {
4278
109
                uint8_t method_idx = READ_BYTE();
4279
109
                uint8_t arg_count = READ_BYTE();
4280
109
                const char *method_name = frame->chunk->constants[method_idx].as.str_val;
4281
4282
                /* Object is below args on the stack */
4283
109
                LatValue *obj = stackvm_peek(vm, arg_count);
4284
4285
109
                if (stackvm_invoke_builtin(vm, obj, method_name, arg_count, NULL)) {
4286
108
                    if (vm->error) {
4287
3
                        StackVMResult r = stackvm_handle_native_error(vm, &frame);
4288
3
                        if (r != STACKVM_OK) return r;
4289
0
                        break;
4290
3
                    }
4291
                    /* Builtin handled it. Pop the object and replace with result. */
4292
105
                    LatValue result_val = pop(vm); /* pop result pushed by builtin */
4293
                    /* Pop the object */
4294
                    /* The object is at stack_top[-1] now (or at the right place). Actually...
4295
                     * We need to be careful here. The args were popped by builtin if needed.
4296
                     * The object is still on the stack. Let's clean up. */
4297
105
                    LatValue obj_val = pop(vm);
4298
105
                    value_free(&obj_val);
4299
105
                    push(vm, result_val);
4300
105
                } else {
4301
                    /* Check if map/struct has a callable closure field */
4302
1
                    if (obj->type == VAL_MAP) {
4303
0
                        LatValue *field = lat_map_get(obj->as.map.map, method_name);
4304
0
                        if (field && field->type == VAL_CLOSURE && field->as.closure.native_fn &&
4305
0
                            field->as.closure.default_values != VM_NATIVE_MARKER) {
4306
                            /* Bytecode closure stored in map - call it */
4307
0
                            Chunk *fn_chunk = (Chunk *)field->as.closure.native_fn;
4308
0
                            int arity = (int)field->as.closure.param_count;
4309
0
                            int adjusted = stackvm_adjust_call_args(vm, fn_chunk, arity, (int)arg_count);
4310
0
                            if (adjusted < 0) {
4311
0
                                char *err = vm->error; vm->error = NULL;
4312
0
                                VM_ERROR("%s", err); free(err); break;
4313
0
                            }
4314
0
                            (void)adjusted;
4315
0
                            ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
4316
0
                            size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
4317
0
                            if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4318
0
                                VM_ERROR("stack overflow (too many nested calls)"); break;
4319
0
                            }
4320
0
                            stackvm_promote_frame_ephemerals(vm, frame);
4321
                            /* Replace the map on the stack with a closure placeholder
4322
                             * so OP_RETURN can clean up properly. */
4323
0
                            LatValue closure_copy = value_deep_clone(field);
4324
0
                            value_free(obj);
4325
0
                            *obj = closure_copy;
4326
0
                            StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4327
0
                            new_frame->chunk = fn_chunk;
4328
0
                            new_frame->ip = fn_chunk->code;
4329
0
                            new_frame->slots = obj;
4330
0
                            new_frame->upvalues = upvals;
4331
0
                            new_frame->upvalue_count = uv_count;
4332
0
                            frame = new_frame;
4333
0
                            break;
4334
0
                        }
4335
0
                        if (field && field->type == VAL_CLOSURE &&
4336
0
                            field->as.closure.default_values == VM_NATIVE_MARKER) {
4337
                            /* StackVM native function stored in map */
4338
0
                            VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
4339
0
                            LatValue *args = (arg_count <= 16) ? vm->fast_args
4340
0
                                           : malloc(arg_count * sizeof(LatValue));
4341
0
                            for (int i = arg_count - 1; i >= 0; i--)
4342
0
                                args[i] = pop(vm);
4343
0
                            LatValue obj_val = pop(vm);
4344
0
                            LatValue ret = native(args, arg_count);
4345
                    /* Bridge: native errors from runtime to StackVM */
4346
0
                    if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; }
4347
0
                            for (int i = 0; i < arg_count; i++)
4348
0
                                value_free(&args[i]);
4349
0
                            if (arg_count > 16) free(args);
4350
0
                            value_free(&obj_val);
4351
0
                            push(vm, ret);
4352
0
                            break;
4353
0
                        }
4354
0
                    }
4355
4356
                    /* Check if struct has a callable closure field */
4357
1
                    if (obj->type == VAL_STRUCT) {
4358
0
                        bool handled = false;
4359
0
                        for (size_t fi = 0; fi < obj->as.strct.field_count; fi++) {
4360
0
                            if (strcmp(obj->as.strct.field_names[fi], method_name) != 0)
4361
0
                                continue;
4362
0
                            LatValue *field = &obj->as.strct.field_values[fi];
4363
0
                            if (field->type == VAL_CLOSURE && field->as.closure.native_fn &&
4364
0
                                field->as.closure.default_values != VM_NATIVE_MARKER) {
4365
                                /* Bytecode closure in struct field — inject self */
4366
0
                                Chunk *fn_chunk = (Chunk *)field->as.closure.native_fn;
4367
0
                                ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
4368
0
                                size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
4369
0
                                if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4370
0
                                    VM_ERROR("stack overflow (too many nested calls)"); break;
4371
0
                                }
4372
0
                                stackvm_promote_frame_ephemerals(vm, frame);
4373
0
                                LatValue self_copy = value_deep_clone(obj);
4374
0
                                LatValue closure_copy = value_deep_clone(field);
4375
                                /* Shift args up by 1 to make room for self */
4376
0
                                push(vm, value_nil());
4377
0
                                for (int si = arg_count; si >= 1; si--)
4378
0
                                    obj[si + 1] = obj[si];
4379
0
                                obj[1] = self_copy;
4380
0
                                value_free(obj);
4381
0
                                *obj = closure_copy;
4382
0
                                StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4383
0
                                new_frame->chunk = fn_chunk;
4384
0
                                new_frame->ip = fn_chunk->code;
4385
0
                                new_frame->slots = obj;
4386
0
                                new_frame->upvalues = upvals;
4387
0
                                new_frame->upvalue_count = uv_count;
4388
0
                                frame = new_frame;
4389
0
                                handled = true;
4390
0
                                break;
4391
0
                            }
4392
0
                            if (field->type == VAL_CLOSURE &&
4393
0
                                field->as.closure.default_values == VM_NATIVE_MARKER) {
4394
                                /* StackVM native in struct field — inject self */
4395
0
                                VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
4396
0
                                LatValue self_copy = value_deep_clone(obj);
4397
0
                                int total_args = arg_count + 1;
4398
0
                                LatValue *args = malloc(total_args * sizeof(LatValue));
4399
0
                                args[0] = self_copy;
4400
0
                                for (int ai = arg_count - 1; ai >= 0; ai--)
4401
0
                                    args[ai + 1] = pop(vm);
4402
0
                                LatValue obj_val = pop(vm);
4403
0
                                LatValue ret = native(args, total_args);
4404
0
                                for (int ai = 0; ai < total_args; ai++)
4405
0
                                    value_free(&args[ai]);
4406
0
                                free(args);
4407
0
                                value_free(&obj_val);
4408
0
                                push(vm, ret);
4409
0
                                handled = true;
4410
0
                                break;
4411
0
                            }
4412
0
                            break; /* found field but not callable */
4413
0
                        }
4414
0
                        if (handled) break;
4415
0
                    }
4416
4417
                    /* Try to find it as a compiled method via "TypeName::method" global */
4418
1
                    const char *type_name = (obj->type == VAL_STRUCT) ? obj->as.strct.name :
4419
1
                                            (obj->type == VAL_ENUM)   ? obj->as.enm.enum_name :
4420
1
                                            value_type_name(obj);
4421
1
                    char key[256];
4422
1
                    snprintf(key, sizeof(key), "%s::%s", type_name, method_name);
4423
1
                    LatValue *method_ref = env_get_ref(vm->env, key);
4424
1
                    if (method_ref &&
4425
1
                        method_ref->type == VAL_CLOSURE && method_ref->as.closure.native_fn) {
4426
                        /* Found a compiled method - call it with self + args */
4427
0
                        Chunk *fn_chunk = (Chunk *)method_ref->as.closure.native_fn;
4428
                        /* Rearrange stack: obj is already below args, use as slot 0 */
4429
0
                        if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4430
0
                            VM_ERROR("stack overflow (too many nested calls)"); break;
4431
0
                        }
4432
0
                        stackvm_promote_frame_ephemerals(vm, frame);
4433
0
                        StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4434
0
                        new_frame->chunk = fn_chunk;
4435
0
                        new_frame->ip = fn_chunk->code;
4436
0
                        new_frame->slots = obj; /* self is in slot 0 */
4437
0
                        new_frame->upvalues = NULL;
4438
0
                        new_frame->upvalue_count = 0;
4439
0
                        frame = new_frame;
4440
1
                    } else {
4441
                        /* Method not found - error */
4442
1
                        const char *tname = value_type_name(obj);
4443
1
                        for (int i = 0; i < arg_count; i++) {
4444
0
                            LatValue v = pop(vm);
4445
0
                            value_free(&v);
4446
0
                        }
4447
1
                        LatValue obj_val = pop(vm);
4448
1
                        value_free(&obj_val);
4449
1
                        VM_ERROR("type '%s' has no method '%s'", tname, method_name);
4450
0
                        break;
4451
1
                    }
4452
1
                }
4453
105
                break;
4454
109
            }
4455
4456
105
#ifdef VM_USE_COMPUTED_GOTO
4457
2.49k
            lbl_OP_INVOKE_LOCAL:
4458
2.49k
#endif
4459
2.49k
            case OP_INVOKE_LOCAL: {
4460
2.49k
                uint8_t slot = READ_BYTE();
4461
2.49k
                uint8_t method_idx = READ_BYTE();
4462
2.49k
                uint8_t arg_count = READ_BYTE();
4463
2.49k
                const char *method_name = frame->chunk->constants[method_idx].as.str_val;
4464
2.49k
                LatValue *obj = &frame->slots[slot]; /* Direct pointer to local */
4465
4466
2.49k
                const char *local_var_name = (frame->chunk->local_names && slot < frame->chunk->local_name_cap)
4467
2.49k
                    ? frame->chunk->local_names[slot] : NULL;
4468
2.49k
                if (stackvm_invoke_builtin(vm, obj, method_name, arg_count, local_var_name)) {
4469
2.48k
                    if (vm->error) {
4470
6
                        StackVMResult r = stackvm_handle_native_error(vm, &frame);
4471
6
                        if (r != STACKVM_OK) return r;
4472
0
                        break;
4473
6
                    }
4474
                    /* Builtin popped args and pushed result.
4475
                     * obj was mutated in-place (e.g. push modified the array). */
4476
2.48k
                    break;
4477
2.48k
                }
4478
4479
                /* Check if map has a callable closure field */
4480
10
                if (obj->type == VAL_MAP) {
4481
0
                    LatValue *field = lat_map_get(obj->as.map.map, method_name);
4482
0
                    if (field && field->type == VAL_CLOSURE && field->as.closure.native_fn &&
4483
0
                        field->as.closure.default_values != VM_NATIVE_MARKER) {
4484
                        /* Bytecode closure stored in local map - call it */
4485
0
                        Chunk *fn_chunk = (Chunk *)field->as.closure.native_fn;
4486
0
                        int arity = (int)field->as.closure.param_count;
4487
0
                        int adjusted = stackvm_adjust_call_args(vm, fn_chunk, arity, (int)arg_count);
4488
0
                        if (adjusted < 0) {
4489
0
                            char *err = vm->error; vm->error = NULL;
4490
0
                            VM_ERROR("%s", err); free(err); break;
4491
0
                        }
4492
0
                        arg_count = (uint8_t)adjusted;
4493
0
                        ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
4494
0
                        size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
4495
0
                        if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4496
0
                            VM_ERROR("stack overflow (too many nested calls)"); break;
4497
0
                        }
4498
0
                        stackvm_promote_frame_ephemerals(vm, frame);
4499
                        /* Set up frame: closure in slot 0, args follow */
4500
0
                        LatValue closure_copy = value_deep_clone(field);
4501
0
                        LatValue *arg_base = vm->stack_top - arg_count;
4502
0
                        push(vm, value_nil()); /* make room */
4503
0
                        for (int si = arg_count - 1; si >= 0; si--)
4504
0
                            arg_base[si + 1] = arg_base[si];
4505
0
                        arg_base[0] = closure_copy;
4506
0
                        StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4507
0
                        new_frame->chunk = fn_chunk;
4508
0
                        new_frame->ip = fn_chunk->code;
4509
0
                        new_frame->slots = arg_base;
4510
0
                        new_frame->upvalues = upvals;
4511
0
                        new_frame->upvalue_count = uv_count;
4512
0
                        frame = new_frame;
4513
0
                        break;
4514
0
                    }
4515
0
                    if (field && field->type == VAL_CLOSURE &&
4516
0
                        field->as.closure.default_values == VM_NATIVE_MARKER) {
4517
                        /* StackVM native function stored in local map */
4518
0
                        VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
4519
0
                        LatValue *args = (arg_count <= 16) ? vm->fast_args
4520
0
                                       : malloc(arg_count * sizeof(LatValue));
4521
0
                        for (int i = arg_count - 1; i >= 0; i--)
4522
0
                            args[i] = pop(vm);
4523
0
                        LatValue ret = native(args, arg_count);
4524
                    /* Bridge: native errors from runtime to StackVM */
4525
0
                    if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; }
4526
0
                        for (int i = 0; i < arg_count; i++)
4527
0
                            value_free(&args[i]);
4528
0
                        if (arg_count > 16) free(args);
4529
0
                        if (vm->error) {
4530
0
                            value_free(&ret);
4531
0
                            char *err = vm->error; vm->error = NULL;
4532
0
                            VM_ERROR("%s", err); free(err); break;
4533
0
                        }
4534
0
                        push(vm, ret);
4535
0
                        break;
4536
0
                    }
4537
0
                }
4538
4539
                /* Check if struct has a callable closure field */
4540
10
                if (obj->type == VAL_STRUCT) {
4541
10
                    bool handled = false;
4542
20
                    for (size_t fi = 0; fi < obj->as.strct.field_count; fi++) {
4543
14
                        if (strcmp(obj->as.strct.field_names[fi], method_name) != 0)
4544
10
                            continue;
4545
4
                        LatValue *field = &obj->as.strct.field_values[fi];
4546
4
                        if (field->type == VAL_CLOSURE && field->as.closure.native_fn &&
4547
4
                            field->as.closure.default_values != VM_NATIVE_MARKER) {
4548
                            /* Bytecode closure — inject [closure, self] below args */
4549
4
                            Chunk *fn_chunk = (Chunk *)field->as.closure.native_fn;
4550
4
                            ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
4551
4
                            size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
4552
4
                            if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4553
0
                                VM_ERROR("stack overflow (too many nested calls)"); break;
4554
0
                            }
4555
4
                            stackvm_promote_frame_ephemerals(vm, frame);
4556
4
                            LatValue self_copy = value_deep_clone(obj);
4557
4
                            LatValue closure_copy = value_deep_clone(field);
4558
4
                            LatValue *arg_base = vm->stack_top - arg_count;
4559
4
                            push(vm, value_nil()); push(vm, value_nil());
4560
6
                            for (int si = arg_count - 1; si >= 0; si--)
4561
2
                                arg_base[si + 2] = arg_base[si];
4562
4
                            arg_base[0] = closure_copy;
4563
4
                            arg_base[1] = self_copy;
4564
4
                            StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4565
4
                            new_frame->chunk = fn_chunk;
4566
4
                            new_frame->ip = fn_chunk->code;
4567
4
                            new_frame->slots = arg_base;
4568
4
                            new_frame->upvalues = upvals;
4569
4
                            new_frame->upvalue_count = uv_count;
4570
4
                            frame = new_frame;
4571
4
                            handled = true;
4572
4
                            break;
4573
4
                        }
4574
0
                        if (field->type == VAL_CLOSURE &&
4575
0
                            field->as.closure.default_values == VM_NATIVE_MARKER) {
4576
                            /* StackVM native — inject self */
4577
0
                            VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
4578
0
                            LatValue self_copy = value_deep_clone(obj);
4579
0
                            int total_args = arg_count + 1;
4580
0
                            LatValue *args = malloc(total_args * sizeof(LatValue));
4581
0
                            args[0] = self_copy;
4582
0
                            for (int ai = arg_count - 1; ai >= 0; ai--)
4583
0
                                args[ai + 1] = pop(vm);
4584
0
                            LatValue ret = native(args, total_args);
4585
0
                            for (int ai = 0; ai < total_args; ai++)
4586
0
                                value_free(&args[ai]);
4587
0
                            free(args);
4588
0
                            push(vm, ret);
4589
0
                            handled = true;
4590
0
                            break;
4591
0
                        }
4592
0
                        break;
4593
0
                    }
4594
10
                    if (handled) break;
4595
10
                }
4596
4597
6
                {
4598
                    /* Try compiled method via "TypeName::method" global */
4599
6
                    const char *type_name = (obj->type == VAL_STRUCT) ? obj->as.strct.name :
4600
6
                                            (obj->type == VAL_ENUM)   ? obj->as.enm.enum_name :
4601
0
                                            value_type_name(obj);
4602
6
                    char key[256];
4603
6
                    snprintf(key, sizeof(key), "%s::%s", type_name, method_name);
4604
6
                    LatValue *method_ref = env_get_ref(vm->env, key);
4605
6
                    if (method_ref &&
4606
6
                        method_ref->type == VAL_CLOSURE && method_ref->as.closure.native_fn) {
4607
6
                        Chunk *fn_chunk = (Chunk *)method_ref->as.closure.native_fn;
4608
6
                        if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4609
0
                            VM_ERROR("stack overflow (too many nested calls)"); break;
4610
0
                        }
4611
6
                        stackvm_promote_frame_ephemerals(vm, frame);
4612
                        /* Push self (deep clone of local) below args for the new frame. */
4613
6
                        LatValue *arg_base = vm->stack_top - arg_count;
4614
6
                        push(vm, value_nil());
4615
7
                        for (int i = arg_count; i > 0; i--)
4616
1
                            vm->stack_top[-1 - (arg_count - i)] = arg_base[i - 1];
4617
6
                        *arg_base = value_deep_clone(obj);
4618
6
                        StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4619
6
                        new_frame->chunk = fn_chunk;
4620
6
                        new_frame->ip = fn_chunk->code;
4621
6
                        new_frame->slots = arg_base;
4622
6
                        new_frame->upvalues = NULL;
4623
6
                        new_frame->upvalue_count = 0;
4624
6
                        frame = new_frame;
4625
6
                    } else {
4626
                        /* Method not found - pop args, push nil */
4627
0
                        for (int i = 0; i < arg_count; i++) {
4628
0
                            LatValue v = pop(vm);
4629
0
                            value_free(&v);
4630
0
                        }
4631
0
                        push(vm, value_nil());
4632
0
                    }
4633
6
                }
4634
6
                break;
4635
6
            }
4636
4637
6
#ifdef VM_USE_COMPUTED_GOTO
4638
209
            lbl_OP_INVOKE_GLOBAL:
4639
209
#endif
4640
209
            case OP_INVOKE_GLOBAL: {
4641
209
                uint8_t name_idx = READ_BYTE();
4642
209
                uint8_t method_idx = READ_BYTE();
4643
209
                uint8_t arg_count = READ_BYTE();
4644
209
                const char *global_name = frame->chunk->constants[name_idx].as.str_val;
4645
209
                const char *method_name = frame->chunk->constants[method_idx].as.str_val;
4646
4647
                /* Fast path: simple builtins (no closures) can mutate in place */
4648
209
                uint32_t mhash_g = method_hash(method_name);
4649
209
                if (stackvm_invoke_builtin_is_simple(mhash_g)) {
4650
209
                    LatValue *ref = env_get_ref(vm->env, global_name);
4651
209
                    if (!ref) {
4652
0
                        VM_ERROR("undefined variable '%s'", global_name); break;
4653
0
                    }
4654
209
                    if (stackvm_invoke_builtin(vm, ref, method_name, arg_count, global_name)) {
4655
41
                        if (vm->error) {
4656
0
                            StackVMResult r = stackvm_handle_native_error(vm, &frame);
4657
0
                            if (r != STACKVM_OK) return r;
4658
0
                            break;
4659
0
                        }
4660
                        /* Record history for tracked variables */
4661
41
                        if (vm->rt->tracking_active) {
4662
0
                            stackvm_record_history(vm, global_name, ref);
4663
0
                        }
4664
41
                        break;
4665
41
                    }
4666
209
                }
4667
4668
                /* Slow path: closure-invoking builtins or non-builtin dispatch */
4669
168
                LatValue obj_val;
4670
168
                if (!env_get(vm->env, global_name, &obj_val)) {
4671
0
                    VM_ERROR("undefined variable '%s'", global_name); break;
4672
0
                }
4673
4674
168
                if (stackvm_invoke_builtin(vm, &obj_val, method_name, arg_count, global_name)) {
4675
0
                    if (vm->error) {
4676
0
                        value_free(&obj_val);
4677
0
                        StackVMResult r = stackvm_handle_native_error(vm, &frame);
4678
0
                        if (r != STACKVM_OK) return r;
4679
0
                        break;
4680
0
                    }
4681
                    /* Write back the mutated object to the global env */
4682
0
                    env_set(vm->env, global_name, obj_val);
4683
                    /* Record history for tracked variables */
4684
0
                    if (vm->rt->tracking_active) {
4685
0
                        LatValue cur;
4686
0
                        if (env_get(vm->env, global_name, &cur)) {
4687
0
                            stackvm_record_history(vm, global_name, &cur);
4688
0
                            value_free(&cur);
4689
0
                        }
4690
0
                    }
4691
0
                    break;
4692
0
                }
4693
4694
                /* Not a builtin — insert object below args on stack and
4695
                 * dispatch like OP_INVOKE (struct closures, impl methods, etc.) */
4696
168
                push(vm, value_nil()); /* make room */
4697
168
                LatValue *base = vm->stack_top - arg_count - 1;
4698
168
                memmove(base + 1, base, arg_count * sizeof(LatValue));
4699
168
                *base = obj_val;
4700
168
                LatValue *obj = base;
4701
4702
                /* Check if map/struct has a callable closure field */
4703
168
                if (obj->type == VAL_MAP) {
4704
168
                    LatValue *field = lat_map_get(obj->as.map.map, method_name);
4705
168
                    if (field && field->type == VAL_CLOSURE && field->as.closure.native_fn &&
4706
168
                        field->as.closure.default_values != VM_NATIVE_MARKER) {
4707
166
                        Chunk *fn_chunk = (Chunk *)field->as.closure.native_fn;
4708
166
                        int arity = (int)field->as.closure.param_count;
4709
166
                        int adjusted = stackvm_adjust_call_args(vm, fn_chunk, arity, (int)arg_count);
4710
166
                        if (adjusted < 0) {
4711
0
                            char *err = vm->error; vm->error = NULL;
4712
0
                            value_free(&obj_val);
4713
0
                            VM_ERROR("%s", err); free(err); break;
4714
0
                        }
4715
166
                        arg_count = (uint8_t)adjusted;
4716
                        /* Recalculate obj pointer — vm_adjust may have pushed defaults */
4717
166
                        obj = vm->stack_top - arg_count - 1;
4718
166
                        ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
4719
166
                        size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
4720
166
                        if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4721
0
                            VM_ERROR("stack overflow (too many nested calls)"); break;
4722
0
                        }
4723
166
                        stackvm_promote_frame_ephemerals(vm, frame);
4724
166
                        LatValue closure_copy = value_deep_clone(field);
4725
166
                        value_free(obj);
4726
166
                        *obj = closure_copy;
4727
166
                        StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4728
166
                        new_frame->chunk = fn_chunk;
4729
166
                        new_frame->ip = fn_chunk->code;
4730
166
                        new_frame->slots = obj;
4731
166
                        new_frame->upvalues = upvals;
4732
166
                        new_frame->upvalue_count = uv_count;
4733
166
                        frame = new_frame;
4734
166
                        break;
4735
166
                    }
4736
2
                    if (field && field->type == VAL_CLOSURE &&
4737
2
                        field->as.closure.default_values == VM_NATIVE_MARKER) {
4738
2
                        VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
4739
2
                        LatValue *args = (arg_count <= 16) ? vm->fast_args
4740
2
                                       : malloc(arg_count * sizeof(LatValue));
4741
4
                        for (int i = arg_count - 1; i >= 0; i--)
4742
2
                            args[i] = pop(vm);
4743
2
                        LatValue obj_popped = pop(vm);
4744
2
                        LatValue ret = native(args, arg_count);
4745
                    /* Bridge: native errors from runtime to StackVM */
4746
2
                    if (vm->rt->error) { vm->error = vm->rt->error; vm->rt->error = NULL; }
4747
4
                        for (int i = 0; i < arg_count; i++)
4748
2
                            value_free(&args[i]);
4749
2
                        if (arg_count > 16) free(args);
4750
2
                        value_free(&obj_popped);
4751
2
                        push(vm, ret);
4752
2
                        break;
4753
2
                    }
4754
2
                }
4755
4756
0
                if (obj->type == VAL_STRUCT) {
4757
0
                    bool handled = false;
4758
0
                    for (size_t fi = 0; fi < obj->as.strct.field_count; fi++) {
4759
0
                        if (strcmp(obj->as.strct.field_names[fi], method_name) != 0)
4760
0
                            continue;
4761
0
                        LatValue *field = &obj->as.strct.field_values[fi];
4762
0
                        if (field->type == VAL_CLOSURE && field->as.closure.native_fn &&
4763
0
                            field->as.closure.default_values != VM_NATIVE_MARKER) {
4764
0
                            Chunk *fn_chunk = (Chunk *)field->as.closure.native_fn;
4765
0
                            ObjUpvalue **upvals = (ObjUpvalue **)field->as.closure.captured_env;
4766
0
                            size_t uv_count = field->region_id != (size_t)-1 ? field->region_id : 0;
4767
0
                            if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4768
0
                                VM_ERROR("stack overflow (too many nested calls)"); break;
4769
0
                            }
4770
0
                            stackvm_promote_frame_ephemerals(vm, frame);
4771
0
                            LatValue self_copy = value_deep_clone(obj);
4772
0
                            LatValue closure_copy = value_deep_clone(field);
4773
0
                            push(vm, value_nil());
4774
0
                            for (int si = arg_count; si >= 1; si--)
4775
0
                                obj[si + 1] = obj[si];
4776
0
                            obj[1] = self_copy;
4777
0
                            value_free(obj);
4778
0
                            *obj = closure_copy;
4779
0
                            StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4780
0
                            new_frame->chunk = fn_chunk;
4781
0
                            new_frame->ip = fn_chunk->code;
4782
0
                            new_frame->slots = obj;
4783
0
                            new_frame->upvalues = upvals;
4784
0
                            new_frame->upvalue_count = uv_count;
4785
0
                            frame = new_frame;
4786
0
                            handled = true;
4787
0
                            break;
4788
0
                        }
4789
0
                        if (field->type == VAL_CLOSURE &&
4790
0
                            field->as.closure.default_values == VM_NATIVE_MARKER) {
4791
0
                            VMNativeFn native = (VMNativeFn)field->as.closure.native_fn;
4792
0
                            LatValue self_copy = value_deep_clone(obj);
4793
0
                            int total_args = arg_count + 1;
4794
0
                            LatValue *args = malloc(total_args * sizeof(LatValue));
4795
0
                            args[0] = self_copy;
4796
0
                            for (int ai = arg_count - 1; ai >= 0; ai--)
4797
0
                                args[ai + 1] = pop(vm);
4798
0
                            LatValue obj_popped = pop(vm);
4799
0
                            LatValue ret = native(args, total_args);
4800
0
                            for (int ai = 0; ai < total_args; ai++)
4801
0
                                value_free(&args[ai]);
4802
0
                            free(args);
4803
0
                            value_free(&obj_popped);
4804
0
                            push(vm, ret);
4805
0
                            handled = true;
4806
0
                            break;
4807
0
                        }
4808
0
                        break;
4809
0
                    }
4810
0
                    if (handled) break;
4811
0
                }
4812
4813
                /* Try compiled method via "TypeName::method" global */
4814
0
                {
4815
0
                    const char *type_name = (obj->type == VAL_STRUCT) ? obj->as.strct.name :
4816
0
                                            (obj->type == VAL_ENUM)   ? obj->as.enm.enum_name :
4817
0
                                            value_type_name(obj);
4818
0
                    char key[256];
4819
0
                    snprintf(key, sizeof(key), "%s::%s", type_name, method_name);
4820
0
                    LatValue *method_ref = env_get_ref(vm->env, key);
4821
0
                    if (method_ref &&
4822
0
                        method_ref->type == VAL_CLOSURE && method_ref->as.closure.native_fn) {
4823
0
                        Chunk *fn_chunk = (Chunk *)method_ref->as.closure.native_fn;
4824
0
                        if (vm->frame_count >= STACKVM_FRAMES_MAX) {
4825
0
                            VM_ERROR("stack overflow (too many nested calls)"); break;
4826
0
                        }
4827
0
                        stackvm_promote_frame_ephemerals(vm, frame);
4828
                        /* Replace obj with self clone, shift args */
4829
0
                        LatValue self_copy = value_deep_clone(obj);
4830
0
                        value_free(obj);
4831
0
                        *obj = self_copy;
4832
0
                        StackCallFrame *new_frame = &vm->frames[vm->frame_count++];
4833
0
                        new_frame->chunk = fn_chunk;
4834
0
                        new_frame->ip = fn_chunk->code;
4835
0
                        new_frame->slots = obj;
4836
0
                        new_frame->upvalues = NULL;
4837
0
                        new_frame->upvalue_count = 0;
4838
0
                        frame = new_frame;
4839
0
                    } else {
4840
0
                        for (int i = 0; i < arg_count; i++) {
4841
0
                            LatValue v = pop(vm);
4842
0
                            value_free(&v);
4843
0
                        }
4844
0
                        LatValue obj_popped = pop(vm);
4845
0
                        value_free(&obj_popped);
4846
0
                        push(vm, value_nil());
4847
0
                    }
4848
0
                }
4849
0
                break;
4850
0
            }
4851
4852
0
#ifdef VM_USE_COMPUTED_GOTO
4853
27
            lbl_OP_SET_INDEX_LOCAL:
4854
27
#endif
4855
27
            case OP_SET_INDEX_LOCAL: {
4856
27
                uint8_t slot = READ_BYTE();
4857
27
                LatValue idx = pop(vm);
4858
27
                LatValue val = pop(vm);
4859
27
                stackvm_promote_value(&val);
4860
27
                LatValue *obj = &frame->slots[slot]; /* Direct pointer to local */
4861
4862
                /* Ref proxy: delegate set-index to inner value */
4863
27
                if (obj->type == VAL_REF) {
4864
1
                    if (obj->phase == VTAG_CRYSTAL) {
4865
0
                        value_free(&val);
4866
0
                        VM_ERROR("cannot assign index on a frozen Ref"); break;
4867
0
                    }
4868
1
                    LatValue *inner = &obj->as.ref.ref->value;
4869
1
                    if (inner->type == VAL_ARRAY && idx.type == VAL_INT) {
4870
0
                        int64_t i = idx.as.int_val;
4871
0
                        if (i < 0 || (size_t)i >= inner->as.array.len) {
4872
0
                            value_free(&val);
4873
0
                            VM_ERROR("array index out of bounds: %lld (len %zu)",
4874
0
                                (long long)i, inner->as.array.len); break;
4875
0
                        }
4876
0
                        value_free(&inner->as.array.elems[i]);
4877
0
                        inner->as.array.elems[i] = val;
4878
0
                        break;
4879
0
                    }
4880
1
                    if (inner->type == VAL_MAP && idx.type == VAL_STR) {
4881
1
                        LatValue *old = (LatValue *)lat_map_get(inner->as.map.map, idx.as.str_val);
4882
1
                        if (old) value_free(old);
4883
1
                        lat_map_set(inner->as.map.map, idx.as.str_val, &val);
4884
1
                        value_free(&idx);
4885
1
                        break;
4886
1
                    }
4887
0
                    value_free(&val); value_free(&idx);
4888
0
                    VM_ERROR("invalid index assignment on Ref"); break;
4889
0
                }
4890
                /* Phase check: reject mutation on crystal/sublimated values */
4891
26
                if (obj->phase == VTAG_CRYSTAL || obj->phase == VTAG_SUBLIMATED) {
4892
                    /* Check per-field phases for structs/maps with partial freeze */
4893
1
                    bool field_frozen = false;
4894
1
                    if (obj->type == VAL_MAP && idx.type == VAL_STR && obj->as.map.key_phases) {
4895
0
                        PhaseTag *kp = lat_map_get(obj->as.map.key_phases, idx.as.str_val);
4896
0
                        if (kp && *kp == VTAG_CRYSTAL) field_frozen = true;
4897
0
                    }
4898
1
                    if (obj->phase == VTAG_CRYSTAL || obj->phase == VTAG_SUBLIMATED || field_frozen) {
4899
1
                        value_free(&val); value_free(&idx);
4900
1
                        VM_ERROR("cannot modify a %s value",
4901
1
                            obj->phase == VTAG_CRYSTAL ? "frozen" : "sublimated"); break;
4902
1
                    }
4903
1
                }
4904
                /* Check per-key phase on non-frozen maps */
4905
25
                if (obj->type == VAL_MAP && idx.type == VAL_STR && obj->as.map.key_phases) {
4906
3
                    PhaseTag *kp = lat_map_get(obj->as.map.key_phases, idx.as.str_val);
4907
3
                    if (kp && *kp == VTAG_CRYSTAL) {
4908
1
                        value_free(&val); value_free(&idx);
4909
1
                        VM_ERROR("cannot modify frozen key '%s'", idx.as.str_val); break;
4910
1
                    }
4911
3
                }
4912
24
                if (obj->type == VAL_ARRAY && idx.type == VAL_INT) {
4913
2
                    int64_t i = idx.as.int_val;
4914
2
                    if (i < 0 || (size_t)i >= obj->as.array.len) {
4915
0
                        value_free(&val);
4916
0
                        VM_ERROR("array index out of bounds: %lld (len %zu)",
4917
0
                            (long long)i, obj->as.array.len); break;
4918
0
                    }
4919
2
                    value_free(&obj->as.array.elems[i]);
4920
2
                    obj->as.array.elems[i] = val;
4921
22
                } else if (obj->type == VAL_MAP && idx.type == VAL_STR) {
4922
22
                    lat_map_set(obj->as.map.map, idx.as.str_val, &val);
4923
22
                    value_free(&idx);
4924
22
                } else if (obj->type == VAL_BUFFER && idx.type == VAL_INT) {
4925
0
                    int64_t i = idx.as.int_val;
4926
0
                    if (i < 0 || (size_t)i >= obj->as.buffer.len) {
4927
0
                        value_free(&val);
4928
0
                        VM_ERROR("buffer index out of bounds: %lld (len %zu)",
4929
0
                            (long long)i, obj->as.buffer.len); break;
4930
0
                    }
4931
0
                    obj->as.buffer.data[i] = (uint8_t)(val.as.int_val & 0xFF);
4932
0
                    value_free(&val);
4933
0
                } else {
4934
0
                    value_free(&val);
4935
0
                    value_free(&idx);
4936
0
                    VM_ERROR("invalid index assignment"); break;
4937
0
                }
4938
24
                break;
4939
24
            }
4940
4941
            /* ── Exception handling ── */
4942
24
#ifdef VM_USE_COMPUTED_GOTO
4943
38
            lbl_OP_PUSH_EXCEPTION_HANDLER:
4944
38
#endif
4945
38
            case OP_PUSH_EXCEPTION_HANDLER: {
4946
38
                uint16_t offset = READ_U16();
4947
38
                if (vm->handler_count >= STACKVM_HANDLER_MAX) {
4948
0
                    VM_ERROR("too many nested exception handlers"); break;
4949
0
                }
4950
38
                StackExceptionHandler *h = &vm->handlers[vm->handler_count++];
4951
38
                h->ip = frame->ip + offset;
4952
38
                h->chunk = frame->chunk;
4953
38
                h->frame_index = vm->frame_count - 1;
4954
38
                h->stack_top = vm->stack_top;
4955
38
                break;
4956
38
            }
4957
4958
0
#ifdef VM_USE_COMPUTED_GOTO
4959
14
            lbl_OP_POP_EXCEPTION_HANDLER:
4960
14
#endif
4961
14
            case OP_POP_EXCEPTION_HANDLER: {
4962
14
                if (vm->handler_count > 0)
4963
14
                    vm->handler_count--;
4964
14
                break;
4965
14
            }
4966
4967
0
#ifdef VM_USE_COMPUTED_GOTO
4968
10
            lbl_OP_THROW:
4969
10
#endif
4970
10
            case OP_THROW: {
4971
10
                LatValue err = pop(vm);
4972
10
                if (vm->handler_count > 0) {
4973
0
                    StackExceptionHandler h = vm->handlers[--vm->handler_count];
4974
                    /* Unwind stack */
4975
0
                    while (vm->frame_count - 1 > h.frame_index) {
4976
0
                        vm->frame_count--;
4977
0
                    }
4978
0
                    frame = &vm->frames[vm->frame_count - 1];
4979
0
                    vm->stack_top = h.stack_top;
4980
0
                    frame->ip = h.ip;
4981
0
                    push(vm, err);
4982
10
                } else {
4983
10
                    StackVMResult res;
4984
10
                    if (err.type == VAL_STR) {
4985
10
                        res = runtime_error(vm, "%s", err.as.str_val);
4986
10
                    } else {
4987
0
                        char *repr = value_repr(&err);
4988
0
                        res = runtime_error(vm, "unhandled exception: %s", repr);
4989
0
                        free(repr);
4990
0
                    }
4991
10
                    value_free(&err);
4992
10
                    return res;
4993
10
                }
4994
0
                break;
4995
10
            }
4996
4997
0
#ifdef VM_USE_COMPUTED_GOTO
4998
6
            lbl_OP_TRY_UNWRAP:
4999
6
#endif
5000
6
            case OP_TRY_UNWRAP: {
5001
                /* Check if TOS is a map with "tag" = "ok" or "tag" = "err" */
5002
6
                LatValue *val = stackvm_peek(vm, 0);
5003
6
                if (val->type == VAL_MAP) {
5004
5
                    LatValue *tag = lat_map_get(val->as.map.map, "tag");
5005
5
                    if (tag && tag->type == VAL_STR) {
5006
5
                        if (strcmp(tag->as.str_val, "ok") == 0) {
5007
3
                            LatValue *inner = lat_map_get(val->as.map.map, "value");
5008
3
                            LatValue result_val = inner ? value_deep_clone(inner) : value_nil();
5009
3
                            LatValue old = pop(vm);
5010
3
                            value_free(&old);
5011
3
                            push(vm, result_val);
5012
3
                            break;
5013
3
                        } else if (strcmp(tag->as.str_val, "err") == 0) {
5014
                            /* Return the error from the current function */
5015
2
                            LatValue err_map = pop(vm);
5016
2
                            close_upvalues(vm, frame->slots);
5017
2
                            vm->frame_count--;
5018
2
                            if (vm->frame_count == 0) {
5019
0
                                *result = err_map;
5020
0
                                return STACKVM_OK;
5021
0
                            }
5022
2
                            vm->stack_top = frame->slots;
5023
2
                            push(vm, err_map);
5024
2
                            frame = &vm->frames[vm->frame_count - 1];
5025
2
                            break;
5026
2
                        }
5027
5
                    }
5028
5
                }
5029
                /* Not a result map - error */
5030
1
                value_free(val);
5031
1
                (void)pop(vm);  /* we already freed the peeked value */
5032
1
                VM_ERROR("'?' operator requires a result map with {tag: \"ok\"|\"err\", value: ...}");
5033
0
                break;
5034
1
            }
5035
5036
            /* ── Defer ── */
5037
0
#ifdef VM_USE_COMPUTED_GOTO
5038
8
            lbl_OP_DEFER_PUSH:
5039
8
#endif
5040
8
            case OP_DEFER_PUSH: {
5041
8
                uint8_t sdepth = READ_BYTE();
5042
8
                uint16_t offset = READ_U16();
5043
8
                if (vm->defer_count < STACKVM_DEFER_MAX) {
5044
8
                    StackDeferEntry *d = &vm->defers[vm->defer_count++];
5045
8
                    d->ip = frame->ip; /* points to start of defer body (current ip after reading offset) */
5046
8
                    d->chunk = frame->chunk;
5047
8
                    d->frame_index = vm->frame_count - 1;
5048
8
                    d->slots = frame->slots;
5049
8
                    d->scope_depth = sdepth;
5050
8
                }
5051
8
                frame->ip += offset; /* skip defer body */
5052
8
                break;
5053
8
            }
5054
5055
0
#ifdef VM_USE_COMPUTED_GOTO
5056
3.18k
            lbl_OP_DEFER_RUN:
5057
3.18k
#endif
5058
3.18k
            case OP_DEFER_RUN: {
5059
                /* Execute pending defers for the current function in LIFO order.
5060
                 * Only run defers that belong to the current call frame AND
5061
                 * have scope_depth >= the operand (scope-aware execution). */
5062
3.18k
                uint8_t min_depth = READ_BYTE();
5063
3.18k
                size_t current_frame_idx = vm->frame_count - 1;
5064
3.19k
                while (vm->defer_count > 0) {
5065
8
                    StackDeferEntry *d = &vm->defers[vm->defer_count - 1];
5066
8
                    if (d->frame_index != current_frame_idx) break;
5067
8
                    if (d->scope_depth < min_depth) break;
5068
8
                    vm->defer_count--;
5069
5070
                    /* Save return value (TOS) — we push it back after defer */
5071
8
                    LatValue ret_val = pop(vm);
5072
5073
                    /* Create a view chunk over the defer body's bytecode.
5074
                     * The body starts at d->ip and ends with OP_RETURN.
5075
                     * stackvm_run will push a new frame and execute until OP_RETURN.
5076
                     * Use next_frame_slots so the defer body shares the parent
5077
                     * frame's locals (defer body bytecode uses parent slot indices). */
5078
8
                    Chunk wrapper;
5079
8
                    memset(&wrapper, 0, sizeof(wrapper));
5080
8
                    wrapper.code = d->ip;
5081
8
                    wrapper.code_len = (size_t)(d->chunk->code + d->chunk->code_len - d->ip);
5082
8
                    wrapper.constants = d->chunk->constants;
5083
8
                    wrapper.const_len = d->chunk->const_len;
5084
8
                    wrapper.const_hashes = d->chunk->const_hashes;
5085
8
                    wrapper.lines = d->chunk->lines ? d->chunk->lines + (d->ip - d->chunk->code) : NULL;
5086
5087
8
                    vm->next_frame_slots = d->slots;
5088
8
                    LatValue defer_result;
5089
8
                    stackvm_run(vm, &wrapper, &defer_result);
5090
8
                    value_free(&defer_result);
5091
5092
                    /* Restore the return value */
5093
8
                    push(vm, ret_val);
5094
8
                }
5095
3.18k
                break;
5096
3.18k
            }
5097
5098
            /* ── Phase system ── */
5099
0
#ifdef VM_USE_COMPUTED_GOTO
5100
44
            lbl_OP_FREEZE:
5101
44
#endif
5102
44
            case OP_FREEZE: {
5103
44
                LatValue val = pop(vm);
5104
44
                if (val.type == VAL_CHANNEL) {
5105
0
                    value_free(&val);
5106
0
                    VM_ERROR("cannot freeze a channel"); break;
5107
0
                }
5108
44
                LatValue frozen = value_freeze(val);
5109
44
                push(vm, frozen);
5110
44
                break;
5111
44
            }
5112
5113
0
#ifdef VM_USE_COMPUTED_GOTO
5114
6
            lbl_OP_THAW:
5115
6
#endif
5116
6
            case OP_THAW: {
5117
6
                LatValue val = pop(vm);
5118
6
                LatValue thawed = value_thaw(&val);
5119
6
                value_free(&val);
5120
6
                push(vm, thawed);
5121
6
                break;
5122
6
            }
5123
5124
0
#ifdef VM_USE_COMPUTED_GOTO
5125
3
            lbl_OP_CLONE:
5126
3
#endif
5127
3
            case OP_CLONE: {
5128
3
                LatValue val = pop(vm);
5129
3
                LatValue cloned = value_deep_clone(&val);
5130
3
                value_free(&val);
5131
3
                push(vm, cloned);
5132
3
                break;
5133
3
            }
5134
5135
0
#ifdef VM_USE_COMPUTED_GOTO
5136
343
            lbl_OP_MARK_FLUID:
5137
343
#endif
5138
343
            case OP_MARK_FLUID: {
5139
343
                stackvm_peek(vm, 0)->phase = VTAG_FLUID;
5140
343
                break;
5141
343
            }
5142
5143
            /* ── Phase system: reactions, bonds, seeds ── */
5144
5145
0
#ifdef VM_USE_COMPUTED_GOTO
5146
9
            lbl_OP_REACT:
5147
9
#endif
5148
9
            case OP_REACT: {
5149
9
                uint8_t name_idx = READ_BYTE();
5150
9
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5151
9
                LatValue callback = pop(vm);
5152
9
                if (callback.type != VAL_CLOSURE) {
5153
0
                    value_free(&callback);
5154
0
                    push(vm, value_unit());
5155
0
                    break;
5156
0
                }
5157
                /* Find or create reaction entry */
5158
9
                size_t ri = vm->rt->reaction_count;
5159
9
                for (size_t i = 0; i < vm->rt->reaction_count; i++) {
5160
1
                    if (strcmp(vm->rt->reactions[i].var_name, var_name) == 0) { ri = i; break; }
5161
1
                }
5162
9
                if (ri == vm->rt->reaction_count) {
5163
8
                    if (vm->rt->reaction_count >= vm->rt->reaction_cap) {
5164
8
                        vm->rt->reaction_cap = vm->rt->reaction_cap ? vm->rt->reaction_cap * 2 : 4;
5165
8
                        vm->rt->reactions = realloc(vm->rt->reactions, vm->rt->reaction_cap * sizeof(*vm->rt->reactions));
5166
8
                    }
5167
8
                    vm->rt->reactions[ri].var_name = strdup(var_name);
5168
8
                    vm->rt->reactions[ri].callbacks = NULL;
5169
8
                    vm->rt->reactions[ri].cb_count = 0;
5170
8
                    vm->rt->reactions[ri].cb_cap = 0;
5171
8
                    vm->rt->reaction_count++;
5172
8
                }
5173
9
                if (vm->rt->reactions[ri].cb_count >= vm->rt->reactions[ri].cb_cap) {
5174
8
                    vm->rt->reactions[ri].cb_cap = vm->rt->reactions[ri].cb_cap ? vm->rt->reactions[ri].cb_cap * 2 : 4;
5175
8
                    vm->rt->reactions[ri].callbacks = realloc(vm->rt->reactions[ri].callbacks,
5176
8
                        vm->rt->reactions[ri].cb_cap * sizeof(LatValue));
5177
8
                }
5178
9
                vm->rt->reactions[ri].callbacks[vm->rt->reactions[ri].cb_count++] = value_deep_clone(&callback);
5179
9
                value_free(&callback);
5180
9
                push(vm, value_unit());
5181
9
                break;
5182
9
            }
5183
5184
0
#ifdef VM_USE_COMPUTED_GOTO
5185
1
            lbl_OP_UNREACT:
5186
1
#endif
5187
1
            case OP_UNREACT: {
5188
1
                uint8_t name_idx = READ_BYTE();
5189
1
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5190
1
                for (size_t i = 0; i < vm->rt->reaction_count; i++) {
5191
1
                    if (strcmp(vm->rt->reactions[i].var_name, var_name) != 0) continue;
5192
1
                    free(vm->rt->reactions[i].var_name);
5193
2
                    for (size_t j = 0; j < vm->rt->reactions[i].cb_count; j++)
5194
1
                        value_free(&vm->rt->reactions[i].callbacks[j]);
5195
1
                    free(vm->rt->reactions[i].callbacks);
5196
1
                    vm->rt->reactions[i] = vm->rt->reactions[--vm->rt->reaction_count];
5197
1
                    break;
5198
1
                }
5199
1
                push(vm, value_unit());
5200
1
                break;
5201
1
            }
5202
5203
0
#ifdef VM_USE_COMPUTED_GOTO
5204
17
            lbl_OP_BOND:
5205
17
#endif
5206
17
            case OP_BOND: {
5207
17
                uint8_t target_idx = READ_BYTE();
5208
17
                const char *target_name = frame->chunk->constants[target_idx].as.str_val;
5209
17
                LatValue strategy_v = pop(vm);
5210
17
                LatValue dep_v = pop(vm);
5211
17
                const char *dep_name = (dep_v.type == VAL_STR) ? dep_v.as.str_val : "";
5212
17
                const char *strategy = (strategy_v.type == VAL_STR) ? strategy_v.as.str_val : "mirror";
5213
5214
                /* Validate: dep must be a named variable (non-empty) */
5215
17
                if (dep_name[0] == '\0') {
5216
1
                    value_free(&dep_v); value_free(&strategy_v);
5217
1
                    vm->error = strdup("bond() requires variable names for dependencies");
5218
1
                    StackVMResult r = stackvm_handle_native_error(vm, &frame);
5219
1
                    if (r != STACKVM_OK) return r;
5220
0
                    break;
5221
1
                }
5222
                /* Validate: target must not be already frozen */
5223
16
                {
5224
16
                    LatValue target_val;
5225
16
                    bool target_found = env_get(vm->env, target_name, &target_val);
5226
16
                    if (!target_found) target_found = stackvm_find_local_value(vm, target_name, &target_val);
5227
16
                    if (target_found) {
5228
16
                        if (target_val.phase == VTAG_CRYSTAL) {
5229
1
                            value_free(&target_val); value_free(&dep_v); value_free(&strategy_v);
5230
1
                            char *msg = NULL;
5231
1
                            (void)asprintf(&msg, "cannot bond already-frozen variable '%s'", target_name);
5232
1
                            vm->error = msg;
5233
1
                            StackVMResult r = stackvm_handle_native_error(vm, &frame);
5234
1
                            if (r != STACKVM_OK) return r;
5235
0
                            break;
5236
1
                        }
5237
15
                        value_free(&target_val);
5238
15
                    }
5239
16
                }
5240
                /* Validate: dep variable must exist */
5241
15
                {
5242
15
                    LatValue dep_val;
5243
15
                    bool dep_found = env_get(vm->env, dep_name, &dep_val);
5244
15
                    if (!dep_found) dep_found = stackvm_find_local_value(vm, dep_name, &dep_val);
5245
15
                    if (!dep_found) {
5246
1
                        char *msg = NULL;
5247
1
                        (void)asprintf(&msg, "cannot bond undefined variable '%s'", dep_name);
5248
1
                        value_free(&dep_v); value_free(&strategy_v);
5249
1
                        vm->error = msg;
5250
1
                        StackVMResult r = stackvm_handle_native_error(vm, &frame);
5251
1
                        if (r != STACKVM_OK) return r;
5252
0
                        break;
5253
1
                    }
5254
14
                    value_free(&dep_val);
5255
14
                }
5256
                /* Find or create bond entry */
5257
0
                size_t bi = vm->rt->bond_count;
5258
15
                for (size_t i = 0; i < vm->rt->bond_count; i++) {
5259
3
                    if (strcmp(vm->rt->bonds[i].target, target_name) == 0) { bi = i; break; }
5260
3
                }
5261
14
                if (bi == vm->rt->bond_count) {
5262
12
                    if (vm->rt->bond_count >= vm->rt->bond_cap) {
5263
11
                        vm->rt->bond_cap = vm->rt->bond_cap ? vm->rt->bond_cap * 2 : 4;
5264
11
                        vm->rt->bonds = realloc(vm->rt->bonds, vm->rt->bond_cap * sizeof(*vm->rt->bonds));
5265
11
                    }
5266
12
                    vm->rt->bonds[bi].target = strdup(target_name);
5267
12
                    vm->rt->bonds[bi].deps = NULL;
5268
12
                    vm->rt->bonds[bi].dep_strategies = NULL;
5269
12
                    vm->rt->bonds[bi].dep_count = 0;
5270
12
                    vm->rt->bonds[bi].dep_cap = 0;
5271
12
                    vm->rt->bond_count++;
5272
12
                }
5273
14
                if (vm->rt->bonds[bi].dep_count >= vm->rt->bonds[bi].dep_cap) {
5274
12
                    vm->rt->bonds[bi].dep_cap = vm->rt->bonds[bi].dep_cap ? vm->rt->bonds[bi].dep_cap * 2 : 4;
5275
12
                    vm->rt->bonds[bi].deps = realloc(vm->rt->bonds[bi].deps,
5276
12
                        vm->rt->bonds[bi].dep_cap * sizeof(char *));
5277
12
                    vm->rt->bonds[bi].dep_strategies = realloc(vm->rt->bonds[bi].dep_strategies,
5278
12
                        vm->rt->bonds[bi].dep_cap * sizeof(char *));
5279
12
                }
5280
14
                vm->rt->bonds[bi].deps[vm->rt->bonds[bi].dep_count] = strdup(dep_name);
5281
14
                vm->rt->bonds[bi].dep_strategies[vm->rt->bonds[bi].dep_count] = strdup(strategy);
5282
14
                vm->rt->bonds[bi].dep_count++;
5283
14
                value_free(&dep_v);
5284
14
                value_free(&strategy_v);
5285
14
                push(vm, value_unit());
5286
14
                break;
5287
15
            }
5288
5289
0
#ifdef VM_USE_COMPUTED_GOTO
5290
1
            lbl_OP_UNBOND:
5291
1
#endif
5292
1
            case OP_UNBOND: {
5293
1
                uint8_t target_idx = READ_BYTE();
5294
1
                const char *target_name = frame->chunk->constants[target_idx].as.str_val;
5295
1
                LatValue dep_v = pop(vm);
5296
1
                const char *dep_name = (dep_v.type == VAL_STR) ? dep_v.as.str_val : "";
5297
1
                for (size_t i = 0; i < vm->rt->bond_count; i++) {
5298
1
                    if (strcmp(vm->rt->bonds[i].target, target_name) != 0) continue;
5299
1
                    for (size_t j = 0; j < vm->rt->bonds[i].dep_count; j++) {
5300
1
                        if (strcmp(vm->rt->bonds[i].deps[j], dep_name) != 0) continue;
5301
1
                        free(vm->rt->bonds[i].deps[j]);
5302
1
                        if (vm->rt->bonds[i].dep_strategies) free(vm->rt->bonds[i].dep_strategies[j]);
5303
                        /* Swap-remove */
5304
1
                        vm->rt->bonds[i].deps[j] = vm->rt->bonds[i].deps[vm->rt->bonds[i].dep_count - 1];
5305
1
                        if (vm->rt->bonds[i].dep_strategies)
5306
1
                            vm->rt->bonds[i].dep_strategies[j] = vm->rt->bonds[i].dep_strategies[vm->rt->bonds[i].dep_count - 1];
5307
1
                        vm->rt->bonds[i].dep_count--;
5308
1
                        break;
5309
1
                    }
5310
                    /* If empty, remove the bond entry */
5311
1
                    if (vm->rt->bonds[i].dep_count == 0) {
5312
1
                        free(vm->rt->bonds[i].target);
5313
1
                        free(vm->rt->bonds[i].deps);
5314
1
                        free(vm->rt->bonds[i].dep_strategies);
5315
1
                        vm->rt->bonds[i] = vm->rt->bonds[--vm->rt->bond_count];
5316
1
                    }
5317
1
                    break;
5318
1
                }
5319
1
                value_free(&dep_v);
5320
1
                push(vm, value_unit());
5321
1
                break;
5322
1
            }
5323
5324
0
#ifdef VM_USE_COMPUTED_GOTO
5325
4
            lbl_OP_SEED:
5326
4
#endif
5327
4
            case OP_SEED: {
5328
4
                uint8_t name_idx = READ_BYTE();
5329
4
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5330
4
                LatValue contract = pop(vm);
5331
4
                if (contract.type != VAL_CLOSURE) {
5332
0
                    value_free(&contract);
5333
0
                    push(vm, value_unit());
5334
0
                    break;
5335
0
                }
5336
4
                if (vm->rt->seed_count >= vm->rt->seed_cap) {
5337
4
                    vm->rt->seed_cap = vm->rt->seed_cap ? vm->rt->seed_cap * 2 : 4;
5338
4
                    vm->rt->seeds = realloc(vm->rt->seeds, vm->rt->seed_cap * sizeof(*vm->rt->seeds));
5339
4
                }
5340
4
                vm->rt->seeds[vm->rt->seed_count].var_name = strdup(var_name);
5341
4
                vm->rt->seeds[vm->rt->seed_count].contract = value_deep_clone(&contract);
5342
4
                vm->rt->seed_count++;
5343
4
                value_free(&contract);
5344
4
                push(vm, value_unit());
5345
4
                break;
5346
4
            }
5347
5348
0
#ifdef VM_USE_COMPUTED_GOTO
5349
1
            lbl_OP_UNSEED:
5350
1
#endif
5351
1
            case OP_UNSEED: {
5352
1
                uint8_t name_idx = READ_BYTE();
5353
1
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5354
1
                for (size_t i = 0; i < vm->rt->seed_count; i++) {
5355
1
                    if (strcmp(vm->rt->seeds[i].var_name, var_name) != 0) continue;
5356
1
                    free(vm->rt->seeds[i].var_name);
5357
1
                    value_free(&vm->rt->seeds[i].contract);
5358
1
                    vm->rt->seeds[i] = vm->rt->seeds[--vm->rt->seed_count];
5359
1
                    break;
5360
1
                }
5361
1
                push(vm, value_unit());
5362
1
                break;
5363
1
            }
5364
5365
0
#ifdef VM_USE_COMPUTED_GOTO
5366
201
            lbl_OP_FREEZE_VAR:
5367
201
#endif
5368
201
            case OP_FREEZE_VAR: {
5369
201
                uint8_t name_idx = READ_BYTE();
5370
201
                uint8_t loc_type = READ_BYTE();
5371
201
                uint8_t loc_slot = READ_BYTE();
5372
201
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5373
201
                LatValue val = pop(vm);
5374
201
                if (val.type == VAL_CHANNEL) {
5375
1
                    value_free(&val);
5376
1
                    VM_ERROR("cannot freeze a channel"); break;
5377
1
                }
5378
                /* Validate seed contracts (don't consume — matches tree-walker freeze behavior) */
5379
200
                char *seed_err = stackvm_validate_seeds(vm, var_name, &val, false);
5380
200
                if (seed_err) {
5381
1
                    value_free(&val);
5382
1
                    vm->error = seed_err;
5383
1
                    StackVMResult r = stackvm_handle_native_error(vm, &frame);
5384
1
                    if (r != STACKVM_OK) return r;
5385
0
                    break;
5386
1
                }
5387
199
                LatValue frozen = value_freeze(val);
5388
199
                LatValue ret = value_deep_clone(&frozen);
5389
199
                stackvm_write_back(vm, frame, loc_type, loc_slot, var_name, frozen);
5390
199
                value_free(&frozen);
5391
199
                StackVMResult cr = stackvm_freeze_cascade(vm, &frame, var_name);
5392
199
                if (cr != STACKVM_OK) { value_free(&ret); return cr; }
5393
198
                StackVMResult rr = stackvm_fire_reactions(vm, &frame, var_name, "crystal");
5394
198
                if (rr != STACKVM_OK) { value_free(&ret); return rr; }
5395
197
                push(vm, ret);
5396
197
                break;
5397
198
            }
5398
5399
0
#ifdef VM_USE_COMPUTED_GOTO
5400
165
            lbl_OP_THAW_VAR:
5401
165
#endif
5402
165
            case OP_THAW_VAR: {
5403
165
                uint8_t name_idx = READ_BYTE();
5404
165
                uint8_t loc_type = READ_BYTE();
5405
165
                uint8_t loc_slot = READ_BYTE();
5406
165
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5407
165
                LatValue val = pop(vm);
5408
165
                LatValue thawed = value_thaw(&val);
5409
165
                value_free(&val);
5410
165
                LatValue ret = value_deep_clone(&thawed);
5411
165
                stackvm_write_back(vm, frame, loc_type, loc_slot, var_name, thawed);
5412
165
                value_free(&thawed);
5413
165
                StackVMResult rr = stackvm_fire_reactions(vm, &frame, var_name, "fluid");
5414
165
                if (rr != STACKVM_OK) { value_free(&ret); return rr; }
5415
165
                push(vm, ret);
5416
165
                break;
5417
165
            }
5418
5419
0
#ifdef VM_USE_COMPUTED_GOTO
5420
5
            lbl_OP_SUBLIMATE_VAR:
5421
5
#endif
5422
5
            case OP_SUBLIMATE_VAR: {
5423
5
                uint8_t name_idx = READ_BYTE();
5424
5
                uint8_t loc_type = READ_BYTE();
5425
5
                uint8_t loc_slot = READ_BYTE();
5426
5
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5427
5
                LatValue val = pop(vm);
5428
5
                val.phase = VTAG_SUBLIMATED;
5429
5
                LatValue ret = value_deep_clone(&val);
5430
5
                stackvm_write_back(vm, frame, loc_type, loc_slot, var_name, val);
5431
5
                value_free(&val);
5432
5
                StackVMResult rr = stackvm_fire_reactions(vm, &frame, var_name, "sublimated");
5433
5
                if (rr != STACKVM_OK) { value_free(&ret); return rr; }
5434
5
                push(vm, ret);
5435
5
                break;
5436
5
            }
5437
5438
0
#ifdef VM_USE_COMPUTED_GOTO
5439
0
            lbl_OP_SUBLIMATE:
5440
0
#endif
5441
0
            case OP_SUBLIMATE: {
5442
0
                LatValue val = pop(vm);
5443
0
                val.phase = VTAG_SUBLIMATED;
5444
0
                push(vm, val);
5445
0
                break;
5446
0
            }
5447
5448
0
#ifdef VM_USE_COMPUTED_GOTO
5449
16
            lbl_OP_IS_CRYSTAL:
5450
16
#endif
5451
16
            case OP_IS_CRYSTAL: {
5452
16
                LatValue val = pop(vm);
5453
16
                bool is_crystal = (val.phase == VTAG_CRYSTAL);
5454
16
                value_free(&val);
5455
16
                push(vm, value_bool(is_crystal));
5456
16
                break;
5457
16
            }
5458
5459
0
#ifdef VM_USE_COMPUTED_GOTO
5460
3
            lbl_OP_FREEZE_EXCEPT:
5461
3
#endif
5462
3
            case OP_FREEZE_EXCEPT: {
5463
3
                uint8_t name_idx = READ_BYTE();
5464
3
                uint8_t loc_type = READ_BYTE();
5465
3
                uint8_t loc_slot = READ_BYTE();
5466
3
                uint8_t except_count = READ_BYTE();
5467
3
                const char *var_name = frame->chunk->constants[name_idx].as.str_val;
5468
5469
                /* Pop except field names from stack (pushed first-to-last) */
5470
3
                char **except_names = malloc(except_count * sizeof(char *));
5471
6
                for (int i = except_count - 1; i >= 0; i--) {
5472
3
                    LatValue v = pop(vm);
5473
3
                    except_names[i] = (v.type == VAL_STR) ? strdup(v.as.str_val) : strdup("");
5474
3
                    value_free(&v);
5475
3
                }
5476
5477
                /* Get a working copy of the variable value */
5478
3
                LatValue val;
5479
3
                switch (loc_type) {
5480
3
                    case 0: val = value_deep_clone(&frame->slots[loc_slot]); break;
5481
0
                    case 1:
5482
0
                        if (frame->upvalues && loc_slot < frame->upvalue_count && frame->upvalues[loc_slot])
5483
0
                            val = value_deep_clone(frame->upvalues[loc_slot]->location);
5484
0
                        else val = value_nil();
5485
0
                        break;
5486
0
                    default: {
5487
0
                        LatValue tmp;
5488
0
                        if (!env_get(vm->env, var_name, &tmp)) tmp = value_nil();
5489
0
                        val = tmp;
5490
0
                        break;
5491
0
                    }
5492
3
                }
5493
5494
3
                if (val.type == VAL_STRUCT) {
5495
2
                    if (!val.as.strct.field_phases) {
5496
2
                        val.as.strct.field_phases = calloc(val.as.strct.field_count, sizeof(PhaseTag));
5497
8
                        for (size_t i = 0; i < val.as.strct.field_count; i++)
5498
6
                            val.as.strct.field_phases[i] = val.phase;
5499
2
                    }
5500
8
                    for (size_t i = 0; i < val.as.strct.field_count; i++) {
5501
6
                        bool exempted = false;
5502
10
                        for (uint8_t j = 0; j < except_count; j++) {
5503
6
                            if (strcmp(val.as.strct.field_names[i], except_names[j]) == 0) {
5504
2
                                exempted = true; break;
5505
2
                            }
5506
6
                        }
5507
6
                        if (!exempted) {
5508
4
                            val.as.strct.field_values[i] = value_freeze(val.as.strct.field_values[i]);
5509
4
                            val.as.strct.field_phases[i] = VTAG_CRYSTAL;
5510
4
                        } else {
5511
2
                            val.as.strct.field_phases[i] = VTAG_FLUID;
5512
2
                        }
5513
6
                    }
5514
2
                } else if (val.type == VAL_MAP) {
5515
1
                    if (!val.as.map.key_phases) {
5516
1
                        val.as.map.key_phases = calloc(1, sizeof(LatMap));
5517
1
                        *val.as.map.key_phases = lat_map_new(sizeof(PhaseTag));
5518
1
                    }
5519
17
                    for (size_t i = 0; i < val.as.map.map->cap; i++) {
5520
16
                        if (val.as.map.map->entries[i].state != MAP_OCCUPIED) continue;
5521
3
                        const char *key = val.as.map.map->entries[i].key;
5522
3
                        bool exempted = false;
5523
5
                        for (uint8_t j = 0; j < except_count; j++) {
5524
3
                            if (strcmp(key, except_names[j]) == 0) {
5525
1
                                exempted = true; break;
5526
1
                            }
5527
3
                        }
5528
3
                        PhaseTag phase;
5529
3
                        if (!exempted) {
5530
2
                            LatValue *vp = (LatValue *)val.as.map.map->entries[i].value;
5531
2
                            *vp = value_freeze(*vp);
5532
2
                            phase = VTAG_CRYSTAL;
5533
2
                        } else {
5534
1
                            phase = VTAG_FLUID;
5535
1
                        }
5536
3
                        lat_map_set(val.as.map.key_phases, key, &phase);
5537
3
                    }
5538
1
                }
5539
5540
                /* Write back and push result */
5541
3
                LatValue ret = value_deep_clone(&val);
5542
3
                stackvm_write_back(vm, frame, loc_type, loc_slot, var_name, val);
5543
3
                value_free(&val);
5544
3
                push(vm, ret);
5545
6
                for (uint8_t i = 0; i < except_count; i++) free(except_names[i]);
5546
3
                free(except_names);
5547
3
                break;
5548
3
            }
5549
5550
0
#ifdef VM_USE_COMPUTED_GOTO
5551
5
            lbl_OP_FREEZE_FIELD:
5552
5
#endif
5553
5
            case OP_FREEZE_FIELD: {
5554
5
                uint8_t pname_idx = READ_BYTE();
5555
5
                uint8_t loc_type = READ_BYTE();
5556
5
                uint8_t loc_slot = READ_BYTE();
5557
5
                const char *parent_name = frame->chunk->constants[pname_idx].as.str_val;
5558
5
                LatValue field_name = pop(vm);
5559
5560
                /* Get a working copy of the parent variable */
5561
5
                LatValue parent;
5562
5
                switch (loc_type) {
5563
5
                    case 0: parent = value_deep_clone(&frame->slots[loc_slot]); break;
5564
0
                    case 1:
5565
0
                        if (frame->upvalues && loc_slot < frame->upvalue_count && frame->upvalues[loc_slot])
5566
0
                            parent = value_deep_clone(frame->upvalues[loc_slot]->location);
5567
0
                        else parent = value_nil();
5568
0
                        break;
5569
0
                    default: {
5570
0
                        LatValue tmp;
5571
0
                        if (!env_get(vm->env, parent_name, &tmp)) tmp = value_nil();
5572
0
                        parent = tmp;
5573
0
                        break;
5574
0
                    }
5575
5
                }
5576
5577
5
                if (parent.type == VAL_STRUCT && field_name.type == VAL_STR) {
5578
3
                    const char *fname = field_name.as.str_val;
5579
3
                    size_t fi = (size_t)-1;
5580
3
                    for (size_t i = 0; i < parent.as.strct.field_count; i++) {
5581
3
                        if (strcmp(parent.as.strct.field_names[i], fname) == 0) { fi = i; break; }
5582
3
                    }
5583
3
                    if (fi == (size_t)-1) {
5584
0
                        value_free(&parent); value_free(&field_name);
5585
0
                        VM_ERROR("struct has no field '%s'", fname); break;
5586
0
                    }
5587
3
                    parent.as.strct.field_values[fi] = value_freeze(parent.as.strct.field_values[fi]);
5588
3
                    if (!parent.as.strct.field_phases)
5589
3
                        parent.as.strct.field_phases = calloc(parent.as.strct.field_count, sizeof(PhaseTag));
5590
3
                    parent.as.strct.field_phases[fi] = VTAG_CRYSTAL;
5591
3
                    LatValue ret = value_deep_clone(&parent.as.strct.field_values[fi]);
5592
3
                    stackvm_write_back(vm, frame, loc_type, loc_slot, parent_name, parent);
5593
3
                    value_free(&parent);
5594
3
                    value_free(&field_name);
5595
3
                    push(vm, ret);
5596
3
                } else if (parent.type == VAL_MAP && field_name.type == VAL_STR) {
5597
2
                    const char *key = field_name.as.str_val;
5598
2
                    LatValue *val_ptr = (LatValue *)lat_map_get(parent.as.map.map, key);
5599
2
                    if (!val_ptr) {
5600
0
                        value_free(&parent); value_free(&field_name);
5601
0
                        VM_ERROR("map has no key '%s'", key); break;
5602
0
                    }
5603
2
                    *val_ptr = value_freeze(*val_ptr);
5604
2
                    if (!parent.as.map.key_phases) {
5605
2
                        parent.as.map.key_phases = calloc(1, sizeof(LatMap));
5606
2
                        *parent.as.map.key_phases = lat_map_new(sizeof(PhaseTag));
5607
2
                    }
5608
2
                    PhaseTag crystal = VTAG_CRYSTAL;
5609
2
                    lat_map_set(parent.as.map.key_phases, key, &crystal);
5610
2
                    LatValue ret = value_deep_clone(val_ptr);
5611
2
                    stackvm_write_back(vm, frame, loc_type, loc_slot, parent_name, parent);
5612
2
                    value_free(&parent);
5613
2
                    value_free(&field_name);
5614
2
                    push(vm, ret);
5615
2
                } else {
5616
0
                    value_free(&parent); value_free(&field_name);
5617
0
                    VM_ERROR("freeze field requires a struct or map"); break;
5618
0
                }
5619
5
                break;
5620
5
            }
5621
5622
            /* ── Print ── */
5623
5
#ifdef VM_USE_COMPUTED_GOTO
5624
1.01k
            lbl_OP_PRINT:
5625
1.01k
#endif
5626
1.01k
            case OP_PRINT: {
5627
1.01k
                uint8_t argc = READ_BYTE();
5628
1.01k
                LatValue *vals = malloc(argc * sizeof(LatValue));
5629
2.03k
                for (int i = argc - 1; i >= 0; i--)
5630
1.02k
                    vals[i] = pop(vm);
5631
2.03k
                for (uint8_t i = 0; i < argc; i++) {
5632
1.02k
                    if (i > 0) printf(" ");
5633
1.02k
                    if (vals[i].type == VAL_STR) {
5634
396
                        printf("%s", vals[i].as.str_val);
5635
624
                    } else {
5636
624
                        char *repr = value_repr(&vals[i]);
5637
624
                        printf("%s", repr);
5638
624
                        free(repr);
5639
624
                    }
5640
1.02k
                    value_free(&vals[i]);
5641
1.02k
                }
5642
1.01k
                printf("\n");
5643
1.01k
                free(vals);
5644
1.01k
                push(vm, value_unit());
5645
1.01k
                break;
5646
1.01k
            }
5647
5648
            /* ── Import ── */
5649
0
#ifdef VM_USE_COMPUTED_GOTO
5650
94
            lbl_OP_IMPORT:
5651
94
#endif
5652
94
            case OP_IMPORT: {
5653
94
                uint8_t path_idx = READ_BYTE();
5654
94
                const char *raw_path = frame->chunk->constants[path_idx].as.str_val;
5655
5656
                /* Check for built-in stdlib module */
5657
94
                LatValue builtin_mod;
5658
94
                if (rt_try_builtin_import(raw_path, &builtin_mod)) {
5659
11
                    push(vm, builtin_mod);
5660
11
                    break;
5661
11
                }
5662
5663
                /* Resolve file path: append .lat if not present */
5664
83
                size_t plen = strlen(raw_path);
5665
83
                char *file_path;
5666
83
                if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) {
5667
0
                    file_path = strdup(raw_path);
5668
83
                } else {
5669
83
                    file_path = malloc(plen + 5);
5670
83
                    memcpy(file_path, raw_path, plen);
5671
83
                    memcpy(file_path + plen, ".lat", 5);
5672
83
                }
5673
5674
                /* Resolve to absolute path */
5675
83
                char resolved[PATH_MAX];
5676
83
                if (!realpath(file_path, resolved)) {
5677
1
                    char errbuf[512];
5678
1
                    snprintf(errbuf, sizeof(errbuf), "import: cannot find '%s'", file_path);
5679
1
                    free(file_path);
5680
1
                    VM_ERROR("%s", errbuf); break;
5681
1
                }
5682
82
                free(file_path);
5683
5684
                /* Check module cache */
5685
82
                LatValue *cached = lat_map_get(&vm->module_cache, resolved);
5686
82
                if (cached) {
5687
1
                    push(vm, value_deep_clone(cached));
5688
1
                    break;
5689
1
                }
5690
5691
                /* Read the file */
5692
81
                char *source = builtin_read_file(resolved);
5693
81
                if (!source) {
5694
0
                    VM_ERROR("import: cannot read '%s'", resolved); break;
5695
0
                }
5696
5697
                /* Lex */
5698
81
                Lexer mod_lex = lexer_new(source);
5699
81
                char *lex_err = NULL;
5700
81
                LatVec mod_toks = lexer_tokenize(&mod_lex, &lex_err);
5701
81
                free(source);
5702
81
                if (lex_err) {
5703
0
                    char errmsg[1024];
5704
0
                    snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved, lex_err);
5705
0
                    free(lex_err);
5706
0
                    lat_vec_free(&mod_toks);
5707
0
                    VM_ERROR("%s", errmsg); break;
5708
0
                }
5709
5710
                /* Parse */
5711
81
                Parser mod_parser = parser_new(&mod_toks);
5712
81
                char *parse_err = NULL;
5713
81
                Program mod_prog = parser_parse(&mod_parser, &parse_err);
5714
81
                if (parse_err) {
5715
0
                    char errmsg[1024];
5716
0
                    snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved, parse_err);
5717
0
                    free(parse_err);
5718
0
                    program_free(&mod_prog);
5719
0
                    for (size_t ti = 0; ti < mod_toks.len; ti++)
5720
0
                        token_free(lat_vec_get(&mod_toks, ti));
5721
0
                    lat_vec_free(&mod_toks);
5722
0
                    VM_ERROR("%s", errmsg); break;
5723
0
                }
5724
5725
                /* Compile as module (no auto-call of main) */
5726
81
                char *comp_err = NULL;
5727
81
                Chunk *mod_chunk = stack_compile_module(&mod_prog, &comp_err);
5728
5729
                /* Free parse artifacts */
5730
81
                program_free(&mod_prog);
5731
117k
                for (size_t ti = 0; ti < mod_toks.len; ti++)
5732
117k
                    token_free(lat_vec_get(&mod_toks, ti));
5733
81
                lat_vec_free(&mod_toks);
5734
5735
81
                if (!mod_chunk) {
5736
0
                    char errmsg[1024];
5737
0
                    snprintf(errmsg, sizeof(errmsg), "import '%s': %s", resolved,
5738
0
                             comp_err ? comp_err : "compile error");
5739
0
                    free(comp_err);
5740
0
                    VM_ERROR("%s", errmsg); break;
5741
0
                }
5742
5743
                /* Track the chunk for proper lifetime management */
5744
81
                if (vm->fn_chunk_count >= vm->fn_chunk_cap) {
5745
81
                    vm->fn_chunk_cap = vm->fn_chunk_cap ? vm->fn_chunk_cap * 2 : 8;
5746
81
                    vm->fn_chunks = realloc(vm->fn_chunks,
5747
81
                                            vm->fn_chunk_cap * sizeof(Chunk *));
5748
81
                }
5749
81
                vm->fn_chunks[vm->fn_chunk_count++] = mod_chunk;
5750
5751
                /* Push a module scope so module globals are isolated */
5752
81
                env_push_scope(vm->env);
5753
5754
                /* Run the module chunk */
5755
81
                LatValue mod_result;
5756
81
                StackVMResult mod_r = stackvm_run(vm, mod_chunk, &mod_result);
5757
81
                if (mod_r != STACKVM_OK) {
5758
0
                    env_pop_scope(vm->env);
5759
0
                    push(vm, value_nil());
5760
0
                    break;
5761
0
                }
5762
81
                value_free(&mod_result);
5763
5764
                /* Build module Map from the module scope */
5765
81
                LatValue module_map = value_map_new();
5766
81
                Scope *mod_scope = &vm->env->scopes[vm->env->count - 1];
5767
3.21k
                for (size_t mi = 0; mi < mod_scope->cap; mi++) {
5768
3.13k
                    if (mod_scope->entries[mi].state != MAP_OCCUPIED) continue;
5769
1.77k
                    const char *name = mod_scope->entries[mi].key;
5770
1.77k
                    LatValue *val_ptr = (LatValue *)mod_scope->entries[mi].value;
5771
5772
                    /* Copy all module bindings to base scope so that closures
5773
                     * exported from the module can still resolve their globals
5774
                     * (OP_GET_GLOBAL) after the module scope is popped. */
5775
1.77k
                    env_define_at(vm->env, 0, name, value_deep_clone(val_ptr));
5776
5777
                    /* Filter based on export declarations */
5778
1.77k
                    if (!module_should_export(name,
5779
1.77k
                            (const char **)mod_chunk->export_names,
5780
1.77k
                            mod_chunk->export_count, mod_chunk->has_exports))
5781
12
                        continue;
5782
5783
1.76k
                    LatValue exported = value_deep_clone(val_ptr);
5784
1.76k
                    lat_map_set(module_map.as.map.map, name, &exported);
5785
1.76k
                }
5786
5787
81
                env_pop_scope(vm->env);
5788
5789
                /* Cache the module map */
5790
81
                LatValue cache_copy = value_deep_clone(&module_map);
5791
81
                lat_map_set(&vm->module_cache, resolved, &cache_copy);
5792
5793
81
                push(vm, module_map);
5794
81
                break;
5795
81
            }
5796
5797
            /* ── Concurrency ── */
5798
5799
0
#ifdef VM_USE_COMPUTED_GOTO
5800
4
            lbl_OP_SCOPE:
5801
4
#endif
5802
4
            case OP_SCOPE: {
5803
                /* Read all inline data upfront */
5804
4
                uint8_t spawn_count = READ_BYTE();
5805
4
                uint8_t sync_idx = READ_BYTE();
5806
4
                uint8_t spawn_indices[256];
5807
7
                for (uint8_t i = 0; i < spawn_count; i++)
5808
3
                    spawn_indices[i] = READ_BYTE();
5809
5810
#ifdef __EMSCRIPTEN__
5811
                (void)sync_idx;
5812
                push(vm, value_unit());
5813
#else
5814
                /* Export current locals so sub-chunks can see them via env */
5815
4
                env_push_scope(vm->env);
5816
12
                for (size_t fi2 = 0; fi2 < vm->frame_count; fi2++) {
5817
8
                    StackCallFrame *f2 = &vm->frames[fi2];
5818
8
                    if (!f2->chunk) continue;
5819
8
                    size_t lc = (fi2 + 1 < vm->frame_count)
5820
8
                        ? (size_t)(vm->frames[fi2 + 1].slots - f2->slots)
5821
8
                        : (size_t)(vm->stack_top - f2->slots);
5822
14
                    for (size_t sl = 0; sl < lc; sl++) {
5823
6
                        if (sl < f2->chunk->local_name_cap && f2->chunk->local_names[sl])
5824
2
                            env_define(vm->env, f2->chunk->local_names[sl],
5825
2
                                       value_deep_clone(&f2->slots[sl]));
5826
6
                    }
5827
8
                }
5828
5829
4
                if (spawn_count == 0) {
5830
                    /* No spawns — run sync body */
5831
2
                    if (sync_idx != 0xFF) {
5832
2
                        Chunk *body = (Chunk *)frame->chunk->constants[sync_idx].as.closure.native_fn;
5833
2
                        LatValue scope_result;
5834
2
                        StackVMResult sr = stackvm_run(vm, body, &scope_result);
5835
2
                        env_pop_scope(vm->env);
5836
2
                        if (sr != STACKVM_OK) {
5837
0
                            return runtime_error(vm, "%s", vm->error ? vm->error : "scope error");
5838
0
                        }
5839
2
                        push(vm, scope_result);
5840
2
                    } else {
5841
0
                        env_pop_scope(vm->env);
5842
0
                        push(vm, value_unit());
5843
0
                    }
5844
2
                } else {
5845
                    /* Has spawns — run concurrently */
5846
2
                    char *first_error = NULL;
5847
5848
                    /* Run sync body first (non-spawn statements) */
5849
2
                    if (sync_idx != 0xFF) {
5850
0
                        Chunk *sync_body = (Chunk *)frame->chunk->constants[sync_idx].as.closure.native_fn;
5851
0
                        LatValue ns_result;
5852
0
                        StackVMResult nsr = stackvm_run(vm, sync_body, &ns_result);
5853
0
                        if (nsr != STACKVM_OK) {
5854
0
                            first_error = vm->error ? strdup(vm->error) : strdup("scope stmt error");
5855
0
                            free(vm->error);
5856
0
                            vm->error = NULL;
5857
0
                        } else {
5858
0
                            value_free(&ns_result);
5859
0
                        }
5860
0
                    }
5861
5862
                    /* Create child VMs for each spawn */
5863
2
                    VMSpawnTask *tasks = calloc(spawn_count, sizeof(VMSpawnTask));
5864
5
                    for (uint8_t i = 0; i < spawn_count && !first_error; i++) {
5865
3
                        Chunk *sp_chunk = (Chunk *)frame->chunk->constants[spawn_indices[i]].as.closure.native_fn;
5866
3
                        tasks[i].chunk = sp_chunk;
5867
3
                        tasks[i].child_vm = stackvm_clone_for_thread(vm);
5868
3
                        stackvm_export_locals_to_env(vm, tasks[i].child_vm);
5869
3
                        tasks[i].error = NULL;
5870
3
                    }
5871
5872
                    /* Launch all spawn threads */
5873
5
                    for (uint8_t i = 0; i < spawn_count; i++) {
5874
3
                        if (!tasks[i].child_vm) continue;
5875
3
                        pthread_create(&tasks[i].thread, NULL, stackvm_spawn_thread_fn, &tasks[i]);
5876
3
                    }
5877
5878
                    /* Join all threads */
5879
5
                    for (uint8_t i = 0; i < spawn_count; i++) {
5880
3
                        if (!tasks[i].child_vm) continue;
5881
3
                        pthread_join(tasks[i].thread, NULL);
5882
3
                    }
5883
5884
                    /* Restore parent TLS state */
5885
2
                    lat_runtime_set_current(vm->rt);
5886
2
                    vm->rt->active_vm = vm;
5887
5888
                    /* Collect first error from child threads */
5889
5
                    for (uint8_t i = 0; i < spawn_count; i++) {
5890
3
                        if (tasks[i].error && !first_error) {
5891
1
                            first_error = tasks[i].error;
5892
2
                        } else if (tasks[i].error) {
5893
0
                            free(tasks[i].error);
5894
0
                        }
5895
3
                        if (tasks[i].child_vm)
5896
3
                            stackvm_free_child(tasks[i].child_vm);
5897
3
                    }
5898
5899
2
                    env_pop_scope(vm->env);
5900
2
                    free(tasks);
5901
5902
2
                    if (first_error) {
5903
1
                        StackVMResult err = runtime_error(vm, "%s", first_error);
5904
1
                        free(first_error);
5905
1
                        return err;
5906
1
                    }
5907
1
                    push(vm, value_unit());
5908
1
                }
5909
3
#endif
5910
3
                break;
5911
4
            }
5912
5913
3
#ifdef VM_USE_COMPUTED_GOTO
5914
5
            lbl_OP_SELECT:
5915
5
#endif
5916
5
            case OP_SELECT: {
5917
                /* Read all inline arm data upfront */
5918
5
                uint8_t arm_count = READ_BYTE();
5919
5
                typedef struct { uint8_t flags, chan_idx, body_idx, binding_idx; } SelArmInfo;
5920
5
                SelArmInfo *arm_info = malloc(arm_count * sizeof(SelArmInfo));
5921
14
                for (uint8_t i = 0; i < arm_count; i++) {
5922
9
                    arm_info[i].flags = READ_BYTE();
5923
9
                    arm_info[i].chan_idx = READ_BYTE();
5924
9
                    arm_info[i].body_idx = READ_BYTE();
5925
9
                    arm_info[i].binding_idx = READ_BYTE();
5926
9
                }
5927
5928
#ifdef __EMSCRIPTEN__
5929
                free(arm_info);
5930
                push(vm, value_nil());
5931
#else
5932
                /* Find default and timeout arms */
5933
5
                int default_arm = -1;
5934
5
                int timeout_arm = -1;
5935
14
                for (uint8_t i = 0; i < arm_count; i++) {
5936
9
                    if (arm_info[i].flags & 0x01) default_arm = (int)i;
5937
9
                    if (arm_info[i].flags & 0x02) timeout_arm = (int)i;
5938
9
                }
5939
5940
                /* Export locals to env for sub-chunk visibility */
5941
5
                env_push_scope(vm->env);
5942
15
                for (size_t fi2 = 0; fi2 < vm->frame_count; fi2++) {
5943
10
                    StackCallFrame *f2 = &vm->frames[fi2];
5944
10
                    if (!f2->chunk) continue;
5945
10
                    size_t lc = (fi2 + 1 < vm->frame_count)
5946
10
                        ? (size_t)(vm->frames[fi2 + 1].slots - f2->slots)
5947
10
                        : (size_t)(vm->stack_top - f2->slots);
5948
21
                    for (size_t sl = 0; sl < lc; sl++) {
5949
11
                        if (sl < f2->chunk->local_name_cap && f2->chunk->local_names[sl])
5950
6
                            env_define(vm->env, f2->chunk->local_names[sl],
5951
6
                                       value_deep_clone(&f2->slots[sl]));
5952
11
                    }
5953
10
                }
5954
5955
                /* Evaluate all channel expressions upfront */
5956
5
                LatChannel **channels = calloc(arm_count, sizeof(LatChannel *));
5957
14
                for (uint8_t i = 0; i < arm_count; i++) {
5958
9
                    if (arm_info[i].flags & 0x03) continue; /* skip default/timeout */
5959
6
                    Chunk *ch_chunk = (Chunk *)frame->chunk->constants[arm_info[i].chan_idx].as.closure.native_fn;
5960
6
                    LatValue ch_val;
5961
6
                    StackVMResult cr = stackvm_run(vm, ch_chunk, &ch_val);
5962
6
                    if (cr != STACKVM_OK) {
5963
0
                        env_pop_scope(vm->env);
5964
0
                        for (uint8_t j = 0; j < i; j++)
5965
0
                            if (channels[j]) channel_release(channels[j]);
5966
0
                        free(channels);
5967
0
                        free(arm_info);
5968
0
                        return runtime_error(vm, "%s", vm->error ? vm->error : "select channel error");
5969
0
                    }
5970
6
                    if (ch_val.type != VAL_CHANNEL) {
5971
0
                        value_free(&ch_val);
5972
0
                        env_pop_scope(vm->env);
5973
0
                        for (uint8_t j = 0; j < i; j++)
5974
0
                            if (channels[j]) channel_release(channels[j]);
5975
0
                        free(channels);
5976
0
                        free(arm_info);
5977
0
                        return runtime_error(vm, "select arm: expression is not a Channel");
5978
0
                    }
5979
6
                    channels[i] = ch_val.as.channel.ch;
5980
6
                    channel_retain(channels[i]);
5981
6
                    value_free(&ch_val);
5982
6
                }
5983
5984
                /* Evaluate timeout if present */
5985
5
                long timeout_ms = -1;
5986
5
                if (timeout_arm >= 0) {
5987
0
                    Chunk *to_chunk = (Chunk *)frame->chunk->constants[arm_info[timeout_arm].chan_idx].as.closure.native_fn;
5988
0
                    LatValue to_val;
5989
0
                    StackVMResult tr = stackvm_run(vm, to_chunk, &to_val);
5990
0
                    if (tr != STACKVM_OK) {
5991
0
                        env_pop_scope(vm->env);
5992
0
                        for (uint8_t i = 0; i < arm_count; i++)
5993
0
                            if (channels[i]) channel_release(channels[i]);
5994
0
                        free(channels);
5995
0
                        free(arm_info);
5996
0
                        return runtime_error(vm, "%s", vm->error ? vm->error : "select timeout error");
5997
0
                    }
5998
0
                    if (to_val.type != VAL_INT) {
5999
0
                        value_free(&to_val);
6000
0
                        env_pop_scope(vm->env);
6001
0
                        for (uint8_t i = 0; i < arm_count; i++)
6002
0
                            if (channels[i]) channel_release(channels[i]);
6003
0
                        free(channels);
6004
0
                        free(arm_info);
6005
0
                        return runtime_error(vm, "select timeout must be an integer (milliseconds)");
6006
0
                    }
6007
0
                    timeout_ms = (long)to_val.as.int_val;
6008
0
                    value_free(&to_val);
6009
0
                }
6010
6011
                /* Build shuffled index array for fairness */
6012
5
                size_t ch_arm_count = 0;
6013
5
                size_t *indices = malloc(arm_count * sizeof(size_t));
6014
14
                for (uint8_t i = 0; i < arm_count; i++) {
6015
9
                    if (!(arm_info[i].flags & 0x03))
6016
6
                        indices[ch_arm_count++] = i;
6017
9
                }
6018
                /* Fisher-Yates shuffle */
6019
6
                for (size_t i = ch_arm_count; i > 1; i--) {
6020
1
                    size_t j = (size_t)rand() % i;
6021
1
                    size_t tmp = indices[i-1];
6022
1
                    indices[i-1] = indices[j];
6023
1
                    indices[j] = tmp;
6024
1
                }
6025
6026
                /* Set up waiter for blocking */
6027
5
                pthread_mutex_t sel_mutex = PTHREAD_MUTEX_INITIALIZER;
6028
5
                pthread_cond_t  sel_cond  = PTHREAD_COND_INITIALIZER;
6029
5
                LatSelectWaiter waiter = {
6030
5
                    .mutex = &sel_mutex,
6031
5
                    .cond  = &sel_cond,
6032
5
                    .next  = NULL,
6033
5
                };
6034
6035
5
                LatValue select_result = value_unit();
6036
5
                bool select_found = false;
6037
5
                bool select_error = false;
6038
6039
                /* Compute deadline for timeout */
6040
5
                struct timespec deadline;
6041
5
                if (timeout_ms >= 0) {
6042
0
                    clock_gettime(CLOCK_REALTIME, &deadline);
6043
0
                    deadline.tv_sec  += timeout_ms / 1000;
6044
0
                    deadline.tv_nsec += (timeout_ms % 1000) * 1000000L;
6045
0
                    if (deadline.tv_nsec >= 1000000000L) {
6046
0
                        deadline.tv_sec++;
6047
0
                        deadline.tv_nsec -= 1000000000L;
6048
0
                    }
6049
0
                }
6050
6051
5
                for (;;) {
6052
                    /* Try non-blocking recv on each channel arm (shuffled order) */
6053
5
                    bool all_closed = true;
6054
9
                    for (size_t k = 0; k < ch_arm_count; k++) {
6055
6
                        size_t i = indices[k];
6056
6
                        LatChannel *ch = channels[i];
6057
6
                        LatValue recv_val;
6058
6
                        bool closed = false;
6059
6
                        if (channel_try_recv(ch, &recv_val, &closed)) {
6060
                            /* Got a value — bind in env, run body */
6061
2
                            env_push_scope(vm->env);
6062
2
                            const char *binding = (arm_info[i].flags & 0x04)
6063
2
                                ? frame->chunk->constants[arm_info[i].binding_idx].as.str_val
6064
2
                                : NULL;
6065
2
                            if (binding)
6066
2
                                env_define(vm->env, binding, recv_val);
6067
0
                            else
6068
0
                                value_free(&recv_val);
6069
6070
2
                            Chunk *arm_chunk = (Chunk *)frame->chunk->constants[arm_info[i].body_idx].as.closure.native_fn;
6071
2
                            LatValue arm_result;
6072
2
                            StackVMResult ar = stackvm_run(vm, arm_chunk, &arm_result);
6073
2
                            env_pop_scope(vm->env);
6074
2
                            if (ar != STACKVM_OK) {
6075
0
                                select_error = true;
6076
2
                            } else {
6077
2
                                value_free(&select_result);
6078
2
                                select_result = arm_result;
6079
2
                            }
6080
2
                            select_found = true;
6081
2
                            break;
6082
2
                        }
6083
4
                        if (!closed) all_closed = false;
6084
4
                    }
6085
5
                    if (select_found || select_error) break;
6086
6087
3
                    if (all_closed && ch_arm_count > 0) {
6088
                        /* All channels closed — execute default if present */
6089
2
                        if (default_arm >= 0) {
6090
1
                            env_push_scope(vm->env);
6091
1
                            Chunk *def_chunk = (Chunk *)frame->chunk->constants[arm_info[default_arm].body_idx].as.closure.native_fn;
6092
1
                            LatValue def_result;
6093
1
                            StackVMResult dr = stackvm_run(vm, def_chunk, &def_result);
6094
1
                            if (dr == STACKVM_OK) {
6095
1
                                value_free(&select_result);
6096
1
                                select_result = def_result;
6097
1
                            } else {
6098
0
                                select_error = true;
6099
0
                            }
6100
1
                            env_pop_scope(vm->env);
6101
1
                        }
6102
2
                        break;
6103
2
                    }
6104
6105
                    /* If there's a default arm, execute it immediately */
6106
1
                    if (default_arm >= 0) {
6107
1
                        env_push_scope(vm->env);
6108
1
                        Chunk *def_chunk = (Chunk *)frame->chunk->constants[arm_info[default_arm].body_idx].as.closure.native_fn;
6109
1
                        LatValue def_result;
6110
1
                        StackVMResult dr = stackvm_run(vm, def_chunk, &def_result);
6111
1
                        if (dr == STACKVM_OK) {
6112
1
                            value_free(&select_result);
6113
1
                            select_result = def_result;
6114
1
                        } else {
6115
0
                            select_error = true;
6116
0
                        }
6117
1
                        env_pop_scope(vm->env);
6118
1
                        break;
6119
1
                    }
6120
6121
                    /* Block: register waiter on all channels, then wait */
6122
0
                    for (size_t k = 0; k < ch_arm_count; k++)
6123
0
                        channel_add_waiter(channels[indices[k]], &waiter);
6124
6125
0
                    pthread_mutex_lock(&sel_mutex);
6126
0
                    if (timeout_ms >= 0) {
6127
0
                        int rc = pthread_cond_timedwait(&sel_cond, &sel_mutex, &deadline);
6128
0
                        if (rc != 0) {
6129
                            /* Timeout expired */
6130
0
                            pthread_mutex_unlock(&sel_mutex);
6131
0
                            for (size_t k = 0; k < ch_arm_count; k++)
6132
0
                                channel_remove_waiter(channels[indices[k]], &waiter);
6133
0
                            if (timeout_arm >= 0) {
6134
0
                                env_push_scope(vm->env);
6135
0
                                Chunk *to_body = (Chunk *)frame->chunk->constants[arm_info[timeout_arm].body_idx].as.closure.native_fn;
6136
0
                                LatValue to_result;
6137
0
                                StackVMResult tor = stackvm_run(vm, to_body, &to_result);
6138
0
                                if (tor == STACKVM_OK) {
6139
0
                                    value_free(&select_result);
6140
0
                                    select_result = to_result;
6141
0
                                } else {
6142
0
                                    select_error = true;
6143
0
                                }
6144
0
                                env_pop_scope(vm->env);
6145
0
                            }
6146
0
                            break;
6147
0
                        }
6148
0
                    } else {
6149
0
                        pthread_cond_wait(&sel_cond, &sel_mutex);
6150
0
                    }
6151
0
                    pthread_mutex_unlock(&sel_mutex);
6152
6153
                    /* Remove waiters and retry */
6154
0
                    for (size_t k = 0; k < ch_arm_count; k++)
6155
0
                        channel_remove_waiter(channels[indices[k]], &waiter);
6156
0
                }
6157
6158
5
                pthread_mutex_destroy(&sel_mutex);
6159
5
                pthread_cond_destroy(&sel_cond);
6160
5
                free(indices);
6161
14
                for (uint8_t i = 0; i < arm_count; i++)
6162
9
                    if (channels[i]) channel_release(channels[i]);
6163
5
                free(channels);
6164
5
                env_pop_scope(vm->env);
6165
6166
5
                if (select_error) {
6167
0
                    value_free(&select_result);
6168
0
                    char *err_msg = vm->error ? strdup(vm->error) : strdup("select error");
6169
0
                    free(vm->error);
6170
0
                    vm->error = NULL;
6171
0
                    free(arm_info);
6172
0
                    StackVMResult err = runtime_error(vm, "%s", err_msg);
6173
0
                    free(err_msg);
6174
0
                    return err;
6175
0
                }
6176
5
                free(arm_info);
6177
5
                push(vm, select_result);
6178
5
#endif
6179
5
                break;
6180
5
            }
6181
6182
0
#ifdef VM_USE_COMPUTED_GOTO
6183
3.30k
            lbl_OP_LOAD_INT8:
6184
3.30k
#endif
6185
3.30k
            case OP_LOAD_INT8: {
6186
3.30k
                int8_t val = (int8_t)READ_BYTE();
6187
3.30k
                push(vm, value_int((int64_t)val));
6188
3.30k
                break;
6189
3.30k
            }
6190
6191
0
#ifdef VM_USE_COMPUTED_GOTO
6192
221
            lbl_OP_INC_LOCAL:
6193
221
#endif
6194
221
            case OP_INC_LOCAL: {
6195
221
                uint8_t slot = READ_BYTE();
6196
221
                LatValue *lv = &frame->slots[slot];
6197
221
                if (lv->type == VAL_INT) {
6198
221
                    lv->as.int_val++;
6199
221
                } else {
6200
0
                    VM_ERROR("OP_INC_LOCAL: expected Int");
6201
0
                }
6202
221
                break;
6203
221
            }
6204
6205
221
#ifdef VM_USE_COMPUTED_GOTO
6206
221
            lbl_OP_DEC_LOCAL:
6207
3
#endif
6208
3
            case OP_DEC_LOCAL: {
6209
3
                uint8_t slot = READ_BYTE();
6210
3
                LatValue *lv = &frame->slots[slot];
6211
3
                if (lv->type == VAL_INT) {
6212
3
                    lv->as.int_val--;
6213
3
                } else {
6214
0
                    VM_ERROR("OP_DEC_LOCAL: expected Int");
6215
0
                }
6216
3
                break;
6217
3
            }
6218
6219
3
#ifdef VM_USE_COMPUTED_GOTO
6220
3
            lbl_OP_ADD_INT:
6221
0
#endif
6222
0
            case OP_ADD_INT: {
6223
0
                vm->stack_top--;
6224
0
                vm->stack_top[-1].as.int_val += vm->stack_top[0].as.int_val;
6225
0
                break;
6226
0
            }
6227
6228
0
#ifdef VM_USE_COMPUTED_GOTO
6229
0
            lbl_OP_SUB_INT:
6230
0
#endif
6231
0
            case OP_SUB_INT: {
6232
0
                vm->stack_top--;
6233
0
                vm->stack_top[-1].as.int_val -= vm->stack_top[0].as.int_val;
6234
0
                break;
6235
0
            }
6236
6237
0
#ifdef VM_USE_COMPUTED_GOTO
6238
0
            lbl_OP_MUL_INT:
6239
0
#endif
6240
0
            case OP_MUL_INT: {
6241
0
                vm->stack_top--;
6242
0
                vm->stack_top[-1].as.int_val *= vm->stack_top[0].as.int_val;
6243
0
                break;
6244
0
            }
6245
6246
0
#ifdef VM_USE_COMPUTED_GOTO
6247
0
            lbl_OP_LT_INT:
6248
0
#endif
6249
0
            case OP_LT_INT: {
6250
0
                vm->stack_top--;
6251
0
                int64_t a = vm->stack_top[-1].as.int_val;
6252
0
                vm->stack_top[-1].type = VAL_BOOL;
6253
0
                vm->stack_top[-1].as.bool_val = a < vm->stack_top[0].as.int_val;
6254
0
                break;
6255
0
            }
6256
6257
0
#ifdef VM_USE_COMPUTED_GOTO
6258
0
            lbl_OP_LTEQ_INT:
6259
0
#endif
6260
0
            case OP_LTEQ_INT: {
6261
0
                vm->stack_top--;
6262
0
                int64_t a = vm->stack_top[-1].as.int_val;
6263
0
                vm->stack_top[-1].type = VAL_BOOL;
6264
0
                vm->stack_top[-1].as.bool_val = a <= vm->stack_top[0].as.int_val;
6265
0
                break;
6266
0
            }
6267
6268
0
#ifdef VM_USE_COMPUTED_GOTO
6269
6.47k
            lbl_OP_RESET_EPHEMERAL:
6270
6.47k
#endif
6271
6.47k
            case OP_RESET_EPHEMERAL: {
6272
                /* Promote all ephemeral values on the entire stack before
6273
                 * resetting.  This covers local bindings (which stay on the
6274
                 * stack via add_local without an explicit clone) as well as
6275
                 * expression temporaries in parent frames that could be
6276
                 * invalidated when a callee resets the shared arena. */
6277
6.47k
                if (vm->ephemeral_on_stack) {
6278
3.16k
                    for (LatValue *slot = vm->stack; slot < vm->stack_top; slot++) {
6279
2.90k
                        stackvm_promote_value(slot);
6280
2.90k
                    }
6281
255
                    vm->ephemeral_on_stack = false;
6282
255
                }
6283
6.47k
                bump_arena_reset(vm->ephemeral);
6284
6.47k
                break;
6285
6.47k
            }
6286
6287
            /* ── Runtime type checking ── */
6288
0
#ifdef VM_USE_COMPUTED_GOTO
6289
376
            lbl_OP_CHECK_TYPE:
6290
376
#endif
6291
376
            case OP_CHECK_TYPE: {
6292
376
                uint8_t slot = READ_BYTE();
6293
376
                uint8_t type_idx = READ_BYTE();
6294
376
                uint8_t err_idx = READ_BYTE();
6295
376
                LatValue *val = &frame->slots[slot];
6296
376
                const char *type_name = frame->chunk->constants[type_idx].as.str_val;
6297
376
                if (!stackvm_type_matches(val, type_name)) {
6298
2
                    const char *err_fmt = frame->chunk->constants[err_idx].as.str_val;
6299
2
                    const char *actual = stackvm_value_type_display(val);
6300
                    /* err_fmt has a %s placeholder for the actual type */
6301
2
                    VM_ERROR(err_fmt, actual);
6302
2
                }
6303
374
                break;
6304
376
            }
6305
6306
374
#ifdef VM_USE_COMPUTED_GOTO
6307
524
            lbl_OP_CHECK_RETURN_TYPE:
6308
524
#endif
6309
524
            case OP_CHECK_RETURN_TYPE: {
6310
524
                uint8_t type_idx = READ_BYTE();
6311
524
                uint8_t err_idx = READ_BYTE();
6312
524
                LatValue *val = stackvm_peek(vm, 0);
6313
524
                const char *type_name = frame->chunk->constants[type_idx].as.str_val;
6314
524
                if (!stackvm_type_matches(val, type_name)) {
6315
2
                    const char *err_fmt = frame->chunk->constants[err_idx].as.str_val;
6316
2
                    const char *actual = stackvm_value_type_display(val);
6317
2
                    VM_ERROR(err_fmt, actual);
6318
2
                }
6319
522
                break;
6320
524
            }
6321
6322
522
#ifdef VM_USE_COMPUTED_GOTO
6323
522
            lbl_OP_HALT:
6324
0
#endif
6325
0
            case OP_HALT:
6326
0
                *result = value_unit();
6327
0
                return STACKVM_OK;
6328
6329
0
            default:
6330
0
                VM_ERROR("unknown opcode %d", op); break;
6331
70.7k
        }
6332
70.7k
    }
6333
1.27k
}
6334
6335
#undef READ_BYTE
6336
#undef READ_U16