diff options
author | Jean Boussier <[email protected]> | 2025-06-13 15:22:28 +0200 |
---|---|---|
committer | Jean Boussier <[email protected]> | 2025-06-13 23:50:30 +0200 |
commit | b51078f82ee35d532dfd5b6981733f757d410d79 (patch) | |
tree | b028c5779563fb86ba83ef1c1905d51496da0fc2 | |
parent | f2d7c6afee45cd7db86fbe2508556f88518a3bdb (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.c | 1 | ||||
-rw-r--r-- | shape.c | 7 | ||||
-rw-r--r-- | shape.h | 31 | ||||
-rw-r--r-- | variable.c | 40 |
4 files changed, 60 insertions, 19 deletions
@@ -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)) { @@ -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 @@ -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); } } |