extensive debugging; fixed forwarding error

This commit is contained in:
2026-02-03 01:50:30 -06:00
parent e4b7de46f6
commit 3e40885e07

View File

@@ -81,8 +81,13 @@
64: dump compute_stack_size
*/
// #define DUMP_BYTECODE (1)
/* dump the occurence of the automatic GC */
/* dump GC summary: old/new heap, recovery %, heap growth */
// #define DUMP_GC
/* dump detailed GC: roots, scanning, object traversal (implies DUMP_GC) */
// #define DUMP_GC_DETAIL
#ifdef DUMP_GC_DETAIL
#define DUMP_GC
#endif
/* dump objects freed by the garbage collector */
// #define DUMP_GC_FREE
/* dump memory usage before running the garbage collector */
@@ -2236,54 +2241,68 @@ static size_t gc_object_size (void *ptr) {
return gc_align_up (sizeof (JSFunction));
default:
/* Unknown type - fatal error, heap is corrupt or scan desync'd */
fprintf (stderr, "gc_object_size: unknown object type %d at %p\n", type, ptr);
fflush(stdout);
fprintf (stderr, "gc_object_size: unknown type %d at %p, hdr=0x%llx cap=%llu\n",
type, ptr, (unsigned long long)hdr, (unsigned long long)cap);
/* Dump surrounding memory for debugging */
uint64_t *words = (uint64_t *)ptr;
fprintf (stderr, " words: [0]=0x%llx [1]=0x%llx [2]=0x%llx [3]=0x%llx\n",
(unsigned long long)words[0], (unsigned long long)words[1],
(unsigned long long)words[2], (unsigned long long)words[3]);
fflush(stderr);
abort ();
}
}
static inline int ptr_in_range (void *p, uint8_t *b, uint8_t *e) {
uint8_t *q = (uint8_t *)p;
return q >= b && q < e;
}
/* Copy a single value, returning the new value with updated pointer if needed.
Simple Cheney: if already forwarded, return new address; else copy and forward.
from_base/from_end define the OLD heap bounds (where objects are being copied FROM). */
static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end,
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) {
if (!JS_IsPtr (v)) {
return v; /* Immediate value - no copy needed */
static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) {
if (!JS_IsPtr (v)) return v;
for (;;) {
void *ptr = JS_VALUE_GET_PTR (v);
if (is_stone_ptr (ctx, ptr)) return v;
if (!ptr_in_range (ptr, from_base, from_end)) return v;
objhdr_t *hdr_ptr = (objhdr_t *)ptr;
objhdr_t hdr = *hdr_ptr;
uint8_t type = objhdr_type (hdr);
if (type == OBJ_FORWARD) {
void *t = objhdr_fwd_ptr (hdr);
/* If it already points into to-space, it's a GC forward. */
if (ptr_in_range (t, to_base, *to_free)) return JS_MKPTR (t);
/* Otherwise it's a growth-forward (still in from-space). Chase. */
v = JS_MKPTR (t);
continue;
}
if (type != OBJ_ARRAY && type != OBJ_TEXT && type != OBJ_RECORD && type != OBJ_FUNCTION && type != OBJ_BLOB && type != OBJ_CODE) {
fprintf (stderr, "gc_copy_value: invalid object type %d at %p (hdr=0x%llx)\n", type, ptr, (unsigned long long)hdr);
fprintf (stderr, " This may be an interior pointer or corrupt root\n");
fflush (stderr);
abort ();
}
size_t size = gc_object_size (hdr_ptr);
if (*to_free + size > to_end) {
fprintf (stderr, "gc_copy_value: out of to-space, need %zu bytes\n", size);
abort ();
}
void *new_ptr = *to_free;
memcpy (new_ptr, hdr_ptr, size);
*to_free += size;
*hdr_ptr = objhdr_make_fwd (new_ptr);
return JS_MKPTR (new_ptr);
}
void *ptr = JS_VALUE_GET_PTR (v);
if (is_stone_ptr (ctx, ptr)) return v; /* Stone memory - don't copy */
/* Check if pointer is in old heap (from-space) */
if ((uint8_t *)ptr < from_base || (uint8_t *)ptr >= from_end) {
/* External allocation - keep reference as-is */
return v;
}
objhdr_t *hdr_ptr = ptr;
objhdr_t hdr = *hdr_ptr;
/* Already forwarded? Return the new location. */
if (objhdr_type (hdr) == OBJ_FORWARD) {
void *fwd_target = objhdr_fwd_ptr (hdr);
return JS_MKPTR (fwd_target);
}
/* Copy object to to-space */
size_t size = gc_object_size (hdr_ptr);
if (*to_free + size > to_end) {
/* Out of space - this is a fatal error */
fprintf (stderr, "gc_copy_value: out of to-space, need %zu bytes\n", size);
abort ();
}
void *new_ptr = *to_free;
memcpy (new_ptr, hdr_ptr, size);
*to_free += size;
/* Install forward pointer in old location */
*hdr_ptr = objhdr_make_fwd (new_ptr);
return JS_MKPTR (new_ptr);
}
/* Scan a copied object and update its internal references */
@@ -2302,6 +2321,13 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8
}
case OBJ_RECORD: {
JSRecord *rec = (JSRecord *)ptr;
uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr);
uint32_t num_slots = mask + 1;
uint32_t used_slots = (uint32_t)rec->len;
#ifdef DUMP_GC_DETAIL
printf(" record: slots=%u used=%u proto=%p\n", num_slots, used_slots, (void*)rec->proto);
fflush(stdout);
#endif
/* Copy prototype */
if (rec->proto) {
JSValue proto_val = JS_MKPTR (rec->proto);
@@ -2309,7 +2335,6 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8
rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val);
}
/* Copy table entries */
uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr);
for (uint32_t i = 0; i <= mask; i++) {
JSValue k = rec->slots[i].key;
if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) {
@@ -2328,9 +2353,18 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8
}
case OBJ_TEXT:
case OBJ_BLOB:
case OBJ_CODE:
/* No internal references to scan */
break;
case OBJ_CODE: {
/* JSFunctionBytecode - scan func_name and filename */
JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr;
bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end);
if (bc->has_debug) {
bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end);
}
/* Note: cpool, vardefs, closure_var, byte_code_buf are allocated via js_malloc */
break;
}
default:
/* Unknown type during scan - fatal error */
fprintf (stderr, "gc_scan_object: unknown object type %d at %p\n", type, ptr);
@@ -2344,11 +2378,13 @@ static int ctx_gc (JSContext *ctx) {
size_t old_used = ctx->heap_free - ctx->heap_base;
size_t old_heap_size = ctx->current_block_size;
/* Save OLD heap bounds before allocating new block */
/* Save OLD heap bounds before allocating new block
Use heap_free (not heap_end) as from_end - only the portion up to heap_free
contains valid objects. Beyond heap_free is uninitialized garbage. */
uint8_t *from_base = ctx->heap_base;
uint8_t *from_end = ctx->heap_end;
uint8_t *from_end = ctx->heap_free;
#ifdef DUMP_GC
#ifdef DUMP_GC_DETAIL
printf("ctx_gc: from_base=%p from_end=%p size=%zu\n", (void*)from_base, (void*)from_end, old_heap_size);
#endif
@@ -2367,27 +2403,64 @@ static int ctx_gc (JSContext *ctx) {
uint8_t *to_end = new_block + new_size;
/* Copy roots: global object, class prototypes, exception, etc. */
#ifdef DUMP_GC_DETAIL
printf(" roots: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout);
if (JS_IsPtr(ctx->global_obj)) {
void *gptr = JS_VALUE_GET_PTR(ctx->global_obj);
printf(" ptr=%p in_from=%d is_stone=%d\n", gptr,
((uint8_t*)gptr >= from_base && (uint8_t*)gptr < from_end),
is_stone_ptr(ctx, gptr));
fflush(stdout);
}
#endif
ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, from_base, from_end, to_base, &to_free, to_end);
#ifdef DUMP_GC_DETAIL
printf(" after copy: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout);
#endif
#ifdef DUMP_GC_DETAIL
printf(" roots: global_var_obj\n"); fflush(stdout);
#endif
ctx->global_var_obj = gc_copy_value (ctx, ctx->global_var_obj, from_base, from_end, to_base, &to_free, to_end);
#ifdef DUMP_GC_DETAIL
printf(" roots: regexp_ctor\n"); fflush(stdout);
#endif
ctx->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, from_base, from_end, to_base, &to_free, to_end);
#ifdef DUMP_GC_DETAIL
printf(" roots: throw_type_error\n"); fflush(stdout);
#endif
ctx->throw_type_error = gc_copy_value (ctx, ctx->throw_type_error, from_base, from_end, to_base, &to_free, to_end);
#ifdef DUMP_GC_DETAIL
printf(" roots: eval_obj\n"); fflush(stdout);
#endif
ctx->eval_obj = gc_copy_value (ctx, ctx->eval_obj, from_base, from_end, to_base, &to_free, to_end);
#ifdef DUMP_GC_DETAIL
printf(" roots: native_error_proto\n"); fflush(stdout);
#endif
for (int i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
ctx->native_error_proto[i] = gc_copy_value (ctx, ctx->native_error_proto[i], from_base, from_end, to_base, &to_free, to_end);
}
/* Copy class prototypes */
#ifdef DUMP_GC_DETAIL
printf(" roots: class_proto (count=%d)\n", rt->class_count); fflush(stdout);
#endif
for (int i = 0; i < rt->class_count; i++) {
ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], from_base, from_end, to_base, &to_free, to_end);
}
/* Copy value stack */
#ifdef DUMP_GC_DETAIL
printf(" roots: value_stack (top=%d)\n", ctx->value_stack_top); fflush(stdout);
#endif
for (int i = 0; i < ctx->value_stack_top; i++) {
ctx->value_stack[i] = gc_copy_value (ctx, ctx->value_stack[i], from_base, from_end, to_base, &to_free, to_end);
}
/* Copy frame stack references */
#ifdef DUMP_GC_DETAIL
printf(" roots: frame_stack (top=%d)\n", ctx->frame_stack_top); fflush(stdout);
#endif
for (int i = 0; i <= ctx->frame_stack_top; i++) {
struct VMFrame *frame = &ctx->frame_stack[i];
frame->cur_func = gc_copy_value (ctx, frame->cur_func, from_base, from_end, to_base, &to_free, to_end);
@@ -2395,20 +2468,40 @@ static int ctx_gc (JSContext *ctx) {
}
/* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */
#ifdef DUMP_GC_DETAIL
printf(" roots: top_gc_ref\n"); fflush(stdout);
#endif
for (JSGCRef *ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
ref->val = gc_copy_value (ctx, ref->val, 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);
#endif
for (JSGCRef *ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) {
ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end);
}
/* Cheney scan: scan copied objects to find more references */
uint8_t *scan = to_base;
#ifdef DUMP_GC_DETAIL
printf(" scan: to_base=%p to_free=%p to_end=%p\n", (void*)to_base, (void*)to_free, (void*)to_end);
fflush(stdout);
#endif
while (scan < to_free) {
objhdr_t scan_hdr = *(objhdr_t *)scan;
#ifdef DUMP_GC_DETAIL
printf(" scan %p: type=%d hdr=0x%llx", (void*)scan, objhdr_type(scan_hdr), (unsigned long long)scan_hdr);
fflush(stdout);
#endif
size_t obj_size = gc_object_size (scan);
#ifdef DUMP_GC_DETAIL
printf(" size=%zu\n", obj_size);
fflush(stdout);
#endif
gc_scan_object (ctx, scan, from_base, from_end, to_base, &to_free, to_end);
scan += gc_object_size (scan);
scan += obj_size;
}
/* Return old block to buddy allocator */
@@ -2426,21 +2519,35 @@ static int ctx_gc (JSContext *ctx) {
ctx->heap_end = to_end;
ctx->current_block_size = new_size;
#ifdef DUMP_GC_DETAIL
/* Verify global_obj is in valid heap range after GC */
if (JS_IsPtr(ctx->global_obj)) {
void *gptr = JS_VALUE_GET_PTR(ctx->global_obj);
if ((uint8_t*)gptr < to_base || (uint8_t*)gptr >= to_free) {
printf(" WARNING: global_obj=%p outside [%p, %p) after GC!\n",
gptr, (void*)to_base, (void*)to_free);
fflush(stdout);
}
}
#endif
/* If <20% recovered, double next block size for future allocations */
int will_grow = 0;
if (old_used > 0 && recovered < old_used / 5) {
size_t doubled = new_size * 2;
if (doubled <= (1ULL << BUDDY_MAX_ORDER)) {
ctx->next_block_size = doubled;
will_grow = 1;
}
}
#ifdef DUMP_GC
printf ("GC: old_heap=%zu new_heap=%zu live=%zu recovered=%zu (%.1f%%)\n",
printf ("\nGC: %zu -> %zu bytes, recovered %zu (%.1f%%)%s\n",
old_heap_size,
new_size,
new_used,
recovered,
old_used > 0 ? (recovered * 100.0 / old_used) : 0.0);
old_used > 0 ? (recovered * 100.0 / old_used) : 0.0,
will_grow ? ", heap will grow" : "");
#endif
return 0;
@@ -2613,13 +2720,13 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
ctx->heap_free = ctx->heap_base;
ctx->heap_end = ctx->heap_base + ctx->current_block_size;
#ifdef DUMP_GC
#ifdef DUMP_GC_DETAIL
printf("Context init: heap_base=%p heap_end=%p size=%zu\n", (void*)ctx->heap_base, (void*)ctx->heap_end, ctx->current_block_size);
#endif
JS_AddIntrinsicBasicObjects (ctx);
#ifdef DUMP_GC
#ifdef DUMP_GC_DETAIL
printf("After BasicObjects: heap_base=%p heap_end=%p heap_free=%p\n", (void*)ctx->heap_base, (void*)ctx->heap_end, (void*)ctx->heap_free);
#endif