Merge branch 'fix_gc' into pitweb

This commit is contained in:
2026-02-15 10:04:54 -06:00
52 changed files with 709697 additions and 135532 deletions

View File

@@ -272,32 +272,43 @@ void script_startup(cell_rt *prt)
}
// Create hidden environment
JSValue hidden_env = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "os", js_core_os_use(js));
JS_SetPropertyStr(js, hidden_env, "json", js_core_json_use(js));
// Note: evaluate allocating calls into temporaries before passing to
// JS_SetPropertyStr, so env_ref.val is read AFTER GC may have moved it.
JSGCRef env_ref;
JS_AddGCRef(js, &env_ref);
env_ref.val = JS_NewObject(js);
JSValue tmp;
tmp = js_core_os_use(js);
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
tmp = js_core_json_use(js);
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null)
if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota));
tmp = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, env_ref.val, "init", tmp);
free(crt->init_wota);
crt->init_wota = NULL;
} else {
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL);
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
}
// Set args to null for actor spawn (not CLI mode)
JS_SetPropertyStr(js, hidden_env, "args", JS_NULL);
JS_SetPropertyStr(js, env_ref.val, "args", JS_NULL);
if (core_path)
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
JS_SetPropertyStr(js, hidden_env, "shop_path",
shop_path ? JS_NewString(js, shop_path) : JS_NULL);
if (core_path) {
tmp = JS_NewString(js, core_path);
JS_SetPropertyStr(js, env_ref.val, "core_path", tmp);
}
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
// Stone the environment
hidden_env = JS_Stone(js, hidden_env);
JSValue hidden_env = JS_Stone(js, env_ref.val);
JS_DeleteGCRef(js, &env_ref);
// Run from binary
crt->state = ACTOR_RUNNING;
@@ -358,6 +369,7 @@ static void print_usage(const char *prog)
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
printf(" --dev Dev mode (shop=.cell, core=.)\n");
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
printf(" --seed Use seed bootstrap (minimal, for regen)\n");
printf(" --test [heap_size] Run C test suite\n");
printf(" -h, --help Show this help message\n");
@@ -392,6 +404,7 @@ int cell_init(int argc, char **argv)
/* Default: run script through bootstrap pipeline */
int arg_start = 1;
int seed_mode = 0;
size_t heap_size = 1024 * 1024; /* 1MB default */
const char *shop_override = NULL;
const char *core_override = NULL;
@@ -414,6 +427,17 @@ int cell_init(int argc, char **argv)
} else if (strcmp(argv[arg_start], "--seed") == 0) {
seed_mode = 1;
arg_start++;
} else if (strcmp(argv[arg_start], "--heap") == 0) {
if (arg_start + 1 >= argc) {
printf("ERROR: --heap requires a size argument (e.g. 1GB, 256MB, 65536)\n");
return 1;
}
char *end = NULL;
heap_size = strtoull(argv[arg_start + 1], &end, 0);
if (end && (*end == 'G' || *end == 'g')) heap_size *= 1024ULL * 1024 * 1024;
else if (end && (*end == 'M' || *end == 'm')) heap_size *= 1024ULL * 1024;
else if (end && (*end == 'K' || *end == 'k')) heap_size *= 1024ULL;
arg_start += 2;
} else if (strcmp(argv[arg_start], "--dev") == 0) {
shop_override = ".cell";
core_override = ".";
@@ -462,7 +486,7 @@ int cell_init(int argc, char **argv)
free(bin_data);
return 1;
}
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, 1024 * 1024);
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
if (!ctx) {
printf("Failed to create JS context\n");
free(bin_data); JS_FreeRuntime(g_runtime);
@@ -499,21 +523,31 @@ int cell_init(int argc, char **argv)
JS_FreeValue(ctx, js_core_blob_use(ctx));
JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_core_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JS_SetPropertyStr(ctx, hidden_env, "shop_path",
shop_path ? JS_NewString(ctx, shop_path) : JS_NULL);
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
JS_SetPropertyStr(ctx, hidden_env, "json", js_core_json_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL);
JSValue args_arr = JS_NewArray(ctx);
JSGCRef env_ref;
JS_AddGCRef(ctx, &env_ref);
env_ref.val = JS_NewObject(ctx);
JSValue tmp;
tmp = js_core_os_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
tmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
tmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
JSGCRef args_ref;
JS_AddGCRef(ctx, &args_ref);
args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
JS_ArrayPush(ctx, &args_ref.val, str);
}
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
hidden_env = JS_Stone(ctx, hidden_env);
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
JS_DeleteGCRef(ctx, &args_ref);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
JS_DeleteGCRef(ctx, &env_ref);
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
free(bin_data);

