diff options
author | Jean Boussier <[email protected]> | 2025-05-27 15:53:45 +0200 |
---|---|---|
committer | Jean Boussier <[email protected]> | 2025-06-04 07:59:20 +0200 |
commit | 625d6a9cbb0ed617b28115e4e3cb063e52481635 () | |
tree | 610cfd170759de7e0b65369dfe4e3421a2f699bf | |
parent | 6b7e3395a4ab69f5eaefb983243a8e4cad7f8abf (diff) |
Get rid of frozen shapes.
Instead `shape_id_t` higher bits contain flags, and the first one tells whether the shape is frozen. This has multiple benefits: - Can check if a shape is frozen with a single bit check instead of dereferencing a pointer. - Guarantees it is always possible to transition to frozen. - This allow reclaiming `FL_FREEZE` (not done yet). The downside is you have to be careful to preserve these flags when transitioning.
Notes: Merged: https://.com/ruby/ruby/pull/13289
-rw-r--r-- | ext/objspace/objspace_dump.c | 3 | ||||
-rw-r--r-- | internal/class.h | 1 | ||||
-rw-r--r-- | object.c | 16 | ||||
-rw-r--r-- | shape.c | 175 | ||||
-rw-r--r-- | shape.h | 30 | ||||
-rw-r--r-- | test/ruby/test_shapes.rb | 11 | ||||
-rw-r--r-- | variable.c | 8 |
7 files changed, 95 insertions, 149 deletions
@@ -818,9 +818,6 @@ shape_id_i(shape_id_t shape_id, void *data) dump_append_id(dc, shape->edge_name); break; - case SHAPE_FROZEN: - dump_append(dc, ", \"shape_type\":\"FROZEN\""); - break; case SHAPE_T_OBJECT: dump_append(dc, ", \"shape_type\":\"T_OBJECT\""); break; @@ -297,7 +297,6 @@ static inline void RCLASS_WRITE_CLASSPATH(VALUE klass, VALUE classpath, bool per #define RCLASS_PRIME_CLASSEXT_WRITABLE FL_USER2 #define RCLASS_IS_INITIALIZED FL_USER3 // 3 is RMODULE_IS_REFINEMENT for RMODULE -// 4-19: SHAPE_FLAG_MASK /* class.c */ rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_namespace_t *ns); @@ -496,12 +496,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) if (RB_OBJ_FROZEN(obj)) { shape_id_t next_shape_id = rb_shape_transition_frozen(clone); - if (!rb_shape_obj_too_complex_p(clone) && rb_shape_too_complex_p(next_shape_id)) { - rb_evict_ivars_to_hash(clone); - } - else { - rb_obj_set_shape_id(clone, next_shape_id); - } } break; case Qtrue: { @@ -518,14 +513,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS); RBASIC(clone)->flags |= FL_FREEZE; shape_id_t next_shape_id = rb_shape_transition_frozen(clone); - // If we're out of shapes, but we want to freeze, then we need to - // evacuate this clone to a hash - if (!rb_shape_obj_too_complex_p(clone) && rb_shape_too_complex_p(next_shape_id)) { - rb_evict_ivars_to_hash(clone); - } - else { - rb_obj_set_shape_id(clone, next_shape_id); - } break; } case Qfalse: { @@ -20,17 +20,7 @@ #define SHAPE_DEBUG (VM_CHECK_MODE > 0) #endif -#if SIZEOF_SHAPE_T == 4 -#if RUBY_DEBUG -#define SHAPE_BUFFER_SIZE 0x8000 -#else -#define SHAPE_BUFFER_SIZE 0x80000 -#endif -#else -#define SHAPE_BUFFER_SIZE 0x8000 -#endif - -#define ROOT_TOO_COMPLEX_SHAPE_ID 0x2 #define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32) @@ -53,14 +43,6 @@ ID ruby_internal_object_id; // extern #define BLACK 0x0 #define RED 0x1 -enum shape_flags { - SHAPE_FL_FROZEN = 1 << 0, - SHAPE_FL_HAS_OBJECT_ID = 1 << 1, - SHAPE_FL_TOO_COMPLEX = 1 << 2, - - SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID, -}; - static redblack_node_t * redblack_left(redblack_node_t *node) { @@ -373,7 +355,7 @@ static const rb_data_type_t shape_tree_type = { */ static inline shape_id_t -rb_shape_id(rb_shape_t *shape) { if (shape == NULL) { return INVALID_SHAPE_ID; @@ -381,6 +363,24 @@ rb_shape_id(rb_shape_t *shape) return (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list); } static inline bool shape_too_complex_p(rb_shape_t *shape) { @@ -402,9 +402,10 @@ rb_shape_each_shape_id(each_shape_callback callback, void *data) RUBY_FUNC_EXPORTED rb_shape_t * rb_shape_lookup(shape_id_t shape_id) { - RUBY_ASSERT(shape_id != INVALID_SHAPE_ID); - return &GET_SHAPE_TREE()->shape_list[shape_id]; } RUBY_FUNC_EXPORTED shape_id_t @@ -466,7 +467,7 @@ rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id) static rb_shape_t * rb_shape_alloc(ID edge_name, rb_shape_t *parent, enum shape_type type) { - rb_shape_t *shape = rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent)); shape->type = (uint8_t)type; shape->flags = parent->flags; shape->heap_index = parent->heap_index; @@ -530,10 +531,6 @@ rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type) redblack_cache_ancestors(new_shape); } break; - case SHAPE_FROZEN: - new_shape->next_field_index = shape->next_field_index; - new_shape->flags |= SHAPE_FL_FROZEN; - break; case SHAPE_OBJ_TOO_COMPLEX: case SHAPE_ROOT: case SHAPE_T_OBJECT: @@ -626,8 +623,8 @@ retry: static rb_shape_t * get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed) { - // There should never be outgoing edges from "too complex", except for SHAPE_FROZEN and SHAPE_OBJ_ID - RUBY_ASSERT(!shape_too_complex_p(shape) || shape_type == SHAPE_FROZEN || shape_type == SHAPE_OBJ_ID); if (rb_multi_ractor_p()) { return get_next_shape_internal_atomic(shape, id, shape_type, variation_created, new_variations_allowed); @@ -692,12 +689,6 @@ get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bo return res; } -static inline bool -shape_frozen_p(rb_shape_t *shape) -{ - return SHAPE_FL_FROZEN & shape->flags; -} - static rb_shape_t * remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape) { @@ -749,12 +740,13 @@ rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id) rb_shape_t *shape = RSHAPE(shape_id); RUBY_ASSERT(!shape_too_complex_p(shape)); rb_shape_t *removed_shape = NULL; rb_shape_t *new_shape = remove_shape_recursive(shape, id, &removed_shape); if (new_shape) { - *removed_shape_id = rb_shape_id(removed_shape); - return rb_shape_id(new_shape); } return shape_id; } @@ -765,22 +757,7 @@ rb_shape_transition_frozen(VALUE obj) RUBY_ASSERT(RB_OBJ_FROZEN(obj)); shape_id_t shape_id = rb_obj_shape_id(obj); - if (shape_id == ROOT_SHAPE_ID) { - return SPECIAL_CONST_SHAPE_ID; - } - - rb_shape_t *shape = RSHAPE(shape_id); - RUBY_ASSERT(shape); - - if (shape_frozen_p(shape)) { - return shape_id; - } - - bool dont_care; - rb_shape_t *next_shape = get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true); - - RUBY_ASSERT(next_shape); - return rb_shape_id(next_shape); } static rb_shape_t * @@ -788,11 +765,6 @@ shape_transition_too_complex(rb_shape_t *original_shape) { rb_shape_t *next_shape = RSHAPE(ROOT_TOO_COMPLEX_SHAPE_ID); - if (original_shape->flags & SHAPE_FL_FROZEN) { - bool dont_care; - next_shape = get_next_shape_internal(next_shape, id_frozen, SHAPE_FROZEN, &dont_care, false); - } - if (original_shape->flags & SHAPE_FL_HAS_OBJECT_ID) { bool dont_care; next_shape = get_next_shape_internal(next_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, false); @@ -804,8 +776,8 @@ shape_transition_too_complex(rb_shape_t *original_shape) shape_id_t rb_shape_transition_complex(VALUE obj) { - rb_shape_t *original_shape = obj_shape(obj); - return rb_shape_id(shape_transition_too_complex(original_shape)); } static inline bool @@ -823,7 +795,9 @@ rb_shape_has_object_id(shape_id_t shape_id) shape_id_t rb_shape_transition_object_id(VALUE obj) { - rb_shape_t* shape = obj_shape(obj); RUBY_ASSERT(shape); if (shape->flags & SHAPE_FL_HAS_OBJECT_ID) { @@ -836,7 +810,7 @@ rb_shape_transition_object_id(VALUE obj) shape = get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true); } RUBY_ASSERT(shape); - return rb_shape_id(shape); } /* @@ -856,7 +830,7 @@ rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id) { rb_shape_t *shape = RSHAPE(shape_id); rb_shape_t *next_shape = shape_get_next_iv_shape(shape, id); - return rb_shape_id(next_shape); } static bool @@ -877,7 +851,6 @@ shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) return false; case SHAPE_OBJ_TOO_COMPLEX: case SHAPE_OBJ_ID: - case SHAPE_FROZEN: rb_bug("Ivar should not exist on transition"); } } @@ -945,13 +918,17 @@ shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings) shape_id_t rb_shape_transition_add_ivar(VALUE obj, ID id) { - return rb_shape_id(shape_get_next(obj_shape(obj), obj, id, true)); } shape_id_t rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id) { - return rb_shape_id(shape_get_next(obj_shape(obj), obj, id, false)); } // Same as rb_shape_get_iv_index, but uses a provided valid shape id and index @@ -987,13 +964,13 @@ rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, if (shape_hint == shape) { // We've found a common ancestor so use the index hint *value = index_hint; - *shape_id_hint = rb_shape_id(shape); return true; } if (shape->edge_name == id) { // We found the matching id before a common ancestor *value = shape->next_field_index - 1; - *shape_id_hint = rb_shape_id(shape); return true; } @@ -1080,7 +1057,6 @@ shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shape) switch ((enum shape_type)dest_shape->type) { case SHAPE_IVAR: case SHAPE_OBJ_ID: - case SHAPE_FROZEN: if (!next_shape->edges) { return NULL; } @@ -1120,17 +1096,17 @@ 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 rb_shape_id(shape_traverse_from_new_root(initial_shape, dest_shape)); } // Rebuild a similar shape with the same ivars but starting from // a different SHAPE_T_OBJECT, and don't cary over non-canonical transitions -// such as SHAPE_FROZEN or SHAPE_OBJ_ID. rb_shape_t * rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape) { - RUBY_ASSERT(rb_shape_id(initial_shape) != ROOT_TOO_COMPLEX_SHAPE_ID); - RUBY_ASSERT(rb_shape_id(dest_shape) != ROOT_TOO_COMPLEX_SHAPE_ID); rb_shape_t *midway_shape; @@ -1138,7 +1114,7 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape) if (dest_shape->type != initial_shape->type) { midway_shape = rb_shape_rebuild_shape(initial_shape, RSHAPE(dest_shape->parent_id)); - if (UNLIKELY(rb_shape_id(midway_shape) == ROOT_TOO_COMPLEX_SHAPE_ID)) { return midway_shape; } } @@ -1152,7 +1128,6 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape) break; case SHAPE_OBJ_ID: case SHAPE_ROOT: - case SHAPE_FROZEN: case SHAPE_T_OBJECT: break; case SHAPE_OBJ_TOO_COMPLEX: @@ -1166,7 +1141,7 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape) shape_id_t rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id) { - return rb_shape_id(rb_shape_rebuild_shape(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id))); } void @@ -1266,8 +1241,7 @@ static VALUE shape_frozen(VALUE self) { shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id"))); - rb_shape_t *shape = RSHAPE(shape_id); - return RBOOL(shape_frozen_p(shape)); } static VALUE @@ -1290,12 +1264,13 @@ parse_key(ID key) static VALUE rb_shape_edge_name(rb_shape_t *shape); static VALUE -rb_shape_t_to_rb_cShape(rb_shape_t *shape) { VALUE rb_cShape = rb_const_get(rb_cRubyVM, rb_intern("Shape")); VALUE obj = rb_struct_new(rb_cShape, - INT2NUM(rb_shape_id(shape)), INT2NUM(shape->parent_id), rb_shape_edge_name(shape), INT2NUM(shape->next_field_index), @@ -1309,7 +1284,7 @@ rb_shape_t_to_rb_cShape(rb_shape_t *shape) static enum rb_id_table_iterator_result rb_edges_to_hash(ID key, VALUE value, void *ref) { - rb_hash_aset(*(VALUE *)ref, parse_key(key), rb_shape_t_to_rb_cShape((rb_shape_t *)value)); return ID_TABLE_CONTINUE; } @@ -1360,7 +1335,7 @@ rb_shape_parent(VALUE self) rb_shape_t *shape; shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id")))); if (shape->parent_id != INVALID_SHAPE_ID) { - return rb_shape_t_to_rb_cShape(RSHAPE(shape->parent_id)); } else { return Qnil; @@ -1370,13 +1345,13 @@ rb_shape_parent(VALUE self) static VALUE rb_shape_debug_shape(VALUE self, VALUE obj) { - return rb_shape_t_to_rb_cShape(obj_shape(obj)); } static VALUE rb_shape_root_shape(VALUE self) { - return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape()); } static VALUE @@ -1420,10 +1395,8 @@ shape_to_h(rb_shape_t *shape) { VALUE rb_shape = rb_hash_new(); - rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(rb_shape_id(shape))); - VALUE shape_edges = shape->edges; - rb_hash_aset(rb_shape, ID2SYM(rb_intern("edges")), edges(shape_edges)); - RB_GC_GUARD(shape_edges); if (shape == rb_shape_get_root_shape()) { rb_hash_aset(rb_shape, ID2SYM(rb_intern("parent_id")), INT2NUM(ROOT_SHAPE_ID)); @@ -1449,7 +1422,7 @@ rb_shape_find_by_id(VALUE mod, VALUE id) if (shape_id >= GET_SHAPE_TREE()->next_shape_id) { rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id); } - return rb_shape_t_to_rb_cShape(RSHAPE(shape_id)); } #endif @@ -1511,24 +1484,14 @@ Init_default_shapes(void) root->type = SHAPE_ROOT; root->heap_index = 0; GET_SHAPE_TREE()->root_shape = root; - RUBY_ASSERT(rb_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID); bool dont_care; - // Special const shape -#if RUBY_DEBUG - rb_shape_t *special_const_shape = -#endif - get_next_shape_internal(root, id_frozen, SHAPE_FROZEN, &dont_care, true); - RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID); - RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1)); - RUBY_ASSERT(shape_frozen_p(special_const_shape)); - rb_shape_t *too_complex_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID); too_complex_shape->type = SHAPE_OBJ_TOO_COMPLEX; too_complex_shape->flags |= SHAPE_FL_TOO_COMPLEX; too_complex_shape->heap_index = 0; - RUBY_ASSERT(ROOT_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1)); - RUBY_ASSERT(rb_shape_id(too_complex_shape) == ROOT_TOO_COMPLEX_SHAPE_ID); // Make shapes for T_OBJECT size_t *sizes = rb_gc_heap_sizes(); @@ -1539,17 +1502,12 @@ Init_default_shapes(void) 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; - RUBY_ASSERT(rb_shape_id(t_object_shape) == rb_shape_root(i)); } // Prebuild TOO_COMPLEX variations so that they already exist if we ever need them after we // ran out of shapes. - rb_shape_t *shape; - shape = get_next_shape_internal(too_complex_shape, id_frozen, SHAPE_FROZEN, &dont_care, true); - get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true); - - shape = get_next_shape_internal(too_complex_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true); - get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true); } void @@ -1584,7 +1542,6 @@ Init_shape(void) rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT)); rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR)); rb_define_const(rb_cShape, "SHAPE_T_OBJECT", INT2NUM(SHAPE_T_OBJECT)); - rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN)); rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS)); rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT)); rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID)); @@ -3,17 +3,23 @@ #include "internal/gc.h" -#define SIZEOF_SHAPE_T 4 typedef uint16_t attr_index_t; typedef uint32_t shape_id_t; #define SHAPE_ID_NUM_BITS 32 typedef uint32_t redblack_id_t; #define SHAPE_MAX_FIELDS (attr_index_t)(-1) - #define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS) -#define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * 8) - SHAPE_ID_NUM_BITS) #define SHAPE_MAX_VARIATIONS 8 @@ -21,9 +27,9 @@ typedef uint32_t redblack_id_t; #define ATTR_INDEX_NOT_SET ((attr_index_t)-1) #define ROOT_SHAPE_ID 0x0 -#define SPECIAL_CONST_SHAPE_ID 0x1 -// ROOT_TOO_COMPLEX_SHAPE_ID 0x2 -#define FIRST_T_OBJECT_SHAPE_ID 0x3 extern ID ruby_internal_object_id; @@ -54,11 +60,18 @@ enum shape_type { SHAPE_ROOT, SHAPE_IVAR, SHAPE_OBJ_ID, - SHAPE_FROZEN, SHAPE_T_OBJECT, SHAPE_OBJ_TOO_COMPLEX, }; typedef struct { /* object shapes */ rb_shape_t *shape_list; @@ -105,7 +118,6 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) #if RBASIC_SHAPE_ID_FIELD RBASIC(obj)->shape_id = (VALUE)shape_id; #else - // Ractors are occupying the upper 32 bits of flags, but only in debug mode // Object shapes are occupying top bits RBASIC(obj)->flags &= SHAPE_FLAG_MASK; RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT); @@ -141,7 +153,7 @@ void rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, static inline bool rb_shape_canonical_p(shape_id_t shape_id) { - return !RSHAPE(shape_id)->flags; } static inline shape_id_t @@ -1055,11 +1055,12 @@ class TestShapes < Test::Unit::TestCase def test_freezing_and_duplicating_object obj = Object.new.freeze obj2 = obj.dup refute_predicate(obj2, :frozen?) - # dup'd objects shouldn't be frozen, and the shape should be the - # parent shape of the copied object - assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id) end def test_freezing_and_duplicating_object_with_ivars @@ -1076,6 +1077,7 @@ class TestShapes < Test::Unit::TestCase str.freeze str2 = str.dup refute_predicate(str2, :frozen?) refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id) assert_equal(str2.instance_variable_get(:@a), 1) end @@ -1092,8 +1094,7 @@ class TestShapes < Test::Unit::TestCase obj2 = obj.clone(freeze: true) assert_predicate(obj2, :frozen?) refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) - assert_equal(RubyVM::Shape::SHAPE_FROZEN, RubyVM::Shape.of(obj2).type) - assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2).parent) end def test_freezing_and_cloning_object_with_ivars @@ -2057,12 +2057,6 @@ void rb_obj_freeze_inline(VALUE x) } shape_id_t next_shape_id = rb_shape_transition_frozen(x); - - // If we're transitioning from "not complex" to "too complex" - // then evict ivars. This can happen if we run out of shapes - if (rb_shape_too_complex_p(next_shape_id) && !rb_shape_obj_too_complex_p(x)) { - rb_evict_fields_to_hash(x); - } rb_obj_set_shape_id(x, next_shape_id); if (RBASIC_CLASS(x)) { @@ -2227,8 +2221,6 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu } } return false; - case SHAPE_FROZEN: - return iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data); case SHAPE_OBJ_TOO_COMPLEX: default: rb_bug("Unreachable"); |