ruby/ruby-3.2.0-define-unsupported-gc-compaction-methods-as-rb_f_notimplement.patch

503 lines
16 KiB
Diff
Raw Permalink Normal View History

From 1b3502156a665e2782f366aa5ac8c3bfd7637ab8 Mon Sep 17 00:00:00 2001
From: Mike Dalessio <mike.dalessio@gmail.com>
Date: Mon, 23 May 2022 15:40:22 -0400
Subject: [PATCH 1/2] Move compaction-related methods into gc.c
These methods are removed from gc.rb and added to gc.c:
- GC.compact
- GC.auto_compact
- GC.auto_compact=
- GC.latest_compact_info
- GC.verify_compaction_references
This is a prefactor to allow setting these methods to
`rb_f_notimplement` in a followup commit.
---
gc.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
gc.rb | 68 ---------------------------------------
2 files changed, 91 insertions(+), 78 deletions(-)
diff --git a/gc.c b/gc.c
index ef9327df1f..1c35856c44 100644
--- a/gc.c
+++ b/gc.c
@@ -10195,8 +10195,20 @@ gc_update_references(rb_objspace_t *objspace)
gc_update_table_refs(objspace, finalizer_table);
}
+/*
+ * call-seq:
+ * GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}}
+ *
+ * Returns information about object moved in the most recent GC compaction.
+ *
+ * The returned hash has two keys :considered and :moved. The hash for
+ * :considered lists the number of objects that were considered for movement
+ * by the compactor, and the :moved hash lists the number of objects that
+ * were actually moved. Some objects can't be moved (maybe they were pinned)
+ * so these numbers can be used to calculate compaction efficiency.
+ */
static VALUE
-gc_compact_stats(rb_execution_context_t *ec, VALUE self)
+gc_compact_stats(VALUE self)
{
size_t i;
rb_objspace_t *objspace = &rb_objspace;
@@ -10269,22 +10281,70 @@ heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data)
return 0;
}
+/*
+ * call-seq:
+ * GC.compact
+ *
+ * This function compacts objects together in Ruby's heap. It eliminates
+ * unused space (or fragmentation) in the heap by moving objects in to that
+ * unused space. This function returns a hash which contains statistics about
+ * which objects were moved. See `GC.latest_gc_info` for details about
+ * compaction statistics.
+ *
+ * This method is implementation specific and not expected to be implemented
+ * in any implementation besides MRI.
+ */
static VALUE
-gc_compact(rb_execution_context_t *ec, VALUE self)
+gc_compact(VALUE self)
{
/* Run GC with compaction enabled */
- gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qtrue);
+ gc_start_internal(NULL, self, Qtrue, Qtrue, Qtrue, Qtrue);
- return gc_compact_stats(ec, self);
+ return gc_compact_stats(self);
}
+/*
+ * call-seq:
+ * GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
+ *
+ * Verify compaction reference consistency.
+ *
+ * This method is implementation specific. During compaction, objects that
+ * were moved are replaced with T_MOVED objects. No object should have a
+ * reference to a T_MOVED object after compaction.
+ *
+ * This function doubles the heap to ensure room to move all objects,
+ * compacts the heap to make sure everything moves, updates all references,
+ * then performs a full GC. If any object contains a reference to a T_MOVED
+ * object, that object should be pushed on the mark stack, and will
+ * make a SEGV.
+ */
static VALUE
-gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE double_heap, VALUE toward_empty)
+gc_verify_compaction_references(int argc, VALUE *argv, VALUE self)
{
rb_objspace_t *objspace = &rb_objspace;
+ VALUE kwargs, double_heap = Qfalse, toward_empty = Qfalse;
+ static ID id_toward, id_double_heap, id_empty;
+
+ if (!id_toward) {
+ id_toward = rb_intern("toward");
+ id_double_heap = rb_intern("double_heap");
+ id_empty = rb_intern("empty");
+ }
+
+ rb_scan_args(argc, argv, ":", &kwargs);
+ if (!NIL_P(kwargs)) {
+ if (rb_hash_has_key(kwargs, ID2SYM(id_toward))) {
+ VALUE toward = rb_hash_aref(kwargs, ID2SYM(id_toward));
+ toward_empty = (toward == ID2SYM(id_empty)) ? Qtrue : Qfalse;
+ }
+ if (rb_hash_has_key(kwargs, ID2SYM(id_double_heap))) {
+ double_heap = rb_hash_aref(kwargs, ID2SYM(id_double_heap));
+ }
+ }
/* Clear the heap. */
- gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qfalse);
+ gc_start_internal(NULL, self, Qtrue, Qtrue, Qtrue, Qfalse);
RB_VM_LOCK_ENTER();
{
@@ -10304,12 +10364,12 @@ gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE do
}
RB_VM_LOCK_LEAVE();
- gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qtrue);
+ gc_start_internal(NULL, self, Qtrue, Qtrue, Qtrue, Qtrue);
objspace_reachable_objects_from_root(objspace, root_obj_check_moved_i, NULL);
objspace_each_objects(objspace, heap_check_moved_i, NULL, TRUE);
- return gc_compact_stats(ec, self);
+ return gc_compact_stats(self);
}
VALUE
@@ -10770,8 +10830,18 @@ gc_disable(rb_execution_context_t *ec, VALUE _)
return rb_gc_disable();
}
+/*
+ * call-seq:
+ * GC.auto_compact = flag
+ *
+ * Updates automatic compaction mode.
+ *
+ * When enabled, the compactor will execute on every major collection.
+ *
+ * Enabling compaction will degrade performance on major collections.
+ */
static VALUE
-gc_set_auto_compact(rb_execution_context_t *ec, VALUE _, VALUE v)
+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. */
@@ -10785,8 +10855,14 @@ gc_set_auto_compact(rb_execution_context_t *ec, VALUE _, VALUE v)
return v;
}
+/*
+ * call-seq:
+ * GC.auto_compact -> true or false
+ *
+ * Returns whether or not automatic compaction has been enabled.
+ */
static VALUE
-gc_get_auto_compact(rb_execution_context_t *ec, VALUE _)
+gc_get_auto_compact(VALUE _)
{
return RBOOL(ruby_enable_autocompact);
}
@@ -13694,6 +13770,11 @@ 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_DEBUG_STRESS_TO_CLASS
rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1);
diff --git a/gc.rb b/gc.rb
index 72637f3796..9265dd7b57 100644
--- a/gc.rb
+++ b/gc.rb
@@ -38,27 +38,6 @@ def garbage_collect full_mark: true, immediate_mark: true, immediate_sweep: true
Primitive.gc_start_internal full_mark, immediate_mark, immediate_sweep, false
end
- # call-seq:
- # GC.auto_compact -> true or false
- #
- # Returns whether or not automatic compaction has been enabled.
- #
- def self.auto_compact
- Primitive.gc_get_auto_compact
- end
-
- # call-seq:
- # GC.auto_compact = flag
- #
- # Updates automatic compaction mode.
- #
- # When enabled, the compactor will execute on every major collection.
- #
- # Enabling compaction will degrade performance on major collections.
- def self.auto_compact=(flag)
- Primitive.gc_set_auto_compact(flag)
- end
-
# call-seq:
# GC.enable -> true or false
#
@@ -210,53 +189,6 @@ def self.latest_gc_info hash_or_key = nil
Primitive.gc_latest_gc_info hash_or_key
end
- # call-seq:
- # GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}}
- #
- # Returns information about object moved in the most recent GC compaction.
- #
- # The returned hash has two keys :considered and :moved. The hash for
- # :considered lists the number of objects that were considered for movement
- # by the compactor, and the :moved hash lists the number of objects that
- # were actually moved. Some objects can't be moved (maybe they were pinned)
- # so these numbers can be used to calculate compaction efficiency.
- def self.latest_compact_info
- Primitive.gc_compact_stats
- end
-
- # call-seq:
- # GC.compact
- #
- # This function compacts objects together in Ruby's heap. It eliminates
- # unused space (or fragmentation) in the heap by moving objects in to that
- # unused space. This function returns a hash which contains statistics about
- # which objects were moved. See `GC.latest_gc_info` for details about
- # compaction statistics.
- #
- # This method is implementation specific and not expected to be implemented
- # in any implementation besides MRI.
- def self.compact
- Primitive.gc_compact
- end
-
- # call-seq:
- # GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
- #
- # Verify compaction reference consistency.
- #
- # This method is implementation specific. During compaction, objects that
- # were moved are replaced with T_MOVED objects. No object should have a
- # reference to a T_MOVED object after compaction.
- #
- # This function doubles the heap to ensure room to move all objects,
- # compacts the heap to make sure everything moves, updates all references,
- # then performs a full GC. If any object contains a reference to a T_MOVED
- # object, that object should be pushed on the mark stack, and will
- # make a SEGV.
- def self.verify_compaction_references(toward: nil, double_heap: false)
- Primitive.gc_verify_compaction_references(double_heap, toward == :empty)
- end
-
# call-seq:
# GC.using_rvargc? -> true or false
#
From d3273559356db6852d1fd794f0f076fba100e09e Mon Sep 17 00:00:00 2001
From: Mike Dalessio <mike.dalessio@gmail.com>
Date: Mon, 23 May 2022 17:31:14 -0400
Subject: [PATCH 2/2] Define unsupported GC compaction methods as
rb_f_notimplement
Fixes [Bug #18779]
Define the following methods as `rb_f_notimplement` on unsupported
platforms:
- GC.compact
- GC.auto_compact
- GC.auto_compact=
- GC.latest_compact_info
- GC.verify_compaction_references
This change allows users to call `GC.respond_to?(:compact)` to
properly test for compaction support. Previously, it was necessary to
invoke `GC.compact` or `GC.verify_compaction_references` and check if
those methods raised `NotImplementedError` to determine if compaction
was supported.
This follows the precedent set for other platform-specific
methods. For example, in `process.c` for methods such as
`Process.fork`, `Process.setpgid`, and `Process.getpriority`.
---
gc.c | 31 +++++++++++++++----
test/ruby/test_gc_compact.rb | 58 ++++++++++++++++++++++++++----------
2 files changed, 69 insertions(+), 20 deletions(-)
diff --git a/gc.c b/gc.c
index 92ed76cf96..d71924846a 100644
--- a/gc.c
+++ b/gc.c
@@ -9442,6 +9442,7 @@ gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t slot_size)
return (VALUE)src;
}
+#if GC_COMPACTION_SUPPORTED
static int
compare_free_slots(const void *left, const void *right, void *dummy)
{
@@ -9489,6 +9490,7 @@ gc_sort_heap_by_empty_slots(rb_objspace_t *objspace)
free(page_list);
}
}
+#endif
static void
gc_ref_update_array(rb_objspace_t * objspace, VALUE v)
@@ -10195,6 +10197,7 @@ gc_update_references(rb_objspace_t *objspace)
gc_update_table_refs(objspace, finalizer_table);
}
+#if GC_COMPACTION_SUPPORTED
/*
* call-seq:
* GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}}
@@ -10231,7 +10234,11 @@ gc_compact_stats(VALUE self)
return h;
}
+#else
+# define gc_compact_stats rb_f_notimplement
+#endif
+#if GC_COMPACTION_SUPPORTED
static void
root_obj_check_moved_i(const char *category, VALUE obj, void *data)
{
@@ -10293,6 +10300,10 @@ heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data)
*
* This method is implementation specific and not expected to be implemented
* in any implementation besides MRI.
+ *
+ * To test whether GC compaction is supported, use the idiom:
+ *
+ * GC.respond_to?(:compact)
*/
static VALUE
gc_compact(VALUE self)
@@ -10302,7 +10313,11 @@ gc_compact(VALUE self)
return gc_compact_stats(self);
}
+#else
+# define gc_compact rb_f_notimplement
+#endif
+#if GC_COMPACTION_SUPPORTED
/*
* call-seq:
* GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
@@ -10371,6 +10386,9 @@ gc_verify_compaction_references(int argc, VALUE *argv, VALUE self)
return gc_compact_stats(self);
}
+#else
+# define gc_verify_compaction_references rb_f_notimplement
+#endif
VALUE
rb_gc_start(void)
@@ -10830,6 +10848,7 @@ gc_disable(rb_execution_context_t *ec, VALUE _)
return rb_gc_disable();
}
+#if GC_COMPACTION_SUPPORTED
/*
* call-seq:
* GC.auto_compact = flag
@@ -10845,16 +10864,15 @@ 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. */
-#if !defined(__MINGW32__) && !defined(_WIN32)
- if (!USE_MMAP_ALIGNED_ALLOC) {
- rb_raise(rb_eNotImpError, "Automatic compaction isn't available on this platform");
- }
-#endif
ruby_enable_autocompact = RTEST(v);
return v;
}
+#else
+# define gc_set_auto_compact rb_f_notimplement
+#endif
+#if GC_COMPACTION_SUPPORTED
/*
* call-seq:
* GC.auto_compact -> true or false
@@ -10866,6 +10884,9 @@ gc_get_auto_compact(VALUE _)
{
return RBOOL(ruby_enable_autocompact);
}
+#else
+# define gc_get_auto_compact rb_f_notimplement
+#endif
static int
get_envparam_size(const char *name, size_t *default_value, size_t lower_bound)
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index 42ad028530..411d5eab69 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -9,14 +9,7 @@
end
class TestGCCompact < Test::Unit::TestCase
- module SupportsCompact
- def setup
- skip "autocompact not supported on this platform" unless supports_auto_compact?
- super
- end
-
- private
-
+ module CompactionSupportInspector
def supports_auto_compact?
return true unless defined?(Etc::SC_PAGE_SIZE)
@@ -30,10 +23,19 @@ def supports_auto_compact?
end
end
- include SupportsCompact
+ module OmitUnlessCompactSupported
+ include CompactionSupportInspector
+
+ def setup
+ omit "autocompact not supported on this platform" unless supports_auto_compact?
+ super
+ end
+ end
+
+ include OmitUnlessCompactSupported
class AutoCompact < Test::Unit::TestCase
- include SupportsCompact
+ include OmitUnlessCompactSupported
def test_enable_autocompact
before = GC.auto_compact
@@ -87,13 +89,39 @@ def test_implicit_compaction_does_something
end
end
- def os_page_size
- return true unless defined?(Etc::SC_PAGE_SIZE)
+ class CompactMethodsNotImplemented < Test::Unit::TestCase
+ include CompactionSupportInspector
+
+ def assert_not_implemented(method, *args)
+ omit "autocompact is supported on this platform" if supports_auto_compact?
+
+ assert_raise(NotImplementedError) { GC.send(method, *args) }
+ refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement")
+ end
+
+ def test_gc_compact_not_implemented
+ assert_not_implemented(:compact)
+ end
+
+ def test_gc_auto_compact_get_not_implemented
+ assert_not_implemented(:auto_compact)
+ end
+
+ def test_gc_auto_compact_set_not_implemented
+ assert_not_implemented(:auto_compact=, true)
+ end
+
+ def test_gc_latest_compact_info_not_implemented
+ assert_not_implemented(:latest_compact_info)
+ end
+
+ def test_gc_verify_compaction_references_not_implemented
+ assert_not_implemented(:verify_compaction_references)
+ end
end
- def setup
- skip "autocompact not supported on this platform" unless supports_auto_compact?
- super
+ def os_page_size
+ return true unless defined?(Etc::SC_PAGE_SIZE)
end
def test_gc_compact_stats