From 1484f4a4f5a04a83738e6616b926a472d23b8adf Mon Sep 17 00:00:00 2001
From: Peter Zhu <peter@peterzhu.ca>
Date: Tue, 14 Jun 2022 14:37:21 -0400
Subject: [PATCH] Define the GC compaction support during run time.

== Summary of the implementation ==

The patch is created by 3 commits on the upstream Ruby master branch in
the chronological order below.
https://github.com/ruby/ruby/commit/52d42e702375446746164a0251e1a10bce813b78
https://github.com/ruby/ruby/commit/79eaaf2d0b641710613f16525e4b4c439dfe854e
https://github.com/ruby/ruby/commit/2c190863239bee3f54cfb74b16bb6ea4cae6ed20

The implementation is basically to replace the wrongly used
`GC_COMPACTION_SUPPORTED` with the `GC_CAN_COMPILE_COMPACTION`.

* GC_COMPACTION_SUPPORTED macro
  only checks whether compaction is supported at compile time.
* GC_CAN_COMPILE_COMPACTION macro
  checks whether compaction is supported at both compile and run time.

== How to create this patch ==

Download Ruby source code.
```
$ git clone https://github.com/ruby/ruby.git
$ cd ruby
```

First create a commit squashed from the 3 commits above.
Checkout the second commmit above, and create a temporary branch.
```
$ git checkout 79eaaf2d0b641710613f16525e4b4c439dfe854e
$ git checkout -b wip/detect-compaction-runtime-tmp
```

Cherry pick the third commit on the second commit.
```
$ git cherry-pick 2c190863239bee3f54cfb74b16bb6ea4cae6ed20
```

Squash the last 3 commits on the branch.
```
$ git rebase -i 2223eb082afa6d05321b69df783d4133b9aacba6
```

Then checkout Ruby 3.1.2 branch.
Create a new branch.
Merge the Fedora Ruby's ruby-3.2.0-define-unsupported-gc-compaction-methods-as-rb_f_notimplement.patch.
```
$ git checkout v3_1_2
$ git checkout -b wip/detect-compaction-runtime
$ patch -p1 < ~/fed/ruby/ruby-3.2.0-define-unsupported-gc-compaction-methods-as-rb_f_notimplement.patch
$ git add gc.c gc.rb test/ruby/test_gc_compact.rb
$ git commit
```

Merge the squashed on commit on the `wip/detect-compaction-runtime-tmp` branch
into the `wip/detect-compaction-runtime` branch.
```
$ git cherry-pick <the squashed commit above>
```

Fix conflicts seeing the difference by `git show <the squashed commit above>`
on another terminal.
```
$ vi gc.c
$ git add gc.c
$ git commit
```

== Original commit messages ==

This is a combination of 3 commits.
This is the 1st commit message:
~~~
Rename GC_COMPACTION_SUPPORTED

Naming this macro GC_COMPACTION_SUPPORTED is misleading because it
only checks whether compaction is supported at compile time.

[Bug #18829]
~~~

This is the commit message #2:
~~~
Include runtime checks for compaction support

Commit 0c36ba53192c5a0d245c9b626e4346a32d7d144e changed GC compaction
methods to not be implemented when not supported. However, that commit
only does compile time checks (which currently only checks for WASM),
but there are additional compaction support checks during run time.

This commit changes it so that GC compaction methods aren't defined
during run time if the platform does not support GC compaction.

[Bug #18829]
~~~

This is the commit message #3:
~~~
Suppress code unused unless GC_CAN_COMPILE_COMPACTION
~~~
---
 gc.c | 62 ++++++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 42 insertions(+), 20 deletions(-)

diff --git a/gc.c b/gc.c
index 1c35856c44..3dbb4d156b 100644
--- a/gc.c
+++ b/gc.c
@@ -4980,6 +4980,23 @@ gc_unprotect_pages(rb_objspace_t *objspace, rb_heap_t *heap)
 static void gc_update_references(rb_objspace_t * objspace);
 static void invalidate_moved_page(rb_objspace_t *objspace, struct heap_page *page);
 
+#ifndef GC_CAN_COMPILE_COMPACTION
+#if defined(__wasi__) /* WebAssembly doesn't support signals */
+# define GC_CAN_COMPILE_COMPACTION 0
+#else
+# define GC_CAN_COMPILE_COMPACTION 1
+#endif
+#endif
+
+#if defined(__MINGW32__) || defined(_WIN32)
+# define GC_COMPACTION_SUPPORTED 1
+#else
+/* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
+ * the read barrier, so we must disable compaction. */
+# define GC_COMPACTION_SUPPORTED (GC_CAN_COMPILE_COMPACTION && HEAP_PAGE_ALLOC_USE_MMAP)
+#endif
+
+#if GC_CAN_COMPILE_COMPACTION
 static void
 read_barrier_handler(uintptr_t address)
 {
@@ -5000,6 +5017,7 @@ read_barrier_handler(uintptr_t address)
     }
     RB_VM_LOCK_LEAVE();
 }
+#endif
 
 #if defined(_WIN32)
 static LPTOP_LEVEL_EXCEPTION_FILTER old_handler;
@@ -9250,13 +9268,7 @@ gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE
 
     /* For now, compact implies full mark / sweep, so ignore other flags */
     if (RTEST(compact)) {
-        /* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
-         * the read barrier, so we must disable compaction. */
-#if !defined(__MINGW32__) && !defined(_WIN32)
-        if (!USE_MMAP_ALIGNED_ALLOC) {
-            rb_raise(rb_eNotImpError, "Compaction isn't available on this platform");
-        }
-#endif
+        GC_ASSERT(GC_COMPACTION_SUPPORTED);
 
         reason |= GPR_FLAG_COMPACT;
     }
@@ -9421,7 +9433,7 @@ gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t slot_size)
     return (VALUE)src;
 }
 
