From 63feabed5df4c3d903abc5e1bb9c9de3fb07ad86 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 6 Feb 2026 02:50:48 -0600 Subject: [PATCH] mach vm --- source/cell.c | 19 +- source/quickjs.c | 4593 +++++++++++++++------------------------------- source/quickjs.h | 6 +- source/suite.c | 116 +- 4 files changed, 1515 insertions(+), 3219 deletions(-) diff --git a/source/cell.c b/source/cell.c index e30bd151..c87166dc 100644 --- a/source/cell.c +++ b/source/cell.c @@ -552,27 +552,14 @@ int cell_init(int argc, char **argv) return 1; } - char *mcode_json = JS_Mcode(ctx, ast_json); + /* --mcode now uses the new compiler and dumps bytecode (same as --mach) */ + JS_DumpMach(ctx, ast_json, JS_NULL); free(ast_json); - if (mcode_json) { - if (print_json_errors(mcode_json)) { - free(mcode_json); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - free(allocated_script); - return 1; - } - printf("%s\n", mcode_json); - free(mcode_json); - } else { - printf("Failed to generate MCODE\n"); - } - JS_FreeContext(ctx); JS_FreeRuntime(rt); free(allocated_script); - return mcode_json ? 0 : 1; + return 0; } /* Check for --mach flag to dump MACH bytecode */ diff --git a/source/quickjs.c b/source/quickjs.c index f336becf..1548add5 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -453,201 +453,186 @@ typedef struct JSFrameRegister { Register-Based VM Data Structures ============================================================ */ -/* Machine code opcode enum - matches instruction names from JS_Mcode output */ +/* 32-bit instruction encoding (Lua-style) + Formats: + iABC: [op:8][A:8][B:8][C:8] — register ops + iABx: [op:8][A:8][Bx:16] — constant/global loads (unsigned) + iAsBx: [op:8][A:8][sBx:16] — conditional jumps (signed offset) + isJ: [op:8][sJ:24] — unconditional jump (signed offset) */ + +typedef uint32_t MachInstr32; + +/* Encoding macros */ +#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24)) +#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16)) +#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16)) +#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8)) + +/* Decoding macros */ +#define MACH_GET_OP(i) ((i) & 0xFF) +#define MACH_GET_A(i) (((i) >> 8) & 0xFF) +#define MACH_GET_B(i) (((i) >> 16) & 0xFF) +#define MACH_GET_C(i) (((i) >> 24) & 0xFF) +#define MACH_GET_Bx(i) ((i) >> 16) +#define MACH_GET_sBx(i) ((int16_t)((i) >> 16)) +#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8) + typedef enum MachOpcode { - MACH_OP_NOP = 0, - /* Constants */ - MACH_OP_ACCESS, /* access dest, literal (number or string) */ - MACH_OP_NULL, /* null dest */ - MACH_OP_TRUE, /* true dest */ - MACH_OP_FALSE, /* false dest */ + /* Constants & Loading */ + MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */ + MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */ + MACH_LOADNULL, /* R(A) = null (A only) */ + MACH_LOADTRUE, /* R(A) = true (A only) */ + MACH_LOADFALSE, /* R(A) = false (A only) */ + /* Movement */ - MACH_OP_MOVE, /* move dest, src */ - /* Arithmetic */ - MACH_OP_ADD, /* add dest, src1, src2 */ - MACH_OP_SUB, /* sub dest, src1, src2 */ - MACH_OP_MUL, /* mul dest, src1, src2 */ - MACH_OP_DIV, /* div dest, src1, src2 */ - MACH_OP_MOD, /* mod dest, src1, src2 */ - MACH_OP_POW, /* pow dest, src1, src2 */ - MACH_OP_NEG, /* neg dest, src */ - MACH_OP_INC, /* inc dest, src */ - MACH_OP_DEC, /* dec dest, src */ - /* Comparison */ - MACH_OP_EQ, /* eq dest, src1, src2 */ - MACH_OP_NE, /* ne dest, src1, src2 */ - MACH_OP_LT, /* lt dest, src1, src2 */ - MACH_OP_LE, /* le dest, src1, src2 */ - MACH_OP_GT, /* gt dest, src1, src2 */ - MACH_OP_GE, /* ge dest, src1, src2 */ - MACH_OP_STRICT_EQ, /* strict_eq dest, src1, src2 */ - MACH_OP_STRICT_NE, /* strict_ne dest, src1, src2 */ + MACH_MOVE, /* R(A) = R(B) */ + + /* Arithmetic (ABC) */ + MACH_ADD, /* R(A) = R(B) + R(C) */ + MACH_SUB, /* R(A) = R(B) - R(C) */ + MACH_MUL, /* R(A) = R(B) * R(C) */ + MACH_DIV, /* R(A) = R(B) / R(C) */ + MACH_MOD, /* R(A) = R(B) % R(C) */ + MACH_POW, /* R(A) = R(B) ** R(C) */ + MACH_NEG, /* R(A) = -R(B) */ + MACH_INC, /* R(A) = R(B) + 1 */ + MACH_DEC, /* R(A) = R(B) - 1 */ + + /* Comparison (ABC) */ + MACH_EQ, /* R(A) = (R(B) == R(C)) */ + MACH_NEQ, /* R(A) = (R(B) != R(C)) */ + MACH_LT, /* R(A) = (R(B) < R(C)) */ + MACH_LE, /* R(A) = (R(B) <= R(C)) */ + MACH_GT, /* R(A) = (R(B) > R(C)) */ + MACH_GE, /* R(A) = (R(B) >= R(C)) */ + /* Logical/Bitwise */ - MACH_OP_NOT, /* not dest, src */ - MACH_OP_BITNOT, /* bitnot dest, src */ - MACH_OP_BITAND, /* bitand dest, src1, src2 */ - MACH_OP_BITOR, /* bitor dest, src1, src2 */ - MACH_OP_BITXOR, /* bitxor dest, src1, src2 */ - MACH_OP_SHL, /* shl dest, src1, src2 */ - MACH_OP_SHR, /* shr dest, src1, src2 */ - MACH_OP_USHR, /* ushr dest, src1, src2 */ + MACH_LNOT, /* R(A) = !R(B) */ + MACH_BNOT, /* R(A) = ~R(B) */ + MACH_BAND, /* R(A) = R(B) & R(C) */ + MACH_BOR, /* R(A) = R(B) | R(C) */ + MACH_BXOR, /* R(A) = R(B) ^ R(C) */ + MACH_SHL, /* R(A) = R(B) << R(C) */ + MACH_SHR, /* R(A) = R(B) >> R(C) */ + MACH_USHR, /* R(A) = R(B) >>> R(C) */ + /* Property access */ - MACH_OP_LOAD_PROP, /* load_prop dest, obj, "prop" (cpool idx) */ - MACH_OP_STORE_PROP, /* store_prop obj, val, "prop" (cpool idx) */ - MACH_OP_LOAD_IDX, /* load_idx dest, obj, idx */ - MACH_OP_STORE_IDX, /* store_idx obj, idx, val */ - MACH_OP_DELETE_PROP, /* delete_prop dest, obj, "prop" */ - MACH_OP_DELETE_IDX, /* delete_idx dest, obj, idx */ - /* Closure access */ - MACH_OP_GET, /* get dest, slot, depth */ - MACH_OP_PUT, /* put src, slot, depth */ - /* Resolved external/global access (post-linking) */ - MACH_OP_CAP_NAME, /* cap_name dest, "name" (cpool idx) - pre-link */ - MACH_OP_GET_ENV_SLOT, /* get_env_slot dest, slot_idx */ - MACH_OP_SET_ENV_SLOT, /* set_env_slot src, slot_idx */ - MACH_OP_GET_GLOBAL_SLOT, /* get_global_slot dest, slot_idx */ - MACH_OP_SET_GLOBAL_SLOT, /* set_global_slot src, slot_idx */ - MACH_OP_SET_VAR, /* set_var "name", src - pre-link */ + MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */ + MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */ + MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */ + MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */ + + /* Global/environment access (ABx) */ + MACH_GETGLOBAL, /* R(A) = global[K(Bx)] — post-link */ + MACH_SETGLOBAL, /* global[K(Bx)] = R(A) — post-link */ + MACH_GETNAME, /* R(A) = resolve(K(Bx)) — pre-link, patched to GETGLOBAL */ + MACH_SETNAME, /* resolve(K(Bx)) = R(A) — pre-link, patched to SETGLOBAL */ + + /* Closure access (ABC) */ + MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */ + MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */ + /* Control flow */ - MACH_OP_JUMP, /* jump label_idx */ - MACH_OP_JUMP_TRUE, /* jump_true cond, label_idx */ - MACH_OP_JUMP_FALSE, /* jump_false cond, label_idx */ - MACH_OP_JUMP_NOT_NULL, /* jump_not_null cond, label_idx */ - /* Function calls */ - MACH_OP_FRAME, /* frame frame_reg, func_reg, argc */ - MACH_OP_SET_THIS, /* set_this frame_reg, this_reg */ - MACH_OP_ARG, /* arg frame_reg, arg_idx, val_reg */ - MACH_OP_INVOKE, /* invoke frame_reg, ret_reg */ - /* Tail calls */ - MACH_OP_GOFRAME, /* goframe frame_reg, func_reg, argc */ - MACH_OP_GOINVOKE, /* goinvoke frame_reg */ - /* Function/Object creation */ - MACH_OP_MKFUNC, /* mkfunc dest, func_id */ - MACH_OP_MKFUNC_ARROW, /* mkfunc_arrow dest, func_id */ - MACH_OP_MKRECORD, /* mkrecord dest, argc (followed by key/val pairs) */ - MACH_OP_MKARRAY, /* mkarray dest, argc (followed by elements) */ - MACH_OP_MKNEW, /* mknew dest, func_reg, argc */ - /* Return */ - MACH_OP_RETURN, /* return src */ - MACH_OP_RETURN_UNDEF, /* return_undef */ - /* Exception handling */ - MACH_OP_THROW, /* throw src */ - MACH_OP_TRY_BEGIN, /* try_begin catch_label */ - MACH_OP_TRY_END, /* try_end */ - MACH_OP_CATCH_BEGIN, /* catch_begin dest */ - MACH_OP_CATCH_END, /* catch_end */ - MACH_OP_FINALLY_BEGIN, /* finally_begin */ - MACH_OP_FINALLY_END, /* finally_end */ + MACH_JMP, /* pc += sJ — unconditional (isJ format) */ + MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */ + MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */ + MACH_JMPNULL, /* if R(A)==null: pc += sBx */ + + /* Function calls — Lua-style consecutive registers */ + MACH_CALL, /* Call R(A) with B args R(A+1)..R(A+B), C=0 discard, C=1 keep result in R(A) */ + MACH_RETURN, /* Return R(A) */ + MACH_RETNIL, /* Return null */ + + /* Object/array creation */ + MACH_NEWOBJECT, /* R(A) = {} */ + MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */ + MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */ + + /* Special */ + MACH_TYPEOF, /* R(A) = typeof R(B) */ + MACH_THROW, /* throw R(A) */ + + MACH_NOP, + MACH_OP_COUNT } MachOpcode; static const char *mach_opcode_names[MACH_OP_COUNT] = { - [MACH_OP_NOP] = "nop", - [MACH_OP_ACCESS] = "access", - [MACH_OP_NULL] = "null", - [MACH_OP_TRUE] = "true", - [MACH_OP_FALSE] = "false", - [MACH_OP_MOVE] = "move", - [MACH_OP_ADD] = "add", - [MACH_OP_SUB] = "sub", - [MACH_OP_MUL] = "mul", - [MACH_OP_DIV] = "div", - [MACH_OP_MOD] = "mod", - [MACH_OP_POW] = "pow", - [MACH_OP_NEG] = "neg", - [MACH_OP_INC] = "inc", - [MACH_OP_DEC] = "dec", - [MACH_OP_EQ] = "eq", - [MACH_OP_NE] = "ne", - [MACH_OP_LT] = "lt", - [MACH_OP_LE] = "le", - [MACH_OP_GT] = "gt", - [MACH_OP_GE] = "ge", - [MACH_OP_STRICT_EQ] = "strict_eq", - [MACH_OP_STRICT_NE] = "strict_ne", - [MACH_OP_NOT] = "not", - [MACH_OP_BITNOT] = "bitnot", - [MACH_OP_BITAND] = "bitand", - [MACH_OP_BITOR] = "bitor", - [MACH_OP_BITXOR] = "bitxor", - [MACH_OP_SHL] = "shl", - [MACH_OP_SHR] = "shr", - [MACH_OP_USHR] = "ushr", - [MACH_OP_LOAD_PROP] = "load_prop", - [MACH_OP_STORE_PROP] = "store_prop", - [MACH_OP_LOAD_IDX] = "load_idx", - [MACH_OP_STORE_IDX] = "store_idx", - [MACH_OP_DELETE_PROP] = "delete_prop", - [MACH_OP_DELETE_IDX] = "delete_idx", - [MACH_OP_GET] = "get", - [MACH_OP_PUT] = "put", - [MACH_OP_CAP_NAME] = "cap_name", - [MACH_OP_GET_ENV_SLOT] = "get_env_slot", - [MACH_OP_SET_ENV_SLOT] = "set_env_slot", - [MACH_OP_GET_GLOBAL_SLOT] = "get_global_slot", - [MACH_OP_SET_GLOBAL_SLOT] = "set_global_slot", - [MACH_OP_SET_VAR] = "set_var", - [MACH_OP_JUMP] = "jump", - [MACH_OP_JUMP_TRUE] = "jump_true", - [MACH_OP_JUMP_FALSE] = "jump_false", - [MACH_OP_JUMP_NOT_NULL] = "jump_not_null", - [MACH_OP_FRAME] = "frame", - [MACH_OP_SET_THIS] = "set_this", - [MACH_OP_ARG] = "arg", - [MACH_OP_INVOKE] = "invoke", - [MACH_OP_GOFRAME] = "goframe", - [MACH_OP_GOINVOKE] = "goinvoke", - [MACH_OP_MKFUNC] = "mkfunc", - [MACH_OP_MKFUNC_ARROW] = "mkfunc_arrow", - [MACH_OP_MKRECORD] = "mkrecord", - [MACH_OP_MKARRAY] = "mkarray", - [MACH_OP_MKNEW] = "mknew", - [MACH_OP_RETURN] = "return", - [MACH_OP_RETURN_UNDEF] = "return_undef", - [MACH_OP_THROW] = "throw", - [MACH_OP_TRY_BEGIN] = "try_begin", - [MACH_OP_TRY_END] = "try_end", - [MACH_OP_CATCH_BEGIN] = "catch_begin", - [MACH_OP_CATCH_END] = "catch_end", - [MACH_OP_FINALLY_BEGIN] = "finally_begin", - [MACH_OP_FINALLY_END] = "finally_end", + [MACH_LOADK] = "loadk", + [MACH_LOADI] = "loadi", + [MACH_LOADNULL] = "loadnull", + [MACH_LOADTRUE] = "loadtrue", + [MACH_LOADFALSE] = "loadfalse", + [MACH_MOVE] = "move", + [MACH_ADD] = "add", + [MACH_SUB] = "sub", + [MACH_MUL] = "mul", + [MACH_DIV] = "div", + [MACH_MOD] = "mod", + [MACH_POW] = "pow", + [MACH_NEG] = "neg", + [MACH_INC] = "inc", + [MACH_DEC] = "dec", + [MACH_EQ] = "eq", + [MACH_NEQ] = "neq", + [MACH_LT] = "lt", + [MACH_LE] = "le", + [MACH_GT] = "gt", + [MACH_GE] = "ge", + [MACH_LNOT] = "lnot", + [MACH_BNOT] = "bnot", + [MACH_BAND] = "band", + [MACH_BOR] = "bor", + [MACH_BXOR] = "bxor", + [MACH_SHL] = "shl", + [MACH_SHR] = "shr", + [MACH_USHR] = "ushr", + [MACH_GETFIELD] = "getfield", + [MACH_SETFIELD] = "setfield", + [MACH_GETINDEX] = "getindex", + [MACH_SETINDEX] = "setindex", + [MACH_GETGLOBAL] = "getglobal", + [MACH_SETGLOBAL] = "setglobal", + [MACH_GETNAME] = "getname", + [MACH_SETNAME] = "setname", + [MACH_GETUP] = "getup", + [MACH_SETUP] = "setup", + [MACH_JMP] = "jmp", + [MACH_JMPTRUE] = "jmptrue", + [MACH_JMPFALSE] = "jmpfalse", + [MACH_JMPNULL] = "jmpnull", + [MACH_CALL] = "call", + [MACH_RETURN] = "return", + [MACH_RETNIL] = "retnil", + [MACH_NEWOBJECT] = "newobject", + [MACH_NEWARRAY] = "newarray", + [MACH_CLOSURE] = "closure", + [MACH_TYPEOF] = "typeof", + [MACH_THROW] = "throw", + [MACH_NOP] = "nop", }; -/* Compiled instruction for register VM. - Each instruction has opcode + up to 3 operands. - Operands can be register indices, constant pool indices, or literal values. */ -typedef struct MachInstr { - uint8_t opcode; /* MachOpcode */ - int16_t a; /* dest or first operand */ - int16_t b; /* second operand (or cpool_idx) */ - int16_t c; /* third operand */ -} MachInstr; - /* Compiled register-based code (off-heap, never GC'd). - Created by js_link_mach from JSON machine code. */ + Created by JS_CompileMach from AST JSON. */ typedef struct JSCodeRegister { uint16_t arity; /* number of arguments */ uint16_t nr_close_slots; /* closed-over variable count */ uint16_t nr_slots; /* total frame size */ uint16_t entry_point; /* start instruction (usually 0) */ - /* Constant pool for "access" literals (strings/numbers) */ + /* Constant pool */ uint32_t cpool_count; JSValue *cpool; /* allocated via js_malloc_rt */ - /* Compiled instructions */ + /* Compiled 32-bit instructions */ uint32_t instr_count; - MachInstr *instructions; + MachInstr32 *instructions; - /* Nested functions (for mkfunc) */ + /* Nested functions (for closure) */ uint32_t func_count; struct JSCodeRegister **functions; - /* Labels resolved to instruction indices (for debugging) */ - uint32_t label_count; - uint32_t *label_offsets; /* label_idx -> instr_idx */ - char **label_names; /* label_idx -> label name string */ - /* Debug info */ JSValue name; /* function name (stone text) */ } JSCodeRegister; @@ -30741,7 +30726,7 @@ char *JS_Tokenize (JSContext *ctx, const char *source, size_t len, const char *f } /* ============================================================ - MCODE Generator — AST to JSON IR + MACH Compiler — AST directly to binary JSCodeRegister ============================================================ */ /* Variable kinds */ @@ -30759,2374 +30744,1033 @@ typedef enum MachVarResolution { typedef struct MachVarInfo { char *name; int slot; - int kind; int is_const; /* 1 for def, function args; 0 for var */ } MachVarInfo; -typedef struct MachClosureRef { - char *name; - int parent_slot; - int depth; - int local_slot; -} MachClosureRef; +/* ---- Compiler state ---- */ -typedef struct MachResolveResult { - MachVarResolution type; - int slot; - int depth; -} MachResolveResult; - -typedef struct MachGenState { +typedef struct MachCompState { JSContext *ctx; - cJSON *instructions; - cJSON *data; - cJSON *functions; - int this_slot; /* always 0 */ - int nr_args; - int nr_close_slots; /* captured from parents */ - int nr_local_slots; - int next_temp_slot; - int max_slot; + /* Instruction buffer (growable) */ + MachInstr32 *code; + int code_count; + int code_capacity; + /* Constant pool */ + JSValue *cpool; + int cpool_count; + int cpool_capacity; + + /* Nested functions */ + JSCodeRegister **functions; + int func_count; + int func_capacity; + + /* Variables */ MachVarInfo *vars; int var_count; int var_capacity; - MachClosureRef *closure_refs; - int closure_ref_count; - int closure_ref_capacity; + /* Register allocation (Lua-style) */ + int freereg; /* next free register */ + int maxreg; /* high-water mark */ + int nr_args; /* parameter count */ - int label_counter; - int func_counter; + /* Loop labels for break/continue */ + int loop_break; /* instruction index to patch, or -1 */ + int loop_continue; /* instruction index to patch, or -1 */ - struct MachGenState *parent; - const char *loop_break; - const char *loop_continue; + /* Scope depth for block scoping */ + int scope_depth; - int is_arrow; - int has_inner_function; /* Set if function contains nested functions */ + /* Parent for nested function compilation */ + struct MachCompState *parent; /* Error tracking */ - cJSON *errors; int has_error; -} MachGenState; +} MachCompState; -static int mach_gen_expr (MachGenState *s, cJSON *expr); -static void mach_gen_statement (MachGenState *s, cJSON *stmt); +/* Forward declarations */ +static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest); +static void mach_compile_stmt(MachCompState *cs, cJSON *stmt); -/* Allocate a temporary slot */ -static int mach_alloc_slot (MachGenState *s) { - int slot = s->next_temp_slot++; - if (slot > s->max_slot) s->max_slot = slot; - return slot; +/* ---- Compiler helpers ---- */ + +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; + } + cs->code[cs->code_count++] = instr; } -/* Add a variable to the tracking table */ -static void mach_add_var (MachGenState *s, const char *name, int slot, int kind, int is_const) { - if (s->var_count >= s->var_capacity) { - int new_cap = s->var_capacity ? s->var_capacity * 2 : 16; - s->vars = sys_realloc (s->vars, new_cap * sizeof(MachVarInfo)); - s->var_capacity = new_cap; +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 a constant to the pool, return its index */ +static int mach_cpool_add(MachCompState *cs, JSValue val) { + /* Check for duplicates (simple linear scan) */ + for (int i = 0; i < cs->cpool_count; i++) { + JSValue existing = cs->cpool[i]; + /* Compare by tag and value */ + if (JS_IsInt(val) && JS_IsInt(existing)) { + if (JS_VALUE_GET_INT(val) == JS_VALUE_GET_INT(existing)) + return i; + } else if (JS_VALUE_IS_TEXT(val) && JS_VALUE_IS_TEXT(existing)) { + const char *a = JS_ToCString(cs->ctx, val); + const char *b = JS_ToCString(cs->ctx, existing); + int eq = a && b && strcmp(a, b) == 0; + JS_FreeCString(cs->ctx, a); + JS_FreeCString(cs->ctx, b); + if (eq) return i; + } } - MachVarInfo *v = &s->vars[s->var_count++]; - v->name = sys_malloc (strlen (name) + 1); - strcpy (v->name, name); + + 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(JSValue)); + cs->cpool_capacity = new_cap; + } + cs->cpool[cs->cpool_count] = 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 first (before allocating) */ + for (int i = 0; i < cs->cpool_count; i++) { + JSValue existing = cs->cpool[i]; + if (JS_VALUE_IS_TEXT(existing)) { + const char *s = JS_ToCString(cs->ctx, existing); + int eq = s && strcmp(s, str) == 0; + JS_FreeCString(cs->ctx, s); + if (eq) return i; + } + } + JSValue val = JS_NewString(cs->ctx, str); + 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(JSValue)); + cs->cpool_capacity = new_cap; + } + cs->cpool[cs->cpool_count] = val; + return cs->cpool_count++; +} + +/* 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->kind = kind; v->is_const = is_const; } -/* Find a variable in the current scope only */ -static int mach_find_var_local (MachGenState *s, const char *name) { - for (int i = 0; i < s->var_count; i++) { - if (strcmp (s->vars[i].name, name) == 0) { - return s->vars[i].slot; - } +/* 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; } -/* Find a variable and return its info, or NULL if not found */ -static MachVarInfo *mach_find_var_info (MachGenState *s, const char *name) { - for (int i = 0; i < s->var_count; i++) { - if (strcmp (s->vars[i].name, name) == 0) { - return &s->vars[i]; +/* Add a nested function, return its index */ +static int mach_add_function(MachCompState *cs, JSCodeRegister *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(JSCodeRegister*)); + cs->func_capacity = new_cap; + } + cs->functions[cs->func_count] = fn; + return cs->func_count++; +} + +/* Scan AST statements for var/def declarations (hoisting) */ +static void mach_scan_vars(MachCompState *cs, cJSON *stmts) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) { + cJSON *stmt = cJSON_GetArrayItem(stmts, i); + const char *kind = cJSON_GetStringValue(cJSON_GetObjectItem(stmt, "kind")); + if (!kind) continue; + + if (strcmp(kind, "var") == 0 || strcmp(kind, "def") == 0) { + cJSON *left = cJSON_GetObjectItem(stmt, "left"); + const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(left, "name")); + if (name && mach_find_var(cs, name) < 0) { + int slot = mach_reserve_reg(cs); + mach_add_var(cs, name, slot, strcmp(kind, "def") == 0); + } } - } - return NULL; -} - -/* Add an error to the state */ -static void mach_error (MachGenState *s, cJSON *node, const char *fmt, ...) { - va_list ap; - char buf[256]; - - va_start (ap, fmt); - vsnprintf (buf, sizeof(buf), fmt, ap); - va_end (ap); - - cJSON *err = cJSON_CreateObject (); - cJSON_AddStringToObject (err, "message", buf); - - /* Try to get line info from node */ - cJSON *line_obj = cJSON_GetObjectItem (node, "from_row"); - cJSON *col_obj = cJSON_GetObjectItem (node, "from_column"); - if (line_obj) { - cJSON_AddNumberToObject (err, "line", cJSON_GetNumberValue (line_obj) + 1); - } - if (col_obj) { - cJSON_AddNumberToObject (err, "column", cJSON_GetNumberValue (col_obj) + 1); - } - - if (!s->errors) { - s->errors = cJSON_CreateArray (); - } - cJSON_AddItemToArray (s->errors, err); - s->has_error = 1; -} - -/* Scan a node for var/def declarations and register them (hoisting) */ -static void mach_scan_vars (MachGenState *s, cJSON *node) { - if (!node) return; - - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (node, "kind")); - if (!kind) return; - - /* var/def declaration - register the variable */ - if (strcmp (kind, "var") == 0 || strcmp (kind, "def") == 0) { - int is_const = (strcmp (kind, "def") == 0); - cJSON *left = cJSON_GetObjectItem (node, "left"); - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); - if (name) { - MachVarInfo *existing = mach_find_var_info (s, name); - if (existing) { - /* Check for redeclaration errors */ - if (existing->is_const) { - mach_error (s, node, "cannot redeclare constant '%s'", name); - } else if (is_const) { - mach_error (s, node, "cannot redeclare '%s' as constant", name); + /* Also handle function declarations at top level */ + if (strcmp(kind, "call") == 0) { + cJSON *expr = cJSON_GetObjectItem(stmt, "expression"); + if (expr) { + const char *ek = cJSON_GetStringValue(cJSON_GetObjectItem(expr, "kind")); + if (ek && strcmp(ek, "function") == 0) { + cJSON *fname = cJSON_GetObjectItem(expr, "name"); + if (fname && cJSON_IsString(fname)) { + const char *fn = cJSON_GetStringValue(fname); + if (fn && mach_find_var(cs, fn) < 0) { + int slot = mach_reserve_reg(cs); + mach_add_var(cs, fn, slot, 1); + } + } } - /* If var redeclaring var, it's allowed (hoisting) */ + } + } + } +} + +/* ---- 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; + } + + const char *kind = cJSON_GetStringValue(cJSON_GetObjectItem(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_GetObjectItem(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 { - int slot = 1 + s->nr_args + s->nr_local_slots++; - mach_add_var (s, name, slot, MACH_VAR_LOCAL, is_const); + /* Large number: use constant pool */ + JSValue val; + if (dval == (double)(int32_t)dval) + val = JS_NewInt32(cs->ctx, (int32_t)dval); + else + val = JS_NewFloat64(cs->ctx, dval); + int ki = mach_cpool_add(cs, val); + mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); } - } - return; - } - - /* Block - scan children */ - if (strcmp (kind, "block") == 0) { - cJSON *stmts = cJSON_GetObjectItem (node, "statements"); - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_scan_vars (s, child); - } - return; - } - - /* If - scan then/else branches */ - if (strcmp (kind, "if") == 0) { - cJSON *then_stmts = cJSON_GetObjectItem (node, "then"); - cJSON *else_stmts = cJSON_GetObjectItem (node, "else"); - cJSON *child; - cJSON_ArrayForEach (child, then_stmts) { - mach_scan_vars (s, child); - } - if (else_stmts) { - cJSON_ArrayForEach (child, else_stmts) { - mach_scan_vars (s, child); - } - } - return; - } - - /* While - scan body */ - if (strcmp (kind, "while") == 0) { - cJSON *stmts = cJSON_GetObjectItem (node, "statements"); - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_scan_vars (s, child); - } - return; - } - - /* Do-while - scan body */ - if (strcmp (kind, "do") == 0) { - cJSON *stmts = cJSON_GetObjectItem (node, "statements"); - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_scan_vars (s, child); - } - return; - } - - /* For - scan init and body */ - if (strcmp (kind, "for") == 0) { - cJSON *init = cJSON_GetObjectItem (node, "init"); - cJSON *stmts = cJSON_GetObjectItem (node, "statements"); - if (init) mach_scan_vars (s, init); - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_scan_vars (s, child); - } - return; - } - - /* Switch - scan case bodies */ - if (strcmp (kind, "switch") == 0) { - cJSON *cases = cJSON_GetObjectItem (node, "cases"); - cJSON *case_node; - cJSON_ArrayForEach (case_node, cases) { - cJSON *case_stmts = cJSON_GetObjectItem (case_node, "statements"); - cJSON *child; - cJSON_ArrayForEach (child, case_stmts) { - mach_scan_vars (s, child); - } - } - return; - } - - /* Try-catch-finally - scan all blocks */ - if (strcmp (kind, "try") == 0) { - cJSON *try_stmts = cJSON_GetObjectItem (node, "statements"); - cJSON *catch_node = cJSON_GetObjectItem (node, "catch"); - cJSON *finally_node = cJSON_GetObjectItem (node, "finally"); - cJSON *child; - cJSON_ArrayForEach (child, try_stmts) { - mach_scan_vars (s, child); - } - if (catch_node) { - cJSON *catch_stmts = cJSON_GetObjectItem (catch_node, "statements"); - cJSON_ArrayForEach (child, catch_stmts) { - mach_scan_vars (s, child); - } - } - if (finally_node) { - cJSON *finally_stmts = cJSON_GetObjectItem (finally_node, "statements"); - cJSON_ArrayForEach (child, finally_stmts) { - mach_scan_vars (s, child); - } - } - return; - } - - /* NOTE: Do NOT recurse into nested function nodes - they have their own scope */ -} - -/* Recursively scan for inner function definitions */ -static void mach_scan_inner_functions (MachGenState *s, cJSON *node) { - if (!node || s->has_inner_function) return; - - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (node, "kind")); - if (!kind) return; - - /* Found an inner function */ - if (strcmp (kind, "function") == 0 || strcmp (kind, "=>") == 0) { - s->has_inner_function = 1; - return; - } - - /* Scan child nodes */ - cJSON *expr = cJSON_GetObjectItem (node, "expression"); - cJSON *left = cJSON_GetObjectItem (node, "left"); - cJSON *right = cJSON_GetObjectItem (node, "right"); - cJSON *list = cJSON_GetObjectItem (node, "list"); - cJSON *stmts = cJSON_GetObjectItem (node, "statements"); - cJSON *then_stmts = cJSON_GetObjectItem (node, "then"); - cJSON *else_stmts = cJSON_GetObjectItem (node, "else"); - - if (expr) mach_scan_inner_functions (s, expr); - if (left) mach_scan_inner_functions (s, left); - if (right) mach_scan_inner_functions (s, right); - - cJSON *item; - if (list) cJSON_ArrayForEach (item, list) mach_scan_inner_functions (s, item); - if (stmts) cJSON_ArrayForEach (item, stmts) mach_scan_inner_functions (s, item); - if (then_stmts) cJSON_ArrayForEach (item, then_stmts) mach_scan_inner_functions (s, item); - if (else_stmts) cJSON_ArrayForEach (item, else_stmts) mach_scan_inner_functions (s, item); -} - -/* Resolve a variable - returns resolution type and slot/depth info */ -static MachResolveResult mach_resolve_var (MachGenState *s, const char *name) { - MachResolveResult result = {MACH_VAR_UNBOUND, -1, 0}; - - /* Check current scope */ - int local_slot = mach_find_var_local (s, name); - if (local_slot >= 0) { - result.type = MACH_VAR_LOCAL_SLOT; - result.slot = local_slot; - return result; - } - - /* Check parent scopes */ - MachGenState *parent = s->parent; - int depth = 1; - while (parent) { - int parent_slot = mach_find_var_local (parent, name); - if (parent_slot >= 0) { - /* Found in parent scope - add closure ref */ - if (s->closure_ref_count >= s->closure_ref_capacity) { - int new_cap = s->closure_ref_capacity ? s->closure_ref_capacity * 2 : 8; - s->closure_refs = sys_realloc (s->closure_refs, new_cap * sizeof(MachClosureRef)); - s->closure_ref_capacity = new_cap; - } - int close_slot = 1 + s->nr_args + s->nr_close_slots; - MachClosureRef *ref = &s->closure_refs[s->closure_ref_count++]; - ref->name = sys_malloc (strlen (name) + 1); - strcpy (ref->name, name); - ref->parent_slot = parent_slot; - ref->depth = depth; - ref->local_slot = close_slot; - s->nr_close_slots++; - - result.type = MACH_VAR_CLOSURE; - result.slot = parent_slot; - result.depth = depth; - return result; - } - parent = parent->parent; - depth++; - } - - /* Not found - unbound */ - return result; -} - -static char *mach_gen_label (MachGenState *s, const char *prefix) { - char *label = sys_malloc (64); - snprintf (label, 64, "%s_%d", prefix, s->label_counter++); - return label; -} - -static void mach_emit_label (MachGenState *s, const char *label) { - cJSON *item = cJSON_CreateString (label); - cJSON_AddItemToArray (s->instructions, item); -} - -/* Emit instruction with string opcode */ -static void mach_emit_0 (MachGenState *s, const char *op) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (s->instructions, instr); -} - -static void mach_emit_1 (MachGenState *s, const char *op, int a) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (s->instructions, instr); -} - -static void mach_emit_2 (MachGenState *s, const char *op, int a, int b) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); - cJSON_AddItemToArray (s->instructions, instr); -} - -static void mach_emit_3 (MachGenState *s, const char *op, int a, int b, int c) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (c)); - cJSON_AddItemToArray (s->instructions, instr); -} - -static void mach_emit_4 (MachGenState *s, const char *op, int a, int b, int c, int d) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (c)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (d)); - cJSON_AddItemToArray (s->instructions, instr); -} - -/* Emit number constant - inline literal */ -static void mach_emit_const_num (MachGenState *s, int dest, double val) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); - cJSON_AddItemToArray (s->instructions, instr); -} - -/* Emit string constant - inline literal */ -static void mach_emit_const_str (MachGenState *s, int dest, const char *val) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateString (val ? val : "")); - cJSON_AddItemToArray (s->instructions, instr); -} - -/* Emit boolean constant */ -static void mach_emit_const_bool (MachGenState *s, int dest, int val) { - mach_emit_1 (s, val ? "true" : "false", dest); -} - -/* Emit null constant */ -static void mach_emit_const_null (MachGenState *s, int dest) { - mach_emit_1 (s, "null", dest); -} - -/* Emit jump with label */ -static void mach_emit_jump (MachGenState *s, const char *label) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("jump")); - cJSON_AddItemToArray (instr, cJSON_CreateString (label)); - cJSON_AddItemToArray (s->instructions, instr); -} - -/* Emit conditional jump */ -static void mach_emit_jump_cond (MachGenState *s, const char *op, int slot, const char *label) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); - cJSON_AddItemToArray (instr, cJSON_CreateString (label)); - cJSON_AddItemToArray (s->instructions, instr); -} - -/* Emit get variable - resolves variable and emits appropriate opcode */ -static void mach_emit_get_var (MachGenState *s, int dest, const char *name) { - MachResolveResult res = mach_resolve_var (s, name); - if (res.type == MACH_VAR_LOCAL_SLOT) { - /* Local variable - emit move */ - mach_emit_2 (s, "move", dest, res.slot); - } else if (res.type == MACH_VAR_CLOSURE) { - /* Closure variable - emit get */ - mach_emit_3 (s, "get", dest, res.slot, res.depth); - } else { - /* Unbound variable - emit cap_name for linker to resolve */ - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("cap_name")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateString (name)); - cJSON_AddItemToArray (s->instructions, instr); - } -} - -/* Check if assignment to a variable is allowed (not const) */ -static int mach_check_assign_var (MachGenState *s, const char *name, cJSON *node) { - MachVarInfo *info = mach_find_var_info (s, name); - if (info && info->is_const) { - if (info->kind == MACH_VAR_ARG) { - mach_error (s, node, "cannot assign to function argument '%s'", name); } else { - mach_error (s, node, "cannot assign to constant '%s'", name); + mach_emit(cs, MACH_AsBx(MACH_LOADI, dest, 0)); } - return 0; /* not allowed */ + return dest; } - return 1; /* allowed */ -} -/* Emit set variable - resolves variable and emits appropriate opcode */ -static void mach_emit_set_var (MachGenState *s, const char *name, int slot, cJSON *node) { - int local_slot = mach_find_var_local (s, name); - if (local_slot >= 0) { - /* Local variable - emit move */ - mach_emit_2 (s, "move", local_slot, slot); - } else { - /* Try to resolve in parent scopes for closure write */ - MachResolveResult res = mach_resolve_var (s, name); - if (res.type == MACH_VAR_CLOSURE) { - /* Closure variable - emit put */ - mach_emit_3 (s, "put", slot, res.slot, res.depth); + /* 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_GetObjectItem(node, "value")); + if (val) { + int ki = mach_cpool_add_str(cs, val); + mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); } else { - /* Unbound - emit set_var with name for linker to resolve */ - if (node) - mach_error (s, node, "cannot assign to '%s' (not declared in this scope)", name); - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("set_var")); - cJSON_AddItemToArray (instr, cJSON_CreateString (name)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); - cJSON_AddItemToArray (s->instructions, instr); + int ki = mach_cpool_add_str(cs, ""); + mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); } - } -} - -/* Emit property access with inline key */ -static void mach_emit_get_prop (MachGenState *s, int dest, int obj, const char *prop) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("load_prop")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); - cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - cJSON_AddItemToArray (s->instructions, instr); -} - -/* Emit property store with inline key */ -static void mach_emit_set_prop (MachGenState *s, int obj, const char *prop, int val) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("store_prop")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); - cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - cJSON_AddItemToArray (s->instructions, instr); -} - -/* Emit element access */ -static void mach_emit_get_elem (MachGenState *s, int dest, int obj, int idx) { - mach_emit_3 (s, "load_idx", dest, obj, idx); -} - -/* Emit element store */ -static void mach_emit_set_elem (MachGenState *s, int obj, int idx, int val) { - mach_emit_3 (s, "store_idx", obj, idx, val); -} - -/* Emit function call using frame/arg/invoke sequence */ -static void mach_emit_call (MachGenState *s, int dest, int func_slot, cJSON *args) { - int argc = cJSON_GetArraySize (args); - int frame_slot = mach_alloc_slot (s); - - /* frame frame_slot, func_slot, argc */ - mach_emit_3 (s, "frame", frame_slot, func_slot, argc); - - /* set_this frame_slot, null (or global for non-method calls) */ - int null_slot = mach_alloc_slot (s); - mach_emit_1 (s, "null", null_slot); - mach_emit_2 (s, "set_this", frame_slot, null_slot); - - /* arg frame_slot, arg_index, val_slot for each arg */ - int arg_idx = 1; - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - mach_emit_3 (s, "arg", frame_slot, arg_idx++, arg->valueint); - } - - /* invoke frame_slot, ret_slot */ - mach_emit_2 (s, "invoke", frame_slot, dest); -} - -/* Emit method call using frame/arg/invoke sequence */ -static void mach_emit_call_method (MachGenState *s, int dest, int obj, const char *prop, cJSON *args) { - /* First get the method */ - int func_slot = mach_alloc_slot (s); - mach_emit_get_prop (s, func_slot, obj, prop); - - int argc = cJSON_GetArraySize (args); - int frame_slot = mach_alloc_slot (s); - - /* frame frame_slot, func_slot, argc */ - mach_emit_3 (s, "frame", frame_slot, func_slot, argc); - - /* set_this frame_slot, obj */ - mach_emit_2 (s, "set_this", frame_slot, obj); - - /* arg frame_slot, arg_index, val_slot for each arg */ - int arg_idx = 1; - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - mach_emit_3 (s, "arg", frame_slot, arg_idx++, arg->valueint); - } - - /* invoke frame_slot, ret_slot */ - mach_emit_2 (s, "invoke", frame_slot, dest); -} - -/* Emit tail call using goframe/goinvoke sequence */ -static void mach_emit_go_call (MachGenState *s, int func_slot, cJSON *args) { - int argc = cJSON_GetArraySize (args); - int frame_slot = mach_alloc_slot (s); - - mach_emit_3 (s, "goframe", frame_slot, func_slot, argc); - - int null_slot = mach_alloc_slot (s); - mach_emit_1 (s, "null", null_slot); - mach_emit_2 (s, "set_this", frame_slot, null_slot); - - int arg_idx = 1; - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - mach_emit_3 (s, "arg", frame_slot, arg_idx++, arg->valueint); - } - - mach_emit_1 (s, "goinvoke", frame_slot); -} - -/* Emit method tail call using goframe/goinvoke sequence */ -static void mach_emit_go_call_method (MachGenState *s, int obj, const char *prop, cJSON *args) { - int func_slot = mach_alloc_slot (s); - mach_emit_get_prop (s, func_slot, obj, prop); - - int argc = cJSON_GetArraySize (args); - int frame_slot = mach_alloc_slot (s); - - mach_emit_3 (s, "goframe", frame_slot, func_slot, argc); - mach_emit_2 (s, "set_this", frame_slot, obj); - - int arg_idx = 1; - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - mach_emit_3 (s, "arg", frame_slot, arg_idx++, arg->valueint); - } - - mach_emit_1 (s, "goinvoke", frame_slot); -} - -/* Map JS operator to opcode string */ -static const char *mach_binop_to_string (const char *kind) { - if (strcmp (kind, "+") == 0) return "add"; - if (strcmp (kind, "-") == 0) return "sub"; - if (strcmp (kind, "*") == 0) return "mul"; - if (strcmp (kind, "/") == 0) return "div"; - if (strcmp (kind, "%") == 0) return "mod"; - if (strcmp (kind, "**") == 0) return "pow"; - if (strcmp (kind, "&") == 0) return "bitand"; - if (strcmp (kind, "|") == 0) return "bitor"; - if (strcmp (kind, "^") == 0) return "bitxor"; - if (strcmp (kind, "<<") == 0) return "shl"; - if (strcmp (kind, ">>") == 0) return "shr"; - if (strcmp (kind, ">>>") == 0) return "ushr"; - if (strcmp (kind, "==") == 0 || strcmp (kind, "===") == 0) return "eq"; - if (strcmp (kind, "!=") == 0 || strcmp (kind, "!==") == 0) return "ne"; - if (strcmp (kind, "<") == 0) return "lt"; - if (strcmp (kind, "<=") == 0) return "le"; - if (strcmp (kind, ">") == 0) return "gt"; - if (strcmp (kind, ">=") == 0) return "ge"; - if (strcmp (kind, "in") == 0) return "in"; - return "add"; -} - -/* Generate code for binary operators */ -static int mach_gen_binary (MachGenState *s, cJSON *node) { - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (node, "kind")); - cJSON *left = cJSON_GetObjectItem (node, "left"); - cJSON *right = cJSON_GetObjectItem (node, "right"); - - /* Handle short-circuit operators */ - if (strcmp (kind, "&&") == 0) { - char *end_label = mach_gen_label (s, "and_end"); - int left_slot = mach_gen_expr (s, left); - int dest = mach_alloc_slot (s); - mach_emit_2 (s, "move", dest, left_slot); - mach_emit_jump_cond (s, "jump_false", dest, end_label); - int right_slot = mach_gen_expr (s, right); - mach_emit_2 (s, "move", dest, right_slot); - mach_emit_label (s, end_label); - sys_free (end_label); return dest; } - if (strcmp (kind, "||") == 0) { - char *end_label = mach_gen_label (s, "or_end"); - int left_slot = mach_gen_expr (s, left); - int dest = mach_alloc_slot (s); - mach_emit_2 (s, "move", dest, left_slot); - mach_emit_jump_cond (s, "jump_true", dest, end_label); - int right_slot = mach_gen_expr (s, right); - mach_emit_2 (s, "move", dest, right_slot); - mach_emit_label (s, end_label); - sys_free (end_label); + /* 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; } - if (strcmp (kind, "??") == 0) { - char *end_label = mach_gen_label (s, "nullish_end"); - int left_slot = mach_gen_expr (s, left); - int dest = mach_alloc_slot (s); - mach_emit_2 (s, "move", dest, left_slot); - mach_emit_jump_cond (s, "jump_not_null", dest, end_label); - int right_slot = mach_gen_expr (s, right); - mach_emit_2 (s, "move", dest, right_slot); - mach_emit_label (s, end_label); - sys_free (end_label); + /* Name (variable reference) */ + if (strcmp(kind, "name") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(node, "name")); + if (name) { + int slot = mach_find_var(cs, name); + if (slot >= 0) { + /* Local variable — just return its register */ + if (dest >= 0 && dest != slot) { + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); + return dest; + } + return slot; + } + /* Unbound — emit GETNAME (will be patched to GETGLOBAL 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; } - /* Regular binary operators */ - int left_slot = mach_gen_expr (s, left); - int right_slot = mach_gen_expr (s, right); - int dest = mach_alloc_slot (s); + /* Function call: kind="(" */ + if (strcmp(kind, "(") == 0) { + cJSON *fn_expr = cJSON_GetObjectItem(node, "expression"); + cJSON *args = cJSON_GetObjectItem(node, "list"); + int nargs = args ? cJSON_GetArraySize(args) : 0; - const char *op = mach_binop_to_string (kind); - mach_emit_3 (s, op, dest, left_slot, right_slot); - return dest; -} + /* Save freereg so we can allocate consecutive regs for call */ + int save_freereg = cs->freereg; -/* Generate code for assignment with operator */ -static int mach_gen_compound_assign (MachGenState *s, cJSON *node, const char *op) { - cJSON *left = cJSON_GetObjectItem (node, "left"); - cJSON *right = cJSON_GetObjectItem (node, "right"); + /* Allocate base register for the function */ + int base = mach_reserve_reg(cs); + if (dest < 0) dest = base; /* result goes to base */ - const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItem (left, "kind")); - - if (strcmp (left_kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); - /* Check if assignment is allowed (not to const or argument) */ - mach_check_assign_var (s, name, node); - int left_slot = mach_alloc_slot (s); - mach_emit_get_var (s, left_slot, name); - int right_slot = mach_gen_expr (s, right); - int dest = mach_alloc_slot (s); - mach_emit_3 (s, op, dest, left_slot, right_slot); - mach_emit_set_var (s, name, dest, node); - return dest; - } else if (strcmp (left_kind, ".") == 0) { - cJSON *obj = cJSON_GetObjectItem (left, "left"); - const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (left, "right")); - int obj_slot = mach_gen_expr (s, obj); - int old_val = mach_alloc_slot (s); - mach_emit_get_prop (s, old_val, obj_slot, prop); - int right_slot = mach_gen_expr (s, right); - int dest = mach_alloc_slot (s); - mach_emit_3 (s, op, dest, old_val, right_slot); - mach_emit_set_prop (s, obj_slot, prop, dest); - return dest; - } else if (strcmp (left_kind, "[") == 0) { - cJSON *obj = cJSON_GetObjectItem (left, "left"); - cJSON *idx_expr = cJSON_GetObjectItem (left, "right"); - int obj_slot = mach_gen_expr (s, obj); - int idx_slot = mach_gen_expr (s, idx_expr); - int old_val = mach_alloc_slot (s); - mach_emit_get_elem (s, old_val, obj_slot, idx_slot); - int right_slot = mach_gen_expr (s, right); - int dest = mach_alloc_slot (s); - mach_emit_3 (s, op, dest, old_val, right_slot); - mach_emit_set_elem (s, obj_slot, idx_slot, dest); - return dest; - } - - return -1; -} - -/* Generate code for assignment */ -static int mach_gen_assign (MachGenState *s, cJSON *node) { - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (node, "kind")); - cJSON *left = cJSON_GetObjectItem (node, "left"); - cJSON *right = cJSON_GetObjectItem (node, "right"); - - /* Handle compound assignments */ - if (strcmp (kind, "+=") == 0) return mach_gen_compound_assign (s, node, "add"); - if (strcmp (kind, "-=") == 0) return mach_gen_compound_assign (s, node, "sub"); - if (strcmp (kind, "*=") == 0) return mach_gen_compound_assign (s, node, "mul"); - if (strcmp (kind, "/=") == 0) return mach_gen_compound_assign (s, node, "div"); - if (strcmp (kind, "%=") == 0) return mach_gen_compound_assign (s, node, "mod"); - if (strcmp (kind, "**=") == 0) return mach_gen_compound_assign (s, node, "pow"); - if (strcmp (kind, "&=") == 0) return mach_gen_compound_assign (s, node, "bitand"); - if (strcmp (kind, "|=") == 0) return mach_gen_compound_assign (s, node, "bitor"); - if (strcmp (kind, "^=") == 0) return mach_gen_compound_assign (s, node, "bitxor"); - if (strcmp (kind, "<<=") == 0) return mach_gen_compound_assign (s, node, "shl"); - if (strcmp (kind, ">>=") == 0) return mach_gen_compound_assign (s, node, "shr"); - if (strcmp (kind, ">>>=") == 0) return mach_gen_compound_assign (s, node, "ushr"); - - /* Simple assignment */ - int val_slot = mach_gen_expr (s, right); - - const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItem (left, "kind")); - - if (strcmp (left_kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); - /* Check if assignment is allowed (not to const or argument) */ - mach_check_assign_var (s, name, node); - mach_emit_set_var (s, name, val_slot, node); - } else if (strcmp (left_kind, ".") == 0) { - cJSON *obj = cJSON_GetObjectItem (left, "left"); - const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (left, "right")); - int obj_slot = mach_gen_expr (s, obj); - mach_emit_set_prop (s, obj_slot, prop, val_slot); - } else if (strcmp (left_kind, "[") == 0) { - cJSON *obj = cJSON_GetObjectItem (left, "left"); - cJSON *idx_expr = cJSON_GetObjectItem (left, "right"); - int obj_slot = mach_gen_expr (s, obj); - int idx_slot = mach_gen_expr (s, idx_expr); - mach_emit_set_elem (s, obj_slot, idx_slot, val_slot); - } - - return val_slot; -} - -/* Forward declaration for function generation */ -static cJSON *mach_gen_function (MachGenState *s, cJSON *func_node); - -/* Generate code for an expression, returns the slot containing the result */ -static int mach_gen_expr (MachGenState *s, cJSON *expr) { - if (!expr) return -1; - - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "kind")); - if (!kind) return -1; - - - /* Literals */ - if (strcmp (kind, "number") == 0) { - int slot = mach_alloc_slot (s); - double val = cJSON_GetNumberValue (cJSON_GetObjectItem (expr, "number")); - mach_emit_const_num (s, slot, val); - return slot; - } - - if (strcmp (kind, "text") == 0) { - int slot = mach_alloc_slot (s); - const char *val = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "value")); - mach_emit_const_str (s, slot, val ? val : ""); - return slot; - } - - if (strcmp (kind, "true") == 0) { - int slot = mach_alloc_slot (s); - mach_emit_const_bool (s, slot, 1); - return slot; - } - - if (strcmp (kind, "false") == 0) { - int slot = mach_alloc_slot (s); - mach_emit_const_bool (s, slot, 0); - return slot; - } - - if (strcmp (kind, "null") == 0) { - int slot = mach_alloc_slot (s); - mach_emit_const_null (s, slot); - return slot; - } - - if (strcmp (kind, "this") == 0) { - /* this is always slot 0 */ - return s->this_slot; - } - - /* Variable reference */ - if (strcmp (kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "name")); - int slot = mach_alloc_slot (s); - mach_emit_get_var (s, slot, name); - return slot; - } - - /* Property access */ - if (strcmp (kind, ".") == 0) { - cJSON *obj = cJSON_GetObjectItem (expr, "left"); - const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "right")); - int obj_slot = mach_gen_expr (s, obj); - int slot = mach_alloc_slot (s); - mach_emit_get_prop (s, slot, obj_slot, prop); - return slot; - } - - /* Element access */ - if (strcmp (kind, "[") == 0) { - cJSON *obj = cJSON_GetObjectItem (expr, "left"); - cJSON *idx = cJSON_GetObjectItem (expr, "right"); - int obj_slot = mach_gen_expr (s, obj); - int idx_slot = mach_gen_expr (s, idx); - int slot = mach_alloc_slot (s); - mach_emit_get_elem (s, slot, obj_slot, idx_slot); - return slot; - } - - /* Function call */ - if (strcmp (kind, "(") == 0) { - cJSON *callee = cJSON_GetObjectItem (expr, "expression"); - cJSON *args_list = cJSON_GetObjectItem (expr, "list"); - - /* Compile arguments first */ - cJSON *arg_slots = cJSON_CreateArray (); - cJSON *arg; - cJSON_ArrayForEach (arg, args_list) { - int arg_slot = mach_gen_expr (s, arg); - cJSON_AddItemToArray (arg_slots, cJSON_CreateNumber (arg_slot)); + /* 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)); } - const char *callee_kind = cJSON_GetStringValue (cJSON_GetObjectItem (callee, "kind")); + /* 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)); + } + } - int dest = mach_alloc_slot (s); + /* 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)); - if (strcmp (callee_kind, ".") == 0) { - /* Method call */ - cJSON *obj = cJSON_GetObjectItem (callee, "left"); - const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (callee, "right")); - int obj_slot = mach_gen_expr (s, obj); - mach_emit_call_method (s, dest, obj_slot, prop, arg_slots); + /* 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 { - /* Regular call */ - int func_slot = mach_gen_expr (s, callee); - mach_emit_call (s, dest, func_slot, arg_slots); + dest = base; } + return dest; + } - cJSON_Delete (arg_slots); + /* 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_GetObjectItem(node, "left"); + cJSON *right = cJSON_GetObjectItem(node, "right"); + + int lr = mach_compile_expr(cs, left, -1); + 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_GetObjectItem(node, "left"); + cJSON *right = cJSON_GetObjectItem(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) { - cJSON *operand = cJSON_GetObjectItem (expr, "expression"); - int operand_slot = mach_gen_expr (s, operand); - int slot = mach_alloc_slot (s); - mach_emit_2 (s, "not", slot, operand_slot); - return slot; + if (strcmp(kind, "!") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItem(node, "expression"); + if (!operand) operand = cJSON_GetObjectItem(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) { - cJSON *operand = cJSON_GetObjectItem (expr, "expression"); - int operand_slot = mach_gen_expr (s, operand); - int slot = mach_alloc_slot (s); - mach_emit_2 (s, "bitnot", slot, operand_slot); - return slot; + if (strcmp(kind, "~") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItem(node, "expression"); + if (!operand) operand = cJSON_GetObjectItem(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) { - cJSON *operand = cJSON_GetObjectItem (expr, "expression"); - int operand_slot = mach_gen_expr (s, operand); - int slot = mach_alloc_slot (s); - mach_emit_2 (s, "neg", slot, operand_slot); - return slot; + if (strcmp(kind, "unary_-") == 0 || (strcmp(kind, "-") == 0 && !cJSON_GetObjectItem(node, "left"))) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItem(node, "expression"); + if (!operand) operand = cJSON_GetObjectItem(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; } - - if (strcmp (kind, "+unary") == 0) { - /* Unary + is essentially a no-op for numbers */ - cJSON *operand = cJSON_GetObjectItem (expr, "expression"); - return mach_gen_expr (s, operand); - } - - /* Increment/Decrement */ - if (strcmp (kind, "++") == 0 || strcmp (kind, "--") == 0) { - cJSON *operand = cJSON_GetObjectItem (expr, "expression"); - cJSON *is_postfix = cJSON_GetObjectItem (expr, "postfix"); - int postfix = is_postfix && cJSON_IsTrue (is_postfix); - const char *op = (strcmp (kind, "++") == 0) ? "inc" : "dec"; - - const char *operand_kind = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "kind")); - - if (strcmp (operand_kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "name")); - /* Check if assignment is allowed (not to const or argument) */ - mach_check_assign_var (s, name, expr); - int old_slot = mach_alloc_slot (s); - mach_emit_get_var (s, old_slot, name); - int new_slot = mach_alloc_slot (s); - mach_emit_2 (s, op, new_slot, old_slot); - mach_emit_set_var (s, name, new_slot, expr); - return postfix ? old_slot : new_slot; - } else if (strcmp (operand_kind, ".") == 0) { - cJSON *obj = cJSON_GetObjectItem (operand, "left"); - const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "right")); - int obj_slot = mach_gen_expr (s, obj); - int old_slot = mach_alloc_slot (s); - mach_emit_get_prop (s, old_slot, obj_slot, prop); - int new_slot = mach_alloc_slot (s); - mach_emit_2 (s, op, new_slot, old_slot); - mach_emit_set_prop (s, obj_slot, prop, new_slot); - return postfix ? old_slot : new_slot; - } else if (strcmp (operand_kind, "[") == 0) { - cJSON *obj = cJSON_GetObjectItem (operand, "left"); - cJSON *idx_expr = cJSON_GetObjectItem (operand, "right"); - int obj_slot = mach_gen_expr (s, obj); - int idx_slot = mach_gen_expr (s, idx_expr); - int old_slot = mach_alloc_slot (s); - mach_emit_get_elem (s, old_slot, obj_slot, idx_slot); - int new_slot = mach_alloc_slot (s); - mach_emit_2 (s, op, new_slot, old_slot); - mach_emit_set_elem (s, obj_slot, idx_slot, new_slot); - return postfix ? old_slot : new_slot; - } - } - - /* Delete operator */ - if (strcmp (kind, "delete") == 0) { - cJSON *operand = cJSON_GetObjectItem (expr, "expression"); - const char *operand_kind = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "kind")); - int slot = mach_alloc_slot (s); - - if (strcmp (operand_kind, ".") == 0) { - cJSON *obj = cJSON_GetObjectItem (operand, "left"); - const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "right")); - int obj_slot = mach_gen_expr (s, obj); - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("delete_prop")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj_slot)); - cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - cJSON_AddItemToArray (s->instructions, instr); - } else if (strcmp (operand_kind, "[") == 0) { - cJSON *obj = cJSON_GetObjectItem (operand, "left"); - cJSON *idx = cJSON_GetObjectItem (operand, "right"); - int obj_slot = mach_gen_expr (s, obj); - int idx_slot = mach_gen_expr (s, idx); - mach_emit_3 (s, "delete_idx", slot, obj_slot, idx_slot); - } else { - mach_emit_const_bool (s, slot, 1); - } - return slot; - } - - /* Void operator */ - if (strcmp (kind, "void") == 0) { - cJSON *operand = cJSON_GetObjectItem (expr, "expression"); - mach_gen_expr (s, operand); - int slot = mach_alloc_slot (s); - mach_emit_const_null (s, slot); - return slot; - } - - /* Ternary */ - if (strcmp (kind, "then") == 0) { - cJSON *cond = cJSON_GetObjectItem (expr, "expression"); - cJSON *then_expr = cJSON_GetObjectItem (expr, "then"); - cJSON *else_expr = cJSON_GetObjectItem (expr, "else"); - - char *else_label = mach_gen_label (s, "tern_else"); - char *end_label = mach_gen_label (s, "tern_end"); - - int cond_slot = mach_gen_expr (s, cond); - mach_emit_jump_cond (s, "jump_false", cond_slot, else_label); - - int dest = mach_alloc_slot (s); - int then_slot = mach_gen_expr (s, then_expr); - mach_emit_2 (s, "move", dest, then_slot); - mach_emit_jump (s, end_label); - - mach_emit_label (s, else_label); - int else_slot = mach_gen_expr (s, else_expr); - mach_emit_2 (s, "move", dest, else_slot); - - mach_emit_label (s, end_label); - - sys_free (else_label); - sys_free (end_label); + if (strcmp(kind, "typeof") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItem(node, "expression"); + if (!operand) operand = cJSON_GetObjectItem(node, "right"); + int save = cs->freereg; + int r = mach_compile_expr(cs, operand, -1); + mach_emit(cs, MACH_ABC(MACH_TYPEOF, dest, r, 0)); + mach_free_reg_to(cs, save); return dest; } - /* Array literal */ - if (strcmp (kind, "array") == 0) { - cJSON *list = cJSON_GetObjectItem (expr, "list"); - int count = cJSON_GetArraySize (list); + /* Assignment */ + if (strcmp(kind, "=") == 0) { + cJSON *left = cJSON_GetObjectItem(node, "left"); + cJSON *right = cJSON_GetObjectItem(node, "right"); + const char *lk = cJSON_GetStringValue(cJSON_GetObjectItem(left, "kind")); - /* Compile each element */ - cJSON *elem_slots = cJSON_CreateArray (); - cJSON *elem; - cJSON_ArrayForEach (elem, list) { - int slot = mach_gen_expr (s, elem); - cJSON_AddItemToArray (elem_slots, cJSON_CreateNumber (slot)); + if (lk && strcmp(lk, "name") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(left, "name")); + 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; + } + /* Global assignment */ + 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)); + int ki = mach_cpool_add_str(cs, name); + mach_emit(cs, MACH_ABx(MACH_SETNAME, dest, ki)); + return dest; } - int dest = mach_alloc_slot (s); - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("mkarray")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (count)); - cJSON *el; - cJSON_ArrayForEach (el, elem_slots) { - cJSON_AddItemToArray (instr, cJSON_CreateNumber (el->valueint)); + /* 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_GetObjectItem(left, "expression"); + cJSON *prop = cJSON_GetObjectItem(left, "name"); + const char *prop_name = NULL; + if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); + else prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(left, "value")); + + int obj_r = mach_compile_expr(cs, obj_expr, -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; } - cJSON_AddItemToArray (s->instructions, instr); - cJSON_Delete (elem_slots); + + /* 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_GetObjectItem(left, "expression"); + cJSON *idx_expr = cJSON_GetObjectItem(left, "index"); + if (!idx_expr) idx_expr = cJSON_GetObjectItem(left, "right"); + + int obj_r = mach_compile_expr(cs, obj_expr, -1); + int idx_r = mach_compile_expr(cs, idx_expr, -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; } - /* Object literal */ - if (strcmp (kind, "record") == 0) { - cJSON *list = cJSON_GetObjectItem (expr, "list"); + /* Property access: kind="." */ + if (strcmp(kind, ".") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItem(node, "expression"); + cJSON *prop = cJSON_GetObjectItem(node, "name"); + const char *prop_name = NULL; + if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); + else prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(node, "value")); - /* Create empty record */ - int dest = mach_alloc_slot (s); - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("mkrecord")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (0)); - cJSON_AddItemToArray (s->instructions, instr); + 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; + } - /* Add each property */ - cJSON *pair; - cJSON_ArrayForEach (pair, list) { - cJSON *key = cJSON_GetObjectItem (pair, "left"); - cJSON *val = cJSON_GetObjectItem (pair, "right"); - int val_slot = mach_gen_expr (s, val); + /* Computed property access: kind="[" */ + if (strcmp(kind, "[") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItem(node, "expression"); + cJSON *idx_expr = cJSON_GetObjectItem(node, "index"); + if (!idx_expr) idx_expr = cJSON_GetObjectItem(node, "right"); - const char *key_kind = cJSON_GetStringValue (cJSON_GetObjectItem (key, "kind")); - if (key_kind && strcmp (key_kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (key, "name")); - mach_emit_set_prop (s, dest, name, val_slot); - } else if (key_kind && strcmp (key_kind, "text") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (key, "value")); - mach_emit_set_prop (s, dest, name ? name : "", val_slot); - } else { - /* Computed property - use store_idx */ - int key_slot = mach_gen_expr (s, key); - mach_emit_set_elem (s, dest, key_slot, val_slot); + int obj_r = mach_compile_expr(cs, obj_expr, -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" */ + if (strcmp(kind, "object") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABC(MACH_NEWOBJECT, dest, 0, 0)); + cJSON *props = cJSON_GetObjectItem(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_GetObjectItem(prop, "key"); + cJSON *val_node = cJSON_GetObjectItem(prop, "value"); + if (!val_node) val_node = cJSON_GetObjectItem(prop, "expression"); + const char *key = cJSON_GetStringValue(key_node); + if (!key) key = cJSON_GetStringValue(cJSON_GetObjectItem(key_node, "value")); + if (!key) key = cJSON_GetStringValue(cJSON_GetObjectItem(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; } - /* Function expression */ - if (strcmp (kind, "function") == 0) { - int func_id = s->func_counter++; - cJSON *func = mach_gen_function (s, expr); - cJSON_AddItemToArray (s->functions, func); + /* Array literal: kind="array" */ + if (strcmp(kind, "array") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *elems = cJSON_GetObjectItem(node, "list"); + int count = elems ? cJSON_GetArraySize(elems) : 0; - /* Check if it's an arrow function */ - cJSON *is_arrow = cJSON_GetObjectItem (expr, "arrow"); - int dest = mach_alloc_slot (s); - if (is_arrow && cJSON_IsTrue (is_arrow)) { - mach_emit_2 (s, "mkfunc_arrow", dest, func_id); - } else { - mach_emit_2 (s, "mkfunc", dest, func_id); + /* Reserve consecutive regs for elements starting at dest+1 */ + int save = cs->freereg; + if (cs->freereg <= dest) cs->freereg = dest + 1; + 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, dest, count, 0)); + mach_free_reg_to(cs, save); return dest; } - /* New expression */ - if (strcmp (kind, "new") == 0) { - cJSON *callee = cJSON_GetObjectItem (expr, "expression"); - cJSON *args_list = cJSON_GetObjectItem (expr, "list"); + /* Ternary: kind="?" */ + if (strcmp(kind, "?") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *cond = cJSON_GetObjectItem(node, "expression"); + if (!cond) cond = cJSON_GetObjectItem(node, "condition"); + cJSON *then_expr = cJSON_GetObjectItem(node, "then"); + if (!then_expr) then_expr = cJSON_GetObjectItem(node, "left"); + cJSON *else_expr = cJSON_GetObjectItem(node, "else"); + if (!else_expr) else_expr = cJSON_GetObjectItem(node, "right"); - int func_slot = mach_gen_expr (s, callee); + 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); - cJSON *arg_slots = cJSON_CreateArray (); - int argc = 0; - if (args_list) { - cJSON *arg; - cJSON_ArrayForEach (arg, args_list) { - int slot = mach_gen_expr (s, arg); - cJSON_AddItemToArray (arg_slots, cJSON_CreateNumber (slot)); - argc++; + 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.ctx = cs->ctx; + child.parent = cs; + child.freereg = 1; /* slot 0 = this */ + + /* Register parameters */ + cJSON *params = cJSON_GetObjectItem(node, "params"); + if (!params) params = cJSON_GetObjectItem(node, "parameters"); + 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_GetObjectItem(p, "name")); + if (!pname) pname = cJSON_GetStringValue(p); + if (pname) { + int slot = mach_reserve_reg(&child); + mach_add_var(&child, pname, slot, 1); } } - int dest = mach_alloc_slot (s); - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("new")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (func_slot)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (argc)); - cJSON *el; - cJSON_ArrayForEach (el, arg_slots) { - cJSON_AddItemToArray (instr, cJSON_CreateNumber (el->valueint)); + /* Scan for var/def */ + cJSON *body = cJSON_GetObjectItem(node, "body"); + if (body) { + cJSON *stmts = cJSON_GetObjectItem(body, "statements"); + if (!stmts) stmts = body; /* body might be the statements array directly */ + if (cJSON_IsArray(stmts)) + mach_scan_vars(&child, stmts); + + /* Compile body */ + if (cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) { + mach_compile_stmt(&child, cJSON_GetArrayItem(stmts, i)); + } + } } - cJSON_AddItemToArray (s->instructions, instr); - cJSON_Delete (arg_slots); + + /* Implicit return null */ + mach_emit(&child, MACH_ABC(MACH_RETNIL, 0, 0, 0)); + + /* Build JSCodeRegister for the child function */ + JSCodeRegister *fn_code = js_mallocz_rt(sizeof(JSCodeRegister)); + fn_code->arity = nparams; + fn_code->nr_slots = child.maxreg; + fn_code->nr_close_slots = 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; + + cJSON *fname = cJSON_GetObjectItem(node, "name"); + if (fname && cJSON_IsString(fname)) { + fn_code->name = JS_NewString(cs->ctx, cJSON_GetStringValue(fname)); + fn_code->name = JS_CellStone(cs->ctx, fn_code->name); + } else { + fn_code->name = JS_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; } - /* Assignment operators */ - 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 || - strcmp (kind, "??=") == 0) { - return mach_gen_assign (s, expr); - } - - /* Binary operators */ - return mach_gen_binary (s, expr); + /* 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; } -/* Generate code for a statement */ -static void mach_gen_statement (MachGenState *s, cJSON *stmt) { - if (!stmt) return; +/* ---- Statement compiler ---- */ - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "kind")); +static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { + if (!stmt) return; + const char *kind = cJSON_GetStringValue(cJSON_GetObjectItem(stmt, "kind")); if (!kind) return; - /* Variable declaration */ - if (strcmp (kind, "var") == 0 || strcmp (kind, "def") == 0) { - cJSON *left = cJSON_GetObjectItem (stmt, "left"); - cJSON *right = cJSON_GetObjectItem (stmt, "right"); - const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); - - /* Variable should already be registered from scan phase */ - int local_slot = mach_find_var_local (s, name); - + /* var / def declaration */ + if (strcmp(kind, "var") == 0 || strcmp(kind, "def") == 0) { + cJSON *left = cJSON_GetObjectItem(stmt, "left"); + cJSON *right = cJSON_GetObjectItem(stmt, "right"); + const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(left, "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, strcmp(kind, "def") == 0); + } if (right) { - int val_slot = mach_gen_expr (s, right); - if (local_slot >= 0) { - mach_emit_2 (s, "move", local_slot, val_slot); - } else { - /* Fallback for unregistered (shouldn't happen) */ - mach_emit_set_var (s, name, val_slot, stmt); - } - } else if (local_slot >= 0) { - /* Initialize to null */ - mach_emit_const_null (s, local_slot); - } - return; - } - - /* Block statement */ - if (strcmp (kind, "block") == 0) { - cJSON *stmts = cJSON_GetObjectItem (stmt, "statements"); - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_gen_statement (s, child); - } - return; - } - - /* If statement */ - if (strcmp (kind, "if") == 0) { - cJSON *cond = cJSON_GetObjectItem (stmt, "expression"); - cJSON *then_stmts = cJSON_GetObjectItem (stmt, "then"); - cJSON *else_stmts = cJSON_GetObjectItem (stmt, "else"); - - char *else_label = mach_gen_label (s, "if_else"); - char *end_label = mach_gen_label (s, "if_end"); - - int cond_slot = mach_gen_expr (s, cond); - mach_emit_jump_cond (s, "jump_false", cond_slot, else_label); - - cJSON *child; - cJSON_ArrayForEach (child, then_stmts) { - mach_gen_statement (s, child); - } - mach_emit_jump (s, end_label); - - mach_emit_label (s, else_label); - if (else_stmts) { - cJSON_ArrayForEach (child, else_stmts) { - mach_gen_statement (s, child); - } - } - - mach_emit_label (s, end_label); - sys_free (else_label); - sys_free (end_label); - return; - } - - /* While loop */ - if (strcmp (kind, "while") == 0) { - cJSON *cond = cJSON_GetObjectItem (stmt, "expression"); - cJSON *stmts = cJSON_GetObjectItem (stmt, "statements"); - - char *start_label = mach_gen_label (s, "while_start"); - char *end_label = mach_gen_label (s, "while_end"); - - const char *old_break = s->loop_break; - const char *old_continue = s->loop_continue; - s->loop_break = end_label; - s->loop_continue = start_label; - - mach_emit_label (s, start_label); - int cond_slot = mach_gen_expr (s, cond); - mach_emit_jump_cond (s, "jump_false", cond_slot, end_label); - - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_gen_statement (s, child); - } - mach_emit_jump (s, start_label); - - mach_emit_label (s, end_label); - - s->loop_break = old_break; - s->loop_continue = old_continue; - sys_free (start_label); - sys_free (end_label); - return; - } - - /* Do-while loop */ - if (strcmp (kind, "do") == 0) { - cJSON *cond = cJSON_GetObjectItem (stmt, "expression"); - cJSON *stmts = cJSON_GetObjectItem (stmt, "statements"); - - char *start_label = mach_gen_label (s, "do_start"); - char *cond_label = mach_gen_label (s, "do_cond"); - char *end_label = mach_gen_label (s, "do_end"); - - const char *old_break = s->loop_break; - const char *old_continue = s->loop_continue; - s->loop_break = end_label; - s->loop_continue = cond_label; - - mach_emit_label (s, start_label); - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_gen_statement (s, child); - } - - mach_emit_label (s, cond_label); - int cond_slot = mach_gen_expr (s, cond); - mach_emit_jump_cond (s, "jump_true", cond_slot, start_label); - - mach_emit_label (s, end_label); - - s->loop_break = old_break; - s->loop_continue = old_continue; - sys_free (start_label); - sys_free (cond_label); - sys_free (end_label); - return; - } - - /* For loop */ - if (strcmp (kind, "for") == 0) { - cJSON *init = cJSON_GetObjectItem (stmt, "init"); - cJSON *test = cJSON_GetObjectItem (stmt, "test"); - cJSON *update = cJSON_GetObjectItem (stmt, "update"); - cJSON *stmts = cJSON_GetObjectItem (stmt, "statements"); - - char *start_label = mach_gen_label (s, "for_start"); - char *update_label = mach_gen_label (s, "for_update"); - char *end_label = mach_gen_label (s, "for_end"); - - const char *old_break = s->loop_break; - const char *old_continue = s->loop_continue; - s->loop_break = end_label; - s->loop_continue = update_label; - - /* Init */ - if (init) { - const char *init_kind = cJSON_GetStringValue (cJSON_GetObjectItem (init, "kind")); - if (init_kind && (strcmp (init_kind, "var") == 0 || strcmp (init_kind, "def") == 0)) { - mach_gen_statement (s, init); - } else { - mach_gen_expr (s, init); - } - } - - mach_emit_label (s, start_label); - - /* Test */ - if (test) { - int test_slot = mach_gen_expr (s, test); - mach_emit_jump_cond (s, "jump_false", test_slot, end_label); - } - - /* Body */ - cJSON *child; - cJSON_ArrayForEach (child, stmts) { - mach_gen_statement (s, child); - } - - mach_emit_label (s, update_label); - - /* Update */ - if (update) { - mach_gen_expr (s, update); - } - mach_emit_jump (s, start_label); - - mach_emit_label (s, end_label); - - s->loop_break = old_break; - s->loop_continue = old_continue; - sys_free (start_label); - sys_free (update_label); - sys_free (end_label); - return; - } - - /* Return statement */ - if (strcmp (kind, "return") == 0) { - cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); - if (expr) { - int slot = mach_gen_expr (s, expr); - mach_emit_1 (s, "return", slot); - } else { - mach_emit_0 (s, "return_undef"); - } - return; - } - - /* Go (tail call) statement */ - if (strcmp (kind, "go") == 0) { - if (s->has_inner_function) { - mach_error (s, stmt, "'go' cannot be used in functions containing inner functions"); - return; - } - - cJSON *call_expr = cJSON_GetObjectItem (stmt, "expression"); - if (!call_expr) { - mach_error (s, stmt, "'go' requires a function call expression"); - return; - } - - const char *call_kind = cJSON_GetStringValue (cJSON_GetObjectItem (call_expr, "kind")); - if (!call_kind || strcmp (call_kind, "(") != 0) { - mach_error (s, stmt, "'go' requires a function call expression"); - return; - } - - cJSON *callee = cJSON_GetObjectItem (call_expr, "expression"); - cJSON *args_list = cJSON_GetObjectItem (call_expr, "list"); - - /* Compile arguments first */ - cJSON *arg_slots = cJSON_CreateArray (); - cJSON *arg; - cJSON_ArrayForEach (arg, args_list) { - int arg_slot = mach_gen_expr (s, arg); - cJSON_AddItemToArray (arg_slots, cJSON_CreateNumber (arg_slot)); - } - - const char *callee_kind = cJSON_GetStringValue (cJSON_GetObjectItem (callee, "kind")); - - if (callee_kind && strcmp (callee_kind, ".") == 0) { - /* Method tail call: go obj.method(args) */ - cJSON *obj_node = cJSON_GetObjectItem (callee, "left"); - cJSON *prop_node = cJSON_GetObjectItem (callee, "right"); - const char *prop = cJSON_GetStringValue (prop_node); - int obj_slot = mach_gen_expr (s, obj_node); - mach_emit_go_call_method (s, obj_slot, prop, arg_slots); - } else { - /* Regular tail call: go func(args) */ - int func_slot = mach_gen_expr (s, callee); - mach_emit_go_call (s, func_slot, arg_slots); - } - - cJSON_Delete (arg_slots); - return; - } - - /* Throw statement */ - if (strcmp (kind, "throw") == 0) { - cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); - int slot = mach_gen_expr (s, expr); - mach_emit_1 (s, "throw", slot); - return; - } - - /* Break statement */ - if (strcmp (kind, "break") == 0) { - if (s->loop_break) { - mach_emit_jump (s, s->loop_break); - } else { - mach_error (s, stmt, "'break' used outside of loop or switch"); - } - return; - } - - /* Continue statement */ - if (strcmp (kind, "continue") == 0) { - if (s->loop_continue) { - mach_emit_jump (s, s->loop_continue); - } else { - mach_error (s, stmt, "'continue' used outside of loop"); - } - return; - } - - /* Switch statement */ - if (strcmp (kind, "switch") == 0) { - cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); - cJSON *cases = cJSON_GetObjectItem (stmt, "cases"); - - int switch_val = mach_gen_expr (s, expr); - char *end_label = mach_gen_label (s, "switch_end"); - char *default_label = NULL; - - const char *old_break = s->loop_break; - s->loop_break = end_label; - - /* First pass: generate case tests and jumps */ - cJSON *case_node; - cJSON_ArrayForEach (case_node, cases) { - const char *case_kind = cJSON_GetStringValue (cJSON_GetObjectItem (case_node, "kind")); - if (strcmp (case_kind, "default") == 0) { - default_label = mach_gen_label (s, "switch_default"); - } else { - char *case_label = mach_gen_label (s, "switch_case"); - cJSON *case_expr = cJSON_GetObjectItem (case_node, "expression"); - int case_val = mach_gen_expr (s, case_expr); - int cmp_slot = mach_alloc_slot (s); - mach_emit_3 (s, "eq", cmp_slot, switch_val, case_val); - mach_emit_jump_cond (s, "jump_true", cmp_slot, case_label); - /* Store label in case node for second pass */ - cJSON_AddStringToObject (case_node, "_label", case_label); - sys_free (case_label); - } - } - - /* Jump to default or end */ - if (default_label) { - mach_emit_jump (s, default_label); - } else { - mach_emit_jump (s, end_label); - } - - /* Second pass: generate case bodies */ - cJSON_ArrayForEach (case_node, cases) { - const char *case_kind = cJSON_GetStringValue (cJSON_GetObjectItem (case_node, "kind")); - if (strcmp (case_kind, "default") == 0) { - mach_emit_label (s, default_label); - sys_free (default_label); - } else { - const char *label = cJSON_GetStringValue (cJSON_GetObjectItem (case_node, "_label")); - mach_emit_label (s, label); - } - cJSON *case_stmts = cJSON_GetObjectItem (case_node, "statements"); - cJSON *child; - cJSON_ArrayForEach (child, case_stmts) { - mach_gen_statement (s, child); - } - } - - mach_emit_label (s, end_label); - s->loop_break = old_break; - sys_free (end_label); - return; - } - - /* Try-catch-finally */ - if (strcmp (kind, "try") == 0) { - cJSON *try_stmts = cJSON_GetObjectItem (stmt, "statements"); - cJSON *catch_node = cJSON_GetObjectItem (stmt, "catch"); - cJSON *finally_node = cJSON_GetObjectItem (stmt, "finally"); - - char *catch_label = catch_node ? mach_gen_label (s, "catch") : NULL; - char *finally_label = finally_node ? mach_gen_label (s, "finally") : NULL; - char *end_label = mach_gen_label (s, "try_end"); - - /* try_begin */ - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("try_begin")); - cJSON_AddItemToArray (instr, cJSON_CreateString (catch_label ? catch_label : end_label)); - cJSON_AddItemToArray (instr, cJSON_CreateString (finally_label ? finally_label : end_label)); - cJSON_AddItemToArray (s->instructions, instr); - - /* Try body */ - cJSON *child; - cJSON_ArrayForEach (child, try_stmts) { - mach_gen_statement (s, child); - } - mach_emit_0 (s, "try_end"); - - /* Skip catch if no exception */ - if (catch_node) { - mach_emit_jump (s, finally_label ? finally_label : end_label); - } - - /* Catch block */ - if (catch_node) { - mach_emit_label (s, catch_label); - const char *err_name = cJSON_GetStringValue (cJSON_GetObjectItem (catch_node, "name")); - int err_slot = mach_alloc_slot (s); - mach_emit_1 (s, "catch_begin", err_slot); - if (err_name) { - mach_emit_set_var (s, err_name, err_slot, NULL); - } - cJSON *catch_stmts = cJSON_GetObjectItem (catch_node, "statements"); - cJSON_ArrayForEach (child, catch_stmts) { - mach_gen_statement (s, child); - } - mach_emit_0 (s, "catch_end"); - sys_free (catch_label); - } - - /* Finally block */ - if (finally_node) { - mach_emit_label (s, finally_label); - mach_emit_0 (s, "finally_begin"); - cJSON *finally_stmts = cJSON_GetObjectItem (finally_node, "statements"); - cJSON_ArrayForEach (child, finally_stmts) { - mach_gen_statement (s, child); - } - mach_emit_0 (s, "finally_end"); - sys_free (finally_label); - } - - mach_emit_label (s, end_label); - sys_free (end_label); - return; - } - - /* Function declaration (in statement context, add to functions) */ - if (strcmp (kind, "function") == 0) { - cJSON *name_obj = cJSON_GetObjectItem (stmt, "name"); - if (name_obj && cJSON_IsString (name_obj)) { - const char *name = cJSON_GetStringValue (name_obj); - int func_id = s->func_counter++; - cJSON *func = mach_gen_function (s, stmt); - cJSON_AddItemToArray (s->functions, func); - - int dest = mach_alloc_slot (s); - mach_emit_2 (s, "mkfunc", dest, func_id); - mach_emit_set_var (s, name, dest, stmt); + int r = mach_compile_expr(cs, right, slot); + if (r != slot) + mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0)); } return; } /* Expression statement (call) */ - if (strcmp (kind, "call") == 0) { - cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); - mach_gen_expr (s, expr); + if (strcmp(kind, "call") == 0) { + cJSON *expr = cJSON_GetObjectItem(stmt, "expression"); + if (expr) { + int save = cs->freereg; + mach_compile_expr(cs, expr, -1); + mach_free_reg_to(cs, save); + } return; } - /* Any other expression used as statement */ - mach_gen_expr (s, stmt); -} - -/* Generate code for a function */ -static cJSON *mach_gen_function (MachGenState *parent, cJSON *func_node) { - MachGenState s = {0}; - s.ctx = parent->ctx; - s.instructions = cJSON_CreateArray (); - s.data = parent->data; - s.functions = parent->functions; - s.parent = parent; - s.label_counter = parent->label_counter; - s.func_counter = parent->func_counter; - /* Share errors with parent - all errors go to same array */ - s.errors = parent->errors; - s.has_error = parent->has_error; - - cJSON *result = cJSON_CreateObject (); - - /* Function name */ - cJSON *name_obj = cJSON_GetObjectItem (func_node, "name"); - if (name_obj && cJSON_IsString (name_obj)) { - cJSON_AddStringToObject (result, "name", cJSON_GetStringValue (name_obj)); - } else { - cJSON_AddStringToObject (result, "name", ""); - } - - /* Check if arrow function */ - cJSON *is_arrow = cJSON_GetObjectItem (func_node, "arrow"); - s.is_arrow = is_arrow && cJSON_IsTrue (is_arrow); - - /* Parameters - slot 0 is this, slots 1..nr_args are args */ - cJSON *params = cJSON_GetObjectItem (func_node, "list"); - s.nr_args = cJSON_GetArraySize (params); - s.this_slot = 0; - s.nr_close_slots = 0; - s.nr_local_slots = 0; - - /* Register parameters as variables (slots 1..nr_args) */ - /* Function arguments are treated as constants - they cannot be reassigned */ - int param_slot = 1; - cJSON *param; - cJSON_ArrayForEach (param, params) { - const char *param_name = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name")); - if (param_name) { - mach_add_var (&s, param_name, param_slot, MACH_VAR_ARG, 1); /* is_const=1 */ - param_slot++; - } - } - - /* Temps start after this (0), args (1..nr_args), closed vars, and locals */ - /* For now start at 1 + nr_args, will be adjusted after scanning for vars */ - s.next_temp_slot = 1 + s.nr_args; - s.max_slot = 1 + s.nr_args; - - cJSON_AddNumberToObject (result, "nr_args", s.nr_args); - - cJSON *vars = cJSON_AddArrayToObject (result, "vars"); - cJSON_ArrayForEach (param, params) { - const char *param_name = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name")); - if (param_name) { - cJSON_AddItemToArray (vars, cJSON_CreateString (param_name)); - } - } - - /* Scan for var/def declarations and register them (hoisting) */ - cJSON *stmts = cJSON_GetObjectItem (func_node, "statements"); - cJSON *stmt; - cJSON_ArrayForEach (stmt, stmts) { - mach_scan_vars (&s, stmt); - } - - /* Scan for inner function definitions (for 'go' validation) */ - cJSON_ArrayForEach (stmt, stmts) { - mach_scan_inner_functions (&s, stmt); - } - - /* Adjust temp slot start after all locals are allocated */ - s.next_temp_slot = 1 + s.nr_args + s.nr_local_slots; - if (s.next_temp_slot > s.max_slot) s.max_slot = s.next_temp_slot; - - /* Generate body */ - cJSON_ArrayForEach (stmt, stmts) { - mach_gen_statement (&s, stmt); - } - - /* Add implicit return */ - mach_emit_0 (&s, "return_undef"); - - cJSON_AddNumberToObject (result, "nr_close_slots", s.nr_close_slots); - cJSON_AddNumberToObject (result, "nr_slots", s.max_slot + 1); - cJSON_AddItemToObject (result, "labels", cJSON_CreateObject ()); - cJSON_AddItemToObject (result, "instructions", s.instructions); - - /* Propagate counters and errors back */ - parent->label_counter = s.label_counter; - parent->func_counter = s.func_counter; - /* Propagate errors back to parent */ - if (s.errors && s.errors != parent->errors) { - /* Function created its own error array - merge to parent */ - if (!parent->errors) { - parent->errors = s.errors; + /* Return statement */ + if (strcmp(kind, "return") == 0) { + cJSON *expr = cJSON_GetObjectItem(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 { - /* Append all errors from function to parent */ - cJSON *err; - cJSON_ArrayForEach (err, s.errors) { - cJSON_AddItemToArray (parent->errors, cJSON_Duplicate (err, 1)); + mach_emit(cs, MACH_ABC(MACH_RETNIL, 0, 0, 0)); + } + return; + } + + /* Block */ + if (strcmp(kind, "block") == 0) { + cJSON *stmts = cJSON_GetObjectItem(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)); } - cJSON_Delete (s.errors); } - } - parent->has_error = parent->has_error || s.has_error; - - /* Free variable tracking arrays */ - for (int i = 0; i < s.var_count; i++) { - sys_free (s.vars[i].name); - } - if (s.vars) sys_free (s.vars); - for (int i = 0; i < s.closure_ref_count; i++) { - sys_free (s.closure_refs[i].name); - } - if (s.closure_refs) sys_free (s.closure_refs); - - return result; -} - -/* Generate machine code for the main program */ -static cJSON *mach_gen_program (MachGenState *s, cJSON *ast) { - cJSON *result = cJSON_CreateObject (); - - /* Filename */ - const char *filename = cJSON_GetStringValue (cJSON_GetObjectItem (ast, "filename")); - cJSON_AddStringToObject (result, "name", filename ? filename : ""); - - /* Data section (empty for now) */ - s->data = cJSON_AddObjectToObject (result, "data"); - - /* Functions array */ - s->functions = cJSON_AddArrayToObject (result, "functions"); - - /* Main scope setup */ - s->this_slot = 0; - s->nr_args = 0; - s->nr_close_slots = 0; - s->nr_local_slots = 0; - s->next_temp_slot = 1; /* slot 0 is this */ - s->max_slot = 1; - - /* Scan main scope for var/def declarations FIRST (hoisting) */ - /* This must happen before compiling functions so closure resolution works */ - cJSON *statements = cJSON_GetObjectItem (ast, "statements"); - cJSON *stmt; - cJSON_ArrayForEach (stmt, statements) { - mach_scan_vars (s, stmt); + return; } - /* Adjust temp slot start after all locals are allocated */ - s->next_temp_slot = 1 + s->nr_local_slots; - if (s->next_temp_slot > s->max_slot) s->max_slot = s->next_temp_slot; + /* If statement */ + if (strcmp(kind, "if") == 0) { + cJSON *cond = cJSON_GetObjectItem(stmt, "expression"); + if (!cond) cond = cJSON_GetObjectItem(stmt, "condition"); + cJSON *then_body = cJSON_GetObjectItem(stmt, "then"); + if (!then_body) then_body = cJSON_GetObjectItem(stmt, "block"); + cJSON *else_body = cJSON_GetObjectItem(stmt, "else"); - /* Process top-level function declarations */ - cJSON *functions = cJSON_GetObjectItem (ast, "functions"); - cJSON *func_node; - cJSON_ArrayForEach (func_node, functions) { - cJSON *name_obj = cJSON_GetObjectItem (func_node, "name"); - if (name_obj && cJSON_IsString (name_obj)) { - const char *name = cJSON_GetStringValue (name_obj); - int func_id = s->func_counter++; - cJSON *func = mach_gen_function (s, func_node); - cJSON_AddItemToArray (s->functions, func); + 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); - int dest = mach_alloc_slot (s); - mach_emit_2 (s, "mkfunc", dest, func_id); - mach_emit_set_var (s, name, dest, func_node); - } - } - - /* Generate main code - track last expression result for implicit return */ - int last_expr_slot = -1; - cJSON_ArrayForEach (stmt, statements) { - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "kind")); - if (kind) { - /* Check if this is an expression statement - if so, track result slot */ - if (strcmp(kind, "call") == 0) { - /* call statement wraps an expression */ - cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); - last_expr_slot = mach_gen_expr (s, expr); - } else if (strcmp(kind, "return") == 0 || strcmp(kind, "throw") == 0 || - strcmp(kind, "break") == 0 || strcmp(kind, "continue") == 0) { - /* Control flow - don't track as last expression */ - mach_gen_statement (s, stmt); - last_expr_slot = -1; - } else if (strcmp(kind, "var") == 0 || strcmp(kind, "def") == 0 || - strcmp(kind, "function") == 0 || strcmp(kind, "block") == 0 || - strcmp(kind, "if") == 0 || strcmp(kind, "while") == 0 || - strcmp(kind, "do") == 0 || strcmp(kind, "for") == 0 || - strcmp(kind, "switch") == 0 || strcmp(kind, "try") == 0) { - /* Statements - don't track */ - mach_gen_statement (s, stmt); - last_expr_slot = -1; + /* Compile then branch */ + if (then_body) { + cJSON *stmts = cJSON_GetObjectItem(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 { - /* Assume it's an expression - generate and track */ - last_expr_slot = mach_gen_expr (s, stmt); + mach_compile_stmt(cs, then_body); } + } + + 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 */ + cJSON *stmts = cJSON_GetObjectItem(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); + } + + /* Patch jmpend */ + offset = mach_current_pc(cs) - (jmpend_pc + 1); + cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset); } else { - mach_gen_statement (s, stmt); + /* 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_GetObjectItem(stmt, "expression"); + if (!cond) cond = cJSON_GetObjectItem(stmt, "condition"); + cJSON *body = cJSON_GetObjectItem(stmt, "block"); + if (!body) body = cJSON_GetObjectItem(stmt, "body"); + + 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 */ + if (body) { + cJSON *stmts = cJSON_GetObjectItem(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, body); + } + } + + /* 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); + return; + } + + /* For loop */ + if (strcmp(kind, "for") == 0) { + cJSON *init = cJSON_GetObjectItem(stmt, "initial"); + cJSON *cond = cJSON_GetObjectItem(stmt, "condition"); + if (!cond) cond = cJSON_GetObjectItem(stmt, "expression"); + cJSON *update = cJSON_GetObjectItem(stmt, "update"); + cJSON *body = cJSON_GetObjectItem(stmt, "block"); + if (!body) body = cJSON_GetObjectItem(stmt, "body"); + + /* 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 */ + if (body) { + cJSON *stmts = cJSON_GetObjectItem(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, body); + } + } + + /* Update */ + if (update) { + int save = cs->freereg; + mach_compile_expr(cs, update, -1); + mach_free_reg_to(cs, save); + } + + /* 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); + } + return; + } + + /* Throw statement */ + if (strcmp(kind, "throw") == 0) { + cJSON *expr = cJSON_GetObjectItem(stmt, "expression"); + int save = cs->freereg; + int r = mach_compile_expr(cs, expr, -1); + mach_emit(cs, MACH_ABC(MACH_THROW, r, 0, 0)); + mach_free_reg_to(cs, save); + return; + } + + /* Fallback: treat as expression statement */ + { + cJSON *expr = cJSON_GetObjectItem(stmt, "expression"); + if (expr) { + int save = cs->freereg; + mach_compile_expr(cs, expr, -1); + mach_free_reg_to(cs, save); } } - - /* Add implicit return of last expression (or null) for main module */ - if (last_expr_slot >= 0) { - mach_emit_1 (s, "return", last_expr_slot); - } else { - mach_emit_0 (s, "return_undef"); - } - - /* Main section */ - cJSON *main_obj = cJSON_CreateObject (); - cJSON_AddNumberToObject (main_obj, "nr_args", 0); - cJSON_AddNumberToObject (main_obj, "nr_close_slots", 0); - cJSON_AddNumberToObject (main_obj, "nr_slots", s->max_slot + 1); - cJSON_AddItemToObject (main_obj, "vars", cJSON_CreateArray ()); - cJSON_AddItemToObject (main_obj, "labels", cJSON_CreateObject ()); - cJSON_AddItemToObject (main_obj, "instructions", s->instructions); - cJSON_AddItemToObject (result, "main", main_obj); - - return result; } -char *JS_Mcode (JSContext *ctx, const char *ast_json) { - cJSON *ast = cJSON_Parse (ast_json); +/* ---- Link pass: resolve GETNAME/SETNAME to GETGLOBAL/SETGLOBAL ---- */ + +static void mach_link_names(MachCompState *cs, JSValue env) { + for (int i = 0; i < cs->code_count; i++) { + MachInstr32 instr = cs->code[i]; + int op = MACH_GET_OP(instr); + if (op == MACH_GETNAME) { + int a = MACH_GET_A(instr); + int bx = MACH_GET_Bx(instr); + /* Patch to GETGLOBAL — the VM will look up K(Bx) in global_obj */ + cs->code[i] = MACH_ABx(MACH_GETGLOBAL, a, bx); + } else if (op == MACH_SETNAME) { + int a = MACH_GET_A(instr); + int bx = MACH_GET_Bx(instr); + cs->code[i] = MACH_ABx(MACH_SETGLOBAL, a, bx); + } + } +} + +/* ---- Top-level compiler ---- */ + +static JSCodeRegister *mach_compile_program(MachCompState *cs, cJSON *ast, JSValue env) { + cJSON *stmts = cJSON_GetObjectItem(ast, "statements"); + if (!stmts || !cJSON_IsArray(stmts)) return NULL; + + /* Scan for var/def declarations (hoisting) */ + mach_scan_vars(cs, stmts); + + /* 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)); + + /* Link: resolve GETNAME/SETNAME */ + mach_link_names(cs, env); + + /* Build JSCodeRegister */ + JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister)); + code->arity = 0; + code->nr_slots = cs->maxreg; + code->nr_close_slots = 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 = JS_NULL; + + return code; +} + +/* Public API: compile AST JSON to JSCodeRegister */ +JSCodeRegister *JS_CompileMach(JSContext *ctx, const char *ast_json, JSValue env) { + cJSON *ast = cJSON_Parse(ast_json); if (!ast) return NULL; - MachGenState s = {0}; - s.ctx = ctx; - s.instructions = cJSON_CreateArray (); - s.errors = NULL; - s.has_error = 0; + MachCompState cs = {0}; + cs.ctx = ctx; + cs.freereg = 1; /* slot 0 = this */ + cs.maxreg = 1; - cJSON *mach = mach_gen_program (&s, ast); - cJSON_Delete (ast); + JSCodeRegister *code = mach_compile_program(&cs, ast, env); - /* Free any allocated variable tracking */ - for (int i = 0; i < s.var_count; i++) { - sys_free (s.vars[i].name); - } - if (s.vars) sys_free (s.vars); - for (int i = 0; i < s.closure_ref_count; i++) { - sys_free (s.closure_refs[i].name); - } - if (s.closure_refs) sys_free (s.closure_refs); - - if (!mach) { - if (s.errors) cJSON_Delete (s.errors); - return NULL; - } - - /* Add errors to output if any */ - if (s.errors) { - cJSON_AddItemToObject (mach, "errors", s.errors); - } - - char *json = cJSON_Print (mach); - cJSON_Delete (mach); - return json; -} - -/* ============================================================ - MACH Linker — MCODE to binary bytecode - ============================================================ */ - -/* Opcode name to MachOpcode mapping */ -static MachOpcode mach_opcode_from_string(const char *name) { - if (!name) return MACH_OP_NOP; - if (strcmp(name, "access") == 0) return MACH_OP_ACCESS; - if (strcmp(name, "null") == 0) return MACH_OP_NULL; - if (strcmp(name, "true") == 0) return MACH_OP_TRUE; - if (strcmp(name, "false") == 0) return MACH_OP_FALSE; - if (strcmp(name, "move") == 0) return MACH_OP_MOVE; - if (strcmp(name, "add") == 0) return MACH_OP_ADD; - if (strcmp(name, "sub") == 0) return MACH_OP_SUB; - if (strcmp(name, "mul") == 0) return MACH_OP_MUL; - if (strcmp(name, "div") == 0) return MACH_OP_DIV; - if (strcmp(name, "mod") == 0) return MACH_OP_MOD; - if (strcmp(name, "pow") == 0) return MACH_OP_POW; - if (strcmp(name, "neg") == 0) return MACH_OP_NEG; - if (strcmp(name, "inc") == 0) return MACH_OP_INC; - if (strcmp(name, "dec") == 0) return MACH_OP_DEC; - if (strcmp(name, "eq") == 0) return MACH_OP_EQ; - if (strcmp(name, "ne") == 0) return MACH_OP_NE; - if (strcmp(name, "lt") == 0) return MACH_OP_LT; - if (strcmp(name, "le") == 0) return MACH_OP_LE; - if (strcmp(name, "gt") == 0) return MACH_OP_GT; - if (strcmp(name, "ge") == 0) return MACH_OP_GE; - if (strcmp(name, "strict_eq") == 0) return MACH_OP_STRICT_EQ; - if (strcmp(name, "strict_ne") == 0) return MACH_OP_STRICT_NE; - if (strcmp(name, "not") == 0) return MACH_OP_NOT; - if (strcmp(name, "bitnot") == 0) return MACH_OP_BITNOT; - if (strcmp(name, "bitand") == 0) return MACH_OP_BITAND; - if (strcmp(name, "bitor") == 0) return MACH_OP_BITOR; - if (strcmp(name, "bitxor") == 0) return MACH_OP_BITXOR; - if (strcmp(name, "shl") == 0) return MACH_OP_SHL; - if (strcmp(name, "shr") == 0) return MACH_OP_SHR; - if (strcmp(name, "ushr") == 0) return MACH_OP_USHR; - if (strcmp(name, "load_prop") == 0) return MACH_OP_LOAD_PROP; - if (strcmp(name, "store_prop") == 0) return MACH_OP_STORE_PROP; - if (strcmp(name, "load_idx") == 0) return MACH_OP_LOAD_IDX; - if (strcmp(name, "store_idx") == 0) return MACH_OP_STORE_IDX; - if (strcmp(name, "delete_prop") == 0) return MACH_OP_DELETE_PROP; - if (strcmp(name, "delete_idx") == 0) return MACH_OP_DELETE_IDX; - if (strcmp(name, "get") == 0) return MACH_OP_GET; - if (strcmp(name, "put") == 0) return MACH_OP_PUT; - if (strcmp(name, "cap_name") == 0) return MACH_OP_CAP_NAME; - if (strcmp(name, "set_var") == 0) return MACH_OP_SET_VAR; - if (strcmp(name, "jump") == 0) return MACH_OP_JUMP; - if (strcmp(name, "jump_true") == 0) return MACH_OP_JUMP_TRUE; - if (strcmp(name, "jump_false") == 0) return MACH_OP_JUMP_FALSE; - if (strcmp(name, "jump_not_null") == 0) return MACH_OP_JUMP_NOT_NULL; - if (strcmp(name, "frame") == 0) return MACH_OP_FRAME; - if (strcmp(name, "set_this") == 0) return MACH_OP_SET_THIS; - if (strcmp(name, "arg") == 0) return MACH_OP_ARG; - if (strcmp(name, "invoke") == 0) return MACH_OP_INVOKE; - if (strcmp(name, "goframe") == 0) return MACH_OP_GOFRAME; - if (strcmp(name, "goinvoke") == 0) return MACH_OP_GOINVOKE; - if (strcmp(name, "mkfunc") == 0) return MACH_OP_MKFUNC; - if (strcmp(name, "mkfunc_arrow") == 0) return MACH_OP_MKFUNC_ARROW; - if (strcmp(name, "mkrecord") == 0) return MACH_OP_MKRECORD; - if (strcmp(name, "mkarray") == 0) return MACH_OP_MKARRAY; - if (strcmp(name, "mknew") == 0) return MACH_OP_MKNEW; - if (strcmp(name, "return") == 0) return MACH_OP_RETURN; - if (strcmp(name, "return_undef") == 0) return MACH_OP_RETURN_UNDEF; - if (strcmp(name, "throw") == 0) return MACH_OP_THROW; - if (strcmp(name, "try_begin") == 0) return MACH_OP_TRY_BEGIN; - if (strcmp(name, "try_end") == 0) return MACH_OP_TRY_END; - if (strcmp(name, "catch_begin") == 0) return MACH_OP_CATCH_BEGIN; - if (strcmp(name, "catch_end") == 0) return MACH_OP_CATCH_END; - if (strcmp(name, "finally_begin") == 0) return MACH_OP_FINALLY_BEGIN; - if (strcmp(name, "finally_end") == 0) return MACH_OP_FINALLY_END; - return MACH_OP_NOP; -} - -/* Label tracking for linker */ -typedef struct { - char *name; - uint32_t instr_idx; -} MachLabel; - -typedef struct { - MachLabel *labels; - int label_count; - int label_capacity; -} MachLabelTable; - -static void mach_label_table_init(MachLabelTable *t) { - t->labels = NULL; - t->label_count = 0; - t->label_capacity = 0; -} - -static void mach_label_table_free(MachLabelTable *t) { - for (int i = 0; i < t->label_count; i++) { - js_free_rt(t->labels[i].name); - } - js_free_rt(t->labels); -} - -static void mach_label_table_add(MachLabelTable *t, const char *name, uint32_t idx) { - if (t->label_count >= t->label_capacity) { - int new_cap = t->label_capacity ? t->label_capacity * 2 : 16; - t->labels = js_realloc_rt(t->labels, new_cap * sizeof(MachLabel)); - t->label_capacity = new_cap; - } - t->labels[t->label_count].name = js_strdup_rt(name); - t->labels[t->label_count].instr_idx = idx; - t->label_count++; -} - -static int mach_label_table_find(MachLabelTable *t, const char *name) { - for (int i = 0; i < t->label_count; i++) { - if (strcmp(t->labels[i].name, name) == 0) { - return t->labels[i].instr_idx; - } - } - return -1; -} - -/* Constant pool builder */ -typedef struct { - JSValue *values; - int count; - int capacity; -} MachCPool; - -static void mach_cpool_init(MachCPool *cp) { - cp->values = NULL; - cp->count = 0; - cp->capacity = 0; -} - -static void mach_cpool_free(MachCPool *cp) { - js_free_rt(cp->values); -} - -static int mach_cpool_add(MachCPool *cp, JSValue v) { - /* Check if already in pool */ - for (int i = 0; i < cp->count; i++) { - if (cp->values[i] == v) return i; - } - if (cp->count >= cp->capacity) { - int new_cap = cp->capacity ? cp->capacity * 2 : 16; - cp->values = js_realloc_rt(cp->values, new_cap * sizeof(JSValue)); - cp->capacity = new_cap; - } - cp->values[cp->count] = v; - return cp->count++; -} - -/* Forward declaration */ -static JSCodeRegister *js_link_mach_unit(JSContext *ctx, cJSON *unit, JSValue env, - cJSON *functions_array, JSCodeRegister **linked_funcs); - -/* Link a single function/unit from JSON to JSCodeRegister */ -static JSCodeRegister *js_link_mach_unit(JSContext *ctx, cJSON *unit, JSValue env, - cJSON *functions_array, JSCodeRegister **linked_funcs) { - JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister)); - if (!code) return NULL; - - /* Parse metadata */ - cJSON *nr_args = cJSON_GetObjectItem(unit, "nr_args"); - cJSON *nr_slots = cJSON_GetObjectItem(unit, "nr_slots"); - cJSON *nr_close = cJSON_GetObjectItem(unit, "nr_close_slots"); - cJSON *name_obj = cJSON_GetObjectItem(unit, "name"); - - code->arity = nr_args ? (uint16_t)cJSON_GetNumberValue(nr_args) : 0; - code->nr_slots = nr_slots ? (uint16_t)cJSON_GetNumberValue(nr_slots) : 1; - code->nr_close_slots = nr_close ? (uint16_t)cJSON_GetNumberValue(nr_close) : 0; - code->entry_point = 0; - - if (name_obj && cJSON_IsString(name_obj)) { - code->name = JS_NewString(ctx, cJSON_GetStringValue(name_obj)); - } else { - code->name = JS_NULL; - } - - /* First pass: collect labels and count instructions */ - cJSON *instructions = cJSON_GetObjectItem(unit, "instructions"); - if (!instructions) { - js_free_rt(code); - return NULL; - } - - MachLabelTable labels; - mach_label_table_init(&labels); - - MachCPool cpool; - mach_cpool_init(&cpool); - - /* Count actual instructions (excluding label strings) */ - int instr_count = 0; - cJSON *item; - cJSON_ArrayForEach(item, instructions) { - if (cJSON_IsString(item)) { - /* Label definition */ - mach_label_table_add(&labels, cJSON_GetStringValue(item), instr_count); - } else if (cJSON_IsArray(item)) { - instr_count++; - } - } - - /* Allocate instructions */ - code->instr_count = instr_count; - code->instructions = js_mallocz_rt(instr_count * sizeof(MachInstr)); - if (!code->instructions) { - mach_label_table_free(&labels); - mach_cpool_free(&cpool); - js_free_rt(code); - return NULL; - } - - /* Second pass: compile instructions */ - int idx = 0; - cJSON_ArrayForEach(item, instructions) { - if (cJSON_IsString(item)) { - /* Skip labels in second pass */ - continue; - } - if (!cJSON_IsArray(item)) continue; - - MachInstr *instr = &code->instructions[idx]; - instr->a = instr->b = instr->c = 0; - - cJSON *op = cJSON_GetArrayItem(item, 0); - if (!op || !cJSON_IsString(op)) { - idx++; - continue; - } - - const char *opname = cJSON_GetStringValue(op); - instr->opcode = mach_opcode_from_string(opname); - - /* Parse operands based on opcode */ - switch (instr->opcode) { - case MACH_OP_ACCESS: { - /* access dest, literal */ - cJSON *dest = cJSON_GetArrayItem(item, 1); - cJSON *lit = cJSON_GetArrayItem(item, 2); - instr->a = dest ? (int16_t)cJSON_GetNumberValue(dest) : 0; - if (lit) { - if (cJSON_IsNumber(lit)) { - /* Add number to cpool */ - JSValue v = JS_NewFloat64(ctx, cJSON_GetNumberValue(lit)); - instr->b = mach_cpool_add(&cpool, v); - } else if (cJSON_IsString(lit)) { - /* Add string to cpool */ - JSValue v = JS_NewString(ctx, cJSON_GetStringValue(lit)); - instr->b = mach_cpool_add(&cpool, v); - } - } - break; - } - case MACH_OP_NULL: - case MACH_OP_TRUE: - case MACH_OP_FALSE: - case MACH_OP_RETURN: { - /* op dest/src */ - cJSON *a = cJSON_GetArrayItem(item, 1); - instr->a = a ? (int16_t)cJSON_GetNumberValue(a) : 0; - break; - } - case MACH_OP_MOVE: - case MACH_OP_NOT: - case MACH_OP_NEG: - case MACH_OP_BITNOT: - case MACH_OP_INC: - case MACH_OP_DEC: - case MACH_OP_INVOKE: - case MACH_OP_SET_THIS: - case MACH_OP_MKFUNC: - case MACH_OP_MKFUNC_ARROW: { - /* op dest, src */ - cJSON *a = cJSON_GetArrayItem(item, 1); - cJSON *b = cJSON_GetArrayItem(item, 2); - instr->a = a ? (int16_t)cJSON_GetNumberValue(a) : 0; - instr->b = b ? (int16_t)cJSON_GetNumberValue(b) : 0; - break; - } - case MACH_OP_ADD: - case MACH_OP_SUB: - case MACH_OP_MUL: - case MACH_OP_DIV: - case MACH_OP_MOD: - case MACH_OP_POW: - case MACH_OP_EQ: - case MACH_OP_NE: - case MACH_OP_LT: - case MACH_OP_LE: - case MACH_OP_GT: - case MACH_OP_GE: - case MACH_OP_STRICT_EQ: - case MACH_OP_STRICT_NE: - case MACH_OP_BITAND: - case MACH_OP_BITOR: - case MACH_OP_BITXOR: - case MACH_OP_SHL: - case MACH_OP_SHR: - case MACH_OP_USHR: - case MACH_OP_LOAD_IDX: - case MACH_OP_STORE_IDX: - case MACH_OP_DELETE_IDX: - case MACH_OP_GET: - case MACH_OP_PUT: - case MACH_OP_FRAME: - case MACH_OP_GOFRAME: - case MACH_OP_ARG: { - /* op a, b, c */ - cJSON *a = cJSON_GetArrayItem(item, 1); - cJSON *b = cJSON_GetArrayItem(item, 2); - cJSON *c = cJSON_GetArrayItem(item, 3); - instr->a = a ? (int16_t)cJSON_GetNumberValue(a) : 0; - instr->b = b ? (int16_t)cJSON_GetNumberValue(b) : 0; - instr->c = c ? (int16_t)cJSON_GetNumberValue(c) : 0; - break; - } - case MACH_OP_LOAD_PROP: { - /* load_prop dest, obj, "prop" */ - cJSON *dest = cJSON_GetArrayItem(item, 1); - cJSON *obj = cJSON_GetArrayItem(item, 2); - cJSON *prop = cJSON_GetArrayItem(item, 3); - instr->a = dest ? (int16_t)cJSON_GetNumberValue(dest) : 0; - instr->b = obj ? (int16_t)cJSON_GetNumberValue(obj) : 0; - if (prop && cJSON_IsString(prop)) { - JSValue v = JS_NewString(ctx, cJSON_GetStringValue(prop)); - instr->c = mach_cpool_add(&cpool, v); - } - break; - } - case MACH_OP_STORE_PROP: { - /* store_prop obj, val, "prop" */ - cJSON *obj = cJSON_GetArrayItem(item, 1); - cJSON *val = cJSON_GetArrayItem(item, 2); - cJSON *prop = cJSON_GetArrayItem(item, 3); - instr->a = obj ? (int16_t)cJSON_GetNumberValue(obj) : 0; - instr->b = val ? (int16_t)cJSON_GetNumberValue(val) : 0; - if (prop && cJSON_IsString(prop)) { - JSValue v = JS_NewString(ctx, cJSON_GetStringValue(prop)); - instr->c = mach_cpool_add(&cpool, v); - } - break; - } - case MACH_OP_CAP_NAME: { - /* cap_name dest, "name" - resolve to env or global */ - cJSON *dest = cJSON_GetArrayItem(item, 1); - cJSON *name = cJSON_GetArrayItem(item, 2); - instr->a = dest ? (int16_t)cJSON_GetNumberValue(dest) : 0; - if (name && cJSON_IsString(name)) { - const char *varname = cJSON_GetStringValue(name); - /* Try to resolve in env first */ - if (!JS_IsNull(env)) { - JSValue key = JS_NewString(ctx, varname); - JSValue val = JS_GetProperty(ctx, env, key); - if (!JS_IsNull(val) && !JS_IsException(val)) { - /* Found in env - convert to GET_ENV_SLOT */ - /* For now, just add name to cpool and resolve at runtime */ - instr->opcode = MACH_OP_GET_ENV_SLOT; - instr->b = mach_cpool_add(&cpool, key); - } else { - /* Try global */ - val = JS_GetProperty(ctx, ctx->global_obj, key); - if (!JS_IsNull(val) && !JS_IsException(val)) { - instr->opcode = MACH_OP_GET_GLOBAL_SLOT; - instr->b = mach_cpool_add(&cpool, key); - } else { - /* Unresolved - keep as cap_name with name in cpool */ - instr->b = mach_cpool_add(&cpool, key); - } - } - } else { - /* No env - try global */ - JSValue key = JS_NewString(ctx, varname); - JSValue val = JS_GetProperty(ctx, ctx->global_obj, key); - if (!JS_IsNull(val) && !JS_IsException(val)) { - instr->opcode = MACH_OP_GET_GLOBAL_SLOT; - instr->b = mach_cpool_add(&cpool, key); - } else { - instr->b = mach_cpool_add(&cpool, key); - } - } - } - break; - } - case MACH_OP_SET_VAR: { - /* set_var "name", src - resolve to env or global */ - cJSON *name = cJSON_GetArrayItem(item, 1); - cJSON *src = cJSON_GetArrayItem(item, 2); - instr->b = src ? (int16_t)cJSON_GetNumberValue(src) : 0; - if (name && cJSON_IsString(name)) { - const char *varname = cJSON_GetStringValue(name); - JSValue key = JS_NewString(ctx, varname); - /* Try env first, then global */ - if (!JS_IsNull(env)) { - JSValue val = JS_GetProperty(ctx, env, key); - if (!JS_IsNull(val) && !JS_IsException(val)) { - instr->opcode = MACH_OP_SET_ENV_SLOT; - instr->a = mach_cpool_add(&cpool, key); - } else { - instr->opcode = MACH_OP_SET_GLOBAL_SLOT; - instr->a = mach_cpool_add(&cpool, key); - } - } else { - instr->opcode = MACH_OP_SET_GLOBAL_SLOT; - instr->a = mach_cpool_add(&cpool, key); - } - } - break; - } - case MACH_OP_JUMP: { - /* jump label */ - cJSON *label = cJSON_GetArrayItem(item, 1); - if (label && cJSON_IsString(label)) { - int target = mach_label_table_find(&labels, cJSON_GetStringValue(label)); - instr->a = target >= 0 ? target : 0; - } - break; - } - case MACH_OP_JUMP_TRUE: - case MACH_OP_JUMP_FALSE: - case MACH_OP_JUMP_NOT_NULL: { - /* jump_cond cond, label */ - cJSON *cond = cJSON_GetArrayItem(item, 1); - cJSON *label = cJSON_GetArrayItem(item, 2); - instr->a = cond ? (int16_t)cJSON_GetNumberValue(cond) : 0; - if (label && cJSON_IsString(label)) { - int target = mach_label_table_find(&labels, cJSON_GetStringValue(label)); - instr->b = target >= 0 ? target : 0; - } - break; - } - case MACH_OP_TRY_BEGIN: { - /* try_begin catch_label */ - cJSON *label = cJSON_GetArrayItem(item, 1); - if (label && cJSON_IsString(label)) { - int target = mach_label_table_find(&labels, cJSON_GetStringValue(label)); - instr->a = target >= 0 ? target : 0; - } - break; - } - case MACH_OP_GOINVOKE: - case MACH_OP_CATCH_BEGIN: { - /* op dest */ - cJSON *a = cJSON_GetArrayItem(item, 1); - instr->a = a ? (int16_t)cJSON_GetNumberValue(a) : 0; - break; - } - case MACH_OP_RETURN_UNDEF: - case MACH_OP_TRY_END: - case MACH_OP_CATCH_END: - case MACH_OP_FINALLY_BEGIN: - case MACH_OP_FINALLY_END: - case MACH_OP_NOP: - /* No operands */ - break; - case MACH_OP_MKRECORD: - case MACH_OP_MKARRAY: { - /* mkrecord dest, count or mkarray dest, count */ - cJSON *dest = cJSON_GetArrayItem(item, 1); - cJSON *count = cJSON_GetArrayItem(item, 2); - instr->a = dest ? (int16_t)cJSON_GetNumberValue(dest) : 0; - instr->b = count ? (int16_t)cJSON_GetNumberValue(count) : 0; - break; - } - default: - /* Handle remaining cases */ - break; - } - - idx++; - } - - /* Copy cpool to code */ - code->cpool_count = cpool.count; - if (cpool.count > 0) { - code->cpool = js_malloc_rt(cpool.count * sizeof(JSValue)); - memcpy(code->cpool, cpool.values, cpool.count * sizeof(JSValue)); - } - - /* Link nested functions */ - int func_array_size = functions_array ? cJSON_GetArraySize(functions_array) : 0; - code->func_count = func_array_size; - if (func_array_size > 0) { - code->functions = js_mallocz_rt(func_array_size * sizeof(JSCodeRegister*)); - for (int i = 0; i < func_array_size; i++) { - if (linked_funcs && linked_funcs[i]) { - code->functions[i] = linked_funcs[i]; - } else { - cJSON *func = cJSON_GetArrayItem(functions_array, i); - code->functions[i] = js_link_mach_unit(ctx, func, env, NULL, NULL); - } - } - } - - /* Copy label info for debugging before freeing the table */ - if (labels.label_count > 0) { - code->label_count = labels.label_count; - code->label_offsets = js_mallocz_rt(labels.label_count * sizeof(uint32_t)); - code->label_names = js_mallocz_rt(labels.label_count * sizeof(char *)); - for (int i = 0; i < labels.label_count; i++) { - code->label_offsets[i] = labels.labels[i].instr_idx; - code->label_names[i] = js_strdup_rt(labels.labels[i].name); - } - } - - mach_label_table_free(&labels); - mach_cpool_free(&cpool); + /* Free var table (code/cpool/functions are owned by JSCodeRegister now) */ + for (int i = 0; i < cs.var_count; i++) + sys_free(cs.vars[i].name); + sys_free(cs.vars); + cJSON_Delete(ast); return code; } @@ -33135,11 +31779,6 @@ static void js_free_code_register(JSCodeRegister *code) { if (!code) return; js_free_rt(code->instructions); js_free_rt(code->cpool); - js_free_rt(code->label_offsets); - for (uint32_t i = 0; i < code->label_count; i++) { - js_free_rt(code->label_names[i]); - } - js_free_rt(code->label_names); for (uint32_t i = 0; i < code->func_count; i++) { js_free_code_register(code->functions[i]); } @@ -33147,34 +31786,6 @@ static void js_free_code_register(JSCodeRegister *code) { js_free_rt(code); } -/* Link JSON machine code to JSCodeRegister */ -static JSCodeRegister *js_link_mach(JSContext *ctx, const char *mach_json, JSValue env) { - cJSON *mach = cJSON_Parse(mach_json); - if (!mach) return NULL; - - /* Get functions array */ - cJSON *functions = cJSON_GetObjectItem(mach, "functions"); - int func_count = functions ? cJSON_GetArraySize(functions) : 0; - - /* Link all nested functions first */ - JSCodeRegister **linked_funcs = NULL; - if (func_count > 0) { - linked_funcs = js_mallocz_rt(func_count * sizeof(JSCodeRegister*)); - for (int i = 0; i < func_count; i++) { - cJSON *func = cJSON_GetArrayItem(functions, i); - linked_funcs[i] = js_link_mach_unit(ctx, func, env, NULL, NULL); - } - } - - /* Link main */ - cJSON *main_obj = cJSON_GetObjectItem(mach, "main"); - JSCodeRegister *code = js_link_mach_unit(ctx, main_obj, env, functions, linked_funcs); - - js_free_rt(linked_funcs); - cJSON_Delete(mach); - - return code; -} /* ============================================================ MACH VM — register-based bytecode interpreter @@ -33217,60 +31828,60 @@ static JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JS } /* Binary operations helper */ -static JSValue reg_vm_binop(JSContext *ctx, MachOpcode op, JSValue a, JSValue b) { +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_OP_ADD: { + 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_OP_SUB: { + 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_OP_MUL: { + 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_OP_DIV: - if (ib == 0) return JS_NULL; /* Division by zero -> null */ + 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_OP_MOD: + case MACH_MOD: if (ib == 0) return JS_NULL; return JS_NewInt32(ctx, ia % ib); - case MACH_OP_EQ: + case MACH_EQ: return JS_NewBool(ctx, ia == ib); - case MACH_OP_NE: + case MACH_NEQ: return JS_NewBool(ctx, ia != ib); - case MACH_OP_LT: + case MACH_LT: return JS_NewBool(ctx, ia < ib); - case MACH_OP_LE: + case MACH_LE: return JS_NewBool(ctx, ia <= ib); - case MACH_OP_GT: + case MACH_GT: return JS_NewBool(ctx, ia > ib); - case MACH_OP_GE: + case MACH_GE: return JS_NewBool(ctx, ia >= ib); - case MACH_OP_BITAND: + case MACH_BAND: return JS_NewInt32(ctx, ia & ib); - case MACH_OP_BITOR: + case MACH_BOR: return JS_NewInt32(ctx, ia | ib); - case MACH_OP_BITXOR: + case MACH_BXOR: return JS_NewInt32(ctx, ia ^ ib); - case MACH_OP_SHL: + case MACH_SHL: return JS_NewInt32(ctx, ia << (ib & 31)); - case MACH_OP_SHR: + case MACH_SHR: return JS_NewInt32(ctx, ia >> (ib & 31)); - case MACH_OP_USHR: + case MACH_USHR: return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); default: break; @@ -33283,47 +31894,45 @@ static JSValue reg_vm_binop(JSContext *ctx, MachOpcode op, JSValue a, JSValue b) return JS_EXCEPTION; switch (op) { - case MACH_OP_ADD: + case MACH_ADD: return JS_NewFloat64(ctx, da + db); - case MACH_OP_SUB: + case MACH_SUB: return JS_NewFloat64(ctx, da - db); - case MACH_OP_MUL: + case MACH_MUL: return JS_NewFloat64(ctx, da * db); - case MACH_OP_DIV: + case MACH_DIV: return JS_NewFloat64(ctx, da / db); - case MACH_OP_MOD: + case MACH_MOD: return JS_NewFloat64(ctx, fmod(da, db)); - case MACH_OP_POW: + case MACH_POW: return JS_NewFloat64(ctx, pow(da, db)); - case MACH_OP_EQ: - case MACH_OP_STRICT_EQ: + case MACH_EQ: return JS_NewBool(ctx, da == db); - case MACH_OP_NE: - case MACH_OP_STRICT_NE: + case MACH_NEQ: return JS_NewBool(ctx, da != db); - case MACH_OP_LT: + case MACH_LT: return JS_NewBool(ctx, da < db); - case MACH_OP_LE: + case MACH_LE: return JS_NewBool(ctx, da <= db); - case MACH_OP_GT: + case MACH_GT: return JS_NewBool(ctx, da > db); - case MACH_OP_GE: + case MACH_GE: return JS_NewBool(ctx, da >= db); - case MACH_OP_BITAND: - case MACH_OP_BITOR: - case MACH_OP_BITXOR: - case MACH_OP_SHL: - case MACH_OP_SHR: - case MACH_OP_USHR: { + 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_OP_BITAND: return JS_NewInt32(ctx, ia & ib); - case MACH_OP_BITOR: return JS_NewInt32(ctx, ia | ib); - case MACH_OP_BITXOR: return JS_NewInt32(ctx, ia ^ ib); - case MACH_OP_SHL: return JS_NewInt32(ctx, ia << (ib & 31)); - case MACH_OP_SHR: return JS_NewInt32(ctx, ia >> (ib & 31)); - case MACH_OP_USHR: return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); + 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; } } @@ -33362,7 +31971,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, frame_ref.val = JS_MKPTR(frame); /* Setup initial frame */ - frame->slots[0] = this_obj; /* slot 0 is this */ + frame->slots[0] = this_obj; /* slot 0 = this */ /* Copy arguments */ for (int i = 0; i < argc && i < code->arity; i++) { @@ -33372,549 +31981,433 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, uint32_t pc = code->entry_point; JSValue result = JS_NULL; - /* Execution loop */ + /* Execution loop — 32-bit instruction dispatch */ for (;;) { - /* Check for interrupt periodically */ if (reg_vm_check_interrupt(ctx)) { result = JS_ThrowInternalError(ctx, "interrupted"); goto done; } if (pc >= code->instr_count) { - /* End of code - implicit return null */ + /* End of code — implicit return null */ result = JS_NULL; + if (JS_IsNull(frame->caller)) goto done; - /* Check if we need to return to caller */ - if (JS_IsNull(frame->caller)) { - goto done; - } - - /* Pop frame and resume caller */ + /* Pop frame */ int ret_info = JS_VALUE_GET_INT(frame->address); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); - frame->caller = JS_NULL; /* Allow GC to collect */ - + frame->caller = JS_NULL; frame = caller; frame_ref.val = JS_MKPTR(frame); - - /* Get code from function */ JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); code = fn->u.reg.code; - + env = fn->u.reg.env_record; pc = ret_info >> 16; - frame->slots[ret_info & 0xFFFF] = result; + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; continue; } - MachInstr *instr = &code->instructions[pc++]; + MachInstr32 instr = code->instructions[pc++]; + 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 (instr->opcode) { - case MACH_OP_NOP: + switch (op) { + case MACH_NOP: break; - case MACH_OP_ACCESS: - /* Load constant from cpool */ - if (instr->b >= 0 && (uint32_t)instr->b < code->cpool_count) { - frame->slots[instr->a] = code->cpool[instr->b]; - } - break; - - case MACH_OP_NULL: - frame->slots[instr->a] = JS_NULL; - break; - - case MACH_OP_TRUE: - frame->slots[instr->a] = JS_TRUE; - break; - - case MACH_OP_FALSE: - frame->slots[instr->a] = JS_FALSE; - break; - - case MACH_OP_MOVE: - frame->slots[instr->a] = frame->slots[instr->b]; - break; - - case MACH_OP_ADD: - case MACH_OP_SUB: - case MACH_OP_MUL: - case MACH_OP_DIV: - case MACH_OP_MOD: - case MACH_OP_POW: - case MACH_OP_EQ: - case MACH_OP_NE: - case MACH_OP_LT: - case MACH_OP_LE: - case MACH_OP_GT: - case MACH_OP_GE: - case MACH_OP_STRICT_EQ: - case MACH_OP_STRICT_NE: - case MACH_OP_BITAND: - case MACH_OP_BITOR: - case MACH_OP_BITXOR: - case MACH_OP_SHL: - case MACH_OP_SHR: - case MACH_OP_USHR: { - JSValue left = frame->slots[instr->b]; - JSValue right = frame->slots[instr->c]; - JSValue res = reg_vm_binop(ctx, instr->opcode, left, right); - /* GC may have moved frame - refresh from GC root */ - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->slots[instr->a] = res; + case MACH_LOADK: { + int bx = MACH_GET_Bx(instr); + if (bx < (int)code->cpool_count) + frame->slots[a] = code->cpool[bx]; break; } - case MACH_OP_NEG: { - JSValue v = frame->slots[instr->b]; + 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); + 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[instr->a] = JS_NewFloat64(ctx, -(double)i); + frame->slots[a] = JS_NewFloat64(ctx, -(double)i); else - frame->slots[instr->a] = JS_NewInt32(ctx, -i); + frame->slots[a] = JS_NewInt32(ctx, -i); } else { double d; JS_ToFloat64(ctx, &d, v); - frame->slots[instr->a] = JS_NewFloat64(ctx, -d); + frame->slots[a] = JS_NewFloat64(ctx, -d); } break; } - case MACH_OP_INC: { - JSValue v = frame->slots[instr->b]; + 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[instr->a] = JS_NewFloat64(ctx, (double)i + 1); + frame->slots[a] = JS_NewFloat64(ctx, (double)i + 1); else - frame->slots[instr->a] = JS_NewInt32(ctx, i + 1); + frame->slots[a] = JS_NewInt32(ctx, i + 1); } else { double d; JS_ToFloat64(ctx, &d, v); - frame->slots[instr->a] = JS_NewFloat64(ctx, d + 1); + frame->slots[a] = JS_NewFloat64(ctx, d + 1); } break; } - case MACH_OP_DEC: { - JSValue v = frame->slots[instr->b]; + 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[instr->a] = JS_NewFloat64(ctx, (double)i - 1); + frame->slots[a] = JS_NewFloat64(ctx, (double)i - 1); else - frame->slots[instr->a] = JS_NewInt32(ctx, i - 1); + frame->slots[a] = JS_NewInt32(ctx, i - 1); } else { double d; JS_ToFloat64(ctx, &d, v); - frame->slots[instr->a] = JS_NewFloat64(ctx, d - 1); + frame->slots[a] = JS_NewFloat64(ctx, d - 1); } break; } - case MACH_OP_NOT: { - int b = JS_ToBool(ctx, frame->slots[instr->b]); - frame->slots[instr->a] = JS_NewBool(ctx, !b); + case MACH_LNOT: { + int bval = JS_ToBool(ctx, frame->slots[b]); + frame->slots[a] = JS_NewBool(ctx, !bval); break; } - case MACH_OP_BITNOT: { + case MACH_BNOT: { int32_t i; - JS_ToInt32(ctx, &i, frame->slots[instr->b]); - frame->slots[instr->a] = JS_NewInt32(ctx, ~i); + JS_ToInt32(ctx, &i, frame->slots[b]); + frame->slots[a] = JS_NewInt32(ctx, ~i); break; } - case MACH_OP_LOAD_PROP: { - JSValue obj = frame->slots[instr->b]; - JSValue key = code->cpool[instr->c]; + case MACH_TYPEOF: { + JSValue v = frame->slots[b]; + const char *type_str; + if (JS_IsNull(v)) type_str = "object"; + else if (JS_IsBool(v)) type_str = "boolean"; + else if (JS_IsInt(v) || JS_IsNumber(v)) type_str = "number"; + else if (JS_VALUE_IS_TEXT(v)) type_str = "string"; + else if (JS_IsFunction(v)) type_str = "function"; + else type_str = "object"; + JSValue ts = JS_NewString(ctx, type_str); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + frame->slots[a] = ts; + break; + } + + case MACH_GETFIELD: { + JSValue obj = frame->slots[b]; + JSValue key = code->cpool[c]; JSValue val = JS_GetProperty(ctx, obj, key); - /* GC may have moved frame - refresh from GC root */ frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->slots[instr->a] = val; + frame->slots[a] = val; break; } - case MACH_OP_STORE_PROP: { - JSValue obj = frame->slots[instr->a]; - JSValue val = frame->slots[instr->b]; - JSValue key = code->cpool[instr->c]; + case MACH_SETFIELD: { + /* R(A)[K(B)] = R(C) */ + JSValue obj = frame->slots[a]; + JSValue key = code->cpool[b]; + JSValue val = frame->slots[c]; JS_SetProperty(ctx, obj, key, val); - /* GC may have moved frame - refresh from GC root */ frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); break; } - case MACH_OP_LOAD_IDX: { - JSValue obj = frame->slots[instr->b]; - JSValue idx = frame->slots[instr->c]; + case MACH_GETINDEX: { + JSValue obj = frame->slots[b]; + JSValue idx = frame->slots[c]; JSValue val; - if (JS_IsInt(idx)) { + if (JS_IsInt(idx)) val = JS_GetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx)); - } else { + else val = JS_GetProperty(ctx, obj, idx); - } - /* GC may have moved frame - refresh from GC root */ frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->slots[instr->a] = val; + frame->slots[a] = val; break; } - case MACH_OP_STORE_IDX: { - JSValue obj = frame->slots[instr->a]; - JSValue idx = frame->slots[instr->b]; - JSValue val = frame->slots[instr->c]; - if (JS_IsInt(idx)) { + case MACH_SETINDEX: { + /* R(A)[R(B)] = R(C) */ + JSValue obj = frame->slots[a]; + JSValue idx = frame->slots[b]; + JSValue val = frame->slots[c]; + if (JS_IsInt(idx)) JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val); - } else { + else JS_SetProperty(ctx, obj, idx, val); - } - /* GC may have moved frame - refresh from GC root */ frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); break; } - case MACH_OP_GET: { - /* Closure access: get dest, slot, depth */ - int depth = instr->c; - JSFrameRegister *target = frame; - for (int d = 0; d < depth && !JS_IsNull(target->caller); d++) { - target = (JSFrameRegister *)JS_VALUE_GET_PTR(target->caller); + case MACH_GETGLOBAL: { + 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) || JS_IsException(val)) { + /* Try env too */ + if (!JS_IsNull(env)) { + val = JS_GetProperty(ctx, env, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + } } - frame->slots[instr->a] = target->slots[instr->b]; + frame->slots[a] = val; break; } - case MACH_OP_PUT: { - /* Closure write: put src, slot, depth */ - int depth = instr->c; - JSFrameRegister *target = frame; - for (int d = 0; d < depth && !JS_IsNull(target->caller); d++) { - target = (JSFrameRegister *)JS_VALUE_GET_PTR(target->caller); + case MACH_SETGLOBAL: { + int bx = MACH_GET_Bx(instr); + JSValue key = code->cpool[bx]; + JS_SetProperty(ctx, ctx->global_obj, key, frame->slots[a]); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + break; + } + + case MACH_GETNAME: { + /* Runtime fallback: try env then global */ + 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); } - target->slots[instr->b] = frame->slots[instr->a]; - break; - } - - case MACH_OP_GET_ENV_SLOT: { - /* Get from env record */ - JSValue key = code->cpool[instr->b]; - frame->slots[instr->a] = JS_GetProperty(ctx, env, key); - break; - } - - case MACH_OP_SET_ENV_SLOT: { - /* Set in env record */ - JSValue key = code->cpool[instr->a]; - JS_SetProperty(ctx, env, key, frame->slots[instr->b]); - break; - } - - case MACH_OP_GET_GLOBAL_SLOT: { - /* Get from global */ - JSValue key = code->cpool[instr->b]; - frame->slots[instr->a] = JS_GetProperty(ctx, ctx->global_obj, key); - break; - } - - case MACH_OP_SET_GLOBAL_SLOT: { - /* Set in global */ - JSValue key = code->cpool[instr->a]; - JS_SetProperty(ctx, ctx->global_obj, key, frame->slots[instr->b]); - break; - } - - case MACH_OP_CAP_NAME: { - /* Unresolved capability - try env then global at runtime */ - JSValue key = code->cpool[instr->b]; - JSValue val = JS_GetProperty(ctx, env, key); 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[instr->a] = val; + frame->slots[a] = val; break; } - case MACH_OP_JUMP: - pc = instr->a; - break; - - case MACH_OP_JUMP_TRUE: { - int cond = JS_ToBool(ctx, frame->slots[instr->a]); - if (cond) pc = instr->b; + case MACH_SETNAME: { + int bx = MACH_GET_Bx(instr); + JSValue key = code->cpool[bx]; + JS_SetProperty(ctx, ctx->global_obj, key, frame->slots[a]); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); break; } - case MACH_OP_JUMP_FALSE: { - int cond = JS_ToBool(ctx, frame->slots[instr->a]); - if (!cond) pc = instr->b; + case MACH_GETUP: { + /* R(A) = outer_frame[B].slots[C] */ + int depth = b; + JSFrameRegister *target = frame; + for (int d = 0; d < depth && !JS_IsNull(target->caller); d++) + target = (JSFrameRegister *)JS_VALUE_GET_PTR(target->caller); + frame->slots[a] = target->slots[c]; break; } - case MACH_OP_JUMP_NOT_NULL: { - if (!JS_IsNull(frame->slots[instr->a])) pc = instr->b; + case MACH_SETUP: { + /* outer_frame[B].slots[C] = R(A) */ + int depth = b; + JSFrameRegister *target = frame; + for (int d = 0; d < depth && !JS_IsNull(target->caller); d++) + target = (JSFrameRegister *)JS_VALUE_GET_PTR(target->caller); + target->slots[c] = frame->slots[a]; break; } - case MACH_OP_FRAME: { - /* Allocate new frame for call: frame frame_reg, func_reg, argc */ - JSValue func_val = frame->slots[instr->b]; + 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)) { result = JS_ThrowTypeError(ctx, "not a function"); goto done; } + JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val); - if (fn->kind != JS_FUNC_KIND_REGISTER) { - /* For non-register functions, we'll handle in invoke */ - frame->slots[instr->a] = func_val; - } else { + if (fn->kind == JS_FUNC_KIND_C) { + /* C function: call directly with args from consecutive slots */ + JSValue ret = js_call_c_function(ctx, func_val, JS_NULL, nargs, &frame->slots[base + 1]); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(ret)) { result = ret; goto done; } + 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) { - result = JS_EXCEPTION; - goto done; - } - /* GC may have moved frame - refresh from GC root */ + if (!new_frame) { result = JS_EXCEPTION; goto done; } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); new_frame->function = func_val; - frame->slots[instr->a] = JS_MKPTR(new_frame); - } - break; - } + 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]; - case MACH_OP_SET_THIS: { - /* Set this on prepared frame */ - JSValue target = frame->slots[instr->a]; - if (JS_IsFunction(target)) { - /* C function - this will be passed at invoke time */ + /* 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 { - JSFrameRegister *call_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target); - call_frame->slots[0] = frame->slots[instr->b]; - } - break; - } - - case MACH_OP_ARG: { - /* Set argument on prepared frame: arg frame_reg, arg_idx, val_reg */ - JSValue target = frame->slots[instr->a]; - if (!JS_IsFunction(target)) { - JSFrameRegister *call_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target); - call_frame->slots[instr->b] = frame->slots[instr->c]; - } - /* For C functions, args are collected separately */ - break; - } - - case MACH_OP_INVOKE: { - /* Invoke prepared frame: invoke frame_reg, ret_reg */ - JSValue target = frame->slots[instr->a]; - - if (JS_IsFunction(target)) { - /* C function call - collect args and call directly */ - JSFunction *fn = JS_VALUE_GET_FUNCTION(target); - /* TODO: proper C function call with collected args */ - result = JS_ThrowInternalError(ctx, "C function call not yet supported in register VM"); - goto done; - } - - JSFrameRegister *new_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target); - JSFunction *fn = JS_VALUE_GET_FUNCTION(new_frame->function); - - if (fn->kind != JS_FUNC_KIND_REGISTER) { - result = JS_ThrowInternalError(ctx, "non-register function in invoke"); - goto done; - } - - /* Store return address: pc << 16 | ret_slot */ - frame->address = JS_NewInt32(ctx, (pc << 16) | instr->b); - - /* Link caller */ - new_frame->caller = JS_MKPTR(frame); - - /* Switch to new frame */ - frame = new_frame; - frame_ref.val = JS_MKPTR(frame); - code = fn->u.reg.code; - env = fn->u.reg.env_record; - pc = code->entry_point; - break; - } - - case MACH_OP_GOFRAME: { - /* Tail call frame setup - similar to FRAME but reuses current frame */ - /* For now, fall through to regular call semantics */ - JSValue func_val = frame->slots[instr->b]; - if (!JS_IsFunction(func_val)) { - result = JS_ThrowTypeError(ctx, "not a function"); - goto done; - } - JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val); - 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) { - result = JS_EXCEPTION; - goto done; - } - /* GC may have moved frame - refresh from GC root */ + /* Other function kinds (bytecode) — call via C bridge */ + JSValue ret = js_call_c_function(ctx, func_val, JS_NULL, nargs, &frame->slots[base + 1]); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - new_frame->function = func_val; - frame->slots[instr->a] = JS_MKPTR(new_frame); - } else { - frame->slots[instr->a] = func_val; + if (JS_IsException(ret)) { result = ret; goto done; } + if (nresults > 0) frame->slots[base] = ret; } break; } - case MACH_OP_GOINVOKE: { - /* Tail call invoke - doesn't push return address */ - JSValue target = frame->slots[instr->a]; - - if (JS_IsFunction(target)) { - result = JS_ThrowInternalError(ctx, "C function tail call not supported"); - goto done; - } - - JSFrameRegister *new_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target); - JSFunction *fn = JS_VALUE_GET_FUNCTION(new_frame->function); - - if (fn->kind != JS_FUNC_KIND_REGISTER) { - result = JS_ThrowInternalError(ctx, "non-register function in goinvoke"); - goto done; - } - - /* For tail call, pass through caller (skip current frame) */ - new_frame->caller = frame->caller; - - /* Switch to new frame */ - frame = new_frame; - frame_ref.val = JS_MKPTR(frame); - code = fn->u.reg.code; - env = fn->u.reg.env_record; - pc = code->entry_point; - break; - } - - case MACH_OP_MKFUNC: { - /* Create function from nested function: mkfunc dest, func_id */ - if ((uint32_t)instr->b < code->func_count) { - JSCodeRegister *fn_code = code->functions[instr->b]; - JSValue fn_val = js_new_register_function(ctx, fn_code, env, frame_ref.val); - /* GC may have moved frame - refresh from GC root */ - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->slots[instr->a] = fn_val; - } else { - frame->slots[instr->a] = JS_NULL; - } - break; - } - - case MACH_OP_MKFUNC_ARROW: { - /* Arrow function captures this from enclosing scope */ - if ((uint32_t)instr->b < code->func_count) { - JSCodeRegister *fn_code = code->functions[instr->b]; - JSValue fn = js_new_register_function(ctx, fn_code, env, frame_ref.val); - /* GC may have moved frame - refresh from GC root */ - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - /* Arrow functions have lexical this - handled at call time */ - frame->slots[instr->a] = fn; - } else { - frame->slots[instr->a] = JS_NULL; - } - break; - } - - case MACH_OP_MKRECORD: { - /* Create empty record */ - JSValue rec = JS_NewObject(ctx); - /* GC may have moved frame - refresh from GC root */ - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(rec)) { - result = rec; - goto done; - } - frame->slots[instr->a] = rec; - break; - } - - case MACH_OP_MKARRAY: { - /* Create empty array */ - JSValue arr = JS_NewArray(ctx); - /* GC may have moved frame - refresh from GC root */ - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(arr)) { - result = arr; - goto done; - } - frame->slots[instr->a] = arr; - break; - } - - case MACH_OP_RETURN: - result = frame->slots[instr->a]; - - if (JS_IsNull(frame->caller)) { - goto done; - } - + case MACH_RETURN: + result = frame->slots[a]; + if (JS_IsNull(frame->caller)) goto done; { int ret_info = JS_VALUE_GET_INT(frame->address); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); frame->caller = JS_NULL; - frame = caller; frame_ref.val = JS_MKPTR(frame); - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); code = fn->u.reg.code; env = fn->u.reg.env_record; - pc = ret_info >> 16; - frame->slots[ret_info & 0xFFFF] = result; + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; } break; - case MACH_OP_RETURN_UNDEF: + case MACH_RETNIL: result = JS_NULL; - - if (JS_IsNull(frame->caller)) { - goto done; - } - + if (JS_IsNull(frame->caller)) goto done; { int ret_info = JS_VALUE_GET_INT(frame->address); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); frame->caller = JS_NULL; - frame = caller; frame_ref.val = JS_MKPTR(frame); - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); code = fn->u.reg.code; env = fn->u.reg.env_record; - pc = ret_info >> 16; - frame->slots[ret_info & 0xFFFF] = result; + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; } break; - case MACH_OP_THROW: - result = JS_Throw(ctx, frame->slots[instr->a]); + case MACH_NEWOBJECT: { + JSValue obj = JS_NewObject(ctx); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(obj)) { result = obj; goto done; } + 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)) { result = arr; goto done; } + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(ctx, arr, i, frame->slots[a + 1 + i]); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + } + frame->slots[a] = arr; + 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_THROW: + result = JS_Throw(ctx, frame->slots[a]); goto done; - /* Exception handling - basic implementation */ - case MACH_OP_TRY_BEGIN: - case MACH_OP_TRY_END: - case MACH_OP_CATCH_BEGIN: - case MACH_OP_CATCH_END: - case MACH_OP_FINALLY_BEGIN: - case MACH_OP_FINALLY_END: - /* TODO: Implement exception handling */ - break; - default: - /* Unknown opcode */ - result = JS_ThrowInternalError(ctx, "unknown register VM opcode %d", instr->opcode); + result = JS_ThrowInternalError(ctx, "unknown register VM opcode %d", op); goto done; } } @@ -33979,17 +32472,9 @@ static void dump_cpool_value(JSContext *ctx, JSValue val) { } } -/* Find label name for an instruction index, or NULL if none */ -static const char *find_label_for_instr(JSCodeRegister *code, uint32_t instr_idx) { - for (uint32_t i = 0; i < code->label_count; i++) { - if (code->label_offsets[i] == instr_idx) { - return code->label_names[i]; - } - } - return NULL; -} +/* (labels removed in new format) */ -/* Internal helper to dump JSCodeRegister */ +/* 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; @@ -34023,227 +32508,152 @@ static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent) /* Instructions */ printf("%s\n%sInstructions (%d):\n", pad, pad, code->instr_count); for (uint32_t i = 0; i < code->instr_count; i++) { - /* Print any labels pointing to this instruction */ - const char *label = find_label_for_instr(code, i); - if (label) { - printf("%s%s:\n", pad, label); - } - - MachInstr *instr = &code->instructions[i]; - uint8_t op = instr->opcode; + 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_OP_NOP: - case MACH_OP_RETURN_UNDEF: - case MACH_OP_TRY_END: - case MACH_OP_CATCH_END: - case MACH_OP_FINALLY_BEGIN: - case MACH_OP_FINALLY_END: + case MACH_NOP: + case MACH_RETNIL: break; - /* dest only */ - case MACH_OP_NULL: - case MACH_OP_TRUE: - case MACH_OP_FALSE: - printf("r%d", instr->a); + /* A only */ + case MACH_LOADNULL: + case MACH_LOADTRUE: + case MACH_LOADFALSE: + printf("r%d", a); break; - /* dest, cpool_idx */ - case MACH_OP_ACCESS: - printf("r%d, #%d", instr->a, instr->b); - if (instr->b >= 0 && (uint32_t)instr->b < code->cpool_count) { + /* 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[instr->b]); + dump_cpool_value(ctx, code->cpool[bx]); } break; - - /* dest, src */ - case MACH_OP_MOVE: - case MACH_OP_NEG: - case MACH_OP_INC: - case MACH_OP_DEC: - case MACH_OP_NOT: - case MACH_OP_BITNOT: - printf("r%d, r%d", instr->a, instr->b); - break; - - /* dest, src1, src2 */ - case MACH_OP_ADD: - case MACH_OP_SUB: - case MACH_OP_MUL: - case MACH_OP_DIV: - case MACH_OP_MOD: - case MACH_OP_POW: - case MACH_OP_EQ: - case MACH_OP_NE: - case MACH_OP_LT: - case MACH_OP_LE: - case MACH_OP_GT: - case MACH_OP_GE: - case MACH_OP_STRICT_EQ: - case MACH_OP_STRICT_NE: - case MACH_OP_BITAND: - case MACH_OP_BITOR: - case MACH_OP_BITXOR: - case MACH_OP_SHL: - case MACH_OP_SHR: - case MACH_OP_USHR: - printf("r%d, r%d, r%d", instr->a, instr->b, instr->c); - break; - - /* dest, obj, cpool_idx (property) */ - case MACH_OP_LOAD_PROP: - printf("r%d, r%d, #%d", instr->a, instr->b, instr->c); - if (instr->c >= 0 && (uint32_t)instr->c < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[instr->c]); - } - break; - - /* obj, val, cpool_idx (property) */ - case MACH_OP_STORE_PROP: - printf("r%d, r%d, #%d", instr->a, instr->b, instr->c); - if (instr->c >= 0 && (uint32_t)instr->c < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[instr->c]); - } - break; - - /* dest, obj, idx */ - case MACH_OP_LOAD_IDX: - case MACH_OP_DELETE_IDX: - printf("r%d, r%d, r%d", instr->a, instr->b, instr->c); - break; - - /* obj, idx, val */ - case MACH_OP_STORE_IDX: - printf("r%d, r%d, r%d", instr->a, instr->b, instr->c); - break; - - /* dest, obj, cpool_idx */ - case MACH_OP_DELETE_PROP: - printf("r%d, r%d, #%d", instr->a, instr->b, instr->c); - break; - - /* dest, slot, depth */ - case MACH_OP_GET: - printf("r%d, slot=%d, depth=%d", instr->a, instr->b, instr->c); - break; - - /* src, slot, depth */ - case MACH_OP_PUT: - printf("r%d, slot=%d, depth=%d", instr->a, instr->b, instr->c); - break; - - /* dest, cpool_idx (name) */ - case MACH_OP_CAP_NAME: - printf("r%d, #%d", instr->a, instr->b); - if (instr->b >= 0 && (uint32_t)instr->b < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[instr->b]); - } - break; - - /* dest, slot_idx */ - case MACH_OP_GET_ENV_SLOT: - case MACH_OP_GET_GLOBAL_SLOT: - printf("r%d, slot=%d", instr->a, instr->b); - break; - - /* src, slot_idx */ - case MACH_OP_SET_ENV_SLOT: - case MACH_OP_SET_GLOBAL_SLOT: - printf("r%d, slot=%d", instr->a, instr->b); - break; - - /* cpool_idx, src */ - case MACH_OP_SET_VAR: - printf("#%d, r%d", instr->a, instr->b); - if (instr->a >= 0 && (uint32_t)instr->a < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[instr->a]); - } - break; - - /* label (resolved to instruction index) */ - case MACH_OP_JUMP: - case MACH_OP_TRY_BEGIN: { - const char *target_label = find_label_for_instr(code, instr->a); - printf("@%d", instr->a); - if (target_label) printf(" ; -> %s", target_label); } + + /* AsBx: load small int */ + case MACH_LOADI: + printf("r%d, %d", a, MACH_GET_sBx(instr)); break; - /* cond, label (resolved to instruction index) */ - case MACH_OP_JUMP_TRUE: - case MACH_OP_JUMP_FALSE: - case MACH_OP_JUMP_NOT_NULL: { - const char *target_label = find_label_for_instr(code, instr->b); - printf("r%d, @%d", instr->a, instr->b); - if (target_label) printf(" ; -> %s", target_label); + /* A, B: move, unary ops */ + case MACH_MOVE: + case MACH_NEG: + case MACH_INC: + case MACH_DEC: + case MACH_LNOT: + case MACH_BNOT: + case MACH_TYPEOF: + 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; + + /* 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: global/name access */ + case MACH_GETGLOBAL: + case MACH_SETGLOBAL: + case MACH_GETNAME: + case MACH_SETNAME: { + 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; - /* frame_reg, func_reg, argc */ - case MACH_OP_FRAME: - case MACH_OP_GOFRAME: - printf("r%d, r%d, %d", instr->a, instr->b, instr->c); + /* 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; - /* frame_reg, this_reg */ - case MACH_OP_SET_THIS: - printf("r%d, r%d", instr->a, instr->b); + /* Return / throw */ + case MACH_RETURN: + case MACH_THROW: + printf("r%d", a); break; - /* frame_reg, arg_idx, val_reg */ - case MACH_OP_ARG: - printf("r%d, [%d], r%d", instr->a, instr->b, instr->c); + /* Object/array creation */ + case MACH_NEWOBJECT: + printf("r%d", a); + break; + case MACH_NEWARRAY: + printf("r%d, %d", a, b); break; - /* frame_reg, ret_reg */ - case MACH_OP_INVOKE: - printf("r%d, r%d", instr->a, instr->b); - break; - - /* frame_reg */ - case MACH_OP_GOINVOKE: - printf("r%d", instr->a); - break; - - /* dest, func_id */ - case MACH_OP_MKFUNC: - case MACH_OP_MKFUNC_ARROW: - printf("r%d, func#%d", instr->a, instr->b); - break; - - /* dest, argc */ - case MACH_OP_MKRECORD: - case MACH_OP_MKARRAY: - printf("r%d, %d", instr->a, instr->b); - break; - - /* dest, func_reg, argc */ - case MACH_OP_MKNEW: - printf("r%d, r%d, %d", instr->a, instr->b, instr->c); - break; - - /* src */ - case MACH_OP_RETURN: - case MACH_OP_THROW: - printf("r%d", instr->a); - break; - - /* dest */ - case MACH_OP_CATCH_BEGIN: - printf("r%d", instr->a); + /* Closure */ + case MACH_CLOSURE: { + int bx = MACH_GET_Bx(instr); + printf("r%d, func#%d", a, bx); break; + } default: - printf("a=%d, b=%d, c=%d", instr->a, instr->b, instr->c); + printf("0x%08x", instr); break; } printf("\n"); @@ -34265,16 +32675,9 @@ static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent) /* Dump MACH bytecode to stdout for debugging. Takes AST JSON. */ void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) { - char *mcode_json = JS_Mcode(ctx, ast_json); - if (!mcode_json) { - printf("=== MACH Bytecode ===\nFailed to generate MCODE\n=== End MACH Bytecode ===\n"); - return; - } - - JSCodeRegister *code = js_link_mach(ctx, mcode_json, env); - sys_free(mcode_json); + JSCodeRegister *code = JS_CompileMach(ctx, ast_json, env); if (!code) { - printf("=== MACH Bytecode ===\nFailed to link MCODE\n=== End MACH Bytecode ===\n"); + printf("=== MACH Bytecode ===\nFailed to compile\n=== End MACH Bytecode ===\n"); return; } @@ -34285,23 +32688,11 @@ void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) { /* Compile and execute MACH bytecode. Takes AST JSON. */ JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) { - char *mcode_json = JS_Mcode(ctx, ast_json); - if (!mcode_json) { - return JS_ThrowSyntaxError(ctx, "failed to generate MCODE from AST"); - } - - JSCodeRegister *code = js_link_mach(ctx, mcode_json, env); - sys_free(mcode_json); + JSCodeRegister *code = JS_CompileMach(ctx, ast_json, env); if (!code) { - return JS_ThrowSyntaxError(ctx, "failed to link MCODE to MACH bytecode"); + return JS_ThrowSyntaxError(ctx, "failed to compile AST to MACH bytecode"); } - /* Execute the main code */ JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL); - - /* Note: We don't free code here because nested functions may still reference it. - The code will be freed when the context is destroyed. In a production - implementation, we'd track all linked code for cleanup. */ - return result; } diff --git a/source/quickjs.h b/source/quickjs.h index 66641f30..59b0b976 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -1226,9 +1226,9 @@ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filena Returns malloc'd JSON string (caller must free), or NULL on error. */ char *JS_Tokenize (JSContext *ctx, const char *source, size_t len, const char *filename); -/* Generate MCODE (JSON IR) from AST JSON. - Returns malloc'd JSON string (caller must free), or NULL on error. */ -char *JS_Mcode (JSContext *ctx, const char *ast_json); +/* Compile AST JSON directly to register bytecode. + Returns JSCodeRegister* (caller should not free — managed by context), or NULL on error. */ +struct JSCodeRegister *JS_CompileMach(JSContext *ctx, const char *ast_json, JSValue env); /* Dump MACH bytecode to stdout for debugging. Takes AST JSON. Internally generates MCODE and links to binary bytecode. */ diff --git a/source/suite.c b/source/suite.c index 40b0f103..250971f6 100644 --- a/source/suite.c +++ b/source/suite.c @@ -2388,102 +2388,26 @@ TEST(ast_sem_nested_function_scope) { } /* ============================================================================ - CODEGEN/SEMANTIC ERROR TESTS + CODEGEN TESTS (updated for new direct AST-to-bytecode compiler) ============================================================================ */ -TEST(mach_assign_to_const) { - const char *src = "def x = 1; x = 2"; - char *ast_json = JS_AST(ctx, src, strlen(src), ""); - ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); - free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_ERROR_MSG_CONTAINS(mach_json, "cannot assign to constant"); - free(mach_json); - return 1; -} - -TEST(mach_assign_to_arg) { - const char *src = "function f(x) { x = 5 }"; - char *ast_json = JS_AST(ctx, src, strlen(src), ""); - ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); - free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_ERROR_MSG_CONTAINS(mach_json, "cannot assign to function argument"); - free(mach_json); - return 1; -} - -TEST(mach_redeclare_const) { - const char *src = "def x = 1; def x = 2"; - char *ast_json = JS_AST(ctx, src, strlen(src), ""); - ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); - free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_ERROR_MSG_CONTAINS(mach_json, "cannot redeclare constant"); - free(mach_json); - return 1; -} - -TEST(mach_break_outside_loop) { - const char *src = "break"; - char *ast_json = JS_AST(ctx, src, strlen(src), ""); - ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); - free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_ERROR_MSG_CONTAINS(mach_json, "outside of loop"); - free(mach_json); - return 1; -} - -TEST(mach_continue_outside_loop) { - const char *src = "continue"; - char *ast_json = JS_AST(ctx, src, strlen(src), ""); - ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); - free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_ERROR_MSG_CONTAINS(mach_json, "outside of loop"); - free(mach_json); - return 1; -} - -TEST(mach_assign_unbound_var) { - const char *src = "x = 42"; - char *ast_json = JS_AST(ctx, src, strlen(src), ""); - ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); - free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_ERROR_MSG_CONTAINS(mach_json, "not declared"); - free(mach_json); - return 1; -} - -TEST(mach_shadow_is_ok) { - const char *src = "var array = []; array"; - char *ast_json = JS_AST(ctx, src, strlen(src), ""); - ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); - free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_NO_ERRORS(mach_json); - free(mach_json); - return 1; -} - -TEST(mach_valid_no_errors) { +TEST(mach_compile_basic) { const char *src = "var x = 1; x = x + 1"; char *ast_json = JS_AST(ctx, src, strlen(src), ""); ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); - char *mach_json = JS_Mcode(ctx, ast_json); + struct JSCodeRegister *code = JS_CompileMach(ctx, ast_json, JS_NULL); free(ast_json); - ASSERT_MSG(mach_json != NULL, "JS_Mcode returned NULL"); - ASSERT_NO_ERRORS(mach_json); - free(mach_json); + ASSERT_MSG(code != NULL, "JS_CompileMach returned NULL"); + return 1; +} + +TEST(mach_compile_function) { + const char *src = "function f(x) { return x + 1 }"; + char *ast_json = JS_AST(ctx, src, strlen(src), ""); + ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); + struct JSCodeRegister *code = JS_CompileMach(ctx, ast_json, JS_NULL); + free(ast_json); + ASSERT_MSG(code != NULL, "JS_CompileMach returned NULL"); return 1; } @@ -2730,15 +2654,9 @@ int run_c_test_suite(JSContext *ctx) RUN_TEST(ast_sem_var_assign_ok); RUN_TEST(ast_sem_nested_function_scope); - printf("\nCodegen Errors:\n"); - RUN_TEST(mach_assign_to_const); - RUN_TEST(mach_assign_to_arg); - RUN_TEST(mach_redeclare_const); - RUN_TEST(mach_break_outside_loop); - RUN_TEST(mach_continue_outside_loop); - RUN_TEST(mach_assign_unbound_var); - RUN_TEST(mach_shadow_is_ok); - RUN_TEST(mach_valid_no_errors); + printf("\nCodegen:\n"); + RUN_TEST(mach_compile_basic); + RUN_TEST(mach_compile_function); printf("\n=================================\n"); printf("Results: %d passed, %d failed\n", tests_passed, tests_failed);