/* * QuickJS Javascript Engine * * Copyright (c) 2017-2025 Fabrice Bellard * Copyright (c) 2017-2025 Charlie Gordon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "quickjs-internal.h" /* ============================================================ MCODE JSON Interpreter ============================================================ */ /* Parse a single MCODE function from cJSON into a JSMCode struct */ /* Parse a single function (no recursive function parsing) */ 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_GetObjectItemCaseSensitive(func_def, "nr_args")); code->nr_slots = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(func_def, "nr_slots")); /* Build instruction array from cJSON linked list */ cJSON *instrs_arr = cJSON_GetObjectItemCaseSensitive(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; /* Extract line table from trailing numbers in each instruction array */ if (idx > 0) { code->line_table = js_mallocz_rt(idx * sizeof(MachLineEntry)); for (uint32_t i = 0; i < idx; i++) { cJSON *instr = code->instrs[i]; int n = cJSON_GetArraySize(instr); if (n >= 2) { cJSON *line_item = cJSON_GetArrayItem(instr, n - 2); cJSON *col_item = cJSON_GetArrayItem(instr, n - 1); if (cJSON_IsNumber(line_item) && cJSON_IsNumber(col_item)) { code->line_table[i].line = (uint16_t)line_item->valuedouble; code->line_table[i].col = (uint16_t)col_item->valuedouble; } } } } /* Extract name and filename from function definition */ code->name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(func_def, "name")); code->filename = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(func_def, "filename")); /* Extract disruption_pc */ cJSON *dpc = cJSON_GetObjectItemCaseSensitive(func_def, "disruption_pc"); code->disruption_pc = dpc ? (uint16_t)cJSON_GetNumberValue(dpc) : 0; return code; } /* Parse MCODE: main + all functions from the global functions array. All JSMCode structs share the same functions[] pointer. */ 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. */ 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); if (code->functions[i]->line_table) js_free_rt(code->functions[i]->line_table); 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->line_table) js_free_rt(code->line_table); 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. */ 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 */ JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, int argc, JSValue *argv, JSValue outer_frame) { /* Protect argv, this_obj, outer_frame from GC using JSGCRef. alloc_frame_register and js_new_mcode_function can trigger GC. */ int nargs_copy = (argc < code->nr_args) ? argc : code->nr_args; JSGCRef this_gc, of_gc; JS_PushGCRef(ctx, &this_gc); this_gc.val = this_obj; JS_PushGCRef(ctx, &of_gc); of_gc.val = outer_frame; JSGCRef arg_gcs[nargs_copy > 0 ? nargs_copy : 1]; for (int i = 0; i < nargs_copy; i++) { JS_PushGCRef(ctx, &arg_gcs[i]); arg_gcs[i].val = argv[i]; } JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); if (!frame) { for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]); JS_PopGCRef(ctx, &of_gc); JS_PopGCRef(ctx, &this_gc); 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); for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]); JS_PopGCRef(ctx, &of_gc); JS_PopGCRef(ctx, &this_gc); 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 value */ JSFunction *main_fn = JS_VALUE_GET_FUNCTION(main_func); main_fn->u.mcode.outer_frame = of_gc.val; frame->function = main_func; /* Setup initial frame from GC-safe refs */ frame->slots[0] = this_gc.val; /* slot 0 is this */ for (int i = 0; i < nargs_copy; i++) { frame->slots[1 + i] = arg_gcs[i].val; } for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]); JS_PopGCRef(ctx, &of_gc); JS_PopGCRef(ctx, &this_gc); 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_IsObject(a2)) { /* Intrinsic: {"kind":"name","name":"...","make":"intrinsic"} */ const char *iname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(a2, "name")); if (iname) { JSValue key = JS_NewString(ctx, iname); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); /* Try env (outer_frame) first, then global */ JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function); JSValue env = (cur_fn && cur_fn->kind == JS_FUNC_KIND_MCODE) ? cur_fn->u.mcode.outer_frame : JS_NULL; JSValue val = JS_NULL; if (!JS_IsNull(env)) { val = JS_GetProperty(ctx, env, key); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); } if (JS_IsNull(val)) { key = JS_NewString(ctx, iname); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); val = JS_GetProperty(ctx, ctx->global_obj, key); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); } if (JS_IsNull(val)) { key = JS_NewString(ctx, iname); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); int has = JS_HasProperty(ctx, ctx->global_obj, key); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (has <= 0) { JS_ThrowReferenceError(ctx, "'%s' is not defined", iname); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto disrupt; } } frame->slots[dest] = val; } else { frame->slots[dest] = JS_NULL; } } else 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 (inline) ---- */ 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]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int64_t r = (int64_t)JS_VALUE_GET_INT(left) + (int64_t)JS_VALUE_GET_INT(right); frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewFloat64(ctx, a + b); } else if (JS_IsText(left) && JS_IsText(right)) { JSValue res = JS_ConcatString(ctx, left, right); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else { goto disrupt; } } else if (strcmp(op, "subtract") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int64_t r = (int64_t)JS_VALUE_GET_INT(left) - (int64_t)JS_VALUE_GET_INT(right); frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewFloat64(ctx, a - b); } else { goto disrupt; } } else if (strcmp(op, "multiply") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int64_t r = (int64_t)JS_VALUE_GET_INT(left) * (int64_t)JS_VALUE_GET_INT(right); frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewFloat64(ctx, a * b); } else { goto disrupt; } } else if (strcmp(op, "divide") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right); if (ib == 0) { frame->slots[dest] = JS_NULL; } else if (ia % ib == 0) frame->slots[dest] = JS_NewInt32(ctx, ia / ib); else frame->slots[dest] = JS_NewFloat64(ctx, (double)ia / (double)ib); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); if (b == 0.0) { frame->slots[dest] = JS_NULL; } else frame->slots[dest] = JS_NewFloat64(ctx, a / b); } else { goto disrupt; } } else if (strcmp(op, "integer_divide") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int32_t ib = JS_VALUE_GET_INT(right); if (ib == 0) { frame->slots[dest] = JS_NULL; } else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) / ib); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); if (b == 0.0) { frame->slots[dest] = JS_NULL; } else frame->slots[dest] = JS_NewInt32(ctx, (int32_t)(a / b)); } else { goto disrupt; } } else if (strcmp(op, "modulo") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int32_t ib = JS_VALUE_GET_INT(right); if (ib == 0) { frame->slots[dest] = JS_NULL; } else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) % ib); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); if (b == 0.0) { frame->slots[dest] = JS_NULL; } else frame->slots[dest] = JS_NewFloat64(ctx, fmod(a, b)); } else { goto disrupt; } } else if (strcmp(op, "remainder") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int32_t ib = JS_VALUE_GET_INT(right); if (ib == 0) { frame->slots[dest] = JS_NULL; } else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) % ib); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); if (b == 0.0) { frame->slots[dest] = JS_NULL; } else frame->slots[dest] = JS_NewFloat64(ctx, remainder(a, b)); } else { goto disrupt; } } 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]; if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewFloat64(ctx, pow(a, b)); } else { goto disrupt; } } else if (strcmp(op, "max") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right); frame->slots[dest] = JS_NewInt32(ctx, ia > ib ? ia : ib); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewFloat64(ctx, a > b ? a : b); } else { goto disrupt; } } else if (strcmp(op, "min") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (JS_VALUE_IS_BOTH_INT(left, right)) { int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right); frame->slots[dest] = JS_NewInt32(ctx, ia < ib ? ia : ib); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewFloat64(ctx, a < b ? a : b); } else { goto disrupt; } } else if (strcmp(op, "neg") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsNumber(v)) { goto disrupt; } 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); } } /* ---- Compiler-internal type guards ---- */ else if (strcmp(op, "is_int") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_IsInt(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "is_num") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_IsNumber(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "is_text") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_IsText(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "is_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, "is_bool") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = (JS_VALUE_GET_TAG(frame->slots[(int)a2->valuedouble]) == JS_TAG_BOOL) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "is_identical") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE; } /* ---- Specialized arithmetic (int) ---- */ else if (strcmp(op, "add_int") == 0) { int dest = (int)a1->valuedouble; int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) + (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]); frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r); } else if (strcmp(op, "sub_int") == 0) { int dest = (int)a1->valuedouble; int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) - (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]); frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r); } else if (strcmp(op, "mul_int") == 0) { int dest = (int)a1->valuedouble; int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) * (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]); frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r); } else if (strcmp(op, "div_int") == 0) { int dest = (int)a1->valuedouble; int32_t ia = JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]); int32_t ib = JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]); if (ib == 0) frame->slots[dest] = JS_NULL; else if (ia % ib == 0) frame->slots[dest] = JS_NewInt32(ctx, ia / ib); else frame->slots[dest] = JS_NewFloat64(ctx, (double)ia / (double)ib); } else if (strcmp(op, "mod_int") == 0) { int dest = (int)a1->valuedouble; int32_t ib = JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]); if (ib == 0) frame->slots[dest] = JS_NULL; else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) % ib); } else if (strcmp(op, "neg_int") == 0) { int dest = (int)a1->valuedouble; int32_t i = JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]); if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, -(double)i); else frame->slots[dest] = JS_NewInt32(ctx, -i); } /* ---- Specialized arithmetic (float) ---- */ else if (strcmp(op, "add_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewFloat64(ctx, a + b); } else if (strcmp(op, "sub_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewFloat64(ctx, a - b); } else if (strcmp(op, "mul_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewFloat64(ctx, a * b); } else if (strcmp(op, "div_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); if (b == 0.0) frame->slots[dest] = JS_NULL; else frame->slots[dest] = JS_NewFloat64(ctx, a / b); } else if (strcmp(op, "mod_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); if (b == 0.0) frame->slots[dest] = JS_NULL; else frame->slots[dest] = JS_NewFloat64(ctx, fmod(a, b)); } else if (strcmp(op, "neg_float") == 0) { int dest = (int)a1->valuedouble; double d; JS_ToFloat64(ctx, &d, frame->slots[(int)a2->valuedouble]); frame->slots[dest] = JS_NewFloat64(ctx, -d); } /* ---- Specialized comparisons (int) ---- */ else if (strcmp(op, "eq_int") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) == JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble])); } else if (strcmp(op, "ne_int") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) != JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble])); } else if (strcmp(op, "lt_int") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) < JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble])); } else if (strcmp(op, "le_int") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) <= JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble])); } else if (strcmp(op, "gt_int") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) > JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble])); } else if (strcmp(op, "ge_int") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) >= JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble])); } /* ---- Specialized comparisons (float) ---- */ else if (strcmp(op, "eq_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewBool(ctx, a == b); } else if (strcmp(op, "ne_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewBool(ctx, a != b); } else if (strcmp(op, "lt_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewBool(ctx, a < b); } else if (strcmp(op, "le_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewBool(ctx, a <= b); } else if (strcmp(op, "gt_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewBool(ctx, a > b); } else if (strcmp(op, "ge_float") == 0) { int dest = (int)a1->valuedouble; double a, b; JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]); JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]); frame->slots[dest] = JS_NewBool(ctx, a >= b); } /* ---- Specialized comparisons (text) ---- */ else if (strcmp(op, "eq_text") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], TRUE) == 0); } else if (strcmp(op, "ne_text") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], TRUE) != 0); } else if (strcmp(op, "lt_text") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) < 0); } else if (strcmp(op, "le_text") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) <= 0); } else if (strcmp(op, "gt_text") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) > 0); } else if (strcmp(op, "ge_text") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) >= 0); } /* ---- Specialized comparisons (bool) ---- */ else if (strcmp(op, "eq_bool") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "ne_bool") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = (frame->slots[(int)a2->valuedouble] != frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "abs") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsNumber(v)) { goto disrupt; } if (JS_IsInt(v)) { int32_t i = JS_VALUE_GET_INT(v); if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, (double)INT32_MAX + 1.0); else frame->slots[dest] = JS_NewInt32(ctx, i < 0 ? -i : i); } else { double d; JS_ToFloat64(ctx, &d, v); frame->slots[dest] = JS_NewFloat64(ctx, fabs(d)); } } else if (strcmp(op, "sign") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsNumber(v)) { goto disrupt; } if (JS_IsInt(v)) { int32_t i = JS_VALUE_GET_INT(v); frame->slots[dest] = JS_NewInt32(ctx, (i > 0) - (i < 0)); } else { double d; JS_ToFloat64(ctx, &d, v); frame->slots[dest] = JS_NewInt32(ctx, (d > 0) - (d < 0)); } } else if (strcmp(op, "fraction") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsNumber(v)) { goto disrupt; } if (JS_IsInt(v)) { frame->slots[dest] = JS_NewInt32(ctx, 0); } else { double d; JS_ToFloat64(ctx, &d, v); frame->slots[dest] = JS_NewFloat64(ctx, d - trunc(d)); } } else if (strcmp(op, "integer") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsNumber(v)) { goto disrupt; } if (JS_IsInt(v)) { frame->slots[dest] = v; } else { double d; JS_ToFloat64(ctx, &d, v); frame->slots[dest] = JS_NewFloat64(ctx, trunc(d)); } } else if (strcmp(op, "ceiling") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; JSValue p = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; } double d, place; JS_ToFloat64(ctx, &d, v); JS_ToFloat64(ctx, &place, p); frame->slots[dest] = JS_NewFloat64(ctx, ceil(d * place) / place); } else if (strcmp(op, "floor") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; JSValue p = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; } double d, place; JS_ToFloat64(ctx, &d, v); JS_ToFloat64(ctx, &place, p); frame->slots[dest] = JS_NewFloat64(ctx, floor(d * place) / place); } else if (strcmp(op, "round") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; JSValue p = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; } double d, place; JS_ToFloat64(ctx, &d, v); JS_ToFloat64(ctx, &place, p); frame->slots[dest] = JS_NewFloat64(ctx, round(d * place) / place); } else if (strcmp(op, "trunc") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; JSValue p = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; } double d, place; JS_ToFloat64(ctx, &d, v); JS_ToFloat64(ctx, &place, p); frame->slots[dest] = JS_NewFloat64(ctx, trunc(d * place) / place); } /* ---- 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]; if (!JS_IsText(left) || !JS_IsText(right)) { goto disrupt; } JSValue res = JS_ConcatString(ctx, left, right); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "concat_space") == 0) { int dest = (int)a1->valuedouble; int left_slot = (int)a2->valuedouble; int right_slot = (int)a3->valuedouble; JSValue left = frame->slots[left_slot]; JSValue right = frame->slots[right_slot]; if (!JS_IsText(left) || !JS_IsText(right)) { goto disrupt; } JSValue space_str = JS_NewString(ctx, " "); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue space = JS_ConcatString(ctx, frame->slots[left_slot], space_str); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue res = JS_ConcatString(ctx, space, frame->slots[right_slot]); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "length") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } frame->slots[dest] = JS_NewInt32(ctx, js_string_value_len(v)); } else if (strcmp(op, "lower") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } JSValue tmp = v; JSValue res = js_cell_text_lower(ctx, JS_NULL, 1, &tmp); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "upper") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } JSValue tmp = v; JSValue res = js_cell_text_upper(ctx, JS_NULL, 1, &tmp); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "character") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsNumber(v)) { goto disrupt; } JSValue tmp = v; JSValue res = js_cell_character(ctx, JS_NULL, 1, &tmp); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "codepoint") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } JSValue tmp = v; JSValue res = js_cell_text_codepoint(ctx, JS_NULL, 1, &tmp); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } /* ---- Comparison (inline) ---- */ 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]; if (left == right) { frame->slots[dest] = JS_TRUE; } else if (JS_VALUE_IS_BOTH_INT(left, right)) { frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) == JS_VALUE_GET_INT(right)); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewBool(ctx, a == b); } else if (JS_IsText(left) && JS_IsText(right)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) == 0); } else if (JS_IsNull(left) && JS_IsNull(right)) { frame->slots[dest] = JS_TRUE; } else if (JS_VALUE_GET_TAG(left) == JS_TAG_BOOL && JS_VALUE_GET_TAG(right) == JS_TAG_BOOL) { frame->slots[dest] = JS_NewBool(ctx, left == right); } else { frame->slots[dest] = JS_FALSE; } } 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]; if (left == right) { frame->slots[dest] = JS_FALSE; } else if (JS_VALUE_IS_BOTH_INT(left, right)) { frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) != JS_VALUE_GET_INT(right)); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewBool(ctx, a != b); } else if (JS_IsText(left) && JS_IsText(right)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) != 0); } else if (JS_IsNull(left) && JS_IsNull(right)) { frame->slots[dest] = JS_FALSE; } else if (JS_VALUE_GET_TAG(left) == JS_TAG_BOOL && JS_VALUE_GET_TAG(right) == JS_TAG_BOOL) { frame->slots[dest] = JS_NewBool(ctx, left != right); } else { frame->slots[dest] = JS_TRUE; } } else if (strcmp(op, "eq_tol") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; cJSON *a4 = cJSON_GetArrayItem(instr, 4); JSValue tol = frame->slots[(int)a4->valuedouble]; if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) { double a, b, t; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); JS_ToFloat64(ctx, &t, tol); frame->slots[dest] = JS_NewBool(ctx, fabs(a - b) <= t); } else if (JS_IsText(left) && JS_IsText(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value_nocase(ctx, left, right) == 0); } else { /* Fall through to standard eq */ if (left == right) frame->slots[dest] = JS_TRUE; else if (JS_IsText(left) && JS_IsText(right)) frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) == 0); else frame->slots[dest] = JS_FALSE; } } else if (strcmp(op, "ne_tol") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; cJSON *a4 = cJSON_GetArrayItem(instr, 4); JSValue tol = frame->slots[(int)a4->valuedouble]; if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) { double a, b, t; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); JS_ToFloat64(ctx, &t, tol); frame->slots[dest] = JS_NewBool(ctx, fabs(a - b) > t); } else if (JS_IsText(left) && JS_IsText(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value_nocase(ctx, left, right) != 0); } else { if (left == right) frame->slots[dest] = JS_FALSE; else if (JS_IsText(left) && JS_IsText(right)) frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) != 0); else frame->slots[dest] = JS_TRUE; } } else if (strcmp(op, "and") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; frame->slots[dest] = JS_ToBool(ctx, left) ? right : left; } else if (strcmp(op, "or") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; frame->slots[dest] = JS_ToBool(ctx, left) ? left : right; } 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]; if (JS_VALUE_IS_BOTH_INT(left, right)) { frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) < JS_VALUE_GET_INT(right)); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewBool(ctx, a < b); } else if (JS_IsText(left) && JS_IsText(right)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) < 0); } else { goto disrupt; } } 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]; if (JS_VALUE_IS_BOTH_INT(left, right)) { frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) <= JS_VALUE_GET_INT(right)); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewBool(ctx, a <= b); } else if (JS_IsText(left) && JS_IsText(right)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) <= 0); } else { goto disrupt; } } 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]; if (JS_VALUE_IS_BOTH_INT(left, right)) { frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) > JS_VALUE_GET_INT(right)); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewBool(ctx, a > b); } else if (JS_IsText(left) && JS_IsText(right)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) > 0); } else { goto disrupt; } } 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]; if (JS_VALUE_IS_BOTH_INT(left, right)) { frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) >= JS_VALUE_GET_INT(right)); } else if (JS_IsNumber(left) && JS_IsNumber(right)) { double a, b; JS_ToFloat64(ctx, &a, left); JS_ToFloat64(ctx, &b, right); frame->slots[dest] = JS_NewBool(ctx, a >= b); } else if (JS_IsText(left) && JS_IsText(right)) { frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) >= 0); } else { goto disrupt; } } /* ---- in operator ---- */ else if (strcmp(op, "in") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; int ret = JS_HasPropertyKey(ctx, right, left); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (ret < 0) { goto disrupt; } frame->slots[dest] = JS_NewBool(ctx, ret); } /* ---- Sensory (type checks) ---- */ 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; } else if (strcmp(op, "blob?") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_IsBlob(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "character?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; frame->slots[dest] = (JS_IsText(v) && js_string_value_len(v) == 1) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "data?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; frame->slots[dest] = (JS_IsRecord(v) || JS_IsArray(v)) ? JS_TRUE : JS_FALSE; } else if (strcmp(op, "digit?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (JS_IsText(v) && js_string_value_len(v) == 1) { uint32_t c = js_string_value_get(v, 0); frame->slots[dest] = (c >= '0' && c <= '9') ? JS_TRUE : JS_FALSE; } else { frame->slots[dest] = JS_FALSE; } } else if (strcmp(op, "fit?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (JS_IsInt(v)) { frame->slots[dest] = JS_TRUE; } else if (JS_IsNumber(v)) { double d; JS_ToFloat64(ctx, &d, v); frame->slots[dest] = (d == (double)(int32_t)d) ? JS_TRUE : JS_FALSE; } else { frame->slots[dest] = JS_FALSE; } } else if (strcmp(op, "letter?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (JS_IsText(v) && js_string_value_len(v) == 1) { uint32_t c = js_string_value_get(v, 0); frame->slots[dest] = ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? JS_TRUE : JS_FALSE; } else { frame->slots[dest] = JS_FALSE; } } else if (strcmp(op, "pattern?") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = JS_FALSE; /* TODO: pattern type check */ } else if (strcmp(op, "stone?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (JS_IsPtr(v)) { objhdr_t hdr = *(objhdr_t *)JS_VALUE_GET_PTR(v); frame->slots[dest] = objhdr_s(hdr) ? JS_TRUE : JS_FALSE; } else { /* Primitives are immutable */ frame->slots[dest] = JS_TRUE; } } else if (strcmp(op, "upper?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (JS_IsText(v) && js_string_value_len(v) == 1) { uint32_t c = js_string_value_get(v, 0); frame->slots[dest] = (c >= 'A' && c <= 'Z') ? JS_TRUE : JS_FALSE; } else { frame->slots[dest] = JS_FALSE; } } else if (strcmp(op, "whitespace?") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (JS_IsText(v) && js_string_value_len(v) == 1) { uint32_t c = js_string_value_get(v, 0); frame->slots[dest] = (c == ' ' || c == '\t' || c == '\n' || c == '\r') ? JS_TRUE : JS_FALSE; } else { frame->slots[dest] = 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 left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; } int32_t ia, ib; JS_ToInt32(ctx, &ia, left); JS_ToInt32(ctx, &ib, right); frame->slots[dest] = JS_NewInt32(ctx, ia & ib); } else if (strcmp(op, "bitor") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; } int32_t ia, ib; JS_ToInt32(ctx, &ia, left); JS_ToInt32(ctx, &ib, right); frame->slots[dest] = JS_NewInt32(ctx, ia | ib); } else if (strcmp(op, "bitxor") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; } int32_t ia, ib; JS_ToInt32(ctx, &ia, left); JS_ToInt32(ctx, &ib, right); frame->slots[dest] = JS_NewInt32(ctx, ia ^ ib); } else if (strcmp(op, "shl") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; } int32_t ia, ib; JS_ToInt32(ctx, &ia, left); JS_ToInt32(ctx, &ib, right); frame->slots[dest] = JS_NewInt32(ctx, ia << (ib & 31)); } else if (strcmp(op, "shr") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; } int32_t ia, ib; JS_ToInt32(ctx, &ia, left); JS_ToInt32(ctx, &ib, right); frame->slots[dest] = JS_NewInt32(ctx, ia >> (ib & 31)); } else if (strcmp(op, "ushr") == 0) { int dest = (int)a1->valuedouble; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; } int32_t ia, ib; JS_ToInt32(ctx, &ia, left); JS_ToInt32(ctx, &ib, right); frame->slots[dest] = JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); } /* ---- 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); } else if (strcmp(op, "jump_empty") == 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, "wary_true") == 0) { int slot = (int)a1->valuedouble; const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL; JSValue v = frame->slots[slot]; if (v == JS_TRUE) { if (label) pc = mcode_resolve_label(code, label); } else if (v != JS_FALSE) { goto disrupt; } } else if (strcmp(op, "wary_false") == 0) { int slot = (int)a1->valuedouble; const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL; JSValue v = frame->slots[slot]; if (v == JS_FALSE) { if (label) pc = mcode_resolve_label(code, label); } else if (v != JS_TRUE) { goto disrupt; } } /* ---- Property/element access (unified) ---- */ else if (strcmp(op, "load") == 0) { int dest = (int)a1->valuedouble; int obj_reg = (int)a2->valuedouble; JSValue obj = frame->slots[obj_reg]; 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; } } JSValue val; if (cJSON_IsString(a3)) { JSValue key = JS_NewString(ctx, a3->valuestring); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); obj = frame->slots[obj_reg]; val = JS_GetProperty(ctx, obj, key); } else { JSValue idx = frame->slots[(int)a3->valuedouble]; if (JS_IsInt(idx)) val = JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(idx)); else val = JS_GetProperty(ctx, obj, idx); } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(val)) goto disrupt; frame->slots[dest] = val; } else if (strcmp(op, "store") == 0) { int obj_reg = (int)a1->valuedouble; int val_reg = (int)a2->valuedouble; JSValue obj = frame->slots[obj_reg]; JSValue val = frame->slots[val_reg]; if (JS_IsFunction(obj)) { JS_ThrowTypeError(ctx, "cannot set property of function"); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto disrupt; } if (cJSON_IsString(a3)) { JSValue key = JS_NewString(ctx, a3->valuestring); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); obj = frame->slots[obj_reg]; val = frame->slots[val_reg]; int ret = JS_SetProperty(ctx, obj, key, val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (ret < 0) goto disrupt; } else { JSValue idx = frame->slots[(int)a3->valuedouble]; int ret; if (JS_IsInt(idx)) { JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(idx), val); ret = JS_IsException(r) ? -1 : 0; } else if (JS_IsArray(obj)) { JS_ThrowTypeError(ctx, "array index must be a number"); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto disrupt; } else if (JS_IsRecord(obj) && !JS_IsText(idx) && !JS_IsRecord(idx)) { JS_ThrowTypeError(ctx, "object key must be a string or object"); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto disrupt; } else { ret = JS_SetProperty(ctx, obj, idx, val); } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (ret < 0) goto disrupt; } } else if (strcmp(op, "delete") == 0) { int dest = (int)a1->valuedouble; int obj_reg = (int)a2->valuedouble; JSValue obj = frame->slots[obj_reg]; JSValue key; if (cJSON_IsString(a3)) { key = JS_NewString(ctx, a3->valuestring); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); obj = frame->slots[obj_reg]; } else { key = frame->slots[(int)a3->valuedouble]; } int ret = JS_DeleteProperty(ctx, obj, key); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (ret < 0) goto disrupt; 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]; } } /* ---- 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)) { goto disrupt; } JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val); int nr_slots; if (fn->kind == JS_FUNC_KIND_MCODE) { nr_slots = fn->u.mcode.code->nr_slots; } else { nr_slots = call_argc + 2; } JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots); if (!new_frame) { goto disrupt; } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); func_val = frame->slots[(int)a2->valuedouble]; new_frame->function = func_val; frame->slots[frame_reg] = JS_MKPTR(new_frame); } else if (strcmp(op, "setarg") == 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]; 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 { /* C or bytecode function — collect args on C stack */ int nr_slots = (int)objhdr_cap56(new_frame->hdr); int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0; JSValue args[c_argc > 0 ? c_argc : 1]; for (int i = 0; i < c_argc; i++) args[i] = new_frame->slots[i + 1]; ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue c_result = JS_Call(ctx, new_frame->function, new_frame->slots[0], c_argc, args); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); ctx->reg_current_frame = JS_NULL; if (JS_IsException(c_result)) { goto disrupt; } frame->slots[ret_reg] = c_result; } } /* ---- Method call (handles function proxies) ---- */ else if (strcmp(op, "callmethod") == 0) { /* ["callmethod", dest, obj_reg, "method_name", arg0_reg, arg1_reg, ...] */ int dest = (int)a1->valuedouble; int obj_reg = (int)a2->valuedouble; JSValue obj = frame->slots[obj_reg]; const char *method_name = a3->valuestring; /* Count arg registers (items after a3, minus trailing line/col) */ int nargs = 0; for (cJSON *p = a3->next; p; p = p->next) nargs++; nargs -= 2; /* subtract line and col metadata */ if (nargs < 0) nargs = 0; if (JS_IsFunction(obj)) { /* Proxy call: obj(name, [args...]) */ JSGCRef key_gc; JS_PushGCRef(ctx, &key_gc); key_gc.val = JS_NewString(ctx, method_name); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue arr = JS_NewArray(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_gc); goto disrupt; } frame->slots[dest] = arr; /* protect from GC */ cJSON *p = a3->next; for (int i = 0; i < nargs; i++, p = p->next) { if (cJSON_IsString(p)) break; /* hit line/col */ int areg = (int)p->valuedouble; JS_SetPropertyNumber(ctx, frame->slots[dest], i, frame->slots[areg]); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); } JSValue call_args[2] = { key_gc.val, frame->slots[dest] }; ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, call_args, 0); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); ctx->reg_current_frame = JS_NULL; JS_PopGCRef(ctx, &key_gc); if (JS_IsException(ret)) goto disrupt; frame->slots[dest] = ret; } else { /* Record method call: get property, call with this=obj */ JSValue key = JS_NewString(ctx, method_name); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue method = JS_GetProperty(ctx, frame->slots[obj_reg], key); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(method)) goto disrupt; if (!JS_IsFunction(method)) { frame->slots[dest] = JS_NULL; } else { JSFunction *fn = JS_VALUE_GET_FUNCTION(method); if (fn->kind == JS_FUNC_KIND_MCODE) { /* mcode function — set up frame and jump */ frame->slots[dest] = method; /* protect from GC */ JSFrameRegister *new_frame = alloc_frame_register(ctx, fn->u.mcode.code->nr_slots); if (!new_frame) { frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto disrupt; } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); method = frame->slots[dest]; /* re-read after GC */ fn = JS_VALUE_GET_FUNCTION(method); new_frame->function = method; new_frame->slots[0] = frame->slots[obj_reg]; /* this */ cJSON *p = a3->next; for (int i = 0; i < nargs && i < fn->u.mcode.code->nr_slots - 1; i++, p = p->next) { if (cJSON_IsString(p)) break; new_frame->slots[1 + i] = frame->slots[(int)p->valuedouble]; } frame->address = JS_NewInt32(ctx, (pc << 16) | dest); new_frame->caller = JS_MKPTR(frame); frame = new_frame; frame_ref.val = JS_MKPTR(frame); code = fn->u.mcode.code; pc = 0; } else { /* C or bytecode function */ JSValue args[nargs > 0 ? nargs : 1]; cJSON *p = a3->next; for (int i = 0; i < nargs; i++, p = p->next) { if (cJSON_IsString(p)) break; args[i] = frame->slots[(int)p->valuedouble]; } ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, args); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); ctx->reg_current_frame = JS_NULL; if (JS_IsException(ret)) goto disrupt; frame->slots[dest] = ret; } } } } else if (strcmp(op, "callmethod_dyn") == 0) { /* ["callmethod_dyn", dest, obj_reg, key_reg, arg0_reg, ...] */ int dest = (int)a1->valuedouble; int obj_reg = (int)a2->valuedouble; int key_reg = (int)a3->valuedouble; JSValue obj = frame->slots[obj_reg]; JSValue key = frame->slots[key_reg]; /* Count arg registers (items after a3, minus trailing line/col) */ int nargs = 0; for (cJSON *p = a3->next; p; p = p->next) nargs++; nargs -= 2; if (nargs < 0) nargs = 0; if (JS_IsFunction(obj) && JS_VALUE_IS_TEXT(key) && JS_VALUE_GET_FUNCTION(obj)->length == 2) { /* Proxy call (arity-2 functions only): obj(key, [args...]) key lives in frame->slots[key_reg], which is GC-safe */ JSValue arr = JS_NewArray(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(arr)) goto disrupt; frame->slots[dest] = arr; /* protect from GC */ cJSON *p = a3->next; for (int i = 0; i < nargs; i++, p = p->next) { int areg = (int)p->valuedouble; JS_SetPropertyNumber(ctx, frame->slots[dest], i, frame->slots[areg]); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); } JSValue call_args[2] = { frame->slots[key_reg], frame->slots[dest] }; ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], 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)) goto disrupt; frame->slots[dest] = ret; } else if (JS_IsFunction(obj)) { /* Non-proxy function: bracket access not allowed */ JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function"); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto disrupt; } else { /* Record method call: get property, call with this=obj */ JSValue method = JS_GetProperty(ctx, frame->slots[obj_reg], key); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(method)) goto disrupt; if (!JS_IsFunction(method)) { frame->slots[dest] = JS_NULL; } else { JSFunction *fn = JS_VALUE_GET_FUNCTION(method); if (fn->kind == JS_FUNC_KIND_MCODE) { frame->slots[dest] = method; /* protect method from GC */ JSFrameRegister *new_frame = alloc_frame_register(ctx, fn->u.mcode.code->nr_slots); if (!new_frame) { frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto disrupt; } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); method = frame->slots[dest]; /* re-read after GC */ fn = JS_VALUE_GET_FUNCTION(method); new_frame->function = method; new_frame->slots[0] = frame->slots[obj_reg]; /* this */ cJSON *p = a3->next; for (int i = 0; i < nargs && i < fn->u.mcode.code->nr_slots - 1; i++, p = p->next) { new_frame->slots[1 + i] = frame->slots[(int)p->valuedouble]; } frame->address = JS_NewInt32(ctx, (pc << 16) | dest); new_frame->caller = JS_MKPTR(frame); frame = new_frame; frame_ref.val = JS_MKPTR(frame); code = fn->u.mcode.code; pc = 0; } else { JSValue args[nargs > 0 ? nargs : 1]; cJSON *p = a3->next; for (int i = 0; i < nargs; i++, p = p->next) { args[i] = frame->slots[(int)p->valuedouble]; } ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, args); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); ctx->reg_current_frame = JS_NULL; if (JS_IsException(ret)) goto disrupt; frame->slots[dest] = ret; } } } } /* ---- Tail calls ---- */ else if (strcmp(op, "goframe") == 0) { int frame_reg = (int)a1->valuedouble; int func_reg = (int)a2->valuedouble; int call_argc = a3 ? (int)a3->valuedouble : 0; JSValue func_val = frame->slots[func_reg]; if (!JS_IsFunction(func_val)) { goto disrupt; } JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val); int nr_slots; if (fn->kind == JS_FUNC_KIND_MCODE) { nr_slots = fn->u.mcode.code->nr_slots; } else { nr_slots = call_argc + 2; } JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots); if (!new_frame) { goto disrupt; } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); func_val = frame->slots[func_reg]; new_frame->function = func_val; frame->slots[frame_reg] = JS_MKPTR(new_frame); } else if (strcmp(op, "goinvoke") == 0) { int frame_reg = (int)a1->valuedouble; JSValue target = frame->slots[frame_reg]; if (JS_IsFunction(target)) { result = JS_ThrowInternalError(ctx, "C function tail call not supported in MCODE"); goto disrupt; } 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) { goto disrupt; } /* 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_value") == 0) { int dest = (int)a1->valuedouble; frame->slots[dest] = result; } /* ---- Apply ---- */ else if (strcmp(op, "apply") == 0) { int func_slot = (int)a1->valuedouble; int arr_slot = (int)a2->valuedouble; if (!JS_IsFunction(frame->slots[func_slot]) || !JS_IsArray(frame->slots[arr_slot])) { goto disrupt; } JSValue len_val = JS_GetProperty(ctx, frame->slots[arr_slot], JS_KEY_length); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); int len = JS_IsNumber(len_val) ? (int)JS_VALUE_GET_INT(len_val) : 0; if (len > 256) len = 256; JSGCRef arg_refs[len > 0 ? len : 1]; for (int i = 0; i < len; i++) { JS_PushGCRef(ctx, &arg_refs[i]); arg_refs[i].val = JS_GetPropertyNumber(ctx, frame->slots[arr_slot], i); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); } JSValue args[len > 0 ? len : 1]; for (int i = 0; i < len; i++) args[i] = arg_refs[i].val; ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; result = JS_Call(ctx, frame->slots[func_slot], JS_NULL, len, args); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); for (int i = len - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_refs[i]); if (JS_IsException(result)) { goto disrupt; } } /* ---- Object/Array creation ---- */ else if (strcmp(op, "record") == 0) { int dest = (int)a1->valuedouble; JSValue rec = JS_NewObject(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(rec)) { goto disrupt; } frame->slots[dest] = rec; } else if (strcmp(op, "array") == 0) { int dest = (int)a1->valuedouble; int nr_elems = a2 ? (int)a2->valuedouble : 0; JSValue arr = JS_NewArray(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(arr)) { goto disrupt; } frame->slots[dest] = arr; for (int i = 0; i < nr_elems; i++) { cJSON *elem = cJSON_GetArrayItem(instr, 3 + i); if (elem) { int elem_slot = (int)elem->valuedouble; JS_SetPropertyNumber(ctx, frame->slots[dest], i, frame->slots[elem_slot]); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); } } } else if (strcmp(op, "function") == 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; } } /* ---- Blob ---- */ else if (strcmp(op, "blob") == 0) { int dest = (int)a1->valuedouble; int nr_bits = a2 ? (int)a2->valuedouble : 0; blob *bd = blob_new((size_t)(nr_bits < 0 ? 0 : nr_bits)); if (!bd) { goto disrupt; } JSValue bv = js_new_blob(ctx, bd); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(bv)) { goto disrupt; } frame->slots[dest] = bv; } /* ---- Pretext ---- */ else if (strcmp(op, "pretext") == 0) { int dest = (int)a1->valuedouble; int nr_chars = a2 ? (int)a2->valuedouble : 16; JSText *s = pretext_init(ctx, nr_chars); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (!s) { goto disrupt; } frame->slots[dest] = JS_MKPTR(s); } /* ---- Append (to pretext) ---- */ else if (strcmp(op, "append") == 0) { int pt_slot = (int)a1->valuedouble; int right_slot = (int)a2->valuedouble; if (!JS_IsText(frame->slots[pt_slot]) || !JS_IsText(frame->slots[right_slot])) { goto disrupt; } JSText *s = JS_VALUE_GET_PTR(frame->slots[pt_slot]); s = pretext_concat_value(ctx, s, frame->slots[right_slot]); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (!s) { goto disrupt; } frame->slots[pt_slot] = JS_MKPTR(s); } /* ---- Stone ---- */ else if (strcmp(op, "stone") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; JSValue stoned = JS_Stone(ctx, v); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = stoned; } /* ---- Regexp literal ---- */ else if (strcmp(op, "regexp") == 0) { int dest = (int)a1->valuedouble; const char *pattern = a2 ? a2->valuestring : ""; cJSON *a3 = cJSON_GetArrayItem(instr, 3); const char *flags_str = a3 ? a3->valuestring : ""; if (!pattern) pattern = ""; if (!flags_str) flags_str = ""; JSGCRef pat_gc, flags_gc; JS_PushGCRef(ctx, &pat_gc); pat_gc.val = JS_NewString(ctx, pattern); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JS_PushGCRef(ctx, &flags_gc); flags_gc.val = *flags_str ? JS_NewString(ctx, flags_str) : JS_NULL; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue bc = js_compile_regexp(ctx, pat_gc.val, flags_gc.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(bc)) { JS_PopGCRef(ctx, &flags_gc); JS_PopGCRef(ctx, &pat_gc); goto disrupt; } JSValue re_obj = js_regexp_constructor_internal(ctx, pat_gc.val, bc); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JS_PopGCRef(ctx, &flags_gc); JS_PopGCRef(ctx, &pat_gc); if (JS_IsException(re_obj)) { goto disrupt; } frame->slots[dest] = re_obj; } /* ---- Push (append to array) ---- */ else if (strcmp(op, "push") == 0) { int arr_slot = (int)a1->valuedouble; int val_slot = (int)a2->valuedouble; if (!JS_IsArray(frame->slots[arr_slot])) { goto disrupt; } JSGCRef arr_gc; JS_PushGCRef(ctx, &arr_gc); arr_gc.val = frame->slots[arr_slot]; JSGCRef val_gc; JS_PushGCRef(ctx, &val_gc); val_gc.val = frame->slots[val_slot]; int rc = JS_ArrayPush(ctx, &arr_gc.val, val_gc.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JS_PopGCRef(ctx, &val_gc); JS_PopGCRef(ctx, &arr_gc); if (rc < 0) goto disrupt; frame->slots[arr_slot] = arr_gc.val; } /* ---- Pop (remove last from array) ---- */ else if (strcmp(op, "pop") == 0) { int dest = (int)a1->valuedouble; JSValue arr = frame->slots[(int)a2->valuedouble]; if (!JS_IsArray(arr)) { goto disrupt; } JSValue popped = JS_ArrayPop(ctx, arr); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = popped; } /* ---- Disruption ---- */ else if (strcmp(op, "disrupt") == 0) { goto disrupt; } /* ---- Unknown opcode ---- */ else { result = JS_ThrowInternalError(ctx, "unknown MCODE opcode: %s", op); goto done; } continue; disrupt: /* Search frame chain for a disruption handler. Use frame_pc to track each frame's execution point: - For the faulting frame, it's the current pc. - For unwound caller frames, read from frame->address. */ frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); /* re-chase after GC */ { uint32_t frame_pc = pc; for (;;) { JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); JSMCode *fn_code = fn->u.mcode.code; /* Only enter handler if we're not already inside it */ if (fn_code->disruption_pc > 0 && frame_pc < fn_code->disruption_pc) { code = fn_code; pc = fn_code->disruption_pc; break; } if (JS_IsNull(frame->caller)) { fprintf(stderr, "unhandled disruption\n"); result = JS_Throw(ctx, JS_NULL); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); goto done; } /* Unwind one frame — read caller's saved pc from its address field */ JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); frame->caller = JS_NULL; frame = caller; frame_ref.val = JS_MKPTR(frame); frame_pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); } } } done: if (JS_IsException(result)) { ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; } JS_DeleteGCRef(ctx, &frame_ref); return result; } JSValue JS_CallMcodeTree(JSContext *ctx, cJSON *root) { if (!root) return JS_ThrowSyntaxError(ctx, "invalid MCODE tree"); cJSON *main_obj = cJSON_GetObjectItemCaseSensitive(root, "main"); if (!main_obj) { cJSON_Delete(root); return JS_ThrowSyntaxError(ctx, "MCODE tree missing 'main' section"); } cJSON *functions = cJSON_GetObjectItemCaseSensitive(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 tree alive — instrs point into it */ /* Execute with global_obj as this */ JSValue result = mcode_exec(ctx, code, ctx->global_obj, 0, NULL, JS_NULL); /* Clear frame ref before freeing mcode — stack trace data is inside code */ ctx->reg_current_frame = JS_NULL; jsmcode_free(code); return result; } JSValue JS_CallMcodeTreeEnv(JSContext *ctx, cJSON *root, JSValue env) { if (!root) return JS_ThrowSyntaxError(ctx, "invalid MCODE tree"); cJSON *main_obj = cJSON_GetObjectItemCaseSensitive(root, "main"); if (!main_obj) { cJSON_Delete(root); return JS_ThrowSyntaxError(ctx, "MCODE tree missing 'main' section"); } cJSON *functions = cJSON_GetObjectItemCaseSensitive(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 tree alive — instrs point into it */ /* Execute with global_obj as this, passing env as outer_frame */ JSValue result = mcode_exec(ctx, code, ctx->global_obj, 0, NULL, env); /* Clear frame ref before freeing mcode — stack trace data is inside code */ ctx->reg_current_frame = JS_NULL; jsmcode_free(code); return result; } JSValue JS_CallMcode(JSContext *ctx, const char *mcode_json) { cJSON *root = cJSON_Parse(mcode_json); if (!root) return JS_ThrowSyntaxError(ctx, "invalid MCODE JSON"); return JS_CallMcodeTree(ctx, root); }