diff --git a/source/runtime.c b/source/runtime.c index 069bb0f6..aa3b30f1 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -724,10 +724,27 @@ void *js_malloc (JSContext *ctx, size_t size) { JS_ThrowOutOfMemory (ctx); return NULL; } - /* Re-check after GC */ + /* Re-check after GC — if still no room, grow and retry. + The second GC is cheap: data was just compacted so there is + almost no garbage to skip. */ if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { - JS_ThrowOutOfMemory (ctx); - return NULL; + size_t need = (size_t)((uint8_t *)ctx->heap_free - (uint8_t *)ctx->heap_base) + size; + size_t ns = ctx->current_block_size; + while (ns < need && ns < (1ULL << BUDDY_MAX_ORDER)) + ns *= 2; + ctx->next_block_size = ns; + if (ctx_gc (ctx, 1, size) < 0) { + JS_ThrowOutOfMemory (ctx); + return NULL; + } + /* The retry pass relocates compacted data — 0% recovery is expected. + Reset next_block_size so the poor-recovery heuristic inside ctx_gc + doesn't cascade into further unnecessary doubling. */ + ctx->next_block_size = ctx->current_block_size; + if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { + JS_ThrowOutOfMemory (ctx); + return NULL; + } } } #endif @@ -1167,15 +1184,17 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { 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. - When allow_grow is set and the pending allocation won't fit in the - current next_block_size, jump straight to a block that can hold - live_data + alloc_size instead of doubling one step at a time. */ - size_t new_size = ctx->next_block_size; + /* Size the new block. Start at current_block_size (guaranteed >= used + portion, so all live data fits). Only grow when: + - next_block_size was bumped by the poor-recovery heuristic, or + - alloc_size alone exceeds the block (rare large allocation). + Crucially, do NOT add live_est to the sizing — it counts garbage + and causes exponential heap growth even with excellent recovery. */ + size_t new_size = ctx->current_block_size; if (allow_grow) { - size_t live_est = (size_t)(from_end - from_base); /* upper bound on live data */ - size_t need = live_est + alloc_size; - while (new_size < need && new_size < (1ULL << BUDDY_MAX_ORDER)) + if (ctx->next_block_size > new_size) + new_size = ctx->next_block_size; + while (new_size < alloc_size && new_size < (1ULL << BUDDY_MAX_ORDER)) new_size *= 2; } #ifdef POISON_HEAP @@ -1324,12 +1343,15 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { } #ifdef DUMP_GC - printf ("\nGC: %zu -> %zu bytes, recovered %zu (%.1f%%)%s\n", + printf ("\nGC: %zu -> %zu bytes (used %zu -> %zu), recovered %zu (%.1f%%)%s\n", old_heap_size, new_size, + old_used, + new_used, recovered, old_used > 0 ? (recovered * 100.0 / old_used) : 0.0, will_grow ? ", heap will grow" : ""); + fflush(stdout); #endif return 0;