diff options
-rw-r--r-- | array.c | 42 | ||||
-rw-r--r-- | gc.c | 25 | ||||
-rw-r--r-- | internal/array.h | 3 | ||||
-rw-r--r-- | test/ruby/test_gc_compact.rb | 40 |
4 files changed, 102 insertions, 8 deletions
@@ -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) { @@ -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; |