remove IV limit / support complex shapes on classes

This commit is contained in:
Aaron Patterson 2023-10-19 11:00:54 -07:00 коммит произвёл Aaron Patterson
Родитель 27c7531939
Коммит caf6a72348
3 изменённых файлов: 165 добавлений и 68 удалений

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

@ -348,7 +348,7 @@ shape_alloc(void)
shape_id_t shape_id = GET_SHAPE_TREE()->next_shape_id;
GET_SHAPE_TREE()->next_shape_id++;
if (shape_id == MAX_SHAPE_ID) {
if (shape_id == (MAX_SHAPE_ID + 1)) {
// TODO: Make an OutOfShapesError ??
rb_bug("Out of shapes");
}
@ -445,7 +445,7 @@ get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type, b
*variation_created = false;
if (new_shape_necessary || (new_shapes_allowed && (shape->next_iv_index < SHAPE_MAX_NUM_IVS))) {
if (new_shape_necessary || (new_shapes_allowed && (GET_SHAPE_TREE()->next_shape_id <= MAX_SHAPE_ID))) {
RB_VM_LOCK_ENTER();
{
// If the current shape has children
@ -633,6 +633,7 @@ rb_shape_t *
rb_shape_get_next(rb_shape_t* shape, VALUE obj, ID id)
{
RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id))));
RUBY_ASSERT(shape->type != SHAPE_OBJ_TOO_COMPLEX);
bool allow_new_shape = true;
@ -643,11 +644,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 = false;
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);
}
@ -850,7 +850,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);
}
@ -1005,6 +1004,13 @@ rb_shape_root_shape(VALUE self)
return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape());
}
/* :nodoc: */
static VALUE
rb_shape_shapes_available(VALUE self)
{
return INT2NUM(MAX_SHAPE_ID - (GET_SHAPE_TREE()->next_shape_id - 1));
}
VALUE rb_obj_shape(rb_shape_t* shape);
static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE value, void *ref)
@ -1183,5 +1189,6 @@ Init_shape(void)
rb_define_singleton_method(rb_cShape, "find_by_id", rb_shape_find_by_id, 1);
rb_define_singleton_method(rb_cShape, "of", rb_shape_debug_shape, 1);
rb_define_singleton_method(rb_cShape, "root_shape", rb_shape_root_shape, 0);
rb_define_singleton_method(rb_cShape, "shapes_available", rb_shape_shapes_available, 0);
#endif
}

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

@ -35,7 +35,7 @@ typedef uint16_t redblack_id_t;
# define SHAPE_MAX_VARIATIONS 8
# define SHAPE_MAX_NUM_IVS (SHAPE_BUFFER_SIZE - 1)
# define MAX_SHAPE_ID SHAPE_BUFFER_SIZE
# define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
# define INVALID_SHAPE_ID SHAPE_MASK
# define ROOT_SHAPE_ID 0x0
@ -191,7 +191,7 @@ ROBJECT_IV_HASH(VALUE obj)
}
static inline void
ROBJECT_SET_IV_HASH(VALUE obj, const struct rb_id_table *tbl)
ROBJECT_SET_IV_HASH(VALUE obj, const st_table *tbl)
{
RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);
RUBY_ASSERT(ROBJECT_SHAPE_ID(obj) == OBJ_TOO_COMPLEX_SHAPE_ID);

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

