geniv objects can become too complex

This commit is contained in:
Aaron Patterson 2023-10-19 16:01:35 -07:00 коммит произвёл Aaron Patterson
Родитель caf6a72348
Коммит a3f66e09f6
7 изменённых файлов: 244 добавлений и 78 удалений

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) */

Просмотреть файл

@ -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
Просмотреть файл

@ -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);

Просмотреть файл

@ -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;

Просмотреть файл

@ -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