/*
 * vm.c — Bytecode compiler + stack-based VM
 */
#include "lumbda.h"

/* ═══════════════════════════════════════════════════════════════════════════
 * CodeObj
 * ═══════════════════════════════════════════════════════════════════════════ */

CodeObj *make_code(const char *name) {
    CodeObj *c = (CodeObj *)ul_malloc(sizeof(CodeObj));
    c->hdr.type = OBJ_CODE;
    c->cap = 64;
    c->count = 0;
    /* Instruction carries a Value `arg` — buffer needs precise tracing. */
    c->instrs = (Instruction *)ul_malloc_values(sizeof(Instruction) * c->cap);
    c->source_map = (int *)ul_malloc(sizeof(int) * c->cap);
    c->name = name ? ul_strdup(name) : NULL;
    c->self_name = NULL;
    c->self_params = NULL;
    c->self_nparams = 0;
    return c;
}

int code_emit(CodeObj *c, Opcode op, Value arg) {
    if (c->count >= c->cap) {
        c->cap *= 2;
        c->instrs = (Instruction *)ul_realloc_values(c->instrs, sizeof(Instruction) * c->cap);
        c->source_map = (int *)ul_realloc(c->source_map, sizeof(int) * c->cap);
    }
    int idx = c->count;
    c->instrs[idx].op = op;
    c->instrs[idx].arg = arg;
    c->instrs[idx].arg2 = 0;
    c->source_map[idx] = 0;
    c->count++;
    return idx;
}

void code_emit2(CodeObj *c, Opcode op, Value arg, int arg2) {
    int idx = code_emit(c, op, arg);
    c->instrs[idx].arg2 = arg2;
}

