From 522ae6128a53be554437f2ebe0356ea2f567b3a7 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 3 Feb 2026 03:00:46 -0600 Subject: [PATCH] add FORCE_GC_AT_MALLOC logic --- source/quickjs.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index aee33d5d..9539d436 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -82,7 +82,7 @@ */ // #define DUMP_BYTECODE (1) /* dump GC summary: old/new heap, recovery %, heap growth */ -#define DUMP_GC +//#define DUMP_GC /* dump detailed GC: roots, scanning, object traversal (implies DUMP_GC) */ // #define DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL @@ -97,7 +97,7 @@ // #define DUMP_ROPE_REBALANCE /* test the GC by forcing it before each object allocation */ -// #define FORCE_GC_AT_MALLOC +#define FORCE_GC_AT_MALLOC #define POISON_HEAP /* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */ @@ -1055,7 +1055,7 @@ void *js_realloc (JSContext *ctx, void *ptr, size_t size) { ============================================================ */ /* Forward declaration for ctx_gc */ -static int ctx_gc (JSContext *ctx); +static int ctx_gc (JSContext *ctx, int allow_grow); /* JS_MarkValue - mark a value during GC traversal. With copying GC, this is a no-op as we discover live objects by tracing. */ @@ -1912,10 +1912,23 @@ void *js_malloc (JSContext *ctx, size_t size) { /* Align size to 8 bytes */ size = (size + 7) & ~7; +#ifdef FORCE_GC_AT_MALLOC + /* Force GC on every allocation for testing - but don't grow heap unless needed */ + int need_space = (uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end; + if (ctx_gc(ctx, need_space) < 0) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + /* Check if we have space after GC */ + if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } +#else /* Check if we have space in current block */ if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { /* Trigger GC to reclaim memory */ - if (ctx_gc (ctx) < 0) { + if (ctx_gc (ctx, 1) < 0) { JS_ThrowOutOfMemory (ctx); return NULL; } @@ -1925,6 +1938,7 @@ void *js_malloc (JSContext *ctx, size_t size) { return NULL; } } +#endif void *ptr = ctx->heap_free; ctx->heap_free = (uint8_t *)ctx->heap_free + size; @@ -2266,7 +2280,7 @@ static void heap_block_free(JSRuntime *rt, void *ptr, size_t size) { ============================================================ */ /* Forward declarations for GC helpers */ -static int ctx_gc (JSContext *ctx); +static int ctx_gc (JSContext *ctx, int allow_grow); 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 *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); @@ -2432,8 +2446,9 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8 } } -/* Cheney copying GC - collect garbage and compact live objects */ -static int ctx_gc (JSContext *ctx) { +/* Cheney copying GC - collect garbage and compact live objects + allow_grow: if true, grow heap when <20% recovered; if false, keep same size */ +static int ctx_gc (JSContext *ctx, int allow_grow) { JSRuntime *rt = ctx->rt; size_t old_used = ctx->heap_free - ctx->heap_base; size_t old_heap_size = ctx->current_block_size; @@ -2588,9 +2603,10 @@ static int ctx_gc (JSContext *ctx) { } #endif - /* If <20% recovered, double next block size for future allocations */ + /* If <20% recovered, double next block size for future allocations + But only if allow_grow is set (i.e., GC was triggered due to low space) */ int will_grow = 0; - if (old_used > 0 && recovered < old_used / 5) { + if (allow_grow && old_used > 0 && recovered < old_used / 5) { size_t doubled = new_size * 2; if (doubled <= (1ULL << BUDDY_MAX_ORDER)) { ctx->next_block_size = doubled;