Merge branch 'mach' into mqbe

This commit is contained in:
2026-02-09 22:22:15 -06:00
3 changed files with 365 additions and 0 deletions

View File

@@ -1834,6 +1834,250 @@ void JS_FreeMachCode(MachCode *mc) {
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 */
@@ -1848,6 +2092,7 @@ JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
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;

View File

@@ -1082,6 +1082,12 @@ MachCode *JS_CompileMach(const char *ast_json);
/* Free a compiled MachCode tree. */
void JS_FreeMachCode(MachCode *mc);
/* Serialize MachCode to binary. Returns sys_malloc'd buffer; caller frees. */
uint8_t *JS_SerializeMachCode(MachCode *mc, size_t *out_size);
/* Deserialize binary back to MachCode. Returns NULL on error. */
MachCode *JS_DeserializeMachCode(const uint8_t *data, size_t size);
/* Load compiled MachCode into a JSContext, materializing JSValues. */
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);

View File

@@ -10424,6 +10424,117 @@ static JSValue js_mach_eval_ast (JSContext *ctx, JSValue this_val, int argc, JSV
return result;
}
/* mach_compile(name, source) - parse and compile to binary blob */
static JSValue js_mach_compile (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
return JS_ThrowTypeError (ctx, "mach_compile requires (name, source) text arguments");
const char *name = JS_ToCString (ctx, argv[0]);
if (!name) return JS_EXCEPTION;
const char *source = JS_ToCString (ctx, argv[1]);
if (!source) {
JS_FreeCString (ctx, name);
return JS_EXCEPTION;
}
cJSON *ast = JS_ASTTree (source, strlen (source), name);
JS_FreeCString (ctx, source);
if (!ast) {
JS_FreeCString (ctx, name);
return JS_ThrowSyntaxError (ctx, "mach_compile: failed to parse source");
}
MachCode *mc = JS_CompileMachTree (ast);
cJSON_Delete (ast);
JS_FreeCString (ctx, name);
if (!mc)
return JS_ThrowSyntaxError (ctx, "mach_compile: failed to compile AST");
size_t blob_size;
uint8_t *buf = JS_SerializeMachCode (mc, &blob_size);
JS_FreeMachCode (mc);
if (!buf)
return JS_ThrowInternalError (ctx, "mach_compile: serialization failed");
JSValue result = js_new_blob_stoned_copy (ctx, buf, blob_size);
sys_free (buf);
return result;
}
/* mach_compile_ast(name, ast_json) - compile pre-parsed AST to binary blob */
static JSValue js_mach_compile_ast (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
return JS_ThrowTypeError (ctx, "mach_compile_ast requires (name, ast_json) text arguments");
const char *name = JS_ToCString (ctx, argv[0]);
if (!name) return JS_EXCEPTION;
const char *json_str = JS_ToCString (ctx, argv[1]);
if (!json_str) {
JS_FreeCString (ctx, name);
return JS_EXCEPTION;
}
cJSON *ast = cJSON_Parse (json_str);
JS_FreeCString (ctx, json_str);
if (!ast) {
JS_FreeCString (ctx, name);
return JS_ThrowSyntaxError (ctx, "mach_compile_ast: failed to parse AST JSON");
}
cJSON_DeleteItemFromObjectCaseSensitive (ast, "filename");
cJSON_AddStringToObject (ast, "filename", name);
MachCode *mc = JS_CompileMachTree (ast);
cJSON_Delete (ast);
JS_FreeCString (ctx, name);
if (!mc)
return JS_ThrowSyntaxError (ctx, "mach_compile_ast: failed to compile AST");
size_t blob_size;
uint8_t *buf = JS_SerializeMachCode (mc, &blob_size);
JS_FreeMachCode (mc);
if (!buf)
return JS_ThrowInternalError (ctx, "mach_compile_ast: serialization failed");
JSValue result = js_new_blob_stoned_copy (ctx, buf, blob_size);
sys_free (buf);
return result;
}
/* mach_load(blob, env?) - deserialize and execute binary blob */
static JSValue js_mach_load (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1)
return JS_ThrowTypeError (ctx, "mach_load requires a blob argument");
size_t data_size;
void *data = js_get_blob_data (ctx, &data_size, argv[0]);
if (!data) return JS_EXCEPTION;
MachCode *mc = JS_DeserializeMachCode ((const uint8_t *)data, data_size);
if (!mc)
return JS_ThrowSyntaxError (ctx, "mach_load: failed to deserialize bytecode");
JSValue env = (argc >= 2 && JS_IsObject (argv[1])) ? argv[1] : JS_NULL;
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;
}
/* mcode_run(name, mcode_json, env?) - run pre-compiled mcode JSON */
static JSValue js_mcode_run (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
@@ -11546,6 +11657,9 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
/* Core functions - using GC-safe helper */
js_set_global_cfunc(ctx, "mach_eval_ast", js_mach_eval_ast, 3);
js_set_global_cfunc(ctx, "mcode_run", js_mcode_run, 3);
js_set_global_cfunc(ctx, "mach_compile", js_mach_compile, 2);
js_set_global_cfunc(ctx, "mach_compile_ast", js_mach_compile_ast, 2);
js_set_global_cfunc(ctx, "mach_load", js_mach_load, 2);
js_set_global_cfunc(ctx, "stone", js_cell_stone, 1);
js_set_global_cfunc(ctx, "length", js_cell_length, 1);
js_set_global_cfunc(ctx, "call", js_cell_call, 3);