Allow classes and modules to become too complex

This makes the behavior of classes and modules when there are too many instance variables match the behavior of objects with too many instance variables.
This commit is contained in:
HParker 2023-02-17 08:15:03 -08:00 коммит произвёл Aaron Patterson
Родитель 65a95b8259
Коммит 69465df424
5 изменённых файлов: 117 добавлений и 30 удалений

6
gc.c
Просмотреть файл

@ -3558,7 +3558,11 @@ 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)) {
RB_DEBUG_COUNTER_INC(obj_obj_too_complex);
rb_id_table_free(RCLASS_TABLE_IVPTR(obj));
}
else if (RCLASS_IVPTR(obj)) {
xfree(RCLASS_IVPTR(obj));
}
if (RCLASS_CONST_TBL(obj)) {

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

@ -96,6 +96,7 @@ STATIC_ASSERT(sizeof_rb_classext_t, sizeof(struct RClass) + sizeof(rb_classext_t
#define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl)
#define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl)
#define RCLASS_IVPTR(c) (RCLASS_EXT(c)->iv_ptr)
#define RCLASS_TABLE_IVPTR(c) (struct rb_id_table *)(RCLASS_EXT(c)->iv_ptr)
#define RCLASS_CALLABLE_M_TBL(c) (RCLASS_EXT(c)->callable_m_tbl)
#define RCLASS_CC_TBL(c) (RCLASS_EXT(c)->cc_tbl)
#define RCLASS_CVC_TBL(c) (RCLASS_EXT(c)->cvc_tbl)

16
shape.c
Просмотреть файл

@ -239,7 +239,7 @@ remove_shape_recursive(VALUE obj, ID id, rb_shape_t * shape, VALUE * removed)
if (new_parent) {
bool dont_care;
enum ruby_value_type type = BUILTIN_TYPE(obj);
bool new_shape_necessary = type != T_OBJECT;
bool new_shape_necessary = type != T_OBJECT && type != T_CLASS && type != T_MODULE;
rb_shape_t * new_child = get_next_shape_internal(new_parent, shape->edge_name, shape->type, &dont_care, true, new_shape_necessary);
new_child->capacity = shape->capacity;
if (new_child->type == SHAPE_IVAR) {
@ -316,12 +316,10 @@ rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
}
bool variation_created = false;
// For non T_OBJECTS, force a new shape
bool new_shape_necessary = BUILTIN_TYPE(obj) != T_OBJECT;
bool new_shape_necessary = BUILTIN_TYPE(obj) != T_OBJECT && BUILTIN_TYPE(obj) != T_CLASS && BUILTIN_TYPE(obj) != T_MODULE;
rb_shape_t * new_shape = get_next_shape_internal(shape, id, SHAPE_IVAR, &variation_created, allow_new_shape, new_shape_necessary);
if (!new_shape) {
RUBY_ASSERT(BUILTIN_TYPE(obj) == T_OBJECT);
new_shape = rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID);
}
@ -336,6 +334,15 @@ rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
RCLASS_EXT(klass)->variation_count++;
}
}
else if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
if (new_shape->next_iv_index > RCLASS_EXT(obj)->max_iv_count) {
RCLASS_EXT(obj)->max_iv_count = new_shape->next_iv_index;
}
if (variation_created) {
RCLASS_EXT(obj)->variation_count++;
}
}
return new_shape;
}
@ -523,7 +530,6 @@ rb_shape_obj_too_complex(VALUE obj)
void
rb_shape_set_too_complex(VALUE obj)
{
RUBY_ASSERT(BUILTIN_TYPE(obj) == T_OBJECT);
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
rb_shape_set_shape_id(obj, OBJ_TOO_COMPLEX_SHAPE_ID);
}

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

@ -105,7 +105,27 @@ class TestShapes < Test::Unit::TestCase
obj.instance_variable_set(:"@a#{_1}", 1)
end
assert_false RubyVM::Shape.of(obj).too_complex?
assert_predicate RubyVM::Shape.of(obj), :too_complex?
end
def test_too_many_ivs_on_module
obj = Module.new
(RubyVM::Shape::SHAPE_MAX_NUM_IVS + 1).times do
obj.instance_variable_set(:"@a#{_1}", 1)
end
assert_predicate RubyVM::Shape.of(obj), :too_complex?
end
def test_too_many_ivs_on_builtin
obj = "string"
(RubyVM::Shape::SHAPE_MAX_NUM_IVS + 1).times do
obj.instance_variable_set(:"@a#{_1}", 1)
end
refute RubyVM::Shape.of(obj).too_complex?
end
def test_removing_when_too_many_ivs_on_class

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

