Differentiate T_OBJECT shapes from other objects

We would like to differentiate types of objects via their shape.  This
commit adds a special T_OBJECT shape when we allocate an instance of
T_OBJECT.  This allows us to avoid testing whether an object is an
instance of a T_OBJECT or not, we can just check the shape.
This commit is contained in:
Aaron Patterson 2022-11-17 15:57:11 -08:00 коммит произвёл Aaron Patterson
Родитель f0ce118662
Коммит 10788166e7
6 изменённых файлов: 46 добавлений и 10 удалений

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

@ -2942,6 +2942,12 @@ rb_class_instance_allocate_internal(VALUE klass, VALUE flags, bool wb_protected)
#endif #endif
VALUE obj = newobj_of(klass, flags, 0, 0, 0, wb_protected, size); VALUE obj = newobj_of(klass, flags, 0, 0, 0, wb_protected, size);
RUBY_ASSERT(rb_shape_get_shape(obj)->type == SHAPE_ROOT ||
rb_shape_get_shape(obj)->type == SHAPE_INITIAL_CAPACITY);
// Set the shape to the specific T_OBJECT shape which is always
// SIZE_POOL_COUNT away from the root shape.
ROBJECT_SET_SHAPE_ID(obj, ROBJECT_SHAPE_ID(obj) + SIZE_POOL_COUNT);
#if RUBY_DEBUG #if RUBY_DEBUG
VALUE *ptr = ROBJECT_IVPTR(obj); VALUE *ptr = ROBJECT_IVPTR(obj);
@ -10030,7 +10036,7 @@ gc_ref_update_object(rb_objspace_t *objspace, VALUE v)
} }
ptr = ROBJECT(v)->as.ary; ptr = ROBJECT(v)->as.ary;
size_t size_pool_shape_id = size_pool_idx_for_size(embed_size); size_t size_pool_shape_id = size_pool_idx_for_size(embed_size);
rb_shape_t * initial_shape = rb_shape_get_shape_by_id((shape_id_t)size_pool_shape_id); rb_shape_t * initial_shape = rb_shape_get_shape_by_id((shape_id_t)size_pool_shape_id + SIZE_POOL_COUNT);
rb_shape_t * new_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_shape(v)); rb_shape_t * new_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_shape(v));
rb_shape_set_shape(v, new_shape); rb_shape_set_shape(v, new_shape);
} }

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

@ -293,7 +293,7 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj)
rb_shape_t * initial_shape = rb_shape_get_shape(dest); rb_shape_t * initial_shape = rb_shape_get_shape(dest);
if (initial_shape->size_pool_index != src_shape->size_pool_index) { if (initial_shape->size_pool_index != src_shape->size_pool_index) {
RUBY_ASSERT(initial_shape->parent_id == ROOT_SHAPE_ID || initial_shape->type == SHAPE_ROOT); RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT);
shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape); shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape);
} }

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

