summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--array.c42
-rw-r--r--gc.c25
-rw-r--r--internal/array.h3
-rw-r--r--test/ruby/test_gc_compact.rb40
4 files changed, 102 insertions, 8 deletions
diff --git a/array.c b/array.c
index 7b3f5bd0b0..dc8c4abe07 100644
--- a/array.c
+++ b/array.c
@@ -107,7 +107,6 @@ should_not_be_shared_and_embedded(VALUE ary)
#define ARY_SET_EMBED_LEN(ary, n) do { \
long tmp_n = (n); \
assert(ARY_EMBED_P(ary)); \
- assert(!OBJ_FROZEN(ary)); \
RBASIC(ary)->flags &= ~RARRAY_EMBED_LEN_MASK; \
RBASIC(ary)->flags |= (tmp_n) << RARRAY_EMBED_LEN_SHIFT; \
} while (0)
@@ -212,6 +211,30 @@ ary_embeddable_p(long capa)
#endif
}
+bool
+rb_ary_embeddable_p(VALUE ary)
+{
+ // if the array is shared or a shared root then it's not moveable
+ return !(ARY_SHARED_P(ary) || ARY_SHARED_ROOT_P(ary));
+}
+
+size_t
+rb_ary_size_as_embedded(VALUE ary)
+{
+ size_t real_size;
+
+ if (ARY_EMBED_P(ary)) {
+ real_size = ary_embed_size(ARY_EMBED_LEN(ary));
+ }
+ else if (rb_ary_embeddable_p(ary)) {
+ real_size = ary_embed_size(ARY_HEAP_CAPA(ary));
+ }
+ else {
+ real_size = sizeof(struct RString);
+ }
+ return real_size;
+}
+
#if ARRAY_DEBUG
#define ary_verify(ary) ary_verify_(ary, __FILE__, __LINE__)
@@ -468,6 +491,23 @@ rb_ary_detransient(VALUE ary)
}
#endif
+void
+rb_ary_make_embedded(VALUE ary)
+{
+ assert(rb_ary_embeddable_p(ary));
+ if (!ARY_EMBED_P(ary)) {
+ VALUE *buf = RARRAY_PTR(ary);
+ long len = RARRAY_LEN(ary);
+
+ FL_SET_EMBED(ary);
+ ARY_SET_EMBED_LEN(ary, len);
+ RARY_TRANSIENT_UNSET(ary);
+
+ memmove(RARRAY_PTR(ary), buf, len * sizeof(VALUE));
+ ary_heap_free_ptr(ary, buf, len * sizeof(VALUE));
+ }
+}
+
static void
ary_resize_capa(VALUE ary, long capacity)
{
diff --git a/gc.c b/gc.c
index 4741cb7b04..39d82a6a77 100644
--- a/gc.c
+++ b/gc.c
@@ -8324,16 +8324,20 @@ gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, V
switch (BUILTIN_TYPE(src)) {
case T_STRING:
obj_size = rb_str_size_as_embedded(src);
- if (rb_gc_size_allocatable_p(obj_size)){
- return &size_pools[size_pool_idx_for_size(obj_size)];
- }
- else {
- GC_ASSERT(!STR_EMBED_P(src));
- return &size_pools[0];
- }
+ break;
+ case T_ARRAY:
+ obj_size = rb_ary_size_as_embedded(src);
+ break;
default:
return src_pool;
}
+
+ if (rb_gc_size_allocatable_p(obj_size)) {
+ return &size_pools[size_pool_idx_for_size(obj_size)];
+ }
+ else {
+ return &size_pools[0];
+ }
}
static bool
@@ -10368,6 +10372,13 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj)
else {
gc_ref_update_array(objspace, obj);
}
+#if USE_RVARGC
+ if ((size_t)GET_HEAP_PAGE(obj)->slot_size >= rb_ary_size_as_embedded(obj)) {
+ if (rb_ary_embeddable_p(obj)) {
+ rb_ary_make_embedded(obj);
+ }
+ }
+#endif
break;
case T_HASH:
diff --git a/internal/array.h b/internal/array.h
index 60f66f31bf..690196a1e2 100644
--- a/internal/array.h
+++ b/internal/array.h
@@ -30,6 +30,9 @@ size_t rb_ary_memsize(VALUE);
VALUE rb_to_array_type(VALUE obj);
VALUE rb_to_array(VALUE obj);
void rb_ary_cancel_sharing(VALUE ary);
+size_t rb_ary_size_as_embedded(VALUE ary);
+void rb_ary_make_embedded(VALUE ary);
+bool rb_ary_embeddable_p(VALUE ary);
static inline VALUE rb_ary_entry_internal(VALUE ary, long offset);
static inline bool ARY_PTR_USING_P(VALUE ary);
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index 3927958e9c..8ecb41438a 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -209,6 +209,46 @@ class TestGCCompact < Test::Unit::TestCase
assert_equal([:call, :line], results)
end
+ def test_moving_arrays_down_size_pools
+ omit if !GC.using_rvargc?
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ARY_COUNT = 500
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ arys = ARY_COUNT.times.map do
+ ary = "abbbbbbbbbb".chars
+ ary.uniq!
+ end
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ assert_operator(stats.dig(:moved_down, :T_ARRAY), :>=, ARY_COUNT)
+ assert(arys) # warning: assigned but unused variable - arys
+ end;
+ end
+
+ def test_moving_arrays_up_size_pools
+ omit if !GC.using_rvargc?
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ARY_COUNT = 500
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ ary = "hello".chars
+ arys = ARY_COUNT.times.map do
+ x = []
+ ary.each { |e| x << e }
+ x
+ end
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ assert_operator(stats.dig(:moved_up, :T_ARRAY), :>=, ARY_COUNT)
+ assert(arys) # warning: assigned but unused variable - arys
+ end;
+ end
+
def test_moving_strings_up_size_pools
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;