@ -1136,6 +1136,16 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
shape_id = RCLASS_SHAPE_ID(obj);
#endif
if (rb_shape_obj_too_complex(obj)) {
struct rb_id_table * iv_table = RCLASS_TABLE_IVPTR(obj);
if (rb_id_table_lookup(iv_table, id, &val)) {
return val;
}
else {
return undef;
}
}
attr_index_t index = 0;
shape = rb_shape_get_shape_by_id(shape_id);
found = rb_shape_get_iv_index(shape, id, &index);
@ -1268,6 +1278,8 @@ generic_ivar_set(VALUE obj, ID id, VALUE val)
attr_index_t index;
// The returned shape will have `id` in its iv_table
rb_shape_t *shape = rb_shape_get_shape(obj);
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
bool found = rb_shape_get_iv_index(shape, id, &index);
if (!found) {
index = shape->next_iv_index;
@ -1655,6 +1667,7 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu
break;
case T_CLASS:
case T_MODULE:
RUBY_ASSERT(!rb_shape_obj_too_complex(itr_data->obj));
iv_list = RCLASS_IVPTR(itr_data->obj);
break;
default:
@ -1724,7 +1737,13 @@ class_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
struct iv_itr_data itr_data;
itr_data.obj = obj;
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_id_table_foreach(RCLASS_TABLE_IVPTR(obj), each_hash_iv, &itr_data);
}
else {
iterate_over_shapes_with_callback(shape, func, &itr_data);
}
}
void
@ -1984,7 +2003,14 @@ rb_obj_remove_instance_variable(VALUE obj, VALUE name)
case T_CLASS:
case T_MODULE:
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id);
rb_shape_transition_shape_remove_ivar(obj, id, shape, &val);
if (rb_shape_obj_too_complex(obj)) {
if (rb_id_table_lookup(RCLASS_TABLE_IVPTR(obj), id, &val)) {
rb_id_table_delete(RCLASS_TABLE_IVPTR(obj), id);
}
} else {
rb_shape_transition_shape_remove_ivar(obj, id, shape, &val);
}
break;
case T_OBJECT: {
if (rb_shape_obj_too_complex(obj)) {
@ -3961,35 +3987,65 @@ rb_class_ivar_set(VALUE obj, ID key, VALUE value)
RB_VM_LOCK_ENTER();
{
rb_shape_t * shape = rb_shape_get_shape(obj);
attr_index_t idx;
found = rb_shape_get_iv_index(shape, key, &idx);
if (found) {
// Changing an existing instance variable
RUBY_ASSERT(RCLASS_IVPTR(obj));
RCLASS_IVPTR(obj)[idx] = value;
if (rb_shape_obj_too_complex(obj)) {
struct rb_id_table * iv_table = RCLASS_TABLE_IVPTR(obj);
rb_id_table_insert(iv_table, key, value);
RB_OBJ_WRITTEN(obj, Qundef, value);
found = 0;
}
else {
// Creating and setting a new instance variable
rb_shape_t * shape = rb_shape_get_shape(obj);
attr_index_t idx;
found = rb_shape_get_iv_index(shape, key, &idx);
// Move to a shape which fits the new ivar
idx = shape->next_iv_index;
shape = rb_shape_get_next(shape, obj, key);
if (found) {
// Changing an existing instance variable
RUBY_ASSERT(RCLASS_IVPTR(obj));
// We always allocate a power of two sized IV array. This way we
// only need to realloc when we expand into a new power of two size
if ((idx & (idx - 1)) == 0) {
size_t newsize = idx ? idx * 2 : 1;
REALLOC_N(RCLASS_IVPTR(obj), VALUE, newsize);
RCLASS_IVPTR(obj)[idx] = value;
RB_OBJ_WRITTEN(obj, Qundef, value);
}
else {
// Creating and setting a new instance variable
RUBY_ASSERT(RCLASS_IVPTR(obj));
// Move to a shape which fits the new ivar
idx = shape->next_iv_index;
shape = rb_shape_get_next(shape, obj, key);
RB_OBJ_WRITE(obj, &RCLASS_IVPTR(obj)[idx], value);
rb_shape_set_shape(obj, shape);
// stop using shapes if we are now too complex
if (shape->type == SHAPE_OBJ_TOO_COMPLEX) {
struct rb_id_table * table = rb_id_table_create(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);
// insert the new value
rb_id_table_insert(table, key, value);
RB_OBJ_WRITTEN(obj, Qundef, value);
rb_shape_set_too_complex(obj);
RUBY_ASSERT(rb_shape_obj_too_complex(obj));
if (RCLASS_IVPTR(obj)) {
xfree(RCLASS_IVPTR(obj));
}
RCLASS_EXT(obj)->iv_ptr = (VALUE *)table;
}
else {
// We always allocate a power of two sized IV array. This way we
// only need to realloc when we expand into a new power of two size
if ((idx & (idx - 1)) == 0) {
size_t newsize = idx ? idx * 2 : 1;
REALLOC_N(RCLASS_IVPTR(obj), VALUE, newsize);
}
RUBY_ASSERT(RCLASS_IVPTR(obj));
RB_OBJ_WRITE(obj, &RCLASS_IVPTR(obj)[idx], value);
rb_shape_set_shape(obj, shape);
}
}
}
}
RB_VM_LOCK_LEAVE();