View File

@@ -154,6 +154,43 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
#define countof(x) (sizeof(x)/sizeof((x)[0]))
/* GC safety macros for C functions that allocate multiple heap objects.
Any allocation call (JS_NewObject, JS_SetPropertyStr, etc.) can trigger GC.
JS_ROOT style: explicit, use .val to access the rooted value.
JS_LOCAL style: transparent, GC updates the C local through a pointer. */
#define JS_FRAME(ctx) \
JSContext *_js_ctx = (ctx); \
JSGCRef *_js_gc_frame = JS_GetGCFrame(_js_ctx); \
JSLocalRef *_js_local_frame = JS_GetLocalFrame(_js_ctx)
#define JS_ROOT(name, init) \
JSGCRef name; \
JS_PushGCRef(_js_ctx, &name); \
name.val = (init)
#define JS_LOCAL(name, init) \
JSValue name = (init); \
JSLocalRef name##__lr; \
name##__lr.ptr = &name; \
JS_PushLocalRef(_js_ctx, &name##__lr)
#define JS_RETURN(val) do { \
JSValue _js_ret = (val); \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return _js_ret; \
} while (0)
#define JS_RETURN_NULL() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_NULL; \
} while (0)
#define JS_RETURN_EX() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_EXCEPTION; \
} while (0)
// Common macros for property access
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \

View File

