add function aware to gc

This commit is contained in:
2026-02-03 01:20:24 -06:00
parent e680439a9b
commit e4b7de46f6
2 changed files with 84 additions and 74 deletions

View File

@@ -126,7 +126,7 @@ void script_startup(cell_rt *prt)
rt = JS_NewRuntime(); rt = JS_NewRuntime();
JSContext *js = JS_NewContextRaw(rt); JSContext *js = JS_NewContextRaw(rt);
JS_SetInterruptHandler(rt, actor_interrupt_cb, prt); JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
JS_AddIntrinsicBaseObjects(js); JS_AddIntrinsicBaseObjects(js);
JS_AddIntrinsicEval(js); JS_AddIntrinsicEval(js);

View File

@@ -486,7 +486,9 @@ static inline objhdr_t *chase(JSValue v) {
objhdr_t *oh = JS_VALUE_GET_PTR(v); objhdr_t *oh = JS_VALUE_GET_PTR(v);
if (objhdr_type(*oh) != OBJ_FORWARD) return oh; if (objhdr_type(*oh) != OBJ_FORWARD) return oh;
do { do {
oh = (objhdr_t*)objhdr_fwd_ptr(*oh); objhdr_t *next = (objhdr_t*)objhdr_fwd_ptr(*oh);
if (!next) return oh; /* NULL forward pointer (stale reference bug) */
oh = next;
} while (objhdr_type(*oh) == OBJ_FORWARD); } while (objhdr_type(*oh) == OBJ_FORWARD);
return oh; return oh;
} }
@@ -2200,13 +2202,17 @@ static void buddy_destroy (BuddyAllocator *b) {
/* Forward declarations for GC helpers */ /* Forward declarations for GC helpers */
static int ctx_gc (JSContext *ctx); static int ctx_gc (JSContext *ctx);
static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); 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);
static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
static size_t gc_object_size (void *ptr); static size_t gc_object_size (void *ptr);
/* Alignment for GC object sizes - must match max alignment requirement */
#define GC_ALIGN 8
static inline size_t gc_align_up (size_t n) { return (n + GC_ALIGN - 1) & ~(GC_ALIGN - 1); }
/* Get size of a heap object based on its type */ /* Get size of a heap object based on its type */
static size_t gc_object_size (void *ptr) { static size_t gc_object_size (void *ptr) {
objhdr_t *hdr = ptr; objhdr_t hdr = *(objhdr_t *)ptr;
uint8_t type = objhdr_type (hdr); uint8_t type = objhdr_type (hdr);
uint64_t cap = objhdr_cap56 (hdr); uint64_t cap = objhdr_cap56 (hdr);
@@ -2214,97 +2220,75 @@ static size_t gc_object_size (void *ptr) {
case OBJ_ARRAY: { case OBJ_ARRAY: {
/* JSArray + inline values array. Cap is element capacity. */ /* JSArray + inline values array. Cap is element capacity. */
size_t values_size = sizeof (JSValue) * cap; size_t values_size = sizeof (JSValue) * cap;
return sizeof (JSArray) + values_size; return gc_align_up (sizeof (JSArray) + values_size);
} }
case OBJ_TEXT: { case OBJ_TEXT: {
/* JSText: header + pad + hdr + length + packed chars */ /* JSText: header + pad + hdr + length + packed chars */
size_t word_count = (cap + 1) / 2; size_t word_count = (cap + 1) / 2;
return sizeof (JSText) + word_count * sizeof (uint64_t); return gc_align_up (sizeof (JSText) + word_count * sizeof (uint64_t));
} }
case OBJ_RECORD: { case OBJ_RECORD: {
/* JSRecord + inline tab. Cap is mask, so tab size is mask+1 entries. */ /* JSRecord + inline tab. Cap is mask, so tab size is mask+1 entries. */
size_t tab_size = sizeof (JSRecordEntry) * (cap + 1); size_t tab_size = sizeof (JSRecordEntry) * (cap + 1);
return sizeof (JSRecord) + tab_size; return gc_align_up (sizeof (JSRecord) + tab_size);
} }
case OBJ_FUNCTION: case OBJ_FUNCTION:
case OBJ_CODE: return gc_align_up (sizeof (JSFunction));
case OBJ_FRAME:
case OBJ_BLOB:
default: default:
/* Conservative estimate for unknown types */ /* Unknown type - fatal error, heap is corrupt or scan desync'd */
return 64; fprintf (stderr, "gc_object_size: unknown object type %d at %p\n", type, ptr);
abort ();
} }
} }
/* Copy a single value, returning the new value with updated pointer if needed. /* Copy a single value, returning the new value with updated pointer if needed.
Follows and collapses forward pointer chains. */ Simple Cheney: if already forwarded, return new address; else copy and forward.
static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *to_base, from_base/from_end define the OLD heap bounds (where objects are being copied FROM). */
uint8_t **to_free, uint8_t *to_end) { static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end,
if (!JS_IsPtr (v)) return v; /* Immediate value - no copy needed */ uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) {
if (!JS_IsPtr (v)) {
return v; /* Immediate value - no copy needed */
}
void *ptr = JS_VALUE_GET_PTR (v); void *ptr = JS_VALUE_GET_PTR (v);
if (is_stone_ptr (ctx, ptr)) return v; /* Stone memory - don't copy */ if (is_stone_ptr (ctx, ptr)) return v; /* Stone memory - don't copy */
/* Check if pointer is in current heap (not external allocation) */ /* Check if pointer is in old heap (from-space) */
if ((uint8_t *)ptr < ctx->heap_base || (uint8_t *)ptr >= ctx->heap_end) { if ((uint8_t *)ptr < from_base || (uint8_t *)ptr >= from_end) {
/* External allocation (using js_malloc_rt) - keep reference */ /* External allocation - keep reference as-is */
return v; return v;
} }
objhdr_t *hdr_ptr = ptr; objhdr_t *hdr_ptr = ptr;
objhdr_t hdr = *hdr_ptr; objhdr_t hdr = *hdr_ptr;
/* Collect forward pointer chain */ /* Already forwarded? Return the new location. */
objhdr_t *chain[64]; if (objhdr_type (hdr) == OBJ_FORWARD) {
int chain_len = 0;
while (objhdr_type (hdr) == OBJ_FORWARD) {
void *fwd_target = objhdr_fwd_ptr (hdr); void *fwd_target = objhdr_fwd_ptr (hdr);
return JS_MKPTR (fwd_target);
/* Stale pointer bug: forward target is NULL (should not happen with proper rooting) */
if (!fwd_target) {
return v; /* Return original value and let caller handle it */
}
/* Check if forward target is in new space */
if ((uint8_t *)fwd_target >= to_base && (uint8_t *)fwd_target < *to_free) {
/* Target is in new space - update chain and return */
for (int i = 0; i < chain_len; i++) {
*chain[i] = objhdr_make_fwd (fwd_target);
}
return JS_MKPTR (fwd_target);
}
if (chain_len < 64) chain[chain_len++] = hdr_ptr;
hdr_ptr = (objhdr_t *)fwd_target;
hdr = *hdr_ptr;
} }
/* hdr_ptr points to real object in old space - copy it */ /* Copy object to to-space */
size_t size = gc_object_size (hdr_ptr); size_t size = gc_object_size (hdr_ptr);
if (*to_free + size > to_end) { if (*to_free + size > to_end) {
/* Should not happen if we sized new block correctly */ /* Out of space - this is a fatal error */
return v; fprintf (stderr, "gc_copy_value: out of to-space, need %zu bytes\n", size);
abort ();
} }
void *new_ptr = *to_free; void *new_ptr = *to_free;
memcpy (new_ptr, hdr_ptr, size); memcpy (new_ptr, hdr_ptr, size);
*to_free += size; *to_free += size;
/* Install forward in old location */ /* Install forward pointer in old location */
*hdr_ptr = objhdr_make_fwd (new_ptr); *hdr_ptr = objhdr_make_fwd (new_ptr);
/* Update ALL chain pointers to final location */
for (int i = 0; i < chain_len; i++) {
*chain[i] = objhdr_make_fwd (new_ptr);
}
return JS_MKPTR (new_ptr); return JS_MKPTR (new_ptr);
} }
/* Scan a copied object and update its internal references */ /* Scan a copied object and update its internal references */
static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *to_base, static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end,
uint8_t **to_free, uint8_t *to_end) { uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) {
objhdr_t hdr = *(objhdr_t *)ptr; objhdr_t hdr = *(objhdr_t *)ptr;
uint8_t type = objhdr_type (hdr); uint8_t type = objhdr_type (hdr);
@@ -2312,7 +2296,7 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *to_base,
case OBJ_ARRAY: { case OBJ_ARRAY: {
JSArray *arr = (JSArray *)ptr; JSArray *arr = (JSArray *)ptr;
for (uint32_t i = 0; i < arr->len; i++) { for (uint32_t i = 0; i < arr->len; i++) {
arr->values[i] = gc_copy_value (ctx, arr->values[i], to_base, to_free, to_end); arr->values[i] = gc_copy_value (ctx, arr->values[i], from_base, from_end, to_base, to_free, to_end);
} }
break; break;
} }
@@ -2321,7 +2305,7 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *to_base,
/* Copy prototype */ /* Copy prototype */
if (rec->proto) { if (rec->proto) {
JSValue proto_val = JS_MKPTR (rec->proto); JSValue proto_val = JS_MKPTR (rec->proto);
proto_val = gc_copy_value (ctx, proto_val, to_base, to_free, to_end); proto_val = gc_copy_value (ctx, proto_val, from_base, from_end, to_base, to_free, to_end);
rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val); rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val);
} }
/* Copy table entries */ /* Copy table entries */
@@ -2329,19 +2313,28 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *to_base,
for (uint32_t i = 0; i <= mask; i++) { for (uint32_t i = 0; i <= mask; i++) {
JSValue k = rec->slots[i].key; JSValue k = rec->slots[i].key;
if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) {
rec->slots[i].key = gc_copy_value (ctx, k, to_base, to_free, to_end); rec->slots[i].key = gc_copy_value (ctx, k, from_base, from_end, to_base, to_free, to_end);
rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, to_base, to_free, to_end); rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, from_base, from_end, to_base, to_free, to_end);
} }
} }
break; break;
} }
case OBJ_FUNCTION: {
JSFunction *fn = (JSFunction *)ptr;
/* Scan the function name */
fn->name = gc_copy_value (ctx, fn->name, from_base, from_end, to_base, to_free, to_end);
/* Note: function_bytecode and var_refs are allocated via js_malloc, not bump allocator */
break;
}
case OBJ_TEXT: case OBJ_TEXT:
case OBJ_BLOB: case OBJ_BLOB:
case OBJ_CODE: case OBJ_CODE:
/* No internal references to scan */ /* No internal references to scan */
break; break;
default: default:
break; /* Unknown type during scan - fatal error */
fprintf (stderr, "gc_scan_object: unknown object type %d at %p\n", type, ptr);
abort ();
} }
} }
@@ -2351,6 +2344,14 @@ static int ctx_gc (JSContext *ctx) {
size_t old_used = ctx->heap_free - ctx->heap_base; size_t old_used = ctx->heap_free - ctx->heap_base;
size_t old_heap_size = ctx->current_block_size; size_t old_heap_size = ctx->current_block_size;
/* Save OLD heap bounds before allocating new block */
uint8_t *from_base = ctx->heap_base;
uint8_t *from_end = ctx->heap_end;
#ifdef DUMP_GC
printf("ctx_gc: from_base=%p from_end=%p size=%zu\n", (void*)from_base, (void*)from_end, old_heap_size);
#endif
/* Request new block from runtime */ /* Request new block from runtime */
size_t new_size = ctx->next_block_size; size_t new_size = ctx->next_block_size;
uint8_t *new_block = buddy_alloc (&rt->buddy, new_size); uint8_t *new_block = buddy_alloc (&rt->buddy, new_size);
@@ -2366,55 +2367,55 @@ static int ctx_gc (JSContext *ctx) {
uint8_t *to_end = new_block + new_size; uint8_t *to_end = new_block + new_size;
/* Copy roots: global object, class prototypes, exception, etc. */ /* Copy roots: global object, class prototypes, exception, etc. */
ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, to_base, &to_free, to_end); ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, from_base, from_end, to_base, &to_free, to_end);
ctx->global_var_obj = gc_copy_value (ctx, ctx->global_var_obj, to_base, &to_free, to_end); ctx->global_var_obj = gc_copy_value (ctx, ctx->global_var_obj, from_base, from_end, to_base, &to_free, to_end);
ctx->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, to_base, &to_free, to_end); ctx->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, from_base, from_end, to_base, &to_free, to_end);
ctx->throw_type_error = gc_copy_value (ctx, ctx->throw_type_error, to_base, &to_free, to_end); ctx->throw_type_error = gc_copy_value (ctx, ctx->throw_type_error, from_base, from_end, to_base, &to_free, to_end);
ctx->eval_obj = gc_copy_value (ctx, ctx->eval_obj, to_base, &to_free, to_end); ctx->eval_obj = gc_copy_value (ctx, ctx->eval_obj, from_base, from_end, to_base, &to_free, to_end);
for (int i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { for (int i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
ctx->native_error_proto[i] = gc_copy_value (ctx, ctx->native_error_proto[i], to_base, &to_free, to_end); 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 */ /* Copy class prototypes */
for (int i = 0; i < rt->class_count; i++) { for (int i = 0; i < rt->class_count; i++) {
ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], to_base, &to_free, to_end); 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 */ /* Copy value stack */
for (int i = 0; i < ctx->value_stack_top; i++) { for (int i = 0; i < ctx->value_stack_top; i++) {
ctx->value_stack[i] = gc_copy_value (ctx, ctx->value_stack[i], to_base, &to_free, to_end); 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 */ /* Copy frame stack references */
for (int i = 0; i <= ctx->frame_stack_top; i++) { for (int i = 0; i <= ctx->frame_stack_top; i++) {
struct VMFrame *frame = &ctx->frame_stack[i]; struct VMFrame *frame = &ctx->frame_stack[i];
frame->cur_func = gc_copy_value (ctx, frame->cur_func, to_base, &to_free, to_end); frame->cur_func = gc_copy_value (ctx, frame->cur_func, from_base, from_end, to_base, &to_free, to_end);
frame->this_obj = gc_copy_value (ctx, frame->this_obj, to_base, &to_free, to_end); frame->this_obj = gc_copy_value (ctx, frame->this_obj, from_base, from_end, to_base, &to_free, to_end);
} }
/* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */ /* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */
for (JSGCRef *ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { for (JSGCRef *ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
ref->val = gc_copy_value (ctx, ref->val, to_base, &to_free, to_end); ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end);
} }
/* Copy JS_AddGCRef/JS_DeleteGCRef roots */ /* Copy JS_AddGCRef/JS_DeleteGCRef roots */
for (JSGCRef *ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { for (JSGCRef *ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) {
ref->val = gc_copy_value (ctx, ref->val, to_base, &to_free, to_end); 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 */ /* Cheney scan: scan copied objects to find more references */
uint8_t *scan = to_base; uint8_t *scan = to_base;
while (scan < to_free) { while (scan < to_free) {
gc_scan_object (ctx, scan, to_base, &to_free, to_end); gc_scan_object (ctx, scan, from_base, from_end, to_base, &to_free, to_end);
scan += gc_object_size (scan); scan += gc_object_size (scan);
} }
/* Return old block to buddy allocator */ /* Return old block to buddy allocator */
#ifdef POISON_HEAP #ifdef POISON_HEAP
gc_poison_region(ctx->heap_base, ctx->current_block_size); gc_poison_region(from_base, old_heap_size);
#endif #endif
buddy_free (&rt->buddy, ctx->heap_base, ctx->current_block_size); buddy_free (&rt->buddy, from_base, old_heap_size);
/* Update context with new block */ /* Update context with new block */
size_t new_used = to_free - to_base; size_t new_used = to_free - to_base;
@@ -2612,7 +2613,16 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
ctx->heap_free = ctx->heap_base; ctx->heap_free = ctx->heap_base;
ctx->heap_end = ctx->heap_base + ctx->current_block_size; ctx->heap_end = ctx->heap_base + ctx->current_block_size;
#ifdef DUMP_GC
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); JS_AddIntrinsicBasicObjects (ctx);
#ifdef DUMP_GC
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
return ctx; return ctx;
} }