This commit is contained in:
2026-02-03 00:34:07 -06:00
parent 893deaec23
commit ae11504e00
2 changed files with 95 additions and 39 deletions

View File

@@ -117,7 +117,6 @@ typedef struct JSVarRef JSVarRef;
#define objhdr_fwd_ptr(h) ((void*)(uintptr_t)(((h) >> OBJHDR_FWD_PTR_SHIFT) & OBJHDR_FWD_PTR_MASK))
#define objhdr_make_fwd(ptr) (((objhdr_t)(uintptr_t)(ptr) << OBJHDR_FWD_PTR_SHIFT) | OBJ_FORWARD)
/* Extract pointer (clear low bits) */
#define JS_VALUE_GET_PTR(v) ((void *)((v) & ~((JSValue)(JSW - 1))))
@@ -1848,9 +1847,16 @@ void *js_malloc (JSContext *ctx, size_t size) {
/* Check if we have space in current block */
if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) {
/* TODO: trigger GC or allocate new block */
JS_ThrowOutOfMemory (ctx);
return NULL;
/* Trigger GC to reclaim memory */
if (ctx_gc (ctx) < 0) {
JS_ThrowOutOfMemory (ctx);
return NULL;
}
/* Re-check after GC */
if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) {
JS_ThrowOutOfMemory (ctx);
return NULL;
}
}
void *ptr = ctx->heap_free;
@@ -2159,8 +2165,8 @@ static void buddy_destroy (BuddyAllocator *b) {
/* Forward declarations for GC helpers */
static int ctx_gc (JSContext *ctx);
static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end);
static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end);
static JSValue gc_copy_value (JSContext *ctx, JSValue v, 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 size_t gc_object_size (void *ptr);
/* Get size of a heap object based on its type */
@@ -2195,8 +2201,10 @@ static size_t gc_object_size (void *ptr) {
}
}
/* Copy a single value, returning the new value with updated pointer if needed */
static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end) {
/* Copy a single value, returning the new value with updated pointer if needed.
Follows and collapses forward pointer chains. */
static JSValue gc_copy_value (JSContext *ctx, JSValue v, 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);
@@ -2204,39 +2212,59 @@ static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t **to_free, uint
/* Check if pointer is in current heap (not external allocation) */
if ((uint8_t *)ptr < ctx->heap_base || (uint8_t *)ptr >= ctx->heap_end) {
/* External allocation (using js_malloc) - keep reference */
/* External allocation (using js_malloc_rt) - keep reference */
return v;
}
objhdr_t *hdr_ptr = ptr;
objhdr_t hdr = *hdr_ptr;
/* Already forwarded? */
if (objhdr_type (hdr) == OBJ_FORWARD) {
/* Extract forwarding address from forward header */
void *new_ptr = objhdr_fwd_ptr (hdr);
return JS_MKPTR (new_ptr);
/* Collect forward pointer chain */
objhdr_t *chain[64];
int chain_len = 0;
while (objhdr_type (hdr) == OBJ_FORWARD) {
void *fwd_target = objhdr_fwd_ptr (hdr);
/* 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;
}
/* Copy object to new space */
size_t size = gc_object_size (ptr);
/* hdr_ptr points to real object in old space - copy it */
size_t size = gc_object_size (hdr_ptr);
if (*to_free + size > to_end) {
/* Should not happen if we sized new block correctly */
return v;
}
void *new_ptr = *to_free;
memcpy (new_ptr, ptr, size);
memcpy (new_ptr, hdr_ptr, size);
*to_free += size;
/* Install forwarding pointer in old location */
/* Install forward in old location */
*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);
}
/* Scan a copied object and update its internal references */
static void gc_scan_object (JSContext *ctx, void *ptr, 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) {
objhdr_t hdr = *(objhdr_t *)ptr;
uint8_t type = objhdr_type (hdr);
@@ -2244,7 +2272,7 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t **to_free, uint8_
case OBJ_ARRAY: {
JSArray *arr = (JSArray *)ptr;
for (uint32_t i = 0; i < arr->len; i++) {
arr->values[i] = gc_copy_value (ctx, arr->values[i], to_free, to_end);
arr->values[i] = gc_copy_value (ctx, arr->values[i], to_base, to_free, to_end);
}
break;
}
@@ -2253,7 +2281,7 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t **to_free, uint8_
/* Copy prototype */
if (rec->proto) {
JSValue proto_val = JS_MKPTR (rec->proto);
proto_val = gc_copy_value (ctx, proto_val, to_free, to_end);
proto_val = gc_copy_value (ctx, proto_val, to_base, to_free, to_end);
rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val);
}
/* Copy table entries */
@@ -2261,8 +2289,8 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t **to_free, uint8_
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)) {
rec->slots[i].key = gc_copy_value (ctx, k, to_free, to_end);
rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, to_free, to_end);
rec->slots[i].key = gc_copy_value (ctx, k, to_base, to_free, to_end);
rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, to_base, to_free, to_end);
}
}
break;
@@ -2281,6 +2309,7 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t **to_free, uint8_
static int ctx_gc (JSContext *ctx) {
JSRuntime *rt = ctx->rt;
size_t old_used = ctx->heap_free - ctx->heap_base;
size_t old_heap_size = ctx->current_block_size;
/* Request new block from runtime */
size_t new_size = ctx->next_block_size;
@@ -2297,37 +2326,47 @@ static int ctx_gc (JSContext *ctx) {
uint8_t *to_end = new_block + new_size;
/* Copy roots: global object, class prototypes, exception, etc. */
ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, &to_free, to_end);
ctx->global_var_obj = gc_copy_value (ctx, ctx->global_var_obj, &to_free, to_end);
ctx->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, &to_free, to_end);
ctx->throw_type_error = gc_copy_value (ctx, ctx->throw_type_error, &to_free, to_end);
ctx->eval_obj = gc_copy_value (ctx, ctx->eval_obj, &to_free, to_end);
ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, 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->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, 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->eval_obj = gc_copy_value (ctx, ctx->eval_obj, to_base, &to_free, to_end);
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_free, to_end);
ctx->native_error_proto[i] = gc_copy_value (ctx, ctx->native_error_proto[i], to_base, &to_free, to_end);
}
/* Copy class prototypes */
for (int i = 0; i < rt->class_count; i++) {
ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], &to_free, to_end);
ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], to_base, &to_free, to_end);
}
/* Copy value stack */
for (int i = 0; i < ctx->value_stack_top; i++) {
ctx->value_stack[i] = gc_copy_value (ctx, ctx->value_stack[i], &to_free, to_end);
ctx->value_stack[i] = gc_copy_value (ctx, ctx->value_stack[i], to_base, &to_free, to_end);
}
/* Copy frame stack references */
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, &to_free, to_end);
frame->this_obj = gc_copy_value (ctx, frame->this_obj, &to_free, to_end);
frame->cur_func = gc_copy_value (ctx, frame->cur_func, to_base, &to_free, to_end);
frame->this_obj = gc_copy_value (ctx, frame->this_obj, to_base, &to_free, to_end);
}
/* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */
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);
}
/* Copy JS_AddGCRef/JS_DeleteGCRef roots */
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);
}
/* Cheney scan: scan copied objects to find more references */
uint8_t *scan = to_base;
while (scan < to_free) {
gc_scan_object (ctx, scan, &to_free, to_end);
gc_scan_object (ctx, scan, to_base, &to_free, to_end);
scan += gc_object_size (scan);
}
@@ -2352,11 +2391,12 @@ static int ctx_gc (JSContext *ctx) {
}
#ifdef DUMP_GC
printf ("GC: old_used=%zu new_used=%zu recovered=%zu new_block_size=%zu\n",
old_used,
printf ("GC: old_heap=%zu new_heap=%zu live=%zu recovered=%zu (%.1f%%)\n",
old_heap_size,
new_size,
new_used,
recovered,
new_size);
old_used > 0 ? (recovered * 100.0 / old_used) : 0.0);
#endif
return 0;
@@ -2443,10 +2483,21 @@ void JS_FreeRuntime (JSRuntime *rt) {
buddy_destroy (&rt->buddy);
}
JSContext *JS_NewContextRaw (JSRuntime *rt) {
JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
JSContext *ctx;
int i;
/* Round up to buddy allocator minimum */
size_t min_size = 1ULL << BUDDY_MIN_ORDER;
if (heap_size < min_size) heap_size = min_size;
/* Round up to power of 2 for buddy allocator */
size_t actual = min_size;
while (actual < heap_size && actual < (1ULL << BUDDY_MAX_ORDER)) {
actual <<= 1;
}
heap_size = actual;
ctx = js_mallocz_rt (sizeof (JSContext));
if (!ctx) return NULL;
@@ -2503,7 +2554,7 @@ JSContext *JS_NewContextRaw (JSRuntime *rt) {
}
/* Allocate initial heap block for bump allocation */
ctx->current_block_size = 1ULL << BUDDY_MIN_ORDER; /* 64KB */
ctx->current_block_size = heap_size;
ctx->next_block_size = ctx->current_block_size;
ctx->heap_base = buddy_alloc (&rt->buddy, ctx->current_block_size);
if (!ctx->heap_base) {
@@ -2522,6 +2573,10 @@ JSContext *JS_NewContextRaw (JSRuntime *rt) {
return ctx;
}
JSContext *JS_NewContextRaw (JSRuntime *rt) {
return JS_NewContextRawWithHeapSize (rt, 1ULL << BUDDY_MIN_ORDER);
}
JSContext *JS_NewContext (JSRuntime *rt) {
JSContext *ctx;

View File

@@ -403,6 +403,7 @@ JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id);
/* the following functions are used to select the intrinsic object to
save memory */
JSContext *JS_NewContextRaw (JSRuntime *rt);
JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size);
typedef struct JSMemoryUsage {
int64_t malloc_size, malloc_limit, memory_used_size;