Merge branch 'bytecode_cleanup' into mach
This commit is contained in:
@@ -275,6 +275,7 @@ static void print_usage(const char *prog)
|
||||
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
|
||||
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
|
||||
printf(" --emit-qbe Emit QBE IL (for native compilation)\n");
|
||||
printf(" --dump-mach Dump MACH bytecode disassembly\n");
|
||||
printf(" --test [heap_size] Run C test suite\n");
|
||||
printf(" -h, --help Show this help message\n");
|
||||
printf("\nEnvironment:\n");
|
||||
@@ -307,6 +308,7 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
/* Default: run script through bootstrap pipeline */
|
||||
int emit_qbe = 0;
|
||||
int dump_mach = 0;
|
||||
int arg_start = 1;
|
||||
const char *shop_override = NULL;
|
||||
const char *core_override = NULL;
|
||||
@@ -319,6 +321,9 @@ int cell_init(int argc, char **argv)
|
||||
} else if (strcmp(argv[arg_start], "--emit-qbe") == 0) {
|
||||
emit_qbe = 1;
|
||||
arg_start++;
|
||||
} else if (strcmp(argv[arg_start], "--dump-mach") == 0) {
|
||||
dump_mach = 1;
|
||||
arg_start++;
|
||||
} else if (strcmp(argv[arg_start], "--shop") == 0) {
|
||||
if (arg_start + 1 >= argc) {
|
||||
printf("ERROR: --shop requires a path argument\n");
|
||||
@@ -398,6 +403,7 @@ int cell_init(int argc, char **argv)
|
||||
JS_SetPropertyStr(ctx, hidden_env, "shop_path",
|
||||
shop_path ? JS_NewString(ctx, shop_path) : JS_NULL);
|
||||
JS_SetPropertyStr(ctx, hidden_env, "emit_qbe", JS_NewBool(ctx, emit_qbe));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "dump_mach", JS_NewBool(ctx, dump_mach));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx));
|
||||
|
||||
268
source/mach.c
268
source/mach.c
@@ -1142,174 +1142,6 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
break;
|
||||
}
|
||||
|
||||
case MACH_CALL: {
|
||||
/* Lua-style call: R(A)=func, B=nargs in R(A+1)..R(A+B), C=nresults */
|
||||
int base = a;
|
||||
int nargs = b;
|
||||
int nresults = c;
|
||||
JSValue func_val = frame->slots[base];
|
||||
|
||||
if (!JS_IsFunction(func_val)) {
|
||||
JS_ThrowTypeError(ctx, "not a function");
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
goto disrupt;
|
||||
}
|
||||
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
|
||||
if (fn->kind == JS_FUNC_KIND_C) {
|
||||
/* C function: copy args to C stack */
|
||||
JSValue args[nargs > 0 ? nargs : 1];
|
||||
for (int i = 0; i < nargs; i++)
|
||||
args[i] = frame->slots[base + 1 + i];
|
||||
ctx->reg_current_frame = frame_ref.val;
|
||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||
JSValue ret = js_call_c_function(ctx, func_val, JS_NULL, nargs, args);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
if (JS_IsException(ret)) { goto disrupt; }
|
||||
if (nresults > 0) frame->slots[base] = ret;
|
||||
} else if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
||||
/* Register function: allocate frame, copy args, switch */
|
||||
JSCodeRegister *fn_code = fn->u.reg.code;
|
||||
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
|
||||
if (!new_frame) {
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
goto disrupt;
|
||||
}
|
||||
/* Re-read pointers — GC may have moved them */
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
func_val = frame->slots[base];
|
||||
fn = JS_VALUE_GET_FUNCTION(func_val);
|
||||
new_frame->function = func_val;
|
||||
new_frame->slots[0] = JS_NULL; /* this */
|
||||
for (int i = 0; i < nargs && i < fn_code->arity; i++)
|
||||
new_frame->slots[1 + i] = frame->slots[base + 1 + i];
|
||||
|
||||
/* Save return info: pc in upper 16 bits, base reg or 0xFFFF (discard) in lower */
|
||||
int ret_slot = (nresults > 0) ? base : 0xFFFF;
|
||||
frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot);
|
||||
new_frame->caller = JS_MKPTR(frame);
|
||||
|
||||
frame = new_frame;
|
||||
frame_ref.val = JS_MKPTR(frame);
|
||||
code = fn_code;
|
||||
env = fn->u.reg.env_record;
|
||||
pc = code->entry_point;
|
||||
} else {
|
||||
/* Other function kinds (bytecode) — copy args to C stack */
|
||||
JSValue args[nargs > 0 ? nargs : 1];
|
||||
for (int i = 0; i < nargs; i++)
|
||||
args[i] = frame->slots[base + 1 + i];
|
||||
JSValue ret = JS_CallInternal(ctx, func_val, JS_NULL, nargs, args, 0);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(ret)) { goto disrupt; }
|
||||
if (nresults > 0) frame->slots[base] = ret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MACH_CALLMETHOD: {
|
||||
/* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key index
|
||||
Result stored in R(A). C=0xFF means key is in R(A+1).
|
||||
If obj is a function (proxy): call obj(key_str, [args...])
|
||||
Else (record): get property, call property(obj_as_this, args...) */
|
||||
int base = a;
|
||||
int nargs = b;
|
||||
JSGCRef key_ref;
|
||||
JS_PushGCRef(ctx, &key_ref);
|
||||
key_ref.val = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c];
|
||||
|
||||
if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val) &&
|
||||
JS_VALUE_GET_FUNCTION(frame->slots[base])->length == 2) {
|
||||
/* Proxy call (arity-2 functions only): obj(name, [args...]) */
|
||||
JSValue arr = JS_NewArray(ctx);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
|
||||
frame->slots[base + 1] = arr; /* protect from GC in temp slot */
|
||||
for (int i = 0; i < nargs; i++) {
|
||||
JS_SetPropertyNumber(ctx, frame->slots[base + 1], i, frame->slots[base + 2 + i]);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
}
|
||||
/* Call proxy with key and array from C stack */
|
||||
JSValue call_args[2] = { key_ref.val, frame->slots[base + 1] };
|
||||
ctx->reg_current_frame = frame_ref.val;
|
||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||
JSValue ret = JS_CallInternal(ctx, frame->slots[base], JS_NULL, 2, call_args, 0);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
|
||||
frame->slots[base] = ret;
|
||||
} else if (JS_IsFunction(frame->slots[base])) {
|
||||
/* Non-proxy function with non-text key: disrupt */
|
||||
JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function");
|
||||
JS_PopGCRef(ctx, &key_ref);
|
||||
goto disrupt;
|
||||
} else {
|
||||
/* Record method call: get property, call with this=obj */
|
||||
if (JS_IsNull(frame->slots[base])) {
|
||||
JS_ThrowTypeError(ctx, "cannot read properties of null");
|
||||
JS_PopGCRef(ctx, &key_ref);
|
||||
goto disrupt;
|
||||
}
|
||||
JSValue method = JS_GetProperty(ctx, frame->slots[base], key_ref.val);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(method)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
|
||||
if (!JS_IsFunction(method)) {
|
||||
JS_ThrowTypeError(ctx, "not a function");
|
||||
JS_PopGCRef(ctx, &key_ref);
|
||||
goto disrupt;
|
||||
}
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(method);
|
||||
if (fn->kind == JS_FUNC_KIND_C) {
|
||||
JSValue args[nargs > 0 ? nargs : 1];
|
||||
for (int i = 0; i < nargs; i++)
|
||||
args[i] = frame->slots[base + 2 + i];
|
||||
ctx->reg_current_frame = frame_ref.val;
|
||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||
JSValue ret = js_call_c_function(ctx, method, frame->slots[base], nargs, args);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
|
||||
frame->slots[base] = ret;
|
||||
} else if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
||||
JSCodeRegister *fn_code = fn->u.reg.code;
|
||||
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
|
||||
if (!new_frame) {
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
JS_PopGCRef(ctx, &key_ref);
|
||||
goto disrupt;
|
||||
}
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
method = JS_GetProperty(ctx, frame->slots[base], key_ref.val);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
fn = JS_VALUE_GET_FUNCTION(method);
|
||||
new_frame->function = method;
|
||||
new_frame->slots[0] = frame->slots[base]; /* this */
|
||||
for (int i = 0; i < nargs && i < fn_code->arity; i++)
|
||||
new_frame->slots[1 + i] = frame->slots[base + 2 + i];
|
||||
int ret_slot = base;
|
||||
frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot);
|
||||
new_frame->caller = JS_MKPTR(frame);
|
||||
frame = new_frame;
|
||||
frame_ref.val = JS_MKPTR(frame);
|
||||
code = fn_code;
|
||||
env = fn->u.reg.env_record;
|
||||
pc = code->entry_point;
|
||||
} else {
|
||||
/* Bytecode or other function */
|
||||
JSValue args[nargs > 0 ? nargs : 1];
|
||||
for (int i = 0; i < nargs; i++)
|
||||
args[i] = frame->slots[base + 2 + i];
|
||||
JSValue ret = JS_CallInternal(ctx, method, frame->slots[base], nargs, args, 0);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
|
||||
frame->slots[base] = ret;
|
||||
}
|
||||
}
|
||||
JS_PopGCRef(ctx, &key_ref);
|
||||
break;
|
||||
}
|
||||
|
||||
case MACH_RETURN:
|
||||
result = frame->slots[a];
|
||||
if (JS_IsNull(frame->caller)) goto done;
|
||||
@@ -1676,6 +1508,16 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
frame->slots[a] = res;
|
||||
break;
|
||||
}
|
||||
case MACH_IS_PROXY: {
|
||||
JSValue v = frame->slots[b];
|
||||
int is_proxy = 0;
|
||||
if (JS_IsFunction(v)) {
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
|
||||
is_proxy = (fn->length == 2);
|
||||
}
|
||||
frame->slots[a] = JS_NewBool(ctx, is_proxy);
|
||||
break;
|
||||
}
|
||||
case MACH_TYPEOF: {
|
||||
JSValue val = frame->slots[b];
|
||||
const char *tname = "unknown";
|
||||
@@ -1699,15 +1541,19 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
break;
|
||||
}
|
||||
case MACH_AND: {
|
||||
int ba = JS_ToBool(ctx, frame->slots[b]);
|
||||
int bb = JS_ToBool(ctx, frame->slots[c]);
|
||||
frame->slots[a] = JS_NewBool(ctx, ba && bb);
|
||||
JSValue left = frame->slots[b];
|
||||
if (!JS_ToBool(ctx, left))
|
||||
frame->slots[a] = left;
|
||||
else
|
||||
frame->slots[a] = frame->slots[c];
|
||||
break;
|
||||
}
|
||||
case MACH_OR: {
|
||||
int ba = JS_ToBool(ctx, frame->slots[b]);
|
||||
int bb = JS_ToBool(ctx, frame->slots[c]);
|
||||
frame->slots[a] = JS_NewBool(ctx, ba || bb);
|
||||
JSValue left = frame->slots[b];
|
||||
if (JS_ToBool(ctx, left))
|
||||
frame->slots[a] = left;
|
||||
else
|
||||
frame->slots[a] = frame->slots[c];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1735,12 +1581,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
JSValue obj = frame->slots[b];
|
||||
JSValue key = code->cpool[c];
|
||||
if (JS_IsFunction(obj)) {
|
||||
JSFunction *fn_chk = JS_VALUE_GET_FUNCTION(obj);
|
||||
if (fn_chk->length != 2) {
|
||||
JS_ThrowTypeError(ctx, "cannot read property of non-proxy function");
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
goto disrupt;
|
||||
}
|
||||
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);
|
||||
@@ -1808,6 +1651,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
} else if (JS_IsArray(obj)) {
|
||||
JS_ThrowTypeError(ctx, "array index must be a number");
|
||||
ret = -1;
|
||||
} else if (JS_IsBool(key) || JS_IsNull(key) || JS_IsArray(key) || JS_IsFunction(key)) {
|
||||
JS_ThrowTypeError(ctx, "object key must be text");
|
||||
ret = -1;
|
||||
} else {
|
||||
ret = JS_SetProperty(ctx, obj, key, val);
|
||||
}
|
||||
@@ -2569,13 +2415,14 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
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_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); }
|
||||
else if (strcmp(op, "typeof") == 0) { AB2(MACH_TYPEOF); }
|
||||
/* Logical */
|
||||
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
|
||||
@@ -2594,7 +2441,15 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
int dest = A1, obj = A2;
|
||||
cJSON *key_item = cJSON_GetArrayItem(it, 3);
|
||||
if (cJSON_IsString(key_item)) {
|
||||
EM(MACH_ABC(MACH_LOAD_FIELD, dest, obj, ml_cpool_str(&s, key_item->valuestring)));
|
||||
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;
|
||||
@@ -2605,7 +2460,15 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
int obj = A1, val = A2;
|
||||
cJSON *key_item = cJSON_GetArrayItem(it, 3);
|
||||
if (cJSON_IsString(key_item)) {
|
||||
EM(MACH_ABC(MACH_STORE_FIELD, obj, ml_cpool_str(&s, key_item->valuestring), val));
|
||||
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;
|
||||
@@ -2624,7 +2487,21 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
}
|
||||
/* Delete */
|
||||
else if (strcmp(op, "delete") == 0) {
|
||||
ABC3(MACH_DELETEINDEX);
|
||||
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) {
|
||||
@@ -3222,11 +3099,6 @@ static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Call */
|
||||
case MACH_CALL:
|
||||
printf("r%d, %d, %d", a, b, c);
|
||||
break;
|
||||
|
||||
/* Return / throw */
|
||||
case MACH_RETURN:
|
||||
case MACH_THROW:
|
||||
@@ -3294,3 +3166,21 @@ JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -469,7 +469,7 @@ typedef enum MachOpcode {
|
||||
MACH_JMPNULL, /* if R(A)==null: pc += sBx */
|
||||
|
||||
/* Function calls — Lua-style consecutive registers (legacy .mach) */
|
||||
MACH_CALL, /* Call R(A) with B args R(A+1)..R(A+B), C=0 discard, C=1 keep result in R(A) */
|
||||
MACH_CALL, /* (removed — placeholder to preserve opcode numbering) */
|
||||
MACH_RETURN, /* Return R(A) */
|
||||
MACH_RETNIL, /* Return null */
|
||||
|
||||
@@ -488,7 +488,7 @@ typedef enum MachOpcode {
|
||||
MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */
|
||||
MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */
|
||||
|
||||
MACH_CALLMETHOD, /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key */
|
||||
MACH_CALLMETHOD, /* (removed — placeholder to preserve opcode numbering) */
|
||||
|
||||
MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */
|
||||
MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */
|
||||
@@ -602,6 +602,7 @@ typedef enum MachOpcode {
|
||||
MACH_IS_RECORD, /* R(A) = is_object(R(B)) */
|
||||
MACH_IS_STONE, /* R(A) = is_stone(R(B)) */
|
||||
MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */
|
||||
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
|
||||
|
||||
MACH_OP_COUNT
|
||||
} MachOpcode;
|
||||
@@ -737,6 +738,7 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
|
||||
[MACH_IS_RECORD] = "is_record",
|
||||
[MACH_IS_STONE] = "is_stone",
|
||||
[MACH_LENGTH] = "length",
|
||||
[MACH_IS_PROXY] = "is_proxy",
|
||||
};
|
||||
|
||||
/* Compiled register-based code (off-heap, never GC'd).
|
||||
|
||||
@@ -995,6 +995,9 @@ struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env
|
||||
/* Deserialize and execute pre-compiled MACH binary bytecode. */
|
||||
JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env);
|
||||
|
||||
/* Dump disassembly of pre-compiled MACH binary bytecode. */
|
||||
void JS_DumpMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env);
|
||||
|
||||
/* Compile mcode JSON IR to MachCode binary. */
|
||||
MachCode *mach_compile_mcode(struct cJSON *mcode_json);
|
||||
|
||||
|
||||
@@ -9788,6 +9788,57 @@ static JSValue js_mach_eval_mcode (JSContext *ctx, JSValue this_val, int argc, J
|
||||
return result;
|
||||
}
|
||||
|
||||
/* mach_dump_mcode(name, mcode_json, env?) - compile mcode IR and dump bytecode disassembly */
|
||||
static JSValue js_mach_dump_mcode (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_dump_mcode requires (name, mcode_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 *mcode = cJSON_Parse (json_str);
|
||||
JS_FreeCString (ctx, json_str);
|
||||
|
||||
if (!mcode) {
|
||||
JS_FreeCString (ctx, name);
|
||||
return JS_ThrowSyntaxError (ctx, "mach_dump_mcode: failed to parse mcode JSON");
|
||||
}
|
||||
|
||||
if (!cJSON_GetObjectItemCaseSensitive (mcode, "filename"))
|
||||
cJSON_AddStringToObject (mcode, "filename", name);
|
||||
|
||||
MachCode *mc = mach_compile_mcode (mcode);
|
||||
cJSON_Delete (mcode);
|
||||
|
||||
if (!mc) {
|
||||
JS_FreeCString (ctx, name);
|
||||
return JS_ThrowInternalError (ctx, "mach_dump_mcode: compilation failed");
|
||||
}
|
||||
|
||||
JSValue env = (argc >= 3 && JS_IsGCObject (argv[2])) ? argv[2] : JS_NULL;
|
||||
|
||||
JSGCRef env_ref;
|
||||
JS_PushGCRef (ctx, &env_ref);
|
||||
env_ref.val = env;
|
||||
|
||||
/* Serialize to binary then dump */
|
||||
size_t bin_size;
|
||||
uint8_t *bin = JS_SerializeMachCode (mc, &bin_size);
|
||||
JS_FreeMachCode (mc);
|
||||
JS_DumpMachBin (ctx, bin, bin_size, env_ref.val);
|
||||
sys_free (bin);
|
||||
|
||||
JS_PopGCRef (ctx, &env_ref);
|
||||
JS_FreeCString (ctx, name);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
/* mach_compile_mcode_bin(name, mcode_json) - compile mcode IR to serialized binary blob */
|
||||
static JSValue js_mach_compile_mcode_bin (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
|
||||
@@ -10920,6 +10971,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
|
||||
/* Core functions - using GC-safe helper */
|
||||
js_set_global_cfunc(ctx, "mach_load", js_mach_load, 2);
|
||||
js_set_global_cfunc(ctx, "mach_eval_mcode", js_mach_eval_mcode, 3);
|
||||
js_set_global_cfunc(ctx, "mach_dump_mcode", js_mach_dump_mcode, 3);
|
||||
js_set_global_cfunc(ctx, "mach_compile_mcode_bin", js_mach_compile_mcode_bin, 2);
|
||||
js_set_global_cfunc(ctx, "stone", js_cell_stone, 1);
|
||||
js_set_global_cfunc(ctx, "length", js_cell_length, 1);
|
||||
|
||||
Reference in New Issue
Block a user