Merge branch 'mach' into mqbe

This commit is contained in:
2026-02-09 18:36:47 -06:00
10 changed files with 2079 additions and 152 deletions

View File

@@ -295,7 +295,7 @@ static void print_usage(const char *prog)
printf("Options:\n");
printf(" --ast <code|file> Output AST as JSON\n");
printf(" --tokenize <code|file> Output token array as JSON\n");
printf(" --mcode <code|file> Output MCODE IR as JSON\n");
printf(" --mcode <script> [args] Run through mcode compilation pipeline\n");
printf(" --run-mcode <code|file> Compile and run through MCODE interpreter\n");
printf(" --mach <code|file> Output MACH bytecode\n");
printf(" --mach-run <code|file> Compile and run through MACH VM\n");
@@ -420,66 +420,6 @@ int cell_init(int argc, char **argv)
}
}
/* Check for --mcode flag to output MCODE JSON IR */
if (argc >= 3 && strcmp(argv[1], "--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 for script\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;
}
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
free(allocated_script);
return 1;
}
cJSON *mcode = JS_McodeTree(ast);
cJSON_Delete(ast);
if (!mcode) {
printf("Failed to generate MCODE\n");
free(allocated_script);
return 1;
}
char *pretty = cJSON_Print(mcode);
cJSON_Delete(mcode);
printf("%s\n", pretty);
free(pretty);
free(allocated_script);
return 0;
}
/* Check for --run-mcode flag to execute via MCODE interpreter */
if (argc >= 3 && strcmp(argv[1], "--run-mcode") == 0) {
const char *filename = argv[2];
@@ -612,59 +552,81 @@ int cell_init(int argc, char **argv)
/* Check for --mach-run flag to compile and run through MACH VM */
if (argc >= 3 && strcmp(argv[1], "--mach-run") == 0) {
if (!find_cell_shop()) return 1;
const char *script_name = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = script_name;
size_t boot_size;
char *boot_data = load_core_file("internal/bootstrap.cm", &boot_size);
if (!boot_data) {
printf("ERROR: Could not load internal/bootstrap.cm from %s\n", core_path);
struct stat st;
if (stat(script_name, &st) == 0 && S_ISREG(st.st_mode)) {
/* Exact name found */
} else {
/* Try .ce then .cm extension */
static char pathbuf[4096];
snprintf(pathbuf, sizeof(pathbuf), "%s.ce", script_name);
if (stat(pathbuf, &st) == 0 && S_ISREG(st.st_mode)) {
script_name = pathbuf;
filename = pathbuf;
} else {
snprintf(pathbuf, sizeof(pathbuf), "%s.cm", script_name);
if (stat(pathbuf, &st) == 0 && S_ISREG(st.st_mode)) {
script_name = pathbuf;
filename = pathbuf;
} else {
printf("Failed to find file: %s\n", argv[2]);
return 1;
}
}
}
FILE *f = fopen(script_name, "r");
if (!f) {
printf("Failed to open file: %s\n", script_name);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\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;
cJSON *ast = JS_ASTTree(script, read_size, filename);
free(allocated_script);
if (!ast) {
printf("Failed to parse %s\n", filename);
return 1;
}
cJSON *boot_ast = JS_ASTTree(boot_data, boot_size, "internal/bootstrap.cm");
free(boot_data);
if (!boot_ast) {
printf("Failed to parse internal/bootstrap.cm\n");
return 1;
}
if (print_tree_errors(boot_ast)) {
cJSON_Delete(boot_ast);
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
return 1;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
cJSON_Delete(boot_ast);
cJSON_Delete(ast);
return 1;
}
JSContext *ctx = JS_NewContextWithHeapSize(rt, 16 * 1024 * 1024);
if (!ctx) {
printf("Failed to create JS context\n");
cJSON_Delete(boot_ast); JS_FreeRuntime(rt);
cJSON_Delete(ast); JS_FreeRuntime(rt);
return 1;
}
JS_FreeValue(ctx, js_blob_use(ctx));
JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JSValue args_arr = JS_NewArray(ctx);
for (int i = 2; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
}
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
hidden_env = JS_Stone(ctx, hidden_env);
JSValue result = JS_RunMachTree(ctx, boot_ast, hidden_env);
cJSON_Delete(boot_ast);
JSValue result = JS_RunMachTree(ctx, ast, JS_NULL);
cJSON_Delete(ast);
int exit_code = 0;
if (JS_IsException(result)) {
/* Error already printed to stderr by JS_Throw* */
JS_GetException(ctx);
exit_code = 1;
} else if (!JS_IsNull(result)) {
@@ -681,6 +643,13 @@ int cell_init(int argc, char **argv)
}
/* Default: run script through mach-run bootstrap pipeline */
int use_mcode = 0;
int arg_start = 1;
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) {
use_mcode = 1;
arg_start = 2;
}
if (!find_cell_shop()) return 1;
size_t boot_size;
@@ -720,8 +689,9 @@ int cell_init(int argc, char **argv)
JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JS_SetPropertyStr(ctx, hidden_env, "use_mcode", JS_NewBool(ctx, use_mcode));
JSValue args_arr = JS_NewArray(ctx);
for (int i = 1; i < argc; i++) {
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
}

View File

@@ -1707,20 +1707,24 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
for (uint32_t i = 0; i < code->instr_count; i++) {
MachInstr32 instr = code->instructions[i];
if (MACH_GET_OP(instr) != MACH_GETNAME) continue;
int a = MACH_GET_A(instr);
int bx = MACH_GET_Bx(instr);
int in_env = 0;
if (!JS_IsNull(env) && (uint32_t)bx < code->cpool_count) {
JSValue val = JS_GetProperty(ctx, env, code->cpool[bx]);
if (!JS_IsNull(env_ref.val) && (uint32_t)bx < code->cpool_count) {
JSValue val = JS_GetProperty(ctx, env_ref.val, code->cpool[bx]);
in_env = !JS_IsNull(val) && !JS_IsException(val);
}
code->instructions[i] = MACH_ABx(in_env ? MACH_GETENV : MACH_GETINTRINSIC, a, bx);
}
for (uint32_t i = 0; i < code->func_count; i++)
if (code->functions[i]) mach_link_code(ctx, code->functions[i], env);
if (code->functions[i]) mach_link_code(ctx, code->functions[i], env_ref.val);
JS_PopGCRef(ctx, &env_ref);
}
/* ---- Top-level compiler ---- */
@@ -1832,6 +1836,11 @@ void JS_FreeMachCode(MachCode *mc) {
/* Load a MachCode into a JSCodeRegister (materializes JSValues, needs ctx) */
JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
/* Protect env from GC — materialize/link calls can trigger collection */
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister));
code->arity = mc->arity;
code->nr_close_slots = mc->nr_close_slots;
@@ -1849,7 +1858,7 @@ JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
if (mc->func_count > 0) {
code->functions = js_malloc_rt(mc->func_count * sizeof(JSCodeRegister *));
for (uint32_t i = 0; i < mc->func_count; i++)
code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env);
code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env_ref.val);
} else {
code->functions = NULL;
}
@@ -1866,8 +1875,9 @@ JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
code->disruption_pc = mc->disruption_pc;
/* Link: resolve GETNAME to GETENV/GETINTRINSIC */
mach_link_code(ctx, code, env);
mach_link_code(ctx, code, env_ref.val);
JS_PopGCRef(ctx, &env_ref);
return code;
}
@@ -2229,6 +2239,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
returning from a called register function can read code/env from frame */
JSValue top_fn = js_new_register_function(ctx, code, env_gc.val, of_gc.val);
JS_PopGCRef(ctx, &of_gc);
env = env_gc.val; /* refresh — GC may have moved env during allocation */
JS_PopGCRef(ctx, &env_gc);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->function = top_fn;
@@ -2278,6 +2289,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int b = MACH_GET_B(instr);
int c = MACH_GET_C(instr);
switch (op) {
case MACH_NOP:
break;
@@ -2499,9 +2511,11 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
}
case MACH_GETENV: {
/* Read env fresh from frame->function — C local env can go stale after GC */
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_GetProperty(ctx, env, key);
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue val = JS_GetProperty(ctx, cur_env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = val;
break;
@@ -2512,8 +2526,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_NULL;
if (!JS_IsNull(env)) {
val = JS_GetProperty(ctx, env, key);
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
if (!JS_IsNull(cur_env)) {
val = JS_GetProperty(ctx, cur_env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
if (JS_IsNull(val) || JS_IsException(val)) {
@@ -2661,8 +2676,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JS_PushGCRef(ctx, &key_ref);
key_ref.val = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c];
if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val)) {
/* Proxy call: obj(name, [args...]) */
if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val) &&
JS_VALUE_GET_FUNCTION(frame->slots[base])->length == 2) {
/* Proxy call (arity-2 functions only): obj(name, [args...]) */
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
@@ -2816,7 +2832,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int bx = MACH_GET_Bx(instr);
if ((uint32_t)bx < code->func_count) {
JSCodeRegister *fn_code = code->functions[bx];
JSValue fn_val = js_new_register_function(ctx, fn_code, env, frame_ref.val);
/* Read env fresh from frame->function — C local can be stale */
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue fn_val = js_new_register_function(ctx, fn_code, cur_env, frame_ref.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = fn_val;
} else {
@@ -3251,8 +3269,14 @@ JSValue JS_RunMachTree(JSContext *ctx, cJSON *ast, JSValue env) {
return JS_ThrowSyntaxError(ctx, "failed to compile AST to MACH bytecode");
}
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env);
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL);
/* Protect env from GC — JS_LoadMachCode allocates on GC heap */
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL);
JS_PopGCRef(ctx, &env_ref);
return result;
}

View File

@@ -3061,8 +3061,9 @@ JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
nargs -= 2;
if (nargs < 0) nargs = 0;
if (JS_IsFunction(obj) && JS_VALUE_IS_TEXT(key)) {
/* Proxy call: obj(key, [args...]) */
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...]) */
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = key; /* protect key on value stack */
ctx->value_stack_top = vs_base + 1; /* protect key from GC */
@@ -3087,7 +3088,8 @@ JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
} else if (JS_IsFunction(obj)) {
JS_ThrowTypeError(ctx, "cannot use non-text bracket notation on function");
/* 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 {

View File

@@ -2515,8 +2515,16 @@ static int js_intrinsic_array_push (JSContext *ctx, JSValue *arr_ptr, JSValue va
}
if (arr->len >= js_array_cap (arr)) {
if (js_array_grow (ctx, arr_ptr, arr->len + 1) < 0)
/* Root val across js_array_grow which can trigger GC */
JSGCRef val_ref;
JS_PushGCRef (ctx, &val_ref);
val_ref.val = val;
if (js_array_grow (ctx, arr_ptr, arr->len + 1) < 0) {
JS_PopGCRef (ctx, &val_ref);
return -1;
}
val = val_ref.val;
JS_PopGCRef (ctx, &val_ref);
arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */
}
@@ -5974,7 +5982,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
int64_t i, len;
int ret;
BOOL has_content;
JSGCRef val_ref, indent_ref, indent1_ref, sep_ref, sep1_ref, tab_ref, prop_ref;
JSGCRef val_ref, indent_ref, indent1_ref, sep_ref, sep1_ref, tab_ref, prop_ref, v_ref;
/* Root all values that can be heap pointers and survive across GC points */
JS_PushGCRef (ctx, &val_ref);
@@ -5984,6 +5992,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
JS_PushGCRef (ctx, &sep1_ref);
JS_PushGCRef (ctx, &tab_ref);
JS_PushGCRef (ctx, &prop_ref);
JS_PushGCRef (ctx, &v_ref);
val_ref.val = val;
indent_ref.val = indent;
@@ -5992,6 +6001,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
sep1_ref.val = JS_NULL;
tab_ref.val = JS_NULL;
prop_ref.val = JS_NULL;
v_ref.val = JS_NULL;
if (js_check_stack_overflow (ctx, 0)) {
JS_ThrowStackOverflow (ctx);
@@ -6038,10 +6048,11 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
JSC_B_CONCAT (jsc, sep_ref.val);
v = JS_GetPropertyInt64 (ctx, val_ref.val, i);
if (JS_IsException (v)) goto exception;
v_ref.val = v; /* root v — JS_ToString below can trigger GC */
/* XXX: could do this string conversion only when needed */
prop_ref.val = JS_ToString (ctx, JS_NewInt64 (ctx, i));
if (JS_IsException (prop_ref.val)) goto exception;
v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val);
v = js_json_check (ctx, jsc, val_ref.val, v_ref.val, prop_ref.val);
prop_ref.val = JS_NULL;
if (JS_IsException (v)) goto exception;
if (JS_IsNull (v)) v = JS_NULL;
@@ -6069,6 +6080,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val);
if (JS_IsException (v)) goto exception;
if (!JS_IsNull (v)) {
v_ref.val = v; /* root v — allocations below can trigger GC */
if (has_content) {
JSC_B_PUTC (jsc, ',');
}
@@ -6080,7 +6092,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
JSC_B_CONCAT (jsc, prop_ref.val);
JSC_B_PUTC (jsc, ':');
JSC_B_CONCAT (jsc, sep1_ref.val);
if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception;
if (js_json_to_str (ctx, jsc, val_ref.val, v_ref.val, indent1_ref.val)) goto exception;
has_content = TRUE;
}
}
@@ -6116,6 +6128,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
}
done:
JS_PopGCRef (ctx, &v_ref);
JS_PopGCRef (ctx, &prop_ref);
JS_PopGCRef (ctx, &tab_ref);
JS_PopGCRef (ctx, &sep1_ref);
@@ -6126,6 +6139,7 @@ done:
return 0;
exception_ret:
JS_PopGCRef (ctx, &v_ref);
JS_PopGCRef (ctx, &prop_ref);
JS_PopGCRef (ctx, &tab_ref);
JS_PopGCRef (ctx, &sep1_ref);
@@ -6283,8 +6297,8 @@ static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSVal
return val;
}
/* Handle string */
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
/* Handle string (immediate ASCII or heap JSText) */
if (JS_IsText (val)) {
const char *str = JS_ToCString (ctx, val);
if (!str) return JS_EXCEPTION;
@@ -6925,7 +6939,7 @@ JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *
int tag = JS_VALUE_GET_TAG (arg);
/* Handle string - return first character */
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
if (JS_IsText (arg)) {
if (js_string_value_len (arg) == 0) return JS_NewString (ctx, "");
return js_sub_string_val (ctx, arg, 0, 1);
}
@@ -6978,7 +6992,7 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue
int tag = JS_VALUE_GET_TAG (arg);
/* Handle string / rope */
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
if (JS_IsText (arg)) {
JSValue str = JS_ToString (ctx, arg); /* owned + flattens rope */
if (JS_IsException (str)) return JS_EXCEPTION;
@@ -10467,6 +10481,35 @@ static JSValue js_mach_eval_ast (JSContext *ctx, JSValue this_val, int argc, JSV
return result;
}
/* mcode_run(name, mcode_json, env?) - run pre-compiled mcode JSON */
static JSValue js_mcode_run (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
return JS_ThrowTypeError (ctx, "mcode_run requires (name, mcode_json) text arguments");
const char *name = JS_ToCString (ctx, argv[0]);
if (!name) return JS_EXCEPTION;
const char *json_str = JS_ToCString (ctx, argv[1]);
if (!json_str) {
JS_FreeCString (ctx, name);
return JS_EXCEPTION;
}
cJSON *mcode = cJSON_Parse (json_str);
JS_FreeCString (ctx, json_str);
if (!mcode) {
JS_FreeCString (ctx, name);
return JS_ThrowSyntaxError (ctx, "mcode_run: failed to parse mcode JSON");
}
JSValue env = (argc >= 3 && JS_IsObject (argv[2])) ? argv[2] : JS_NULL;
JSValue result = JS_CallMcodeTreeEnv (ctx, mcode, env);
cJSON_Delete (mcode);
JS_FreeCString (ctx, name);
return result;
}
/* ============================================================================
* stone() function - deep freeze with blob support
* ============================================================================
@@ -11561,6 +11604,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
js_set_global_cfunc(ctx, "eval", js_cell_eval, 2);
js_set_global_cfunc(ctx, "mach_eval", js_mach_eval, 3);
js_set_global_cfunc(ctx, "mach_eval_ast", js_mach_eval_ast, 3);
js_set_global_cfunc(ctx, "mcode_run", js_mcode_run, 3);
js_set_global_cfunc(ctx, "stone", js_cell_stone, 1);
js_set_global_cfunc(ctx, "length", js_cell_length, 1);
js_set_global_cfunc(ctx, "call", js_cell_call, 3);