Files
cell/source/mach.c

3317 lines
117 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "quickjs-internal.h"
/* ---- Compile-time constant pool entry ---- */
/* Stores raw data during compilation; converted to JSValues when loading into context */
typedef enum { MACH_CP_INT, MACH_CP_FLOAT, MACH_CP_STR } MachCPType;
typedef struct {
MachCPType type;
union {
int32_t ival; /* integer constant */
double fval; /* float constant */
char *str; /* owned C string */
};
} MachCPoolEntry;
/* ---- Compiled output (context-free) ---- */
typedef struct MachCode {
uint16_t arity;
uint16_t nr_close_slots;
uint16_t nr_slots;
uint16_t entry_point;
uint32_t cpool_count;
MachCPoolEntry *cpool;
uint32_t instr_count;
MachInstr32 *instructions;
uint32_t func_count;
struct MachCode **functions;
char *name; /* owned C string, or NULL */
MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */
char *filename; /* source filename (sys_malloc'd) */
uint16_t disruption_pc; /* start of disruption handler (0 = none) */
} MachCode;
/* ---- Compiler state ---- */
typedef struct MachCompState {
/* Instruction buffer (growable) */
MachInstr32 *code;
int code_count;
int code_capacity;
/* Constant pool (raw entries, no GC objects) */
MachCPoolEntry *cpool;
int cpool_count;
int cpool_capacity;
/* Nested functions */
MachCode **functions;
int func_count;
int func_capacity;
/* Variables */
MachVarInfo *vars;
int var_count;
int var_capacity;
/* Register allocation (Lua-style) */
int freereg; /* next free register */
int maxreg; /* high-water mark */
int nr_args; /* parameter count */
/* Loop labels for break/continue */
int loop_break; /* instruction index to patch, or -1 */
int loop_continue; /* instruction index to patch, or -1 */
/* Scope depth for block scoping */
int scope_depth;
/* Parent for nested function compilation */
struct MachCompState *parent;
/* AST semantic annotations */
int function_nr; /* current function number (0=program body) */
cJSON *scopes; /* pointer to AST "scopes" array (not owned) */
/* Error tracking */
int has_error;
/* Line tracking for debug info */
int cur_line, cur_col;
MachLineEntry *line_info; /* growable, parallel to code[] */
int line_capacity;
const char *filename; /* pointer into AST cJSON (not owned) */
} MachCompState;
/* Forward declarations */
static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest);
static void mach_compile_stmt(MachCompState *cs, cJSON *stmt);
/* ---- Compiler helpers ---- */
static void mach_set_pos(MachCompState *cs, cJSON *node) {
cJSON *r = cJSON_GetObjectItemCaseSensitive(node, "from_row");
cJSON *c = cJSON_GetObjectItemCaseSensitive(node, "from_column");
if (r) cs->cur_line = (int)r->valuedouble + 1;
if (c) cs->cur_col = (int)c->valuedouble + 1;
}
static void mach_emit(MachCompState *cs, MachInstr32 instr) {
if (cs->code_count >= cs->code_capacity) {
int new_cap = cs->code_capacity ? cs->code_capacity * 2 : 64;
cs->code = sys_realloc(cs->code, new_cap * sizeof(MachInstr32));
cs->code_capacity = new_cap;
}
if (cs->code_count >= cs->line_capacity) {
int new_cap = cs->line_capacity ? cs->line_capacity * 2 : 64;
cs->line_info = sys_realloc(cs->line_info, new_cap * sizeof(MachLineEntry));
cs->line_capacity = new_cap;
}
cs->line_info[cs->code_count] = (MachLineEntry){cs->cur_line, cs->cur_col};
cs->code[cs->code_count++] = instr;
}
static int mach_current_pc(MachCompState *cs) {
return cs->code_count;
}
/* Reserve a register at freereg */
static int mach_reserve_reg(MachCompState *cs) {
int r = cs->freereg++;
if (cs->freereg > cs->maxreg) cs->maxreg = cs->freereg;
return r;
}
/* Free temporary registers back to a saved freereg level */
static void mach_free_reg_to(MachCompState *cs, int saved) {
cs->freereg = saved;
}
/* Add an integer constant to the pool, return its index */
static int mach_cpool_add_int(MachCompState *cs, int32_t val) {
for (int i = 0; i < cs->cpool_count; i++) {
MachCPoolEntry *e = &cs->cpool[i];
if (e->type == MACH_CP_INT && e->ival == val) return i;
}
if (cs->cpool_count >= cs->cpool_capacity) {
int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16;
cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry));
cs->cpool_capacity = new_cap;
}
cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_INT, .ival = val };
return cs->cpool_count++;
}
/* Add a float constant to the pool, return its index */
static int mach_cpool_add_float(MachCompState *cs, double val) {
for (int i = 0; i < cs->cpool_count; i++) {
MachCPoolEntry *e = &cs->cpool[i];
if (e->type == MACH_CP_FLOAT && e->fval == val) return i;
}
if (cs->cpool_count >= cs->cpool_capacity) {
int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16;
cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry));
cs->cpool_capacity = new_cap;
}
cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_FLOAT, .fval = val };
return cs->cpool_count++;
}
/* Add a string constant, return its cpool index */
static int mach_cpool_add_str(MachCompState *cs, const char *str) {
/* Check for existing identical string */
for (int i = 0; i < cs->cpool_count; i++) {
MachCPoolEntry *e = &cs->cpool[i];
if (e->type == MACH_CP_STR && strcmp(e->str, str) == 0)
return i;
}
if (cs->cpool_count >= cs->cpool_capacity) {
int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16;
cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry));
cs->cpool_capacity = new_cap;
}
char *dup = sys_malloc(strlen(str) + 1);
memcpy(dup, str, strlen(str) + 1);
cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_STR, .str = dup };
return cs->cpool_count++;
}
/* Convert compile-time cpool entries to JSValue array for JSCodeRegister.
Caller takes ownership of the returned array. Frees the raw entries.
Strings are interned into stone memory (no GC allocation). */
static JSValue *mach_materialize_cpool(JSContext *ctx, MachCPoolEntry *entries, int count) {
if (count == 0) { sys_free(entries); return NULL; }
JSValue *cpool = js_malloc_rt(count * sizeof(JSValue));
for (int i = 0; i < count; i++) {
switch (entries[i].type) {
case MACH_CP_INT:
cpool[i] = JS_NewInt32(ctx, entries[i].ival);
break;
case MACH_CP_FLOAT:
cpool[i] = JS_NewFloat64(ctx, entries[i].fval);
break;
case MACH_CP_STR:
cpool[i] = js_key_new(ctx, entries[i].str);
break;
}
}
return cpool;
}
/* Add a variable */
static void mach_add_var(MachCompState *cs, const char *name, int slot, int is_const) {
if (cs->var_count >= cs->var_capacity) {
int new_cap = cs->var_capacity ? cs->var_capacity * 2 : 16;
cs->vars = sys_realloc(cs->vars, new_cap * sizeof(MachVarInfo));
cs->var_capacity = new_cap;
}
MachVarInfo *v = &cs->vars[cs->var_count++];
v->name = sys_malloc(strlen(name) + 1);
strcpy(v->name, name);
v->slot = slot;
v->is_const = is_const;
v->is_closure = 0;
v->scope_depth = cs->scope_depth;
}
/* Find a variable in the current scope */
static int mach_find_var(MachCompState *cs, const char *name) {
for (int i = cs->var_count - 1; i >= 0; i--) {
if (strcmp(cs->vars[i].name, name) == 0)
return cs->vars[i].slot;
}
return -1;
}
/* Add a nested function, return its index */
static int mach_add_function(MachCompState *cs, MachCode *fn) {
if (cs->func_count >= cs->func_capacity) {
int new_cap = cs->func_capacity ? cs->func_capacity * 2 : 4;
cs->functions = sys_realloc(cs->functions, new_cap * sizeof(MachCode*));
cs->func_capacity = new_cap;
}
cs->functions[cs->func_count] = fn;
return cs->func_count++;
}
/* Find the scope record for a given function_nr in the scopes array */
cJSON *mach_find_scope_record(cJSON *scopes, int function_nr) {
if (!scopes) return NULL;
int count = cJSON_GetArraySize(scopes);
for (int i = 0; i < count; i++) {
cJSON *scope = cJSON_GetArrayItem(scopes, i);
cJSON *fn_nr = cJSON_GetObjectItemCaseSensitive(scope, "function_nr");
if (fn_nr && (int)cJSON_GetNumberValue(fn_nr) == function_nr)
return scope;
}
return NULL;
}
/* Scan AST scope record for variable declarations.
Variables are direct keys on the scope object with a "make" field. */
static void mach_scan_scope(MachCompState *cs) {
cJSON *scope = mach_find_scope_record(cs->scopes, cs->function_nr);
if (!scope) return;
cJSON *v;
cJSON_ArrayForEach(v, scope) {
const char *name = v->string;
if (!name || strcmp(name, "function_nr") == 0 || strcmp(name, "nr_close_slots") == 0) continue;
const char *make = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(v, "make"));
if (!make || strcmp(make, "input") == 0) continue;
if (mach_find_var(cs, name) < 0) {
int is_const = (strcmp(make, "def") == 0 || strcmp(make, "function") == 0);
int slot = mach_reserve_reg(cs);
mach_add_var(cs, name, slot, is_const);
}
}
}
/* ---- Expression compiler ---- */
/* Compile an expression into register dest. If dest < 0, allocate a temp.
Returns the register containing the result. */
static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) {
if (!node) {
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
return dest;
}
mach_set_pos(cs, node);
const char *kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "kind"));
if (!kind) {
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
return dest;
}
/* Number literal */
if (strcmp(kind, "number") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *num = cJSON_GetObjectItemCaseSensitive(node, "number");
if (num && cJSON_IsNumber(num)) {
double dval = num->valuedouble;
int ival = (int)dval;
if (dval == (double)ival && ival >= -32768 && ival <= 32767) {
/* Small integer: use LOADI */
mach_emit(cs, MACH_AsBx(MACH_LOADI, dest, (int16_t)ival));
} else {
/* Large number: use constant pool */
int ki;
if (dval == (double)(int32_t)dval)
ki = mach_cpool_add_int(cs, (int32_t)dval);
else
ki = mach_cpool_add_float(cs, dval);
mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki));
}
} else {
mach_emit(cs, MACH_AsBx(MACH_LOADI, dest, 0));
}
return dest;
}
/* String literal */
if (strcmp(kind, "string") == 0 || strcmp(kind, "text") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
const char *val = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value"));
if (val) {
int ki = mach_cpool_add_str(cs, val);
mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki));
} else {
int ki = mach_cpool_add_str(cs, "");
mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki));
}
return dest;
}
/* Template literal with expressions: kind="text literal"
Format: value = "hello {0} world {1}", list = [expr0, expr1]
Compile as: format(fmt_string, [expr0, expr1, ...]) */
if (strcmp(kind, "text literal") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
int save_freereg = cs->freereg;
cJSON *list = cJSON_GetObjectItemCaseSensitive(node, "list");
int nexpr = list ? cJSON_GetArraySize(list) : 0;
/* Reserve consecutive regs for call: [format_fn, fmt_str, arr] */
int call_base = mach_reserve_reg(cs);
int arg1_reg = mach_reserve_reg(cs);
int arg2_reg = mach_reserve_reg(cs);
/* Reserve consecutive regs for NEWARRAY: arr_reg, then elem slots */
int arr_base = mach_reserve_reg(cs);
for (int i = 0; i < nexpr; i++) mach_reserve_reg(cs);
/* Now arr_base+1..arr_base+nexpr are the element slots */
/* Compile expressions into arr_base+1..arr_base+nexpr */
for (int i = 0; i < nexpr; i++) {
cJSON *expr = cJSON_GetArrayItem(list, i);
int slot = arr_base + 1 + i;
int r = mach_compile_expr(cs, expr, slot);
if (r != slot) mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0));
}
/* Create array from consecutive element regs */
mach_emit(cs, MACH_ABC(MACH_NEWARRAY, arr_base, nexpr, 0));
/* Load format function */
int ki_format = mach_cpool_add_str(cs, "format");
mach_emit(cs, MACH_ABx(MACH_GETINTRINSIC, call_base, ki_format));
/* Load format string */
const char *fmt = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value"));
int ki_fmt = mach_cpool_add_str(cs, fmt ? fmt : "");
mach_emit(cs, MACH_ABx(MACH_LOADK, arg1_reg, ki_fmt));
/* Move array to arg2 position */
mach_emit(cs, MACH_ABC(MACH_MOVE, arg2_reg, arr_base, 0));
/* Call format(fmt, arr) */
mach_emit(cs, MACH_ABC(MACH_CALL, call_base, 2, 1));
mach_free_reg_to(cs, save_freereg);
if (dest != call_base)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, call_base, 0));
return dest;
}
/* Boolean/null literals */
if (strcmp(kind, "true") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0));
return dest;
}
if (strcmp(kind, "false") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADFALSE, dest, 0));
return dest;
}
if (strcmp(kind, "null") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
return dest;
}
/* Name (variable reference) */
if (strcmp(kind, "name") == 0) {
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "name"));
if (name) {
cJSON *level_node = cJSON_GetObjectItemCaseSensitive(node, "level");
int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1;
if (level == 0) {
/* Local variable */
int slot = mach_find_var(cs, name);
if (slot >= 0) {
if (dest >= 0 && dest != slot) {
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0));
return dest;
}
return slot;
}
} else if (level > 0) {
/* Closure variable — walk parent compiler states for slot */
MachCompState *target = cs;
for (int i = 0; i < level; i++) target = target->parent;
int slot = mach_find_var(target, name);
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABC(MACH_GETUP, dest, level, slot));
return dest;
}
/* Unbound or fallback — emit placeholder, patched at link time */
if (dest < 0) dest = mach_reserve_reg(cs);
int ki = mach_cpool_add_str(cs, name);
mach_emit(cs, MACH_ABx(MACH_GETNAME, dest, ki));
return dest;
}
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
return dest;
}
/* Function call: kind="(" */
if (strcmp(kind, "(") == 0) {
cJSON *fn_expr = cJSON_GetObjectItemCaseSensitive(node, "expression");
cJSON *args = cJSON_GetObjectItemCaseSensitive(node, "list");
int nargs = args ? cJSON_GetArraySize(args) : 0;
/* Check if this is a method call: obj.method(args) or obj[key](args) */
const char *fn_kind = NULL;
if (fn_expr)
fn_kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "kind"));
/* Functino: inline operator call */
if (fn_kind && strcmp(fn_kind, "name") == 0) {
const char *fn_make = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "make"));
if (fn_make && strcmp(fn_make, "functino") == 0) {
const char *fname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "name"));
if (dest < 0) dest = mach_reserve_reg(cs);
if (strcmp(fname, "~!") == 0) {
int save = cs->freereg;
int r = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1);
mach_emit(cs, MACH_ABC(MACH_BNOT, dest, r, 0));
mach_free_reg_to(cs, save);
return dest;
}
if (strcmp(fname, "[]!") == 0) {
int save = cs->freereg;
int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1);
if (cs->freereg <= r0) cs->freereg = r0 + 1;
int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1);
mach_emit(cs, MACH_ABC(MACH_GETINDEX, dest, r0, r1));
mach_free_reg_to(cs, save);
return dest;
}
if ((strcmp(fname, "=!") == 0 || strcmp(fname, "!=!") == 0) && nargs == 3) {
int save = cs->freereg;
int base = mach_reserve_reg(cs);
int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), base);
if (r0 != base) mach_emit(cs, MACH_ABC(MACH_MOVE, base, r0, 0));
int r1_reg = mach_reserve_reg(cs);
int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), r1_reg);
if (r1 != r1_reg) mach_emit(cs, MACH_ABC(MACH_MOVE, r1_reg, r1, 0));
int r2_reg = mach_reserve_reg(cs);
int r2 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 2), r2_reg);
if (r2 != r2_reg) mach_emit(cs, MACH_ABC(MACH_MOVE, r2_reg, r2, 0));
MachOpcode top = (strcmp(fname, "=!") == 0) ? MACH_EQ_TOL : MACH_NEQ_TOL;
mach_emit(cs, MACH_ABC(top, dest, base, 3));
mach_free_reg_to(cs, save);
return dest;
}
if (strcmp(fname, "&&!") == 0) {
int save = cs->freereg;
int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1);
if (cs->freereg <= r0) cs->freereg = r0 + 1;
int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1);
/* Non-short-circuiting: if left is falsy, result=left, else result=right */
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r0, 0));
int jmp_pc = mach_current_pc(cs);
mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, dest, 0));
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r1, 0));
{
int offset = mach_current_pc(cs) - (jmp_pc + 1);
cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset);
}
mach_free_reg_to(cs, save);
return dest;
}
if (strcmp(fname, "||!") == 0) {
int save = cs->freereg;
int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1);
if (cs->freereg <= r0) cs->freereg = r0 + 1;
int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1);
/* Non-short-circuiting: if left is truthy, result=left, else result=right */
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r0, 0));
int jmp_pc = mach_current_pc(cs);
mach_emit(cs, MACH_AsBx(MACH_JMPTRUE, dest, 0));
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r1, 0));
{
int offset = mach_current_pc(cs) - (jmp_pc + 1);
cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset);
}
mach_free_reg_to(cs, save);
return dest;
}
/* Standard 2-arg binary functino */
{
int save = cs->freereg;
int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1);
if (cs->freereg <= r0) cs->freereg = r0 + 1;
int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1);
MachOpcode op;
if (strcmp(fname, "+!") == 0) op = MACH_ADD;
else if (strcmp(fname, "-!") == 0) op = MACH_SUB;
else if (strcmp(fname, "*!") == 0) op = MACH_MUL;
else if (strcmp(fname, "/!") == 0) op = MACH_DIV;
else if (strcmp(fname, "%!") == 0) op = MACH_MOD;
else if (strcmp(fname, "**!") == 0) op = MACH_POW;
else if (strcmp(fname, "<!") == 0) op = MACH_LT;
else if (strcmp(fname, ">!") == 0) op = MACH_GT;
else if (strcmp(fname, "<=!") == 0) op = MACH_LE;
else if (strcmp(fname, ">=!") == 0) op = MACH_GE;
else if (strcmp(fname, "=!") == 0) op = MACH_EQ;
else if (strcmp(fname, "!=!") == 0) op = MACH_NEQ;
else if (strcmp(fname, "&!") == 0) op = MACH_BAND;
else if (strcmp(fname, "|!") == 0) op = MACH_BOR;
else if (strcmp(fname, "^!") == 0) op = MACH_BXOR;
else if (strcmp(fname, "<<!") == 0) op = MACH_SHL;
else if (strcmp(fname, ">>!") == 0) op = MACH_SHR;
else op = MACH_USHR; /* >>>! */
mach_emit(cs, MACH_ABC(op, dest, r0, r1));
mach_free_reg_to(cs, save);
return dest;
}
}
}
if (fn_kind && strcmp(fn_kind, ".") == 0) {
/* Method call with dot notation: obj.method(args) */
int save_freereg = cs->freereg;
int base = mach_reserve_reg(cs); /* R(base) = obj */
if (dest < 0) dest = base;
mach_reserve_reg(cs); /* R(base+1) = temp slot for VM */
/* Compile obj into base */
cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "expression");
if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "left");
int obj_r = mach_compile_expr(cs, obj_expr, base);
if (obj_r != base)
mach_emit(cs, MACH_ABC(MACH_MOVE, base, obj_r, 0));
/* Extract property name */
cJSON *prop = cJSON_GetObjectItemCaseSensitive(fn_expr, "name");
if (!prop) prop = cJSON_GetObjectItemCaseSensitive(fn_expr, "right");
const char *prop_name = NULL;
if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop);
else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value"));
if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name"));
if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "value"));
if (!prop_name) prop_name = "unknown";
int ki = mach_cpool_add_str(cs, prop_name);
/* Compile args into R(base+2)..R(base+1+nargs) */
for (int i = 0; i < nargs; i++) {
int arg_reg = mach_reserve_reg(cs);
cJSON *arg = cJSON_GetArrayItem(args, i);
int r = mach_compile_expr(cs, arg, arg_reg);
if (r != arg_reg)
mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0));
}
mach_emit(cs, MACH_ABC(MACH_CALLMETHOD, base, nargs, ki));
mach_free_reg_to(cs, save_freereg);
if (dest >= 0 && dest != base)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0));
else
dest = base;
return dest;
}
if (fn_kind && strcmp(fn_kind, "[") == 0) {
/* Method call with bracket notation: obj[expr](args) */
int save_freereg = cs->freereg;
int base = mach_reserve_reg(cs); /* R(base) = obj */
if (dest < 0) dest = base;
int key_reg = mach_reserve_reg(cs); /* R(base+1) = key */
/* Compile obj into base */
cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "expression");
if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "left");
int obj_r = mach_compile_expr(cs, obj_expr, base);
if (obj_r != base)
mach_emit(cs, MACH_ABC(MACH_MOVE, base, obj_r, 0));
/* Compile key expr into R(base+1) */
cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "index");
if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "right");
int kr = mach_compile_expr(cs, idx_expr, key_reg);
if (kr != key_reg)
mach_emit(cs, MACH_ABC(MACH_MOVE, key_reg, kr, 0));
/* Compile args into R(base+2)..R(base+1+nargs) */
for (int i = 0; i < nargs; i++) {
int arg_reg = mach_reserve_reg(cs);
cJSON *arg = cJSON_GetArrayItem(args, i);
int r = mach_compile_expr(cs, arg, arg_reg);
if (r != arg_reg)
mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0));
}
/* C=0xFF signals key is in R(base+1) */
mach_emit(cs, MACH_ABC(MACH_CALLMETHOD, base, nargs, 0xFF));
mach_free_reg_to(cs, save_freereg);
if (dest >= 0 && dest != base)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0));
else
dest = base;
return dest;
}
/* Save freereg so we can allocate consecutive regs for call */
int save_freereg = cs->freereg;
/* Allocate base register for the function */
int base = mach_reserve_reg(cs);
if (dest < 0) dest = base; /* result goes to base */
/* Compile function expression into base */
int fn_reg = mach_compile_expr(cs, fn_expr, base);
if (fn_reg != base) {
mach_emit(cs, MACH_ABC(MACH_MOVE, base, fn_reg, 0));
}
/* Allocate consecutive arg registers and compile args */
for (int i = 0; i < nargs; i++) {
int arg_reg = mach_reserve_reg(cs);
cJSON *arg = cJSON_GetArrayItem(args, i);
int r = mach_compile_expr(cs, arg, arg_reg);
if (r != arg_reg) {
mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0));
}
}
/* Emit CALL: base=func, B=nargs, C=1 if we want result */
int keep = (dest >= 0) ? 1 : 0;
mach_emit(cs, MACH_ABC(MACH_CALL, base, nargs, keep));
/* Restore freereg */
mach_free_reg_to(cs, save_freereg);
/* If we want the result and dest != base, move it */
if (dest >= 0 && dest != base) {
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0));
} else {
dest = base;
}
return dest;
}
/* Binary operators */
if (strcmp(kind, "+") == 0 || strcmp(kind, "-") == 0 ||
strcmp(kind, "*") == 0 || strcmp(kind, "/") == 0 ||
strcmp(kind, "%") == 0 || strcmp(kind, "**") == 0 ||
strcmp(kind, "==") == 0 || strcmp(kind, "!=") == 0 ||
strcmp(kind, "===") == 0 || strcmp(kind, "!==") == 0 ||
strcmp(kind, "<") == 0 || strcmp(kind, "<=") == 0 ||
strcmp(kind, ">") == 0 || strcmp(kind, ">=") == 0 ||
strcmp(kind, "&") == 0 || strcmp(kind, "|") == 0 ||
strcmp(kind, "^") == 0 || strcmp(kind, "<<") == 0 ||
strcmp(kind, ">>") == 0 || strcmp(kind, ">>>") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
int save = cs->freereg;
cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right");
int lr = mach_compile_expr(cs, left, -1);
if (cs->freereg <= lr) cs->freereg = lr + 1; /* protect lr from reuse */
int rr = mach_compile_expr(cs, right, -1);
MachOpcode op;
if (strcmp(kind, "+") == 0) op = MACH_ADD;
else if (strcmp(kind, "-") == 0) op = MACH_SUB;
else if (strcmp(kind, "*") == 0) op = MACH_MUL;
else if (strcmp(kind, "/") == 0) op = MACH_DIV;
else if (strcmp(kind, "%") == 0) op = MACH_MOD;
else if (strcmp(kind, "**") == 0) op = MACH_POW;
else if (strcmp(kind, "==") == 0 || strcmp(kind, "===") == 0) op = MACH_EQ;
else if (strcmp(kind, "!=") == 0 || strcmp(kind, "!==") == 0) op = MACH_NEQ;
else if (strcmp(kind, "<") == 0) op = MACH_LT;
else if (strcmp(kind, "<=") == 0) op = MACH_LE;
else if (strcmp(kind, ">") == 0) op = MACH_GT;
else if (strcmp(kind, ">=") == 0) op = MACH_GE;
else if (strcmp(kind, "&") == 0) op = MACH_BAND;
else if (strcmp(kind, "|") == 0) op = MACH_BOR;
else if (strcmp(kind, "^") == 0) op = MACH_BXOR;
else if (strcmp(kind, "<<") == 0) op = MACH_SHL;
else if (strcmp(kind, ">>") == 0) op = MACH_SHR;
else op = MACH_USHR; /* >>> */
mach_emit(cs, MACH_ABC(op, dest, lr, rr));
mach_free_reg_to(cs, save);
return dest;
}
/* Short-circuit logical operators */
if (strcmp(kind, "&&") == 0 || strcmp(kind, "||") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right");
int lr = mach_compile_expr(cs, left, dest);
if (lr != dest)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, lr, 0));
/* Emit conditional jump — patch offset later */
int jmp_pc = mach_current_pc(cs);
if (strcmp(kind, "&&") == 0)
mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, dest, 0)); /* skip right if false */
else
mach_emit(cs, MACH_AsBx(MACH_JMPTRUE, dest, 0)); /* skip right if true */
int rr = mach_compile_expr(cs, right, dest);
if (rr != dest)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, rr, 0));
/* Patch jump offset: target is current PC, offset relative to instruction after jmp */
int offset = mach_current_pc(cs) - (jmp_pc + 1);
cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset);
return dest;
}
/* Unary operators */
if (strcmp(kind, "!") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression");
if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right");
int save = cs->freereg;
int r = mach_compile_expr(cs, operand, -1);
mach_emit(cs, MACH_ABC(MACH_LNOT, dest, r, 0));
mach_free_reg_to(cs, save);
return dest;
}
if (strcmp(kind, "~") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression");
if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right");
int save = cs->freereg;
int r = mach_compile_expr(cs, operand, -1);
mach_emit(cs, MACH_ABC(MACH_BNOT, dest, r, 0));
mach_free_reg_to(cs, save);
return dest;
}
if (strcmp(kind, "unary_-") == 0 || strcmp(kind, "-unary") == 0 || (strcmp(kind, "-") == 0 && !cJSON_GetObjectItemCaseSensitive(node, "left"))) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression");
if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right");
int save = cs->freereg;
int r = mach_compile_expr(cs, operand, -1);
mach_emit(cs, MACH_ABC(MACH_NEG, dest, r, 0));
mach_free_reg_to(cs, save);
return dest;
}
/* Unary plus: identity for numbers */
if (strcmp(kind, "+unary") == 0 || strcmp(kind, "pos") == 0) {
cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression");
return mach_compile_expr(cs, operand, dest);
}
/* Comma operator: compile left for side effects, return right */
if (strcmp(kind, ",") == 0) {
cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right");
int save = cs->freereg;
mach_compile_expr(cs, left, -1);
mach_free_reg_to(cs, save);
return mach_compile_expr(cs, right, dest);
}
/* Increment/Decrement as expression */
if (strcmp(kind, "++") == 0 || strcmp(kind, "--") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
MachOpcode inc_op = (kind[0] == '+') ? MACH_INC : MACH_DEC;
cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression");
cJSON *postfix_node = cJSON_GetObjectItemCaseSensitive(node, "postfix");
int is_postfix = postfix_node && cJSON_IsTrue(postfix_node);
const char *op_kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "kind"));
if (op_kind && strcmp(op_kind, "name") == 0) {
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "name"));
cJSON *level_node = cJSON_GetObjectItemCaseSensitive(operand, "level");
int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1;
if (level == 0 && name) {
int slot = mach_find_var(cs, name);
if (slot >= 0) {
if (is_postfix) {
/* Return old value, then increment */
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0));
mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0));
} else {
/* Increment, then return new value */
mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0));
if (dest != slot)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0));
}
return dest;
}
}
}
/* Fallback: just compile operand */
return mach_compile_expr(cs, operand, dest);
}
/* Compound assignment operators */
if (strcmp(kind, "+=") == 0 || strcmp(kind, "-=") == 0 ||
strcmp(kind, "*=") == 0 || strcmp(kind, "/=") == 0 ||
strcmp(kind, "%=") == 0 || strcmp(kind, "**=") == 0 ||
strcmp(kind, "&=") == 0 || strcmp(kind, "|=") == 0 ||
strcmp(kind, "^=") == 0 || strcmp(kind, "<<=") == 0 ||
strcmp(kind, ">>=") == 0 || strcmp(kind, ">>>=") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right");
/* Map compound op to binary op */
MachOpcode binop;
if (strcmp(kind, "+=") == 0) binop = MACH_ADD;
else if (strcmp(kind, "-=") == 0) binop = MACH_SUB;
else if (strcmp(kind, "*=") == 0) binop = MACH_MUL;
else if (strcmp(kind, "/=") == 0) binop = MACH_DIV;
else if (strcmp(kind, "%=") == 0) binop = MACH_MOD;
else if (strcmp(kind, "**=") == 0) binop = MACH_POW;
else if (strcmp(kind, "&=") == 0) binop = MACH_BAND;
else if (strcmp(kind, "|=") == 0) binop = MACH_BOR;
else if (strcmp(kind, "^=") == 0) binop = MACH_BXOR;
else if (strcmp(kind, "<<=") == 0) binop = MACH_SHL;
else if (strcmp(kind, ">>=") == 0) binop = MACH_SHR;
else binop = MACH_USHR; /* >>>= */
const char *lk = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "kind"));
if (lk && strcmp(lk, "name") == 0) {
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "name"));
cJSON *level_node = cJSON_GetObjectItemCaseSensitive(left, "level");
int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1;
if (level == 0 && name) {
int slot = mach_find_var(cs, name);
if (slot >= 0) {
int save = cs->freereg;
int rr = mach_compile_expr(cs, right, -1);
mach_emit(cs, MACH_ABC(binop, slot, slot, rr));
mach_free_reg_to(cs, save);
if (dest != slot)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0));
return dest;
}
}
}
/* Fallback: load null */
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
return dest;
}
/* In operator */
if (strcmp(kind, "in") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right");
int save = cs->freereg;
int lr = mach_compile_expr(cs, left, -1);
if (cs->freereg <= lr) cs->freereg = lr + 1;
int rr = mach_compile_expr(cs, right, -1);
mach_emit(cs, MACH_ABC(MACH_HASPROP, dest, rr, lr));
mach_free_reg_to(cs, save);
return dest;
}
/* Assignment */
if (strcmp(kind, "assign") == 0) {
cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right");
/* Push: arr[] = val */
cJSON *push_node = cJSON_GetObjectItemCaseSensitive(node, "push");
if (push_node && cJSON_IsTrue(push_node)) {
if (dest < 0) dest = mach_reserve_reg(cs);
int save = cs->freereg;
cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive(left, "left");
if (!arr_expr) arr_expr = cJSON_GetObjectItemCaseSensitive(left, "expression");
int arr_r = mach_compile_expr(cs, arr_expr, -1);
int val_r = mach_compile_expr(cs, right, -1);
mach_emit(cs, MACH_ABC(MACH_PUSH, arr_r, val_r, 0));
if (dest >= 0)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0));
mach_free_reg_to(cs, save);
return dest;
}
const char *lk = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "kind"));
if (lk && strcmp(lk, "name") == 0) {
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "name"));
cJSON *level_node = cJSON_GetObjectItemCaseSensitive(left, "level");
int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1;
if (level == 0) {
/* Local assignment */
int slot = name ? mach_find_var(cs, name) : -1;
if (slot >= 0) {
int r = mach_compile_expr(cs, right, slot);
if (r != slot)
mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0));
if (dest >= 0 && dest != slot)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0));
return slot;
}
} else if (level > 0) {
/* Closure assignment — walk parent states for slot */
MachCompState *target = cs;
for (int i = 0; i < level; i++) target = target->parent;
int slot = mach_find_var(target, name);
if (dest < 0) dest = mach_reserve_reg(cs);
int r = mach_compile_expr(cs, right, dest);
if (r != dest)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r, 0));
mach_emit(cs, MACH_ABC(MACH_SETUP, dest, level, slot));
return dest;
}
/* Unbound (level -1) — error, AST parser should have rejected this */
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
return dest;
}
/* Property assignment: left kind="." */
if (lk && strcmp(lk, ".") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
int save = cs->freereg;
cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(left, "expression");
if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(left, "left");
cJSON *prop = cJSON_GetObjectItemCaseSensitive(left, "name");
if (!prop) prop = cJSON_GetObjectItemCaseSensitive(left, "right");
const char *prop_name = NULL;
if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop);
else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value"));
if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name"));
if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "value"));
int obj_r = mach_compile_expr(cs, obj_expr, -1);
if (cs->freereg <= obj_r) cs->freereg = obj_r + 1;
int val_r = mach_compile_expr(cs, right, dest);
if (prop_name) {
int ki = mach_cpool_add_str(cs, prop_name);
mach_emit(cs, MACH_ABC(MACH_SETFIELD, obj_r, ki, val_r));
}
mach_free_reg_to(cs, save);
return val_r;
}
/* Computed property assignment: left kind="[" */
if (lk && strcmp(lk, "[") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
int save = cs->freereg;
cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(left, "expression");
if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(left, "left");
cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(left, "index");
if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(left, "right");
int obj_r = mach_compile_expr(cs, obj_expr, -1);
if (cs->freereg <= obj_r) cs->freereg = obj_r + 1;
int idx_r = mach_compile_expr(cs, idx_expr, -1);
if (cs->freereg <= idx_r) cs->freereg = idx_r + 1;
int val_r = mach_compile_expr(cs, right, dest);
mach_emit(cs, MACH_ABC(MACH_SETINDEX, obj_r, idx_r, val_r));
mach_free_reg_to(cs, save);
return val_r;
}
/* Fallback */
if (dest < 0) dest = mach_reserve_reg(cs);
mach_compile_expr(cs, right, dest);
return dest;
}
/* Property access: kind="." */
if (strcmp(kind, ".") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
int save = cs->freereg;
cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(node, "expression");
if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *prop = cJSON_GetObjectItemCaseSensitive(node, "name");
if (!prop) prop = cJSON_GetObjectItemCaseSensitive(node, "right");
const char *prop_name = NULL;
if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop);
else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value"));
if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name"));
if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value"));
int obj_r = mach_compile_expr(cs, obj_expr, -1);
if (prop_name) {
int ki = mach_cpool_add_str(cs, prop_name);
mach_emit(cs, MACH_ABC(MACH_GETFIELD, dest, obj_r, ki));
}
mach_free_reg_to(cs, save);
return dest;
}
/* Computed property access: kind="[" */
if (strcmp(kind, "[") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
int save = cs->freereg;
cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(node, "expression");
if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(node, "index");
if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(node, "right");
int obj_r = mach_compile_expr(cs, obj_expr, -1);
if (cs->freereg <= obj_r) cs->freereg = obj_r + 1;
int idx_r = mach_compile_expr(cs, idx_expr, -1);
mach_emit(cs, MACH_ABC(MACH_GETINDEX, dest, obj_r, idx_r));
mach_free_reg_to(cs, save);
return dest;
}
/* Object literal: kind="object" or "record" */
if (strcmp(kind, "object") == 0 || strcmp(kind, "record") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABC(MACH_NEWOBJECT, dest, 0, 0));
cJSON *props = cJSON_GetObjectItemCaseSensitive(node, "list");
if (props) {
int count = cJSON_GetArraySize(props);
for (int i = 0; i < count; i++) {
cJSON *prop = cJSON_GetArrayItem(props, i);
cJSON *key_node = cJSON_GetObjectItemCaseSensitive(prop, "key");
if (!key_node) key_node = cJSON_GetObjectItemCaseSensitive(prop, "left");
cJSON *val_node = cJSON_GetObjectItemCaseSensitive(prop, "value");
if (!val_node) val_node = cJSON_GetObjectItemCaseSensitive(prop, "right");
if (!val_node) val_node = cJSON_GetObjectItemCaseSensitive(prop, "expression");
const char *key = cJSON_GetStringValue(key_node);
if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(key_node, "value"));
if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(key_node, "name"));
if (key && val_node) {
int save = cs->freereg;
int vr = mach_compile_expr(cs, val_node, -1);
int ki = mach_cpool_add_str(cs, key);
mach_emit(cs, MACH_ABC(MACH_SETFIELD, dest, ki, vr));
mach_free_reg_to(cs, save);
}
}
}
return dest;
}
/* Array literal: kind="array" */
if (strcmp(kind, "array") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *elems = cJSON_GetObjectItemCaseSensitive(node, "list");
int count = elems ? cJSON_GetArraySize(elems) : 0;
/* Reserve consecutive regs for elements starting at arr_base+1.
If dest is below freereg, other temps occupy dest+1..freereg-1
so we must use a fresh base to avoid clobbering them. */
int save = cs->freereg;
int arr_base;
if (dest + 1 >= cs->freereg) {
arr_base = dest;
cs->freereg = dest + 1;
} else {
arr_base = mach_reserve_reg(cs);
}
for (int i = 0; i < count; i++) {
int er = mach_reserve_reg(cs);
cJSON *elem = cJSON_GetArrayItem(elems, i);
int r = mach_compile_expr(cs, elem, er);
if (r != er) mach_emit(cs, MACH_ABC(MACH_MOVE, er, r, 0));
}
mach_emit(cs, MACH_ABC(MACH_NEWARRAY, arr_base, count, 0));
if (arr_base != dest)
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, arr_base, 0));
mach_free_reg_to(cs, save);
return dest;
}
/* Ternary: kind="?" or "then" */
if (strcmp(kind, "?") == 0 || strcmp(kind, "then") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *cond = cJSON_GetObjectItemCaseSensitive(node, "expression");
if (!cond) cond = cJSON_GetObjectItemCaseSensitive(node, "condition");
cJSON *then_expr = cJSON_GetObjectItemCaseSensitive(node, "then");
if (!then_expr) then_expr = cJSON_GetObjectItemCaseSensitive(node, "left");
cJSON *else_expr = cJSON_GetObjectItemCaseSensitive(node, "else");
if (!else_expr) else_expr = cJSON_GetObjectItemCaseSensitive(node, "right");
int save = cs->freereg;
int cr = mach_compile_expr(cs, cond, -1);
int jmpfalse_pc = mach_current_pc(cs);
mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0));
mach_free_reg_to(cs, save);
mach_compile_expr(cs, then_expr, dest);
int jmpend_pc = mach_current_pc(cs);
mach_emit(cs, MACH_sJ(MACH_JMP, 0));
/* Patch jmpfalse */
int offset = mach_current_pc(cs) - (jmpfalse_pc + 1);
cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset);
mach_compile_expr(cs, else_expr, dest);
/* Patch jmpend */
offset = mach_current_pc(cs) - (jmpend_pc + 1);
cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset);
return dest;
}
/* Function literal */
if (strcmp(kind, "function") == 0 || strcmp(kind, "=>") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
/* Compile nested function */
MachCompState child = {0};
child.parent = cs;
child.scopes = cs->scopes;
child.filename = cs->filename;
child.freereg = 1; /* slot 0 = this */
/* Read function_nr from AST node */
cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive(node, "function_nr");
child.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue(fn_nr_node) : 0;
/* Register parameters */
cJSON *params = cJSON_GetObjectItemCaseSensitive(node, "params");
if (!params) params = cJSON_GetObjectItemCaseSensitive(node, "parameters");
if (!params) params = cJSON_GetObjectItemCaseSensitive(node, "list");
int nparams = params ? cJSON_GetArraySize(params) : 0;
child.nr_args = nparams;
for (int i = 0; i < nparams; i++) {
cJSON *p = cJSON_GetArrayItem(params, i);
const char *pname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(p, "name"));
if (!pname) pname = cJSON_GetStringValue(p);
if (pname) {
int slot = mach_reserve_reg(&child);
mach_add_var(&child, pname, slot, 1);
}
}
/* Scan scope record for var/def declarations */
mach_scan_scope(&child);
/* Emit default parameter initialization */
for (int i = 0; i < nparams; i++) {
cJSON *p = cJSON_GetArrayItem(params, i);
cJSON *default_expr = cJSON_GetObjectItemCaseSensitive(p, "expression");
if (default_expr) {
int slot = 1 + i; /* param slots start at 1 (slot 0 = this) */
/* If param is null, skip the JMP and fall into default code */
mach_emit(&child, MACH_AsBx(MACH_JMPNULL, slot, 1));
/* If param is NOT null, jump past the default code */
int jmp_pc = mach_current_pc(&child);
mach_emit(&child, MACH_sJ(MACH_JMP, 0)); /* placeholder */
int save = child.freereg;
mach_compile_expr(&child, default_expr, slot);
child.freereg = save;
/* Patch JMP offset */
int offset = mach_current_pc(&child) - (jmp_pc + 1);
child.code[jmp_pc] = MACH_sJ(MACH_JMP, offset);
}
}
/* Compile body */
cJSON *body = cJSON_GetObjectItemCaseSensitive(node, "body");
if (!body) body = node; /* statements may be directly on the function node */
{
cJSON *stmts = cJSON_GetObjectItemCaseSensitive(body, "statements");
if (!stmts) stmts = body; /* body might be the statements array directly */
if (cJSON_IsArray(stmts)) {
int count = cJSON_GetArraySize(stmts);
for (int i = 0; i < count; i++) {
mach_compile_stmt(&child, cJSON_GetArrayItem(stmts, i));
}
}
}
/* Implicit return null */
mach_emit(&child, MACH_ABC(MACH_RETNIL, 0, 0, 0));
/* Disruption clause — emitted after body, recorded as disruption_pc */
int disruption_start = 0;
cJSON *disruption = cJSON_GetObjectItemCaseSensitive(node, "disruption");
if (disruption && cJSON_IsArray(disruption)) {
disruption_start = mach_current_pc(&child);
int dcount = cJSON_GetArraySize(disruption);
for (int i = 0; i < dcount; i++)
mach_compile_stmt(&child, cJSON_GetArrayItem(disruption, i));
mach_emit(&child, MACH_ABC(MACH_RETNIL, 0, 0, 0));
}
/* Build MachCode for the child function */
cJSON *fn_scope = mach_find_scope_record(cs->scopes, child.function_nr);
cJSON *fn_ncs = fn_scope ? cJSON_GetObjectItemCaseSensitive(fn_scope, "nr_close_slots") : NULL;
MachCode *fn_code = sys_malloc(sizeof(MachCode));
memset(fn_code, 0, sizeof(MachCode));
fn_code->arity = nparams;
fn_code->nr_slots = child.maxreg;
fn_code->nr_close_slots = fn_ncs ? (int)cJSON_GetNumberValue(fn_ncs) : 0;
fn_code->entry_point = 0;
fn_code->instr_count = child.code_count;
fn_code->instructions = child.code;
fn_code->cpool_count = child.cpool_count;
fn_code->cpool = child.cpool;
fn_code->func_count = child.func_count;
fn_code->functions = child.functions;
fn_code->line_table = child.line_info;
fn_code->filename = cs->filename ? strdup(cs->filename) : NULL;
fn_code->disruption_pc = disruption_start;
cJSON *fname = cJSON_GetObjectItemCaseSensitive(node, "name");
if (fname && cJSON_IsString(fname)) {
const char *ns = cJSON_GetStringValue(fname);
fn_code->name = sys_malloc(strlen(ns) + 1);
strcpy(fn_code->name, ns);
} else {
fn_code->name = NULL;
}
/* Free child var table (not code/cpool, those are owned by fn_code now) */
for (int i = 0; i < child.var_count; i++)
sys_free(child.vars[i].name);
sys_free(child.vars);
int fi = mach_add_function(cs, fn_code);
mach_emit(cs, MACH_ABx(MACH_CLOSURE, dest, fi));
return dest;
}
/* Delete operator */
if (strcmp(kind, "delete") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression");
if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right");
if (operand) {
const char *okind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "kind"));
if (okind && strcmp(okind, ".") == 0) {
/* delete obj.prop */
cJSON *obj_node = cJSON_GetObjectItemCaseSensitive(operand, "left");
cJSON *prop_node = cJSON_GetObjectItemCaseSensitive(operand, "right");
const char *pname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop_node, "name"));
if (!pname) pname = cJSON_GetStringValue(prop_node);
int save = cs->freereg;
int objr = mach_compile_expr(cs, obj_node, -1);
int ki = mach_cpool_add_str(cs, pname);
mach_emit(cs, MACH_ABC(MACH_DELETE, dest, objr, ki));
mach_free_reg_to(cs, save);
return dest;
} else if (okind && strcmp(okind, "[") == 0) {
/* delete obj[expr] */
cJSON *obj_node = cJSON_GetObjectItemCaseSensitive(operand, "left");
cJSON *idx_node = cJSON_GetObjectItemCaseSensitive(operand, "right");
int save = cs->freereg;
int objr = mach_compile_expr(cs, obj_node, -1);
int ir = mach_compile_expr(cs, idx_node, -1);
mach_emit(cs, MACH_ABC(MACH_DELETEINDEX, dest, objr, ir));
mach_free_reg_to(cs, save);
return dest;
}
}
mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0));
return dest;
}
/* This reference — slot 0 is always 'this' */
if (strcmp(kind, "this") == 0) {
if (dest >= 0 && dest != 0) {
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, 0, 0));
return dest;
}
return 0;
}
/* Regex literal */
if (strcmp(kind, "regexp") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
const char *pattern = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "pattern"));
const char *flags = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "flags"));
if (!pattern) pattern = "";
if (!flags) flags = "";
int pi = mach_cpool_add_str(cs, pattern);
int fi = mach_cpool_add_str(cs, flags);
mach_emit(cs, MACH_ABC(MACH_REGEXP, dest, pi, fi));
return dest;
}
/* Fallback: unsupported expression kind — load null */
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
return dest;
}
/* ---- Statement compiler ---- */
static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
if (!stmt) return;
mach_set_pos(cs, stmt);
const char *kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "kind"));
if (!kind) return;
/* var / def declaration */
if (strcmp(kind, "var") == 0 || strcmp(kind, "def") == 0) {
cJSON *left = cJSON_GetObjectItemCaseSensitive(stmt, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive(stmt, "right");
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "name"));
if (!name) return;
/* Check if var exists at current scope depth — if so, reuse it.
If it exists at a shallower depth, shadow it with a new slot. */
int slot = -1;
for (int i = cs->var_count - 1; i >= 0; i--) {
if (strcmp(cs->vars[i].name, name) == 0) {
if (cs->vars[i].scope_depth == cs->scope_depth) {
slot = cs->vars[i].slot; /* same scope — reuse */
}
break;
}
}
if (slot < 0) {
slot = mach_reserve_reg(cs);
mach_add_var(cs, name, slot, strcmp(kind, "def") == 0);
}
/* Pop: var x = arr[] */
cJSON *pop_node = cJSON_GetObjectItemCaseSensitive(stmt, "pop");
if (pop_node && cJSON_IsTrue(pop_node) && right) {
cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive(right, "left");
if (!arr_expr) arr_expr = cJSON_GetObjectItemCaseSensitive(right, "expression");
int save = cs->freereg;
int arr_r = mach_compile_expr(cs, arr_expr, -1);
mach_emit(cs, MACH_ABC(MACH_POP, slot, arr_r, 0));
mach_free_reg_to(cs, save);
return;
}
if (right) {
int r = mach_compile_expr(cs, right, slot);
if (r != slot)
mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0));
}
return;
}
/* var_list: multiple declarations in one statement */
if (strcmp(kind, "var_list") == 0) {
cJSON *list = cJSON_GetObjectItemCaseSensitive(stmt, "list");
if (list && cJSON_IsArray(list)) {
int count = cJSON_GetArraySize(list);
for (int i = 0; i < count; i++)
mach_compile_stmt(cs, cJSON_GetArrayItem(list, i));
}
return;
}
/* Function declaration statement */
if (strcmp(kind, "function") == 0) {
const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name"));
if (!name) return;
int slot = mach_find_var(cs, name);
if (slot < 0) {
slot = mach_reserve_reg(cs);
mach_add_var(cs, name, slot, 1);
}
mach_compile_expr(cs, stmt, slot);
return;
}
/* Expression statement (call) */
if (strcmp(kind, "call") == 0) {
cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression");
if (expr) {
int save = cs->freereg;
mach_compile_expr(cs, expr, -1);
mach_free_reg_to(cs, save);
}
return;
}
/* Return statement */
if (strcmp(kind, "return") == 0) {
cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression");
if (expr) {
int save = cs->freereg;
int r = mach_compile_expr(cs, expr, -1);
mach_emit(cs, MACH_ABC(MACH_RETURN, r, 0, 0));
mach_free_reg_to(cs, save);
} else {
mach_emit(cs, MACH_ABC(MACH_RETNIL, 0, 0, 0));
}
return;
}
/* Block */
if (strcmp(kind, "block") == 0) {
int saved_var_count = cs->var_count;
cs->scope_depth++;
cJSON *stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements");
if (stmts && cJSON_IsArray(stmts)) {
int count = cJSON_GetArraySize(stmts);
for (int i = 0; i < count; i++) {
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
}
}
cs->scope_depth--;
for (int i = saved_var_count; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_var_count;
return;
}
/* If statement */
if (strcmp(kind, "if") == 0) {
cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression");
if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition");
cJSON *then_body = cJSON_GetObjectItemCaseSensitive(stmt, "then");
if (!then_body) then_body = cJSON_GetObjectItemCaseSensitive(stmt, "block");
cJSON *else_body = cJSON_GetObjectItemCaseSensitive(stmt, "else");
int save = cs->freereg;
int cr = mach_compile_expr(cs, cond, -1);
int jmpfalse_pc = mach_current_pc(cs);
mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0));
mach_free_reg_to(cs, save);
/* Compile then branch — "then" is a direct array of statements */
if (then_body) {
int saved_vc = cs->var_count;
cs->scope_depth++;
if (cJSON_IsArray(then_body)) {
int count = cJSON_GetArraySize(then_body);
for (int i = 0; i < count; i++)
mach_compile_stmt(cs, cJSON_GetArrayItem(then_body, i));
} else {
cJSON *stmts = cJSON_GetObjectItemCaseSensitive(then_body, "statements");
if (stmts && cJSON_IsArray(stmts)) {
int count = cJSON_GetArraySize(stmts);
for (int i = 0; i < count; i++)
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
} else {
mach_compile_stmt(cs, then_body);
}
}
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
}
/* Check for else-if chain ("list") or plain else */
if (!else_body) {
cJSON *list = cJSON_GetObjectItemCaseSensitive(stmt, "list");
if (list && cJSON_IsArray(list) && cJSON_GetArraySize(list) > 0)
else_body = cJSON_GetArrayItem(list, 0);
}
if (else_body) {
int jmpend_pc = mach_current_pc(cs);
mach_emit(cs, MACH_sJ(MACH_JMP, 0));
/* Patch jmpfalse to else */
int offset = mach_current_pc(cs) - (jmpfalse_pc + 1);
cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset);
/* Compile else — could be a direct array, object, or else-if stmt */
int saved_vc = cs->var_count;
cs->scope_depth++;
if (cJSON_IsArray(else_body)) {
int count = cJSON_GetArraySize(else_body);
for (int i = 0; i < count; i++)
mach_compile_stmt(cs, cJSON_GetArrayItem(else_body, i));
} else {
cJSON *stmts = cJSON_GetObjectItemCaseSensitive(else_body, "statements");
if (stmts && cJSON_IsArray(stmts)) {
int count = cJSON_GetArraySize(stmts);
for (int i = 0; i < count; i++)
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
} else {
mach_compile_stmt(cs, else_body);
}
}
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
/* Patch jmpend */
offset = mach_current_pc(cs) - (jmpend_pc + 1);
cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset);
} else {
/* No else — patch jmpfalse to after then */
int offset = mach_current_pc(cs) - (jmpfalse_pc + 1);
cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset);
}
return;
}
/* While loop */
if (strcmp(kind, "while") == 0) {
cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression");
if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition");
int old_break = cs->loop_break;
int old_continue = cs->loop_continue;
cs->loop_break = -1;
cs->loop_continue = -1;
int loop_top = mach_current_pc(cs);
int save = cs->freereg;
int cr = mach_compile_expr(cs, cond, -1);
int jmpfalse_pc = mach_current_pc(cs);
mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0));
mach_free_reg_to(cs, save);
/* Compile body — "statements" on a child "block"/"body", or directly on the node */
{
int saved_vc = cs->var_count;
cs->scope_depth++;
cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block");
if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body");
cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL;
if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements");
if (stmts && cJSON_IsArray(stmts)) {
int count = cJSON_GetArraySize(stmts);
for (int i = 0; i < count; i++)
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
} else if (body) {
mach_compile_stmt(cs, body);
}
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
}
/* Patch continue chain to loop_top */
{
int cp = cs->loop_continue;
while (cp >= 0) {
int prev = MACH_GET_sJ(cs->code[cp]);
int off = loop_top - (cp + 1);
cs->code[cp] = MACH_sJ(MACH_JMP, off);
cp = prev;
}
}
/* Jump back to loop top */
int offset = loop_top - (mach_current_pc(cs) + 1);
mach_emit(cs, MACH_sJ(MACH_JMP, offset));
/* Patch jmpfalse to after loop */
offset = mach_current_pc(cs) - (jmpfalse_pc + 1);
cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset);
/* Patch break chain */
int bp = cs->loop_break;
while (bp >= 0) {
int prev = MACH_GET_sJ(cs->code[bp]);
offset = mach_current_pc(cs) - (bp + 1);
cs->code[bp] = MACH_sJ(MACH_JMP, offset);
bp = prev;
}
cs->loop_break = old_break;
cs->loop_continue = old_continue;
return;
}
/* For loop */
if (strcmp(kind, "for") == 0) {
int saved_vc = cs->var_count;
cs->scope_depth++;
cJSON *init = cJSON_GetObjectItemCaseSensitive(stmt, "init");
cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "test");
cJSON *update = cJSON_GetObjectItemCaseSensitive(stmt, "update");
cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block");
if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body");
int old_break = cs->loop_break;
int old_continue = cs->loop_continue;
cs->loop_break = -1;
cs->loop_continue = -1;
/* Init */
if (init) mach_compile_stmt(cs, init);
int loop_top = mach_current_pc(cs);
/* Condition */
int jmpfalse_pc = -1;
if (cond) {
int save = cs->freereg;
int cr = mach_compile_expr(cs, cond, -1);
jmpfalse_pc = mach_current_pc(cs);
mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0));
mach_free_reg_to(cs, save);
}
/* Body — "statements" on a child "block"/"body", or directly on the for node */
{
int body_vc = cs->var_count;
cs->scope_depth++;
cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL;
if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements");
if (stmts && cJSON_IsArray(stmts)) {
int count = cJSON_GetArraySize(stmts);
for (int i = 0; i < count; i++)
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
} else if (body) {
mach_compile_stmt(cs, body);
}
cs->scope_depth--;
for (int i = body_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = body_vc;
}
/* Patch continue chain to update (or loop_top if no update) */
{
int continue_target = mach_current_pc(cs);
int cp = cs->loop_continue;
while (cp >= 0) {
int prev = MACH_GET_sJ(cs->code[cp]);
int off = continue_target - (cp + 1);
cs->code[cp] = MACH_sJ(MACH_JMP, off);
cp = prev;
}
}
/* Update — assignment expressions must be compiled as statements */
if (update) {
mach_compile_stmt(cs, update);
}
/* Jump back */
int offset = loop_top - (mach_current_pc(cs) + 1);
mach_emit(cs, MACH_sJ(MACH_JMP, offset));
/* Patch condition exit */
if (jmpfalse_pc >= 0) {
offset = mach_current_pc(cs) - (jmpfalse_pc + 1);
/* Need to recover the register used for condition - use A from the instruction */
int cr = MACH_GET_A(cs->code[jmpfalse_pc]);
cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset);
}
/* Patch break chain */
int bp = cs->loop_break;
while (bp >= 0) {
int prev = MACH_GET_sJ(cs->code[bp]);
offset = mach_current_pc(cs) - (bp + 1);
cs->code[bp] = MACH_sJ(MACH_JMP, offset);
bp = prev;
}
cs->loop_break = old_break;
cs->loop_continue = old_continue;
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
return;
}
/* Break */
if (strcmp(kind, "break") == 0) {
int pc = mach_current_pc(cs);
mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_break));
cs->loop_break = pc;
return;
}
/* Continue */
if (strcmp(kind, "continue") == 0) {
int pc = mach_current_pc(cs);
mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_continue));
cs->loop_continue = pc;
return;
}
/* Assignment as statement */
if (strcmp(kind, "assign") == 0 ||
strcmp(kind, "+=") == 0 || strcmp(kind, "-=") == 0 ||
strcmp(kind, "*=") == 0 || strcmp(kind, "/=") == 0 ||
strcmp(kind, "%=") == 0 || strcmp(kind, "**=") == 0 ||
strcmp(kind, "&=") == 0 || strcmp(kind, "|=") == 0 ||
strcmp(kind, "^=") == 0 || strcmp(kind, "<<=") == 0 ||
strcmp(kind, ">>=") == 0 || strcmp(kind, ">>>=") == 0 ||
strcmp(kind, "++") == 0 || strcmp(kind, "--") == 0) {
int save = cs->freereg;
mach_compile_expr(cs, stmt, -1);
mach_free_reg_to(cs, save);
return;
}
/* Disrupt statement */
if (strcmp(kind, "disrupt") == 0) {
mach_emit(cs, MACH_ABC(MACH_THROW, 0, 0, 0));
return;
}
/* Fallback: treat as expression statement */
{
cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression");
if (expr) {
int save = cs->freereg;
mach_compile_expr(cs, expr, -1);
mach_free_reg_to(cs, save);
}
}
}
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
for (uint32_t i = 0; i < code->instr_count; i++) {
MachInstr32 instr = code->instructions[i];
if (MACH_GET_OP(instr) != MACH_GETNAME) continue;
int a = MACH_GET_A(instr);
int bx = MACH_GET_Bx(instr);
int in_env = 0;
if (!JS_IsNull(env) && (uint32_t)bx < code->cpool_count) {
JSValue val = JS_GetProperty(ctx, env, code->cpool[bx]);
in_env = !JS_IsNull(val) && !JS_IsException(val);
}
code->instructions[i] = MACH_ABx(in_env ? MACH_GETENV : MACH_GETINTRINSIC, a, bx);
}
for (uint32_t i = 0; i < code->func_count; i++)
if (code->functions[i]) mach_link_code(ctx, code->functions[i], env);
}
/* ---- Top-level compiler ---- */
static MachCode *mach_compile_program(MachCompState *cs, cJSON *ast) {
cJSON *stmts = cJSON_GetObjectItemCaseSensitive(ast, "statements");
if (!stmts || !cJSON_IsArray(stmts)) return NULL;
/* Read scopes array from AST */
cs->scopes = cJSON_GetObjectItemCaseSensitive(ast, "scopes");
cs->function_nr = 0;
/* Extract filename for debug info */
cs->filename = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(ast, "filename"));
/* Scan scope record for declarations */
mach_scan_scope(cs);
/* Hoist function declarations */
cJSON *functions = cJSON_GetObjectItemCaseSensitive(ast, "functions");
if (functions && cJSON_IsArray(functions)) {
int fcount = cJSON_GetArraySize(functions);
for (int i = 0; i < fcount; i++) {
cJSON *fn_node = cJSON_GetArrayItem(functions, i);
const char *fn_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_node, "name"));
if (!fn_name) continue;
int slot = mach_find_var(cs, fn_name);
if (slot < 0) continue;
mach_compile_expr(cs, fn_node, slot);
}
}
/* Compile each statement */
int count = cJSON_GetArraySize(stmts);
for (int i = 0; i < count; i++) {
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
}
/* Implicit return null */
mach_emit(cs, MACH_ABC(MACH_RETNIL, 0, 0, 0));
/* nr_close_slots from scope record */
cJSON *prog_scope = mach_find_scope_record(cs->scopes, 0);
cJSON *ncs_node = prog_scope ? cJSON_GetObjectItemCaseSensitive(prog_scope, "nr_close_slots") : NULL;
/* Build MachCode */
MachCode *code = sys_malloc(sizeof(MachCode));
memset(code, 0, sizeof(MachCode));
code->arity = 0;
code->nr_slots = cs->maxreg;
code->nr_close_slots = ncs_node ? (int)cJSON_GetNumberValue(ncs_node) : 0;
code->entry_point = 0;
code->instr_count = cs->code_count;
code->instructions = cs->code;
code->cpool_count = cs->cpool_count;
code->cpool = cs->cpool;
code->func_count = cs->func_count;
code->functions = cs->functions;
code->name = NULL;
code->line_table = cs->line_info;
code->filename = cs->filename ? strdup(cs->filename) : NULL;
return code;
}
/* Public API: compile AST JSON to MachCode (context-free) */
MachCode *JS_CompileMachTree(cJSON *ast) {
if (!ast) return NULL;
MachCompState cs = {0};
cs.freereg = 1; /* slot 0 = this */
cs.maxreg = 1;
MachCode *code = mach_compile_program(&cs, ast);
/* Free var table (code/cpool/functions are owned by MachCode now) */
for (int i = 0; i < cs.var_count; i++)
sys_free(cs.vars[i].name);
sys_free(cs.vars);
return code;
}
MachCode *JS_CompileMach(const char *ast_json) {
cJSON *ast = cJSON_Parse(ast_json);
if (!ast) return NULL;
MachCode *code = JS_CompileMachTree(ast);
cJSON_Delete(ast);
return code;
}
/* Free a MachCode tree (compiled but not yet loaded) */
void JS_FreeMachCode(MachCode *mc) {
if (!mc) return;
sys_free(mc->instructions);
for (uint32_t i = 0; i < mc->cpool_count; i++) {
if (mc->cpool[i].type == MACH_CP_STR)
sys_free(mc->cpool[i].str);
}
sys_free(mc->cpool);
for (uint32_t i = 0; i < mc->func_count; i++)
JS_FreeMachCode(mc->functions[i]);
sys_free(mc->functions);
sys_free(mc->name);
sys_free(mc->line_table);
sys_free(mc->filename);
sys_free(mc);
}
/* Load a MachCode into a JSCodeRegister (materializes JSValues, needs ctx) */
JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister));
code->arity = mc->arity;
code->nr_close_slots = mc->nr_close_slots;
code->nr_slots = mc->nr_slots;
code->entry_point = mc->entry_point;
code->instr_count = mc->instr_count;
code->instructions = mc->instructions; /* transfer ownership */
/* Materialize cpool: raw -> JSValue */
code->cpool_count = mc->cpool_count;
code->cpool = mach_materialize_cpool(ctx, mc->cpool, mc->cpool_count);
/* Recursively load nested functions */
code->func_count = mc->func_count;
if (mc->func_count > 0) {
code->functions = js_malloc_rt(mc->func_count * sizeof(JSCodeRegister *));
for (uint32_t i = 0; i < mc->func_count; i++)
code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env);
} else {
code->functions = NULL;
}
/* Intern function name */
code->name = mc->name ? js_key_new(ctx, mc->name) : JS_NULL;
/* Transfer debug info */
code->line_table = mc->line_table;
mc->line_table = NULL;
code->filename_cstr = mc->filename ? js_strdup_rt(mc->filename) : NULL;
code->name_cstr = mc->name ? js_strdup_rt(mc->name) : NULL;
code->disruption_pc = mc->disruption_pc;
/* Link: resolve GETNAME to GETENV/GETINTRINSIC */
mach_link_code(ctx, code, env);
return code;
}
/* Free a JSCodeRegister and all nested functions */
static void js_free_code_register(JSCodeRegister *code) {
if (!code) return;
js_free_rt(code->instructions);
js_free_rt(code->cpool);
for (uint32_t i = 0; i < code->func_count; i++) {
js_free_code_register(code->functions[i]);
}
js_free_rt(code->functions);
js_free_rt(code->line_table);
js_free_rt(code->filename_cstr);
js_free_rt(code->name_cstr);
js_free_rt(code);
}
/* ============================================================
MACH VM — register-based bytecode interpreter
============================================================ */
/* Allocate a JSFrameRegister on the GC heap */
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count) {
size_t size = sizeof(JSFrameRegister) + slot_count * sizeof(JSValue);
JSFrameRegister *frame = js_mallocz(ctx, size);
if (!frame) return NULL;
/* cap56 = slot count (used by gc_object_size) */
frame->hdr = objhdr_make(slot_count, OBJ_FRAME, 0, 0, 0, 0);
frame->function = JS_NULL;
frame->caller = JS_NULL;
frame->address = JS_NewInt32(ctx, 0);
/* Initialize slots to null */
for (int i = 0; i < slot_count; i++) {
frame->slots[i] = JS_NULL;
}
return frame;
}
/* Create a register-based function from JSCodeRegister */
JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue env, JSValue outer_frame) {
/* Protect env and outer_frame from GC — js_mallocz can trigger
collection which moves heap objects, invalidating stack-local copies */
JSGCRef env_ref, frame_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JS_PushGCRef(ctx, &frame_ref);
frame_ref.val = outer_frame;
JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction));
if (!fn) {
JS_PopGCRef(ctx, &frame_ref);
JS_PopGCRef(ctx, &env_ref);
return JS_EXCEPTION;
}
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
fn->kind = JS_FUNC_KIND_REGISTER;
fn->length = code->arity;
fn->name = code->name;
fn->u.reg.code = code;
fn->u.reg.env_record = env_ref.val;
fn->u.reg.outer_frame = frame_ref.val;
JS_PopGCRef(ctx, &frame_ref);
JS_PopGCRef(ctx, &env_ref);
return JS_MKPTR(fn);
}
/* Binary operations helper */
static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
/* Fast path for integers */
if (JS_VALUE_IS_BOTH_INT(a, b)) {
int32_t ia = JS_VALUE_GET_INT(a);
int32_t ib = JS_VALUE_GET_INT(b);
switch (op) {
case MACH_ADD: {
int64_t r = (int64_t)ia + (int64_t)ib;
if (r >= INT32_MIN && r <= INT32_MAX)
return JS_NewInt32(ctx, (int32_t)r);
return JS_NewFloat64(ctx, (double)r);
}
case MACH_SUB: {
int64_t r = (int64_t)ia - (int64_t)ib;
if (r >= INT32_MIN && r <= INT32_MAX)
return JS_NewInt32(ctx, (int32_t)r);
return JS_NewFloat64(ctx, (double)r);
}
case MACH_MUL: {
int64_t r = (int64_t)ia * (int64_t)ib;
if (r >= INT32_MIN && r <= INT32_MAX)
return JS_NewInt32(ctx, (int32_t)r);
return JS_NewFloat64(ctx, (double)r);
}
case MACH_DIV:
if (ib == 0) return JS_NULL;
if (ia % ib == 0) return JS_NewInt32(ctx, ia / ib);
return JS_NewFloat64(ctx, (double)ia / (double)ib);
case MACH_MOD:
if (ib == 0) return JS_NULL;
return JS_NewInt32(ctx, ia % ib);
case MACH_EQ:
return JS_NewBool(ctx, ia == ib);
case MACH_NEQ:
return JS_NewBool(ctx, ia != ib);
case MACH_LT:
return JS_NewBool(ctx, ia < ib);
case MACH_LE:
return JS_NewBool(ctx, ia <= ib);
case MACH_GT:
return JS_NewBool(ctx, ia > ib);
case MACH_GE:
return JS_NewBool(ctx, ia >= ib);
case MACH_BAND:
return JS_NewInt32(ctx, ia & ib);
case MACH_BOR:
return JS_NewInt32(ctx, ia | ib);
case MACH_BXOR:
return JS_NewInt32(ctx, ia ^ ib);
case MACH_SHL:
return JS_NewInt32(ctx, ia << (ib & 31));
case MACH_SHR:
return JS_NewInt32(ctx, ia >> (ib & 31));
case MACH_USHR:
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
default:
break;
}
}
/* String concat for ADD */
if (op == MACH_ADD && JS_IsText(a) && JS_IsText(b))
return JS_ConcatString(ctx, a, b);
/* Comparison ops allow mixed types — return false for mismatches */
if (op >= MACH_EQ && op <= MACH_GE) {
/* Fast path: identical values (chase pointers for forwarded objects) */
{
JSValue ca = JS_IsPtr(a) ? JS_MKPTR(chase(a)) : a;
JSValue cb = JS_IsPtr(b) ? JS_MKPTR(chase(b)) : b;
if (ca == cb) {
if (op == MACH_EQ || op == MACH_LE || op == MACH_GE) return JS_TRUE;
if (op == MACH_NEQ) return JS_FALSE;
}
}
if (JS_IsNumber(a) && JS_IsNumber(b)) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
switch (op) {
case MACH_EQ: return JS_NewBool(ctx, da == db);
case MACH_NEQ: return JS_NewBool(ctx, da != db);
case MACH_LT: return JS_NewBool(ctx, da < db);
case MACH_LE: return JS_NewBool(ctx, da <= db);
case MACH_GT: return JS_NewBool(ctx, da > db);
case MACH_GE: return JS_NewBool(ctx, da >= db);
default: break;
}
}
/* String comparisons */
if (JS_IsText(a) && JS_IsText(b)) {
int cmp = js_string_compare_value(ctx, a, b, FALSE);
switch (op) {
case MACH_EQ: return JS_NewBool(ctx, cmp == 0);
case MACH_NEQ: return JS_NewBool(ctx, cmp != 0);
case MACH_LT: return JS_NewBool(ctx, cmp < 0);
case MACH_LE: return JS_NewBool(ctx, cmp <= 0);
case MACH_GT: return JS_NewBool(ctx, cmp > 0);
case MACH_GE: return JS_NewBool(ctx, cmp >= 0);
default: break;
}
}
/* Null comparisons */
if (JS_IsNull(a) && JS_IsNull(b)) {
if (op == MACH_EQ || op == MACH_LE || op == MACH_GE)
return JS_TRUE;
return JS_FALSE;
}
/* Boolean comparisons */
if (JS_IsBool(a) && JS_IsBool(b)) {
int ba = JS_VALUE_GET_BOOL(a);
int bb = JS_VALUE_GET_BOOL(b);
switch (op) {
case MACH_EQ: return JS_NewBool(ctx, ba == bb);
case MACH_NEQ: return JS_NewBool(ctx, ba != bb);
case MACH_LT: return JS_NewBool(ctx, ba < bb);
case MACH_LE: return JS_NewBool(ctx, ba <= bb);
case MACH_GT: return JS_NewBool(ctx, ba > bb);
case MACH_GE: return JS_NewBool(ctx, ba >= bb);
default: break;
}
}
/* Different types: EQ→false, NEQ→true, others→false */
if (op == MACH_NEQ) return JS_NewBool(ctx, 1);
return JS_NewBool(ctx, 0);
}
/* Numeric operations — both must be numeric */
if (JS_IsNumber(a) && JS_IsNumber(b)) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
switch (op) {
case MACH_ADD: {
double r = da + db;
if (!isfinite(r)) return JS_NULL;
return JS_NewFloat64(ctx, r);
}
case MACH_SUB: {
double r = da - db;
if (!isfinite(r)) return JS_NULL;
return JS_NewFloat64(ctx, r);
}
case MACH_MUL: {
double r = da * db;
if (!isfinite(r)) return JS_NULL;
return JS_NewFloat64(ctx, r);
}
case MACH_DIV: {
if (db == 0.0) return JS_NULL;
double r = da / db;
if (!isfinite(r)) return JS_NULL;
return JS_NewFloat64(ctx, r);
}
case MACH_MOD: {
if (db == 0.0) return JS_NULL;
double r = fmod(da, db);
if (!isfinite(r)) return JS_NULL;
return JS_NewFloat64(ctx, r);
}
case MACH_POW: {
double r = pow(da, db);
if (!isfinite(r) && isfinite(da) && isfinite(db)) return JS_NULL;
return JS_NewFloat64(ctx, r);
}
case MACH_BAND: case MACH_BOR: case MACH_BXOR:
case MACH_SHL: case MACH_SHR: case MACH_USHR: {
int32_t ia = (int32_t)da;
int32_t ib = (int32_t)db;
switch (op) {
case MACH_BAND: return JS_NewInt32(ctx, ia & ib);
case MACH_BOR: return JS_NewInt32(ctx, ia | ib);
case MACH_BXOR: return JS_NewInt32(ctx, ia ^ ib);
case MACH_SHL: return JS_NewInt32(ctx, ia << (ib & 31));
case MACH_SHR: return JS_NewInt32(ctx, ia >> (ib & 31));
case MACH_USHR: return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
default: break;
}
}
default: break;
}
}
/* Type mismatch — disrupt */
return JS_EXCEPTION;
}
/* Check for interrupt */
int reg_vm_check_interrupt(JSContext *ctx) {
if (--ctx->interrupt_counter <= 0) {
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
if (ctx->interrupt_handler) {
if (ctx->interrupt_handler(ctx->rt, ctx->interrupt_opaque)) {
return -1;
}
}
}
return 0;
}
#ifdef HAVE_ASAN
void __asan_on_error(void) {
JSContext *ctx = __asan_js_ctx;
if (!ctx) return;
if (JS_IsNull(ctx->reg_current_frame)) return;
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
uint32_t cur_pc = ctx->current_register_pc;
fprintf(stderr, "\n=== ASAN error: VM stack trace ===\n");
int is_first = 1;
while (frame) {
if (!JS_IsFunction(frame->function)) break;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
const char *func_name = NULL;
const char *file = NULL;
uint16_t line = 0;
uint32_t pc = is_first ? cur_pc : 0;
if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) {
JSCodeRegister *code = fn->u.reg.code;
file = code->filename_cstr;
func_name = code->name_cstr;
if (!is_first)
pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
if (code->line_table && pc < code->instr_count)
line = code->line_table[pc].line;
} else if (fn->kind == JS_FUNC_KIND_MCODE && fn->u.mcode.code) {
JSMCode *code = fn->u.mcode.code;
file = code->filename;
func_name = code->name;
if (!is_first)
pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
if (code->line_table && pc < code->instr_count)
line = code->line_table[pc].line;
}
fprintf(stderr, " %s (%s:%u)\n",
func_name ? func_name : "<anonymous>",
file ? file : "<unknown>", line);
if (JS_IsNull(frame->caller)) break;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
is_first = 0;
}
fprintf(stderr, "=================================\n");
}
#endif
/* Main register VM execution loop */
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue this_obj, int argc, JSValue *argv,
JSValue env, JSValue outer_frame) {
/* Protect env and outer_frame from GC — alloc_frame_register can trigger
collection which moves heap objects, invalidating stack-local copies */
JSGCRef env_gc, of_gc;
JS_PushGCRef(ctx, &env_gc);
env_gc.val = env;
JS_PushGCRef(ctx, &of_gc);
of_gc.val = outer_frame;
/* Protect argv and this_obj from GC by pushing onto value_stack.
argv is a C-allocated array whose JSValues may point to GC heap objects;
alloc_frame_register and js_new_register_function can trigger GC. */
int vs_save = ctx->value_stack_top;
int nargs_copy = (argc < code->arity) ? argc : code->arity;
ctx->value_stack[vs_save] = this_obj;
for (int i = 0; i < nargs_copy; i++)
ctx->value_stack[vs_save + 1 + i] = argv[i];
ctx->value_stack_top = vs_save + 1 + nargs_copy;
/* Allocate initial frame */
JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots);
if (!frame) {
ctx->value_stack_top = vs_save;
JS_PopGCRef(ctx, &of_gc);
JS_PopGCRef(ctx, &env_gc);
return JS_EXCEPTION;
}
/* Protect frame from GC */
JSGCRef frame_ref;
JS_AddGCRef(ctx, &frame_ref);
frame_ref.val = JS_MKPTR(frame);
#ifdef HAVE_ASAN
__asan_js_ctx = ctx;
#endif
/* Setup initial frame — wrap top-level code in a function object so that
returning from a called register function can read code/env from frame */
JSValue top_fn = js_new_register_function(ctx, code, env_gc.val, of_gc.val);
JS_PopGCRef(ctx, &of_gc);
JS_PopGCRef(ctx, &env_gc);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->function = top_fn;
frame->slots[0] = ctx->value_stack[vs_save]; /* slot 0 = this (GC-safe from value_stack) */
/* Copy arguments from GC-safe value_stack */
for (int i = 0; i < nargs_copy; i++) {
frame->slots[1 + i] = ctx->value_stack[vs_save + 1 + i];
}
ctx->value_stack_top = vs_save;
uint32_t pc = code->entry_point;
JSValue result = JS_NULL;
/* Execution loop — 32-bit instruction dispatch */
for (;;) {
if (reg_vm_check_interrupt(ctx)) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
if (pc >= code->instr_count) {
/* End of code — implicit return null */
result = JS_NULL;
if (JS_IsNull(frame->caller)) goto done;
/* Pop frame */
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL;
frame = caller;
frame_ref.val = JS_MKPTR(frame);
int ret_info = JS_VALUE_GET_INT(frame->address);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
env = fn->u.reg.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result;
continue;
}
MachInstr32 instr = code->instructions[pc++];
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
int op = MACH_GET_OP(instr);
int a = MACH_GET_A(instr);
int b = MACH_GET_B(instr);
int c = MACH_GET_C(instr);
switch (op) {
case MACH_NOP:
break;
case MACH_LOADK: {
int bx = MACH_GET_Bx(instr);
if (bx < (int)code->cpool_count)
frame->slots[a] = code->cpool[bx];
break;
}
case MACH_LOADI:
frame->slots[a] = JS_NewInt32(ctx, MACH_GET_sBx(instr));
break;
case MACH_LOADNULL:
frame->slots[a] = JS_NULL;
break;
case MACH_LOADTRUE:
frame->slots[a] = JS_TRUE;
break;
case MACH_LOADFALSE:
frame->slots[a] = JS_FALSE;
break;
case MACH_MOVE:
frame->slots[a] = frame->slots[b];
break;
/* Arithmetic / comparison / bitwise — all ABC format */
case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV:
case MACH_MOD: case MACH_POW:
case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE:
case MACH_GT: case MACH_GE:
case MACH_BAND: case MACH_BOR: case MACH_BXOR:
case MACH_SHL: case MACH_SHR: case MACH_USHR: {
JSValue left = frame->slots[b];
JSValue right = frame->slots[c];
JSValue res = reg_vm_binop(ctx, op, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) { goto disrupt; }
frame->slots[a] = res;
break;
}
case MACH_EQ_TOL:
case MACH_NEQ_TOL: {
/* A=dest, B=base, C=3; args in R(B), R(B+1), R(B+2) */
JSValue left = frame->slots[b];
JSValue right = frame->slots[b + 1];
JSValue tol = frame->slots[b + 2];
BOOL is_eq_op = (op == MACH_EQ_TOL);
if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) {
double da, db, dt;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
JS_ToFloat64(ctx, &dt, tol);
BOOL eq = fabs(da - db) <= dt;
frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq);
} else if (JS_IsText(left) && JS_IsText(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) {
BOOL eq = js_string_compare_value_nocase(ctx, left, right) == 0;
frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq);
} else {
/* Fall through to standard eq/neq */
JSValue res = reg_vm_binop(ctx, is_eq_op ? MACH_EQ : MACH_NEQ, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) { goto disrupt; }
frame->slots[a] = res;
}
break;
}
case MACH_NEG: {
JSValue v = frame->slots[b];
if (JS_IsInt(v)) {
int32_t i = JS_VALUE_GET_INT(v);
if (i == INT32_MIN)
frame->slots[a] = JS_NewFloat64(ctx, -(double)i);
else
frame->slots[a] = JS_NewInt32(ctx, -i);
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[a] = JS_NewFloat64(ctx, -d);
}
break;
}
case MACH_INC: {
JSValue v = frame->slots[b];
if (JS_IsInt(v)) {
int32_t i = JS_VALUE_GET_INT(v);
if (i == INT32_MAX)
frame->slots[a] = JS_NewFloat64(ctx, (double)i + 1);
else
frame->slots[a] = JS_NewInt32(ctx, i + 1);
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[a] = JS_NewFloat64(ctx, d + 1);
}
break;
}
case MACH_DEC: {
JSValue v = frame->slots[b];
if (JS_IsInt(v)) {
int32_t i = JS_VALUE_GET_INT(v);
if (i == INT32_MIN)
frame->slots[a] = JS_NewFloat64(ctx, (double)i - 1);
else
frame->slots[a] = JS_NewInt32(ctx, i - 1);
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[a] = JS_NewFloat64(ctx, d - 1);
}
break;
}
case MACH_LNOT: {
int bval = JS_ToBool(ctx, frame->slots[b]);
frame->slots[a] = JS_NewBool(ctx, !bval);
break;
}
case MACH_BNOT: {
int32_t i;
JS_ToInt32(ctx, &i, frame->slots[b]);
frame->slots[a] = JS_NewInt32(ctx, ~i);
break;
}
case MACH_GETFIELD: {
JSValue obj = frame->slots[b];
JSValue key = code->cpool[c];
/* Non-proxy functions (arity != 2) can't have properties read */
if (JS_IsFunction(obj)) {
JSFunction *fn_chk = JS_VALUE_GET_FUNCTION(obj);
if (fn_chk->length != 2) {
JS_ThrowTypeError(ctx, "cannot read property of non-proxy function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
}
JSValue val = JS_GetProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[a] = val;
break;
}
case MACH_SETFIELD: {
/* R(A)[K(B)] = R(C) */
JSValue obj = frame->slots[a];
JSValue key = code->cpool[b];
JSValue val = frame->slots[c];
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
break;
}
case MACH_GETINDEX: {
JSValue obj = frame->slots[b];
JSValue idx = frame->slots[c];
JSValue val;
if (JS_IsInt(idx))
val = JS_GetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx));
else
val = JS_GetProperty(ctx, obj, idx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[a] = val;
break;
}
case MACH_SETINDEX: {
/* R(A)[R(B)] = R(C) */
JSValue obj = frame->slots[a];
JSValue idx = frame->slots[b];
JSValue val = frame->slots[c];
int ret;
if (JS_IsInt(idx)) {
ret = JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val);
} else if (JS_IsArray(obj)) {
JS_ThrowTypeError(ctx, "array index must be a number");
ret = -1;
} else if (JS_IsRecord(obj) && !JS_IsText(idx) && !JS_IsRecord(idx)) {
JS_ThrowTypeError(ctx, "object key must be a string or object");
ret = -1;
} else {
ret = JS_SetProperty(ctx, obj, idx, val);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
break;
}
case MACH_GETINTRINSIC: {
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_GetProperty(ctx, ctx->global_obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsNull(val)) {
int has = JS_HasProperty(ctx, ctx->global_obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (has <= 0) {
char buf[128];
JS_KeyGetStr(ctx, buf, sizeof(buf), key);
JS_ThrowReferenceError(ctx, "'%s' is not defined", buf);
goto disrupt;
}
}
frame->slots[a] = val;
break;
}
case MACH_GETENV: {
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_GetProperty(ctx, env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = val;
break;
}
case MACH_GETNAME: {
/* Runtime fallback: try env then global (should not appear in linked code) */
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_NULL;
if (!JS_IsNull(env)) {
val = JS_GetProperty(ctx, env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
if (JS_IsNull(val) || JS_IsException(val)) {
val = JS_GetProperty(ctx, ctx->global_obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
frame->slots[a] = val;
break;
}
case MACH_GETUP: {
/* R(A) = outer_frame[B].slots[C] — walk lexical scope chain */
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
}
frame->slots[a] = target->slots[c];
break;
}
case MACH_SETUP: {
/* outer_frame[B].slots[C] = R(A) — walk lexical scope chain */
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
}
target->slots[c] = frame->slots[a];
break;
}
case MACH_JMP: {
int offset = MACH_GET_sJ(instr);
pc = (uint32_t)((int32_t)pc + offset);
break;
}
case MACH_JMPTRUE: {
int cond = JS_ToBool(ctx, frame->slots[a]);
if (cond) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
}
break;
}
case MACH_JMPFALSE: {
int cond = JS_ToBool(ctx, frame->slots[a]);
if (!cond) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
}
break;
}
case MACH_JMPNULL: {
if (JS_IsNull(frame->slots[a])) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
}
break;
}
case MACH_CALL: {
/* Lua-style call: R(A)=func, B=nargs in R(A+1)..R(A+B), C=nresults */
int base = a;
int nargs = b;
int nresults = c;
JSValue func_val = frame->slots[base];
if (!JS_IsFunction(func_val)) {
goto disrupt;
}
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
if (fn->kind == JS_FUNC_KIND_C) {
/* C function: push args onto value stack (C-allocated, GC-scanned) */
int vs_base = ctx->value_stack_top;
for (int i = 0; i < nargs; i++)
ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i];
ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = js_call_c_function(ctx, func_val, JS_NULL, nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) { goto disrupt; }
if (nresults > 0) frame->slots[base] = ret;
} else if (fn->kind == JS_FUNC_KIND_REGISTER) {
/* Register function: allocate frame, copy args, switch */
JSCodeRegister *fn_code = fn->u.reg.code;
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
/* Re-read pointers — GC may have moved them */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
func_val = frame->slots[base];
fn = JS_VALUE_GET_FUNCTION(func_val);
new_frame->function = func_val;
new_frame->slots[0] = JS_NULL; /* this */
for (int i = 0; i < nargs && i < fn_code->arity; i++)
new_frame->slots[1 + i] = frame->slots[base + 1 + i];
/* Save return info: pc in upper 16 bits, base reg or 0xFFFF (discard) in lower */
int ret_slot = (nresults > 0) ? base : 0xFFFF;
frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot);
new_frame->caller = JS_MKPTR(frame);
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn_code;
env = fn->u.reg.env_record;
pc = code->entry_point;
} else {
/* Other function kinds (bytecode) — push args onto value stack */
int vs_base = ctx->value_stack_top;
for (int i = 0; i < nargs; i++)
ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i];
ctx->value_stack_top = vs_base + nargs;
JSValue ret = JS_CallInternal(ctx, func_val, JS_NULL, nargs, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(ret)) { goto disrupt; }
if (nresults > 0) frame->slots[base] = ret;
}
break;
}
case MACH_CALLMETHOD: {
/* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key index
Result stored in R(A). C=0xFF means key is in R(A+1).
If obj is a function (proxy): call obj(key_str, [args...])
Else (record): get property, call property(obj_as_this, args...) */
int base = a;
int nargs = b;
JSGCRef key_ref;
JS_PushGCRef(ctx, &key_ref);
key_ref.val = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c];
if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val)) {
/* Proxy call: obj(name, [args...]) */
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
frame->slots[base + 1] = arr; /* protect from GC in temp slot */
for (int i = 0; i < nargs; i++) {
JS_SetPropertyUint32(ctx, frame->slots[base + 1], i, frame->slots[base + 2 + i]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
/* Push proxy args onto value stack; re-read obj since GC may have moved it */
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = key_ref.val;
ctx->value_stack[vs_base + 1] = frame->slots[base + 1]; /* the array */
ctx->value_stack_top = vs_base + 2;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_CallInternal(ctx, frame->slots[base], JS_NULL, 2, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
frame->slots[base] = ret;
} else if (JS_IsFunction(frame->slots[base])) {
/* Non-proxy function with non-text key: disrupt */
JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function");
JS_PopGCRef(ctx, &key_ref);
goto disrupt;
} else {
/* Record method call: get property, call with this=obj */
JSValue method = JS_GetProperty(ctx, frame->slots[base], key_ref.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(method)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
if (!JS_IsFunction(method)) {
frame->slots[base] = JS_NULL;
JS_PopGCRef(ctx, &key_ref);
break;
}
JSFunction *fn = JS_VALUE_GET_FUNCTION(method);
if (fn->kind == JS_FUNC_KIND_C) {
int vs_base = ctx->value_stack_top;
for (int i = 0; i < nargs; i++)
ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i];
ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = js_call_c_function(ctx, method, frame->slots[base], nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
frame->slots[base] = ret;
} else if (fn->kind == JS_FUNC_KIND_REGISTER) {
JSCodeRegister *fn_code = fn->u.reg.code;
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JS_PopGCRef(ctx, &key_ref);
goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
method = JS_GetProperty(ctx, frame->slots[base], key_ref.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
fn = JS_VALUE_GET_FUNCTION(method);
new_frame->function = method;
new_frame->slots[0] = frame->slots[base]; /* this */
for (int i = 0; i < nargs && i < fn_code->arity; i++)
new_frame->slots[1 + i] = frame->slots[base + 2 + i];
int ret_slot = base;
frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot);
new_frame->caller = JS_MKPTR(frame);
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn_code;
env = fn->u.reg.env_record;
pc = code->entry_point;
} else {
/* Bytecode or other function */
int vs_base = ctx->value_stack_top;
for (int i = 0; i < nargs; i++)
ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i];
ctx->value_stack_top = vs_base + nargs;
JSValue ret = JS_CallInternal(ctx, method, frame->slots[base], nargs, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
frame->slots[base] = ret;
}
}
JS_PopGCRef(ctx, &key_ref);
break;
}
case MACH_RETURN:
result = frame->slots[a];
if (JS_IsNull(frame->caller)) goto done;
{
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL;
frame = caller;
frame_ref.val = JS_MKPTR(frame);
int ret_info = JS_VALUE_GET_INT(frame->address);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
env = fn->u.reg.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result;
}
break;
case MACH_RETNIL:
result = JS_NULL;
if (JS_IsNull(frame->caller)) goto done;
{
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL;
frame = caller;
frame_ref.val = JS_MKPTR(frame);
int ret_info = JS_VALUE_GET_INT(frame->address);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
env = fn->u.reg.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result;
}
break;
case MACH_NEWOBJECT: {
JSValue obj = JS_NewObject(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(obj)) { goto disrupt; }
frame->slots[a] = obj;
break;
}
case MACH_NEWARRAY: {
int count = b;
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { goto disrupt; }
/* Store array in dest immediately so GC can track it */
frame->slots[a] = arr;
for (int i = 0; i < count; i++) {
JS_SetPropertyUint32(ctx, frame->slots[a], i, frame->slots[a + 1 + i]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
break;
}
case MACH_CLOSURE: {
int bx = MACH_GET_Bx(instr);
if ((uint32_t)bx < code->func_count) {
JSCodeRegister *fn_code = code->functions[bx];
JSValue fn_val = js_new_register_function(ctx, fn_code, env, frame_ref.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = fn_val;
} else {
frame->slots[a] = JS_NULL;
}
break;
}
case MACH_PUSH: {
/* push R(B) onto array R(A) */
JSValue arr = frame->slots[a];
JSValue val = frame->slots[b];
if (!JS_IsArray(arr)) goto disrupt;
JSGCRef arr_gc;
JS_PushGCRef(ctx, &arr_gc);
arr_gc.val = arr;
int rc = JS_ArrayPush(ctx, &arr_gc.val, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JS_PopGCRef(ctx, &arr_gc);
if (rc < 0) goto disrupt;
if (arr_gc.val != arr) frame->slots[a] = arr_gc.val;
break;
}
case MACH_POP: {
/* R(A) = pop last element from array R(B) */
JSValue arr = frame->slots[b];
if (!JS_IsArray(arr)) goto disrupt;
JSValue val = JS_ArrayPop(ctx, arr);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[a] = val;
break;
}
case MACH_DELETE: {
JSValue obj = frame->slots[b];
JSValue key = code->cpool[c];
int ret = JS_DeleteProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
frame->slots[a] = JS_NewBool(ctx, ret >= 0);
break;
}
case MACH_DELETEINDEX: {
JSValue obj = frame->slots[b];
JSValue key = frame->slots[c];
int ret = JS_DeleteProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
frame->slots[a] = JS_NewBool(ctx, ret >= 0);
break;
}
case MACH_HASPROP: {
JSValue obj = frame->slots[b];
JSValue key = frame->slots[c];
int has = JS_HasProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = JS_NewBool(ctx, has > 0);
break;
}
case MACH_REGEXP: {
JSValue argv[2];
argv[0] = code->cpool[b]; /* pattern */
argv[1] = code->cpool[c]; /* flags */
JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(re)) goto disrupt;
frame->slots[a] = re;
break;
}
case MACH_THROW:
goto disrupt;
default:
result = JS_ThrowInternalError(ctx, "unknown register VM opcode %d", op);
goto done;
}
continue;
disrupt:
/* Search frame chain for a disruption handler.
Use frame_pc to track each frame's execution point:
- For the faulting frame, it's the current pc.
- For unwound caller frames, read from frame->address. */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); /* re-chase after GC */
{
uint32_t frame_pc = pc;
for (;;) {
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
/* Only enter handler if we're not already inside it */
if (code->disruption_pc > 0 && frame_pc < code->disruption_pc) {
env = fn->u.reg.env_record;
pc = code->disruption_pc;
break;
}
if (JS_IsNull(frame->caller)) {
result = JS_Throw(ctx, JS_NewString(ctx, "unhandled disruption"));
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto done;
}
/* Unwind one frame — read caller's saved pc from its address field */
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL;
frame = caller;
frame_ref.val = JS_MKPTR(frame);
frame_pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
}
}
}
done:
#ifdef HAVE_ASAN
__asan_js_ctx = NULL;
#endif
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(result)) {
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
}
JS_DeleteGCRef(ctx, &frame_ref);
return result;
}
/* ============================================================
MCODE Generator — AST to MCODE JSON (string-based IR)
============================================================ */
/* ============================================================
MACH Public API
============================================================ */
/* Print a single constant pool value for dump output */
static void dump_cpool_value(JSContext *ctx, JSValue val) {
uint32_t tag = JS_VALUE_GET_TAG(val);
if (JS_IsPtr(val)) {
void *ptr = JS_VALUE_GET_PTR(val);
objhdr_t hdr = *(objhdr_t *)ptr;
uint8_t mist_type = objhdr_type(hdr);
if (mist_type == OBJ_TEXT) {
const char *str = JS_ToCString(ctx, val);
if (str) {
printf("\"%s\"", str);
JS_FreeCString(ctx, str);
} else {
printf("<string>");
}
return;
}
printf("<ptr type=%d>", mist_type);
return;
}
switch (tag) {
case JS_TAG_INT:
printf("%d", JS_VALUE_GET_INT(val));
break;
case JS_TAG_BOOL:
printf("%s", JS_VALUE_GET_BOOL(val) ? "true" : "false");
break;
case JS_TAG_NULL:
printf("null");
break;
case JS_TAG_SHORT_FLOAT:
printf("%g", JS_VALUE_GET_FLOAT64(val));
break;
case JS_TAG_STRING_IMM: {
const char *str = JS_ToCString(ctx, val);
if (str) {
printf("\"%s\"", str);
JS_FreeCString(ctx, str);
} else {
printf("<imm string>");
}
break;
}
default:
printf("<tag=%d>", tag);
break;
}
}
/* (labels removed in new format) */
/* Internal helper to dump JSCodeRegister (32-bit instruction format) */
static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent) {
char pad[64];
int pad_len = indent * 2;
if (pad_len > 60) pad_len = 60;
memset(pad, ' ', pad_len);
pad[pad_len] = '\0';
/* Function header */
const char *name = "<anonymous>";
if (!JS_IsNull(code->name)) {
const char *n = JS_ToCString(ctx, code->name);
if (n) name = n;
}
printf("%sFunction: %s\n", pad, name);
printf("%s Arity: %d, Slots: %d, Close: %d\n", pad,
code->arity, code->nr_slots, code->nr_close_slots);
if (!JS_IsNull(code->name)) {
JS_FreeCString(ctx, name);
}
if (code->disruption_pc > 0)
printf("%s Disruption handler at: %d\n", pad, code->disruption_pc);
/* Constant pool */
if (code->cpool_count > 0) {
printf("%s\n%sConstant Pool (%d entries):\n", pad, pad, code->cpool_count);
for (uint32_t i = 0; i < code->cpool_count; i++) {
printf("%s [%d]: ", pad, i);
dump_cpool_value(ctx, code->cpool[i]);
printf("\n");
}
}
/* Instructions */
printf("%s\n%sInstructions (%d):\n", pad, pad, code->instr_count);
for (uint32_t i = 0; i < code->instr_count; i++) {
MachInstr32 instr = code->instructions[i];
int op = MACH_GET_OP(instr);
int a = MACH_GET_A(instr);
int b = MACH_GET_B(instr);
int c = MACH_GET_C(instr);
const char *op_name = (op < MACH_OP_COUNT) ? mach_opcode_names[op] : "???";
if (!op_name) op_name = "???";
printf("%s %3d: %-14s ", pad, i, op_name);
switch (op) {
/* No operands */
case MACH_NOP:
case MACH_RETNIL:
break;
/* A only */
case MACH_LOADNULL:
case MACH_LOADTRUE:
case MACH_LOADFALSE:
printf("r%d", a);
break;
/* ABx: load constant */
case MACH_LOADK: {
int bx = MACH_GET_Bx(instr);
printf("r%d, #%d", a, bx);
if (bx >= 0 && (uint32_t)bx < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[bx]);
}
break;
}
/* AsBx: load small int */
case MACH_LOADI:
printf("r%d, %d", a, MACH_GET_sBx(instr));
break;
/* A, B: move, unary ops */
case MACH_MOVE:
case MACH_NEG:
case MACH_INC:
case MACH_DEC:
case MACH_LNOT:
case MACH_BNOT:
printf("r%d, r%d", a, b);
break;
/* A, B, C: arithmetic, comparison, bitwise */
case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV:
case MACH_MOD: case MACH_POW:
case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE:
case MACH_GT: case MACH_GE:
case MACH_BAND: case MACH_BOR: case MACH_BXOR:
case MACH_SHL: case MACH_SHR: case MACH_USHR:
printf("r%d, r%d, r%d", a, b, c);
break;
case MACH_EQ_TOL: case MACH_NEQ_TOL:
printf("r%d, r%d, %d", a, b, c);
break;
/* Property access */
case MACH_GETFIELD:
printf("r%d, r%d, #%d", a, b, c);
if ((uint32_t)c < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[c]);
}
break;
case MACH_SETFIELD:
printf("r%d, #%d, r%d", a, b, c);
if ((uint32_t)b < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[b]);
}
break;
case MACH_GETINDEX:
case MACH_SETINDEX:
printf("r%d, r%d, r%d", a, b, c);
break;
/* ABx: name/intrinsic/env access */
case MACH_GETNAME:
case MACH_GETINTRINSIC:
case MACH_GETENV: {
int bx = MACH_GET_Bx(instr);
printf("r%d, #%d", a, bx);
if ((uint32_t)bx < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[bx]);
}
break;
}
/* Closure access */
case MACH_GETUP:
case MACH_SETUP:
printf("r%d, depth=%d, slot=%d", a, b, c);
break;
/* isJ: unconditional jump */
case MACH_JMP: {
int offset = MACH_GET_sJ(instr);
printf("%+d", offset);
printf(" ; -> %d", (int)i + 1 + offset);
break;
}
/* iAsBx: conditional jumps */
case MACH_JMPTRUE:
case MACH_JMPFALSE:
case MACH_JMPNULL: {
int offset = MACH_GET_sBx(instr);
printf("r%d, %+d", a, offset);
printf(" ; -> %d", (int)i + 1 + offset);
break;
}
/* Call */
case MACH_CALL:
printf("r%d, %d, %d", a, b, c);
break;
/* Return / throw */
case MACH_RETURN:
case MACH_THROW:
printf("r%d", a);
break;
/* Object/array creation */
case MACH_NEWOBJECT:
printf("r%d", a);
break;
case MACH_NEWARRAY:
printf("r%d, %d", a, b);
break;
/* Push/Pop */
case MACH_PUSH:
printf("r%d, r%d", a, b);
break;
case MACH_POP:
printf("r%d, r%d", a, b);
break;
/* Closure */
case MACH_CLOSURE: {
int bx = MACH_GET_Bx(instr);
printf("r%d, func#%d", a, bx);
break;
}
default:
printf("0x%08x", instr);
break;
}
printf("\n");
}
/* Nested functions */
if (code->func_count > 0) {
printf("%s\n%sNested Functions (%d):\n", pad, pad, code->func_count);
for (uint32_t i = 0; i < code->func_count; i++) {
printf("%s [%d]:\n", pad, i);
if (code->functions[i]) {
dump_register_code(ctx, code->functions[i], indent + 2);
} else {
printf("%s <null>\n", pad);
}
}
}
}
/* Dump MACH bytecode to stdout for debugging. Takes AST cJSON tree. */
void JS_DumpMachTree(JSContext *ctx, cJSON *ast, JSValue env) {
MachCode *mc = JS_CompileMachTree(ast);
if (!mc) {
printf("=== MACH Bytecode ===\nFailed to compile\n=== End MACH Bytecode ===\n");
return;
}
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env);
printf("=== MACH Bytecode ===\n");
dump_register_code(ctx, code, 0);
printf("=== End MACH Bytecode ===\n");
}
void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) {
cJSON *ast = cJSON_Parse(ast_json);
if (!ast) {
printf("=== MACH Bytecode ===\nFailed to parse\n=== End MACH Bytecode ===\n");
return;
}
JS_DumpMachTree(ctx, ast, env);
cJSON_Delete(ast);
}
/* Compile and execute MACH bytecode. Takes AST cJSON tree. */
JSValue JS_RunMachTree(JSContext *ctx, cJSON *ast, JSValue env) {
MachCode *mc = JS_CompileMachTree(ast);
if (!mc) {
return JS_ThrowSyntaxError(ctx, "failed to compile AST to MACH bytecode");
}
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env);
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL);
return result;
}
JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) {
cJSON *ast = cJSON_Parse(ast_json);
if (!ast) {
return JS_ThrowSyntaxError(ctx, "failed to parse AST JSON");
}
JSValue result = JS_RunMachTree(ctx, ast, env);
cJSON_Delete(ast);
return result;
}