Both bytecode VMs had a latent O(n^2) defect on self-recursive tail calls invoked from inside let/let*/letrec/letrec*/do bodies. The self-tail-call op assumed reusing "current env" was safe, but current env was the innermost let* frame, not the lambda body env. Each iter pushed a fresh let* frame on top (PUSH_ENV at compile site), the self-tail-call rebound params into that frame & jumped to ip=0 without unwinding. Env chain grew linearly with iters; every var lookup walked O(n) chain; effective O(n^2) behaviour. Symptom observed 2026-06-14: 156k circ-ops walk hung > 5min instead of 1.4s. K=5 doctrine reducers ran 30+ runaway lumbda procs at 99% CPU across multiple `make sweep-doctrine` invocations before we tracked it back to language layer (initially misdiagnosed as K=5 substrate). Fix: track scope depth at compile time on CodeObj (scope_depth bumped on PUSH_ENV emit, decremented on POP_ENV emit). Record self_base at lambda body entry (0 unless internal defines pushed a frame). At self-tail-call emit, encode pops_needed = scope_depth - self_base in the op arg. Runtime handler unwinds that many env frames before rebinding params + jumping to ip=0. Tree-walker (c/lumbda without --fast) already worked - it walks the ast & lets recursion clean up frames naturally. Asm tier also fine - no self-tail-call op, uses different lambda-call convention. Verification: python tier: 571 tests PASS, our 100k let* repro 1.04s wall (was infinite) c tier: 205 tests PASS, same repro 0.05s wall (was infinite) asm tier: 158 tests PASS (no fix needed, never had the bug) Portal-resume backwards-compat: pre-fix portals stored OP_SELF_TAIL_CALL arg as 2-tuple. Deserializer fills pops=0 when 'pops' key is absent, so an old portal resumes at correct behaviour at the cost of slow walk on its very next self-tail-call body (no worse than pre-fix). Memory note saved at reference_lumbda_let_star_in_tail_loop in our foxhop blackops memory for future agents.
1006 lines
42 KiB
C
1006 lines
42 KiB
C
/*
|
|
* 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);
|
|
}
|
|
/* 2026-06-14 self-tail-call frame-unwind: track env-frame depth so
|
|
OP_SELF_TAIL_CALL pops accumulated let/let-star/letrec/do frames. */
|
|
if (op == OP_PUSH_ENV) c->scope_depth++;
|
|
else if (op == OP_POP_ENV) c->scope_depth--;
|
|
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]);
|
|
}
|
|
}
|
|
/* 2026-06-14: record baseline depth after internal-defines frame.
|
|
Self-tail-call unwinds back to here, not all the way to 0. */
|
|
inner->self_base = inner->scope_depth;
|
|
|
|
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);
|
|
/* 2026-06-14 frame-unwind: pops accumulated let/let-star/letrec/do
|
|
frames before we reuse our lambda body env. arg2 now carries
|
|
pops_needed (was self_nparams, redundant since cur_code knows it). */
|
|
int pops_needed = code->scope_depth - code->self_base;
|
|
code_emit2(code, OP_SELF_TAIL_CALL, VAL_INT(nca), pops_needed);
|
|
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 pops = instr->arg2;
|
|
Value *args_arr = stack.data + stack.len - nargs;
|
|
/* 2026-06-14 frame-unwind: pop let/let-star/letrec/do frames
|
|
accumulated since lambda body entry. Otherwise env grows
|
|
per iter & every var lookup walks an O(n) chain. */
|
|
while (pops > 0 && env->parent) {
|
|
env = env->parent;
|
|
pops--;
|
|
}
|
|
/* Rebind in lambda body env (post-unwind) */
|
|
if (cur_code->self_params) {
|
|
for (int i = 0; i < nargs && i < cur_code->self_nparams; 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;
|
|
}
|