8000 Get rid of SHAPE_T_OBJECT by casperisfine · Pull Request #13556 · ruby/ruby · GitHub
[go: up one dir, main page]

Skip to content

Get rid of SHAPE_T_OBJECT #13556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 7, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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.
  • Loading branch information
byroot committed Jun 7, 2025
commit fc9f38c9818738aff7ccb7aafbefba477a2bc266
16 changes: 7 additions & 9 deletions object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 16 additions & 3 deletions shape.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand All @@ -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",
Expand Down
24 changes: 22 additions & 2 deletions shape.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/ruby/test_shapes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ def test_iv_index
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
Expand All @@ -987,7 +987,7 @@ def test_iv_index
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

Expand Down
0