зеркало из https://github.com/github/ruby.git
geniv objects can become too complex
This commit is contained in:
Родитель
caf6a72348
Коммит
a3f66e09f6
15
gc.c
15
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));
|
||||
|
||||
|
|
|
@ -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) */
|
||||
|
|
18
object.c
18
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: {
|
||||
|
|
12
shape.c
12
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
4
shape.h
4
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);
|
||||
|
|
|
@ -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
|
||||
|
||||
assert_predicate RubyVM::Shape.of(obj), :too_complex?
|
||||
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?
|
||||
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;
|
||||
|
|
178
variable.c
178
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче