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

commit 9f26dbb1fc296f38c2236437dbcf82d76cb042a5
Author: Andy Wingo <wi...@igalia.com>
AuthorDate: Wed Sep 18 10:32:38 2024 +0200

    Implement per-object pinning API
    
    Fixes https://github.com/wingo/whippet/issues/6.
---
 README.md        |  4 ++--
 api/bdw-attrs.h  |  4 ++++
 api/gc-api.h     |  2 ++
 api/gc-attrs.h   |  2 ++
 api/mmc-attrs.h  |  4 ++++
 api/pcc-attrs.h  |  4 ++++
 api/semi-attrs.h |  4 ++++
 doc/manual.md    | 19 +++++++++++++++++++
 src/bdw.c        |  4 ++++
 src/mmc.c        |  8 ++++++++
 src/nofl-space.h | 23 ++++++++++++++++++++---
 src/pcc.c        |  4 ++++
 src/semi.c       |  4 ++++
 13 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index b3689fcd9..fc60c5ccf 100644
--- a/README.md
+++ b/README.md
@@ -45,8 +45,8 @@ See the [documentation](./doc/README.md).
 ## Status and roadmap
 
 As of September 2024, Whippet is almost feature-complete.  We need to
-land a per-object pinning API, and an API for cooperative safepoints for
-use by threads that are looping without allocating.
+land an API for cooperative safepoints for use by threads that are
+looping without allocating.
 
 After that, the next phase on the roadmap is support for tracing, and
 some performance noodling.
diff --git a/api/bdw-attrs.h b/api/bdw-attrs.h
index e190c2cee..85c3aa250 100644
--- a/api/bdw-attrs.h
+++ b/api/bdw-attrs.h
@@ -54,4 +54,8 @@ static inline enum gc_safepoint_mechanism 
gc_safepoint_mechanism(void) {
   return GC_SAFEPOINT_MECHANISM_SIGNAL;
 }
 
+static inline int gc_can_pin_objects(void) {
+  return 1;
+}
+
 #endif // BDW_ATTRS_H
diff --git a/api/gc-api.h b/api/gc-api.h
index e60be7579..071655bff 100644
--- a/api/gc-api.h
+++ b/api/gc-api.h
@@ -206,4 +206,6 @@ static inline void gc_write_barrier(struct gc_ref obj, 
size_t obj_size,
   }
 }
 
+GC_API_ void gc_pin_object(struct gc_mutator *mut, struct gc_ref obj);
+
 #endif // GC_API_H_
diff --git a/api/gc-attrs.h b/api/gc-attrs.h
index c08330eaa..8e914e361 100644
--- a/api/gc-attrs.h
+++ b/api/gc-attrs.h
@@ -43,4 +43,6 @@ enum gc_safepoint_mechanism {
 };
 static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) 
GC_ALWAYS_INLINE;
 
+static inline int gc_can_pin_objects(void) GC_ALWAYS_INLINE;
+
 #endif // GC_ATTRS_H
diff --git a/api/mmc-attrs.h b/api/mmc-attrs.h
index 111b2512c..d56b3d88c 100644
--- a/api/mmc-attrs.h
+++ b/api/mmc-attrs.h
@@ -61,4 +61,8 @@ static inline enum gc_safepoint_mechanism 
gc_safepoint_mechanism(void) {
   return GC_SAFEPOINT_MECHANISM_COOPERATIVE;
 }
 
+static inline int gc_can_pin_objects(void) {
+  return 1;
+}
+
 #endif // MMC_ATTRS_H
diff --git a/api/pcc-attrs.h b/api/pcc-attrs.h
index 5f80488a0..b8b42baf0 100644
--- a/api/pcc-attrs.h
+++ b/api/pcc-attrs.h
@@ -57,4 +57,8 @@ static inline enum gc_safepoint_mechanism 
gc_safepoint_mechanism(void) {
   return GC_SAFEPOINT_MECHANISM_COOPERATIVE;
 }
 
+static inline int gc_can_pin_objects(void) {
+  return 0;
+}
+
 #endif // PCC_ATTRS_H
diff --git a/api/semi-attrs.h b/api/semi-attrs.h
index be906768f..3e0511074 100644
--- a/api/semi-attrs.h
+++ b/api/semi-attrs.h
@@ -56,4 +56,8 @@ static inline enum gc_safepoint_mechanism 
gc_safepoint_mechanism(void) {
   return GC_SAFEPOINT_MECHANISM_COOPERATIVE;
 }
 
+static inline int gc_can_pin_objects(void) {
+  return 0;
+}
+
 #endif // SEMI_ATTRS_H
diff --git a/doc/manual.md b/doc/manual.md
index 45e8d019d..d2878ebd6 100644
--- a/doc/manual.md
+++ b/doc/manual.md
@@ -538,6 +538,25 @@ Also, the BDW collector actually uses pre-emptive 
safepoints: it stops
 threads via POSIX signals.  `gc_safepoint` is (or will be) a no-op with
 BDW.
 
+### Pinning
+
+Sometimes a mutator or embedder would like to tell the collector to not
+move a particular object.  This can happen for example during a foreign
+function call, or if the embedder allows programs to access the address
+of an object, for example to compute an identity hash code.  To support
+this use case, some Whippet collectors allow the embedder to *pin*
+objects.  Call `gc_pin_object` to prevent the collector from relocating
+an object.
+
+Pinning is currently supported by the `bdw` collector, which never moves
+objects, and also by the various `mmc` collectors, which can move
+objects that have no inbound conservative references.
+
+Pinning is not supported on `semi` or `pcc`.
+
+Call `gc_can_pin_objects` to determine whether the current collector can
+pin objects.
+
 ### Statistics
 
 Sometimes a program would like some information from the GC: how many