-#if GC_COMPACTION_SUPPORTED
+#if GC_CAN_COMPILE_COMPACTION
 static int
 compare_free_slots(const void *left, const void *right, void *dummy)
 {
@@ -10149,7 +10161,7 @@ gc_update_references(rb_objspace_t *objspace)
     gc_update_table_refs(objspace, finalizer_table);
 }
 
-#if GC_COMPACTION_SUPPORTED
+#if GC_CAN_COMPILE_COMPACTION
 /*
  *  call-seq:
  *     GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}}
@@ -10190,7 +10202,7 @@ gc_compact_stats(VALUE self)
 #  define gc_compact_stats rb_f_notimplement
 #endif
 
-#if GC_COMPACTION_SUPPORTED
+#if GC_CAN_COMPILE_COMPACTION
 static void
 root_obj_check_moved_i(const char *category, VALUE obj, void *data)
 {
@@ -10269,7 +10281,7 @@ gc_compact(VALUE self)
 #  define gc_compact rb_f_notimplement
 #endif
 
-#if GC_COMPACTION_SUPPORTED
+#if GC_CAN_COMPILE_COMPACTION
 /*
  * call-seq:
  *    GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
@@ -10800,7 +10812,7 @@ gc_disable(rb_execution_context_t *ec, VALUE _)
     return rb_gc_disable();
 }
 
-#if GC_COMPACTION_SUPPORTED
+#if GC_CAN_COMPILE_COMPACTION
 /*
  *  call-seq:
  *     GC.auto_compact = flag
@@ -10814,8 +10826,7 @@ gc_disable(rb_execution_context_t *ec, VALUE _)
 static VALUE
 gc_set_auto_compact(VALUE _, VALUE v)
 {
-    /* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
-     * the read barrier, so we must disable automatic compaction. */
+    GC_ASSERT(GC_COMPACTION_SUPPORTED);
 
     ruby_enable_autocompact = RTEST(v);
     return v;
@@ -10824,7 +10835,7 @@ gc_set_auto_compact(VALUE _, VALUE v)
 #  define gc_set_auto_compact rb_f_notimplement
 #endif
 
-#if GC_COMPACTION_SUPPORTED
+#if GC_CAN_COMPILE_COMPACTION
 /*
  *  call-seq:
  *     GC.auto_compact    -> true or false
@@ -13696,11 +13707,21 @@ Init_GC(void)
     rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0);
     rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0);
 #endif
-    rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0);
-    rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0);
-    rb_define_singleton_method(rb_mGC, "auto_compact=", gc_set_auto_compact, 1);
-    rb_define_singleton_method(rb_mGC, "latest_compact_info", gc_compact_stats, 0);
-    rb_define_singleton_method(rb_mGC, "verify_compaction_references", gc_verify_compaction_references, -1);
+
+    if (GC_COMPACTION_SUPPORTED) {
+        rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0);
+        rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0);
+        rb_define_singleton_method(rb_mGC, "auto_compact=", gc_set_auto_compact, 1);
+        rb_define_singleton_method(rb_mGC, "latest_compact_info", gc_compact_stats, 0);
+    }
+    else {
+        rb_define_singleton_method(rb_mGC, "compact", rb_f_notimplement, 0);
+        rb_define_singleton_method(rb_mGC, "auto_compact", rb_f_notimplement, 0);
+        rb_define_singleton_method(rb_mGC, "auto_compact=", rb_f_notimplement, 1);
+        rb_define_singleton_method(rb_mGC, "latest_compact_info", rb_f_notimplement, 0);
+        /* When !GC_COMPACTION_SUPPORTED, this method is not defined in gc.rb */
+        rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1);
+    }
 
 #if GC_DEBUG_STRESS_TO_CLASS
     rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1);
@@ -13724,6 +13745,7 @@ Init_GC(void)
 	OPT(MALLOC_ALLOCATED_SIZE);
 	OPT(MALLOC_ALLOCATED_SIZE_CHECK);
 	OPT(GC_PROFILE_DETAIL_MEMORY);
+	OPT(GC_COMPACTION_SUPPORTED);
 #undef OPT
 	OBJ_FREEZE(opts);
     }
-- 
2.36.1

