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