extensive debugging; fixed forwarding error
This commit is contained in:
217
source/quickjs.c
217
source/quickjs.c
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user