Merge branch 'bytecode_cleanup' into mach

This commit is contained in:
2026-02-12 14:08:45 -06:00
18 changed files with 344 additions and 225 deletions

View File

@@ -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);
}