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

@@ -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) {