wingo pushed a commit to branch wip-whippet
in repository guile.

commit 6032adede98cf0743372acc96c6a547e0e1ed7f2
Author: Andy Wingo <wi...@igalia.com>
AuthorDate: Wed Apr 23 10:47:13 2025 +0200

    semi: Tail-call an out-of-memory handler if allocation fails
---
 src/semi.c | 55 +++++++++++++++++++++++++++++++++----------------------
 1 file changed, 33 insertions(+), 22 deletions(-)

diff --git a/src/semi.c b/src/semi.c
index a4ee85f39..23ec3d01d 100644
--- a/src/semi.c
+++ b/src/semi.c
@@ -52,6 +52,7 @@ struct gc_heap {
   struct gc_heap_sizer sizer;
   struct gc_event_listener event_listener;
   void *event_listener_data;
+  void* (*allocation_failure)(struct gc_heap *, size_t);
 };
 // One mutator per space, can just store the heap in the mutator.
 struct gc_mutator {
@@ -103,9 +104,9 @@ static uintptr_t align_up(uintptr_t addr, size_t align) {
 static size_t min_size(size_t a, size_t b) { return a < b ? a : b; }
 static size_t max_size(size_t a, size_t b) { return a < b ? b : a; }
 
-static void collect(struct gc_mutator *mut, size_t for_alloc) GC_NEVER_INLINE;
-static void collect_for_alloc(struct gc_mutator *mut,
-                              size_t bytes) GC_NEVER_INLINE;
+static void collect(struct gc_mutator *mut) GC_NEVER_INLINE;
+static int collect_for_alloc(struct gc_mutator *mut,
+                             size_t bytes) GC_NEVER_INLINE;
 
 static void trace(struct gc_edge edge, struct gc_heap *heap, void *visit_data);
 
@@ -385,7 +386,7 @@ static uintptr_t resolve_finalizers(struct gc_heap *heap, 
uintptr_t grey) {
   return grey;
 }
 
-static void collect(struct gc_mutator *mut, size_t for_alloc) {
+static void collect(struct gc_mutator *mut) {
   struct gc_heap *heap = mutator_heap(mut);
   int is_minor = 0;
   int is_compacting = 1;
@@ -429,7 +430,6 @@ static void collect(struct gc_mutator *mut, size_t 
for_alloc) {
   gc_sweep_pending_ephemerons(heap->pending_ephemerons, 0, 1);
   size_t live_size = semi->live_bytes_at_last_gc;
   live_size += large_object_space_size_at_last_collection(large);
-  live_size += for_alloc;
   uint64_t pause_ns = gc_platform_monotonic_nanoseconds() - start_ns;
   HEAP_EVENT(heap, live_data_size, live_size);
   DEBUG("gc %zu: live size %zu, heap size %zu\n", heap->count, live_size,
@@ -443,30 +443,29 @@ static void collect(struct gc_mutator *mut, size_t 
for_alloc) {
   // fprintf(stderr, "%zd bytes copied\n", 
(space->size>>1)-(space->limit-space->hp));
 }
 
-static void collect_for_alloc(struct gc_mutator *mut, size_t bytes) {
-  collect(mut, bytes);
+static int collect_for_alloc(struct gc_mutator *mut, size_t bytes) {
+  collect(mut);
 
   struct semi_space *space = mutator_semi_space(mut);
-  if (bytes < space->limit - space->hp)
-    return;
+  if (bytes <= space->limit - space->hp)
+    return 1;
 
   struct gc_heap *heap = mutator_heap(mut);
   if (heap->options->common.heap_size_policy != GC_HEAP_SIZE_FIXED) {
     // Each collection can potentially resize only the inactive
     // fromspace, so if we really run out of space we will need to
     // collect again in order to resize the other half.
-    collect(mut, bytes);
-    if (bytes < space->limit - space->hp)
-      return;
+    collect(mut);
+    if (bytes <= space->limit - space->hp)
+      return 1;
   }
-  fprintf(stderr, "ran out of space, heap size %zu\n", heap->size);
-  GC_CRASH();
+  return 0;
 }
 
 void gc_collect(struct gc_mutator *mut,
                 enum gc_collection_kind requested_kind) {
   // Ignore requested kind, because we always compact.
-  collect(mut, 0);
+  collect(mut);
 }
 
 int gc_object_is_old_generation_slow(struct gc_mutator *mut,
@@ -482,8 +481,11 @@ void gc_write_barrier_slow(struct gc_mutator *mut, struct 
gc_ref obj,
 int* gc_safepoint_flag_loc(struct gc_mutator *mut) { GC_CRASH(); }
 void gc_safepoint_slow(struct gc_mutator *mut) { GC_CRASH(); }
   
-static void collect_for_large_alloc(struct gc_mutator *mut, size_t npages) {
-  collect_for_alloc(mut, npages * mutator_semi_space(mut)->page_size);
+static int collect_for_large_alloc(struct gc_mutator *mut, size_t npages) {
+  size_t bytes = npages * mutator_semi_space(mut)->page_size;
+  // Large object pages don't need a copy reserve.
+  bytes /= 2;
+  return collect_for_alloc(mut, bytes);
 }
 
 static void* allocate_large(struct gc_mutator *mut, size_t size) {
@@ -492,8 +494,9 @@ static void* allocate_large(struct gc_mutator *mut, size_t 
size) {
   struct semi_space *semi_space = heap_semi_space(heap);
 
   size_t npages = large_object_space_npages(space, size);
-  while (!semi_space_steal_pages(semi_space, npages))
-    collect_for_large_alloc(mut, npages);
+  if (!semi_space_steal_pages(semi_space, npages)
+      && !collect_for_large_alloc(mut, npages))
+    return heap->allocation_failure(heap, size);
 
   void *ret = large_object_space_alloc(space, npages, GC_TRACE_PRECISELY);
 
@@ -522,9 +525,10 @@ void* gc_allocate_slow(struct gc_mutator *mut, size_t size,
     uintptr_t addr = space->hp;
     uintptr_t new_hp = align_up (addr + size, GC_ALIGNMENT);
     if (space->limit < new_hp) {
-      // The factor of 2 is for both regions.
-      collect_for_alloc(mut, size * 2);
-      continue;
+      if (collect_for_alloc(mut, size))
+        continue;
+      struct gc_heap *heap = mutator_heap(mut);
+      return heap->allocation_failure(heap, size);
     }
     space->hp = new_hp;
     return (void *)addr;
@@ -626,6 +630,12 @@ static void ignore_async_heap_size_adjustment(struct 
gc_heap *heap,
                                               size_t size) {
 }
 
+static void* allocation_failure(struct gc_heap *heap, size_t size) {
+  fprintf(stderr, "ran out of space, heap size %zu\n", heap->size);
+  GC_CRASH();
+  return NULL;
+}
+
 static int heap_init(struct gc_heap *heap, const struct gc_options *options) {
   heap->extern_space = NULL;
   heap->pending_ephemerons_size_factor = 0.01;
@@ -642,6 +652,7 @@ static int heap_init(struct gc_heap *heap, const struct 
gc_options *options) {
                                    get_allocation_counter,
                                    ignore_async_heap_size_adjustment,
                                    NULL);
+  heap->allocation_failure = allocation_failure;
 
   return heap_prepare_pending_ephemerons(heap);
 }

Reply via email to