diff --git a/src/bdw.c b/src/bdw.c
index 3149bf3c7..e5394427a 100644
--- a/src/bdw.c
+++ b/src/bdw.c
@@ -127,6 +127,10 @@ void* gc_allocate_pointerless(struct gc_mutator *mut,
   return GC_malloc_atomic(size);
 }
 
+void gc_pin_object(struct gc_mutator *mut, struct gc_ref ref) {
+  // Nothing to do.
+}
+
 void gc_collect(struct gc_mutator *mut,
                 enum gc_collection_kind requested_kind) {
   switch (requested_kind) {
diff --git a/src/mmc.c b/src/mmc.c
index 0c351f2e8..97091e9b6 100644
--- a/src/mmc.c
+++ b/src/mmc.c
@@ -880,6 +880,14 @@ gc_allocate_pointerless(struct gc_mutator *mut, size_t 
size) {
   return gc_allocate(mut, size);
 }
 
+void
+gc_pin_object(struct gc_mutator *mut, struct gc_ref ref) {
+  struct nofl_space *nofl = heap_nofl_space(mutator_heap(mut));
+  if (nofl_space_contains(nofl, ref))
+    nofl_space_pin_object(nofl, ref);
+  // Otherwise if it's a large or external object, it won't move.
+}
+
 void
 gc_write_barrier_extern(struct gc_ref obj, size_t obj_size,
                         struct gc_edge edge, struct gc_ref new_val) {
diff --git a/src/nofl-space.h b/src/nofl-space.h
index ea7fe1c31..a962cac81 100644
--- a/src/nofl-space.h
+++ b/src/nofl-space.h
@@ -1328,9 +1328,12 @@ nofl_space_sweep_until_memory_released(struct nofl_space 
*space,
 }
 
 static inline int
-nofl_space_should_evacuate(struct nofl_space *space, struct gc_ref obj) {
+nofl_space_should_evacuate(struct nofl_space *space, uint8_t metadata_byte,
+                           struct gc_ref obj) {
   if (!space->evacuating)
     return 0;
+  if (metadata_byte & NOFL_METADATA_BYTE_PINNED)
+    return 0;
   return nofl_block_has_flag(nofl_block_for_addr(gc_ref_value(obj)),
                              NOFL_BLOCK_EVACUATE);
 }
@@ -1353,6 +1356,20 @@ nofl_space_set_nonempty_mark(struct nofl_space *space, 
uint8_t *metadata,
   return 1;
 }
 
+static inline void
+nofl_space_pin_object(struct nofl_space *space, struct gc_ref ref) {
+  uint8_t *metadata = nofl_metadata_byte_for_object(ref);
+  uint8_t byte = atomic_load_explicit(metadata, memory_order_relaxed);
+  if (byte & NOFL_METADATA_BYTE_PINNED)
+    return;
+  uint8_t new_byte;
+  do {
+    new_byte = byte | NOFL_METADATA_BYTE_PINNED;
+  } while (!atomic_compare_exchange_weak_explicit(metadata, &byte, new_byte,
+                                                  memory_order_acq_rel,
+                                                  memory_order_acquire));
+}
+
 static inline int
 nofl_space_evacuate(struct nofl_space *space, uint8_t *metadata, uint8_t byte,
                     struct gc_edge edge,
@@ -1429,7 +1446,7 @@ nofl_space_evacuate_or_mark_object(struct nofl_space 
*space,
   if (byte & space->marked_mask)
     return 0;
 
-  if (nofl_space_should_evacuate(space, old_ref))
+  if (nofl_space_should_evacuate(space, byte, old_ref))
     return nofl_space_evacuate(space, metadata, byte, edge, old_ref,
                                evacuate);
 
@@ -1490,7 +1507,7 @@ nofl_space_forward_or_mark_if_traced(struct nofl_space 
*space,
   if (byte & space->marked_mask)
     return 1;
 
-  if (!nofl_space_should_evacuate(space, ref))
+  if (!nofl_space_should_evacuate(space, byte, ref))
     return 0;
 
   return nofl_space_forward_if_evacuated(space, edge, ref);
diff --git a/src/pcc.c b/src/pcc.c
index 34a7f47b6..a99e2a967 100644
--- a/src/pcc.c
+++ b/src/pcc.c
@@ -504,6 +504,10 @@ void* gc_allocate_pointerless(struct gc_mutator *mut, 
size_t size) {
   return gc_allocate(mut, size);
 }
 
+void gc_pin_object(struct gc_mutator *mut, struct gc_ref ref) {
+  GC_CRASH();
+}
+
 void gc_write_barrier_extern(struct gc_ref obj, size_t obj_size,
                              struct gc_edge edge, struct gc_ref new_val) {
 }
diff --git a/src/semi.c b/src/semi.c
index 3be0b034b..84493e81c 100644
--- a/src/semi.c
+++ b/src/semi.c
@@ -505,6 +505,10 @@ void* gc_allocate_pointerless(struct gc_mutator *mut, 
size_t size) {
   return gc_allocate(mut, size);
 }
 
+void gc_pin_object(struct gc_mutator *mut, struct gc_ref ref) {
+  GC_CRASH();
+}
+
 struct gc_ephemeron* gc_allocate_ephemeron(struct gc_mutator *mut) {
   return gc_allocate(mut, gc_ephemeron_size());
 }

Reply via email to