From ff8c68d01c57c39d0aa9ea5aab042773a2b11ca9 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 6 Feb 2026 03:31:31 -0600 Subject: [PATCH] mcode and mcode interpreter --- source/cell.c | 77 ++++ source/quickjs.c | 938 +++++++++++++++++++++++++++++++++++++++++++++++ source/quickjs.h | 4 + 3 files changed, 1019 insertions(+) diff --git a/source/cell.c b/source/cell.c index e30bd151..91bc56d0 100644 --- a/source/cell.c +++ b/source/cell.c @@ -575,6 +575,83 @@ int cell_init(int argc, char **argv) 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 = ""; + + 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 */ if (argc >= 3 && strcmp(argv[1], "--mach") == 0) { const char *script_or_file = argv[2]; diff --git a/source/quickjs.c b/source/quickjs.c index 769bb244..4ad82f56 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -652,6 +652,25 @@ typedef struct JSCodeRegister { JSValue name; /* function name (stone text) */ } 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 reference outer frames via (depth, slot) addressing. Stores function as JSValue to survive GC movements. */ @@ -1763,6 +1782,7 @@ typedef enum { JS_FUNC_KIND_BYTECODE, JS_FUNC_KIND_C_DATA, JS_FUNC_KIND_REGISTER, /* register-based VM function */ + JS_FUNC_KIND_MCODE, /* MCODE JSON interpreter */ } JSFunctionKind; typedef struct JSFunction { @@ -1786,6 +1806,11 @@ typedef struct JSFunction { JSValue env_record; /* stone record, module environment */ JSValue outer_frame; /* JSFrame JSValue, for closures */ } 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; } 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); } } + } 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; } @@ -33843,6 +33872,915 @@ done: 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 ============================================================ */ diff --git a/source/quickjs.h b/source/quickjs.h index 66641f30..1b169371 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -1239,6 +1239,10 @@ void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env); Returns result of execution, or JS_EXCEPTION on error. */ 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. Returns callable function value, or JS_EXCEPTION on error. */ JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);