@@ -981,6 +981,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
@@ -1001,6 +1002,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
@@ -1127,6 +1129,17 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
result = frame->slots[a];
if (JS_IsNull(frame->caller)) goto done;
{
#ifdef VALIDATE_GC
const char *callee_name = "?";
const char *callee_file = "?";
{
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) {
if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr;
if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr;
}
}
#endif
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL;
frame = caller;
@@ -1143,8 +1156,11 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
void *rp = JS_VALUE_GET_PTR(result);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp))
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u\n",
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc);
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n",
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc,
callee_name, callee_file,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
}
#endif
@@ -1180,7 +1196,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
}
case MACH_NEWARRAY: {
JSValue arr = JS_NewArray(ctx);
JSValue arr = JS_NewArrayCap(ctx, b);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { goto disrupt; }
frame->slots[a] = arr;
@@ -1474,6 +1490,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
case MACH_LOAD_INDEX: {
@@ -1492,6 +1509,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
case MACH_LOAD_DYNAMIC: {
@@ -1526,12 +1544,13 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
/* New record */
case MACH_NEWRECORD: {
JSValue obj = JS_NewObject(ctx);
JSValue obj = b > 0 ? JS_NewObjectCap(ctx, b) : JS_NewObject(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(obj)) goto disrupt;
frame->slots[a] = obj;
@@ -1617,9 +1636,15 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
if (JS_IsPtr(ret)) {
void *rp = JS_VALUE_GET_PTR(ret);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp))
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d\n",
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind);
if (!is_ct_ptr(ctx, rp)) {
int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1;
void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL;
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n",
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind,
magic, cfp,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
}
}
#endif
@@ -1983,11 +2008,9 @@ static int mcode_reg_items(cJSON *it, cJSON **out) {
/* record: [1]=dest, [2]=0(const) — no line/col suffix */
if (!strcmp(op, "record")) { ADD(1); return c; }
/* array: [1]=dest, [2]=count(const), [3..]=elements (no line/col suffix) */
/* array: [1]=dest, [2]=count(const)elements added via separate push instrs */
if (!strcmp(op, "array")) {
ADD(1);
int cnt = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
for (int j = 0; j < cnt; j++) ADD(3 + j);
return c;
}
@@ -2043,8 +2066,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
int pinned = 1 + nr_args;
for (int i = 0; i < pinned; i++) { first_ref[i] = 0; last_ref[i] = n; }
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs);
@@ -2054,7 +2077,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
if (first_ref[s] < 0) first_ref[s] = i;
last_ref[s] = i;
}
}
} }
/* Step 1a: extend live ranges for closure-captured slots.
If a child function captures a parent slot via get/put, that slot must
@@ -2076,8 +2099,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
typedef struct { const char *name; int pos; } LabelPos;
int lbl_cap = 32, lbl_n = 0;
LabelPos *lbls = sys_malloc(lbl_cap * sizeof(LabelPos));
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (cJSON_IsString(it)) {
if (lbl_n >= lbl_cap) {
lbl_cap *= 2;
@@ -2085,23 +2108,23 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
}
lbls[lbl_n++] = (LabelPos){it->valuestring, i};
}
}
} }
/* Find backward jumps and extend live ranges */
int changed = 1;
while (changed) {
changed = 0;
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
int sz = cJSON_GetArraySize(it);
if (sz < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
const char *target = NULL;
if (!strcmp(op, "jump")) {
target = cJSON_GetArrayItem(it, 1)->valuestring;
target = it->child->next->valuestring;
} else if (!strcmp(op, "jump_true") || !strcmp(op, "jump_false") ||
!strcmp(op, "jump_not_null")) {
target = cJSON_GetArrayItem(it, 2)->valuestring;
target = it->child->next->next->valuestring;
}
if (!target) continue;
/* Find label position */
@@ -2207,8 +2230,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
}
/* Step 3: apply remap to instructions */
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs);
@@ -2218,7 +2241,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
cJSON_SetNumberValue(regs[j], remap[old]);
}
}
}
} }
/* Update nr_slots in the JSON */
cJSON_SetNumberValue(nr_slots_j, new_max);
@@ -2250,8 +2273,8 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
s.flat_to_pc = sys_malloc((n + 1) * sizeof(int));
s.flat_count = n;
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
s.flat_to_pc[i] = s.code_count;
if (cJSON_IsString(it)) {
ml_label(&s, it->valuestring);
@@ -2450,15 +2473,10 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
}
/* Array/Object creation */
else if (strcmp(op, "array") == 0) {
int dest = A1, count = A2;
EM(MACH_ABC(MACH_NEWARRAY, dest, 0, 0));
for (int j = 0; j < count; j++) {
int elem = ml_int(it, 3 + j);
EM(MACH_ABC(MACH_PUSH, dest, elem, 0));
}
EM(MACH_ABC(MACH_NEWARRAY, A1, A2, 0));
}
else if (strcmp(op, "record") == 0) {
EM(MACH_ABC(MACH_NEWRECORD, A1, 0, 0));
EM(MACH_ABC(MACH_NEWRECORD, A1, A2, 0));
}
/* Push/Pop */
else if (strcmp(op, "push") == 0) {
@@ -2551,7 +2569,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
/* Unknown opcode — emit NOP */
EM(MACH_ABC(MACH_NOP, 0, 0, 0));
}
}
} }
/* Sentinel for flat_to_pc */
s.flat_to_pc[n] = s.code_count;
@@ -2690,34 +2708,32 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
/* Scan main's instructions */
{
cJSON *main_instrs = cJSON_GetObjectItemCaseSensitive(main_obj, "instructions");
int mn = main_instrs ? cJSON_GetArraySize(main_instrs) : 0;
for (int i = 0; i < mn; i++) {
cJSON *it = cJSON_GetArrayItem(main_instrs, i);
cJSON *it = main_instrs ? main_instrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (!strcmp(op, "function")) {
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = func_count; /* main */
}
}
}
/* Scan each function's instructions */
for (int fi = 0; fi < func_count; fi++) {
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
for (int i = 0; i < fn; i++) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (!strcmp(op, "function")) {
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = fi;
}
}
}
} }
/* Build per-function capture sets: for each function F, which of its slots
are captured by descendant functions via get/put. Captured slots must
@@ -2727,17 +2743,16 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
memset(cap_slots, 0, (func_count + 1) * sizeof(int *));
memset(cap_counts, 0, (func_count + 1) * sizeof(int));
for (int fi = 0; fi < func_count; fi++) {
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
for (int i = 0; i < fn; i++) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue;
int slot = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble;
int slot = (int)it->child->next->next->valuedouble;
int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain to find the ancestor whose slot is referenced */
int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++)
@@ -2753,7 +2768,7 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
cap_slots[ancestor][cap_counts[ancestor]++] = slot;
}
}
}
} }
/* Compress registers for functions that exceed 8-bit slot limits.
Save remap tables so we can fix get/put parent_slot references. */
@@ -2761,9 +2776,11 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
int *remap_sizes = sys_malloc((func_count + 1) * sizeof(int));
memset(remaps, 0, (func_count + 1) * sizeof(int *));
for (int i = 0; i < func_count; i++)
remaps[i] = mcode_compress_regs(cJSON_GetArrayItem(funcs_arr, i),
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int i = 0; fobj; i++, fobj = fobj->next)
remaps[i] = mcode_compress_regs(fobj,
&remap_sizes[i], cap_slots[i], cap_counts[i]);
}
/* main is stored at index func_count in our arrays */
remaps[func_count] = mcode_compress_regs(main_obj,
&remap_sizes[func_count], cap_slots[func_count], cap_counts[func_count]);
@@ -2775,16 +2792,15 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
sys_free(cap_counts);
/* Fix up get/put parent_slot references using ancestor remap tables */
for (int fi = 0; fi < func_count; fi++) {
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
for (int i = 0; i < fn; i++) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue;
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble;
int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain 'level' times to find ancestor */
int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++) {
@@ -2793,14 +2809,14 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
if (ancestor < 0) continue; /* unknown parent — leave as is */
int *anc_remap = remaps[ancestor];
if (!anc_remap) continue; /* ancestor wasn't compressed */
cJSON *slot_item = cJSON_GetArrayItem(it, 2);
cJSON *slot_item = it->child->next->next;
int old_slot = (int)slot_item->valuedouble;
if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) {
int new_slot = anc_remap[old_slot];
cJSON_SetNumberValue(slot_item, new_slot);
}
}
}
} }
/* Free remap tables */
for (int i = 0; i <= func_count; i++)
@@ -2814,8 +2830,10 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
if (func_count > 0) {
compiled = sys_malloc(func_count * sizeof(MachCode *));
memset(compiled, 0, func_count * sizeof(MachCode *));
for (int i = 0; i < func_count; i++)
compiled[i] = mcode_lower_func(cJSON_GetArrayItem(funcs_arr, i), filename);
{ cJSON *fobj = funcs_arr->child;
for (int i = 0; fobj; i++, fobj = fobj->next)
compiled[i] = mcode_lower_func(fobj, filename);
}
}
/* Compile main */

