diff options
author | Jean Boussier <[email protected]> | 2025-06-06 09:26:35 +0200 |
---|---|---|
committer | Jean Boussier <[email protected]> | 2025-06-07 18:30:44 +0200 |
commit | 689ec5114624978f47edcba4f055e62017e4ac36 (patch) | |
tree | 2fffb2d5d1523a8149249c85185cf932ebde6ff1 | |
parent | 42cf301254d4bfec30c07a221f34f3fa85a29f33 (diff) |
Replicate `heap_index` in shape_id flags.
This is preparation to getting rid of `T_OBJECT` transitions.
By first only replicating the information it's easier to ensure
consistency.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/13556
-rw-r--r-- | object.c | 16 | ||||
-rw-r--r-- | shape.c | 19 | ||||
-rw-r--r-- | shape.h | 24 | ||||
-rw-r--r-- | test/ruby/test_shapes.rb | 4 |
4 files changed, 47 insertions, 16 deletions
@@ -339,17 +339,15 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj) shape_id_t dest_shape_id = src_shape_id; shape_id_t initial_shape_id = RBASIC_SHAPE_ID(dest); - if (RSHAPE(initial_shape_id)->heap_index != RSHAPE(src_shape_id)->heap_index || !rb_shape_canonical_p(src_shape_id)) { - RUBY_ASSERT(RSHAPE(initial_shape_id)->type == SHAPE_T_OBJECT); + RUBY_ASSERT(RSHAPE(initial_shape_id)->type == SHAPE_T_OBJECT); - dest_shape_id = rb_shape_rebuild(initial_shape_id, src_shape_id); - if (UNLIKELY(rb_shape_too_complex_p(dest_shape_id))) { - st_table *table = rb_st_init_numtable_with_size(src_num_ivs); - rb_obj_copy_ivs_to_hash_table(obj, table); - rb_obj_init_too_complex(dest, table); + dest_shape_id = rb_shape_rebuild(initial_shape_id, src_shape_id); + if (UNLIKELY(rb_shape_too_complex_p(dest_shape_id))) { + st_table *table = rb_st_init_numtable_with_size(src_num_ivs); + rb_obj_copy_ivs_to_hash_table(obj, table); + rb_obj_init_too_complex(dest, table); - return; - } + return; } VALUE *src_buf = ROBJECT_FIELDS(obj); @@ -1092,7 +1092,10 @@ rb_shape_traverse_from_new_root(shape_id_t initial_shape_id, shape_id_t dest_sha { rb_shape_t *initial_shape = RSHAPE(initial_shape_id); rb_shape_t *dest_shape = RSHAPE(dest_shape_id); - return shape_id(shape_traverse_from_new_root(initial_shape, dest_shape), dest_shape_id); + + // Keep all dest_shape_id flags except for the heap_index. + shape_id_t dest_flags = (dest_shape_id & ~SHAPE_ID_HEAP_INDEX_MASK) | (initial_shape_id & SHAPE_ID_HEAP_INDEX_MASK); + return shape_id(shape_traverse_from_new_root(initial_shape, dest_shape), dest_flags); } // Rebuild a similar shape with the same ivars but starting from @@ -1136,7 +1139,7 @@ rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id) RUBY_ASSERT(!rb_shape_too_complex_p(initial_shape_id)); RUBY_ASSERT(!rb_shape_too_complex_p(dest_shape_id)); - return raw_shape_id(shape_rebuild(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id))); + return shape_id(shape_rebuild(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id)), initial_shape_id); } void @@ -1238,6 +1241,14 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) } } + // All complex shape are in heap_index=0, it's a limitation + if (!rb_shape_too_complex_p(shape_id)) { + uint8_t flags_heap_index = rb_shape_heap_index(shape_id); + if (flags_heap_index != shape->heap_index) { + rb_bug("shape_id heap_index flags mismatch: flags=%u, transition=%u\n", flags_heap_index, shape->heap_index); + } + } + return true; } #endif @@ -1288,6 +1299,7 @@ shape_id_t_to_rb_cShape(shape_id_t shape_id) VALUE obj = rb_struct_new(rb_cShape, INT2NUM(shape_id), + INT2NUM(shape_id & SHAPE_ID_OFFSET_MASK), INT2NUM(shape->parent_id), rb_shape_edge_name(shape), INT2NUM(shape->next_field_index), @@ -1528,7 +1540,7 @@ Init_default_shapes(void) for (int i = 0; sizes[i] > 0; i++) { rb_shape_t *t_object_shape = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID); t_object_shape->type = SHAPE_T_OBJECT; - t_object_shape->heap_index = i; + t_object_shape->heap_index = i + 1; t_object_shape->capacity = (uint32_t)((sizes[i] - offsetof(struct RObject, as.ary)) / sizeof(VALUE)); t_object_shape->edges = rb_managed_id_table_new(256); t_object_shape->ancestor_index = LEAF; @@ -1552,6 +1564,7 @@ Init_shape(void) * :nodoc: */ VALUE rb_cShape = rb_struct_define_under(rb_cRubyVM, "Shape", "id", + "raw_id", "parent_id", "edge_name", "next_field_index", @@ -16,8 +16,18 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_ #define SHAPE_ID_FL_FROZEN (SHAPE_FL_FROZEN << SHAPE_ID_OFFSET_NUM_BITS) #define SHAPE_ID_FL_HAS_OBJECT_ID (SHAPE_FL_HAS_OBJECT_ID << SHAPE_ID_OFFSET_NUM_BITS) #define SHAPE_ID_FL_TOO_COMPLEX (SHAPE_FL_TOO_COMPLEX << SHAPE_ID_OFFSET_NUM_BITS) +#define SHAPE_ID_FL_EMBEDDED (SHAPE_FL_EMBEDDED << SHAPE_ID_OFFSET_NUM_BITS) #define SHAPE_ID_FL_NON_CANONICAL_MASK (SHAPE_FL_NON_CANONICAL_MASK << SHAPE_ID_OFFSET_NUM_BITS) -#define SHAPE_ID_READ_ONLY_MASK (~SHAPE_ID_FL_FROZEN) + +#define SHAPE_ID_HEAP_INDEX_BITS 3 +#define SHAPE_ID_HEAP_INDEX_OFFSET (SHAPE_ID_NUM_BITS - SHAPE_ID_HEAP_INDEX_BITS) +#define SHAPE_ID_HEAP_INDEX_MAX ((1 << SHAPE_ID_HEAP_INDEX_BITS) - 1) +#define SHAPE_ID_HEAP_INDEX_MASK (SHAPE_ID_HEAP_INDEX_MAX << SHAPE_ID_HEAP_INDEX_OFFSET) + +// The interpreter doesn't care about embeded or frozen status when reading ivars. +// So we normalize shape_id by clearing these bits to improve cache hits. +// JITs however might care about it. +#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_EMBEDDED | SHAPE_ID_HEAP_INDEX_MASK)) typedef uint32_t redblack_id_t; @@ -72,6 +82,7 @@ enum shape_flags { SHAPE_FL_FROZEN = 1 << 0, SHAPE_FL_HAS_OBJECT_ID = 1 << 1, SHAPE_FL_TOO_COMPLEX = 1 << 2, + SHAPE_FL_EMBEDDED = 1 << 3, SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID, }; @@ -189,10 +200,19 @@ rb_shape_canonical_p(shape_id_t shape_id) return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK); } +static inline uint8_t +rb_shape_heap_index(shape_id_t shape_id) +{ + return (uint8_t)((shape_id & SHAPE_ID_HEAP_INDEX_MASK) >> SHAPE_ID_HEAP_INDEX_OFFSET); +} + static inline shape_id_t rb_shape_root(size_t heap_id) { - return (shape_id_t)(heap_id + FIRST_T_OBJECT_SHAPE_ID); + shape_id_t heap_index = (shape_id_t)heap_id; + + shape_id_t shape_id = (heap_index + FIRST_T_OBJECT_SHAPE_ID); + return shape_id | ((heap_index + 1) << SHAPE_ID_HEAP_INDEX_OFFSET) | SHAPE_ID_FL_EMBEDDED; } static inline bool diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index 7d9e28ba7a..e62b8c33e7 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -976,7 +976,7 @@ class TestShapes < Test::Unit::TestCase example.add_foo # makes a transition add_foo_shape = RubyVM::Shape.of(example) assert_equal([:@foo], example.instance_variables) - assert_equal(initial_shape.id, add_foo_shape.parent.id) + assert_equal(initial_shape.raw_id, add_foo_shape.parent.raw_id) assert_equal(1, add_foo_shape.next_field_index) example.remove_foo # makes a transition @@ -987,7 +987,7 @@ class TestShapes < Test::Unit::TestCase example.add_bar # makes a transition bar_shape = RubyVM::Shape.of(example) assert_equal([:@bar], example.instance_variables) - assert_equal(initial_shape.id, bar_shape.parent_id) + assert_equal(initial_shape.raw_id, bar_shape.parent_id) assert_equal(1, bar_shape.next_field_index) end |