Files
cell/source/mach.c

3530 lines
123 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "quickjs-internal.h"
/* ---- Compile-time constant pool entry ---- */
/* Stores raw data during compilation; converted to JSValues when loading into context */
typedef enum { MACH_CP_INT, MACH_CP_FLOAT, MACH_CP_STR } MachCPType;
typedef struct {
MachCPType type;
union {
int32_t ival; /* integer constant */
double fval; /* float constant */
char *str; /* owned C string */
};
} MachCPoolEntry;
/* ---- Compiled output (context-free) ---- */
typedef struct MachCode {
uint16_t arity;
uint16_t nr_close_slots;
uint16_t nr_slots;
uint16_t entry_point;
uint32_t cpool_count;
MachCPoolEntry *cpool;
uint32_t instr_count;
MachInstr32 *instructions;
uint32_t func_count;
struct MachCode **functions;
char *name; /* owned C string, or NULL */
MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */
char *filename; /* source filename (sys_malloc'd) */
uint16_t disruption_pc; /* start of disruption handler (0 = none) */
} MachCode;
/* ---- Helpers ---- */
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;
}
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = 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_ref.val) && (uint32_t)bx < code->cpool_count) {
in_env = JS_HasProperty(ctx, env_ref.val, code->cpool[bx]);
}
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_ref.val);
JS_PopGCRef(ctx, &env_ref);
}
/* 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);
}
/* ---- MachCode binary serialization ---- */
static size_t mach_serialized_size(MachCode *mc) {
/* Header: 6 x uint16_t + 1 x uint16_t padding = 14 bytes (but let's be explicit) */
size_t sz = 6 * sizeof(uint16_t); /* arity, nr_close_slots, nr_slots, entry_point, disruption_pc, padding */
/* name */
sz += sizeof(uint32_t);
if (mc->name) sz += strlen(mc->name);
/* filename */
sz += sizeof(uint32_t);
if (mc->filename) sz += strlen(mc->filename);
/* cpool */
sz += sizeof(uint32_t); /* cpool_count */
for (uint32_t i = 0; i < mc->cpool_count; i++) {
sz += 1; /* type tag */
switch (mc->cpool[i].type) {
case MACH_CP_INT: sz += sizeof(int32_t); break;
case MACH_CP_FLOAT: sz += sizeof(double); break;
case MACH_CP_STR:
sz += sizeof(uint32_t);
sz += strlen(mc->cpool[i].str);
break;
}
}
/* instructions + line_table */
sz += sizeof(uint32_t); /* instr_count */
sz += mc->instr_count * sizeof(MachInstr32);
sz += mc->instr_count * sizeof(MachLineEntry);
/* nested functions */
sz += sizeof(uint32_t); /* func_count */
for (uint32_t i = 0; i < mc->func_count; i++)
sz += mach_serialized_size(mc->functions[i]);
return sz;
}
static void mach_serialize_write(MachCode *mc, uint8_t **p) {
uint8_t *w = *p;
/* Header fields */
memcpy(w, &mc->arity, 2); w += 2;
memcpy(w, &mc->nr_close_slots, 2); w += 2;
memcpy(w, &mc->nr_slots, 2); w += 2;
memcpy(w, &mc->entry_point, 2); w += 2;
memcpy(w, &mc->disruption_pc, 2); w += 2;
uint16_t pad = 0;
memcpy(w, &pad, 2); w += 2;
/* name */
uint32_t name_len = mc->name ? (uint32_t)strlen(mc->name) : 0;
memcpy(w, &name_len, 4); w += 4;
if (name_len) { memcpy(w, mc->name, name_len); w += name_len; }
/* filename */
uint32_t fn_len = mc->filename ? (uint32_t)strlen(mc->filename) : 0;
memcpy(w, &fn_len, 4); w += 4;
if (fn_len) { memcpy(w, mc->filename, fn_len); w += fn_len; }
/* cpool */
memcpy(w, &mc->cpool_count, 4); w += 4;
for (uint32_t i = 0; i < mc->cpool_count; i++) {
uint8_t tag = (uint8_t)mc->cpool[i].type;
*w++ = tag;
switch (mc->cpool[i].type) {
case MACH_CP_INT:
memcpy(w, &mc->cpool[i].ival, 4); w += 4;
break;
case MACH_CP_FLOAT:
memcpy(w, &mc->cpool[i].fval, 8); w += 8;
break;
case MACH_CP_STR: {
uint32_t slen = (uint32_t)strlen(mc->cpool[i].str);
memcpy(w, &slen, 4); w += 4;
memcpy(w, mc->cpool[i].str, slen); w += slen;
break;
}
}
}
/* instructions */
memcpy(w, &mc->instr_count, 4); w += 4;
memcpy(w, mc->instructions, mc->instr_count * sizeof(MachInstr32));
w += mc->instr_count * sizeof(MachInstr32);
/* line_table (write zeros if NULL) */
if (mc->line_table) {
memcpy(w, mc->line_table, mc->instr_count * sizeof(MachLineEntry));
} else {
memset(w, 0, mc->instr_count * sizeof(MachLineEntry));
}
w += mc->instr_count * sizeof(MachLineEntry);
/* nested functions */
memcpy(w, &mc->func_count, 4); w += 4;
*p = w;
for (uint32_t i = 0; i < mc->func_count; i++)
mach_serialize_write(mc->functions[i], p);
}
uint8_t *JS_SerializeMachCode(MachCode *mc, size_t *out_size) {
if (!mc) return NULL;
size_t sz = mach_serialized_size(mc);
uint8_t *buf = sys_malloc(sz);
if (!buf) return NULL;
uint8_t *p = buf;
mach_serialize_write(mc, &p);
*out_size = sz;
return buf;
}
#define DESER_CHECK(cond) do { if (!(cond)) goto fail; } while(0)
static MachCode *mach_deserialize_read(const uint8_t **p, const uint8_t *end) {
const uint8_t *r = *p;
/* Need at least the fixed header: 6 * uint16_t = 12 bytes */
if (r + 12 > end) return NULL;
MachCode *mc = sys_malloc(sizeof(MachCode));
if (!mc) return NULL;
memset(mc, 0, sizeof(MachCode));
memcpy(&mc->arity, r, 2); r += 2;
memcpy(&mc->nr_close_slots, r, 2); r += 2;
memcpy(&mc->nr_slots, r, 2); r += 2;
memcpy(&mc->entry_point, r, 2); r += 2;
memcpy(&mc->disruption_pc, r, 2); r += 2;
r += 2; /* skip padding */
/* name */
DESER_CHECK(r + 4 <= end);
uint32_t name_len;
memcpy(&name_len, r, 4); r += 4;
DESER_CHECK(r + name_len <= end);
if (name_len) {
mc->name = sys_malloc(name_len + 1);
memcpy(mc->name, r, name_len);
mc->name[name_len] = '\0';
r += name_len;
}
/* filename */
DESER_CHECK(r + 4 <= end);
uint32_t fn_len;
memcpy(&fn_len, r, 4); r += 4;
DESER_CHECK(r + fn_len <= end);
if (fn_len) {
mc->filename = sys_malloc(fn_len + 1);
memcpy(mc->filename, r, fn_len);
mc->filename[fn_len] = '\0';
r += fn_len;
}
/* cpool */
DESER_CHECK(r + 4 <= end);
memcpy(&mc->cpool_count, r, 4); r += 4;
if (mc->cpool_count) {
mc->cpool = sys_malloc(mc->cpool_count * sizeof(MachCPoolEntry));
for (uint32_t i = 0; i < mc->cpool_count; i++) {
DESER_CHECK(r + 1 <= end);
uint8_t tag = *r++;
mc->cpool[i].type = (MachCPType)tag;
switch (tag) {
case MACH_CP_INT:
DESER_CHECK(r + 4 <= end);
memcpy(&mc->cpool[i].ival, r, 4); r += 4;
break;
case MACH_CP_FLOAT:
DESER_CHECK(r + 8 <= end);
memcpy(&mc->cpool[i].fval, r, 8); r += 8;
break;
case MACH_CP_STR: {
DESER_CHECK(r + 4 <= end);
uint32_t slen;
memcpy(&slen, r, 4); r += 4;
DESER_CHECK(r + slen <= end);
mc->cpool[i].str = sys_malloc(slen + 1);
memcpy(mc->cpool[i].str, r, slen);
mc->cpool[i].str[slen] = '\0';
r += slen;
break;
}
default:
goto fail;
}
}
}
/* instructions */
DESER_CHECK(r + 4 <= end);
memcpy(&mc->instr_count, r, 4); r += 4;
DESER_CHECK(r + mc->instr_count * sizeof(MachInstr32) <= end);
mc->instructions = sys_malloc(mc->instr_count * sizeof(MachInstr32));
memcpy(mc->instructions, r, mc->instr_count * sizeof(MachInstr32));
r += mc->instr_count * sizeof(MachInstr32);
/* line_table */
DESER_CHECK(r + mc->instr_count * sizeof(MachLineEntry) <= end);
mc->line_table = sys_malloc(mc->instr_count * sizeof(MachLineEntry));
memcpy(mc->line_table, r, mc->instr_count * sizeof(MachLineEntry));
r += mc->instr_count * sizeof(MachLineEntry);
/* nested functions */
DESER_CHECK(r + 4 <= end);
memcpy(&mc->func_count, r, 4); r += 4;
if (mc->func_count) {
mc->functions = sys_malloc(mc->func_count * sizeof(MachCode *));
for (uint32_t i = 0; i < mc->func_count; i++) {
mc->functions[i] = mach_deserialize_read(&r, end);
if (!mc->functions[i]) {
mc->func_count = i; /* only free what we allocated */
goto fail;
}
}
}
*p = r;
return mc;
fail:
JS_FreeMachCode(mc);
return NULL;
}
#undef DESER_CHECK
MachCode *JS_DeserializeMachCode(const uint8_t *data, size_t size) {
if (!data || size == 0) return NULL;
const uint8_t *p = data;
const uint8_t *end = data + size;
MachCode *mc = mach_deserialize_read(&p, end);
if (mc && p != end) {
/* Trailing data — treat as error */
JS_FreeMachCode(mc);
return NULL;
}
return mc;
}
/* Load a MachCode into a JSCodeRegister (materializes JSValues, needs ctx) */
JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
/* Protect env from GC — materialize/link calls can trigger collection */
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = 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 */
mc->instructions = NULL;
/* 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_ref.val);
} 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_ref.val);
JS_PopGCRef(ctx, &env_ref);
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_malloc(ctx, size);
if (!frame) return NULL;
/* cap56 = slot count (used by gc_object_size) */
frame->header = 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 && mist_is_text(a) && mist_is_text(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 (mist_is_text(a) && mist_is_text(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_ThrowTypeError(ctx, "type mismatch in binary operation");
}
/* 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 (!mist_is_function(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;
}
fprintf(stderr, " %s (%s:%u)\n",
func_name ? func_name : "<anonymous>",
file ? file : "<unknown>", line);
if (JS_IsNull(frame->caller)) break;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
is_first = 0;
}
fprintf(stderr, "=================================\n");
}
#endif
/* Main register VM execution loop */
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue this_obj, int argc, JSValue *argv,
JSValue env, JSValue outer_frame) {
/* Protect env and outer_frame from GC — alloc_frame_register can trigger
collection which moves heap objects, invalidating stack-local copies */
JSGCRef env_gc, of_gc;
JS_PushGCRef(ctx, &env_gc);
env_gc.val = env;
JS_PushGCRef(ctx, &of_gc);
of_gc.val = outer_frame;
/* Protect argv and this_obj from GC using JSGCRef.
alloc_frame_register and js_new_register_function can trigger GC. */
int nargs_copy = (argc < code->arity) ? argc : code->arity;
JSGCRef this_gc;
JS_PushGCRef(ctx, &this_gc);
this_gc.val = this_obj;
JSGCRef arg_gcs[nargs_copy > 0 ? nargs_copy : 1];
for (int i = 0; i < nargs_copy; i++) {
JS_PushGCRef(ctx, &arg_gcs[i]);
arg_gcs[i].val = argv[i];
}
/* Allocate initial frame */
JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots);
if (!frame) {
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
JS_PopGCRef(ctx, &this_gc);
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);
env = env_gc.val; /* refresh — GC may have moved env during allocation */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->function = top_fn;
frame->slots[0] = this_gc.val; /* slot 0 = this */
/* Copy arguments from GC-safe refs */
for (int i = 0; i < nargs_copy; i++) {
frame->slots[1 + i] = arg_gcs[i].val;
}
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
JS_PopGCRef(ctx, &this_gc);
JS_PopGCRef(ctx, &of_gc);
JS_PopGCRef(ctx, &env_gc);
uint32_t pc = code->entry_point;
JSValue result = JS_NULL;
/* Execution loop — 32-bit instruction dispatch */
for (;;) {
#ifndef NDEBUG
if (pc >= code->instr_count) {
fprintf(stderr, "mach VM: pc %u overran code->instr_count %u (missing RETURN/RETNIL?)\n",
pc, code->instr_count);
result = JS_ThrowInternalError(ctx, "pc overrun");
goto done;
}
#endif
MachInstr32 instr;
int op, a, b, c;
#ifdef __GNUC__
/* Computed goto dispatch — each opcode gets its own branch predictor entry */
/* Use a macro to generate consistent dispatch table entries and labels */
#define DT(x) [x] = &&op_##x
static const void *dispatch_table[256] = {
[0 ... 255] = &&op_DEFAULT,
DT(MACH_LOADK), DT(MACH_LOADI),
DT(MACH_LOADNULL), DT(MACH_LOADTRUE),
DT(MACH_LOADFALSE), DT(MACH_MOVE),
DT(MACH_ADD), DT(MACH_SUB),
DT(MACH_MUL), DT(MACH_DIV),
DT(MACH_MOD), DT(MACH_POW),
DT(MACH_NEG), DT(MACH_INC), DT(MACH_DEC),
DT(MACH_EQ), DT(MACH_NEQ),
DT(MACH_LT), DT(MACH_LE),
DT(MACH_GT), DT(MACH_GE),
DT(MACH_LNOT), DT(MACH_BNOT),
DT(MACH_BAND), DT(MACH_BOR),
DT(MACH_BXOR), DT(MACH_SHL),
DT(MACH_SHR), DT(MACH_USHR),
DT(MACH_GETFIELD), DT(MACH_SETFIELD),
DT(MACH_GETINDEX), DT(MACH_SETINDEX),
DT(MACH_GETNAME), DT(MACH_GETINTRINSIC),
DT(MACH_GETENV),
DT(MACH_GETUP), DT(MACH_SETUP),
DT(MACH_JMP), DT(MACH_JMPTRUE),
DT(MACH_JMPFALSE), DT(MACH_JMPNULL),
DT(MACH_RETURN), DT(MACH_RETNIL),
DT(MACH_NEWOBJECT), DT(MACH_NEWARRAY),
DT(MACH_CLOSURE),
DT(MACH_THROW), DT(MACH_PUSH), DT(MACH_POP),
DT(MACH_DELETE), DT(MACH_DELETEINDEX),
DT(MACH_HASPROP), DT(MACH_REGEXP),
DT(MACH_EQ_TOL), DT(MACH_NEQ_TOL),
DT(MACH_NOP),
DT(MACH_CONCAT),
DT(MACH_EQ_INT), DT(MACH_NE_INT),
DT(MACH_LT_INT), DT(MACH_LE_INT),
DT(MACH_GT_INT), DT(MACH_GE_INT),
DT(MACH_EQ_FLOAT), DT(MACH_NE_FLOAT),
DT(MACH_LT_FLOAT), DT(MACH_LE_FLOAT),
DT(MACH_GT_FLOAT), DT(MACH_GE_FLOAT),
DT(MACH_EQ_TEXT), DT(MACH_NE_TEXT),
DT(MACH_LT_TEXT), DT(MACH_LE_TEXT),
DT(MACH_GT_TEXT), DT(MACH_GE_TEXT),
DT(MACH_EQ_BOOL), DT(MACH_NE_BOOL),
DT(MACH_IS_IDENTICAL),
DT(MACH_IS_INT), DT(MACH_IS_NUM),
DT(MACH_IS_TEXT), DT(MACH_IS_BOOL),
DT(MACH_IS_NULL),
DT(MACH_NOT), DT(MACH_AND), DT(MACH_OR),
DT(MACH_BITNOT), DT(MACH_BITAND),
DT(MACH_BITOR), DT(MACH_BITXOR),
DT(MACH_LOAD_FIELD), DT(MACH_STORE_FIELD),
DT(MACH_LOAD_INDEX), DT(MACH_STORE_INDEX),
DT(MACH_LOAD_DYNAMIC), DT(MACH_STORE_DYNAMIC),
DT(MACH_NEWRECORD),
DT(MACH_FRAME), DT(MACH_SETARG),
DT(MACH_INVOKE), DT(MACH_GOFRAME),
DT(MACH_GOINVOKE),
DT(MACH_JMPNOTNULL),
DT(MACH_DISRUPT),
DT(MACH_SET_VAR), DT(MACH_IN),
DT(MACH_IS_ARRAY), DT(MACH_IS_FUNC),
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
};
#undef DT
#define VM_DECODE() do { \
instr = code->instructions[pc++]; \
op = MACH_GET_OP(instr); \
a = MACH_GET_A(instr); \
b = MACH_GET_B(instr); \
c = MACH_GET_C(instr); \
} while(0)
#define VM_DISPATCH() do { VM_DECODE(); goto *dispatch_table[op]; } while(0)
/* VM_CASE: dual-label — goto target for computed goto + case for switch fallback */
#define VM_CASE(x) op_##x: case x
#define VM_DEFAULT op_DEFAULT: default
#define VM_BREAK() VM_DISPATCH()
#else
#define VM_DECODE() do { \
instr = code->instructions[pc++]; \
op = MACH_GET_OP(instr); \
a = MACH_GET_A(instr); \
b = MACH_GET_B(instr); \
c = MACH_GET_C(instr); \
} while(0)
#define VM_DISPATCH() do { VM_DECODE(); } while(0)
#define VM_CASE(x) case x
#define VM_DEFAULT default
#define VM_BREAK() break
#endif
VM_DECODE();
#ifdef __GNUC__
goto *dispatch_table[op];
#endif
switch (op) {
VM_CASE(MACH_NOP):
VM_BREAK();
VM_CASE(MACH_LOADK): {
int bx = MACH_GET_Bx(instr);
if (bx < (int)code->cpool_count)
frame->slots[a] = code->cpool[bx];
VM_BREAK();
}
VM_CASE(MACH_LOADI):
frame->slots[a] = JS_NewInt32(ctx, MACH_GET_sBx(instr));
VM_BREAK();
VM_CASE(MACH_LOADNULL):
frame->slots[a] = JS_NULL;
VM_BREAK();
VM_CASE(MACH_LOADTRUE):
frame->slots[a] = JS_TRUE;
VM_BREAK();
VM_CASE(MACH_LOADFALSE):
frame->slots[a] = JS_FALSE;
VM_BREAK();
VM_CASE(MACH_MOVE):
frame->slots[a] = frame->slots[b];
VM_BREAK();
/* Arithmetic — mcode guarantees both operands are numbers */
VM_CASE(MACH_ADD): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int64_t r = (int64_t)JS_VALUE_GET_INT(left) + (int64_t)JS_VALUE_GET_INT(right);
frame->slots[a] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
} else {
double da, db, r;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
r = da + db;
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
}
VM_BREAK();
}
VM_CASE(MACH_SUB): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int64_t r = (int64_t)JS_VALUE_GET_INT(left) - (int64_t)JS_VALUE_GET_INT(right);
frame->slots[a] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
} else {
double da, db, r;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
r = da - db;
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
}
VM_BREAK();
}
VM_CASE(MACH_MUL): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int64_t r = (int64_t)JS_VALUE_GET_INT(left) * (int64_t)JS_VALUE_GET_INT(right);
frame->slots[a] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
} else {
double da, db, r;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
r = da * db;
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
}
VM_BREAK();
}
VM_CASE(MACH_DIV): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right);
if (ib != 0 && ia % ib == 0)
frame->slots[a] = JS_NewInt32(ctx, ia / ib);
else if (ib != 0)
frame->slots[a] = JS_NewFloat64(ctx, (double)ia / (double)ib);
else
frame->slots[a] = JS_NULL;
} else {
double da, db, r;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
if (db == 0.0) { frame->slots[a] = JS_NULL; }
else {
r = da / db;
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
}
}
VM_BREAK();
}
VM_CASE(MACH_MOD): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ib = JS_VALUE_GET_INT(right);
frame->slots[a] = (ib != 0) ? JS_NewInt32(ctx, JS_VALUE_GET_INT(left) % ib) : JS_NULL;
} else {
double da, db, r;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
if (db == 0.0) { frame->slots[a] = JS_NULL; }
else {
r = fmod(da, db);
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
}
}
VM_BREAK();
}
VM_CASE(MACH_POW): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
double r = pow((double)JS_VALUE_GET_INT(left), (double)JS_VALUE_GET_INT(right));
if (!isfinite(r)) frame->slots[a] = JS_NULL;
else if (r >= INT32_MIN && r <= INT32_MAX && r == (int32_t)r)
frame->slots[a] = JS_NewInt32(ctx, (int32_t)r);
else
frame->slots[a] = JS_NewFloat64(ctx, r);
} else {
double da, db, r;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
r = pow(da, db);
frame->slots[a] = (!isfinite(r) && isfinite(da) && isfinite(db)) ? JS_NULL : JS_NewFloat64(ctx, r);
}
VM_BREAK();
}
/* Comparison — inline integer fast paths */
VM_CASE(MACH_EQ): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) == JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_EQ, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_NEQ): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) != JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_NEQ, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_LT): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) < JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_LT, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_LE): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) <= JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_LE, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_GT): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) > JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_GT, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_GE): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) >= JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_GE, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
/* Bitwise — inline integer fast paths */
VM_CASE(MACH_BAND): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) & JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_BAND, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_BOR): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) | JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_BOR, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_BXOR): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) ^ JS_VALUE_GET_INT(right));
} else {
JSValue res = reg_vm_binop(ctx, MACH_BXOR, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_SHL): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) << (JS_VALUE_GET_INT(right) & 31));
} else {
JSValue res = reg_vm_binop(ctx, MACH_SHL, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_SHR): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) >> (JS_VALUE_GET_INT(right) & 31));
} else {
JSValue res = reg_vm_binop(ctx, MACH_SHR, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_USHR): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[a] = JS_NewInt32(ctx, (uint32_t)JS_VALUE_GET_INT(left) >> (JS_VALUE_GET_INT(right) & 31));
} else {
JSValue res = reg_vm_binop(ctx, MACH_USHR, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_EQ_TOL):
VM_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 (mist_is_text(left) && mist_is_text(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;
}
VM_BREAK();
}
VM_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);
}
VM_BREAK();
}
VM_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);
}
VM_BREAK();
}
VM_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);
}
VM_BREAK();
}
VM_CASE(MACH_LNOT): {
int bval = JS_ToBool(ctx, frame->slots[b]);
frame->slots[a] = JS_NewBool(ctx, !bval);
VM_BREAK();
}
VM_CASE(MACH_BNOT): {
int32_t i;
JS_ToInt32(ctx, &i, frame->slots[b]);
frame->slots[a] = JS_NewInt32(ctx, ~i);
VM_BREAK();
}
VM_CASE(MACH_GETFIELD): {
JSValue obj = frame->slots[b];
JSValue key = code->cpool[c];
/* Non-proxy functions (arity != 2) can't have properties read */
if (mist_is_function(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;
VM_BREAK();
}
VM_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;
mach_resolve_forward(&frame->slots[a]);
VM_BREAK();
}
VM_CASE(MACH_GETINDEX): {
/* R(A) = R(B)[R(C)] — mcode guarantees R(C) is int */
JSValue obj = frame->slots[b];
JSValue val = JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[c]));
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[a] = val;
VM_BREAK();
}
VM_CASE(MACH_SETINDEX): {
/* R(A)[R(B)] = R(C) — mcode guarantees R(B) is int */
JSValue obj = frame->slots[a];
JSValue val = frame->slots[c];
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
VM_BREAK();
}
VM_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;
VM_BREAK();
}
VM_CASE(MACH_GETENV): {
/* Read env fresh from frame->function — C local env can go stale after GC */
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue val = JS_GetProperty(ctx, cur_env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = val;
VM_BREAK();
}
VM_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;
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
if (!JS_IsNull(cur_env)) {
val = JS_GetProperty(ctx, cur_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;
VM_BREAK();
}
VM_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);
if (!target) {
fprintf(stderr, "GETUP: NULL outer_frame at depth 0! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
pc-1, a, depth, c, code->nr_slots, instr);
result = JS_ThrowInternalError(ctx, "GETUP: NULL outer_frame");
goto disrupt;
}
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
if (!next) {
fprintf(stderr, "GETUP: NULL outer_frame at depth %d! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
d, pc-1, a, depth, c, code->nr_slots, instr);
result = JS_ThrowInternalError(ctx, "GETUP: NULL outer_frame at depth %d", d);
goto disrupt;
}
target = next;
}
frame->slots[a] = target->slots[c];
VM_BREAK();
}
VM_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];
VM_BREAK();
}
VM_CASE(MACH_JMP): {
int offset = MACH_GET_sJ(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0 && reg_vm_check_interrupt(ctx)) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
VM_BREAK();
}
VM_CASE(MACH_JMPTRUE): {
JSValue v = frame->slots[a];
int cond;
if (v == JS_TRUE) cond = 1;
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
else cond = JS_ToBool(ctx, v);
if (cond) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0 && reg_vm_check_interrupt(ctx)) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
}
VM_BREAK();
}
VM_CASE(MACH_JMPFALSE): {
JSValue v = frame->slots[a];
int cond;
if (v == JS_TRUE) cond = 1;
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
else cond = JS_ToBool(ctx, v);
if (!cond) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0 && reg_vm_check_interrupt(ctx)) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
}
VM_BREAK();
}
VM_CASE(MACH_JMPNULL): {
if (JS_IsNull(frame->slots[a])) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
}
VM_BREAK();
}
VM_CASE(MACH_RETURN):
result = frame->slots[a];
if (JS_IsNull(frame->caller)) goto done;
{
#ifdef VALIDATE_GC
const char *callee_name = "?";
const char *callee_file = "?";
{
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) {
if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr;
if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr;
}
}
#endif
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) {
#ifdef VALIDATE_GC
if (JS_IsPtr(result)) {
void *rp = JS_VALUE_GET_PTR(result);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp))
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n",
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc,
callee_name, callee_file,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
}
#endif
frame->slots[ret_slot] = result;
}
}
VM_BREAK();
VM_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;
}
VM_BREAK();
VM_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;
VM_BREAK();
}
VM_CASE(MACH_NEWARRAY): {
JSValue arr = JS_NewArrayCap(ctx, b);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { goto disrupt; }
frame->slots[a] = arr;
VM_BREAK();
}
VM_CASE(MACH_CLOSURE): {
int bx = MACH_GET_Bx(instr);
if ((uint32_t)bx < code->func_count) {
JSCodeRegister *fn_code = code->functions[bx];
/* Read env fresh from frame->function — C local can be stale */
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue fn_val = js_new_register_function(ctx, fn_code, cur_env, frame_ref.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = fn_val;
} else {
frame->slots[a] = JS_NULL;
}
VM_BREAK();
}
VM_CASE(MACH_PUSH): {
/* push R(B) onto array R(A) — mcode guarantees R(A) is array */
JSValue arr = frame->slots[a];
JSValue val = frame->slots[b];
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;
VM_BREAK();
}
VM_CASE(MACH_POP): {
/* R(A) = pop last element from array R(B) — mcode guarantees R(B) is array */
JSValue arr = frame->slots[b];
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;
VM_BREAK();
}
VM_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);
VM_BREAK();
}
VM_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);
VM_BREAK();
}
VM_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);
VM_BREAK();
}
VM_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;
VM_BREAK();
}
VM_CASE(MACH_THROW):
goto disrupt;
/* === New mcode-derived opcodes === */
/* Text concatenation */
VM_CASE(MACH_CONCAT): {
JSValue res = JS_ConcatString(ctx, frame->slots[b], frame->slots[c]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(res)) goto disrupt;
frame->slots[a] = res;
VM_BREAK();
}
/* Typed integer comparisons */
VM_CASE(MACH_EQ_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) == JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_NE_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) != JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_LT_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) < JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_LE_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) <= JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_GT_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) > JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_GE_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) >= JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
/* Typed float comparisons */
VM_CASE(MACH_EQ_FLOAT): VM_CASE(MACH_NE_FLOAT):
VM_CASE(MACH_LT_FLOAT): VM_CASE(MACH_LE_FLOAT):
VM_CASE(MACH_GT_FLOAT): VM_CASE(MACH_GE_FLOAT): {
double da, db;
JS_ToFloat64(ctx, &da, frame->slots[b]);
JS_ToFloat64(ctx, &db, frame->slots[c]);
int r;
switch (op) {
case MACH_EQ_FLOAT: r = (da == db); break;
case MACH_NE_FLOAT: r = (da != db); break;
case MACH_LT_FLOAT: r = (da < db); break;
case MACH_LE_FLOAT: r = (da <= db); break;
case MACH_GT_FLOAT: r = (da > db); break;
case MACH_GE_FLOAT: r = (da >= db); break;
default: r = 0; break;
}
frame->slots[a] = JS_NewBool(ctx, r);
VM_BREAK();
}
/* Typed text comparisons */
VM_CASE(MACH_EQ_TEXT): VM_CASE(MACH_NE_TEXT):
VM_CASE(MACH_LT_TEXT): VM_CASE(MACH_LE_TEXT):
VM_CASE(MACH_GT_TEXT): VM_CASE(MACH_GE_TEXT): {
int cmp = js_string_compare_value(ctx, frame->slots[b], frame->slots[c], FALSE);
int r;
switch (op) {
case MACH_EQ_TEXT: r = (cmp == 0); break;
case MACH_NE_TEXT: r = (cmp != 0); break;
case MACH_LT_TEXT: r = (cmp < 0); break;
case MACH_LE_TEXT: r = (cmp <= 0); break;
case MACH_GT_TEXT: r = (cmp > 0); break;
case MACH_GE_TEXT: r = (cmp >= 0); break;
default: r = 0; break;
}
frame->slots[a] = JS_NewBool(ctx, r);
VM_BREAK();
}
/* Typed bool comparisons */
VM_CASE(MACH_EQ_BOOL):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_BOOL(frame->slots[b]) == JS_VALUE_GET_BOOL(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_NE_BOOL):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_BOOL(frame->slots[b]) != JS_VALUE_GET_BOOL(frame->slots[c]));
VM_BREAK();
/* Identity check */
VM_CASE(MACH_IS_IDENTICAL): {
JSValue va = JS_IsPtr(frame->slots[b]) ? JS_MKPTR(chase(frame->slots[b])) : frame->slots[b];
JSValue vb = JS_IsPtr(frame->slots[c]) ? JS_MKPTR(chase(frame->slots[c])) : frame->slots[c];
frame->slots[a] = JS_NewBool(ctx, va == vb);
VM_BREAK();
}
/* Type checks */
VM_CASE(MACH_IS_INT):
frame->slots[a] = JS_NewBool(ctx, JS_IsInt(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_NUM):
frame->slots[a] = JS_NewBool(ctx, JS_IsNumber(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_TEXT):
frame->slots[a] = JS_NewBool(ctx, mist_is_text(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_BOOL):
frame->slots[a] = JS_NewBool(ctx, JS_IsBool(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_NULL):
frame->slots[a] = JS_NewBool(ctx, JS_IsNull(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_ARRAY):
frame->slots[a] = JS_NewBool(ctx, mist_is_array(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_FUNC):
frame->slots[a] = JS_NewBool(ctx, mist_is_function(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_RECORD):
frame->slots[a] = JS_NewBool(ctx, mist_is_record(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_STONE):
frame->slots[a] = JS_NewBool(ctx, mist_is_stone(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_LENGTH): {
JSValue v = frame->slots[b];
if (mist_is_array(v)) {
JSArray *arr = JS_VALUE_GET_ARRAY(v);
frame->slots[a] = JS_NewInt32(ctx, (int32_t)arr->len);
} else if (MIST_IsImmediateASCII(v)) {
frame->slots[a] = JS_NewInt32(ctx, MIST_GetImmediateASCIILen(v));
} else {
/* fallback to C for text/blob/function (still a GC safepoint) */
JSValue res = JS_CellLength(ctx, v);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = res;
}
VM_BREAK();
}
VM_CASE(MACH_IS_PROXY): {
JSValue v = frame->slots[b];
int is_proxy = 0;
if (mist_is_function(v)) {
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
is_proxy = (fn->length == 2);
}
frame->slots[a] = JS_NewBool(ctx, is_proxy);
VM_BREAK();
}
/* Logical */
VM_CASE(MACH_NOT): {
int bval = JS_ToBool(ctx, frame->slots[b]);
frame->slots[a] = JS_NewBool(ctx, !bval);
VM_BREAK();
}
VM_CASE(MACH_AND): {
JSValue left = frame->slots[b];
if (!JS_ToBool(ctx, left))
frame->slots[a] = left;
else
frame->slots[a] = frame->slots[c];
VM_BREAK();
}
VM_CASE(MACH_OR): {
JSValue left = frame->slots[b];
if (JS_ToBool(ctx, left))
frame->slots[a] = left;
else
frame->slots[a] = frame->slots[c];
VM_BREAK();
}
/* Bitwise (mcode names — delegate to same code as legacy) */
VM_CASE(MACH_BITNOT): {
int32_t i;
JS_ToInt32(ctx, &i, frame->slots[b]);
frame->slots[a] = JS_NewInt32(ctx, ~i);
VM_BREAK();
}
VM_CASE(MACH_BITAND): VM_CASE(MACH_BITOR): VM_CASE(MACH_BITXOR): {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, frame->slots[b]);
JS_ToInt32(ctx, &ib, frame->slots[c]);
int32_t r;
if (op == MACH_BITAND) r = ia & ib;
else if (op == MACH_BITOR) r = ia | ib;
else r = ia ^ ib;
frame->slots[a] = JS_NewInt32(ctx, r);
VM_BREAK();
}
/* Property access (mcode names) */
VM_CASE(MACH_LOAD_FIELD): {
JSValue obj = frame->slots[b];
JSValue key = code->cpool[c];
if (mist_is_function(obj)) {
JS_ThrowTypeError(ctx, "cannot read property of 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;
VM_BREAK();
}
VM_CASE(MACH_STORE_FIELD): {
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;
mach_resolve_forward(&frame->slots[a]);
VM_BREAK();
}
VM_CASE(MACH_LOAD_INDEX): {
/* R(A) = R(B)[R(C)] — mcode guarantees R(C) is int */
JSValue obj = frame->slots[b];
JSValue val = JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[c]));
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[a] = val;
VM_BREAK();
}
VM_CASE(MACH_STORE_INDEX): {
/* R(A)[R(B)] = R(C) — mcode guarantees R(B) is int */
JSValue obj = frame->slots[a];
JSValue val = frame->slots[c];
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
VM_BREAK();
}
VM_CASE(MACH_LOAD_DYNAMIC): {
JSValue obj = frame->slots[b];
JSValue key = frame->slots[c];
JSValue val;
if (JS_IsInt(key))
val = JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(key));
else
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;
VM_BREAK();
}
VM_CASE(MACH_STORE_DYNAMIC): {
JSValue obj = frame->slots[a];
JSValue key = frame->slots[b];
JSValue val = frame->slots[c];
int ret;
if (JS_IsInt(key)) {
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(key), val);
ret = JS_IsException(r) ? -1 : 0;
} else if (mist_is_array(obj)) {
JS_ThrowTypeError(ctx, "array index must be a number");
ret = -1;
} else if (JS_IsBool(key) || JS_IsNull(key) || mist_is_array(key) || mist_is_function(key)) {
JS_ThrowTypeError(ctx, "object key must be text");
ret = -1;
} else {
ret = JS_SetProperty(ctx, obj, key, val);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
VM_BREAK();
}
/* New record */
VM_CASE(MACH_NEWRECORD): {
JSValue obj = b > 0 ? JS_NewObjectCap(ctx, b) : JS_NewObject(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(obj)) goto disrupt;
frame->slots[a] = obj;
VM_BREAK();
}
/* Decomposed function calls (inlined from qbe_helpers) */
VM_CASE(MACH_FRAME):
VM_CASE(MACH_GOFRAME): {
/* A=frame_slot, B=func_reg, C=argc */
JSValue func_val = frame->slots[b];
if (!mist_is_function(func_val)) {
JS_ThrowTypeError(ctx, "not a function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
int nr = c + 2; /* argc + this + func overhead */
JSFrameRegister *call_frame = alloc_frame_register(ctx, nr);
if (!call_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
func_val = frame->slots[b]; /* re-read after GC */
call_frame->function = func_val;
frame->slots[a] = JS_MKPTR(call_frame);
VM_BREAK();
}
VM_CASE(MACH_SETARG): {
/* A=frame_slot, B=arg_idx, C=val_reg */
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
fr->slots[b] = frame->slots[c];
VM_BREAK();
}
VM_CASE(MACH_INVOKE): {
/* A=frame_slot, B=result_slot */
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
int nr = (int)objhdr_cap56(fr->header);
int c_argc = (nr >= 2) ? nr - 2 : 0;
JSValue fn_val = fr->function;
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
if (fn->kind == JS_FUNC_KIND_REGISTER) {
/* Register function: switch frames inline (fast path) */
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;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
fn_val = fr->function;
fn = JS_VALUE_GET_FUNCTION(fn_val);
fn_code = fn->u.reg.code;
new_frame->function = fn_val;
/* Copy this + args from call frame to new frame */
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
new_frame->slots[0] = fr->slots[0]; /* this */
for (int i = 0; i < copy_count; i++)
new_frame->slots[1 + i] = fr->slots[1 + i];
/* Save return info */
frame->address = JS_NewInt32(ctx, (pc << 16) | b);
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 {
/* C or bytecode function: args already in fr->slots (GC-protected via frame chain) */
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret;
if (fn->kind == JS_FUNC_KIND_C)
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
else
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
#ifdef VALIDATE_GC
if (JS_IsPtr(ret)) {
void *rp = JS_VALUE_GET_PTR(ret);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp)) {
int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1;
void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL;
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n",
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind,
magic, cfp,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
}
}
#endif
frame->slots[b] = ret;
}
VM_BREAK();
}
VM_CASE(MACH_GOINVOKE): {
/* Tail call: replace current frame with callee */
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
int nr = (int)objhdr_cap56(fr->header);
int c_argc = (nr >= 2) ? nr - 2 : 0;
JSValue fn_val = fr->function;
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
if (fn->kind == JS_FUNC_KIND_REGISTER) {
JSCodeRegister *fn_code = fn->u.reg.code;
int current_slots = (int)objhdr_cap56(frame->header);
if (fn_code->nr_slots <= current_slots) {
/* FAST PATH: reuse current frame — no allocation */
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
frame->slots[0] = fr->slots[0]; /* this */
for (int i = 0; i < copy_count; i++)
frame->slots[1 + i] = fr->slots[1 + i];
/* Null out remaining slots (locals/temps) */
for (int i = 1 + copy_count; i < current_slots; i++)
frame->slots[i] = JS_NULL;
frame->function = fn_val;
/* caller stays the same — we're reusing this frame */
code = fn_code;
env = fn->u.reg.env_record;
pc = code->entry_point;
} else {
/* SLOW PATH: callee needs more slots, must allocate */
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;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
fn_val = fr->function;
fn = JS_VALUE_GET_FUNCTION(fn_val);
fn_code = fn->u.reg.code;
new_frame->function = fn_val;
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
new_frame->slots[0] = fr->slots[0]; /* this */
for (int i = 0; i < copy_count; i++)
new_frame->slots[1 + i] = fr->slots[1 + i];
new_frame->caller = frame->caller;
frame->caller = JS_NULL;
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn_code;
env = fn->u.reg.env_record;
pc = code->entry_point;
}
} else {
/* C/bytecode function: call it, then return result to our caller */
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret;
if (fn->kind == JS_FUNC_KIND_C)
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
else
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
/* Tail-return: act like MACH_RETURN with the result */
result = ret;
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 *ret_fn = JS_VALUE_GET_FUNCTION(frame->function);
code = ret_fn->u.reg.code;
env = ret_fn->u.reg.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = ret;
}
VM_BREAK();
}
/* Jump if not null */
VM_CASE(MACH_JMPNOTNULL): {
if (!JS_IsNull(frame->slots[a])) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
}
VM_BREAK();
}
/* Disrupt (mcode alias) */
VM_CASE(MACH_DISRUPT):
goto disrupt;
/* Variable storage: env/global[K(Bx)] = R(A) */
VM_CASE(MACH_SET_VAR): {
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = frame->slots[a];
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
if (!JS_IsNull(cur_env)) {
JS_SetProperty(ctx, cur_env, key, val);
} else {
JS_SetProperty(ctx, ctx->global_obj, key, val);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
VM_BREAK();
}
/* Has-property check (mcode name) */
VM_CASE(MACH_IN): {
JSValue key = frame->slots[b];
JSValue obj = 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);
VM_BREAK();
}
VM_DEFAULT:
result = JS_ThrowInternalError(ctx, "unknown register VM opcode %d: %s", op, mach_opcode_names[op]);
goto done;
}
continue;
disrupt:
/* Save debug info for stack traces */
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
/* 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.
Keep frame_ref.val at the faulting frame and preserve caller
links so the trace walk can show the full call chain. */
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;
ctx->disruption_reported = FALSE;
frame_ref.val = JS_MKPTR(frame); /* root handler frame for GC */
break;
}
if (JS_IsNull(frame->caller)) {
if (!ctx->disruption_reported) {
/* Use faulting frame (frame_ref.val) for the header */
JSFrameRegister *fault = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSFunction *fault_fn = JS_VALUE_GET_FUNCTION(fault->function);
JSCodeRegister *fault_code = fault_fn->u.reg.code;
const char *fn_name = fault_code->name_cstr ? fault_code->name_cstr : "<anonymous>";
const char *file = fault_code->filename_cstr ? fault_code->filename_cstr : "<unknown>";
uint16_t line = 0, col = 0;
uint32_t fpc = pc > 0 ? pc - 1 : 0;
if (fault_code->line_table && fpc < fault_code->instr_count) {
line = fault_code->line_table[fpc].line;
col = fault_code->line_table[fpc].col;
}
fprintf(stderr, "unhandled disruption in %s (%s:%u:%u)\n", fn_name, file, line, col);
/* Walk intact caller chain for stack trace */
{
JSFrameRegister *trace_frame = fault;
int first = 1;
while (trace_frame) {
if (!mist_is_function(trace_frame->function)) break;
JSFunction *trace_fn = JS_VALUE_GET_FUNCTION(trace_frame->function);
if (trace_fn->kind == JS_FUNC_KIND_REGISTER && trace_fn->u.reg.code) {
JSCodeRegister *tc = trace_fn->u.reg.code;
uint32_t tpc = first ? fpc
: (uint32_t)(JS_VALUE_GET_INT(trace_frame->address) >> 16);
uint16_t tl = 0, tcol = 0;
if (tc->line_table && tpc < tc->instr_count) {
tl = tc->line_table[tpc].line;
tcol = tc->line_table[tpc].col;
}
fprintf(stderr, " at %s (%s:%u:%u)\n",
tc->name_cstr ? tc->name_cstr : "<anonymous>",
tc->filename_cstr ? tc->filename_cstr : "<unknown>", tl, tcol);
}
if (JS_IsNull(trace_frame->caller)) break;
trace_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(trace_frame->caller);
first = 0;
}
}
ctx->disruption_reported = TRUE;
}
frame_ref.val = JS_MKPTR(frame); /* update root for GC / done */
result = JS_Throw(ctx, JS_NULL);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto done;
}
/* Advance to caller — keep chain intact (no nulling caller links) */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
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 Lowering — mcode JSON IR → MachInstr32
============================================================ */
typedef struct {
MachInstr32 *code;
int code_count, code_cap;
MachCPoolEntry *cpool;
int cpool_count, cpool_cap;
MachLineEntry *lines;
int nr_slots;
struct { const char *name; int pc; } *labels;
int label_count, label_cap;
struct { int pc; const char *label; int is_sJ; int reg_a; } *patches;
int patch_count, patch_cap;
int *flat_to_pc;
int flat_count;
} McodeLowerState;
static void ml_emit(McodeLowerState *s, MachInstr32 instr, int line, int col) {
if (s->code_count >= s->code_cap) {
int nc = s->code_cap ? s->code_cap * 2 : 64;
s->code = sys_realloc(s->code, nc * sizeof(MachInstr32));
s->lines = sys_realloc(s->lines, nc * sizeof(MachLineEntry));
s->code_cap = nc;
}
s->lines[s->code_count] = (MachLineEntry){(uint16_t)line, (uint16_t)col};
s->code[s->code_count++] = instr;
}
static int ml_cpool_int(McodeLowerState *s, int32_t val) {
for (int i = 0; i < s->cpool_count; i++)
if (s->cpool[i].type == MACH_CP_INT && s->cpool[i].ival == val) return i;
if (s->cpool_count >= s->cpool_cap) {
int nc = s->cpool_cap ? s->cpool_cap * 2 : 16;
s->cpool = sys_realloc(s->cpool, nc * sizeof(MachCPoolEntry));
s->cpool_cap = nc;
}
s->cpool[s->cpool_count] = (MachCPoolEntry){.type = MACH_CP_INT, .ival = val};
return s->cpool_count++;
}
static int ml_cpool_float(McodeLowerState *s, double val) {
for (int i = 0; i < s->cpool_count; i++)
if (s->cpool[i].type == MACH_CP_FLOAT && s->cpool[i].fval == val) return i;
if (s->cpool_count >= s->cpool_cap) {
int nc = s->cpool_cap ? s->cpool_cap * 2 : 16;
s->cpool = sys_realloc(s->cpool, nc * sizeof(MachCPoolEntry));
s->cpool_cap = nc;
}
s->cpool[s->cpool_count] = (MachCPoolEntry){.type = MACH_CP_FLOAT, .fval = val};
return s->cpool_count++;
}
static int ml_cpool_str(McodeLowerState *s, const char *str) {
if (!str) { fprintf(stderr, "ml_cpool_str: NULL string\n"); str = ""; }
for (int i = 0; i < s->cpool_count; i++)
if (s->cpool[i].type == MACH_CP_STR && strcmp(s->cpool[i].str, str) == 0) return i;
if (s->cpool_count >= s->cpool_cap) {
int nc = s->cpool_cap ? s->cpool_cap * 2 : 16;
s->cpool = sys_realloc(s->cpool, nc * sizeof(MachCPoolEntry));
s->cpool_cap = nc;
}
char *dup = sys_malloc(strlen(str) + 1);
memcpy(dup, str, strlen(str) + 1);
s->cpool[s->cpool_count] = (MachCPoolEntry){.type = MACH_CP_STR, .str = dup};
return s->cpool_count++;
}
static void ml_label(McodeLowerState *s, const char *name) {
if (s->label_count >= s->label_cap) {
int nc = s->label_cap ? s->label_cap * 2 : 32;
s->labels = sys_realloc(s->labels, nc * sizeof(s->labels[0]));
s->label_cap = nc;
}
s->labels[s->label_count].name = name;
s->labels[s->label_count].pc = s->code_count;
s->label_count++;
}
static void ml_patch(McodeLowerState *s, int pc, const char *label, int is_sJ, int reg_a) {
if (s->patch_count >= s->patch_cap) {
int nc = s->patch_cap ? s->patch_cap * 2 : 32;
s->patches = sys_realloc(s->patches, nc * sizeof(s->patches[0]));
s->patch_cap = nc;
}
s->patches[s->patch_count].pc = pc;
s->patches[s->patch_count].label = label;
s->patches[s->patch_count].is_sJ = is_sJ;
s->patches[s->patch_count].reg_a = reg_a;
s->patch_count++;
}
static int ml_int(cJSON *arr, int idx) {
return (int)cJSON_GetArrayItem(arr, idx)->valuedouble;
}
/* ---- Register compression ----
The mcode compiler allocates slots monotonically, producing register numbers
that can exceed 255. Since MachInstr32 uses 8-bit fields, we must compress
the register space via live-range analysis before lowering.
For each slot we record its first and last instruction reference, then do a
greedy linear-scan allocation to pack them into the fewest physical registers.
Slots referenced by child functions via get/put (parent_slot) are in the
PARENT frame and are not remapped here — only current-frame register
operands are touched. */
#define MAX_REG_ITEMS 32
/* Return cJSON pointers to all current-frame register operands in an
instruction. out[] must have room for MAX_REG_ITEMS entries. */
static int mcode_reg_items(cJSON *it, cJSON **out) {
int sz = cJSON_GetArraySize(it);
if (sz < 3) return 0;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
int c = 0;
#define ADD(pos) do { \
cJSON *_r = cJSON_GetArrayItem(it, (pos)); \
if (_r && cJSON_IsNumber(_r) && c < MAX_REG_ITEMS) out[c++] = _r; \
} while (0)
/* get/put: only [1] is current-frame (dest/src); [2]=parent_slot, [3]=level */
if (!strcmp(op, "get") || !strcmp(op, "put")) { ADD(1); return c; }
/* dest-only */
if (!strcmp(op, "access") || !strcmp(op, "int") ||
!strcmp(op, "function") || !strcmp(op, "regexp") ||
!strcmp(op, "true") || !strcmp(op, "false") || !strcmp(op, "null"))
{ ADD(1); return c; }
/* invoke: [1]=frame, [2]=dest (result register) */
if (!strcmp(op, "invoke") || !strcmp(op, "tail_invoke")) { ADD(1); ADD(2); return c; }
/* goinvoke: [1]=frame only (no result) */
if (!strcmp(op, "goinvoke")) { ADD(1); return c; }
/* set_var: [1]=name(string), [2]=val */
if (!strcmp(op, "set_var")) { ADD(2); return c; }
/* setarg: [1]=call, [2]=arg_idx(const), [3]=val */
if (!strcmp(op, "setarg")) { ADD(1); ADD(3); return c; }
/* frame/goframe: [1]=call, [2]=func, [3]=nr_args(const) */
if (!strcmp(op, "frame") || !strcmp(op, "goframe")) { ADD(1); ADD(2); return c; }
/* no regs */
if (!strcmp(op, "jump") || !strcmp(op, "disrupt")) return 0;
/* cond only */
if (!strcmp(op, "jump_true") || !strcmp(op, "jump_false") ||
!strcmp(op, "jump_not_null"))
{ ADD(1); return c; }
/* single reg */
if (!strcmp(op, "return")) { ADD(1); return c; }
/* delete: [1]=dest, [2]=obj, [3]=key (string or reg) */
if (!strcmp(op, "delete")) {
ADD(1); ADD(2);
cJSON *k = cJSON_GetArrayItem(it, 3);
if (k && cJSON_IsNumber(k)) out[c++] = k;
return c;
}
/* record: [1]=dest, [2]=0(const) — no line/col suffix */
if (!strcmp(op, "record")) { ADD(1); return c; }
/* array: [1]=dest, [2]=count(const) — elements added via separate push instrs */
if (!strcmp(op, "array")) {
ADD(1);
return c;
}
/* load_field: [1]=dest, [2]=obj, [3]=key (string or reg) */
if (!strcmp(op, "load_field")) {
ADD(1); ADD(2);
cJSON *key = cJSON_GetArrayItem(it, 3);
if (key && cJSON_IsNumber(key)) out[c++] = key;
return c;
}
/* store_field: [1]=obj, [2]=val, [3]=key (string or reg) */
if (!strcmp(op, "store_field")) {
ADD(1); ADD(2);
cJSON *key = cJSON_GetArrayItem(it, 3);
if (key && cJSON_IsNumber(key)) out[c++] = key;
return c;
}
/* Default: every numeric operand in [1..sz-3] is a register.
Covers move, arithmetic, comparisons, type checks, push, pop,
load_dynamic, store_dynamic, in, concat, logical, bitwise, etc. */
for (int j = 1; j < sz - 2; j++) {
cJSON *item = cJSON_GetArrayItem(it, j);
if (item && cJSON_IsNumber(item)) out[c++] = item;
}
return c;
#undef ADD
}
/* Compress register numbers in a single function's mcode JSON so they
fit in 8 bits. Modifies the cJSON instructions and nr_slots in place.
Returns a malloc'd remap table (caller must free), or NULL if no
compression was needed. *out_old_nr_slots is set to the original count. */
static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
int *captured_slots, int n_captured) {
cJSON *nr_slots_j = cJSON_GetObjectItemCaseSensitive(fobj, "nr_slots");
int nr_slots = (int)cJSON_GetNumberValue(nr_slots_j);
*out_old_nr_slots = nr_slots;
if (nr_slots <= 255) return NULL;
int nr_args = (int)cJSON_GetNumberValue(
cJSON_GetObjectItemCaseSensitive(fobj, "nr_args"));
cJSON *instrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int n = instrs ? cJSON_GetArraySize(instrs) : 0;
/* Step 1: build live ranges (first_ref / last_ref per slot) */
int *first_ref = sys_malloc(nr_slots * sizeof(int));
int *last_ref = sys_malloc(nr_slots * sizeof(int));
for (int i = 0; i < nr_slots; i++) { first_ref[i] = -1; last_ref[i] = -1; }
/* this + args are live for the whole function */
int pinned = 1 + nr_args;
for (int i = 0; i < pinned; i++) { first_ref[i] = 0; last_ref[i] = n; }
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs);
for (int j = 0; j < rc; j++) {
int s = (int)regs[j]->valuedouble;
if (s < 0 || s >= nr_slots) continue;
if (first_ref[s] < 0) first_ref[s] = i;
last_ref[s] = i;
}
} }
/* Step 1a: extend live ranges for closure-captured slots.
If a child function captures a parent slot via get/put, that slot must
remain live for the entire parent function (the closure can read it at
any time while the parent frame is on the stack). */
for (int ci = 0; ci < n_captured; ci++) {
int s = captured_slots[ci];
if (s >= 0 && s < nr_slots) {
if (first_ref[s] < 0) first_ref[s] = 0;
last_ref[s] = n;
}
}
/* Step 1b: extend live ranges for loops (backward jumps).
Build label→position map, then for each backward jump [target..jump],
extend all overlapping live ranges to cover the full loop body. */
{
/* Collect label positions */
typedef struct { const char *name; int pos; } LabelPos;
int lbl_cap = 32, lbl_n = 0;
LabelPos *lbls = sys_malloc(lbl_cap * sizeof(LabelPos));
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (cJSON_IsString(it)) {
if (lbl_n >= lbl_cap) {
lbl_cap *= 2;
lbls = sys_realloc(lbls, lbl_cap * sizeof(LabelPos));
}
lbls[lbl_n++] = (LabelPos){it->valuestring, i};
}
} }
/* Find backward jumps and extend live ranges */
int changed = 1;
while (changed) {
changed = 0;
cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
int sz = cJSON_GetArraySize(it);
if (sz < 3) continue;
const char *op = it->child->valuestring;
const char *target = NULL;
if (!strcmp(op, "jump")) {
target = it->child->next->valuestring;
} else if (!strcmp(op, "jump_true") || !strcmp(op, "jump_false") ||
!strcmp(op, "jump_not_null")) {
target = it->child->next->next->valuestring;
}
if (!target) continue;
/* Find label position */
int tpos = -1;
for (int j = 0; j < lbl_n; j++) {
if (!strcmp(lbls[j].name, target)) { tpos = lbls[j].pos; break; }
}
if (tpos < 0 || tpos >= i) continue; /* forward jump or not found */
/* Backward jump: extend registers that are live INTO the loop
(first_ref < loop start but used inside). Temporaries born
inside the loop body don't need extension — they are per-iteration. */
for (int s = pinned; s < nr_slots; s++) {
if (first_ref[s] < 0) continue;
if (first_ref[s] >= tpos) continue; /* born inside loop — skip */
if (last_ref[s] < tpos) continue; /* dead before loop — skip */
/* Register is live into the loop body — extend to loop end */
if (last_ref[s] < i) { last_ref[s] = i; changed = 1; }
}
}
}
sys_free(lbls);
}
/* Step 2: linear-scan register allocation */
typedef struct { int slot, first, last; } SlotInfo;
int cnt = 0;
SlotInfo *sorted = sys_malloc(nr_slots * sizeof(SlotInfo));
for (int s = pinned; s < nr_slots; s++)
if (first_ref[s] >= 0)
sorted[cnt++] = (SlotInfo){s, first_ref[s], last_ref[s]};
/* Sort by first_ref, tie-break by original slot (keeps named vars first) */
for (int i = 1; i < cnt; i++) {
SlotInfo key = sorted[i];
int j = i - 1;
while (j >= 0 && (sorted[j].first > key.first ||
(sorted[j].first == key.first && sorted[j].slot > key.slot))) {
sorted[j + 1] = sorted[j];
j--;
}
sorted[j + 1] = key;
}
int *remap = sys_malloc(nr_slots * sizeof(int));
for (int i = 0; i < nr_slots; i++) remap[i] = i;
/* Free-register pool (min-heap would be ideal but a flat scan is fine) */
int *pool = sys_malloc(nr_slots * sizeof(int));
int pool_n = 0;
int next_phys = pinned;
typedef struct { int phys, last; } ActiveAlloc;
ActiveAlloc *active = sys_malloc(cnt * sizeof(ActiveAlloc));
int active_n = 0;
for (int i = 0; i < cnt; i++) {
int first = sorted[i].first;
/* Expire intervals whose last_ref < first */
for (int j = 0; j < active_n; ) {
if (active[j].last < first) {
pool[pool_n++] = active[j].phys;
active[j] = active[--active_n];
} else {
j++;
}
}
/* Pick lowest available physical register */
int phys;
if (pool_n > 0) {
int mi = 0;
for (int j = 1; j < pool_n; j++)
if (pool[j] < pool[mi]) mi = j;
phys = pool[mi];
pool[mi] = pool[--pool_n];
} else {
phys = next_phys++;
}
remap[sorted[i].slot] = phys;
active[active_n++] = (ActiveAlloc){phys, sorted[i].last};
}
/* Compute new nr_slots */
int new_max = pinned;
for (int s = 0; s < nr_slots; s++)
if (first_ref[s] >= 0 && remap[s] >= new_max)
new_max = remap[s] + 1;
if (new_max > 255)
fprintf(stderr, " WARNING: %d live regs still exceeds 255\n", new_max);
/* Verify: check no two registers with overlapping live ranges share phys */
for (int a = pinned; a < nr_slots; a++) {
if (first_ref[a] < 0) continue;
for (int b = a + 1; b < nr_slots; b++) {
if (first_ref[b] < 0) continue;
if (remap[a] != remap[b]) continue;
/* Same phys — ranges must NOT overlap */
if (first_ref[a] <= last_ref[b] && first_ref[b] <= last_ref[a]) {
fprintf(stderr, " OVERLAP: slot %d [%d,%d] and slot %d [%d,%d] -> phys %d\n",
a, first_ref[a], last_ref[a], b, first_ref[b], last_ref[b], remap[a]);
}
}
}
/* Step 3: apply remap to instructions */
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs);
for (int j = 0; j < rc; j++) {
int old = (int)regs[j]->valuedouble;
if (old >= 0 && old < nr_slots) {
cJSON_SetNumberValue(regs[j], remap[old]);
}
}
} }
/* Update nr_slots in the JSON */
cJSON_SetNumberValue(nr_slots_j, new_max);
sys_free(first_ref); sys_free(last_ref);
sys_free(sorted);
sys_free(pool); sys_free(active);
return remap; /* caller must free */
}
/* Lower one function's mcode instructions to MachInstr32 */
static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
McodeLowerState s = {0};
int nr_args = (int)cJSON_GetNumberValue(
cJSON_GetObjectItemCaseSensitive(fobj, "nr_args"));
int nr_close = (int)cJSON_GetNumberValue(
cJSON_GetObjectItemCaseSensitive(fobj, "nr_close_slots"));
s.nr_slots = (int)cJSON_GetNumberValue(
cJSON_GetObjectItemCaseSensitive(fobj, "nr_slots"));
int dis_raw = (int)cJSON_GetNumberValue(
cJSON_GetObjectItemCaseSensitive(fobj, "disruption_pc"));
cJSON *nm = cJSON_GetObjectItemCaseSensitive(fobj, "name");
const char *name = nm ? cJSON_GetStringValue(nm) : NULL;
cJSON *fn_j = cJSON_GetObjectItemCaseSensitive(fobj, "filename");
const char *fname = fn_j ? cJSON_GetStringValue(fn_j) : filename;
cJSON *instrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int n = instrs ? cJSON_GetArraySize(instrs) : 0;
s.flat_to_pc = sys_malloc((n + 1) * sizeof(int));
s.flat_count = n;
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
s.flat_to_pc[i] = s.code_count;
if (cJSON_IsString(it)) {
ml_label(&s, it->valuestring);
continue;
}
int sz = cJSON_GetArraySize(it);
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
int line = ml_int(it, sz - 2);
int col = ml_int(it, sz - 1);
#define A1 ml_int(it,1)
#define A2 ml_int(it,2)
#define A3 ml_int(it,3)
#define A4 ml_int(it,4)
#define EM(instr) ml_emit(&s, (instr), line, col)
#define ABC3(opc) EM(MACH_ABC(opc, A1, A2, A3))
#define AB2(opc) EM(MACH_ABC(opc, A1, A2, 0))
if (strcmp(op, "access") == 0) {
int dest = A1;
cJSON *val = cJSON_GetArrayItem(it, 2);
if (cJSON_IsNumber(val)) {
double dv = val->valuedouble;
int32_t iv = (int32_t)dv;
if ((double)iv == dv && iv >= -32768 && iv <= 32767)
EM(MACH_AsBx(MACH_LOADI, dest, iv));
else if ((double)iv == dv)
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_int(&s, iv)));
else
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_float(&s, dv)));
} else if (cJSON_IsString(val)) {
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_str(&s, val->valuestring)));
} else if (cJSON_IsObject(val)) {
const char *vn = cJSON_GetStringValue(
cJSON_GetObjectItemCaseSensitive(val, "name"));
EM(MACH_ABx(MACH_GETNAME, dest, ml_cpool_str(&s, vn)));
}
}
else if (strcmp(op, "int") == 0) {
int dest = A1, v = A2;
if (v >= -32768 && v <= 32767)
EM(MACH_AsBx(MACH_LOADI, dest, v));
else
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_int(&s, v)));
}
else if (strcmp(op, "true") == 0) { EM(MACH_ABC(MACH_LOADTRUE, A1, 0, 0)); }
else if (strcmp(op, "false") == 0) { EM(MACH_ABC(MACH_LOADFALSE, A1, 0, 0)); }
else if (strcmp(op, "null") == 0) { EM(MACH_ABC(MACH_LOADNULL, A1, 0, 0)); }
else if (strcmp(op, "move") == 0) { AB2(MACH_MOVE); }
/* Text */
else if (strcmp(op, "concat") == 0) { ABC3(MACH_CONCAT); }
/* Generic arithmetic */
else if (strcmp(op, "add") == 0) { ABC3(MACH_ADD); }
else if (strcmp(op, "subtract") == 0) { ABC3(MACH_SUB); }
else if (strcmp(op, "multiply") == 0) { ABC3(MACH_MUL); }
else if (strcmp(op, "divide") == 0) { ABC3(MACH_DIV); }
else if (strcmp(op, "modulo") == 0) { ABC3(MACH_MOD); }
else if (strcmp(op, "pow") == 0) { ABC3(MACH_POW); }
else if (strcmp(op, "negate") == 0) { AB2(MACH_NEG); }
/* Typed integer comparisons */
else if (strcmp(op, "eq_int") == 0) { ABC3(MACH_EQ_INT); }
else if (strcmp(op, "ne_int") == 0) { ABC3(MACH_NE_INT); }
else if (strcmp(op, "lt_int") == 0) { ABC3(MACH_LT_INT); }
else if (strcmp(op, "le_int") == 0) { ABC3(MACH_LE_INT); }
else if (strcmp(op, "gt_int") == 0) { ABC3(MACH_GT_INT); }
else if (strcmp(op, "ge_int") == 0) { ABC3(MACH_GE_INT); }
/* Typed float comparisons */
else if (strcmp(op, "eq_float") == 0) { ABC3(MACH_EQ_FLOAT); }
else if (strcmp(op, "ne_float") == 0) { ABC3(MACH_NE_FLOAT); }
else if (strcmp(op, "lt_float") == 0) { ABC3(MACH_LT_FLOAT); }
else if (strcmp(op, "le_float") == 0) { ABC3(MACH_LE_FLOAT); }
else if (strcmp(op, "gt_float") == 0) { ABC3(MACH_GT_FLOAT); }
else if (strcmp(op, "ge_float") == 0) { ABC3(MACH_GE_FLOAT); }
/* Typed text comparisons */
else if (strcmp(op, "eq_text") == 0) { ABC3(MACH_EQ_TEXT); }
else if (strcmp(op, "ne_text") == 0) { ABC3(MACH_NE_TEXT); }
else if (strcmp(op, "lt_text") == 0) { ABC3(MACH_LT_TEXT); }
else if (strcmp(op, "le_text") == 0) { ABC3(MACH_LE_TEXT); }
else if (strcmp(op, "gt_text") == 0) { ABC3(MACH_GT_TEXT); }
else if (strcmp(op, "ge_text") == 0) { ABC3(MACH_GE_TEXT); }
/* Typed bool comparisons */
else if (strcmp(op, "eq_bool") == 0) { ABC3(MACH_EQ_BOOL); }
else if (strcmp(op, "ne_bool") == 0) { ABC3(MACH_NE_BOOL); }
/* Special comparisons */
else if (strcmp(op, "is_identical") == 0) { ABC3(MACH_IS_IDENTICAL); }
else if (strcmp(op, "eq_tol") == 0) {
/* A=dest, B=a, C=b; tolerance in operand 4 */
int dest = A1, ra = A2, rb = A3, rt = A4;
/* Move to consecutive scratch slots at end of frame */
int base = s.nr_slots;
s.nr_slots += 3;
EM(MACH_ABC(MACH_MOVE, base, ra, 0));
EM(MACH_ABC(MACH_MOVE, base + 1, rb, 0));
EM(MACH_ABC(MACH_MOVE, base + 2, rt, 0));
EM(MACH_ABC(MACH_EQ_TOL, dest, base, 3));
}
else if (strcmp(op, "ne_tol") == 0) {
int dest = A1, ra = A2, rb = A3, rt = A4;
int base = s.nr_slots;
s.nr_slots += 3;
EM(MACH_ABC(MACH_MOVE, base, ra, 0));
EM(MACH_ABC(MACH_MOVE, base + 1, rb, 0));
EM(MACH_ABC(MACH_MOVE, base + 2, rt, 0));
EM(MACH_ABC(MACH_NEQ_TOL, dest, base, 3));
}
/* Type checks */
else if (strcmp(op, "is_int") == 0) { AB2(MACH_IS_INT); }
else if (strcmp(op, "is_num") == 0) { AB2(MACH_IS_NUM); }
else if (strcmp(op, "is_text") == 0) { AB2(MACH_IS_TEXT); }
else if (strcmp(op, "is_bool") == 0) { AB2(MACH_IS_BOOL); }
else if (strcmp(op, "is_null") == 0) { AB2(MACH_IS_NULL); }
else if (strcmp(op, "is_array") == 0) { AB2(MACH_IS_ARRAY); }
else if (strcmp(op, "is_func") == 0) { AB2(MACH_IS_FUNC); }
else if (strcmp(op, "is_record") == 0) { AB2(MACH_IS_RECORD); }
else if (strcmp(op, "is_stone") == 0) { AB2(MACH_IS_STONE); }
else if (strcmp(op, "length") == 0) { AB2(MACH_LENGTH); }
else if (strcmp(op, "is_proxy") == 0) { AB2(MACH_IS_PROXY); }
/* Logical */
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); }
else if (strcmp(op, "or") == 0) { ABC3(MACH_OR); }
/* Bitwise */
else if (strcmp(op, "bitnot") == 0) { AB2(MACH_BITNOT); }
else if (strcmp(op, "bitand") == 0) { ABC3(MACH_BITAND); }
else if (strcmp(op, "bitor") == 0) { ABC3(MACH_BITOR); }
else if (strcmp(op, "bitxor") == 0) { ABC3(MACH_BITXOR); }
else if (strcmp(op, "shl") == 0) { ABC3(MACH_SHL); }
else if (strcmp(op, "shr") == 0) { ABC3(MACH_SHR); }
else if (strcmp(op, "ushr") == 0) { ABC3(MACH_USHR); }
/* Property access */
else if (strcmp(op, "load_field") == 0) {
int dest = A1, obj = A2;
cJSON *key_item = cJSON_GetArrayItem(it, 3);
if (cJSON_IsString(key_item)) {
int ki = ml_cpool_str(&s, key_item->valuestring);
if (ki <= 255) {
EM(MACH_ABC(MACH_LOAD_FIELD, dest, obj, ki));
} else {
/* cpool index > 255: load key via LOADK, then use dynamic access */
int tmp = s.nr_slots++;
EM(MACH_ABx(MACH_LOADK, tmp, ki));
EM(MACH_ABC(MACH_LOAD_DYNAMIC, dest, obj, tmp));
}
} else {
/* key is a register — fall back to dynamic access */
int key_reg = (int)key_item->valuedouble;
EM(MACH_ABC(MACH_LOAD_DYNAMIC, dest, obj, key_reg));
}
}
else if (strcmp(op, "store_field") == 0) {
int obj = A1, val = A2;
cJSON *key_item = cJSON_GetArrayItem(it, 3);
if (cJSON_IsString(key_item)) {
int ki = ml_cpool_str(&s, key_item->valuestring);
if (ki <= 255) {
EM(MACH_ABC(MACH_STORE_FIELD, obj, ki, val));
} else {
/* cpool index > 255: load key via LOADK, then use dynamic access */
int tmp = s.nr_slots++;
EM(MACH_ABx(MACH_LOADK, tmp, ki));
EM(MACH_ABC(MACH_STORE_DYNAMIC, obj, tmp, val));
}
} else {
/* key is a register — fall back to dynamic access */
int key_reg = (int)key_item->valuedouble;
EM(MACH_ABC(MACH_STORE_DYNAMIC, obj, key_reg, val));
}
}
else if (strcmp(op, "load_index") == 0) { ABC3(MACH_LOAD_INDEX); }
else if (strcmp(op, "store_index") == 0) {
/* mcode: store_index obj val idx → VM: R(A)[R(B)] = R(C) */
EM(MACH_ABC(MACH_STORE_INDEX, A1, A3, A2));
}
else if (strcmp(op, "load_dynamic") == 0) { ABC3(MACH_LOAD_DYNAMIC); }
else if (strcmp(op, "store_dynamic") == 0) {
/* mcode: store_dynamic obj val key → VM: R(A)[R(B)] = R(C) */
EM(MACH_ABC(MACH_STORE_DYNAMIC, A1, A3, A2));
}
/* Delete */
else if (strcmp(op, "delete") == 0) {
int dest = A1, obj = A2;
cJSON *key_item = cJSON_GetArrayItem(it, 3);
if (cJSON_IsString(key_item)) {
int ki = ml_cpool_str(&s, key_item->valuestring);
if (ki <= 255) {
EM(MACH_ABC(MACH_DELETE, dest, obj, ki));
} else {
int tmp = s.nr_slots++;
EM(MACH_ABx(MACH_LOADK, tmp, ki));
EM(MACH_ABC(MACH_DELETEINDEX, dest, obj, tmp));
}
} else {
int key_reg = (int)key_item->valuedouble;
EM(MACH_ABC(MACH_DELETEINDEX, dest, obj, key_reg));
}
}
/* Array/Object creation */
else if (strcmp(op, "array") == 0) {
EM(MACH_ABC(MACH_NEWARRAY, A1, A2, 0));
}
else if (strcmp(op, "record") == 0) {
EM(MACH_ABC(MACH_NEWRECORD, A1, A2, 0));
}
/* Push/Pop */
else if (strcmp(op, "push") == 0) {
EM(MACH_ABC(MACH_PUSH, A1, A2, 0));
}
else if (strcmp(op, "pop") == 0) {
EM(MACH_ABC(MACH_POP, A1, A2, 0));
}
/* Closure access */
else if (strcmp(op, "get") == 0) {
/* mcode: get dest slot level → GETUP A=dest B=level C=slot */
EM(MACH_ABC(MACH_GETUP, A1, A3, A2));
}
else if (strcmp(op, "put") == 0) {
/* mcode: put src slot level → SETUP A=src B=level C=slot */
EM(MACH_ABC(MACH_SETUP, A1, A3, A2));
}
/* Function creation */
else if (strcmp(op, "function") == 0) {
EM(MACH_ABx(MACH_CLOSURE, A1, A2));
}
/* Decomposed function calls */
else if (strcmp(op, "frame") == 0) {
EM(MACH_ABC(MACH_FRAME, A1, A2, A3));
}
else if (strcmp(op, "setarg") == 0) {
EM(MACH_ABC(MACH_SETARG, A1, A2, A3));
}
else if (strcmp(op, "invoke") == 0 || strcmp(op, "tail_invoke") == 0) {
EM(MACH_ABC(MACH_INVOKE, A1, A2, 0));
}
else if (strcmp(op, "goframe") == 0) {
EM(MACH_ABC(MACH_GOFRAME, A1, A2, A3));
}
else if (strcmp(op, "goinvoke") == 0) {
EM(MACH_ABC(MACH_GOINVOKE, A1, 0, 0));
}
/* Control flow */
else if (strcmp(op, "jump") == 0) {
const char *lbl = cJSON_GetArrayItem(it, 1)->valuestring;
int pc_now = s.code_count;
EM(MACH_sJ(MACH_JMP, 0));
ml_patch(&s, pc_now, lbl, 1, 0);
}
else if (strcmp(op, "jump_true") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_JMPTRUE, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "jump_false") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_JMPFALSE, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "jump_not_null") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_JMPNOTNULL, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
/* Return / error */
else if (strcmp(op, "return") == 0) {
EM(MACH_ABC(MACH_RETURN, A1, 0, 0));
}
else if (strcmp(op, "disrupt") == 0) {
EM(MACH_ABC(MACH_DISRUPT, 0, 0, 0));
}
/* Variable storage */
else if (strcmp(op, "set_var") == 0) {
const char *vname = cJSON_GetArrayItem(it, 1)->valuestring;
int val_reg = A2;
EM(MACH_ABx(MACH_SET_VAR, val_reg, ml_cpool_str(&s, vname)));
}
/* Misc */
else if (strcmp(op, "in") == 0) {
EM(MACH_ABC(MACH_IN, A1, A2, A3));
}
else if (strcmp(op, "regexp") == 0) {
int dest = A1;
const char *pat = cJSON_GetArrayItem(it, 2)->valuestring;
const char *flg = cJSON_GetArrayItem(it, 3)->valuestring;
EM(MACH_ABC(MACH_REGEXP, dest, ml_cpool_str(&s, pat), ml_cpool_str(&s, flg)));
}
else {
/* Unknown opcode — emit NOP */
EM(MACH_ABC(MACH_NOP, 0, 0, 0));
}
} }
/* Sentinel for flat_to_pc */
s.flat_to_pc[n] = s.code_count;
#undef A1
#undef A2
#undef A3
#undef A4
#undef EM
#undef ABC3
#undef AB2
/* Resolve pending jump patches */
for (int i = 0; i < s.patch_count; i++) {
int target = -1;
for (int j = 0; j < s.label_count; j++) {
if (strcmp(s.labels[j].name, s.patches[i].label) == 0) {
target = s.labels[j].pc;
break;
}
}
if (target < 0) {
fprintf(stderr, "mcode_lower: unknown label '%s'\n", s.patches[i].label);
continue;
}
int offset = target - (s.patches[i].pc + 1);
if (s.patches[i].is_sJ) {
int old_op = MACH_GET_OP(s.code[s.patches[i].pc]);
s.code[s.patches[i].pc] = MACH_sJ(old_op, offset);
} else {
int old_op = MACH_GET_OP(s.code[s.patches[i].pc]);
s.code[s.patches[i].pc] = MACH_AsBx(old_op, s.patches[i].reg_a, offset);
}
}
MachCode *mc = sys_malloc(sizeof(MachCode));
memset(mc, 0, sizeof(MachCode));
mc->arity = nr_args;
mc->nr_close_slots = nr_close;
mc->nr_slots = s.nr_slots;
mc->entry_point = 0;
mc->instr_count = s.code_count;
mc->instructions = s.code;
mc->cpool_count = s.cpool_count;
mc->cpool = s.cpool;
mc->line_table = s.lines;
if (name && name[0]) {
mc->name = sys_malloc(strlen(name) + 1);
memcpy(mc->name, name, strlen(name) + 1);
}
if (fname) {
mc->filename = sys_malloc(strlen(fname) + 1);
memcpy(mc->filename, fname, strlen(fname) + 1);
}
mc->disruption_pc = 0;
if (dis_raw > 0 && dis_raw < s.flat_count)
mc->disruption_pc = s.flat_to_pc[dis_raw];
sys_free(s.flat_to_pc);
sys_free(s.labels);
sys_free(s.patches);
return mc;
}
/* Assign nested functions to each MachCode based on CLOSURE instructions.
all_funcs is the flat array from the mcode JSON; compiled[i] is the
compiled MachCode for all_funcs[i]. mc is a compiled MachCode (main or
one of the flat functions). Scans its instructions for MACH_CLOSURE Bx
and builds mc->functions with remapped local indices. */
static void mcode_assign_children(MachCode *mc, MachCode **compiled, int total) {
/* Count unique CLOSURE references */
int *refs = NULL;
int ref_count = 0, ref_cap = 0;
for (uint32_t i = 0; i < mc->instr_count; i++) {
if (MACH_GET_OP(mc->instructions[i]) == MACH_CLOSURE) {
int bx = MACH_GET_Bx(mc->instructions[i]);
/* Check if already in refs */
int found = 0;
for (int j = 0; j < ref_count; j++) {
if (refs[j] == bx) { found = 1; break; }
}
if (!found) {
if (ref_count >= ref_cap) {
ref_cap = ref_cap ? ref_cap * 2 : 8;
refs = sys_realloc(refs, ref_cap * sizeof(int));
}
refs[ref_count++] = bx;
}
}
}
if (ref_count == 0) { sys_free(refs); return; }
/* Build local functions array (preserve original order by flat index) */
/* Sort refs by value to maintain consistent ordering */
for (int i = 0; i < ref_count - 1; i++)
for (int j = i + 1; j < ref_count; j++)
if (refs[i] > refs[j]) { int t = refs[i]; refs[i] = refs[j]; refs[j] = t; }
mc->func_count = ref_count;
mc->functions = sys_malloc(ref_count * sizeof(MachCode *));
for (int i = 0; i < ref_count; i++)
mc->functions[i] = (refs[i] < total) ? compiled[refs[i]] : NULL;
/* Remap CLOSURE Bx: flat index → local index */
for (uint32_t i = 0; i < mc->instr_count; i++) {
if (MACH_GET_OP(mc->instructions[i]) == MACH_CLOSURE) {
int a = MACH_GET_A(mc->instructions[i]);
int bx = MACH_GET_Bx(mc->instructions[i]);
for (int j = 0; j < ref_count; j++) {
if (refs[j] == bx) {
mc->instructions[i] = MACH_ABx(MACH_CLOSURE, a, j);
break;
}
}
}
}
sys_free(refs);
}
/* Compile mcode JSON IR to MachCode binary.
mcode_json has: functions[], main{}, filename, name */
MachCode *mach_compile_mcode(cJSON *mcode_json) {
const char *filename = cJSON_GetStringValue(
cJSON_GetObjectItemCaseSensitive(mcode_json, "filename"));
cJSON *funcs_arr = cJSON_GetObjectItemCaseSensitive(mcode_json, "functions");
int func_count = funcs_arr ? cJSON_GetArraySize(funcs_arr) : 0;
cJSON *main_obj = cJSON_GetObjectItemCaseSensitive(mcode_json, "main");
/* Build parent_of[]: for each function, which function index is its parent.
parent_of[i] = parent index, or func_count for main, or -1 if unknown.
Scan each function (and main) for "function" instructions. */
int *parent_of = sys_malloc(func_count * sizeof(int));
for (int i = 0; i < func_count; i++) parent_of[i] = -1;
/* Scan main's instructions */
{
cJSON *main_instrs = cJSON_GetObjectItemCaseSensitive(main_obj, "instructions");
cJSON *it = main_instrs ? main_instrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = it->child->valuestring;
if (!strcmp(op, "function")) {
int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = func_count; /* main */
}
}
}
/* Scan each function's instructions */
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = it->child->valuestring;
if (!strcmp(op, "function")) {
int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = fi;
}
}
} }
/* Build per-function capture sets: for each function F, which of its slots
are captured by descendant functions via get/put. Captured slots must
have extended live ranges during register compression. */
int **cap_slots = sys_malloc((func_count + 1) * sizeof(int *));
int *cap_counts = sys_malloc((func_count + 1) * sizeof(int));
memset(cap_slots, 0, (func_count + 1) * sizeof(int *));
memset(cap_counts, 0, (func_count + 1) * sizeof(int));
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue;
int slot = (int)it->child->next->next->valuedouble;
int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain to find the ancestor whose slot is referenced */
int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++)
ancestor = parent_of[ancestor];
if (ancestor < 0) continue;
/* Add slot to ancestor's capture list (deduplicate) */
int found = 0;
for (int k = 0; k < cap_counts[ancestor]; k++)
if (cap_slots[ancestor][k] == slot) { found = 1; break; }
if (!found) {
cap_slots[ancestor] = sys_realloc(cap_slots[ancestor],
(cap_counts[ancestor] + 1) * sizeof(int));
cap_slots[ancestor][cap_counts[ancestor]++] = slot;
}
}
} }
/* Compress registers for functions that exceed 8-bit slot limits.
Save remap tables so we can fix get/put parent_slot references. */
int **remaps = sys_malloc((func_count + 1) * sizeof(int *));
int *remap_sizes = sys_malloc((func_count + 1) * sizeof(int));
memset(remaps, 0, (func_count + 1) * sizeof(int *));
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int i = 0; fobj; i++, fobj = fobj->next)
remaps[i] = mcode_compress_regs(fobj,
&remap_sizes[i], cap_slots[i], cap_counts[i]);
}
/* main is stored at index func_count in our arrays */
remaps[func_count] = mcode_compress_regs(main_obj,
&remap_sizes[func_count], cap_slots[func_count], cap_counts[func_count]);
/* Free capture lists */
for (int i = 0; i <= func_count; i++)
if (cap_slots[i]) sys_free(cap_slots[i]);
sys_free(cap_slots);
sys_free(cap_counts);
/* Fix up get/put parent_slot references using ancestor remap tables */
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue;
int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain 'level' times to find ancestor */
int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++) {
ancestor = parent_of[ancestor];
}
if (ancestor < 0) continue; /* unknown parent — leave as is */
int *anc_remap = remaps[ancestor];
if (!anc_remap) continue; /* ancestor wasn't compressed */
cJSON *slot_item = it->child->next->next;
int old_slot = (int)slot_item->valuedouble;
if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) {
int new_slot = anc_remap[old_slot];
cJSON_SetNumberValue(slot_item, new_slot);
}
}
} }
/* Free remap tables */
for (int i = 0; i <= func_count; i++)
if (remaps[i]) sys_free(remaps[i]);
sys_free(remaps);
sys_free(remap_sizes);
sys_free(parent_of);
/* Compile all flat functions */
MachCode **compiled = NULL;
if (func_count > 0) {
compiled = sys_malloc(func_count * sizeof(MachCode *));
memset(compiled, 0, func_count * sizeof(MachCode *));
{ cJSON *fobj = funcs_arr->child;
for (int i = 0; fobj; i++, fobj = fobj->next)
compiled[i] = mcode_lower_func(fobj, filename);
}
}
/* Compile main */
MachCode *main_code = mcode_lower_func(main_obj, filename);
/* Assign nested functions to each compiled unit */
for (int i = 0; i < func_count; i++)
mcode_assign_children(compiled[i], compiled, func_count);
mcode_assign_children(main_code, compiled, func_count);
sys_free(compiled);
return main_code;
}
/* ============================================================
MACH Public API
============================================================ */
/* Print a single constant pool value for dump output */
static void dump_cpool_value(JSContext *ctx, JSValue val) {
uint32_t tag = JS_VALUE_GET_TAG(val);
if (JS_IsPtr(val)) {
void *ptr = JS_VALUE_GET_PTR(val);
objhdr_t hdr = *(objhdr_t *)ptr;
uint8_t mist_type = objhdr_type(hdr);
if (mist_type == OBJ_TEXT) {
const char *str = JS_ToCString(ctx, val);
if (str) {
printf("\"%s\"", str);
JS_FreeCString(ctx, str);
} else {
printf("<string>");
}
return;
}
printf("<ptr type=%d>", mist_type);
return;
}
switch (tag) {
case JS_TAG_INT:
printf("%d", JS_VALUE_GET_INT(val));
break;
case JS_TAG_BOOL:
printf("%s", JS_VALUE_GET_BOOL(val) ? "true" : "false");
break;
case JS_TAG_NULL:
printf("null");
break;
case JS_TAG_SHORT_FLOAT:
printf("%g", JS_VALUE_GET_FLOAT64(val));
break;
case JS_TAG_STRING_IMM: {
const char *str = JS_ToCString(ctx, val);
if (str) {
printf("\"%s\"", str);
JS_FreeCString(ctx, str);
} else {
printf("<imm string>");
}
break;
}
default:
printf("<tag=%d>", tag);
break;
}
}
/* (labels removed in new format) */
/* Internal helper to dump JSCodeRegister (32-bit instruction format) */
static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent) {
char pad[64];
int pad_len = indent * 2;
if (pad_len > 60) pad_len = 60;
memset(pad, ' ', pad_len);
pad[pad_len] = '\0';
/* Function header */
const char *name = "<anonymous>";
if (!JS_IsNull(code->name)) {
const char *n = JS_ToCString(ctx, code->name);
if (n) name = n;
}
printf("%sFunction: %s\n", pad, name);
printf("%s Arity: %d, Slots: %d, Close: %d\n", pad,
code->arity, code->nr_slots, code->nr_close_slots);
if (!JS_IsNull(code->name)) {
JS_FreeCString(ctx, name);
}
if (code->disruption_pc > 0)
printf("%s Disruption handler at: %d\n", pad, code->disruption_pc);
/* Constant pool */
if (code->cpool_count > 0) {
printf("%s\n%sConstant Pool (%d entries):\n", pad, pad, code->cpool_count);
for (uint32_t i = 0; i < code->cpool_count; i++) {
printf("%s [%d]: ", pad, i);
dump_cpool_value(ctx, code->cpool[i]);
printf("\n");
}
}
/* Instructions */
printf("%s\n%sInstructions (%d):\n", pad, pad, code->instr_count);
for (uint32_t i = 0; i < code->instr_count; i++) {
MachInstr32 instr = code->instructions[i];
int op = MACH_GET_OP(instr);
int a = MACH_GET_A(instr);
int b = MACH_GET_B(instr);
int c = MACH_GET_C(instr);
const char *op_name = (op < MACH_OP_COUNT) ? mach_opcode_names[op] : "???";
if (!op_name) op_name = "???";
printf("%s %3d: %-14s ", pad, i, op_name);
switch (op) {
/* No operands */
case MACH_NOP:
case MACH_RETNIL:
break;
/* A only */
case MACH_LOADNULL:
case MACH_LOADTRUE:
case MACH_LOADFALSE:
printf("r%d", a);
break;
/* ABx: load constant */
case MACH_LOADK: {
int bx = MACH_GET_Bx(instr);
printf("r%d, #%d", a, bx);
if (bx >= 0 && (uint32_t)bx < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[bx]);
}
break;
}
/* AsBx: load small int */
case MACH_LOADI:
printf("r%d, %d", a, MACH_GET_sBx(instr));
break;
/* A, B: move, unary ops */
case MACH_MOVE:
case MACH_NEG:
case MACH_INC:
case MACH_DEC:
case MACH_LNOT:
case MACH_BNOT:
printf("r%d, r%d", a, b);
break;
/* A, B, C: arithmetic, comparison, bitwise */
case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV:
case MACH_MOD: case MACH_POW:
case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE:
case MACH_GT: case MACH_GE:
case MACH_BAND: case MACH_BOR: case MACH_BXOR:
case MACH_SHL: case MACH_SHR: case MACH_USHR:
printf("r%d, r%d, r%d", a, b, c);
break;
case MACH_EQ_TOL: case MACH_NEQ_TOL:
printf("r%d, r%d, %d", a, b, c);
break;
/* Property access */
case MACH_GETFIELD:
printf("r%d, r%d, #%d", a, b, c);
if ((uint32_t)c < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[c]);
}
break;
case MACH_SETFIELD:
printf("r%d, #%d, r%d", a, b, c);
if ((uint32_t)b < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[b]);
}
break;
case MACH_GETINDEX:
case MACH_SETINDEX:
printf("r%d, r%d, r%d", a, b, c);
break;
/* ABx: name/intrinsic/env access */
case MACH_GETNAME:
case MACH_GETINTRINSIC:
case MACH_GETENV: {
int bx = MACH_GET_Bx(instr);
printf("r%d, #%d", a, bx);
if ((uint32_t)bx < code->cpool_count) {
printf(" ; ");
dump_cpool_value(ctx, code->cpool[bx]);
}
break;
}
/* Closure access */
case MACH_GETUP:
case MACH_SETUP:
printf("r%d, depth=%d, slot=%d", a, b, c);
break;
/* isJ: unconditional jump */
case MACH_JMP: {
int offset = MACH_GET_sJ(instr);
printf("%+d", offset);
printf(" ; -> %d", (int)i + 1 + offset);
break;
}
/* iAsBx: conditional jumps */
case MACH_JMPTRUE:
case MACH_JMPFALSE:
case MACH_JMPNULL: {
int offset = MACH_GET_sBx(instr);
printf("r%d, %+d", a, offset);
printf(" ; -> %d", (int)i + 1 + offset);
break;
}
/* Return / throw */
case MACH_RETURN:
case MACH_THROW:
printf("r%d", a);
break;
/* Object/array creation */
case MACH_NEWOBJECT:
printf("r%d", a);
break;
case MACH_NEWARRAY:
printf("r%d, %d", a, b);
break;
/* Push/Pop */
case MACH_PUSH:
printf("r%d, r%d", a, b);
break;
case MACH_POP:
printf("r%d, r%d", a, b);
break;
/* Closure */
case MACH_CLOSURE: {
int bx = MACH_GET_Bx(instr);
printf("r%d, func#%d", a, bx);
break;
}
default:
printf("0x%08x", instr);
break;
}
printf("\n");
}
/* Nested functions */
if (code->func_count > 0) {
printf("%s\n%sNested Functions (%d):\n", pad, pad, code->func_count);
for (uint32_t i = 0; i < code->func_count; i++) {
printf("%s [%d]:\n", pad, i);
if (code->functions[i]) {
dump_register_code(ctx, code->functions[i], indent + 2);
} else {
printf("%s <null>\n", pad);
}
}
}
}
JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env) {
MachCode *mc = JS_DeserializeMachCode(data, size);
if (!mc)
return JS_ThrowSyntaxError(ctx, "failed to deserialize MACH bytecode");
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
JS_FreeMachCode(mc);
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL);
JS_PopGCRef(ctx, &env_ref);
return result;
}
JSValue JS_RunMachMcode(JSContext *ctx, const char *json_str, size_t len, JSValue env) {
(void)len;
cJSON *mcode = cJSON_Parse(json_str);
if (!mcode)
return JS_ThrowSyntaxError(ctx, "failed to parse mcode JSON");
MachCode *mc = mach_compile_mcode(mcode);
cJSON_Delete(mcode);
if (!mc)
return JS_ThrowInternalError(ctx, "mcode compilation failed");
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
JS_FreeMachCode(mc);
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL);
JS_PopGCRef(ctx, &env_ref);
return result;
}
void JS_DumpMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env) {
MachCode *mc = JS_DeserializeMachCode(data, size);
if (!mc) {
printf("Failed to deserialize MACH bytecode\n");
return;
}
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
JS_FreeMachCode(mc);
dump_register_code(ctx, code, 0);
JS_PopGCRef(ctx, &env_ref);
}