@ -8,6 +8,7 @@
#include <stdbool.h> #include <stdbool.h>
static ID id_frozen; static ID id_frozen;
static ID id_t_object;
static ID size_pool_edge_names[SIZE_POOL_COUNT]; static ID size_pool_edge_names[SIZE_POOL_COUNT];
/* /*
@ -152,6 +153,7 @@ get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type)
case SHAPE_CAPACITY_CHANGE: case SHAPE_CAPACITY_CHANGE:
case SHAPE_IVAR_UNDEF: case SHAPE_IVAR_UNDEF:
case SHAPE_FROZEN: case SHAPE_FROZEN:
case SHAPE_T_OBJECT:
new_shape->next_iv_index = shape->next_iv_index; new_shape->next_iv_index = shape->next_iv_index;
break; break;
case SHAPE_INITIAL_CAPACITY: case SHAPE_INITIAL_CAPACITY:
@ -264,6 +266,7 @@ rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
case SHAPE_IVAR_UNDEF: case SHAPE_IVAR_UNDEF:
case SHAPE_ROOT: case SHAPE_ROOT:
case SHAPE_INITIAL_CAPACITY: case SHAPE_INITIAL_CAPACITY:
case SHAPE_T_OBJECT:
return false; return false;
case SHAPE_FROZEN: case SHAPE_FROZEN:
rb_bug("Ivar should not exist on transition\n"); rb_bug("Ivar should not exist on transition\n");
@ -333,14 +336,16 @@ rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape)
{ {
rb_shape_t * midway_shape; rb_shape_t * midway_shape;
if (dest_shape->type != SHAPE_ROOT) { RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT);
if (dest_shape->type != initial_shape->type) {
midway_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_parent(dest_shape)); midway_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_parent(dest_shape));
} }
else { else {
midway_shape = initial_shape; midway_shape = initial_shape;
} }
switch (dest_shape->type) { switch ((enum shape_type)dest_shape->type) {
case SHAPE_IVAR: case SHAPE_IVAR:
if (midway_shape->capacity <= midway_shape->next_iv_index) { if (midway_shape->capacity <= midway_shape->next_iv_index) {
// There isn't enough room to write this IV, so we need to increase the capacity // There isn't enough room to write this IV, so we need to increase the capacity
@ -355,6 +360,8 @@ rb_shape_rebuild_shape(rb_shape_t * initial_shape, rb_shape_t * dest_shape)
case SHAPE_ROOT: case SHAPE_ROOT:
case SHAPE_FROZEN: case SHAPE_FROZEN:
case SHAPE_CAPACITY_CHANGE: case SHAPE_CAPACITY_CHANGE:
case SHAPE_INITIAL_CAPACITY:
case SHAPE_T_OBJECT:
break; break;
} }
@ -592,6 +599,7 @@ void
Init_default_shapes(void) Init_default_shapes(void)
{ {
id_frozen = rb_make_internal_id(); id_frozen = rb_make_internal_id();
id_t_object = rb_make_internal_id();
// Shapes by size pool // Shapes by size pool
for (int i = 0; i < SIZE_POOL_COUNT; i++) { for (int i = 0; i < SIZE_POOL_COUNT; i++) {
@ -615,6 +623,16 @@ Init_default_shapes(void)
RUBY_ASSERT(rb_shape_id(new_shape) == (shape_id_t)i); RUBY_ASSERT(rb_shape_id(new_shape) == (shape_id_t)i);
} }
// Make shapes for T_OBJECT
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
rb_shape_t * shape = rb_shape_get_shape_by_id(i);
#if RUBY_DEBUG
rb_shape_t * t_object_shape =
#endif
get_next_shape_internal(shape, id_t_object, SHAPE_T_OBJECT);
RUBY_ASSERT(rb_shape_id(t_object_shape) == (shape_id_t)(i + SIZE_POOL_COUNT));
}
// Special const shape // Special const shape
#if RUBY_DEBUG #if RUBY_DEBUG
rb_shape_t * special_const_shape = rb_shape_t * special_const_shape =
@ -644,6 +662,7 @@ Init_shape(void)
rb_define_method(rb_cShape, "capacity", rb_shape_capacity, 0); rb_define_method(rb_cShape, "capacity", rb_shape_capacity, 0);
rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT)); rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR)); rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR));
rb_define_const(rb_cShape, "SHAPE_T_OBJECT", INT2NUM(SHAPE_T_OBJECT));
rb_define_const(rb_cShape, "SHAPE_IVAR_UNDEF", INT2NUM(SHAPE_IVAR_UNDEF)); rb_define_const(rb_cShape, "SHAPE_IVAR_UNDEF", INT2NUM(SHAPE_IVAR_UNDEF));
rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN)); rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN));
rb_define_const(rb_cShape, "SHAPE_BITS", INT2NUM(SHAPE_BITS)); rb_define_const(rb_cShape, "SHAPE_BITS", INT2NUM(SHAPE_BITS));

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

@ -42,7 +42,7 @@ typedef uint16_t shape_id_t;
# define ROOT_SHAPE_ID 0x0 # define ROOT_SHAPE_ID 0x0
// We use SIZE_POOL_COUNT number of shape IDs for transitions out of different size pools // We use SIZE_POOL_COUNT number of shape IDs for transitions out of different size pools
// The next available shapd ID will be the SPECIAL_CONST_SHAPE_ID // The next available shapd ID will be the SPECIAL_CONST_SHAPE_ID
# define SPECIAL_CONST_SHAPE_ID SIZE_POOL_COUNT # define SPECIAL_CONST_SHAPE_ID (SIZE_POOL_COUNT * 2)
struct rb_shape { struct rb_shape {
struct rb_id_table * edges; // id_table from ID (ivar) to next shape struct rb_id_table * edges; // id_table from ID (ivar) to next shape
@ -63,6 +63,7 @@ enum shape_type {
SHAPE_CAPACITY_CHANGE, SHAPE_CAPACITY_CHANGE,
SHAPE_IVAR_UNDEF, SHAPE_IVAR_UNDEF,
SHAPE_INITIAL_CAPACITY, SHAPE_INITIAL_CAPACITY,
SHAPE_T_OBJECT,
}; };
#if SHAPE_IN_BASIC_FLAGS #if SHAPE_IN_BASIC_FLAGS

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

@ -88,8 +88,8 @@ class TestShapes < Test::Unit::TestCase
class TestObject; end class TestObject; end
def test_new_obj_has_root_shape def test_new_obj_has_t_object_shape
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(TestObject.new)) assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(TestObject.new).parent)
end end
def test_str_has_root_shape def test_str_has_root_shape
@ -114,14 +114,23 @@ class TestShapes < Test::Unit::TestCase
def test_basic_shape_transition def test_basic_shape_transition
obj = Example.new obj = Example.new
refute_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(obj)) shape = RubyVM::Shape.of(obj)
assert_shape_equal(RubyVM::Shape.root_shape.edges[:@a], RubyVM::Shape.of(obj)) refute_equal(RubyVM::Shape.root_shape, shape)
assert_equal :@a, shape.edge_name
assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type
shape = shape.parent
assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
shape = shape.parent
assert_equal(RubyVM::Shape.root_shape.id, shape.id)
assert_equal(obj.instance_variable_get(:@a), 1) assert_equal(obj.instance_variable_get(:@a), 1)
end end
def test_different_objects_make_same_transition def test_different_objects_make_same_transition
obj = Example.new obj = []
obj2 = "" obj2 = ""
obj.instance_variable_set(:@a, 1)
obj2.instance_variable_set(:@a, 1) obj2.instance_variable_set(:@a, 1)
assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
end end

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

@ -1606,6 +1606,7 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu
case SHAPE_CAPACITY_CHANGE: case SHAPE_CAPACITY_CHANGE:
case SHAPE_FROZEN: case SHAPE_FROZEN:
case SHAPE_IVAR_UNDEF: case SHAPE_IVAR_UNDEF:
case SHAPE_T_OBJECT:
iterate_over_shapes_with_callback(rb_shape_get_parent(shape), callback, itr_data); iterate_over_shapes_with_callback(rb_shape_get_parent(shape), callback, itr_data);
return; return;
} }