copy gc
This commit is contained in:
133
source/quickjs.c
133
source/quickjs.c
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user