View File

@@ -9,6 +9,15 @@
#include "quickjs-internal.h"
#include <math.h>
/* Non-inline wrappers for static inline functions in quickjs.h */
JSValue qbe_new_float64(JSContext *ctx, double d) {
return __JS_NewFloat64(ctx, d);
}
JSValue qbe_new_string(JSContext *ctx, const char *str) {
return JS_NewString(ctx, str);
}
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
enum {
QBE_CMP_EQ = 0,
@@ -42,6 +51,16 @@ JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_add);
}
/* Generic add: concat if both text, float add if both numeric, else type error */
JSValue cell_rt_add(JSContext *ctx, JSValue a, JSValue b) {
if (JS_IsText(a) && JS_IsText(b))
return JS_ConcatString(ctx, a, b);
if (JS_IsNumber(a) && JS_IsNumber(b))
return qbe_float_binop(ctx, a, b, op_add);
JS_ThrowTypeError(ctx, "cannot add incompatible types");
return JS_NULL;
}
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_sub);
}
@@ -446,6 +465,15 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, a != b);
}
/* --- Type check: is_proxy (function with arity 2) --- */
int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
(void)ctx;
if (!JS_IsFunction(v)) return 0;
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
return fn->length == 2;
}
/* --- Disruption --- */
void cell_rt_disrupt(JSContext *ctx) {

View File

@@ -156,7 +156,8 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
};
JSValue js_core_actor_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs));
return mod;
}
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs));
JS_RETURN(mod.val);
}

View File

