From 63d9f090b5d9461cf0b9446e0039d9c56156b826 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 May 2026 15:09:34 -0700 Subject: [PATCH 1/2] Reserve 2 bits for expressing object layout (#17139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reserve 2 bits for expressing object layout We would like to make instance variable reads in the JIT compiler faster (as well as simplify the JIT implementation). Currently, in order to read an instance variable, we have to: 1. Test for heap object 2. Load object to a 64 bit register 3. Mask the object header 4. Bit test against the masked header 5. JNE 6. Load field We would like to: 1. Test for heap object 2. Load object shape to a 32 bit register 3. Bit test against the shape 4. JNE 5. Load field The way we fetch instance variables is not consistent across objects. In order to realize our goal, we need to encode object layout inside the shape. If we encode object layout inside the shape, then the shape itself will guarantee that the access pattern generated by the JIT compiler is correct. We should encode the following load patterns into the shape tag bits. This way we can share shapes on transitions, but be able to differentiate the access patterns for the JIT compiler. In other words, two objects can have an `@a -> @b -> @c` transition and share the same shape, but the tag bits can differentiate the access pattern so that the JIT compiler can be confident that the machine code is correct. Here are the patterns: 1. Embedded/Extended T_OBJECT Instance Variables Objects with direct references to instance variables or via malloc buffer 2. Objects with fields_objects fields These are Data and TypedData objects. They have an associated axillary imemo/fields object that stores the instance variables. The access pattern is `object[2] + 2`. The fields object is the 3rd field, and the instance variables start at +2 inside the fields object. The fields object itself is a Ruby object, so it contains the usual header bits + class headers. 3. Non Boxable Classes / Modules This is similar to Objects with fields_objects, but the fields object is stored at a different offset. We’re differentiating this from boxable classes and modules because those are harder to support. 4. Other "Other" pattern is for objects that are rare, or have difficult-to-implement access patterns. This includes: * Boxable classes and modules * Structs (for now) * Objects that use the geniv table Proposed shape bit layout: ``` Current shape_id_t is 32 bits: 31 28 27 26 25 24 23 22 19 18 0 +-----------+--+--+--+--+--+------------+----------------------------+ | unused |L1|L0|OI|FR|CX| heap index | shape tree offset | +-----------+--+--+--+--+--+------------+----------------------------+ | | | | | | | | | | | | | +-- bits 0-18: SHAPE_ID_OFFSET_MASK | | | | | +--------------- bits 19-22: SHAPE_ID_HEAP_INDEX_MASK | | | | +------------------ bit 23: SHAPE_ID_FL_COMPLEX | | | +--------------------- bit 24: SHAPE_ID_FL_FROZEN | | +------------------------ bit 25: SHAPE_ID_FL_HAS_OBJECT_ID +--+--------------------------- bits 26-27: SHAPE_ID_LAYOUT_MASK ``` The important part about these layout patterns is that they do not reflect the _type_ of object, only how the object is laid out in memory. For example, we currently treat structs as "other", but we can refactor them to have the same layout as "Objects with fields_objects", and when we do that they should get a different bit in the shape header. This commit only reserves the two bits, it doesn't use them in the JIT compiler yet. Co-Authored-By: John Hawthorn Co-Authored-By: Max Bernstein * Update gc.c Co-authored-by: Nobuyoshi Nakada * Update shape.h Co-authored-by: Jean Boussier * fix function name * Update shape.c Co-authored-by: Jean Boussier * fix function name * Revert "Update shape.c" This reverts commit 900711defc6c541a93f3393a350819ae88cf87f1. * add comment --------- Co-authored-by: John Hawthorn Co-authored-by: Max Bernstein Co-authored-by: Nobuyoshi Nakada Co-authored-by: Jean Boussier --- array.c | 2 + class.c | 10 ++++- depend | 1 + gc.c | 24 ++++++++--- imemo.c | 18 ++++++-- internal/gc.h | 2 +- ractor.c | 2 +- shape.c | 78 ++++++++++++++++++++++++++++++---- shape.h | 54 ++++++++++++++++++----- string.c | 2 +- test/ruby/test_shapes.rb | 31 ++++++++++++++ variable.c | 22 ++++++---- vm_insnhelper.c | 7 ++- zjit/src/cruby_bindings.inc.rs | 7 ++- 14 files changed, 216 insertions(+), 44 deletions(-) diff --git a/array.c b/array.c index 08d8d17c90f79f..7f06e3c9c72f66 100644 --- a/array.c +++ b/array.c @@ -30,6 +30,7 @@ #include "ruby/thread.h" #include "ruby/util.h" #include "ruby/ractor.h" +#include "shape.h" #include "vm_core.h" #include "builtin.h" @@ -909,6 +910,7 @@ init_fake_ary_flags(void) struct RArray fake_ary = {0}; fake_ary.basic.flags = T_ARRAY; VALUE ary = (VALUE)&fake_ary; + RBASIC_SET_SHAPE_ID(ary, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); rb_ary_freeze(ary); return fake_ary.basic.flags; } diff --git a/class.c b/class.c index 69194ccab23756..02078cc9bc8baf 100644 --- a/class.c +++ b/class.c @@ -585,7 +585,15 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) VALUE flags = type | FL_SHAREABLE; if (boxable) flags |= RCLASS_BOXABLE; - NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size); + shape_id_t shape_id = ROOT_SHAPE_ID; + if (boxable) { + shape_id |= SHAPE_ID_LAYOUT_OTHER; + } + else { + shape_id |= SHAPE_ID_LAYOUT_RCLASS; + } + + struct RClass *obj = (struct RClass *)rb_newobj(GET_EC(), klass, flags, shape_id, true, alloc_size); obj->object_id = 0; diff --git a/depend b/depend index e61b6856704712..a2e8312298e7ea 100644 --- a/depend +++ b/depend @@ -80,6 +80,7 @@ array.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h array.$(OBJEXT): $(top_srcdir)/internal/serial.h array.$(OBJEXT): $(top_srcdir)/internal/set_table.h array.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +array.$(OBJEXT): $(top_srcdir)/internal/struct.h array.$(OBJEXT): $(top_srcdir)/internal/variable.h array.$(OBJEXT): $(top_srcdir)/internal/vm.h array.$(OBJEXT): $(top_srcdir)/internal/warnings.h diff --git a/gc.c b/gc.c index 9eb72329404a3b..6357577e63705f 100644 --- a/gc.c +++ b/gc.c @@ -1056,7 +1056,15 @@ rb_newobj(rb_execution_context_t *ec, VALUE klass, VALUE flags, shape_id_t shape VALUE rb_ec_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, size_t size) { - return rb_newobj(ec, klass, flags, ROOT_SHAPE_ID, true, size); + VALUE type = flags & T_MASK; + RUBY_ASSERT(type != T_OBJECT); + RUBY_ASSERT(type != T_DATA); + RUBY_ASSERT(type != T_CLASS); + RUBY_ASSERT(type != T_MODULE); + RUBY_ASSERT(type != T_ICLASS); + (void)type; + + return rb_newobj(ec, klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, true, size); } VALUE @@ -1068,13 +1076,13 @@ rb_newobj_of_with_shape(VALUE klass, VALUE flags, shape_id_t shape_id, size_t si VALUE rb_newobj_of(VALUE klass, VALUE flags, size_t size) { - return rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID, true, size); + return rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, true, size); } static VALUE class_allocate_complex_instance(VALUE klass, uint32_t capacity) { - shape_id_t initial_shape_id = rb_shape_root(rb_gc_heap_id_for_size(sizeof(struct RObject))); + shape_id_t initial_shape_id = rb_shape_id_with_robject_layout(rb_shape_root(rb_gc_heap_id_for_size(sizeof(struct RObject)))); VALUE obj = rb_newobj_of_with_shape(klass, T_OBJECT, initial_shape_id, sizeof(struct RObject)); rb_obj_init_complex(obj, rb_st_init_numtable_with_size(capacity)); return obj; @@ -1099,7 +1107,7 @@ rb_class_allocate_instance(VALUE klass) // There might be a NEWOBJ tracepoint callback, and it may set fields. // So the shape must be passed to `NEWOBJ_OF`. - obj = rb_newobj_of_with_shape(klass, T_OBJECT, rb_shape_root(rb_gc_heap_id_for_size(size)), size); + obj = rb_newobj_of_with_shape(klass, T_OBJECT, rb_shape_id_with_robject_layout(rb_shape_root(rb_gc_heap_id_for_size(size))), size); #if RUBY_DEBUG VALUE *ptr = ROBJECT_FIELDS(obj); @@ -1149,7 +1157,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_ RBIMPL_NONNULL_ARG(type); if (klass) rb_data_object_check(klass); bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark; - VALUE obj = rb_newobj(GET_EC(), klass, T_DATA, ROOT_SHAPE_ID, wb_protected, size); + VALUE obj = rb_newobj(GET_EC(), klass, T_DATA, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_RDATA, wb_protected, size); rb_gc_register_pinning_obj(obj); @@ -4189,7 +4197,11 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data) break; case ST_DELETE: - RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID); + // When we're removing an object from the weak ref table, we need to + // set the shape on it so that the GC finalizer won't try to remove + // it again. A "root shape" indicates to the GC that this object + // has no fields on it, hence it won't be in the gen fields table. + RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); return ST_DELETE; case ST_REPLACE: { diff --git a/imemo.c b/imemo.c index 3448a8dcd3c54f..796e078c89565f 100644 --- a/imemo.c +++ b/imemo.c @@ -141,7 +141,11 @@ rb_imemo_fields_new(VALUE owner, shape_id_t shape_id, bool shareable) size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE); RUBY_ASSERT(rb_gc_size_allocatable_p(embedded_size)); VALUE fields = rb_imemo_new(imemo_fields, owner, embedded_size, shareable); - RBASIC_SET_SHAPE_ID(fields, shape_id); + // imemo fields objects should always have "RObject" layout. The + // layout in the shape describes the layout of the thing on which it is set. + // Imemo fields have the same layout as robject, therefore the layout + // should reflect that fact. + RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields)); return fields; } @@ -152,7 +156,11 @@ rb_imemo_fields_new_complex(VALUE owner, shape_id_t shape_id, size_t capa, bool VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa); FL_SET_RAW(fields, OBJ_FIELD_HEAP); - RBASIC_SET_SHAPE_ID(fields, shape_id); + // imemo fields objects should always have "RObject" layout. The + // layout in the shape describes the layout of the thing on which it is set. + // Imemo fields have the same layout as robject, therefore the layout + // should reflect that fact. + RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); return fields; } @@ -177,7 +185,11 @@ rb_imemo_fields_new_complex_tbl(VALUE owner, shape_id_t shape_id, st_table *tbl, VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl; FL_SET_RAW(fields, OBJ_FIELD_HEAP); - RBASIC_SET_SHAPE_ID(fields, shape_id); + // imemo fields objects should always have "RObject" layout. The + // layout in the shape describes the layout of the thing on which it is set. + // Imemo fields have the same layout as robject, therefore the layout + // should reflect that fact. + RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields); return fields; } diff --git a/internal/gc.h b/internal/gc.h index 41675810c722c4..b78808370cd33c 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -124,7 +124,7 @@ struct rb_objspace; /* in vm_core.h */ T *(var) = (T *)rb_ec_newobj_of((ec), (c), (f), s) #define NEWOBJ_OF(var, T, c, f, s) EC_NEWOBJ_OF(var, T, c, f, s, GET_EC()) #define UNPROTECTED_NEWOBJ_OF(var, T, c, f, s) \ - T *(var) = (T *)rb_newobj((GET_EC()), (c), (f), 0 /* ROOT_SHAPE_ID */, false, s) + T *(var) = (T *)rb_newobj((GET_EC()), (c), (f), ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, false, s) #ifndef RB_GC_OBJECT_METADATA_ENTRY_DEFINED # define RB_GC_OBJECT_METADATA_ENTRY_DEFINED diff --git a/ractor.c b/ractor.c index d611ca97c273df..f94c06cc738bf4 100644 --- a/ractor.c +++ b/ractor.c @@ -2015,7 +2015,7 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data) else { VALUE type = RB_BUILTIN_TYPE(obj); size_t slot_size = rb_gc_obj_slot_size(obj); - VALUE moved = rb_newobj(GET_EC(), 0, type, 0, wb_protected_types[type], slot_size); + VALUE moved = rb_newobj(GET_EC(), 0, type, RBASIC_SHAPE_ID(obj), wb_protected_types[type], slot_size); MEMZERO(((struct RBasic *)moved) + 1, char, slot_size - sizeof(struct RBasic)); data->replacement = (VALUE)moved; return traverse_cont; diff --git a/shape.c b/shape.c index 24f1394f6cd32f..7a02b230733043 100644 --- a/shape.c +++ b/shape.c @@ -409,10 +409,14 @@ rb_obj_shape_id(VALUE obj) if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + shape_id_t base = ROOT_SHAPE_ID; if (fields_obj) { - return RBASIC_SHAPE_ID(fields_obj); + // Remove the layout from the fields object. We want to + // combine the shape of the fields object with the layout of the + // class / module object. + base = RBASIC_SHAPE_ID(fields_obj) & ~SHAPE_ID_LAYOUT_MASK; } - return ROOT_SHAPE_ID; + return rb_shape_layout(RBASIC_SHAPE_ID(obj)) | base; } return RBASIC_SHAPE_ID(obj); } @@ -697,7 +701,7 @@ rb_shape_transition_object_id(shape_id_t original_shape_id) bool dont_care; rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), id_object_id, SHAPE_OBJ_ID, &dont_care, true); if (!shape) { - return ROOT_COMPLEX_WITH_OBJ_ID | RSHAPE_FLAGS(original_shape_id); + return rb_shape_layout(original_shape_id) | ROOT_COMPLEX_WITH_OBJ_ID | RSHAPE_FLAGS(original_shape_id); } RUBY_ASSERT(shape); @@ -1225,17 +1229,55 @@ rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_ } #if RUBY_DEBUG +/* + * Get the layout of this object. The "layout" indicates what strategy + * we should use for fetching instance variables from `obj`. It's based + * on the C struct layout for each particular object. + * + * TODO: make Struct have a similar layout to RDATA + */ +static shape_id_t +rb_shape_expected_layout(VALUE obj) +{ + switch (BUILTIN_TYPE(obj)) { + case T_OBJECT: + return SHAPE_ID_LAYOUT_ROBJECT; + case T_CLASS: + case T_MODULE: + if (FL_TEST_RAW(obj, RCLASS_BOXABLE)) { + return SHAPE_ID_LAYOUT_OTHER; + } + return SHAPE_ID_LAYOUT_RCLASS; + case T_DATA: + return SHAPE_ID_LAYOUT_RDATA; + case T_IMEMO: + if (IMEMO_TYPE_P(obj, imemo_fields)) { + return SHAPE_ID_LAYOUT_ROBJECT; + } + return SHAPE_ID_LAYOUT_OTHER; + default: + return SHAPE_ID_LAYOUT_OTHER; + } +} + bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) { - if (shape_id == ROOT_SHAPE_ID) { - return true; - } - if (shape_id == INVALID_SHAPE_ID) { rb_bug("Can't set INVALID_SHAPE_ID on an object"); } + shape_id_t actual_layout = rb_shape_layout(rb_obj_shape_id(obj)); + shape_id_t expected_layout = rb_shape_expected_layout(obj); + if (actual_layout != expected_layout) { + rb_bug("shape_id layout mismatch: expected=%x actual=%x shape_id=%u obj=%s", + expected_layout, actual_layout, shape_id, rb_obj_info(obj)); + } + + if (shape_id == ROOT_SHAPE_ID) { + return true; + } + rb_shape_t *shape = RSHAPE(shape_id); bool has_object_id = false; @@ -1249,13 +1291,11 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) if (rb_shape_has_object_id(shape_id)) { if (!has_object_id) { - rb_p(obj); rb_bug("shape_id claim having obj_id but doesn't shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); } } else { if (has_object_id) { - rb_p(obj); rb_bug("shape_id claim not having obj_id but it does shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); } } @@ -1329,6 +1369,25 @@ shape_has_object_id_p(VALUE self) return RBOOL(rb_shape_has_object_id(shape_id)); } +static VALUE +shape_layout(VALUE self) +{ + shape_id_t shape_id = NUM2UINT(rb_struct_getmember(self, rb_intern("id"))); + + switch (rb_shape_layout(shape_id)) { + case SHAPE_ID_LAYOUT_ROBJECT: + return ID2SYM(rb_intern("robject")); + case SHAPE_ID_LAYOUT_RCLASS: + return ID2SYM(rb_intern("rclass")); + case SHAPE_ID_LAYOUT_RDATA: + return ID2SYM(rb_intern("rdata")); + case SHAPE_ID_LAYOUT_OTHER: + return ID2SYM(rb_intern("other")); + default: + rb_bug("unknown shape layout: %u", rb_shape_layout(shape_id)); + } +} + static VALUE parse_key(ID key) { @@ -1628,6 +1687,7 @@ Init_shape(void) rb_define_method(rb_cShape, "complex?", shape_complex, 0); rb_define_method(rb_cShape, "shape_frozen?", shape_frozen, 0); rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id_p, 0); + rb_define_method(rb_cShape, "layout", shape_layout, 0); rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT)); rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR)); diff --git a/shape.h b/shape.h index 61fadca5bace2d..a319449988e8fc 100644 --- a/shape.h +++ b/shape.h @@ -27,12 +27,14 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_ // 19-22 SHAPE_ID_HEAP_INDEX_MASK // index in rb_shape_tree.capacities. Allow to access slot size. // Currently always 0 except for T_OBJECT. -// 23 SHAPE_ID_FL_FROZEN +// 23 SHAPE_ID_FL_COMPLEX +// The object is backed by a `st_table`. +// 24 SHAPE_ID_FL_FROZEN // Whether the object is frozen or not. -// 24 SHAPE_ID_FL_HAS_OBJECT_ID +// 25 SHAPE_ID_FL_HAS_OBJECT_ID // Whether the object has an `SHAPE_OBJ_ID` transition. -// 25 SHAPE_ID_FL_COMPLEX -// The object is backed by a `st_table`. +// 26-27 SHAPE_ID_LAYOUT_MASK +// The object's physical field layout. enum shape_id_fl_type { #define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n)) @@ -43,8 +45,26 @@ enum shape_id_fl_type { SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(1), SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(2), + // Means IVs are found at an offset from the object's addr, or in a + // malloc allocated side table + SHAPE_ID_LAYOUT_ROBJECT = 0, + + // Means this object is a class/module that is NOT RCLASS_BOXABLE, and IV's + // are found in the fields_obj found on the rclass struct + SHAPE_ID_LAYOUT_RCLASS = RBIMPL_SHAPE_ID_FL(3), + + // Means this object is an RData or RTypedData and IVs are found in the + // fields_obj found on the RData/RTypedData struct + SHAPE_ID_LAYOUT_RDATA = RBIMPL_SHAPE_ID_FL(4), + + // Means this is a complicated object: boxable classes, structs, objects + // that store IVs on the geniv table + SHAPE_ID_LAYOUT_OTHER = SHAPE_ID_LAYOUT_RCLASS | SHAPE_ID_LAYOUT_RDATA, + + SHAPE_ID_LAYOUT_MASK = SHAPE_ID_LAYOUT_OTHER, + SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID, - SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX, + SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX | SHAPE_ID_LAYOUT_MASK, #undef RBIMPL_SHAPE_ID_FL }; @@ -55,12 +75,13 @@ enum shape_id_mask { SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1), }; -// The interpreter doesn't care about frozen status, slot size or object id when reading ivars. +// The interpreter doesn't care about frozen status, slot size, or object id, and +// has its own checks for physical field layout when reading ivars. // So we normalize shape_id by clearing these bits to improve cache hits. // JITs however might care about some of it. -#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID)) +#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK)) // For write it's the same idea, but here we do care about frozen status. -#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID)) +#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK)) typedef uint32_t redblack_id_t; @@ -153,11 +174,18 @@ RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) #endif } +static inline shape_id_t +rb_shape_layout(shape_id_t shape_id) +{ + return shape_id & SHAPE_ID_LAYOUT_MASK; +} + static inline void 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_fields)); + RUBY_ASSERT(!IMEMO_TYPE_P(obj, imemo_fields) || rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); @@ -232,6 +260,12 @@ rb_shape_canonical_p(shape_id_t shape_id) return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK); } +static inline shape_id_t +rb_shape_id_with_robject_layout(shape_id_t shape_id) +{ + return (shape_id & ~SHAPE_ID_LAYOUT_MASK) | SHAPE_ID_LAYOUT_ROBJECT; +} + static inline uint8_t rb_shape_heap_index(shape_id_t shape_id) { @@ -450,10 +484,10 @@ rb_shape_transition_frozen(shape_id_t shape_id) static inline shape_id_t rb_shape_transition_complex(shape_id_t shape_id) { - shape_id_t next_shape_id = ROOT_COMPLEX_SHAPE_ID; + shape_id_t next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_SHAPE_ID; if (rb_shape_has_object_id(shape_id)) { - next_shape_id = ROOT_COMPLEX_WITH_OBJ_ID; + next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_WITH_OBJ_ID; } uint8_t heap_index = rb_shape_heap_index(shape_id); diff --git a/string.c b/string.c index 6865b0d8e658f3..61d4f16679d26b 100644 --- a/string.c +++ b/string.c @@ -630,7 +630,7 @@ static VALUE setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx) { fake_str->basic.flags = T_STRING|RSTRING_NOEMBED|STR_NOFREE|STR_FAKESTR; - RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID); + RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); if (!name) { RUBY_ASSERT_ALWAYS(len == 0); diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index ef5dbd9fb15ee5..bace69658adfb5 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -1067,6 +1067,37 @@ def test_new_obj_has_t_object_shape assert_nil shape.parent end + def test_shape_layout + assert_equal :robject, RubyVM::Shape.of(TestObject.new).layout + + if ENV["RUBY_BOX"] + assert_equal :other, RubyVM::Shape.of(Kernel).layout + assert_equal :other, RubyVM::Shape.of(String).layout + else + assert_equal :rclass, RubyVM::Shape.of(Kernel).layout + assert_equal :rclass, RubyVM::Shape.of(String).layout + end + + assert_equal :rclass, RubyVM::Shape.of(Class.new).layout + assert_equal :rclass, RubyVM::Shape.of(Module.new).layout + + klass = Class.new + assert_equal :rclass, RubyVM::Shape.of(klass).layout + klass.instance_variable_set(:@a, 123) + assert_equal :rclass, RubyVM::Shape.of(klass).layout + + assert_equal :rdata, RubyVM::Shape.of(Thread.current).layout + assert_equal :rdata, RubyVM::Shape.of(lambda {}).layout + + assert_equal :other, RubyVM::Shape.of(Struct.new(:x).new(1)).layout + assert_equal :other, RubyVM::Shape.of([]).layout + assert_equal :other, RubyVM::Shape.of("hello").layout + assert_equal :other, RubyVM::Shape.of(/foo/).layout + assert_equal :other, RubyVM::Shape.of(2..3).layout + assert_equal :other, RubyVM::Shape.of(2**67).layout + assert_equal :other, RubyVM::Shape.of(:"aaroniscool#{123}").layout + end + def test_str_has_root_shape assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of("")) end diff --git a/variable.c b/variable.c index 857d870413881f..687fa03631b6bb 100644 --- a/variable.c +++ b/variable.c @@ -1343,7 +1343,7 @@ rb_free_generic_ivar(VALUE obj) } } } - RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | ROOT_SHAPE_ID); } } @@ -1395,7 +1395,7 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie } } - RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(fields_obj)); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | RBASIC_SHAPE_ID(fields_obj)); } void @@ -1710,6 +1710,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) size_t trailing_fields = new_fields_count - removed_index; MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields); + RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); if (FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP)) { @@ -1732,7 +1733,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) } } - RBASIC_SET_SHAPE_ID(obj, next_shape_id); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | next_shape_id); if (fields_obj != original_fields_obj) { switch (type) { case T_OBJECT: @@ -1843,7 +1844,7 @@ imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID f RUBY_ASSERT(field_name); st_insert(table, (st_data_t)field_name, (st_data_t)val); RB_OBJ_WRITTEN(fields_obj, Qundef, val); - RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(target_shape_id)); } else { attr_index_t index = RSHAPE_INDEX(target_shape_id); @@ -1855,7 +1856,7 @@ imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID f RB_OBJ_WRITE(fields_obj, &table[index], val); if (index >= RSHAPE_LEN(current_shape_id)) { - RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(target_shape_id)); } } @@ -2010,11 +2011,12 @@ rb_obj_freeze_inline(VALUE x) RB_FL_UNSET_RAW(x, FL_USER2 | FL_USER3); // STR_CHILLED } + // rb_obj_freeze_inline(String) shape_id_t shape_id = rb_obj_shape_transition_frozen(x); switch (BUILTIN_TYPE(x)) { case T_CLASS: case T_MODULE: - RBASIC_SET_SHAPE_ID(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(x), shape_id); + rb_obj_freeze_inline(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(x)); // FIXME: How to do multi-shape? RBASIC_SET_SHAPE_ID(x, shape_id); break; @@ -2303,7 +2305,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) } if (!RSHAPE_LEN(dest_shape_id)) { - RBASIC_SET_SHAPE_ID(dest, dest_shape_id); + RBASIC_SET_SHAPE_ID(dest, rb_shape_layout(RBASIC_SHAPE_ID(dest)) | dest_shape_id); return; } @@ -4636,6 +4638,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc } if (new_ivar) { + RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } } @@ -4658,6 +4661,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc RB_OBJ_WRITTEN(fields_obj, Qundef, val); if (fields_obj != original_fields_obj) { + RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } } @@ -4684,7 +4688,7 @@ class_ivar_set(VALUE obj, ID id, VALUE val, bool *new_ivar) // TODO: What should we set as the T_CLASS shape_id? // In most case we can replicate the single `fields_obj` shape // but in namespaced case? Perhaps INVALID_SHAPE_ID? - RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); + RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | RBASIC_SHAPE_ID(new_fields_obj)); return index; } @@ -4709,7 +4713,7 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src); if (fields_obj) { RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj)); - RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src)); + RBASIC_SET_SHAPE_ID(dst, rb_shape_layout(RBASIC_SHAPE_ID(dst)) | RBASIC_SHAPE_ID(src)); } } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index ac0d81092d890d..f515662bf07765 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1412,7 +1412,8 @@ vm_setivar_class(VALUE obj, VALUE val, rb_setivar_cache cache) RB_OBJ_WRITE(fields_obj, &rb_imemo_fields_ptr(fields_obj)[cache.index], val); if (shape_id != dest_shape_id) { - RBASIC_SET_SHAPE_ID(obj, dest_shape_id); + // The dest_shape_id comes from the fields_obj + RBASIC_SET_SHAPE_ID(obj, SHAPE_ID_LAYOUT_RCLASS | (dest_shape_id & ~SHAPE_ID_LAYOUT_MASK)); RBASIC_SET_SHAPE_ID(fields_obj, dest_shape_id); } @@ -1437,7 +1438,9 @@ vm_setivar_default(VALUE obj, ID id, VALUE val, rb_setivar_cache cache) if (shape_id != dest_shape_id) { RBASIC_SET_SHAPE_ID(obj, dest_shape_id); - RBASIC_SET_SHAPE_ID(fields_obj, dest_shape_id); + // The dest_shape_id comes from the owner, but fields_obj must always + // have layout RObject, so give the fields_object the right layout. + RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(dest_shape_id)); } RB_DEBUG_COUNTER_INC(ivar_set_ic_hit); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index bd832acc9604cd..08c502b0d84e47 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1491,8 +1491,13 @@ pub const SHAPE_ID_HEAP_INDEX_MASK: shape_id_fl_type = 7864320; pub const SHAPE_ID_FL_COMPLEX: shape_id_fl_type = 8388608; pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 16777216; pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 33554432; +pub const SHAPE_ID_LAYOUT_ROBJECT: shape_id_fl_type = 0; +pub const SHAPE_ID_LAYOUT_RCLASS: shape_id_fl_type = 67108864; +pub const SHAPE_ID_LAYOUT_RDATA: shape_id_fl_type = 134217728; +pub const SHAPE_ID_LAYOUT_OTHER: shape_id_fl_type = 201326592; +pub const SHAPE_ID_LAYOUT_MASK: shape_id_fl_type = 201326592; pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 50331648; -pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 66584576; +pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 267911168; pub type shape_id_fl_type = u32; pub const CONST_DEPRECATED: rb_const_flag_t = 256; pub const CONST_VISIBILITY_MASK: rb_const_flag_t = 255; From ddb5055d961d970aded287cfebd07b78efee3ca7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 May 2026 16:41:31 -0700 Subject: [PATCH 2/2] Revert "Reserve 2 bits for expressing object layout (#17139)" This reverts commit 63d9f090b5d9461cf0b9446e0039d9c56156b826. --- array.c | 2 - class.c | 10 +---- depend | 1 - gc.c | 24 +++-------- imemo.c | 18 ++------ internal/gc.h | 2 +- ractor.c | 2 +- shape.c | 78 ++++------------------------------ shape.h | 54 +++++------------------ string.c | 2 +- test/ruby/test_shapes.rb | 31 -------------- variable.c | 22 ++++------ vm_insnhelper.c | 7 +-- zjit/src/cruby_bindings.inc.rs | 7 +-- 14 files changed, 44 insertions(+), 216 deletions(-) diff --git a/array.c b/array.c index 7f06e3c9c72f66..08d8d17c90f79f 100644 --- a/array.c +++ b/array.c @@ -30,7 +30,6 @@ #include "ruby/thread.h" #include "ruby/util.h" #include "ruby/ractor.h" -#include "shape.h" #include "vm_core.h" #include "builtin.h" @@ -910,7 +909,6 @@ init_fake_ary_flags(void) struct RArray fake_ary = {0}; fake_ary.basic.flags = T_ARRAY; VALUE ary = (VALUE)&fake_ary; - RBASIC_SET_SHAPE_ID(ary, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); rb_ary_freeze(ary); return fake_ary.basic.flags; } diff --git a/class.c b/class.c index 02078cc9bc8baf..69194ccab23756 100644 --- a/class.c +++ b/class.c @@ -585,15 +585,7 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) VALUE flags = type | FL_SHAREABLE; if (boxable) flags |= RCLASS_BOXABLE; - shape_id_t shape_id = ROOT_SHAPE_ID; - if (boxable) { - shape_id |= SHAPE_ID_LAYOUT_OTHER; - } - else { - shape_id |= SHAPE_ID_LAYOUT_RCLASS; - } - - struct RClass *obj = (struct RClass *)rb_newobj(GET_EC(), klass, flags, shape_id, true, alloc_size); + NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size); obj->object_id = 0; diff --git a/depend b/depend index a2e8312298e7ea..e61b6856704712 100644 --- a/depend +++ b/depend @@ -80,7 +80,6 @@ array.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h array.$(OBJEXT): $(top_srcdir)/internal/serial.h array.$(OBJEXT): $(top_srcdir)/internal/set_table.h array.$(OBJEXT): $(top_srcdir)/internal/static_assert.h -array.$(OBJEXT): $(top_srcdir)/internal/struct.h array.$(OBJEXT): $(top_srcdir)/internal/variable.h array.$(OBJEXT): $(top_srcdir)/internal/vm.h array.$(OBJEXT): $(top_srcdir)/internal/warnings.h diff --git a/gc.c b/gc.c index 6357577e63705f..9eb72329404a3b 100644 --- a/gc.c +++ b/gc.c @@ -1056,15 +1056,7 @@ rb_newobj(rb_execution_context_t *ec, VALUE klass, VALUE flags, shape_id_t shape VALUE rb_ec_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, size_t size) { - VALUE type = flags & T_MASK; - RUBY_ASSERT(type != T_OBJECT); - RUBY_ASSERT(type != T_DATA); - RUBY_ASSERT(type != T_CLASS); - RUBY_ASSERT(type != T_MODULE); - RUBY_ASSERT(type != T_ICLASS); - (void)type; - - return rb_newobj(ec, klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, true, size); + return rb_newobj(ec, klass, flags, ROOT_SHAPE_ID, true, size); } VALUE @@ -1076,13 +1068,13 @@ rb_newobj_of_with_shape(VALUE klass, VALUE flags, shape_id_t shape_id, size_t si VALUE rb_newobj_of(VALUE klass, VALUE flags, size_t size) { - return rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, true, size); + return rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID, true, size); } static VALUE class_allocate_complex_instance(VALUE klass, uint32_t capacity) { - shape_id_t initial_shape_id = rb_shape_id_with_robject_layout(rb_shape_root(rb_gc_heap_id_for_size(sizeof(struct RObject)))); + shape_id_t initial_shape_id = rb_shape_root(rb_gc_heap_id_for_size(sizeof(struct RObject))); VALUE obj = rb_newobj_of_with_shape(klass, T_OBJECT, initial_shape_id, sizeof(struct RObject)); rb_obj_init_complex(obj, rb_st_init_numtable_with_size(capacity)); return obj; @@ -1107,7 +1099,7 @@ rb_class_allocate_instance(VALUE klass) // There might be a NEWOBJ tracepoint callback, and it may set fields. // So the shape must be passed to `NEWOBJ_OF`. - obj = rb_newobj_of_with_shape(klass, T_OBJECT, rb_shape_id_with_robject_layout(rb_shape_root(rb_gc_heap_id_for_size(size))), size); + obj = rb_newobj_of_with_shape(klass, T_OBJECT, rb_shape_root(rb_gc_heap_id_for_size(size)), size); #if RUBY_DEBUG VALUE *ptr = ROBJECT_FIELDS(obj); @@ -1157,7 +1149,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_ RBIMPL_NONNULL_ARG(type); if (klass) rb_data_object_check(klass); bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark; - VALUE obj = rb_newobj(GET_EC(), klass, T_DATA, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_RDATA, wb_protected, size); + VALUE obj = rb_newobj(GET_EC(), klass, T_DATA, ROOT_SHAPE_ID, wb_protected, size); rb_gc_register_pinning_obj(obj); @@ -4197,11 +4189,7 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data) break; case ST_DELETE: - // When we're removing an object from the weak ref table, we need to - // set the shape on it so that the GC finalizer won't try to remove - // it again. A "root shape" indicates to the GC that this object - // has no fields on it, hence it won't be in the gen fields table. - RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); + RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID); return ST_DELETE; case ST_REPLACE: { diff --git a/imemo.c b/imemo.c index 796e078c89565f..3448a8dcd3c54f 100644 --- a/imemo.c +++ b/imemo.c @@ -141,11 +141,7 @@ rb_imemo_fields_new(VALUE owner, shape_id_t shape_id, bool shareable) size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE); RUBY_ASSERT(rb_gc_size_allocatable_p(embedded_size)); VALUE fields = rb_imemo_new(imemo_fields, owner, embedded_size, shareable); - // imemo fields objects should always have "RObject" layout. The - // layout in the shape describes the layout of the thing on which it is set. - // Imemo fields have the same layout as robject, therefore the layout - // should reflect that fact. - RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); + RBASIC_SET_SHAPE_ID(fields, shape_id); RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields)); return fields; } @@ -156,11 +152,7 @@ rb_imemo_fields_new_complex(VALUE owner, shape_id_t shape_id, size_t capa, bool VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa); FL_SET_RAW(fields, OBJ_FIELD_HEAP); - // imemo fields objects should always have "RObject" layout. The - // layout in the shape describes the layout of the thing on which it is set. - // Imemo fields have the same layout as robject, therefore the layout - // should reflect that fact. - RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); + RBASIC_SET_SHAPE_ID(fields, shape_id); return fields; } @@ -185,11 +177,7 @@ rb_imemo_fields_new_complex_tbl(VALUE owner, shape_id_t shape_id, st_table *tbl, VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl; FL_SET_RAW(fields, OBJ_FIELD_HEAP); - // imemo fields objects should always have "RObject" layout. The - // layout in the shape describes the layout of the thing on which it is set. - // Imemo fields have the same layout as robject, therefore the layout - // should reflect that fact. - RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id)); + RBASIC_SET_SHAPE_ID(fields, shape_id); st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields); return fields; } diff --git a/internal/gc.h b/internal/gc.h index b78808370cd33c..41675810c722c4 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -124,7 +124,7 @@ struct rb_objspace; /* in vm_core.h */ T *(var) = (T *)rb_ec_newobj_of((ec), (c), (f), s) #define NEWOBJ_OF(var, T, c, f, s) EC_NEWOBJ_OF(var, T, c, f, s, GET_EC()) #define UNPROTECTED_NEWOBJ_OF(var, T, c, f, s) \ - T *(var) = (T *)rb_newobj((GET_EC()), (c), (f), ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER, false, s) + T *(var) = (T *)rb_newobj((GET_EC()), (c), (f), 0 /* ROOT_SHAPE_ID */, false, s) #ifndef RB_GC_OBJECT_METADATA_ENTRY_DEFINED # define RB_GC_OBJECT_METADATA_ENTRY_DEFINED diff --git a/ractor.c b/ractor.c index f94c06cc738bf4..d611ca97c273df 100644 --- a/ractor.c +++ b/ractor.c @@ -2015,7 +2015,7 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data) else { VALUE type = RB_BUILTIN_TYPE(obj); size_t slot_size = rb_gc_obj_slot_size(obj); - VALUE moved = rb_newobj(GET_EC(), 0, type, RBASIC_SHAPE_ID(obj), wb_protected_types[type], slot_size); + VALUE moved = rb_newobj(GET_EC(), 0, type, 0, wb_protected_types[type], slot_size); MEMZERO(((struct RBasic *)moved) + 1, char, slot_size - sizeof(struct RBasic)); data->replacement = (VALUE)moved; return traverse_cont; diff --git a/shape.c b/shape.c index 7a02b230733043..24f1394f6cd32f 100644 --- a/shape.c +++ b/shape.c @@ -409,14 +409,10 @@ rb_obj_shape_id(VALUE obj) if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); - shape_id_t base = ROOT_SHAPE_ID; if (fields_obj) { - // Remove the layout from the fields object. We want to - // combine the shape of the fields object with the layout of the - // class / module object. - base = RBASIC_SHAPE_ID(fields_obj) & ~SHAPE_ID_LAYOUT_MASK; + return RBASIC_SHAPE_ID(fields_obj); } - return rb_shape_layout(RBASIC_SHAPE_ID(obj)) | base; + return ROOT_SHAPE_ID; } return RBASIC_SHAPE_ID(obj); } @@ -701,7 +697,7 @@ rb_shape_transition_object_id(shape_id_t original_shape_id) bool dont_care; rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), id_object_id, SHAPE_OBJ_ID, &dont_care, true); if (!shape) { - return rb_shape_layout(original_shape_id) | ROOT_COMPLEX_WITH_OBJ_ID | RSHAPE_FLAGS(original_shape_id); + return ROOT_COMPLEX_WITH_OBJ_ID | RSHAPE_FLAGS(original_shape_id); } RUBY_ASSERT(shape); @@ -1229,55 +1225,17 @@ rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_ } #if RUBY_DEBUG -/* - * Get the layout of this object. The "layout" indicates what strategy - * we should use for fetching instance variables from `obj`. It's based - * on the C struct layout for each particular object. - * - * TODO: make Struct have a similar layout to RDATA - */ -static shape_id_t -rb_shape_expected_layout(VALUE obj) -{ - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - return SHAPE_ID_LAYOUT_ROBJECT; - case T_CLASS: - case T_MODULE: - if (FL_TEST_RAW(obj, RCLASS_BOXABLE)) { - return SHAPE_ID_LAYOUT_OTHER; - } - return SHAPE_ID_LAYOUT_RCLASS; - case T_DATA: - return SHAPE_ID_LAYOUT_RDATA; - case T_IMEMO: - if (IMEMO_TYPE_P(obj, imemo_fields)) { - return SHAPE_ID_LAYOUT_ROBJECT; - } - return SHAPE_ID_LAYOUT_OTHER; - default: - return SHAPE_ID_LAYOUT_OTHER; - } -} - 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"); - } - - shape_id_t actual_layout = rb_shape_layout(rb_obj_shape_id(obj)); - shape_id_t expected_layout = rb_shape_expected_layout(obj); - if (actual_layout != expected_layout) { - rb_bug("shape_id layout mismatch: expected=%x actual=%x shape_id=%u obj=%s", - expected_layout, actual_layout, shape_id, rb_obj_info(obj)); - } - if (shape_id == ROOT_SHAPE_ID) { return true; } + 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; @@ -1291,11 +1249,13 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) if (rb_shape_has_object_id(shape_id)) { if (!has_object_id) { + rb_p(obj); rb_bug("shape_id claim having obj_id but doesn't shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); } } else { if (has_object_id) { + rb_p(obj); rb_bug("shape_id claim not having obj_id but it does shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); } } @@ -1369,25 +1329,6 @@ shape_has_object_id_p(VALUE self) return RBOOL(rb_shape_has_object_id(shape_id)); } -static VALUE -shape_layout(VALUE self) -{ - shape_id_t shape_id = NUM2UINT(rb_struct_getmember(self, rb_intern("id"))); - - switch (rb_shape_layout(shape_id)) { - case SHAPE_ID_LAYOUT_ROBJECT: - return ID2SYM(rb_intern("robject")); - case SHAPE_ID_LAYOUT_RCLASS: - return ID2SYM(rb_intern("rclass")); - case SHAPE_ID_LAYOUT_RDATA: - return ID2SYM(rb_intern("rdata")); - case SHAPE_ID_LAYOUT_OTHER: - return ID2SYM(rb_intern("other")); - default: - rb_bug("unknown shape layout: %u", rb_shape_layout(shape_id)); - } -} - static VALUE parse_key(ID key) { @@ -1687,7 +1628,6 @@ Init_shape(void) rb_define_method(rb_cShape, "complex?", shape_complex, 0); rb_define_method(rb_cShape, "shape_frozen?", shape_frozen, 0); rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id_p, 0); - rb_define_method(rb_cShape, "layout", shape_layout, 0); rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT)); rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR)); diff --git a/shape.h b/shape.h index a319449988e8fc..61fadca5bace2d 100644 --- a/shape.h +++ b/shape.h @@ -27,14 +27,12 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_ // 19-22 SHAPE_ID_HEAP_INDEX_MASK // index in rb_shape_tree.capacities. Allow to access slot size. // Currently always 0 except for T_OBJECT. -// 23 SHAPE_ID_FL_COMPLEX -// The object is backed by a `st_table`. -// 24 SHAPE_ID_FL_FROZEN +// 23 SHAPE_ID_FL_FROZEN // Whether the object is frozen or not. -// 25 SHAPE_ID_FL_HAS_OBJECT_ID +// 24 SHAPE_ID_FL_HAS_OBJECT_ID // Whether the object has an `SHAPE_OBJ_ID` transition. -// 26-27 SHAPE_ID_LAYOUT_MASK -// The object's physical field layout. +// 25 SHAPE_ID_FL_COMPLEX +// The object is backed by a `st_table`. enum shape_id_fl_type { #define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n)) @@ -45,26 +43,8 @@ enum shape_id_fl_type { SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(1), SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(2), - // Means IVs are found at an offset from the object's addr, or in a - // malloc allocated side table - SHAPE_ID_LAYOUT_ROBJECT = 0, - - // Means this object is a class/module that is NOT RCLASS_BOXABLE, and IV's - // are found in the fields_obj found on the rclass struct - SHAPE_ID_LAYOUT_RCLASS = RBIMPL_SHAPE_ID_FL(3), - - // Means this object is an RData or RTypedData and IVs are found in the - // fields_obj found on the RData/RTypedData struct - SHAPE_ID_LAYOUT_RDATA = RBIMPL_SHAPE_ID_FL(4), - - // Means this is a complicated object: boxable classes, structs, objects - // that store IVs on the geniv table - SHAPE_ID_LAYOUT_OTHER = SHAPE_ID_LAYOUT_RCLASS | SHAPE_ID_LAYOUT_RDATA, - - SHAPE_ID_LAYOUT_MASK = SHAPE_ID_LAYOUT_OTHER, - SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID, - SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX | SHAPE_ID_LAYOUT_MASK, + SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_COMPLEX, #undef RBIMPL_SHAPE_ID_FL }; @@ -75,13 +55,12 @@ enum shape_id_mask { SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1), }; -// The interpreter doesn't care about frozen status, slot size, or object id, and -// has its own checks for physical field layout when reading ivars. +// The interpreter doesn't care about frozen status, slot size or object id when reading ivars. // So we normalize shape_id by clearing these bits to improve cache hits. // JITs however might care about some of it. -#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK)) +#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID)) // For write it's the same idea, but here we do care about frozen status. -#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID | SHAPE_ID_LAYOUT_MASK)) +#define SHAPE_ID_WRITE_MASK (~(SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_HAS_OBJECT_ID)) typedef uint32_t redblack_id_t; @@ -174,18 +153,11 @@ RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) #endif } -static inline shape_id_t -rb_shape_layout(shape_id_t shape_id) -{ - return shape_id & SHAPE_ID_LAYOUT_MASK; -} - static inline void 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_fields)); - RUBY_ASSERT(!IMEMO_TYPE_P(obj, imemo_fields) || rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); @@ -260,12 +232,6 @@ rb_shape_canonical_p(shape_id_t shape_id) return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK); } -static inline shape_id_t -rb_shape_id_with_robject_layout(shape_id_t shape_id) -{ - return (shape_id & ~SHAPE_ID_LAYOUT_MASK) | SHAPE_ID_LAYOUT_ROBJECT; -} - static inline uint8_t rb_shape_heap_index(shape_id_t shape_id) { @@ -484,10 +450,10 @@ rb_shape_transition_frozen(shape_id_t shape_id) static inline shape_id_t rb_shape_transition_complex(shape_id_t shape_id) { - shape_id_t next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_SHAPE_ID; + shape_id_t next_shape_id = ROOT_COMPLEX_SHAPE_ID; if (rb_shape_has_object_id(shape_id)) { - next_shape_id = rb_shape_layout(shape_id) | ROOT_COMPLEX_WITH_OBJ_ID; + next_shape_id = ROOT_COMPLEX_WITH_OBJ_ID; } uint8_t heap_index = rb_shape_heap_index(shape_id); diff --git a/string.c b/string.c index 61d4f16679d26b..6865b0d8e658f3 100644 --- a/string.c +++ b/string.c @@ -630,7 +630,7 @@ static VALUE setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx) { fake_str->basic.flags = T_STRING|RSTRING_NOEMBED|STR_NOFREE|STR_FAKESTR; - RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER); + RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID); if (!name) { RUBY_ASSERT_ALWAYS(len == 0); diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index bace69658adfb5..ef5dbd9fb15ee5 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -1067,37 +1067,6 @@ def test_new_obj_has_t_object_shape assert_nil shape.parent end - def test_shape_layout - assert_equal :robject, RubyVM::Shape.of(TestObject.new).layout - - if ENV["RUBY_BOX"] - assert_equal :other, RubyVM::Shape.of(Kernel).layout - assert_equal :other, RubyVM::Shape.of(String).layout - else - assert_equal :rclass, RubyVM::Shape.of(Kernel).layout - assert_equal :rclass, RubyVM::Shape.of(String).layout - end - - assert_equal :rclass, RubyVM::Shape.of(Class.new).layout - assert_equal :rclass, RubyVM::Shape.of(Module.new).layout - - klass = Class.new - assert_equal :rclass, RubyVM::Shape.of(klass).layout - klass.instance_variable_set(:@a, 123) - assert_equal :rclass, RubyVM::Shape.of(klass).layout - - assert_equal :rdata, RubyVM::Shape.of(Thread.current).layout - assert_equal :rdata, RubyVM::Shape.of(lambda {}).layout - - assert_equal :other, RubyVM::Shape.of(Struct.new(:x).new(1)).layout - assert_equal :other, RubyVM::Shape.of([]).layout - assert_equal :other, RubyVM::Shape.of("hello").layout - assert_equal :other, RubyVM::Shape.of(/foo/).layout - assert_equal :other, RubyVM::Shape.of(2..3).layout - assert_equal :other, RubyVM::Shape.of(2**67).layout - assert_equal :other, RubyVM::Shape.of(:"aaroniscool#{123}").layout - end - def test_str_has_root_shape assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of("")) end diff --git a/variable.c b/variable.c index 687fa03631b6bb..857d870413881f 100644 --- a/variable.c +++ b/variable.c @@ -1343,7 +1343,7 @@ rb_free_generic_ivar(VALUE obj) } } } - RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | ROOT_SHAPE_ID); + RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID); } } @@ -1395,7 +1395,7 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie } } - RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | RBASIC_SHAPE_ID(fields_obj)); + RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(fields_obj)); } void @@ -1710,7 +1710,6 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) size_t trailing_fields = new_fields_count - removed_index; MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields); - RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); if (FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP)) { @@ -1733,7 +1732,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) } } - RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | next_shape_id); + RBASIC_SET_SHAPE_ID(obj, next_shape_id); if (fields_obj != original_fields_obj) { switch (type) { case T_OBJECT: @@ -1844,7 +1843,7 @@ imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID f RUBY_ASSERT(field_name); st_insert(table, (st_data_t)field_name, (st_data_t)val); RB_OBJ_WRITTEN(fields_obj, Qundef, val); - RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(target_shape_id)); + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); } else { attr_index_t index = RSHAPE_INDEX(target_shape_id); @@ -1856,7 +1855,7 @@ imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID f RB_OBJ_WRITE(fields_obj, &table[index], val); if (index >= RSHAPE_LEN(current_shape_id)) { - RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(target_shape_id)); + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); } } @@ -2011,12 +2010,11 @@ rb_obj_freeze_inline(VALUE x) RB_FL_UNSET_RAW(x, FL_USER2 | FL_USER3); // STR_CHILLED } - // rb_obj_freeze_inline(String) shape_id_t shape_id = rb_obj_shape_transition_frozen(x); switch (BUILTIN_TYPE(x)) { case T_CLASS: case T_MODULE: - rb_obj_freeze_inline(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(x)); + RBASIC_SET_SHAPE_ID(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(x), shape_id); // FIXME: How to do multi-shape? RBASIC_SET_SHAPE_ID(x, shape_id); break; @@ -2305,7 +2303,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) } if (!RSHAPE_LEN(dest_shape_id)) { - RBASIC_SET_SHAPE_ID(dest, rb_shape_layout(RBASIC_SHAPE_ID(dest)) | dest_shape_id); + RBASIC_SET_SHAPE_ID(dest, dest_shape_id); return; } @@ -4638,7 +4636,6 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc } if (new_ivar) { - RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } } @@ -4661,7 +4658,6 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc RB_OBJ_WRITTEN(fields_obj, Qundef, val); if (fields_obj != original_fields_obj) { - RUBY_ASSERT(rb_shape_layout(next_shape_id) == SHAPE_ID_LAYOUT_ROBJECT); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } } @@ -4688,7 +4684,7 @@ class_ivar_set(VALUE obj, ID id, VALUE val, bool *new_ivar) // TODO: What should we set as the T_CLASS shape_id? // In most case we can replicate the single `fields_obj` shape // but in namespaced case? Perhaps INVALID_SHAPE_ID? - RBASIC_SET_SHAPE_ID(obj, rb_shape_layout(RBASIC_SHAPE_ID(obj)) | RBASIC_SHAPE_ID(new_fields_obj)); + RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); return index; } @@ -4713,7 +4709,7 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src); if (fields_obj) { RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj)); - RBASIC_SET_SHAPE_ID(dst, rb_shape_layout(RBASIC_SHAPE_ID(dst)) | RBASIC_SHAPE_ID(src)); + RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src)); } } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index f515662bf07765..ac0d81092d890d 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1412,8 +1412,7 @@ vm_setivar_class(VALUE obj, VALUE val, rb_setivar_cache cache) RB_OBJ_WRITE(fields_obj, &rb_imemo_fields_ptr(fields_obj)[cache.index], val); if (shape_id != dest_shape_id) { - // The dest_shape_id comes from the fields_obj - RBASIC_SET_SHAPE_ID(obj, SHAPE_ID_LAYOUT_RCLASS | (dest_shape_id & ~SHAPE_ID_LAYOUT_MASK)); + RBASIC_SET_SHAPE_ID(obj, dest_shape_id); RBASIC_SET_SHAPE_ID(fields_obj, dest_shape_id); } @@ -1438,9 +1437,7 @@ vm_setivar_default(VALUE obj, ID id, VALUE val, rb_setivar_cache cache) if (shape_id != dest_shape_id) { RBASIC_SET_SHAPE_ID(obj, dest_shape_id); - // The dest_shape_id comes from the owner, but fields_obj must always - // have layout RObject, so give the fields_object the right layout. - RBASIC_SET_SHAPE_ID(fields_obj, rb_shape_id_with_robject_layout(dest_shape_id)); + RBASIC_SET_SHAPE_ID(fields_obj, dest_shape_id); } RB_DEBUG_COUNTER_INC(ivar_set_ic_hit); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 08c502b0d84e47..bd832acc9604cd 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1491,13 +1491,8 @@ pub const SHAPE_ID_HEAP_INDEX_MASK: shape_id_fl_type = 7864320; pub const SHAPE_ID_FL_COMPLEX: shape_id_fl_type = 8388608; pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 16777216; pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 33554432; -pub const SHAPE_ID_LAYOUT_ROBJECT: shape_id_fl_type = 0; -pub const SHAPE_ID_LAYOUT_RCLASS: shape_id_fl_type = 67108864; -pub const SHAPE_ID_LAYOUT_RDATA: shape_id_fl_type = 134217728; -pub const SHAPE_ID_LAYOUT_OTHER: shape_id_fl_type = 201326592; -pub const SHAPE_ID_LAYOUT_MASK: shape_id_fl_type = 201326592; pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 50331648; -pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 267911168; +pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 66584576; pub type shape_id_fl_type = u32; pub const CONST_DEPRECATED: rb_const_flag_t = 256; pub const CONST_VISIBILITY_MASK: rb_const_flag_t = 255;