Merge branch 'runtime_rework' into fix_gc
This commit is contained in:
@@ -275,34 +275,47 @@ void script_startup(cell_rt *prt)
|
||||
}
|
||||
|
||||
// Create hidden environment
|
||||
JSValue hidden_env = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_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_os_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
|
||||
tmp = js_json_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
|
||||
tmp = js_nota_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "nota", tmp);
|
||||
tmp = js_wota_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "wota", 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;
|
||||
@@ -363,6 +376,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");
|
||||
@@ -397,6 +411,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;
|
||||
|
||||
@@ -419,6 +434,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 = ".";
|
||||
@@ -467,7 +493,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);
|
||||
@@ -504,23 +530,35 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
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));
|
||||
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_json_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "wota", js_wota_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_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_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
||||
tmp = js_nota_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "nota", tmp);
|
||||
tmp = js_wota_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "wota", 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);
|
||||
|
||||
@@ -156,6 +156,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); \
|
||||
|
||||
156
source/mach.c
156
source/mach.c
@@ -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 */
|
||||
|
||||
@@ -156,7 +156,8 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
|
||||
};
|
||||
|
||||
JSValue js_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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
256
source/runtime.c
256
source/runtime.c
@@ -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_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_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_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) {
|
||||
|
||||
Reference in New Issue
Block a user