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

commit 9b7f7f7554af58296dc88cfaa8d74dd0d8933254
Author: Andy Wingo <wi...@pobox.com>
AuthorDate: Thu Apr 17 14:37:39 2025 +0200

    Switch statistics collection, GC hooks to use Whippet API
    
    * libguile/gc.c: Define an event listener that collects basic
    statistics, runs C hooks, and arranges to run the Scheme hook if it is
    nonempty.
    (scm_gc_stats): Fetch statistics from the gathered event data.
    (scm_gc_dump): Use scm_basic_stats_print.
    (scm_storage_prehistory): Fix indentation.
    (scm_init_gc_protect_object): Remove dead code.
    (queue_after_gc_hook): Not really needed, as we have an after-GC C event
    to run the C hooks.  Scheme hook activation is inlined into the event
    listener.
    (start_gc_timer, accumulate_gc_timer): No need any more.
    (scm_init_gc): Simplify hook registration.
---
 libguile/gc.c | 394 +++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 255 insertions(+), 139 deletions(-)

diff --git a/libguile/gc.c b/libguile/gc.c
index e10ef54f0..33cd09399 100644
--- a/libguile/gc.c
+++ b/libguile/gc.c
@@ -67,6 +67,24 @@
 #include <gc/gc_mark.h>
 
 
+
+
+struct scm_gc_event_listener {
+  struct gc_basic_stats stats;
+  uint64_t last_allocation_counter;
+};
+
+struct scm_gc_event_listener_mutator {
+  struct scm_gc_event_listener *scm_listener;
+  void *stats;
+};
+
+static struct gc_heap *the_gc_heap;
+static struct scm_gc_event_listener the_gc_event_listener;
+
+
+
+
 /* Size in bytes of the initial heap.  This should be about the size of
    result of 'guile -c "(display (assq-ref (gc-stats)
    'heap-total-allocated))"'.  */
@@ -87,6 +105,190 @@ int scm_debug_cells_gc_interval = 0;
    garbage collection.  */
 static SCM scm_protects;
 
