summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean Boussier <[email protected]>2025-06-13 15:22:28 +0200
committerJean Boussier <[email protected]>2025-06-13 23:50:30 +0200
commitb51078f82ee35d532dfd5b6981733f757d410d79 (patch)
treeb028c5779563fb86ba83ef1c1905d51496da0fc2
parentf2d7c6afee45cd7db86fbe2508556f88518a3bdb (diff)
Enforce consistency between shape_id and FL_EXIVAR
The FL_EXIVAR is a bit redundant with the shape_id. Now that the `shape_id` is embedded in all objects on all archs, we can cheaply check if an object has any fields with a simple bitmask.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/13612
-rw-r--r--gc.c1
-rw-r--r--shape.c7
-rw-r--r--shape.h31
-rw-r--r--variable.c40
4 files changed, 60 insertions, 19 deletions
diff --git a/gc.c b/gc.c
index aefe8a116b..6941541a86 100644
--- a/gc.c
+++ b/gc.c
@@ -2072,7 +2072,6 @@ rb_gc_obj_free_vm_weak_references(VALUE obj)
if (FL_TEST_RAW(obj, FL_EXIVAR)) {
rb_free_generic_ivar((VALUE)obj);
- FL_UNSET_RAW(obj, FL_EXIVAR);
}
switch (BUILTIN_TYPE(obj)) {
diff --git a/shape.c b/shape.c
index 06dcb8d610..44f6cf7193 100644
--- a/shape.c
+++ b/shape.c
@@ -1266,6 +1266,13 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
}
}
+ if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ RUBY_ASSERT(rb_obj_has_exivar(obj));
+ }
+ else {
+ RUBY_ASSERT(!rb_obj_has_exivar(obj));
+ }
+
return true;
}
#endif
diff --git a/shape.h b/shape.h
index 35d28b41ed..92a3fb1116 100644
--- a/shape.h
+++ b/shape.h
@@ -136,8 +136,6 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
{
RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj));
RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_class_fields));
- RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
-
#if RBASIC_SHAPE_ID_FIELD
RBASIC(obj)->shape_id = (VALUE)shape_id;
#else
@@ -145,6 +143,7 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
#endif
+ RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
}
static inline rb_shape_t *
@@ -343,6 +342,34 @@ rb_shape_obj_has_ivars(VALUE obj)
return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj));
}
+static inline bool
+rb_shape_has_fields(shape_id_t shape_id)
+{
+ return shape_id & (SHAPE_ID_OFFSET_MASK | SHAPE_ID_FL_TOO_COMPLEX);
+}
+
+static inline bool
+rb_shape_obj_has_fields(VALUE obj)
+{
+ return rb_shape_has_fields(RBASIC_SHAPE_ID(obj));
+}
+
+static inline bool
+rb_obj_has_exivar(VALUE obj)
+{
+ switch (TYPE(obj)) {
+ case T_NONE:
+ case T_OBJECT:
+ case T_CLASS:
+ case T_MODULE:
+ case T_IMEMO:
+ return false;
+ default:
+ break;
+ }
+ return rb_shape_obj_has_fields(obj);
+}
+
// For ext/objspace
RUBY_SYMBOL_EXPORT_BEGIN
typedef void each_shape_callback(shape_id_t shape_id, void *data);
diff --git a/variable.c b/variable.c
index 93ae6bb8b2..0dd0a500bb 100644
--- a/variable.c
+++ b/variable.c
@@ -1270,6 +1270,7 @@ rb_free_generic_ivar(VALUE obj)
xfree(fields_tbl);
}
}
+ FL_UNSET_RAW(obj, FL_EXIVAR);
}
size_t
@@ -1542,23 +1543,30 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
RUBY_ASSERT(removed_shape_id != INVALID_SHAPE_ID);
- attr_index_t new_fields_count = RSHAPE_LEN(next_shape_id);
-
attr_index_t removed_index = RSHAPE_INDEX(removed_shape_id);
val = fields[removed_index];
- size_t trailing_fields = new_fields_count - removed_index;
- MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields);
-
- if (RB_TYPE_P(obj, T_OBJECT) &&
- !RB_FL_TEST_RAW(obj, ROBJECT_EMBED) &&
- rb_obj_embedded_size(new_fields_count) <= rb_gc_obj_slot_size(obj)) {
- // Re-embed objects when instances become small enough
- // This is necessary because YJIT assumes that objects with the same shape
- // have the same embeddedness for efficiency (avoid extra checks)
- RB_FL_SET_RAW(obj, ROBJECT_EMBED);
- MEMCPY(ROBJECT_FIELDS(obj), fields, VALUE, new_fields_count);
- xfree(fields);
+ attr_index_t new_fields_count = RSHAPE_LEN(next_shape_id);
+ if (new_fields_count) {
+ size_t trailing_fields = new_fields_count - removed_index;
+
+ MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields);
+
+ if (RB_TYPE_P(obj, T_OBJECT) &&
+ !RB_FL_TEST_RAW(obj, ROBJECT_EMBED) &&
+ rb_obj_embedded_size(new_fields_count) <= rb_gc_obj_slot_size(obj)) {
+ // Re-embed objects when instances become small enough
+ // This is necessary because YJIT assumes that objects with the same shape
+ // have the same embeddedness for efficiency (avoid extra checks)
+ RB_FL_SET_RAW(obj, ROBJECT_EMBED);
+ MEMCPY(ROBJECT_FIELDS(obj), fields, VALUE, new_fields_count);
+ xfree(fields);
+ }
+ }
+ else {
+ if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ rb_free_generic_ivar(obj);
+ }
}
rb_obj_set_shape_id(obj, next_shape_id);
@@ -1881,8 +1889,8 @@ generic_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *data)
static shape_id_t
generic_ivar_set_transition_too_complex(VALUE obj, void *_data)
{
- shape_id_t new_shape_id = rb_evict_fields_to_hash(obj);
FL_SET_RAW(obj, FL_EXIVAR);
+ shape_id_t new_shape_id = rb_evict_fields_to_hash(obj);
return new_shape_id;
}
@@ -2407,9 +2415,9 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
clear:
if (FL_TEST(dest, FL_EXIVAR)) {
- RBASIC_SET_SHAPE_ID(dest, ROOT_SHAPE_ID);
rb_free_generic_ivar(dest);
FL_UNSET(dest, FL_EXIVAR);
+ RBASIC_SET_SHAPE_ID(dest, ROOT_SHAPE_ID);
}
}