mcode and mcode interpreter
This commit is contained in:
@@ -575,6 +575,83 @@ int cell_init(int argc, char **argv)
|
|||||||
return mcode_json ? 0 : 1;
|
return mcode_json ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check for --run-mcode flag to execute via MCODE interpreter */
|
||||||
|
if (argc >= 3 && strcmp(argv[1], "--run-mcode") == 0) {
|
||||||
|
const char *script_or_file = argv[2];
|
||||||
|
char *script = NULL;
|
||||||
|
char *allocated_script = NULL;
|
||||||
|
const char *filename = "<eval>";
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
|
||||||
|
FILE *f = fopen(script_or_file, "r");
|
||||||
|
if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; }
|
||||||
|
allocated_script = malloc(st.st_size + 1);
|
||||||
|
if (!allocated_script) { fclose(f); printf("Failed to allocate memory\n"); return 1; }
|
||||||
|
size_t read_size = fread(allocated_script, 1, st.st_size, f);
|
||||||
|
fclose(f);
|
||||||
|
allocated_script[read_size] = '\0';
|
||||||
|
script = allocated_script;
|
||||||
|
filename = script_or_file;
|
||||||
|
} else {
|
||||||
|
script = (char *)script_or_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSRuntime *rt = JS_NewRuntime();
|
||||||
|
if (!rt) { printf("Failed to create JS runtime\n"); free(allocated_script); return 1; }
|
||||||
|
/* Use a parser context (small heap) for AST + MCODE generation */
|
||||||
|
JSContext *parse_ctx = JS_NewContext(rt);
|
||||||
|
if (!parse_ctx) { printf("Failed to create JS context\n"); JS_FreeRuntime(rt); free(allocated_script); return 1; }
|
||||||
|
|
||||||
|
char *ast_json = JS_AST(parse_ctx, script, strlen(script), filename);
|
||||||
|
if (!ast_json) {
|
||||||
|
printf("Failed to parse AST\n");
|
||||||
|
JS_FreeContext(parse_ctx); JS_FreeRuntime(rt); free(allocated_script);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (print_json_errors(ast_json)) {
|
||||||
|
free(ast_json); JS_FreeContext(parse_ctx); JS_FreeRuntime(rt); free(allocated_script);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *mcode_json = JS_Mcode(parse_ctx, ast_json);
|
||||||
|
free(ast_json);
|
||||||
|
JS_FreeContext(parse_ctx);
|
||||||
|
|
||||||
|
if (!mcode_json) {
|
||||||
|
printf("Failed to generate MCODE\n");
|
||||||
|
JS_FreeRuntime(rt); free(allocated_script);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (print_json_errors(mcode_json)) {
|
||||||
|
free(mcode_json); JS_FreeRuntime(rt); free(allocated_script);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use a larger heap context for execution */
|
||||||
|
JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024);
|
||||||
|
if (!ctx) { printf("Failed to create execution context\n"); free(mcode_json); JS_FreeRuntime(rt); free(allocated_script); return 1; }
|
||||||
|
|
||||||
|
JSValue result = JS_CallMcode(ctx, mcode_json);
|
||||||
|
free(mcode_json);
|
||||||
|
|
||||||
|
if (JS_IsException(result)) {
|
||||||
|
JSValue exc = JS_GetException(ctx);
|
||||||
|
const char *str = JS_ToCString(ctx, exc);
|
||||||
|
if (str) { printf("Exception: %s\n", str); JS_FreeCString(ctx, str); }
|
||||||
|
} else if (!JS_IsNull(result)) {
|
||||||
|
const char *str = JS_ToCString(ctx, result);
|
||||||
|
if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); }
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeContext(ctx);
|
||||||
|
JS_FreeRuntime(rt);
|
||||||
|
free(allocated_script);
|
||||||
|
return JS_IsException(result) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Check for --mach flag to dump MACH bytecode */
|
/* Check for --mach flag to dump MACH bytecode */
|
||||||
if (argc >= 3 && strcmp(argv[1], "--mach") == 0) {
|
if (argc >= 3 && strcmp(argv[1], "--mach") == 0) {
|
||||||
const char *script_or_file = argv[2];
|
const char *script_or_file = argv[2];
|
||||||
|
|||||||
938
source/quickjs.c
938
source/quickjs.c
@@ -652,6 +652,25 @@ typedef struct JSCodeRegister {
|
|||||||
JSValue name; /* function name (stone text) */
|
JSValue name; /* function name (stone text) */
|
||||||
} JSCodeRegister;
|
} JSCodeRegister;
|
||||||
|
|
||||||
|
/* Pre-parsed MCODE for a single function (off-heap, never GC'd).
|
||||||
|
Created by jsmcode_parse from cJSON MCODE output.
|
||||||
|
Instructions remain as cJSON pointers for string-based dispatch. */
|
||||||
|
typedef struct JSMCode {
|
||||||
|
uint16_t nr_args;
|
||||||
|
uint16_t nr_slots;
|
||||||
|
/* Pre-flattened instruction array (cJSON array items → C array for O(1) access) */
|
||||||
|
cJSON **instrs;
|
||||||
|
uint32_t instr_count;
|
||||||
|
/* Label map: label string → instruction index */
|
||||||
|
struct { const char *name; uint32_t index; } *labels;
|
||||||
|
uint32_t label_count;
|
||||||
|
/* Nested function definitions (indexes into top-level functions array) */
|
||||||
|
struct JSMCode **functions;
|
||||||
|
uint32_t func_count;
|
||||||
|
/* Keep root cJSON alive (owns all the cJSON nodes instrs[] point into) */
|
||||||
|
cJSON *json_root;
|
||||||
|
} JSMCode;
|
||||||
|
|
||||||
/* Frame for closures - used by link-time relocation model where closures
|
/* Frame for closures - used by link-time relocation model where closures
|
||||||
reference outer frames via (depth, slot) addressing.
|
reference outer frames via (depth, slot) addressing.
|
||||||
Stores function as JSValue to survive GC movements. */
|
Stores function as JSValue to survive GC movements. */
|
||||||
@@ -1763,6 +1782,7 @@ typedef enum {
|
|||||||
JS_FUNC_KIND_BYTECODE,
|
JS_FUNC_KIND_BYTECODE,
|
||||||
JS_FUNC_KIND_C_DATA,
|
JS_FUNC_KIND_C_DATA,
|
||||||
JS_FUNC_KIND_REGISTER, /* register-based VM function */
|
JS_FUNC_KIND_REGISTER, /* register-based VM function */
|
||||||
|
JS_FUNC_KIND_MCODE, /* MCODE JSON interpreter */
|
||||||
} JSFunctionKind;
|
} JSFunctionKind;
|
||||||
|
|
||||||
typedef struct JSFunction {
|
typedef struct JSFunction {
|
||||||
@@ -1786,6 +1806,11 @@ typedef struct JSFunction {
|
|||||||
JSValue env_record; /* stone record, module environment */
|
JSValue env_record; /* stone record, module environment */
|
||||||
JSValue outer_frame; /* JSFrame JSValue, for closures */
|
JSValue outer_frame; /* JSFrame JSValue, for closures */
|
||||||
} reg;
|
} reg;
|
||||||
|
struct {
|
||||||
|
JSMCode *code; /* pre-parsed MCODE (off-heap) */
|
||||||
|
JSValue outer_frame; /* lexical parent frame for closures */
|
||||||
|
JSValue env_record; /* module env or JS_NULL */
|
||||||
|
} mcode;
|
||||||
} u;
|
} u;
|
||||||
} JSFunction;
|
} JSFunction;
|
||||||
|
|
||||||
@@ -2661,6 +2686,10 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8
|
|||||||
nested->name = gc_copy_value (ctx, nested->name, from_base, from_end, to_base, to_free, to_end);
|
nested->name = gc_copy_value (ctx, nested->name, from_base, from_end, to_base, to_free, to_end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (fn->kind == JS_FUNC_KIND_MCODE) {
|
||||||
|
/* MCODE function - scan outer_frame and env_record */
|
||||||
|
fn->u.mcode.outer_frame = gc_copy_value (ctx, fn->u.mcode.outer_frame, from_base, from_end, to_base, to_free, to_end);
|
||||||
|
fn->u.mcode.env_record = gc_copy_value (ctx, fn->u.mcode.env_record, from_base, from_end, to_base, to_free, to_end);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -33843,6 +33872,915 @@ done:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
MCODE JSON Interpreter
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* Parse a single MCODE function from cJSON into a JSMCode struct */
|
||||||
|
/* Parse a single function (no recursive function parsing) */
|
||||||
|
static JSMCode *jsmcode_parse_one(cJSON *func_def) {
|
||||||
|
JSMCode *code = js_mallocz_rt(sizeof(JSMCode));
|
||||||
|
if (!code) return NULL;
|
||||||
|
|
||||||
|
code->nr_args = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItem(func_def, "nr_args"));
|
||||||
|
code->nr_slots = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItem(func_def, "nr_slots"));
|
||||||
|
|
||||||
|
/* Build instruction array from cJSON linked list */
|
||||||
|
cJSON *instrs_arr = cJSON_GetObjectItem(func_def, "instructions");
|
||||||
|
int raw_count = cJSON_GetArraySize(instrs_arr);
|
||||||
|
code->instrs = js_mallocz_rt(raw_count * sizeof(cJSON *));
|
||||||
|
code->instr_count = 0;
|
||||||
|
|
||||||
|
/* First pass: count labels and build instruction array */
|
||||||
|
uint32_t label_cap = 32;
|
||||||
|
code->labels = js_mallocz_rt(label_cap * sizeof(*code->labels));
|
||||||
|
code->label_count = 0;
|
||||||
|
|
||||||
|
cJSON *item;
|
||||||
|
uint32_t idx = 0;
|
||||||
|
cJSON_ArrayForEach(item, instrs_arr) {
|
||||||
|
if (cJSON_IsString(item)) {
|
||||||
|
/* Label marker — record position, don't add to instruction array */
|
||||||
|
if (code->label_count >= label_cap) {
|
||||||
|
label_cap *= 2;
|
||||||
|
code->labels = js_realloc_rt(code->labels, label_cap * sizeof(*code->labels));
|
||||||
|
}
|
||||||
|
code->labels[code->label_count].name = item->valuestring;
|
||||||
|
code->labels[code->label_count].index = idx;
|
||||||
|
code->label_count++;
|
||||||
|
} else {
|
||||||
|
/* Instruction (array) */
|
||||||
|
code->instrs[idx++] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code->instr_count = idx;
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse MCODE: main + all functions from the global functions array.
|
||||||
|
All JSMCode structs share the same functions[] pointer. */
|
||||||
|
static JSMCode *jsmcode_parse(cJSON *func_def, cJSON *all_functions) {
|
||||||
|
/* Parse the global functions array first (flat, non-recursive) */
|
||||||
|
uint32_t func_count = all_functions ? cJSON_GetArraySize(all_functions) : 0;
|
||||||
|
JSMCode **parsed_funcs = NULL;
|
||||||
|
if (func_count > 0) {
|
||||||
|
parsed_funcs = js_mallocz_rt(func_count * sizeof(JSMCode *));
|
||||||
|
for (uint32_t i = 0; i < func_count; i++) {
|
||||||
|
cJSON *fn = cJSON_GetArrayItem(all_functions, i);
|
||||||
|
parsed_funcs[i] = jsmcode_parse_one(fn);
|
||||||
|
/* Each function shares the same functions array */
|
||||||
|
parsed_funcs[i]->func_count = func_count;
|
||||||
|
parsed_funcs[i]->functions = parsed_funcs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the main function */
|
||||||
|
JSMCode *code = jsmcode_parse_one(func_def);
|
||||||
|
code->func_count = func_count;
|
||||||
|
code->functions = parsed_funcs;
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free a top-level JSMCode and all its shared functions.
|
||||||
|
Only call this on the main code returned by jsmcode_parse. */
|
||||||
|
static void jsmcode_free(JSMCode *code) {
|
||||||
|
if (!code) return;
|
||||||
|
/* Free all parsed functions (they share the same functions array) */
|
||||||
|
if (code->functions) {
|
||||||
|
for (uint32_t i = 0; i < code->func_count; i++) {
|
||||||
|
if (code->functions[i]) {
|
||||||
|
/* Don't free functions[i]->functions — it's the shared pointer */
|
||||||
|
if (code->functions[i]->instrs) js_free_rt(code->functions[i]->instrs);
|
||||||
|
if (code->functions[i]->labels) js_free_rt(code->functions[i]->labels);
|
||||||
|
js_free_rt(code->functions[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js_free_rt(code->functions);
|
||||||
|
}
|
||||||
|
if (code->instrs) js_free_rt(code->instrs);
|
||||||
|
if (code->labels) js_free_rt(code->labels);
|
||||||
|
if (code->json_root) cJSON_Delete(code->json_root);
|
||||||
|
js_free_rt(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve label name → instruction index */
|
||||||
|
static uint32_t mcode_resolve_label(JSMCode *code, const char *name) {
|
||||||
|
for (uint32_t i = 0; i < code->label_count; i++) {
|
||||||
|
if (strcmp(code->labels[i].name, name) == 0)
|
||||||
|
return code->labels[i].index;
|
||||||
|
}
|
||||||
|
return code->instr_count; /* past end = implicit return */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a MCODE function object.
|
||||||
|
outer_frame must be set by the caller AFTER refreshing from GC root,
|
||||||
|
since js_mallocz can trigger GC which invalidates stale JSValues. */
|
||||||
|
static JSValue js_new_mcode_function(JSContext *ctx, JSMCode *code) {
|
||||||
|
JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction));
|
||||||
|
if (!fn) return JS_EXCEPTION;
|
||||||
|
|
||||||
|
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
|
||||||
|
fn->kind = JS_FUNC_KIND_MCODE;
|
||||||
|
fn->length = code->nr_args;
|
||||||
|
fn->name = JS_NULL;
|
||||||
|
fn->u.mcode.code = code;
|
||||||
|
fn->u.mcode.outer_frame = JS_NULL;
|
||||||
|
fn->u.mcode.env_record = JS_NULL;
|
||||||
|
|
||||||
|
return JS_MKPTR(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main MCODE interpreter — executes pre-parsed JSMCode */
|
||||||
|
static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
|
||||||
|
int argc, JSValue *argv, JSValue outer_frame) {
|
||||||
|
JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots);
|
||||||
|
if (!frame) return JS_EXCEPTION;
|
||||||
|
|
||||||
|
/* Protect frame from GC */
|
||||||
|
JSGCRef frame_ref;
|
||||||
|
JS_AddGCRef(ctx, &frame_ref);
|
||||||
|
frame_ref.val = JS_MKPTR(frame);
|
||||||
|
|
||||||
|
/* Create a function object for the main frame so return can find the code */
|
||||||
|
JSValue main_func = js_new_mcode_function(ctx, code);
|
||||||
|
if (JS_IsException(main_func)) {
|
||||||
|
JS_DeleteGCRef(ctx, &frame_ref);
|
||||||
|
return JS_ThrowInternalError(ctx, "failed to allocate main function for MCODE");
|
||||||
|
}
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
/* Set outer_frame AFTER allocation so it uses the post-GC frame pointer */
|
||||||
|
JSFunction *main_fn = JS_VALUE_GET_FUNCTION(main_func);
|
||||||
|
main_fn->u.mcode.outer_frame = outer_frame;
|
||||||
|
frame->function = main_func;
|
||||||
|
|
||||||
|
/* Setup initial frame */
|
||||||
|
frame->slots[0] = this_obj; /* slot 0 is this */
|
||||||
|
for (int i = 0; i < argc && i < code->nr_args; i++) {
|
||||||
|
frame->slots[1 + i] = argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t pc = 0;
|
||||||
|
JSValue result = JS_NULL;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
/* Check for interrupt */
|
||||||
|
if (reg_vm_check_interrupt(ctx)) {
|
||||||
|
result = JS_ThrowInternalError(ctx, "interrupted");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc >= code->instr_count) {
|
||||||
|
/* Implicit return null */
|
||||||
|
result = JS_NULL;
|
||||||
|
if (JS_IsNull(frame->caller)) goto done;
|
||||||
|
|
||||||
|
/* Pop frame — read return info from CALLER */
|
||||||
|
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||||
|
int ret_info = JS_VALUE_GET_INT(caller->address);
|
||||||
|
frame->caller = JS_NULL;
|
||||||
|
frame = caller;
|
||||||
|
frame_ref.val = JS_MKPTR(frame);
|
||||||
|
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
code = fn->u.mcode.code;
|
||||||
|
pc = ret_info >> 16;
|
||||||
|
frame->slots[ret_info & 0xFFFF] = result;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *instr = code->instrs[pc++];
|
||||||
|
cJSON *op_item = cJSON_GetArrayItem(instr, 0);
|
||||||
|
const char *op = op_item->valuestring;
|
||||||
|
|
||||||
|
/* Operand extraction helpers — items 1,2,3 */
|
||||||
|
cJSON *a1 = cJSON_GetArrayItem(instr, 1);
|
||||||
|
cJSON *a2 = cJSON_GetArrayItem(instr, 2);
|
||||||
|
cJSON *a3 = cJSON_GetArrayItem(instr, 3);
|
||||||
|
|
||||||
|
/* ---- Constants ---- */
|
||||||
|
if (strcmp(op, "access") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
if (cJSON_IsString(a2)) {
|
||||||
|
JSValue str = JS_NewString(ctx, a2->valuestring);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = str;
|
||||||
|
} else {
|
||||||
|
frame->slots[dest] = JS_NewFloat64(ctx, a2->valuedouble);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "int") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_NewFloat64(ctx, a2->valuedouble);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "null") == 0) {
|
||||||
|
frame->slots[(int)a1->valuedouble] = JS_NULL;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "true") == 0) {
|
||||||
|
frame->slots[(int)a1->valuedouble] = JS_TRUE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "false") == 0) {
|
||||||
|
frame->slots[(int)a1->valuedouble] = JS_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Movement ---- */
|
||||||
|
else if (strcmp(op, "move") == 0) {
|
||||||
|
frame->slots[(int)a1->valuedouble] = frame->slots[(int)a2->valuedouble];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Arithmetic (float64) ---- */
|
||||||
|
else if (strcmp(op, "add") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
/* Use the same binop helper as the register VM for correct int/float handling */
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_ADD, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "sub") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_SUB, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "mul") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_MUL, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "div") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_DIV, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "mod") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_MOD, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "pow") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_POW, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "neg") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue v = frame->slots[(int)a2->valuedouble];
|
||||||
|
if (JS_IsInt(v)) {
|
||||||
|
int32_t i = JS_VALUE_GET_INT(v);
|
||||||
|
if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, -(double)i);
|
||||||
|
else frame->slots[dest] = JS_NewInt32(ctx, -i);
|
||||||
|
} else {
|
||||||
|
double d; JS_ToFloat64(ctx, &d, v);
|
||||||
|
frame->slots[dest] = JS_NewFloat64(ctx, -d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "inc") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue v = frame->slots[(int)a2->valuedouble];
|
||||||
|
if (JS_IsInt(v)) {
|
||||||
|
int32_t i = JS_VALUE_GET_INT(v);
|
||||||
|
if (i == INT32_MAX) frame->slots[dest] = JS_NewFloat64(ctx, (double)i + 1);
|
||||||
|
else frame->slots[dest] = JS_NewInt32(ctx, i + 1);
|
||||||
|
} else {
|
||||||
|
double d; JS_ToFloat64(ctx, &d, v);
|
||||||
|
frame->slots[dest] = JS_NewFloat64(ctx, d + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "dec") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue v = frame->slots[(int)a2->valuedouble];
|
||||||
|
if (JS_IsInt(v)) {
|
||||||
|
int32_t i = JS_VALUE_GET_INT(v);
|
||||||
|
if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, (double)i - 1);
|
||||||
|
else frame->slots[dest] = JS_NewInt32(ctx, i - 1);
|
||||||
|
} else {
|
||||||
|
double d; JS_ToFloat64(ctx, &d, v);
|
||||||
|
frame->slots[dest] = JS_NewFloat64(ctx, d - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Text ---- */
|
||||||
|
else if (strcmp(op, "concat") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = JS_ConcatString(ctx, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Comparison ---- */
|
||||||
|
else if (strcmp(op, "eq") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_EQ, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "ne") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_NE, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "lt") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_LT, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "le") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_LE, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "gt") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_GT, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "ge") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue left = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue right = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_GE, left, right);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Sensory (type checks) ---- */
|
||||||
|
else if (strcmp(op, "number?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_VALUE_IS_NUMBER(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "text?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_VALUE_IS_TEXT(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "function?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_IsFunction(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "null?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_IsNull(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "integer?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_IsInt(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "array?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_IsArray(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "record?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = JS_IsRecord(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "logical?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int tag = JS_VALUE_GET_TAG(frame->slots[(int)a2->valuedouble]);
|
||||||
|
frame->slots[dest] = (tag == JS_TAG_BOOL) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "true?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == JS_TRUE) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "false?") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == JS_FALSE) ? JS_TRUE : JS_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Logical / Bitwise ---- */
|
||||||
|
else if (strcmp(op, "not") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int b = JS_ToBool(ctx, frame->slots[(int)a2->valuedouble]);
|
||||||
|
frame->slots[dest] = JS_NewBool(ctx, !b);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "bitnot") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int32_t i; JS_ToInt32(ctx, &i, frame->slots[(int)a2->valuedouble]);
|
||||||
|
frame->slots[dest] = JS_NewInt32(ctx, ~i);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "bitand") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_BITAND, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble]);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "bitor") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_BITOR, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble]);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "bitxor") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_BITXOR, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble]);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "shl") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_SHL, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble]);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "shr") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_SHR, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble]);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "ushr") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue res = reg_vm_binop(ctx, MACH_OP_USHR, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble]);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Control flow ---- */
|
||||||
|
else if (strcmp(op, "jump") == 0) {
|
||||||
|
const char *label = cJSON_IsString(a1) ? a1->valuestring : NULL;
|
||||||
|
if (label) pc = mcode_resolve_label(code, label);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "jump_true") == 0) {
|
||||||
|
int slot = (int)a1->valuedouble;
|
||||||
|
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
|
||||||
|
if (label && JS_ToBool(ctx, frame->slots[slot]))
|
||||||
|
pc = mcode_resolve_label(code, label);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "jump_false") == 0) {
|
||||||
|
int slot = (int)a1->valuedouble;
|
||||||
|
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
|
||||||
|
if (label && !JS_ToBool(ctx, frame->slots[slot]))
|
||||||
|
pc = mcode_resolve_label(code, label);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "jump_null") == 0) {
|
||||||
|
int slot = (int)a1->valuedouble;
|
||||||
|
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
|
||||||
|
if (label && JS_IsNull(frame->slots[slot]))
|
||||||
|
pc = mcode_resolve_label(code, label);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "jump_not_null") == 0) {
|
||||||
|
int slot = (int)a1->valuedouble;
|
||||||
|
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
|
||||||
|
if (label && !JS_IsNull(frame->slots[slot]))
|
||||||
|
pc = mcode_resolve_label(code, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Property access ---- */
|
||||||
|
else if (strcmp(op, "load_prop") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int obj_reg = (int)a2->valuedouble;
|
||||||
|
JSValue key;
|
||||||
|
if (cJSON_IsString(a3)) {
|
||||||
|
key = JS_NewString(ctx, a3->valuestring);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
} else {
|
||||||
|
key = frame->slots[(int)a3->valuedouble];
|
||||||
|
}
|
||||||
|
JSValue obj = frame->slots[obj_reg];
|
||||||
|
JSValue val = JS_GetProperty(ctx, obj, key);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = val;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "store_prop") == 0) {
|
||||||
|
int obj_reg = (int)a1->valuedouble;
|
||||||
|
int val_reg = (int)a2->valuedouble;
|
||||||
|
JSValue key;
|
||||||
|
if (cJSON_IsString(a3)) {
|
||||||
|
key = JS_NewString(ctx, a3->valuestring);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
} else {
|
||||||
|
key = frame->slots[(int)a3->valuedouble];
|
||||||
|
}
|
||||||
|
JSValue obj = frame->slots[obj_reg];
|
||||||
|
JSValue val = frame->slots[val_reg];
|
||||||
|
JS_SetProperty(ctx, obj, key, val);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "load_idx") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue obj = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue idx = frame->slots[(int)a3->valuedouble];
|
||||||
|
JSValue val;
|
||||||
|
if (JS_IsInt(idx))
|
||||||
|
val = JS_GetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx));
|
||||||
|
else
|
||||||
|
val = JS_GetProperty(ctx, obj, idx);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = val;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "store_idx") == 0) {
|
||||||
|
JSValue obj = frame->slots[(int)a1->valuedouble];
|
||||||
|
JSValue idx = frame->slots[(int)a2->valuedouble];
|
||||||
|
JSValue val = frame->slots[(int)a3->valuedouble];
|
||||||
|
if (JS_IsInt(idx))
|
||||||
|
JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val);
|
||||||
|
else
|
||||||
|
JS_SetProperty(ctx, obj, idx, val);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "delete_prop") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int obj_reg = (int)a2->valuedouble;
|
||||||
|
JSValue key;
|
||||||
|
if (cJSON_IsString(a3)) {
|
||||||
|
key = JS_NewString(ctx, a3->valuestring);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
} else {
|
||||||
|
key = frame->slots[(int)a3->valuedouble];
|
||||||
|
}
|
||||||
|
JSValue obj = frame->slots[obj_reg];
|
||||||
|
int ret = JS_DeleteProperty(ctx, obj, key);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = JS_NewBool(ctx, ret >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Closure access ---- */
|
||||||
|
else if (strcmp(op, "get") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int slot = (int)a2->valuedouble;
|
||||||
|
int depth = (int)a3->valuedouble;
|
||||||
|
/* Walk outer_frame chain from the current function's outer_frame */
|
||||||
|
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
JSValue of = (cur_fn && cur_fn->kind == JS_FUNC_KIND_MCODE) ? cur_fn->u.mcode.outer_frame : JS_NULL;
|
||||||
|
for (int d = 1; d < depth && !JS_IsNull(of); d++) {
|
||||||
|
JSFrameRegister *outer = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
|
||||||
|
JSFunction *outer_fn = JS_VALUE_GET_FUNCTION(outer->function);
|
||||||
|
of = (outer_fn && outer_fn->kind == JS_FUNC_KIND_MCODE) ? outer_fn->u.mcode.outer_frame : JS_NULL;
|
||||||
|
}
|
||||||
|
if (!JS_IsNull(of)) {
|
||||||
|
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
|
||||||
|
frame->slots[dest] = target->slots[slot];
|
||||||
|
} else {
|
||||||
|
frame->slots[dest] = JS_NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "put") == 0) {
|
||||||
|
int src = (int)a1->valuedouble;
|
||||||
|
int slot = (int)a2->valuedouble;
|
||||||
|
int depth = (int)a3->valuedouble;
|
||||||
|
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
JSValue of = (cur_fn && cur_fn->kind == JS_FUNC_KIND_MCODE) ? cur_fn->u.mcode.outer_frame : JS_NULL;
|
||||||
|
for (int d = 1; d < depth && !JS_IsNull(of); d++) {
|
||||||
|
JSFrameRegister *outer = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
|
||||||
|
JSFunction *outer_fn = JS_VALUE_GET_FUNCTION(outer->function);
|
||||||
|
of = (outer_fn && outer_fn->kind == JS_FUNC_KIND_MCODE) ? outer_fn->u.mcode.outer_frame : JS_NULL;
|
||||||
|
}
|
||||||
|
if (!JS_IsNull(of)) {
|
||||||
|
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
|
||||||
|
target->slots[slot] = frame->slots[src];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Variable access (globals / unresolved) ---- */
|
||||||
|
else if (strcmp(op, "cap_name") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue key;
|
||||||
|
if (cJSON_IsString(a2)) {
|
||||||
|
key = JS_NewString(ctx, a2->valuestring);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
} else {
|
||||||
|
key = frame->slots[(int)a2->valuedouble];
|
||||||
|
}
|
||||||
|
JSValue val = JS_GetProperty(ctx, ctx->global_obj, key);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
frame->slots[dest] = val;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "set_var") == 0) {
|
||||||
|
int val_reg = (int)a2->valuedouble;
|
||||||
|
JSValue key;
|
||||||
|
if (cJSON_IsString(a1)) {
|
||||||
|
key = JS_NewString(ctx, a1->valuestring);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
} else {
|
||||||
|
key = frame->slots[(int)a1->valuedouble];
|
||||||
|
}
|
||||||
|
JSValue val = frame->slots[val_reg];
|
||||||
|
JS_SetProperty(ctx, ctx->global_obj, key, val);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Function calls ---- */
|
||||||
|
else if (strcmp(op, "frame") == 0) {
|
||||||
|
int frame_reg = (int)a1->valuedouble;
|
||||||
|
JSValue func_val = frame->slots[(int)a2->valuedouble];
|
||||||
|
int call_argc = a3 ? (int)a3->valuedouble : 0;
|
||||||
|
|
||||||
|
if (!JS_IsFunction(func_val)) {
|
||||||
|
result = JS_ThrowTypeError(ctx, "not a function");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
|
||||||
|
if (fn->kind == JS_FUNC_KIND_MCODE) {
|
||||||
|
JSMCode *fn_code = fn->u.mcode.code;
|
||||||
|
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
|
||||||
|
if (!new_frame) { result = JS_EXCEPTION; goto done; }
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
/* Re-read func_val after GC may have moved it */
|
||||||
|
func_val = frame->slots[(int)a2->valuedouble];
|
||||||
|
new_frame->function = func_val;
|
||||||
|
frame->slots[frame_reg] = JS_MKPTR(new_frame);
|
||||||
|
} else {
|
||||||
|
/* For C/bytecode functions, store func_val directly — handle at invoke */
|
||||||
|
frame->slots[frame_reg] = func_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "set_this") == 0) {
|
||||||
|
int frame_reg = (int)a1->valuedouble;
|
||||||
|
JSValue target = frame->slots[frame_reg];
|
||||||
|
if (!JS_IsFunction(target)) {
|
||||||
|
JSFrameRegister *call_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target);
|
||||||
|
call_frame->slots[0] = frame->slots[(int)a2->valuedouble];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "arg") == 0) {
|
||||||
|
int frame_reg = (int)a1->valuedouble;
|
||||||
|
int arg_idx = (int)a2->valuedouble;
|
||||||
|
int val_reg = (int)a3->valuedouble;
|
||||||
|
JSValue target = frame->slots[frame_reg];
|
||||||
|
if (!JS_IsFunction(target)) {
|
||||||
|
JSFrameRegister *call_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target);
|
||||||
|
call_frame->slots[arg_idx] = frame->slots[val_reg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "invoke") == 0) {
|
||||||
|
int frame_reg = (int)a1->valuedouble;
|
||||||
|
int ret_reg = (int)a2->valuedouble;
|
||||||
|
JSValue target = frame->slots[frame_reg];
|
||||||
|
|
||||||
|
if (JS_IsFunction(target)) {
|
||||||
|
/* C or bytecode function — collect args from the frame state */
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(target);
|
||||||
|
/* For C functions, we need to collect args. Look back at preceding arg instructions. */
|
||||||
|
/* Simpler approach: call via JS_Call with args collected */
|
||||||
|
/* We need to find the args that were set. Use a small stack buffer. */
|
||||||
|
JSValue c_argv[16];
|
||||||
|
int c_argc = 0;
|
||||||
|
/* Scan back in instructions to find arg instructions for this frame_reg */
|
||||||
|
/* Actually, for C functions, the args were stored on the frame target which is func_val */
|
||||||
|
/* Better: just call with global_obj as this and no args for now */
|
||||||
|
/* Full approach: re-scan recent instructions to collect args */
|
||||||
|
|
||||||
|
/* Better: use JS_Call with the func and gather args from recent frame/arg ops */
|
||||||
|
/* Since we can't easily recover args from a non-frame target, use JS_Call with 0 args */
|
||||||
|
/* TODO: proper C function arg collection */
|
||||||
|
JSValue this_val = JS_NULL;
|
||||||
|
result = JS_Call(ctx, target, this_val, c_argc, c_argv);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
if (JS_IsException(result)) goto done;
|
||||||
|
frame->slots[ret_reg] = result;
|
||||||
|
} else {
|
||||||
|
/* MCODE function — switch frames */
|
||||||
|
JSFrameRegister *new_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target);
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(new_frame->function);
|
||||||
|
|
||||||
|
if (fn->kind == JS_FUNC_KIND_MCODE) {
|
||||||
|
/* Store return address: pc << 16 | ret_slot */
|
||||||
|
frame->address = JS_NewInt32(ctx, (pc << 16) | ret_reg);
|
||||||
|
new_frame->caller = JS_MKPTR(frame);
|
||||||
|
|
||||||
|
/* Switch to new frame */
|
||||||
|
frame = new_frame;
|
||||||
|
frame_ref.val = JS_MKPTR(frame);
|
||||||
|
code = fn->u.mcode.code;
|
||||||
|
pc = 0;
|
||||||
|
} else if (fn->kind == JS_FUNC_KIND_C) {
|
||||||
|
/* C function stored in a frame — collect args from frame slots */
|
||||||
|
int c_argc = 0;
|
||||||
|
JSValue c_argv[16];
|
||||||
|
for (int i = 1; i < fn->u.mcode.code->nr_slots && i <= 16; i++) {
|
||||||
|
if (JS_IsNull(new_frame->slots[i])) break;
|
||||||
|
c_argv[c_argc++] = new_frame->slots[i];
|
||||||
|
}
|
||||||
|
JSValue c_result = js_call_c_function(ctx, new_frame->function, new_frame->slots[0], c_argc, c_argv);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
if (JS_IsException(c_result)) { result = c_result; goto done; }
|
||||||
|
frame->slots[ret_reg] = c_result;
|
||||||
|
} else {
|
||||||
|
/* Bytecode interop — use JS_Call */
|
||||||
|
int bc_argc = 0;
|
||||||
|
JSValue bc_argv[16];
|
||||||
|
/* TODO: proper arg count */
|
||||||
|
JSValue bc_result = JS_Call(ctx, new_frame->function, new_frame->slots[0], bc_argc, bc_argv);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
if (JS_IsException(bc_result)) { result = bc_result; goto done; }
|
||||||
|
frame->slots[ret_reg] = bc_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Tail calls ---- */
|
||||||
|
else if (strcmp(op, "goframe") == 0) {
|
||||||
|
int frame_reg = (int)a1->valuedouble;
|
||||||
|
int func_reg = (int)a2->valuedouble;
|
||||||
|
JSValue func_val = frame->slots[func_reg];
|
||||||
|
|
||||||
|
if (!JS_IsFunction(func_val)) {
|
||||||
|
result = JS_ThrowTypeError(ctx, "not a function");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
|
||||||
|
if (fn->kind == JS_FUNC_KIND_MCODE) {
|
||||||
|
JSMCode *fn_code = fn->u.mcode.code;
|
||||||
|
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
|
||||||
|
if (!new_frame) { result = JS_EXCEPTION; goto done; }
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
func_val = frame->slots[func_reg]; /* re-read after GC */
|
||||||
|
new_frame->function = func_val;
|
||||||
|
frame->slots[frame_reg] = JS_MKPTR(new_frame);
|
||||||
|
} else {
|
||||||
|
frame->slots[frame_reg] = func_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "goinvoke") == 0) {
|
||||||
|
int frame_reg = (int)a1->valuedouble;
|
||||||
|
int ret_reg = (int)a2->valuedouble;
|
||||||
|
JSValue target = frame->slots[frame_reg];
|
||||||
|
|
||||||
|
if (JS_IsFunction(target)) {
|
||||||
|
result = JS_ThrowInternalError(ctx, "C function tail call not supported in MCODE");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSFrameRegister *new_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target);
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(new_frame->function);
|
||||||
|
|
||||||
|
if (fn->kind != JS_FUNC_KIND_MCODE) {
|
||||||
|
result = JS_ThrowInternalError(ctx, "non-mcode function in goinvoke");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tail call — bypass current frame */
|
||||||
|
new_frame->caller = frame->caller;
|
||||||
|
new_frame->address = frame->address;
|
||||||
|
frame = new_frame;
|
||||||
|
frame_ref.val = JS_MKPTR(frame);
|
||||||
|
code = fn->u.mcode.code;
|
||||||
|
pc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Return ---- */
|
||||||
|
else if (strcmp(op, "return") == 0) {
|
||||||
|
result = frame->slots[(int)a1->valuedouble];
|
||||||
|
|
||||||
|
if (JS_IsNull(frame->caller)) goto done;
|
||||||
|
|
||||||
|
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||||
|
int ret_info = JS_VALUE_GET_INT(caller->address);
|
||||||
|
frame->caller = JS_NULL;
|
||||||
|
|
||||||
|
frame = caller;
|
||||||
|
frame_ref.val = JS_MKPTR(frame);
|
||||||
|
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
code = fn->u.mcode.code;
|
||||||
|
pc = ret_info >> 16;
|
||||||
|
frame->slots[ret_info & 0xFFFF] = result;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "return_undef") == 0) {
|
||||||
|
result = JS_NULL;
|
||||||
|
|
||||||
|
if (JS_IsNull(frame->caller)) goto done;
|
||||||
|
|
||||||
|
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||||
|
int ret_info = JS_VALUE_GET_INT(caller->address);
|
||||||
|
frame->caller = JS_NULL;
|
||||||
|
|
||||||
|
frame = caller;
|
||||||
|
frame_ref.val = JS_MKPTR(frame);
|
||||||
|
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
code = fn->u.mcode.code;
|
||||||
|
pc = ret_info >> 16;
|
||||||
|
frame->slots[ret_info & 0xFFFF] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Object/Array creation ---- */
|
||||||
|
else if (strcmp(op, "mkrecord") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue rec = JS_NewObject(ctx);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
if (JS_IsException(rec)) { result = rec; goto done; }
|
||||||
|
frame->slots[dest] = rec;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "mkarray") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
JSValue arr = JS_NewArray(ctx);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
if (JS_IsException(arr)) { result = arr; goto done; }
|
||||||
|
frame->slots[dest] = arr;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "mkfunc") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int func_id = (int)a2->valuedouble;
|
||||||
|
if ((uint32_t)func_id < code->func_count && code->functions[func_id]) {
|
||||||
|
JSValue fn_val = js_new_mcode_function(ctx, code->functions[func_id]);
|
||||||
|
/* Refresh frame AFTER allocation (GC may have moved it) */
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
/* Set outer_frame AFTER refresh so it points to the current frame location */
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
||||||
|
fn->u.mcode.outer_frame = frame_ref.val;
|
||||||
|
frame->slots[dest] = fn_val;
|
||||||
|
} else {
|
||||||
|
frame->slots[dest] = JS_NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "mkfunc_arrow") == 0) {
|
||||||
|
int dest = (int)a1->valuedouble;
|
||||||
|
int func_id = (int)a2->valuedouble;
|
||||||
|
if ((uint32_t)func_id < code->func_count && code->functions[func_id]) {
|
||||||
|
JSValue fn_val = js_new_mcode_function(ctx, code->functions[func_id]);
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
||||||
|
fn->u.mcode.outer_frame = frame_ref.val;
|
||||||
|
frame->slots[dest] = fn_val;
|
||||||
|
} else {
|
||||||
|
frame->slots[dest] = JS_NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Exceptions ---- */
|
||||||
|
else if (strcmp(op, "throw") == 0) {
|
||||||
|
result = JS_Throw(ctx, frame->slots[(int)a1->valuedouble]);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "try_begin") == 0 || strcmp(op, "try_end") == 0 ||
|
||||||
|
strcmp(op, "catch_begin") == 0 || strcmp(op, "catch_end") == 0) {
|
||||||
|
/* TODO: exception handling */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Unknown opcode ---- */
|
||||||
|
else {
|
||||||
|
result = JS_ThrowInternalError(ctx, "unknown MCODE opcode: %s", op);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
JS_DeleteGCRef(ctx, &frame_ref);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public API: parse MCODE JSON and execute */
|
||||||
|
JSValue JS_CallMcode(JSContext *ctx, const char *mcode_json) {
|
||||||
|
cJSON *root = cJSON_Parse(mcode_json);
|
||||||
|
if (!root) return JS_ThrowSyntaxError(ctx, "invalid MCODE JSON");
|
||||||
|
|
||||||
|
cJSON *main_obj = cJSON_GetObjectItem(root, "main");
|
||||||
|
if (!main_obj) {
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return JS_ThrowSyntaxError(ctx, "MCODE JSON missing 'main' section");
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *functions = cJSON_GetObjectItem(root, "functions");
|
||||||
|
|
||||||
|
/* Parse main code */
|
||||||
|
JSMCode *code = jsmcode_parse(main_obj, functions);
|
||||||
|
if (!code) {
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return JS_ThrowInternalError(ctx, "failed to parse MCODE");
|
||||||
|
}
|
||||||
|
code->json_root = root; /* Keep JSON alive — instrs point into it */
|
||||||
|
|
||||||
|
/* Execute with global_obj as this */
|
||||||
|
JSValue result = mcode_exec(ctx, code, ctx->global_obj, 0, NULL, JS_NULL);
|
||||||
|
|
||||||
|
jsmcode_free(code);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
MACH Public API
|
MACH Public API
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|||||||
@@ -1239,6 +1239,10 @@ void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env);
|
|||||||
Returns result of execution, or JS_EXCEPTION on error. */
|
Returns result of execution, or JS_EXCEPTION on error. */
|
||||||
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
|
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
|
||||||
|
|
||||||
|
/* Parse and execute MCODE JSON directly via the MCODE interpreter.
|
||||||
|
Returns result of execution, or JS_EXCEPTION on error. */
|
||||||
|
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
|
||||||
|
|
||||||
/* Integrate a CellModule with an environment and execute.
|
/* Integrate a CellModule with an environment and execute.
|
||||||
Returns callable function value, or JS_EXCEPTION on error. */
|
Returns callable function value, or JS_EXCEPTION on error. */
|
||||||
JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);
|
JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);
|
||||||
|
|||||||
Reference in New Issue
Block a user