+/* Hooks.  */
+scm_t_c_hook scm_before_gc_c_hook;
+scm_t_c_hook scm_before_mark_c_hook;
+scm_t_c_hook scm_before_sweep_c_hook;
+scm_t_c_hook scm_after_sweep_c_hook;
+scm_t_c_hook scm_after_gc_c_hook;
+
+SCM scm_after_gc_hook;
+
+static SCM after_gc_async_cell;
+
+
+
+
+static void
+scm_gc_event_listener_init (void *data, size_t heap_size)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_init (&scm_listener->stats, heap_size);
+}
+
+static void
+scm_gc_event_listener_requesting_stop (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_requesting_stop (&scm_listener->stats);
+}
+
+static inline void
+scm_gc_event_listener_waiting_for_stop (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_waiting_for_stop (&scm_listener->stats);
+}
+
+static inline void
+scm_gc_event_listener_mutators_stopped (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_mutators_stopped (&scm_listener->stats);
+  scm_c_hook_run (&scm_before_gc_c_hook, NULL);
+  scm_listener->last_allocation_counter = gc_allocation_counter (the_gc_heap);
+}
+
+static inline void
+scm_gc_event_listener_prepare_gc (void *data, enum gc_collection_kind kind)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_prepare_gc (&scm_listener->stats, kind);
+}
+
+static inline void
+scm_gc_event_listener_roots_traced (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_roots_traced (&scm_listener->stats);
+}
+
+static inline void
+scm_gc_event_listener_heap_traced (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_heap_traced (&scm_listener->stats);
+}
+
+static inline void
+scm_gc_event_listener_ephemerons_traced (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_ephemerons_traced (&scm_listener->stats);
+}
+
+static inline void
+scm_gc_event_listener_finalizers_traced (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_finalizers_traced (&scm_listener->stats);
+}
+
+static inline void
+scm_gc_event_listener_restarting_mutators (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_restarting_mutators (&scm_listener->stats);
+
+  /* Run any C hooks.  The mutator is not yet let go, so we can't
+     allocate here.  */
+  scm_c_hook_run (&scm_after_gc_c_hook, NULL);
+
+  /* If there are Scheme hooks and we have a current Guile thread,
+     enqueue those to be run on the current thread.  */
+  scm_thread *t = SCM_I_CURRENT_THREAD;
+  if (t && scm_is_false (SCM_CDR (after_gc_async_cell)) &&
+      scm_is_false (scm_hook_empty_p (scm_after_gc_hook)))
+    {
+      SCM_SETCDR (after_gc_async_cell, t->pending_asyncs);
+      t->pending_asyncs = after_gc_async_cell;
+    }
+}
+
+static inline void*
+scm_gc_event_listener_mutator_added (void *data)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  struct scm_gc_event_listener_mutator *mutator = malloc (sizeof(*mutator));
+  if (!mutator) abort();
+  mutator->scm_listener = scm_listener;
+  mutator->stats = gc_basic_stats_mutator_added (&scm_listener->stats);
+  return mutator;
+}
+
+static inline void
+scm_gc_event_listener_mutator_cause_gc (void *mutator_data)
+{
+  struct scm_gc_event_listener_mutator *mutator = mutator_data;
+  gc_basic_stats_mutator_cause_gc (mutator->stats);
+}
+
+static inline void
+scm_gc_event_listener_mutator_stopping (void *mutator_data)
+{
+  struct scm_gc_event_listener_mutator *mutator = mutator_data;
+  gc_basic_stats_mutator_stopping (mutator->stats);
+}
+
+static inline void
+scm_gc_event_listener_mutator_stopped (void *mutator_data)
+{
+  struct scm_gc_event_listener_mutator *mutator = mutator_data;
+  gc_basic_stats_mutator_stopped (mutator->stats);
+}
+
+static inline void
+scm_gc_event_listener_mutator_restarted (void *mutator_data)
+{
+  struct scm_gc_event_listener_mutator *mutator = mutator_data;
+  gc_basic_stats_mutator_restarted (mutator->stats);
+}
+
+static inline void
+scm_gc_event_listener_mutator_removed (void *mutator_data)
+{
+  struct scm_gc_event_listener_mutator *mutator = mutator_data;
+  gc_basic_stats_mutator_removed (mutator->stats);
+  free(mutator);
+}
+
+static inline void
+scm_gc_event_listener_heap_resized (void *data, size_t size)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_heap_resized (&scm_listener->stats, size);
+}
+
+static inline void
+scm_gc_event_listener_live_data_size (void *data, size_t size)
+{
+  struct scm_gc_event_listener *scm_listener = data;
+  gc_basic_stats_live_data_size (&scm_listener->stats, size);
+}
+
+#define SCM_GC_EVENT_LISTENER                                         \
+  ((struct gc_event_listener) {                                       \
+    scm_gc_event_listener_init,                                       \
+    scm_gc_event_listener_requesting_stop,                            \
+    scm_gc_event_listener_waiting_for_stop,                           \
+    scm_gc_event_listener_mutators_stopped,                           \
+    scm_gc_event_listener_prepare_gc,                                 \
+    scm_gc_event_listener_roots_traced,                               \
+    scm_gc_event_listener_heap_traced,                                \
+    scm_gc_event_listener_ephemerons_traced,                          \
+    scm_gc_event_listener_finalizers_traced,                          \
+    scm_gc_event_listener_restarting_mutators,                        \
+    scm_gc_event_listener_mutator_added,                              \
+    scm_gc_event_listener_mutator_cause_gc,                           \
+    scm_gc_event_listener_mutator_stopping,                           \
+    scm_gc_event_listener_mutator_stopped,                            \
+    scm_gc_event_listener_mutator_restarted,                          \
+    scm_gc_event_listener_mutator_removed,                            \
+    scm_gc_event_listener_heap_resized,                               \
+    scm_gc_event_listener_live_data_size,                             \
+  })
+
+
 
 
 
@@ -121,33 +323,9 @@ scm_gc_after_nonlocal_exit (void)
 }
 
 
-
-
-/* Hooks.  */
-scm_t_c_hook scm_before_gc_c_hook;
-scm_t_c_hook scm_before_mark_c_hook;
-scm_t_c_hook scm_before_sweep_c_hook;
-scm_t_c_hook scm_after_sweep_c_hook;
-scm_t_c_hook scm_after_gc_c_hook;
-
-
-static void
-run_before_gc_c_hook (void)
-{
-  if (!SCM_I_CURRENT_THREAD)
-    /* GC while a thread is spinning up; punt.  */
-    return;
-
-  scm_c_hook_run (&scm_before_gc_c_hook, NULL);
-}
-
-
 /* GC Statistics Keeping
  */
 unsigned long scm_gc_ports_collected = 0;
-static long gc_time_taken = 0;
-static long gc_start_time = 0;
-
 static unsigned long protected_obj_count = 0;
 
 
