mcode and mcode interpreter

This commit is contained in:
2026-02-06 03:31:31 -06:00
parent c814c0e1d8
commit ff8c68d01c
3 changed files with 1019 additions and 0 deletions

View File

@@ -575,6 +575,83 @@ int cell_init(int argc, char **argv)
return mcode_json ? 0 : 1; return mcode_json ? 0 : 1;
} }
/* Check for --run-mcode flag to execute via MCODE interpreter */
if (argc >= 3 && strcmp(argv[1], "--run-mcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; }
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) { fclose(f); printf("Failed to allocate memory\n"); return 1; }
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) { printf("Failed to create JS runtime\n"); free(allocated_script); return 1; }
/* Use a parser context (small heap) for AST + MCODE generation */
JSContext *parse_ctx = JS_NewContext(rt);
if (!parse_ctx) { printf("Failed to create JS context\n"); JS_FreeRuntime(rt); free(allocated_script); return 1; }
char *ast_json = JS_AST(parse_ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(parse_ctx); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
if (print_json_errors(ast_json)) {
free(ast_json); JS_FreeContext(parse_ctx); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
char *mcode_json = JS_Mcode(parse_ctx, ast_json);
free(ast_json);
JS_FreeContext(parse_ctx);
if (!mcode_json) {
printf("Failed to generate MCODE\n");
JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
if (print_json_errors(mcode_json)) {
free(mcode_json); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
/* Use a larger heap context for execution */
JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024);
if (!ctx) { printf("Failed to create execution context\n"); free(mcode_json); JS_FreeRuntime(rt); free(allocated_script); return 1; }
JSValue result = JS_CallMcode(ctx, mcode_json);
free(mcode_json);
if (JS_IsException(result)) {
JSValue exc = JS_GetException(ctx);
const char *str = JS_ToCString(ctx, exc);
if (str) { printf("Exception: %s\n", str); JS_FreeCString(ctx, str); }
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); }
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return JS_IsException(result) ? 1 : 0;
}
/* Check for --mach flag to dump MACH bytecode */ /* Check for --mach flag to dump MACH bytecode */
if (argc >= 3 && strcmp(argv[1], "--mach") == 0) { if (argc >= 3 && strcmp(argv[1], "--mach") == 0) {
const char *script_or_file = argv[2]; const char *script_or_file = argv[2];

View File

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

View File

@@ -1239,6 +1239,10 @@ void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env);
Returns result of execution, or JS_EXCEPTION on error. */ Returns result of execution, or JS_EXCEPTION on error. */
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env); JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Parse and execute MCODE JSON directly via the MCODE interpreter.
Returns result of execution, or JS_EXCEPTION on error. */
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
/* Integrate a CellModule with an environment and execute. /* Integrate a CellModule with an environment and execute.
Returns callable function value, or JS_EXCEPTION on error. */ Returns callable function value, or JS_EXCEPTION on error. */
JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env); JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);