@@ -845,6 +845,18 @@ static inline objhdr_t *chase(JSValue v) {
return oh;
}
/* Resolve a forward pointer in-place. After rec_resize the old record
gets a forward header; any JSValue slot still pointing at it must be
updated to follow the chain to the live copy. */
static inline void mach_resolve_forward(JSValue *slot) {
if (JS_IsPtr(*slot)) {
objhdr_t h = *(objhdr_t *)JS_VALUE_GET_PTR(*slot);
if (objhdr_type(h) == OBJ_FORWARD) {
*slot = JS_MKPTR(objhdr_fwd_ptr(h));
}
}
}
/* Inline type checks — use these in the VM dispatch loop to avoid
function call overhead. The public API (JS_IsArray etc. in quickjs.h)
remains non-inline for external callers; those wrappers live in runtime.c. */
@@ -1070,6 +1082,11 @@ struct JSContext {
uint8_t *heap_end; /* end of block */
size_t current_block_size; /* current block size (64KB initially) */
size_t next_block_size; /* doubles if <10% recovered after GC */
int gc_poor_streak; /* consecutive poor-recovery GC cycles */
/* GC stats (lightweight, always on) */
uint64_t gc_count; /* number of GC cycles */
uint64_t gc_bytes_copied; /* total bytes copied across all GCs */
/* Constant text pool — compilation constants */
uint8_t *ct_base; /* pool base */
@@ -1092,6 +1109,7 @@ struct JSContext {
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
int class_count; /* size of class_array and class_proto */
@@ -1185,7 +1203,15 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size);
/* Helper to check if a pointer is in constant text pool memory */
static inline int is_ct_ptr (JSContext *ctx, void *ptr) {
return (uint8_t *)ptr >= ctx->ct_base && (uint8_t *)ptr < ctx->ct_end;
uint8_t *p = (uint8_t *)ptr;
if (p >= ctx->ct_base && p < ctx->ct_end) return 1;
/* Also check overflow pages */
CTPage *page = (CTPage *)ctx->ct_pages;
while (page) {
if (p >= page->data && p < page->data + page->size) return 1;
page = page->next;
}
return 0;
}
#ifdef HEAP_CHECK

View File

@@ -146,10 +146,22 @@ typedef struct JSGCRef {
struct JSGCRef *prev;
} JSGCRef;
/* JSLocalRef - GC updates C locals through pointers (OCaml-style) */
typedef struct JSLocalRef {
JSValue *ptr;
struct JSLocalRef *prev;
} JSLocalRef;
/* stack of JSGCRef */
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref);
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref);
/* JS_FRAME/JS_ROOT/JS_LOCAL helpers (for use from cell.h macros) */
JSGCRef *JS_GetGCFrame(JSContext *ctx);
JSLocalRef *JS_GetLocalFrame(JSContext *ctx);
void JS_PushLocalRef(JSContext *ctx, JSLocalRef *ref);
void JS_RestoreFrame(JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame);
#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0)
#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref)
@@ -502,9 +514,11 @@ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto, JSClassID class_i
JSValue JS_NewObjectClass (JSContext *ctx, int class_id);
JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto);
JSValue JS_NewObject (JSContext *ctx);
JSValue JS_NewObjectCap (JSContext *ctx, uint32_t n);
JSValue JS_NewArray (JSContext *ctx);
JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len);
JSValue JS_NewArrayCap (JSContext *ctx, uint32_t cap);
JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values);
int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val);
JSValue JS_ArrayPop (JSContext *ctx, JSValue obj);

View File