@@ -160,6 +338,19 @@ SCM_SYMBOL (sym_protected_objects, "protected-objects");
 SCM_SYMBOL (sym_times, "gc-times");
 
 
+static struct scm_gc_event_listener
+get_gc_event_listener_data (void)
+{
+  struct scm_gc_event_listener data;
+  do
+    {
+      memcpy (&data, &the_gc_event_listener, sizeof data);
+      atomic_thread_fence (memory_order_seq_cst);
+    }
+  while (memcmp (&data, &the_gc_event_listener, sizeof data));
+  return data;
+}
+
 /* {Scheme Interface to GC}
  */
 extern int scm_gc_malloc_yield_percentage;
@@ -169,28 +360,29 @@ SCM_DEFINE (scm_gc_stats, "gc-stats", 0, 0, 0,
            "use of storage.\n")
 #define FUNC_NAME s_scm_gc_stats
 {
-  SCM answer;
-  GC_word heap_size, free_bytes, unmapped_bytes, bytes_since_gc, total_bytes;
-  size_t gc_times;
-
-  GC_get_heap_usage_safe (&heap_size, &free_bytes, &unmapped_bytes,
-                          &bytes_since_gc, &total_bytes);
-  gc_times = GC_get_gc_no ();
-
-  answer =
-    scm_list_n (scm_cons (sym_gc_time_taken, scm_from_long (gc_time_taken)),
-               scm_cons (sym_heap_size, scm_from_size_t (heap_size)),
-               scm_cons (sym_heap_free_size, scm_from_size_t (free_bytes)),
-               scm_cons (sym_heap_total_allocated,
-                         scm_from_size_t (total_bytes)),
-                scm_cons (sym_heap_allocated_since_gc,
-                         scm_from_size_t (bytes_since_gc)),
-               scm_cons (sym_protected_objects,
-                         scm_from_ulong (protected_obj_count)),
-               scm_cons (sym_times, scm_from_size_t (gc_times)),
-               SCM_UNDEFINED);
-
-  return answer;
+  struct scm_gc_event_listener data = get_gc_event_listener_data ();
+  double scale_usecs = scm_c_time_units_per_second / 1e6;
+
+  uint64_t gc_time_taken = data.stats.cpu_collector_usec * scale_usecs;
+  size_t heap_size = data.stats.heap_size;
+  uint64_t total_bytes = gc_allocation_counter (the_gc_heap);
+  uint64_t bytes_since_gc = total_bytes - data.last_allocation_counter;
+  ssize_t free_bytes = heap_size - data.stats.live_data_size;
+  free_bytes -= bytes_since_gc;
+  if (free_bytes < 0)
+    free_bytes = 0;
+  uint64_t gc_times =
+    data.stats.major_collection_count + data.stats.minor_collection_count;
+
+  return scm_list_n
+    (scm_cons (sym_gc_time_taken, scm_from_uint64 (gc_time_taken)),
+     scm_cons (sym_heap_size, scm_from_size_t (heap_size)),
+     scm_cons (sym_heap_free_size, scm_from_ssize_t (free_bytes)),
+     scm_cons (sym_heap_total_allocated, scm_from_uint64 (total_bytes)),
+     scm_cons (sym_heap_allocated_since_gc, scm_from_uint64 (bytes_since_gc)),
+     scm_cons (sym_protected_objects, scm_from_ulong (protected_obj_count)),
+     scm_cons (sym_times, scm_from_size_t (gc_times)),
+     SCM_UNDEFINED);
 }
 #undef FUNC_NAME
 
@@ -201,7 +393,8 @@ SCM_DEFINE (scm_gc_dump, "gc-dump", 0, 0, 0,
            "structures and memory usage to the standard output.")
 #define FUNC_NAME s_scm_gc_dump
 {
-  GC_dump ();
+  struct scm_gc_event_listener data = get_gc_event_listener_data ();
+  gc_basic_stats_print (&data.stats, stdout);
 
   return SCM_UNSPECIFIED;
 }
@@ -445,9 +638,6 @@ scm_gc_unregister_roots (SCM *b, unsigned long n)
 
 
 
