diff options
-rw-r--r-- | gc.c | 15 | ||||
-rw-r--r-- | internal/variable.h | 1 | ||||
-rw-r--r-- | object.c | 18 | ||||
-rw-r--r-- | shape.c | 12 | ||||
-rw-r--r-- | shape.h | 4 | ||||
-rw-r--r-- | test/ruby/test_shapes.rb | 94 | ||||
-rw-r--r-- | variable.c | 178 |
7 files changed, 244 insertions, 78 deletions
@@ -3533,9 +3533,13 @@ obj_free(rb_objspace_t *objspace, VALUE obj) case T_CLASS: rb_id_table_free(RCLASS_M_TBL(obj)); cc_table_free(objspace, obj, FALSE); - if (RCLASS_IVPTR(obj)) { + if (rb_shape_obj_too_complex(obj)) { + st_free_table((st_table *)RCLASS_IVPTR(obj)); + } + else if (RCLASS_IVPTR(obj)) { xfree(RCLASS_IVPTR(obj)); } + if (RCLASS_CONST_TBL(obj)) { rb_free_const_table(RCLASS_CONST_TBL(obj)); } @@ -7256,8 +7260,13 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) mark_m_tbl(objspace, RCLASS_M_TBL(obj)); mark_cvc_tbl(objspace, obj); cc_table_mark(objspace, obj); - for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { - gc_mark(objspace, RCLASS_IVPTR(obj)[i]); + if (rb_shape_obj_too_complex(obj)) { + mark_tbl(objspace, (st_table *)RCLASS_IVPTR(obj)); + } + else { + for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { + gc_mark(objspace, RCLASS_IVPTR(obj)[i]); + } } mark_const_tbl(objspace, RCLASS_CONST_TBL(obj)); diff --git a/internal/variable.h b/internal/variable.h index c467489b77..c9a3a4676d 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -48,6 +48,7 @@ VALUE rb_mod_set_temporary_name(VALUE, VALUE); struct gen_ivtbl; int rb_gen_ivtbl_get(VALUE obj, ID id, struct gen_ivtbl **ivtbl); int rb_obj_evacuate_ivs_to_hash_table(ID key, VALUE val, st_data_t arg); +void rb_evict_ivars_to_hash(VALUE obj, rb_shape_t * shape); RUBY_SYMBOL_EXPORT_BEGIN /* variable.c (export) */ @@ -463,7 +463,13 @@ mutable_obj_clone(VALUE obj, VALUE kwfreeze) rb_funcall(clone, id_init_clone, 1, obj); RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE; if (RB_OBJ_FROZEN(obj)) { - rb_shape_transition_shape_frozen(clone); + rb_shape_t * next_shape = rb_shape_transition_shape_frozen(clone); + if (!rb_shape_obj_too_complex(clone) && next_shape->type == SHAPE_OBJ_TOO_COMPLEX) { + rb_evict_ivars_to_hash(clone, rb_shape_get_shape(clone)); + } + else { + rb_shape_set_shape(clone, next_shape); + } } break; case Qtrue: { @@ -479,7 +485,15 @@ mutable_obj_clone(VALUE obj, VALUE kwfreeze) argv[1] = freeze_true_hash; rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS); RBASIC(clone)->flags |= FL_FREEZE; - rb_shape_transition_shape_frozen(clone); + rb_shape_t * next_shape = rb_shape_transition_shape_frozen(clone); + // If we're out of shapes, but we want to freeze, then we need to + // evacuate this clone to a hash + if (!rb_shape_obj_too_complex(clone) && next_shape->type == SHAPE_OBJ_TOO_COMPLEX) { + rb_evict_ivars_to_hash(clone, rb_shape_get_shape(clone)); + } + else { + rb_shape_set_shape(clone, next_shape); + } break; } case Qfalse: { @@ -592,7 +592,7 @@ rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape, VALUE } } -void +rb_shape_t * rb_shape_transition_shape_frozen(VALUE obj) { rb_shape_t* shape = rb_shape_get_shape(obj); @@ -600,21 +600,23 @@ rb_shape_transition_shape_frozen(VALUE obj) RUBY_ASSERT(RB_OBJ_FROZEN(obj)); if (rb_shape_frozen_shape_p(shape) || rb_shape_obj_too_complex(obj)) { - return; + return shape; } rb_shape_t* next_shape; if (shape == rb_shape_get_root_shape()) { - rb_shape_set_shape_id(obj, SPECIAL_CONST_SHAPE_ID); - return; + return rb_shape_get_shape_by_id(SPECIAL_CONST_SHAPE_ID); } bool dont_care; next_shape = get_next_shape_internal(shape, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true, false); + if (!next_shape) { + next_shape = rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID); + } RUBY_ASSERT(next_shape); - rb_shape_set_shape(obj, next_shape); + return next_shape; } /* @@ -33,7 +33,7 @@ typedef uint16_t redblack_id_t; # define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * 8) - SHAPE_ID_NUM_BITS) # define SHAPE_MAX_VARIATIONS 8 -# define SHAPE_MAX_NUM_IVS (SHAPE_BUFFER_SIZE - 1) +# define SHAPE_MAX_NUM_IVS 80 # define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1) # define INVALID_SHAPE_ID SHAPE_MASK @@ -165,7 +165,7 @@ bool rb_shape_obj_too_complex(VALUE obj); void rb_shape_set_shape(VALUE obj, rb_shape_t* shape); rb_shape_t* rb_shape_get_shape(VALUE obj); int rb_shape_frozen_shape_p(rb_shape_t* shape); -void rb_shape_transition_shape_frozen(VALUE obj); +rb_shape_t* rb_shape_transition_shape_frozen(VALUE obj); void rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape, VALUE * removed); rb_shape_t * rb_shape_transition_shape_capa(rb_shape_t * shape); rb_shape_t* rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id); diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index ebc94a12d2..591fdbe4a3 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -126,13 +126,24 @@ class TestShapes < Test::Unit::TestCase end def test_too_many_ivs_on_obj - obj = Object.new + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end - (RubyVM::Shape::SHAPE_MAX_NUM_IVS + 1).times do - obj.instance_variable_set(:"@a#{_1}", 1) - end + obj = Hi.new + i = 0 + while RubyVM::Shape.shapes_available > 2 + obj.instance_variable_set(:"@a#{i}", 1) + i += 1 + end + + obj = Hi.new + obj.instance_variable_set(:"@b", 1) + obj.instance_variable_set(:"@c", 1) + obj.instance_variable_set(:"@d", 1) - assert_predicate RubyVM::Shape.of(obj), :too_complex? + assert_predicate RubyVM::Shape.of(obj), :too_complex? + end; end def test_too_many_ivs_on_class @@ -171,6 +182,79 @@ class TestShapes < Test::Unit::TestCase assert_empty obj.instance_variables end + def test_use_all_shapes_then_freeze + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + + obj = Hi.new + i = 0 + while RubyVM::Shape.shapes_available > 2 + obj.instance_variable_set(:"@a#{i}", 1) + i += 1 + end + + obj = Hi.new + i = 0 + while RubyVM::Shape.shapes_available > 0 + obj.instance_variable_set(:"@b#{i}", 1) + i += 1 + end + obj.freeze + obj.frozen? + end; + end + + def test_use_all_shapes_module + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + + obj = Hi.new + i = 0 + while RubyVM::Shape.shapes_available > 2 + obj.instance_variable_set(:"@a#{i}", 1) + i += 1 + end + + obj = Module.new + 3.times do + obj.instance_variable_set(:"@a#{_1}", _1) + end + + ivs = 3.times.map do + obj.instance_variable_get(:"@a#{_1}") + end + + assert_equal [0, 1, 2], ivs + end; + end + + def test_complex_freeze_after_clone + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + + obj = Hi.new + i = 0 + while RubyVM::Shape.shapes_available > 2 + obj.instance_variable_set(:"@a#{i}", 1) + i += 1 + end + + obj = Object.new + i = 0 + while RubyVM::Shape.shapes_available > 0 + obj.instance_variable_set(:"@b#{i}", i) + i += 1 + end + + v = obj.clone(freeze: true) + assert_predicate v, :frozen? + assert_equal 0, v.instance_variable_get(:@b0) + end; + end + def test_too_complex_ractor assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; diff --git a/variable.c b/variable.c index 0c9e6b2618..a42032bd41 100644 --- a/variable.c +++ b/variable.c @@ -1155,10 +1155,19 @@ gen_ivtbl_mark_and_update(struct gen_ivtbl *ivtbl) void rb_mark_and_update_generic_ivar(VALUE obj) { - struct gen_ivtbl *ivtbl; + if (rb_shape_obj_too_complex(obj)) { + st_table *ivtbl; - if (rb_gen_ivtbl_get(obj, 0, &ivtbl)) { - gen_ivtbl_mark_and_update(ivtbl); + if (rb_gen_ivtbl_get(obj, 0, (struct gen_ivtbl **)&ivtbl)) { + rb_mark_tbl(ivtbl); + } + } + else { + struct gen_ivtbl *ivtbl; + + if (rb_gen_ivtbl_get(obj, 0, &ivtbl)) { + gen_ivtbl_mark_and_update(ivtbl); + } } } @@ -1317,6 +1326,18 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) if (FL_TEST_RAW(obj, FL_EXIVAR)) { struct gen_ivtbl *ivtbl; rb_gen_ivtbl_get(obj, id, &ivtbl); + + if (rb_shape_obj_too_complex(obj)) { + st_table * iv_table = (st_table *)ivtbl; + VALUE val; + if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { + return val; + } + else { + return undef; + } + } + #if !SHAPE_IN_BASIC_FLAGS shape_id = ivtbl->shape_id; #endif @@ -1387,6 +1408,74 @@ rb_attr_delete(VALUE obj, ID id) return rb_ivar_delete(obj, id, Qnil); } +static int +rb_complex_ivar_set(VALUE obj, ID id, VALUE val) +{ + st_table * table; + RUBY_ASSERT(rb_shape_obj_too_complex(obj)); + + switch (BUILTIN_TYPE(obj)) { + case T_OBJECT: + table = ROBJECT_IV_HASH(obj); + break; + case T_CLASS: + case T_MODULE: + table = RCLASS_IV_HASH(obj); + break; + default: + if (!rb_gen_ivtbl_get(obj, 0, (struct gen_ivtbl **)&table)) { + rb_bug("Object should have a gen_iv entry"); + } + } + + int found = st_insert(table, (st_data_t)id, (st_data_t)val); + RB_OBJ_WRITTEN(obj, Qundef, val); + return found; +} + +void +rb_evict_ivars_to_hash(VALUE obj, rb_shape_t * shape) +{ + RUBY_ASSERT(!rb_shape_obj_too_complex(obj)); + + st_table * table = st_init_numtable_with_size(shape->next_iv_index); + + // Evacuate all previous values from shape into id_table + rb_ivar_foreach(obj, rb_obj_evacuate_ivs_to_hash_table, (st_data_t)table); + + rb_shape_set_too_complex(obj); + RUBY_ASSERT(rb_shape_obj_too_complex(obj)); + + switch (BUILTIN_TYPE(obj)) { + case T_OBJECT: + if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { + xfree(ROBJECT(obj)->as.heap.ivptr); + } + + ROBJECT_SET_IV_HASH(obj, table); + break; + case T_CLASS: + case T_MODULE: + xfree(RCLASS_IVPTR(obj)); + RCLASS_SET_IV_HASH(obj, table); + break; + default: + RB_VM_LOCK_ENTER(); + { + struct st_table * gen_ivs = generic_ivtbl_no_ractor_check(obj); + st_data_t ivtbl; + + // Free the old IV table + if (st_delete(gen_ivs, &obj, &ivtbl)) + xfree((struct gen_ivtbl *)ivtbl); + + // Insert the hash table + st_insert(gen_ivs, (st_data_t)obj, (st_data_t)table); + } + RB_VM_LOCK_LEAVE(); + } +} + static void generic_ivar_set(VALUE obj, ID id, VALUE val) { @@ -1396,10 +1485,18 @@ generic_ivar_set(VALUE obj, ID id, VALUE val) // The returned shape will have `id` in its iv_table rb_shape_t *shape = rb_shape_get_shape(obj); bool found = rb_shape_get_iv_index(shape, id, &index); + rb_shape_t *next_shape = shape; if (!found) { index = shape->next_iv_index; - shape = rb_shape_get_next(shape, obj, id); - RUBY_ASSERT(index == (shape->next_iv_index - 1)); + next_shape = rb_shape_get_next(shape, obj, id); + if (next_shape->type == SHAPE_OBJ_TOO_COMPLEX) { + rb_evict_ivars_to_hash(obj, shape); + rb_complex_ivar_set(obj, id, val); + rb_shape_set_shape(obj, next_shape); + return; + } + shape = next_shape; + RUBY_ASSERT(index == (next_shape->next_iv_index - 1)); } ivup.max_index = shape->next_iv_index; @@ -1467,60 +1564,6 @@ rb_ensure_generic_iv_list_size(VALUE obj, rb_shape_t *shape, uint32_t newsize) return ivtbl; } -static int -rb_complex_ivar_set(VALUE obj, ID id, VALUE val) -{ - st_table * table; - RUBY_ASSERT(rb_shape_obj_too_complex(obj)); - - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - table = ROBJECT_IV_HASH(obj); - break; - case T_CLASS: - case T_MODULE: - table = RCLASS_IV_HASH(obj); - break; - default: - rb_bug("oh no"); - } - - int found = st_insert(table, (st_data_t)id, (st_data_t)val); - RB_OBJ_WRITTEN(obj, Qundef, val); - return found; -} - -static void -rb_evict_ivars_to_hash(VALUE obj, rb_shape_t * shape) -{ - RUBY_ASSERT(!rb_shape_obj_too_complex(obj)); - - st_table * table = st_init_numtable_with_size(shape->next_iv_index); - - // Evacuate all previous values from shape into id_table - rb_ivar_foreach(obj, rb_obj_evacuate_ivs_to_hash_table, (st_data_t)table); - - rb_shape_set_too_complex(obj); - RUBY_ASSERT(rb_shape_obj_too_complex(obj)); - - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { - xfree(ROBJECT(obj)->as.heap.ivptr); - } - - ROBJECT_SET_IV_HASH(obj, table); - break; - case T_CLASS: - case T_MODULE: - xfree(RCLASS_IVPTR(obj)); - RCLASS_SET_IV_HASH(obj, table); - break; - default: - rb_bug("oops!"); - } -} - // @note May raise when there are too many instance variables. rb_shape_t * rb_grow_iv_list(VALUE obj) @@ -1664,7 +1707,14 @@ void rb_obj_freeze_inline(VALUE x) if (RB_FL_ABLE(x)) { RB_OBJ_FREEZE_RAW(x); - rb_shape_transition_shape_frozen(x); + rb_shape_t * next_shape = rb_shape_transition_shape_frozen(x); + + // If we're transitioning from "not complex" to "too complex" + // then evict ivars. This can happen if we run out of shapes + if (!rb_shape_obj_too_complex(x) && next_shape->type == SHAPE_OBJ_TOO_COMPLEX) { + rb_evict_ivars_to_hash(x, rb_shape_get_shape(x)); + } + rb_shape_set_shape(x, next_shape); if (RBASIC_CLASS(x) && !(RBASIC(x)->flags & RUBY_FL_SINGLETON)) { rb_freeze_singleton_class(x); @@ -1827,7 +1877,13 @@ gen_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) itr_data.obj = obj; itr_data.ivtbl = ivtbl; itr_data.arg = arg; - iterate_over_shapes_with_callback(shape, func, &itr_data); + itr_data.func = func; + if (rb_shape_obj_too_complex(obj)) { + rb_st_foreach((st_table *)ivtbl, each_hash_iv, (st_data_t)&itr_data); + } + else { + iterate_over_shapes_with_callback(shape, func, &itr_data); + } } static void |