bytecode serialization
This commit is contained in:
245
source/mach.c
245
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
114
source/runtime.c
114
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);
|
||||
|
||||
Reference in New Issue
Block a user