void code_patch(CodeObj *c, int addr, Value arg) {
    c->instrs[addr].arg = arg;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * Bytecode compiler
 * ═══════════════════════════════════════════════════════════════════════════ */

static bool bc_is_global(Value sym, Env *env) {
    Env *e = env;
    Env *g = e->global;
    while (e && e != g) {
        /* Check if sym is locally bound */
        EnvBinding *b = NULL;
        uint32_t h = (uint32_t)((GET_PAYLOAD(sym) * 2654435761ULL) % e->nbuckets);
        b = e->buckets[h];
        while (b) { if (b->sym == sym) return false; b = b->next; }
        e = e->parent;
    }
    return true;
}

/* Fallback forms — these get compiled to OP_EVAL */
static bool is_fallback_form(Value head) {
    return head == SYM_QUASIQUOTE || head == SYM_DEFINE_MACRO || head == SYM_DEFMACRO ||
           head == SYM_DEFINE_SYNTAX || head == SYM_LET_SYNTAX || head == SYM_LETREC_SYNTAX ||
           head == SYM_SYNTAX_RULES || head == SYM_DEFINE_VALUES || head == SYM_LET_VALUES ||
           head == SYM_LET_STAR_VALUES || head == SYM_DEFINE_RECORD_TYPE ||
           head == SYM_MODULE || head == SYM_IMPORT || head == SYM_INCLUDE || head == SYM_LOAD ||
           head == SYM_PARAMETERIZE || head == SYM_DYNAMIC_WIND ||
           head == SYM_WITH_EXCEPTION_HANDLER || head == SYM_GUARD ||
           head == SYM_CALL_WITH_VALUES || head == SYM_VALUES || head == SYM_EVAL ||
           head == SYM_ERROR || head == SYM_CASE;
}

static void bc_body(Value *body, int nbody, CodeObj *code, Env *env, bool tail) {
    if (nbody == 0) { code_emit(code, OP_VOID, VAL_NIL); return; }
    for (int i = 0; i < nbody - 1; i++) {
        bc_compile(body[i], code, env, false);
        code_emit(code, OP_POP, VAL_NIL);
    }
    bc_compile(body[nbody - 1], code, env, tail);
}

CodeObj *bc_lambda(Value *body, int nbody, Value *params, int nparams,
                   Value rest, Env *env, const char *name,
                   const char *self_name, Value *self_params, int self_nparams) {
    CodeObj *inner = make_code(name);
    if (self_name) {
        inner->self_name = ul_strdup(self_name);
        inner->self_params = (Value *)ul_malloc_values(sizeof(Value) * self_nparams);
        memcpy(inner->self_params, self_params, sizeof(Value) * self_nparams);
        inner->self_nparams = self_nparams;
    }

    /* Handle internal defines */
    int i = 0;
    Value *def_names = NULL;
    int ndef = 0;
    Value *expanded = (Value *)ul_malloc_values(sizeof(Value) * (nbody + 64));
    memcpy(expanded, body, sizeof(Value) * nbody);
    int n = nbody;

    while (i < n) {
        Value f = expanded[i];
        if (IS_PAIR(f) && CAR(f) == SYM_DEFINE) {
            Value *a; int na = value_to_list(CDR(f), &a);
            Value nm = IS_PAIR(a[0]) ? CAR(a[0]) : a[0];
            if (IS_SYM(nm)) {
                def_names = (Value *)ul_realloc_values(def_names, sizeof(Value) * (ndef + 1));
                def_names[ndef++] = nm;
            }
            ul_free(a);
            i++;
        } else if (IS_PAIR(f) && CAR(f) == SYM_BEGIN) {
            Value *spliced; int ns = value_to_list(CDR(f), &spliced);
            memmove(expanded + i + ns, expanded + i + 1, sizeof(Value) * (n - i - 1));
            memcpy(expanded + i, spliced, sizeof(Value) * ns);
            n = n + ns - 1;
            ul_free(spliced);
        } else {
            break;
        }
    }

    if (ndef > 0) {
        code_emit(inner, OP_PUSH_ENV, VAL_NIL);
        for (int j = 0; j < ndef; j++) {
            code_emit(inner, OP_VOID, VAL_NIL);
            code_emit(inner, OP_BIND, def_names[j]);
        }
    }

    bc_body(expanded, n, inner, env, true);
    code_emit(inner, OP_RETURN, VAL_NIL);

    ul_free(expanded);
    ul_free(def_names);
    return inner;
}

void bc_compile(Value expr, CodeObj *code, Env *env, bool tail) {
    /* Self-evaluating */
    if (IS_VOID(expr)) { code_emit(code, OP_VOID, VAL_NIL); return; }
    if (IS_NIL(expr) || IS_TRUE(expr) || IS_FALSE(expr) || IS_EOF(expr) || IS_CHAR(expr)) {
        code_emit(code, OP_CONST, expr); return;
    }
    if (IS_INT(expr) || IS_DOUBLE(expr) || IS_RATIONAL(expr) || IS_BIGNUM(expr)) {
        code_emit(code, OP_CONST, expr); return;
    }
    if (IS_STRING(expr) || IS_VECTOR(expr)) {
        code_emit(code, OP_CONST, expr); return;
    }
    if (IS_SYM(expr)) { code_emit(code, OP_LOOKUP, expr); return; }
    if (!IS_PAIR(expr)) { code_emit(code, OP_CONST, expr); return; }

    Value head = CAR(expr);
    Value args = CDR(expr);

    /* Fallback forms */
    if (IS_SYM(head) && is_fallback_form(head)) {
        code_emit(code, OP_EVAL, expr); return;
    }

    /* quote */
    if (head == SYM_QUOTE) { code_emit(code, OP_CONST, CADR(expr)); return; }

    /* if */
    if (head == SYM_IF) {
        Value *a; int na = value_to_list(args, &a);
        bc_compile(a[0], code, env, false);
        int jf = code_emit(code, OP_JUMP_IF_FALSE, VAL_NIL);
        bc_compile(a[1], code, env, tail);
        int je = code_emit(code, OP_JUMP, VAL_NIL);
        code_patch(code, jf, VAL_INT(code->count));
        if (na > 2) bc_compile(a[2], code, env, tail);
        else code_emit(code, OP_VOID, VAL_NIL);
        code_patch(code, je, VAL_INT(code->count));
        ul_free(a); return;
    }

    /* begin */
    if (head == SYM_BEGIN) {
        Value *a; int na = value_to_list(args, &a);
        if (na == 0) { code_emit(code, OP_VOID, VAL_NIL); ul_free(a); return; }
        for (int i = 0; i < na - 1; i++) { bc_compile(a[i], code, env, false); code_emit(code, OP_POP, VAL_NIL); }
        bc_compile(a[na-1], code, env, tail);
        ul_free(a); return;
    }

    /* and */
    if (head == SYM_AND) {
        Value *a; int na = value_to_list(args, &a);
        if (na == 0) { code_emit(code, OP_CONST, VAL_TRUE); ul_free(a); return; }
        if (na == 1) { bc_compile(a[0], code, env, tail); ul_free(a); return; }
        int *ends = (int *)ul_malloc(sizeof(int) * na);
        int nends = 0;
        for (int i = 0; i < na - 1; i++) {
            bc_compile(a[i], code, env, false);
            ends[nends++] = code_emit(code, OP_JUMP_IF_FALSE_KEEP, VAL_NIL);
        }
        bc_compile(a[na-1], code, env, tail);
        int end = code->count;
        for (int i = 0; i < nends; i++) code_patch(code, ends[i], VAL_INT(end));
        ul_free(ends); ul_free(a); return;
    }

    /* or */
    if (head == SYM_OR) {
        Value *a; int na = value_to_list(args, &a);
        if (na == 0) { code_emit(code, OP_CONST, VAL_FALSE); ul_free(a); return; }
        if (na == 1) { bc_compile(a[0], code, env, tail); ul_free(a); return; }
        int *ends = (int *)ul_malloc(sizeof(int) * na);
        int nends = 0;
        for (int i = 0; i < na - 1; i++) {
            bc_compile(a[i], code, env, false);
            ends[nends++] = code_emit(code, OP_JUMP_IF_TRUE_KEEP, VAL_NIL);
        }
        bc_compile(a[na-1], code, env, tail);
        int end = code->count;
        for (int i = 0; i < nends; i++) code_patch(code, ends[i], VAL_INT(end));
        ul_free(ends); ul_free(a); return;
    }

    /* when */
    if (head == SYM_WHEN) {
        Value *a; int na = value_to_list(args, &a);
        bc_compile(a[0], code, env, false);
        int jf = code_emit(code, OP_JUMP_IF_FALSE, VAL_NIL);
        for (int i = 1; i < na - 1; i++) { bc_compile(a[i], code, env, false); code_emit(code, OP_POP, VAL_NIL); }
        bc_compile(a[na-1], code, env, tail);
        int je = code_emit(code, OP_JUMP, VAL_NIL);
        code_patch(code, jf, VAL_INT(code->count));
        code_emit(code, OP_VOID, VAL_NIL);
        code_patch(code, je, VAL_INT(code->count));
        ul_free(a); return;
    }

    /* unless */
    if (head == SYM_UNLESS) {
        Value *a; int na = value_to_list(args, &a);
        bc_compile(a[0], code, env, false);
        int jf = code_emit(code, OP_JUMP_IF_FALSE, VAL_NIL);
        code_emit(code, OP_VOID, VAL_NIL);
        int je = code_emit(code, OP_JUMP, VAL_NIL);
        code_patch(code, jf, VAL_INT(code->count));
        for (int i = 1; i < na - 1; i++) { bc_compile(a[i], code, env, false); code_emit(code, OP_POP, VAL_NIL); }
        bc_compile(a[na-1], code, env, tail);
        code_patch(code, je, VAL_INT(code->count));
        ul_free(a); return;
    }

    /* cond */
    if (head == SYM_COND) {
        Value *clauses; int nc = value_to_list(args, &clauses);
        int *ends = (int *)ul_malloc(sizeof(int) * nc);
        int nends = 0;
        for (int i = 0; i < nc; i++) {
            Value *cl; int ncl = value_to_list(clauses[i], &cl);
            if (cl[0] == SYM_ELSE) {
                for (int j = 1; j < ncl - 1; j++) { bc_compile(cl[j], code, env, false); code_emit(code, OP_POP, VAL_NIL); }
                bc_compile(ncl > 1 ? cl[ncl-1] : VAL_VOID, code, env, tail);
                ul_free(cl); break;
            }
            if ((ncl >= 3 && cl[1] == SYM_ARROW) || ncl == 1) {
                /* Fallback for => and bare test */
                code_emit(code, OP_EVAL, expr);
                ul_free(cl); ul_free(ends); ul_free(clauses); return;
            }
            bc_compile(cl[0], code, env, false);
            int jf = code_emit(code, OP_JUMP_IF_FALSE, VAL_NIL);
            for (int j = 1; j < ncl - 1; j++) { bc_compile(cl[j], code, env, false); code_emit(code, OP_POP, VAL_NIL); }
            bc_compile(cl[ncl-1], code, env, tail);
            ends[nends++] = code_emit(code, OP_JUMP, VAL_NIL);
            code_patch(code, jf, VAL_INT(code->count));
            ul_free(cl);
            if (i == nc - 1) code_emit(code, OP_VOID, VAL_NIL);
        }
        int end = code->count;
        for (int i = 0; i < nends; i++) code_patch(code, ends[i], VAL_INT(end));
        ul_free(ends); ul_free(clauses); return;
    }

    /* define */
    if (head == SYM_DEFINE) {
        Value *a; int na = value_to_list(args, &a);
        if (IS_PAIR(a[0])) {
            Value fname = CAR(a[0]);
            Formals f = parse_formals(CDR(a[0]));
            CodeObj *inner = bc_lambda(a + 1, na - 1, f.params, f.nparams, f.rest, env,
                                       sym_name(fname), NULL, NULL, 0);
            /* Emit closure + define */
            /* Pack closure info as a vector: [inner_code, nparams, rest] */
            Value closure_info = make_vector(3, VAL_NIL);
            AS_VECTOR(closure_info)->data[0] = VAL_PTR(inner);
            AS_VECTOR(closure_info)->data[1] = VAL_INT(f.nparams);
            AS_VECTOR(closure_info)->data[2] = f.rest;
            /* Store params in the vector too */
            Value params_vec = make_vector_from(f.params, f.nparams);
            AS_VECTOR(closure_info)->data = (Value *)ul_realloc_values(AS_VECTOR(closure_info)->data, sizeof(Value) * 4);
            AS_VECTOR(closure_info)->len = 4;
            AS_VECTOR(closure_info)->cap = 4;
            AS_VECTOR(closure_info)->data[3] = params_vec;

            code_emit(code, OP_MAKE_CLOSURE, closure_info);
            code_emit(code, OP_DEFINE, fname);
        } else {
            if (na > 1) bc_compile(a[1], code, env, false);
            else code_emit(code, OP_VOID, VAL_NIL);
            code_emit(code, OP_DEFINE, a[0]);
        }
        code_emit(code, OP_VOID, VAL_NIL);
        ul_free(a); return;
    }

    /* set! */
    if (head == SYM_SET) {
        Value *a; int na = value_to_list(args, &a);
        bc_compile(a[1], code, env, false);
        code_emit(code, OP_SET, a[0]);
        code_emit(code, OP_VOID, VAL_NIL);
        ul_free(a); return;
    }

    /* lambda */
    if (head == SYM_LAMBDA || head == SYM_LAMBDA_UC) {
        Value *a; int na = value_to_list(args, &a);
        Formals f = parse_formals(a[0]);
        CodeObj *inner = bc_lambda(a + 1, na - 1, f.params, f.nparams, f.rest, env,
                                   NULL, NULL, NULL, 0);
        Value closure_info = make_vector(4, VAL_NIL);
        AS_VECTOR(closure_info)->data[0] = VAL_PTR(inner);
        AS_VECTOR(closure_info)->data[1] = VAL_INT(f.nparams);
        AS_VECTOR(closure_info)->data[2] = f.rest;
        AS_VECTOR(closure_info)->data[3] = make_vector_from(f.params, f.nparams);
        code_emit(code, OP_MAKE_CLOSURE, closure_info);
        ul_free(a); return;
    }

    /* let */
    if (head == SYM_LET) {
        Value *a; int na = value_to_list(args, &a);
        if (IS_SYM(a[0])) {
            /* Named let */
            Value name = a[0];
            Value *binds; int nb = value_to_list(a[1], &binds);
            Value *bps = (Value *)ul_malloc_values(sizeof(Value) * nb);
            for (int i = 0; i < nb; i++) {
                Value *bp; int nbp = value_to_list(binds[i], &bp);
                bps[i] = bp[0];
                ul_free(bp);
            }
            CodeObj *inner = bc_lambda(a + 2, na - 2, bps, nb, VAL_NIL, env,
                                       sym_name(name), sym_name(name), bps, nb);
            code_emit(code, OP_PUSH_ENV, VAL_NIL);
            Value closure_info = make_vector(4, VAL_NIL);
            AS_VECTOR(closure_info)->data[0] = VAL_PTR(inner);
            AS_VECTOR(closure_info)->data[1] = VAL_INT(nb);
            AS_VECTOR(closure_info)->data[2] = VAL_NIL;
            AS_VECTOR(closure_info)->data[3] = make_vector_from(bps, nb);
            code_emit(code, OP_MAKE_CLOSURE, closure_info);
            code_emit(code, OP_DUP, VAL_NIL);
            code_emit(code, OP_BIND, name);
            for (int i = 0; i < nb; i++) {
                Value *bp; int nbp = value_to_list(binds[i], &bp);
                bc_compile(bp[1], code, env, false);
                ul_free(bp);
            }
            code_emit(code, tail ? OP_TAIL_CALL : OP_CALL, VAL_INT(nb));
            if (!tail) code_emit(code, OP_POP_ENV, VAL_NIL);
            ul_free(bps); ul_free(binds); ul_free(a); return;
        }
        /* Regular let */
        Value *binds; int nb = value_to_list(a[0], &binds);
        for (int i = 0; i < nb; i++) {
            Value *bp; int nbp = value_to_list(binds[i], &bp);
            bc_compile(bp[1], code, env, false);
            ul_free(bp);
        }
        code_emit(code, OP_PUSH_ENV, VAL_NIL);
        for (int i = nb - 1; i >= 0; i--) {
            Value *bp; int nbp = value_to_list(binds[i], &bp);
            code_emit(code, OP_BIND, bp[0]);
            ul_free(bp);
        }
        bc_body(a + 1, na - 1, code, env, tail);
        if (!tail) code_emit(code, OP_POP_ENV, VAL_NIL);
        ul_free(binds); ul_free(a); return;
    }

    /* let* */
    if (head == SYM_LET_STAR) {
        Value *a; int na = value_to_list(args, &a);
        Value *binds; int nb = value_to_list(a[0], &binds);
        code_emit(code, OP_PUSH_ENV, VAL_NIL);
        for (int i = 0; i < nb; i++) {
            Value *bp; int nbp = value_to_list(binds[i], &bp);
            bc_compile(bp[1], code, env, false);
            code_emit(code, OP_BIND, bp[0]);
            ul_free(bp);
        }
        bc_body(a + 1, na - 1, code, env, tail);
        if (!tail) code_emit(code, OP_POP_ENV, VAL_NIL);
        ul_free(binds); ul_free(a); return;
    }

    /* letrec / letrec* */
    if (head == SYM_LETREC || head == SYM_LETREC_STAR) {
        Value *a; int na = value_to_list(args, &a);
        Value *binds; int nb = value_to_list(a[0], &binds);
        code_emit(code, OP_PUSH_ENV, VAL_NIL);
        for (int i = 0; i < nb; i++) {
            Value *bp; int nbp = value_to_list(binds[i], &bp);
            code_emit(code, OP_VOID, VAL_NIL);
            code_emit(code, OP_BIND, bp[0]);
            ul_free(bp);
        }
        for (int i = 0; i < nb; i++) {
            Value *bp; int nbp = value_to_list(binds[i], &bp);
            bc_compile(bp[1], code, env, false);
            code_emit(code, OP_SET, bp[0]);
            ul_free(bp);
        }
        bc_body(a + 1, na - 1, code, env, tail);
        if (!tail) code_emit(code, OP_POP_ENV, VAL_NIL);
        ul_free(binds); ul_free(a); return;
    }

    /* do */
    if (head == SYM_DO) {
        Value *a; int na = value_to_list(args, &a);
        Value *vcs; int nvc = value_to_list(a[0], &vcs);
        Value *term; int nterm = value_to_list(a[1], &term);

        /* Initialize vars */
        typedef struct { Value var; Value step; } DoSpec;
        DoSpec *specs = (DoSpec *)ul_malloc_values(sizeof(DoSpec) * nvc);
        for (int i = 0; i < nvc; i++) {
            Value *sp; int nsp = value_to_list(vcs[i], &sp);
            specs[i].var = sp[0];
            specs[i].step = nsp > 2 ? sp[2] : sp[0];
            bc_compile(sp[1], code, env, false);
            ul_free(sp);
        }
        code_emit(code, OP_PUSH_ENV, VAL_NIL);
        for (int i = nvc - 1; i >= 0; i--) code_emit(code, OP_BIND, specs[i].var);

        int loop_start = code->count;
        bc_compile(term[0], code, env, false);
        int jf = code_emit(code, OP_JUMP_IF_FALSE, VAL_NIL);
        if (nterm > 1) {
            for (int i = 1; i < nterm - 1; i++) { bc_compile(term[i], code, env, false); code_emit(code, OP_POP, VAL_NIL); }
            bc_compile(term[nterm-1], code, env, tail);
        } else {
            code_emit(code, OP_VOID, VAL_NIL);
        }
        int je = code_emit(code, OP_JUMP, VAL_NIL);
        code_patch(code, jf, VAL_INT(code->count));

        /* Body */
        for (int i = 2; i < na; i++) { bc_compile(a[i], code, env, false); code_emit(code, OP_POP, VAL_NIL); }

        /* Steps */
        for (int i = 0; i < nvc; i++) bc_compile(specs[i].step, code, env, false);
        for (int i = nvc - 1; i >= 0; i--) code_emit(code, OP_SET, specs[i].var);
        code_emit(code, OP_JUMP, VAL_INT(loop_start));
        code_patch(code, je, VAL_INT(code->count));
        if (!tail) code_emit(code, OP_POP_ENV, VAL_NIL);

        ul_free(specs); ul_free(vcs); ul_free(term); ul_free(a);
        return;
    }

    /* call/cc */
    if (head == SYM_CALL_CC || head == SYM_CALL_CC2) {
        Value *a; int na = value_to_list(args, &a);
        bc_compile(a[0], code, env, false);
        code_emit(code, OP_CALL_CC, VAL_NIL);
        ul_free(a); return;
    }

    /* apply — fallback to eval */
    if (head == SYM_APPLY) {
        code_emit(code, OP_EVAL, expr); return;
    }

    /* Macro expansion at compile time */
    if (IS_SYM(head)) {
        TRY(ctx) {
            Value hval = env_lookup(env, head);
            if (IS_MACRO(hval)) {
                ULMacro *m = AS_MACRO(hval);
                Value *a; int na = value_to_list(args, &a);
                Value expanded;
                if (IS_SYNTAX_TRANSFORMER(m->transformer)) {
                    Value form = list_to_value(a, na);
                    expanded = syntax_transform_value(AS_SYNTAX_TRANSFORMER(m->transformer), form);
                } else {
                    expanded = call_proc(m->transformer, a, na, env);
                }
                ul_free(a);
                bc_compile(expanded, code, env, tail);
                return;
            }
        } CATCH {
            /* Not found — continue to regular call */
        } ENDTRY;
    }

    /* Specialized opcodes for hot builtins */
    if (IS_SYM(head) && bc_is_global(head, env)) {
        Value *call_args; int nca = value_to_list(args, &call_args);

        /* + with 2 args */
        if (head == intern("+") && nca == 2) {
            /* Check for +1 optimization */
            if (IS_INT(call_args[1]) && as_int(call_args[1]) == 1 && IS_SYM(call_args[0])) {
                code_emit(code, OP_LOOK_ADD1, call_args[0]);
                ul_free(call_args); return;
            }
            if (IS_INT(call_args[0]) && as_int(call_args[0]) == 1 && IS_SYM(call_args[1])) {
                code_emit(code, OP_LOOK_ADD1, call_args[1]);
                ul_free(call_args); return;
            }
            bc_compile(call_args[0], code, env, false);
            bc_compile(call_args[1], code, env, false);
            code_emit(code, OP_ADD, VAL_NIL);
            ul_free(call_args); return;
        }
        /* - with 2 args */
        if (head == intern("-") && nca == 2) {
            if (IS_INT(call_args[1]) && as_int(call_args[1]) == 1 && IS_SYM(call_args[0])) {
                code_emit(code, OP_LOOK_SUB1, call_args[0]);
                ul_free(call_args); return;
            }
            bc_compile(call_args[0], code, env, false);
            bc_compile(call_args[1], code, env, false);
            code_emit(code, OP_SUB, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("-") && nca == 1) {
            bc_compile(call_args[0], code, env, false);
            code_emit(code, OP_NEG, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("*") && nca == 2) {
            bc_compile(call_args[0], code, env, false);
            bc_compile(call_args[1], code, env, false);
            code_emit(code, OP_MUL, VAL_NIL);
            ul_free(call_args); return;
        }

        /* Comparison ops */
        #define SPECIALIZE_CMP(sym_str, opcode) \
            if (head == intern(sym_str) && nca == 2) { \
                bc_compile(call_args[0], code, env, false); \
                bc_compile(call_args[1], code, env, false); \
                code_emit(code, opcode, VAL_NIL); \
                ul_free(call_args); return; \
            }
        SPECIALIZE_CMP("=", OP_NUM_EQ)
        SPECIALIZE_CMP("<", OP_LT)
        SPECIALIZE_CMP(">", OP_GT)
        SPECIALIZE_CMP("<=", OP_LE)
        SPECIALIZE_CMP(">=", OP_GE)
        #undef SPECIALIZE_CMP

        /* car, cdr, cons, null?, pair?, not, zero? */
        if (head == intern("car") && nca == 1) {
            bc_compile(call_args[0], code, env, false);
            code_emit(code, OP_CAR, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("cdr") && nca == 1) {
            bc_compile(call_args[0], code, env, false);
            code_emit(code, OP_CDR, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("cons") && nca == 2) {
            bc_compile(call_args[0], code, env, false);
            bc_compile(call_args[1], code, env, false);
            code_emit(code, OP_CONS, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("null?") && nca == 1) {
            bc_compile(call_args[0], code, env, false);
            code_emit(code, OP_NULL_P, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("pair?") && nca == 1) {
            bc_compile(call_args[0], code, env, false);
            code_emit(code, OP_PAIR_P, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("not") && nca == 1) {
            bc_compile(call_args[0], code, env, false);
            code_emit(code, OP_NOT, VAL_NIL);
            ul_free(call_args); return;
        }
        if (head == intern("zero?") && nca == 1) {
            bc_compile(call_args[0], code, env, false);
            code_emit(code, OP_ZERO_P, VAL_NIL);
            ul_free(call_args); return;
        }

        ul_free(call_args);
    }

    /* Self tail-call optimization */
    if (tail && IS_SYM(head) && code->self_name && strcmp(sym_name(head), code->self_name) == 0) {
        Value *call_args; int nca = value_to_list(args, &call_args);
        for (int i = 0; i < nca; i++) bc_compile(call_args[i], code, env, false);
        code_emit2(code, OP_SELF_TAIL_CALL, VAL_INT(nca), code->self_nparams);
        ul_free(call_args); return;
    }

    /* Regular function call */
    bc_compile(head, code, env, false);
    Value *call_args; int nca = value_to_list(args, &call_args);
    for (int i = 0; i < nca; i++) bc_compile(call_args[i], code, env, false);
    code_emit(code, tail ? OP_TAIL_CALL : OP_CALL, VAL_INT(nca));
    ul_free(call_args);
}

/* syntax_transform_value is declared in lumbda.h, implemented in eval.c */

/* ═══════════════════════════════════════════════════════════════════════════
 * Compile a Proc into a CompiledProc
 * ═══════════════════════════════════════════════════════════════════════════ */

CompiledProc *compile_proc(Proc *p, Env *env) {
    CodeObj *code = bc_lambda(p->body.exprs, p->body.count,
                              p->params, p->nparams, p->rest, env,
                              p->name, NULL, NULL, 0);
    CompiledProc *cp = (CompiledProc *)ul_malloc_values(sizeof(CompiledProc));
    cp->hdr.type = OBJ_COMPILED_PROC;
    cp->code = code;
    cp->params = (Value *)ul_malloc_values(sizeof(Value) * p->nparams);
    memcpy(cp->params, p->params, sizeof(Value) * p->nparams);
    cp->nparams = p->nparams;
    cp->rest = p->rest;
    cp->env = p->env;
    cp->name = p->name ? ul_strdup(p->name) : NULL;
    return cp;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * VM execution
 * ═══════════════════════════════════════════════════════════════════════════ */

/* Invoke a FullCont — signals the VM trampoline via longjmp */
static Value invoke_continuation(FullCont *cont, Value val) {
    if (!g_cont_trampoline || !g_cont_trampoline->active) {
        lisp_error("continuation invoked outside VM execution");
    }
    g_cont_invoked = cont;
    g_cont_invoked_val = val;
    longjmp(g_cont_trampoline->jmp, 1);
    return VAL_VOID; /* unreachable */
}

/* Builtin wrapper for calling a continuation as a function */
static Value cont_call_builtin(Value *args, int nargs, Env *env) {
    (void)env;
    /* The continuation value is stored as the first arg; we passed it differently.
     * Actually, we need to find the continuation. The trick: we use the eval.c
     * call path which checks IS_CONTINUATION. So this path is only for when
     * continuations end up in the generic builtin slot (which won't happen). */
    lisp_error("continuation called through wrong path");
    return VAL_VOID;
}

Value vm_exec(CodeObj *code, Env *env) {
    CodeObj *cur_code = code;
    Instruction *instrs = code->instrs;
    int ip = 0;
    int n_instrs = code->count;

    ValueStack stack;
    vs_init(&stack, 128);

    /* Frame stack */
    int frame_cap = 32;
    int frame_count = 0;
    VMFrame *frames = (VMFrame *)ul_malloc(sizeof(VMFrame) * frame_cap);

    /* Unique ID for this VM invocation (used to match continuations) */
    int vm_id_storage;
    void *vm_id = &vm_id_storage;

    /* Set up trampoline for continuation invocation */
    ContTrampoline trampoline;
    trampoline.active = true;
    ContTrampoline *prev_trampoline = g_cont_trampoline;
    g_cont_trampoline = &trampoline;

    if (setjmp(trampoline.jmp) != 0) {
        /* A continuation was invoked — resume it */
        FullCont *c = g_cont_invoked;
        Value val = g_cont_invoked_val;
        g_cont_invoked = NULL;

        /* Deep-copy the continuation state for multi-shot safety */
        frame_count = c->nframes;
        if (frame_count > frame_cap) {
            frame_cap = frame_count * 2;
        }
        ul_free(frames);
        frames = deep_copy_frames(c->frames, c->nframes);
        frame_cap = frame_count > 0 ? frame_count * 2 : 32;

        ul_free(stack.data);
        stack.len = c->stack_len;
        stack.cap = c->stack_len + 16;
        stack.data = (Value *)ul_malloc_values(sizeof(Value) * stack.cap);
        memcpy(stack.data, c->stack, sizeof(Value) * c->stack_len);
        vs_push(&stack, val);

        ip = c->ip;
        instrs = c->instrs;
        n_instrs = c->n_instrs;
        env = deep_copy_env(c->env);
        /* Fall through to main loop */
    }

    while (ip < n_instrs) {
        Instruction *instr = &instrs[ip++];
        Opcode op = instr->op;
        Value arg = instr->arg;

        switch (op) {
        case OP_CONST:
            vs_push(&stack, arg);
            break;
        case OP_LOOKUP:
            vs_push(&stack, env_lookup(env, arg));
            break;
        case OP_SET:
            env_set(env, arg, vs_pop(&stack));
            break;
        case OP_DEFINE:
            env_define(env, arg, vs_pop(&stack));
            break;
        case OP_POP:
            vs_pop(&stack);
            break;
        case OP_DUP:
            vs_push(&stack, vs_peek(&stack));
            break;
        case OP_VOID:
            vs_push(&stack, VAL_VOID);
            break;
        case OP_JUMP:
            ip = (int)as_int(arg);
            if (g_portal_checkpoint_path) {
                FullCont *cp_cont = make_full_cont(frames, frame_count,
                    stack.data, stack.len, ip, instrs, n_instrs, env, vm_id);
                portal_save(env, g_portal_checkpoint_path, cp_cont);
                ul_free((void *)g_portal_checkpoint_path);
                g_portal_checkpoint_path = NULL;
            }
            break;
        case OP_JUMP_IF_FALSE: {
            Value v = vs_pop(&stack);
            if (IS_FALSE(v)) ip = (int)as_int(arg);
            break;
        }
        case OP_JUMP_IF_FALSE_KEEP:
            if (IS_FALSE(stack.data[stack.len - 1])) ip = (int)as_int(arg);
            else vs_pop(&stack);
            break;
        case OP_JUMP_IF_TRUE_KEEP:
            if (IS_TRUTHY(stack.data[stack.len - 1])) ip = (int)as_int(arg);
            else vs_pop(&stack);
            break;
        case OP_CALL: {
            int nargs = (int)as_int(arg);
            Value *args_arr = stack.data + stack.len - nargs;
            stack.len -= nargs;
            Value func = vs_pop(&stack);

            if (IS_CONTINUATION(func)) {
                Value val = nargs > 0 ? args_arr[0] : VAL_VOID;
                invoke_continuation(AS_CONTINUATION(func), val);
                break; /* unreachable */
            }
            if (IS_COMPILED_PROC(func)) {
                CompiledProc *cp = AS_COMPILED_PROC(func);
                /* Push frame */
                if (frame_count >= frame_cap) {
                    frame_cap *= 2;
                    frames = (VMFrame *)ul_realloc(frames, sizeof(VMFrame) * frame_cap);
                }
                VMFrame *f = &frames[frame_count++];
                f->instrs = instrs; f->ip = ip; f->n_instrs = n_instrs;
                f->env = env;
                f->stack = stack.data; f->stack_len = stack.len; f->stack_cap = stack.cap;
                f->cur_code = cur_code;

                env = env_child(cp->env, cp->params, cp->nparams, cp->rest, args_arr, nargs);
                cur_code = cp->code;
                instrs = cp->code->instrs; ip = 0; n_instrs = cp->code->count;
                vs_init(&stack, 64);
                continue;
            }
            if (IS_PROC(func)) {
                vs_push(&stack, call_proc(func, args_arr, nargs, env));
            } else if (IS_BUILTIN(func)) {
                vs_push(&stack, AS_BUILTIN(func)(args_arr, nargs, env));
            } else {
                lisp_error("not callable");
            }
            break;
        }
        case OP_TAIL_CALL: {
            int nargs = (int)as_int(arg);
            Value *args_arr = stack.data + stack.len - nargs;
            stack.len -= nargs;
            Value func = vs_pop(&stack);

            if (IS_CONTINUATION(func)) {
                Value val = nargs > 0 ? args_arr[0] : VAL_VOID;
                invoke_continuation(AS_CONTINUATION(func), val);
                break; /* unreachable */
            }
            if (IS_COMPILED_PROC(func)) {
                CompiledProc *cp = AS_COMPILED_PROC(func);
                env = env_child(cp->env, cp->params, cp->nparams, cp->rest, args_arr, nargs);
                cur_code = cp->code;
                instrs = cp->code->instrs; ip = 0; n_instrs = cp->code->count;
                vs_clear(&stack);
                if (g_portal_checkpoint_path) {
                    FullCont *cp_cont = make_full_cont(frames, frame_count,
                        stack.data, stack.len, ip, instrs, n_instrs, env, vm_id);
                    portal_save(env, g_portal_checkpoint_path, cp_cont);
                    ul_free((void *)g_portal_checkpoint_path);
                    g_portal_checkpoint_path = NULL;
                }
                continue;
            }
            if (IS_PROC(func) || IS_BUILTIN(func)) {
                Value ret = call_proc(func, args_arr, nargs, env);
                if (frame_count == 0) { ul_free(frames); ul_free(stack.data); g_cont_trampoline = prev_trampoline; return ret; }
                VMFrame *f = &frames[--frame_count];
                instrs = f->instrs; ip = f->ip; n_instrs = f->n_instrs;
                env = f->env;
                ul_free(stack.data);
                stack.data = f->stack; stack.len = f->stack_len; stack.cap = f->stack_cap;
                cur_code = f->cur_code;
                vs_push(&stack, ret);
                continue;
            }
            lisp_error("not callable");
            break;
        }
        case OP_RETURN: {
            Value ret = stack.len > 0 ? vs_pop(&stack) : VAL_VOID;
            if (frame_count == 0) { ul_free(frames); ul_free(stack.data); g_cont_trampoline = prev_trampoline; return ret; }
            VMFrame *f = &frames[--frame_count];
            instrs = f->instrs; ip = f->ip; n_instrs = f->n_instrs;
            env = f->env;
            ul_free(stack.data);
            stack.data = f->stack; stack.len = f->stack_len; stack.cap = f->stack_cap;
            cur_code = f->cur_code;
            vs_push(&stack, ret);
            continue;
        }
        case OP_MAKE_CLOSURE: {
            /* arg is a vector: [code, nparams, rest, params_vec] */
            ULVector *info = AS_VECTOR(arg);
            CodeObj *inner = AS_CODE(info->data[0]);
            int nparams = (int)as_int(info->data[1]);
            Value rest = info->data[2];
            ULVector *params_vec = AS_VECTOR(info->data[3]);

            CompiledProc *cp = (CompiledProc *)ul_malloc_values(sizeof(CompiledProc));
            cp->hdr.type = OBJ_COMPILED_PROC;
            cp->code = inner;
            cp->params = params_vec->data;
            cp->nparams = nparams;
            cp->rest = rest;
            cp->env = env;
            cp->name = inner->name;
            vs_push(&stack, VAL_PTR(cp));
            break;
        }
        case OP_PUSH_ENV:
            env = make_env(env);
            break;
        case OP_POP_ENV:
            env = env->parent;
            break;
        case OP_BIND:
            env_define(env, arg, vs_pop(&stack));
            break;
        case OP_EVAL:
            vs_push(&stack, leval(arg, env));
            break;
        case OP_CALL_CC:
            /* Full call/cc — capture current VM state as a FullCont */
            {
                Value proc = vs_pop(&stack);
                FullCont *cont = make_full_cont(frames, frame_count,
                    stack.data, stack.len, ip, instrs, n_instrs, env, vm_id);
                Value cont_val = VAL_PTR(cont);

                if (IS_COMPILED_PROC(proc)) {
                    /* Push frame and enter compiled proc with continuation as arg */
                    CompiledProc *cp = AS_COMPILED_PROC(proc);
                    if (frame_count >= frame_cap) {
                        frame_cap *= 2;
                        frames = (VMFrame *)ul_realloc(frames, sizeof(VMFrame) * frame_cap);
                    }
                    VMFrame *f = &frames[frame_count++];
                    f->instrs = instrs; f->ip = ip; f->n_instrs = n_instrs;
                    f->env = env;
                    f->stack = stack.data; f->stack_len = stack.len; f->stack_cap = stack.cap;
                    f->cur_code = cur_code;
                    Value kargs[1] = {cont_val};
                    env = env_child(cp->env, cp->params, cp->nparams, cp->rest, kargs, 1);
                    cur_code = cp->code;
                    instrs = cp->code->instrs; ip = 0; n_instrs = cp->code->count;
                    vs_init(&stack, 64);
                    continue;
                }
                if (IS_PROC(proc) || IS_BUILTIN(proc)) {
                    Value kargs[1] = {cont_val};
                    vs_push(&stack, call_proc(proc, kargs, 1, env));
                } else {
                    lisp_error("call/cc: not callable");
                }
            }
            break;

        /* Specialized opcodes */
        case OP_ADD: { Value b = vs_pop(&stack); stack.data[stack.len-1] = num_add(stack.data[stack.len-1], b); break; }
        case OP_SUB: { Value b = vs_pop(&stack); stack.data[stack.len-1] = num_sub(stack.data[stack.len-1], b); break; }
        case OP_MUL: { Value b = vs_pop(&stack); stack.data[stack.len-1] = num_mul(stack.data[stack.len-1], b); break; }
        case OP_NEG: stack.data[stack.len-1] = num_neg(stack.data[stack.len-1]); break;
        case OP_ADD1: stack.data[stack.len-1] = num_add(stack.data[stack.len-1], VAL_INT(1)); break;
        case OP_SUB1: stack.data[stack.len-1] = num_sub(stack.data[stack.len-1], VAL_INT(1)); break;
        case OP_NUM_EQ: { Value b = vs_pop(&stack); stack.data[stack.len-1] = VAL_BOOL(num_eq(stack.data[stack.len-1], b)); break; }
        case OP_LT: { Value b = vs_pop(&stack); stack.data[stack.len-1] = VAL_BOOL(num_lt(stack.data[stack.len-1], b)); break; }
        case OP_GT: { Value b = vs_pop(&stack); stack.data[stack.len-1] = VAL_BOOL(num_gt(stack.data[stack.len-1], b)); break; }
        case OP_LE: { Value b = vs_pop(&stack); stack.data[stack.len-1] = VAL_BOOL(num_le(stack.data[stack.len-1], b)); break; }
        case OP_GE: { Value b = vs_pop(&stack); stack.data[stack.len-1] = VAL_BOOL(num_ge(stack.data[stack.len-1], b)); break; }
        case OP_CAR: stack.data[stack.len-1] = CAR(stack.data[stack.len-1]); break;
        case OP_CDR: stack.data[stack.len-1] = CDR(stack.data[stack.len-1]); break;
        case OP_CONS: { Value d = vs_pop(&stack); stack.data[stack.len-1] = cons(stack.data[stack.len-1], d); break; }
        case OP_NULL_P: stack.data[stack.len-1] = VAL_BOOL(IS_NIL(stack.data[stack.len-1])); break;
        case OP_PAIR_P: stack.data[stack.len-1] = VAL_BOOL(IS_PAIR(stack.data[stack.len-1])); break;
        case OP_NOT: stack.data[stack.len-1] = VAL_BOOL(IS_FALSE(stack.data[stack.len-1])); break;
        case OP_ZERO_P: stack.data[stack.len-1] = VAL_BOOL(num_eq(stack.data[stack.len-1], VAL_INT(0))); break;
        case OP_VEC_REF: { Value i = vs_pop(&stack); stack.data[stack.len-1] = AS_VECTOR(stack.data[stack.len-1])->data[(int)as_int(i)]; break; }
        case OP_VEC_SET: { Value v = vs_pop(&stack); Value i = vs_pop(&stack); AS_VECTOR(stack.data[stack.len-1])->data[(int)as_int(i)] = v; stack.data[stack.len-1] = VAL_VOID; break; }

        /* Superinstructions */
        case OP_LOOK_ADD1: vs_push(&stack, num_add(env_lookup(env, arg), VAL_INT(1))); break;
        case OP_LOOK_SUB1: vs_push(&stack, num_sub(env_lookup(env, arg), VAL_INT(1))); break;
        case OP_SELF_TAIL_CALL: {
            int nargs = (int)as_int(arg);
            int nparams_expected = instr->arg2;
            Value *args_arr = stack.data + stack.len - nargs;
            /* Rebind in current env */
            if (cur_code->self_params) {
                for (int i = 0; i < nargs && i < nparams_expected; i++) {
                    env_set(env, cur_code->self_params[i], args_arr[i]);
                }
            }
            ip = 0;
            vs_clear(&stack);
            continue;
        }
        default:
            lisp_error("unknown opcode: %d", op);
        }
    }

    Value ret = stack.len > 0 ? stack.data[stack.len - 1] : VAL_VOID;
    ul_free(stack.data);
    ul_free(frames);
    g_cont_trampoline = prev_trampoline;
    return ret;
}