@ -72,6 +72,22 @@ struct ivar_update {
#endif
};
static inline st_table *
RCLASS_IV_HASH(VALUE obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
RUBY_ASSERT(RCLASS_SHAPE_ID(obj) == OBJ_TOO_COMPLEX_SHAPE_ID);
return (st_table *)RCLASS_IVPTR(obj);
}
static inline void
RCLASS_SET_IV_HASH(VALUE obj, const st_table *tbl)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
RUBY_ASSERT(RCLASS_SHAPE_ID(obj) == OBJ_TOO_COMPLEX_SHAPE_ID);
RCLASS_IVPTR(obj) = (VALUE *)tbl;
}
void
Init_var_tables(void)
{
@ -1231,7 +1247,7 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
case T_CLASS:
case T_MODULE:
{
bool found;
bool found = false;
VALUE val;
RB_VM_LOCK_ENTER();
@ -1240,18 +1256,30 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
shape_id = RCLASS_SHAPE_ID(obj);
#endif
attr_index_t index = 0;
shape = rb_shape_get_shape_by_id(shape_id);
found = rb_shape_get_iv_index(shape, id, &index);
if (found) {
ivar_list = RCLASS_IVPTR(obj);
RUBY_ASSERT(ivar_list);
val = ivar_list[index];
if (rb_shape_obj_too_complex(obj)) {
st_table * iv_table = RCLASS_IV_HASH(obj);
if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
found = true;
}
else {
val = undef;
}
}
else {
val = undef;
attr_index_t index = 0;
shape = rb_shape_get_shape_by_id(shape_id);
found = rb_shape_get_iv_index(shape, id, &index);
if (found) {
ivar_list = RCLASS_IVPTR(obj);
RUBY_ASSERT(ivar_list);
val = ivar_list[index];
}
else {
val = undef;
}
}
}
RB_VM_LOCK_LEAVE();
@ -1439,6 +1467,60 @@ 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)
@ -1446,10 +1528,13 @@ rb_grow_iv_list(VALUE obj)
rb_shape_t * initial_shape = rb_shape_get_shape(obj);
RUBY_ASSERT(initial_shape->capacity > 0);
rb_shape_t * res = rb_shape_transition_shape_capa(initial_shape);
rb_ensure_iv_list_size(obj, initial_shape->capacity, res->capacity);
rb_shape_set_shape(obj, res);
if (res->type == SHAPE_OBJ_TOO_COMPLEX) { // Out of shapes
rb_evict_ivars_to_hash(obj, initial_shape);
}
else {
rb_ensure_iv_list_size(obj, initial_shape->capacity, res->capacity);
rb_shape_set_shape(obj, res);
}
return res;
}
@ -1470,12 +1555,12 @@ rb_obj_ivar_set(VALUE obj, ID id, VALUE val)
uint32_t num_iv = shape->capacity;
if (rb_shape_obj_too_complex(obj)) {
st_table * table = ROBJECT_IV_HASH(obj);
st_insert(table, (st_data_t)id, (st_data_t)val);
RB_OBJ_WRITTEN(obj, Qundef, val);
rb_complex_ivar_set(obj, id, val);
return 0;
}
rb_shape_t *next_shape;
if (!rb_shape_get_iv_index(shape, id, &index)) {
index = shape->next_iv_index;
if (index >= MAX_IVARS) {
@ -1487,31 +1572,20 @@ rb_obj_ivar_set(VALUE obj, ID id, VALUE val)
if (UNLIKELY(shape->next_iv_index >= num_iv)) {
RUBY_ASSERT(shape->next_iv_index == num_iv);
shape = rb_grow_iv_list(obj);
next_shape = rb_grow_iv_list(obj);
if (next_shape->type == SHAPE_OBJ_TOO_COMPLEX) {
rb_complex_ivar_set(obj, id, val);
return 0;
}
shape = next_shape;
RUBY_ASSERT(shape->type == SHAPE_CAPACITY_CHANGE);
}
rb_shape_t *next_shape = rb_shape_get_next(shape, obj, id);
next_shape = rb_shape_get_next(shape, obj, id);
if (next_shape->type == SHAPE_OBJ_TOO_COMPLEX) {
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);
// Insert new value too
st_insert(table, (st_data_t)id, (st_data_t)val);
RB_OBJ_WRITTEN(obj, Qundef, val);
rb_shape_set_too_complex(obj);
RUBY_ASSERT(rb_shape_obj_too_complex(obj));
if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) {
xfree(ROBJECT(obj)->as.heap.ivptr);
}
ROBJECT(obj)->as.heap.ivptr = (VALUE *)table;
rb_evict_ivars_to_hash(obj, shape);
rb_complex_ivar_set(obj, id, val);
return 0;
}
else {
@ -1765,7 +1839,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_st_foreach(RCLASS_IV_HASH(obj), each_hash_iv, (st_data_t)&itr_data);
}
else {
iterate_over_shapes_with_callback(shape, func, &itr_data);
}
}
void
@ -3989,40 +4069,50 @@ int
rb_class_ivar_set(VALUE obj, ID key, VALUE value)
{
RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
int found;
int found = 0;
rb_check_frozen(obj);
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;
RB_OBJ_WRITTEN(obj, Qundef, value);
if (shape->type == SHAPE_OBJ_TOO_COMPLEX) {
found = rb_complex_ivar_set(obj, key, value);
}
else {
// Creating and setting a new instance variable
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;
rb_shape_t * next_shape = rb_shape_get_next(shape, obj, key);
if (next_shape->type == SHAPE_OBJ_TOO_COMPLEX) {
rb_evict_ivars_to_hash(obj, shape);
rb_complex_ivar_set(obj, key, value);
}
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);
}
RB_OBJ_WRITE(obj, &RCLASS_IVPTR(obj)[idx], value);
rb_shape_set_shape(obj, shape);
RUBY_ASSERT(RCLASS_IVPTR(obj));
RB_OBJ_WRITE(obj, &RCLASS_IVPTR(obj)[idx], value);
rb_shape_set_shape(obj, next_shape);
}
}
}
}
RB_VM_LOCK_LEAVE();