@@ -183,6 +183,25 @@ void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) {
}
}
/* JS_FRAME/JS_ROOT/JS_LOCAL helper functions */
JSGCRef *JS_GetGCFrame (JSContext *ctx) {
return ctx->top_gc_ref;
}
JSLocalRef *JS_GetLocalFrame (JSContext *ctx) {
return ctx->top_local_ref;
}
void JS_PushLocalRef (JSContext *ctx, JSLocalRef *ref) {
ref->prev = ctx->top_local_ref;
ctx->top_local_ref = ref;
}
void JS_RestoreFrame (JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame) {
ctx->top_gc_ref = gc_frame;
ctx->top_local_ref = local_frame;
}
void *ct_alloc (JSContext *ctx, size_t bytes, size_t align) {
/* Align the request */
bytes = (bytes + align - 1) & ~(align - 1);
@@ -1300,8 +1319,6 @@ JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *f
for (;;) {
void *ptr = JS_VALUE_GET_PTR (v);
if (is_ct_ptr (ctx, ptr)) return v;
if (!ptr_in_range (ptr, from_base, from_end)) return v;
objhdr_t *hdr_ptr = (objhdr_t *)ptr;
@@ -1608,6 +1625,14 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end);
}
/* Copy JS_LOCAL roots (update C locals through pointers) */
#ifdef DUMP_GC_DETAIL
printf(" roots: top_local_ref\n"); fflush(stdout);
#endif
for (JSLocalRef *ref = ctx->top_local_ref; ref != NULL; ref = ref->prev) {
*ref->ptr = gc_copy_value (ctx, *ref->ptr, from_base, from_end, to_base, &to_free, to_end);
}
/* Copy JS_AddGCRef/JS_DeleteGCRef roots */
#ifdef DUMP_GC_DETAIL
printf(" roots: last_gc_ref\n"); fflush(stdout);
@@ -1651,6 +1676,10 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
/* Update context with new block */
size_t new_used = to_free - to_base;
/* Update GC stats */
ctx->gc_count++;
ctx->gc_bytes_copied += new_used;
size_t recovered = old_used > new_used ? old_used - new_used : 0;
ctx->heap_base = to_base;
@@ -1670,19 +1699,23 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
}
#endif
/* If <20% recovered, double next block size for future allocations
But only if allow_grow is set (i.e., GC was triggered due to low space) */
/* If <40% recovered, grow next block size for future allocations.
First poor recovery: double. Consecutive poor: quadruple. */
#ifdef DUMP_GC
int will_grow = 0;
#endif
if (allow_grow && recovered > 0 && old_used > 0 && recovered < old_used / 5) {
size_t doubled = new_size * 2;
if (doubled <= buddy_max_block(&ctx->rt->buddy)) {
ctx->next_block_size = doubled;
if (allow_grow && recovered > 0 && old_used > 0 && recovered < old_used * 2 / 5) {
size_t factor = ctx->gc_poor_streak >= 1 ? 4 : 2;
size_t grown = new_size * factor;
if (grown <= buddy_max_block(&ctx->rt->buddy)) {
ctx->next_block_size = grown;
#ifdef DUMP_GC
will_grow = 1;
#endif
}
ctx->gc_poor_streak++;
} else {
ctx->gc_poor_streak = 0;
}
#ifdef DUMP_GC
@@ -1828,6 +1861,20 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
/* Initialize per-context execution state (moved from JSRuntime) */
ctx->current_exception = JS_NULL;
/* Initialize constant text pool (avoids overflow pages for common case) */
{
size_t ct_pool_size = 64 * 1024; /* 64KB initial CT pool */
ctx->ct_base = js_malloc_rt (ct_pool_size);
if (!ctx->ct_base) {
js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto);
js_free_rt (ctx);
return NULL;
}
ctx->ct_free = ctx->ct_base;
ctx->ct_end = ctx->ct_base + ct_pool_size;
}
/* Initialize constant text intern table */
ctx->ct_pages = NULL;
ctx->ct_array = NULL;
@@ -1917,6 +1964,7 @@ void JS_FreeContext (JSContext *ctx) {
/* Free constant text pool and intern table */
ct_free_all (ctx);
if (ctx->ct_base) js_free_rt (ctx->ct_base);
js_free_rt (ctx->ct_hash);
js_free_rt (ctx->ct_array);
@@ -2109,8 +2157,24 @@ static JSValue js_sub_string_val (JSContext *ctx, JSValue src, int start, int en
return js_new_string8_len (ctx, buf, len);
}
/* Heap string — fast path for short ASCII substrings (avoids heap alloc) */
JSText *p = JS_VALUE_GET_STRING (src);
if (len <= MIST_ASCII_MAX_LEN) {
char buf[MIST_ASCII_MAX_LEN];
int all_ascii = 1;
for (int i = 0; i < len; i++) {
uint32_t c = string_get (p, start + i);
if (c >= 0x80) { all_ascii = 0; break; }
buf[i] = (char)c;
}
if (all_ascii) {
JSValue imm = MIST_TryNewImmediateASCII (buf, len);
if (!JS_IsNull (imm)) return imm;
}
}
/* Heap string — delegate to existing js_sub_string */
return js_sub_string (ctx, JS_VALUE_GET_STRING (src), start, end);
return js_sub_string (ctx, p, start, end);
}
/* Allocate a new pretext (mutable JSText) with initial capacity */
@@ -2619,11 +2683,51 @@ JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len) {
JSValue JS_NewArray (JSContext *ctx) { return JS_NewArrayLen (ctx, 0); }
/* Create array with pre-allocated capacity but len=0 (for push-fill patterns) */
JSValue JS_NewArrayCap (JSContext *ctx, uint32_t cap) {
if (cap == 0) cap = JS_ARRAY_INITIAL_SIZE;
size_t values_size = sizeof (JSValue) * cap;
size_t total_size = sizeof (JSArray) + values_size;
JSArray *arr = js_malloc (ctx, total_size);
if (!arr) return JS_EXCEPTION;
arr->mist_hdr = objhdr_make (cap, OBJ_ARRAY, false, false, false, false);
arr->len = 0;
for (uint32_t i = 0; i < cap; i++)
arr->values[i] = JS_NULL;
return JS_MKPTR (arr);
}
JSValue JS_NewObject (JSContext *ctx) {
/* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */
return JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT);
}
/* Create object with pre-allocated hash table for n properties */
JSValue JS_NewObjectCap (JSContext *ctx, uint32_t n) {
/* slot 0 is reserved, so need n+1 slots minimum.
Hash table needs ~2x entries for good load factor.
mask must be power-of-2 minus 1. */
uint32_t need = (n + 1) * 2;
uint32_t mask = JS_RECORD_INITIAL_MASK;
while (mask + 1 < need) mask = (mask << 1) | 1;
JSGCRef proto_ref;
JS_PushGCRef (ctx, &proto_ref);
proto_ref.val = ctx->class_proto[JS_CLASS_OBJECT];
JSRecord *rec = js_new_record_class (ctx, mask, JS_CLASS_OBJECT);
JSValue proto_val = proto_ref.val;
JS_PopGCRef (ctx, &proto_ref);
if (!rec) return JS_EXCEPTION;
if (JS_IsRecord (proto_val))
rec->proto = proto_val;
return JS_MKPTR (rec);
}
/* Note: at least 'length' arguments will be readable in 'argv' */
static JSValue JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) {
@@ -4383,6 +4487,10 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj,
ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
}
#ifdef VALIDATE_GC
uint8_t *pre_heap_base = ctx->heap_base;
#endif
switch (cproto) {
case JS_CFUNC_generic:
ret_val = func.generic (ctx, this_obj, argc, arg_copy);
@@ -4449,6 +4557,21 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj,
abort ();
}
#ifdef VALIDATE_GC
if (ctx->heap_base != pre_heap_base && JS_IsPtr (ret_val)) {
void *rp = JS_VALUE_GET_PTR (ret_val);
if (!is_ct_ptr (ctx, rp) &&
((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free)) {
/* Note: f is stale after GC (func_obj was passed by value), so we
cannot read f->name here. Just report the pointer. */
fprintf (stderr, "VALIDATE_GC: C function returned stale ptr=%p "
"heap=[%p,%p) after GC\n", rp,
(void *)ctx->heap_base, (void *)ctx->heap_free);
fflush (stderr);
}
}
#endif
ctx->c_call_root = root.prev;
if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
@@ -5356,19 +5479,27 @@ static JSValue cjson_to_jsvalue (JSContext *ctx, const cJSON *item) {
if (cJSON_IsString (item)) return JS_NewString (ctx, item->valuestring);
if (cJSON_IsArray (item)) {
int n = cJSON_GetArraySize (item);
JSValue arr = JS_NewArrayLen (ctx,n);
JSGCRef arr_ref;
JS_AddGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen (ctx, n);
for (int i = 0; i < n; i++) {
cJSON *child = cJSON_GetArrayItem (item, i);
JS_SetPropertyNumber (ctx, arr, i, cjson_to_jsvalue (ctx, child));
JS_SetPropertyNumber (ctx, arr_ref.val, i, cjson_to_jsvalue (ctx, child));
}
return arr;
JSValue result = arr_ref.val;
JS_DeleteGCRef (ctx, &arr_ref);
return result;
}
if (cJSON_IsObject (item)) {
JSValue obj = JS_NewObject (ctx);
JSGCRef obj_ref;
JS_AddGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
for (cJSON *child = item->child; child; child = child->next) {
JS_SetPropertyStr (ctx, obj, child->string, cjson_to_jsvalue (ctx, child));
JS_SetPropertyStr (ctx, obj_ref.val, child->string, cjson_to_jsvalue (ctx, child));
}
return obj;
JSValue result = obj_ref.val;
JS_DeleteGCRef (ctx, &obj_ref);
return result;
}
return JS_NULL;
}
@@ -7857,8 +7988,8 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
JSGCRef result_ref;
JS_PushGCRef (ctx, &result_ref); /* Push first - sets val to JS_NULL */
result_ref.val = JS_NewArray (ctx); /* Then assign */
JS_PushGCRef (ctx, &result_ref);
result_ref.val = JS_NewArrayLen (ctx, len);
if (JS_IsException (result_ref.val)) {
JS_PopGCRef (ctx, &result_ref);
JS_PopGCRef (ctx, &arg1_ref);
@@ -7866,17 +7997,23 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
return result_ref.val;
}
int out_idx = 0;
#define MAP_STORE(val) do { \
JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val); \
out->values[out_idx++] = (val); \
} while(0)
#define MAP_ERR() do { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } while(0)
if (arity >= 2) {
if (reverse) {
for (int i = len - 1; i >= 0; i--) {
/* Re-chase input array each iteration */
arr = JS_VALUE_GET_ARRAY (arg0_ref.val);
if (i >= (int)arr->len) continue; /* array may have shrunk */
if (i >= (int)arr->len) continue;
JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) };
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
MAP_STORE (val);
}
} else {
for (int i = 0; i < len; i++) {
@@ -7884,9 +8021,9 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
if (i >= (int)arr->len) break;
JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) };
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
MAP_STORE (val);
}
}
} else {
@@ -7896,9 +8033,9 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
if (i >= (int)arr->len) continue;
JSValue item = arr->values[i];
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
MAP_STORE (val);
}
} else {
for (int i = 0; i < len; i++) {
@@ -7906,12 +8043,17 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
if (i >= (int)arr->len) break;
JSValue item = arr->values[i];
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; }
MAP_STORE (val);
}
}
}
#undef MAP_STORE
#undef MAP_ERR
/* Truncate if early exit produced fewer elements */
JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val);
out->len = out_idx;
JSValue result = result_ref.val;
JS_PopGCRef (ctx, &result_ref);
JS_PopGCRef (ctx, &arg1_ref);
@@ -8018,7 +8160,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
JS_PushGCRef (ctx, &arr_ref);
JS_PushGCRef (ctx, &str_ref);
str_ref.val = arg;
arr_ref.val = JS_NewArray (ctx);
arr_ref.val = JS_NewArrayLen (ctx, len);
if (JS_IsException (arr_ref.val)) {
JS_PopGCRef (ctx, &str_ref);
JS_PopGCRef (ctx, &arr_ref);
@@ -8031,7 +8173,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
JS_PopGCRef (ctx, &arr_ref);
return JS_EXCEPTION;
}
JS_ArrayPush (ctx, &arr_ref.val, ch);
JS_SetPropertyNumber (ctx, arr_ref.val, i, ch);
}
JSValue result = arr_ref.val;
JS_PopGCRef (ctx, &str_ref);
@@ -9636,6 +9778,22 @@ static JSValue js_mach_dump_mcode (JSContext *ctx, JSValue this_val, int argc, J
return JS_NULL;
}
/* gc_stats() — return {count, bytes_copied, heap_size, ct_pages} and reset counters */
static JSValue js_gc_stats (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
JSValue obj = JS_NewObject (ctx);
if (JS_IsException (obj)) return obj;
JS_SetPropertyStr (ctx, obj, "count", JS_NewInt64 (ctx, (int64_t)ctx->gc_count));
JS_SetPropertyStr (ctx, obj, "bytes_copied", JS_NewInt64 (ctx, (int64_t)ctx->gc_bytes_copied));
JS_SetPropertyStr (ctx, obj, "heap_size", JS_NewInt64 (ctx, (int64_t)ctx->current_block_size));
/* Count CT overflow pages */
int ct_page_count = 0;
for (CTPage *p = (CTPage *)ctx->ct_pages; p; p = p->next) ct_page_count++;
JS_SetPropertyStr (ctx, obj, "ct_pages", JS_NewInt32 (ctx, ct_page_count));
ctx->gc_count = 0;
ctx->gc_bytes_copied = 0;
return obj;
}
/* mach_compile_mcode_bin(name, mcode_json) - compile mcode IR to serialized binary blob */
static JSValue js_mach_compile_mcode_bin (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
@@ -10128,16 +10286,13 @@ JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue
JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values) {
JSGCRef arr_ref;
JS_PushGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArray (ctx);
arr_ref.val = JS_NewArrayLen (ctx, count);
if (JS_IsException (arr_ref.val)) {
JS_PopGCRef (ctx, &arr_ref);
return JS_EXCEPTION;
}
for (int i = 0; i < count; i++) {
if (JS_ArrayPush (ctx, &arr_ref.val, values[i]) < 0) {
JS_PopGCRef (ctx, &arr_ref);
return JS_EXCEPTION;
}
JS_SetPropertyNumber (ctx, arr_ref.val, i, values[i]);
}
JSValue result = arr_ref.val;
JS_PopGCRef (ctx, &arr_ref);
@@ -10731,6 +10886,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
js_set_global_cfunc(ctx, "mach_eval_mcode", js_mach_eval_mcode, 3);
js_set_global_cfunc(ctx, "mach_dump_mcode", js_mach_dump_mcode, 3);
js_set_global_cfunc(ctx, "mach_compile_mcode_bin", js_mach_compile_mcode_bin, 2);
js_set_global_cfunc(ctx, "gc_stats", js_gc_stats, 0);
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);
@@ -11009,7 +11165,7 @@ static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue
break;
case NOTA_ARR:
nota = nota_read_array (&n, nota);
*tmp = JS_NewArray (js);
*tmp = JS_NewArrayLen (js, n);
for (int i = 0; i < n; i++) {
nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver);
JS_SetPropertyNumber (js, *tmp, i, ret2);
@@ -11879,9 +12035,13 @@ static const JSCFunctionListEntry js_math_radians_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_radians_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj, js_math_radians_funcs, countof (js_math_radians_funcs));
return obj;
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_radians_funcs, countof (js_math_radians_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}
/* ============================================================================
@@ -11945,9 +12105,13 @@ static const JSCFunctionListEntry js_math_degrees_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_degrees_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj, js_math_degrees_funcs, countof (js_math_degrees_funcs));
return obj;
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_degrees_funcs, countof (js_math_degrees_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}
/* ============================================================================
@@ -12010,9 +12174,13 @@ static const JSCFunctionListEntry js_math_cycles_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_cycles_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj, js_math_cycles_funcs, countof (js_math_cycles_funcs));
return obj;
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_cycles_funcs, countof (js_math_cycles_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}
/* Public API: get stack trace as cJSON array */
cJSON *JS_GetStack(JSContext *ctx) {