summaryrefslogtreecommitdiff
path: root/shape.c
diff options
context:
space:
mode:
authorJean Boussier <jean.boussier@gmail.com>2025-06-07 10:55:49 +0200
committerJean Boussier <jean.boussier@gmail.com>2025-06-07 18:30:44 +0200
commit8c4e368dcf9e50814ed8a65a824f22035bbe6770 (patch)
treef202afa0ef0f9370fb73e32ae2b0ef13d338dc89 /shape.c
parent54edc930f9f0a658da45cfcef46648d1b6f82467 (diff)
shape.c: ensure heap_index is consistent for complex shapes
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/13556
Diffstat (limited to 'shape.c')
-rw-r--r--shape.c146
1 files changed, 96 insertions, 50 deletions
diff --git a/shape.c b/shape.c
index c8ca907d49..2c2a8daf50 100644
--- a/shape.c
+++ b/shape.c
@@ -723,15 +723,67 @@ remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape)
}
}
+
+static shape_id_t
+shape_transition_object_id(shape_id_t original_shape_id)
+{
+ RUBY_ASSERT(!rb_shape_has_object_id(original_shape_id));
+
+ bool dont_care;
+ rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
+
+ RUBY_ASSERT(shape);
+
+ return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
+}
+
+shape_id_t
+rb_shape_transition_object_id(VALUE obj)
+{
+ return shape_transition_object_id(RBASIC_SHAPE_ID(obj));
+}
+
+shape_id_t
+rb_shape_object_id(shape_id_t original_shape_id)
+{
+ RUBY_ASSERT(rb_shape_has_object_id(original_shape_id));
+
+ rb_shape_t *shape = RSHAPE(original_shape_id);
+ while (shape->type != SHAPE_OBJ_ID) {
+ if (UNLIKELY(shape->parent_id == INVALID_SHAPE_ID)) {
+ rb_bug("Missing object_id in shape tree");
+ }
+ shape = RSHAPE(shape->parent_id);
+ }
+
+ return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
+}
+
static inline shape_id_t
transition_complex(shape_id_t shape_id)
{
- if (rb_shape_has_object_id(shape_id)) {
- return ROOT_TOO_COMPLEX_WITH_OBJ_ID | (shape_id & SHAPE_ID_FLAGS_MASK);
+ uint8_t heap_index = RSHAPE(shape_id)->heap_index;
+ shape_id_t next_shape_id;
+
+ if (heap_index) {
+ next_shape_id = rb_shape_root(heap_index - 1) | SHAPE_ID_FL_TOO_COMPLEX;
+ if (rb_shape_has_object_id(shape_id)) {
+ next_shape_id = shape_transition_object_id(next_shape_id);
+ }
+ }
+ else {
+ if (rb_shape_has_object_id(shape_id)) {
+ next_shape_id = ROOT_TOO_COMPLEX_WITH_OBJ_ID | (shape_id & SHAPE_ID_FLAGS_MASK);
+ }
+ else {
+ next_shape_id = ROOT_TOO_COMPLEX_SHAPE_ID | (shape_id & SHAPE_ID_FLAGS_MASK);
+ }
}
- return ROOT_TOO_COMPLEX_SHAPE_ID | (shape_id & SHAPE_ID_FLAGS_MASK);
-}
+ RUBY_ASSERT(rb_shape_has_object_id(shape_id) == rb_shape_has_object_id(next_shape_id));
+
+ return next_shape_id;
+}
shape_id_t
rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id)
@@ -754,7 +806,9 @@ rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id)
else if (removed_shape) {
// We found the shape to remove, but couldn't create a new variation.
// We must transition to TOO_COMPLEX.
- return transition_complex(original_shape_id);
+ shape_id_t next_shape_id = transition_complex(original_shape_id);
+ RUBY_ASSERT(rb_shape_has_object_id(next_shape_id) == rb_shape_has_object_id(original_shape_id));
+ return next_shape_id;
}
return original_shape_id;
}
@@ -774,41 +828,6 @@ rb_shape_transition_complex(VALUE obj)
return transition_complex(RBASIC_SHAPE_ID(obj));
}
-shape_id_t
-rb_shape_transition_object_id(VALUE obj)
-{
- shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
-
- RUBY_ASSERT(!rb_shape_has_object_id(original_shape_id));
-
- rb_shape_t *shape = NULL;
- if (!rb_shape_too_complex_p(original_shape_id)) {
- bool dont_care;
- shape = get_next_shape_internal(RSHAPE(original_shape_id), ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
- }
-
- if (!shape) {
- shape = RSHAPE(ROOT_TOO_COMPLEX_WITH_OBJ_ID);
- }
- return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
-}
-
-shape_id_t
-rb_shape_object_id(shape_id_t original_shape_id)
-{
- RUBY_ASSERT(rb_shape_has_object_id(original_shape_id));
-
- rb_shape_t *shape = RSHAPE(original_shape_id);
- while (shape->type != SHAPE_OBJ_ID) {
- if (UNLIKELY(shape->parent_id == INVALID_SHAPE_ID)) {
- rb_bug("Missing object_id in shape tree");
- }
- shape = RSHAPE(shape->parent_id);
- }
-
- return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
-}
-
/*
* This function is used for assertions where we don't want to increment
* max_iv_count
@@ -918,7 +937,13 @@ rb_shape_transition_add_ivar(VALUE obj, ID id)
shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
RUBY_ASSERT(!shape_frozen_p(original_shape_id));
- return shape_id(shape_get_next(RSHAPE(original_shape_id), obj, id, true), original_shape_id);
+ rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), obj, id, true);
+ if (next_shape) {
+ return shape_id(next_shape, original_shape_id);
+ }
+ else {
+ return transition_complex(original_shape_id);
+ }
}
shape_id_t
@@ -927,7 +952,13 @@ rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id)
shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj);
RUBY_ASSERT(!shape_frozen_p(original_shape_id));
- return shape_id(shape_get_next(RSHAPE(original_shape_id), obj, id, false), original_shape_id);
+ rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), obj, id, false);
+ if (next_shape) {
+ return shape_id(next_shape, original_shape_id);
+ }
+ else {
+ return transition_complex(original_shape_id);
+ }
}
// Same as rb_shape_get_iv_index, but uses a provided valid shape id and index
@@ -1139,7 +1170,13 @@ 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 shape_id(shape_rebuild(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id)), initial_shape_id);
+ rb_shape_t *next_shape = shape_rebuild(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id));
+ if (next_shape) {
+ return shape_id(next_shape, initial_shape_id);
+ }
+ else {
+ return transition_complex(initial_shape_id | (dest_shape_id & SHAPE_ID_FL_HAS_OBJECT_ID));
+ }
}
void
@@ -1217,6 +1254,10 @@ rb_shape_memsize(shape_id_t shape_id)
bool
rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
{
+ if (shape_id == INVALID_SHAPE_ID) {
+ rb_bug("Can't set INVALID_SHAPE_ID on an object");
+ }
+
rb_shape_t *shape = RSHAPE(shape_id);
bool has_object_id = false;
@@ -1241,12 +1282,9 @@ 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);
- }
+ 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;
@@ -1537,7 +1575,8 @@ Init_default_shapes(void)
// Make shapes for T_OBJECT
size_t *sizes = rb_gc_heap_sizes();
- for (int i = 0; sizes[i] > 0; i++) {
+ int i;
+ for (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 + 1;
@@ -1546,6 +1585,13 @@ Init_default_shapes(void)
t_object_shape->ancestor_index = LEAF;
RUBY_ASSERT(t_object_shape == RSHAPE(rb_shape_root(i)));
}
+
+ // Prebuild all ROOT + OBJ_ID shapes so that even when we run out of shape we can always transtion to
+ // COMPLEX + OBJ_ID.
+ bool dont_care;
+ for (i = 0; sizes[i] > 0; i++) {
+ get_next_shape_internal(RSHAPE(rb_shape_root(i)), ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
+ }
}
void