-static struct gc_heap *the_gc_heap;
-static struct gc_basic_stats the_gc_stats;
-
 void
 scm_storage_prehistory (void)
 {
@@ -468,10 +658,11 @@ scm_storage_prehistory (void)
 
   struct gc_mutator *mut;
   if (!gc_init (options, NULL, &the_gc_heap, &mut,
-                GC_BASIC_STATS, &the_gc_stats)) {
-    fprintf (stderr, "Failed to initialize GC\n");
-    abort ();
-  }
+                SCM_GC_EVENT_LISTENER, &the_gc_event_listener))
+    {
+      fprintf (stderr, "Failed to initialize GC\n");
+      abort ();
+    }
 
   /* Sanity check.  */
   if (!GC_is_visible (&scm_protects))
@@ -488,91 +679,11 @@ void
 scm_init_gc_protect_object ()
 {
   scm_protects = scm_c_make_hash_table (31);
-
-#if 0
-  /* We can't have a cleanup handler since we have no thread to run it
-     in. */
-
-#ifdef HAVE_ATEXIT
-  atexit (cleanup);
-#else
-#ifdef HAVE_ON_EXIT
-  on_exit (cleanup, 0);
-#endif
-#endif
-
-#endif
-}
-
-
-
-SCM scm_after_gc_hook;
-
-static SCM after_gc_async_cell;
-
-/* The function after_gc_async_thunk causes the execution of the
- * after-gc-hook.  It is run after the gc, as soon as the asynchronous
- * events are handled by the evaluator.
- */
-static SCM
-after_gc_async_thunk (void)
-{
-  /* Fun, no? Hook-run *and* run-hook?  */
-  scm_c_hook_run (&scm_after_gc_c_hook, NULL);
-  scm_c_run_hook (scm_after_gc_hook, SCM_EOL);
-  return SCM_UNSPECIFIED;
 }
 
 
-/* The function queue_after_gc_hook is run by the scm_before_gc_c_hook
- * at the end of the garbage collection.  The only purpose of this
- * function is to mark the after_gc_async (which will eventually lead to
- * the execution of the after_gc_async_thunk).
- */
-static void *
-queue_after_gc_hook (void * hook_data SCM_UNUSED,
-                     void *fn_data SCM_UNUSED,
-                     void *data SCM_UNUSED)
-{
-  scm_thread *t = SCM_I_CURRENT_THREAD;
-
-  if (scm_is_false (SCM_CDR (after_gc_async_cell)))
-    {
-      SCM_SETCDR (after_gc_async_cell, t->pending_asyncs);
-      t->pending_asyncs = after_gc_async_cell;
-    }
-
-  return NULL;
-}
-
 
 
-static void *
-start_gc_timer (void * hook_data SCM_UNUSED,
-                void *fn_data SCM_UNUSED,
-                void *data SCM_UNUSED)
-{
-  if (!gc_start_time)
-    gc_start_time = scm_c_get_internal_run_time ();
-
-  return NULL;
-}
-
-static void *
-accumulate_gc_timer (void * hook_data SCM_UNUSED,
-                void *fn_data SCM_UNUSED,
-                void *data SCM_UNUSED)
-{
-  if (gc_start_time)
-    {
-      long now = scm_c_get_internal_run_time ();
-      gc_time_taken += now - gc_start_time;
-      gc_start_time = 0;
-    }
-
-  return NULL;
-}
-
 static size_t bytes_until_gc = DEFAULT_INITIAL_HEAP_SIZE;
 static scm_i_pthread_mutex_t bytes_until_gc_lock = 
SCM_I_PTHREAD_MUTEX_INITIALIZER;
 
@@ -595,6 +706,16 @@ scm_gc_register_allocation (size_t size)
 
 
 
+
+static SCM
+after_gc_async_thunk (void)
+{
+  /* Fun, no? Hook-run *and* run-hook?  */
+  scm_c_run_hook (scm_after_gc_hook, SCM_EOL);
+  return SCM_UNSPECIFIED;
+}
+
+
 void
 scm_init_gc ()
 {
@@ -609,13 +730,8 @@ scm_init_gc ()
                                                     after_gc_async_thunk),
                                   SCM_BOOL_F);
 
-  scm_c_hook_add (&scm_before_gc_c_hook, queue_after_gc_hook, NULL, 0);
-  scm_c_hook_add (&scm_before_gc_c_hook, start_gc_timer, NULL, 0);
-  scm_c_hook_add (&scm_after_gc_c_hook, accumulate_gc_timer, NULL, 0);
-
   GC_set_oom_fn (scm_oom_fn);
   GC_set_warn_proc (scm_gc_warn_proc);
-  GC_set_start_callback (run_before_gc_c_hook);
 
 #include "gc.x"
 }

Reply via email to