diff --git a/source/mach.c b/source/mach.c index cfaadfa9..93c35641 100644 --- a/source/mach.c +++ b/source/mach.c @@ -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; diff --git a/source/quickjs.h b/source/quickjs.h index d36f421a..6cd67f92 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -1092,6 +1092,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); diff --git a/source/runtime.c b/source/runtime.c index 0f6c0f88..36be5911 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -10481,6 +10481,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])) @@ -11605,6 +11716,9 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) { js_set_global_cfunc(ctx, "mach_eval", js_mach_eval, 3); 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);