summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gc.c15
-rw-r--r--internal/variable.h1
-rw-r--r--object.c18
-rw-r--r--shape.c12
-rw-r--r--shape.h4
-rw-r--r--test/ruby/test_shapes.rb94
-rw-r--r--variable.c178
7 files changed, 244 insertions, 78 deletions
diff --git a/gc.c b/gc.c
index a9167e2ede..fce9202d9f 100644
--- a/gc.c
+++ b/gc.c
@@ -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) */
diff --git a/object.c b/object.c
index c32cb82dd9..4c589b2655 100644
--- a/object.c
+++ b/object.c
@@ -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: {
diff --git a/shape.c b/shape.c
index 54805e27c8..0dd0c109a0 100644
--- a/shape.c
+++ b/shape.c
@@ -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;
}
/*
diff --git a/shape.h b/shape.h
index 68820ca2cc..7424701200 100644
--- a/shape.h
+++ b/shape.h
@@ -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