diff options
Diffstat (limited to 'class.c')
| -rw-r--r-- | class.c | 856 |
1 files changed, 396 insertions, 460 deletions
@@ -21,16 +21,18 @@ #include "debug_counter.h" #include "id_table.h" #include "internal.h" +#include "internal/box.h" #include "internal/class.h" #include "internal/eval.h" #include "internal/hash.h" -#include "internal/namespace.h" #include "internal/object.h" #include "internal/string.h" #include "internal/variable.h" #include "ruby/st.h" #include "vm_core.h" +#include "ruby/ractor.h" #include "yjit.h" +#include "zjit.h" /* Flags of T_CLASS * @@ -39,22 +41,22 @@ * This is done for classes defined from C to allow storing them in global variables. * 1: RUBY_FL_SINGLETON * This class is a singleton class. - * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE - * This class's prime classext is the only classext and writable from any namespaces. - * If unset, the prime classext is writable only from the root namespace. + * 2: RCLASS_PRIME_CLASSEXT_WRITABLE + * This class's prime classext is the only classext and writable from any boxes. + * If unset, the prime classext is writable only from the root box. * 3: RCLASS_IS_INITIALIZED * Class has been initialized. - * 4: RCLASS_NAMESPACEABLE - * Is a builtin class that may be namespaced. It larger than a normal class. + * 4: RCLASS_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. */ /* Flags of T_ICLASS * - * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE - * This module's prime classext is the only classext and writable from any namespaces. - * If unset, the prime classext is writable only from the root namespace. - * 4: RCLASS_NAMESPACEABLE - * Is a builtin class that may be namespaced. It larger than a normal class. + * 2: RCLASS_PRIME_CLASSEXT_WRITABLE + * This module's prime classext is the only classext and writable from any boxes. + * If unset, the prime classext is writable only from the root box. + * 4: RCLASS_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. */ /* Flags of T_MODULE @@ -62,35 +64,140 @@ * 0: RCLASS_IS_ROOT * The class has been added to the VM roots. Will always be marked and pinned. * This is done for classes defined from C to allow storing them in global variables. - * 1: RMODULE_IS_REFINEMENT - * Module is used for refinements. - * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE - * This module's prime classext is the only classext and writable from any namespaces. - * If unset, the prime classext is writable only from the root namespace. + * 1: <reserved> + * Ensures that RUBY_FL_SINGLETON is never set on a T_MODULE. See `rb_class_real`. + * 2: RCLASS_PRIME_CLASSEXT_WRITABLE + * This module's prime classext is the only classext and writable from any boxes. + * If unset, the prime classext is writable only from the root box. * 3: RCLASS_IS_INITIALIZED * Module has been initialized. - * 4: RCLASS_NAMESPACEABLE - * Is a builtin class that may be namespaced. It larger than a normal class. + * 4: RCLASS_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. + * 5: RMODULE_IS_REFINEMENT + * Module is used for refinements. */ #define METACLASS_OF(k) RBASIC(k)->klass #define SET_METACLASS_OF(k, cls) RBASIC_SET_CLASS(k, cls) -RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state; +rb_classext_t * +rb_class_unlink_classext(VALUE klass, const rb_box_t *box) +{ + st_data_t ext; + st_data_t key = (st_data_t)box->box_object; + st_delete(box->classext_cow_classes, &klass, 0); + st_delete(RCLASS_CLASSEXT_TBL(klass), &key, &ext); + return (rb_classext_t *)ext; +} -struct duplicate_id_tbl_data { +void +rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) +{ struct rb_id_table *tbl; - VALUE klass; + + rb_id_table_free(RCLASSEXT_M_TBL(ext)); + + if (!RCLASSEXT_SHARED_CONST_TBL(ext) && (tbl = RCLASSEXT_CONST_TBL(ext)) != NULL) { + rb_free_const_table(tbl); + } + + if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { + RUBY_ASSERT(is_prime); // superclasses should only be used on prime + size_t depth = RCLASSEXT_SUPERCLASS_DEPTH(ext); + if (depth != RCLASS_MAX_SUPERCLASS_DEPTH) { + depth++; + } + SIZED_FREE_N(RCLASSEXT_SUPERCLASSES(ext), depth); + } + + if (!is_prime) { // the prime classext will be freed with RClass + SIZED_FREE(ext); + } +} + +void +rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) +{ + if (RCLASSEXT_ICLASS_IS_ORIGIN(ext) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext)) { + /* Method table is not shared for origin iclasses of classes */ + rb_id_table_free(RCLASSEXT_M_TBL(ext)); + } + + if (RCLASSEXT_CALLABLE_M_TBL(ext) != NULL) { + rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); + } + + if (!is_prime) { // the prime classext will be freed with RClass + SIZED_FREE(ext); + } +} + +static void +iclass_free_orphan_classext(VALUE klass, rb_classext_t *ext) +{ + if (RCLASSEXT_ICLASS_IS_ORIGIN(ext) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext)) { + /* Method table is not shared for origin iclasses of classes */ + rb_id_table_free(RCLASSEXT_M_TBL(ext)); + } + + if (RCLASSEXT_CALLABLE_M_TBL(ext) != NULL) { + rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); + } + + SIZED_FREE(ext); +} + +struct rb_class_set_box_classext_args { + VALUE obj; + rb_classext_t *ext; }; -static enum rb_id_table_iterator_result -duplicate_classext_id_table_i(ID key, VALUE value, void *data) +static int +set_box_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t a, int existing) { - struct rb_id_table *tbl = (struct rb_id_table *)data; - rb_id_table_insert(tbl, key, value); - return ID_TABLE_CONTINUE; + struct rb_class_set_box_classext_args *args = (struct rb_class_set_box_classext_args *)a; + + if (existing) { + if (LIKELY(BUILTIN_TYPE(args->obj) == T_ICLASS)) { + iclass_free_orphan_classext(args->obj, (rb_classext_t *)*val_ptr); + } + else { + rb_bug("Updating existing classext for non-iclass never happen"); + } + } + + *val_ptr = (st_data_t)args->ext; + + return ST_CONTINUE; } +void +rb_class_set_box_classext(VALUE obj, const rb_box_t *box, rb_classext_t *ext) +{ + struct rb_class_set_box_classext_args args = { + .obj = obj, + .ext = ext, + }; + + VM_ASSERT(BOX_MUTABLE_P(box)); + + st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)box->box_object, set_box_classext_update, (st_data_t)&args); + + // The classext references are now visible via the classext table, + // so we must issue the write barrier before any further allocations + // (e.g. st_insert below) that could trigger GC. + rb_gc_writebarrier_remember(obj); + + st_insert(box->classext_cow_classes, (st_data_t)obj, 0); +} + +RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state; + +struct duplicate_id_tbl_data { + struct rb_id_table *tbl; + VALUE klass; +}; + static enum rb_id_table_iterator_result duplicate_classext_m_tbl_i(ID key, VALUE value, void *data) { @@ -119,22 +226,6 @@ duplicate_classext_m_tbl(struct rb_id_table *orig, VALUE klass, bool init_missin return tbl; } -static struct rb_id_table * -duplicate_classext_id_table(struct rb_id_table *orig, bool init_missing) -{ - struct rb_id_table *tbl; - - if (!orig) { - if (init_missing) - return rb_id_table_create(0); - else - return NULL; - } - tbl = rb_id_table_create(rb_id_table_size(orig)); - rb_id_table_foreach(orig, duplicate_classext_id_table_i, tbl); - return tbl; -} - static rb_const_entry_t * duplicate_classext_const_entry(rb_const_entry_t *src, VALUE klass) { @@ -179,73 +270,13 @@ duplicate_classext_const_tbl(struct rb_id_table *src, VALUE klass) return dst; } -static VALUE -namespace_subclasses_tbl_key(const rb_namespace_t *ns) -{ - if (!ns){ - return 0; - } - return (VALUE)ns->ns_id; -} - -static void -duplicate_classext_subclasses(rb_classext_t *orig, rb_classext_t *copy) -{ - rb_subclass_anchor_t *anchor, *orig_anchor; - rb_subclass_entry_t *head, *cur, *cdr, *entry, *first = NULL; - rb_ns_subclasses_t *ns_subclasses; - struct st_table *tbl; - - if (RCLASSEXT_SUBCLASSES(orig)) { - orig_anchor = RCLASSEXT_SUBCLASSES(orig); - ns_subclasses = orig_anchor->ns_subclasses; - tbl = ((rb_ns_subclasses_t *)ns_subclasses)->tbl; - - anchor = ZALLOC(rb_subclass_anchor_t); - anchor->ns_subclasses = rb_ns_subclasses_ref_inc(ns_subclasses); - - head = ZALLOC(rb_subclass_entry_t); - anchor->head = head; - - RCLASSEXT_SUBCLASSES(copy) = anchor; - - cur = head; - entry = orig_anchor->head; - RUBY_ASSERT(!entry->klass); - // The head entry has NULL klass always. See rb_class_foreach_subclass(). - entry = entry->next; - while (entry) { - if (rb_objspace_garbage_object_p(entry->klass)) { - entry = entry->next; - continue; - } - cdr = ZALLOC(rb_subclass_entry_t); - cdr->klass = entry->klass; - cdr->prev = cur; - cur->next = cdr; - if (!first) { - VALUE ns_id = namespace_subclasses_tbl_key(RCLASSEXT_NS(copy)); - first = cdr; - st_insert(tbl, ns_id, (st_data_t)first); - } - cur = cdr; - entry = entry->next; - } - } - - if (RCLASSEXT_NS_SUPER_SUBCLASSES(orig)) - RCLASSEXT_NS_SUPER_SUBCLASSES(copy) = rb_ns_subclasses_ref_inc(RCLASSEXT_NS_SUPER_SUBCLASSES(orig)); - if (RCLASSEXT_NS_MODULE_SUBCLASSES(orig)) - RCLASSEXT_NS_MODULE_SUBCLASSES(copy) = rb_ns_subclasses_ref_inc(RCLASSEXT_NS_MODULE_SUBCLASSES(orig)); -} - static void -class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_namespace_t *ns) +class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_box_t *box) { RUBY_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); rb_classext_t *src = RCLASS_EXT_PRIME(iclass); - rb_classext_t *ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(iclass, ns); + rb_classext_t *ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(iclass, box); int first_set = 0; if (ext) { @@ -255,7 +286,7 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n ext = ZALLOC(rb_classext_t); - RCLASSEXT_NS(ext) = ns; + RCLASSEXT_BOX(ext) = box; RCLASSEXT_SUPER(ext) = RCLASSEXT_SUPER(src); @@ -274,8 +305,7 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n // RCLASSEXT_CALLABLE_M_TBL(ext) = NULL; // RCLASSEXT_CC_TBL(ext) = NULL; - // subclasses, namespace_super_subclasses_tbl, namespace_module_subclasses_tbl - duplicate_classext_subclasses(src, ext); + // Subclasses/back-pointers are only in the prime classext. RCLASSEXT_SET_ORIGIN(ext, iclass, RCLASSEXT_ORIGIN(src)); RCLASSEXT_ICLASS_IS_ORIGIN(ext) = RCLASSEXT_ICLASS_IS_ORIGIN(src); @@ -283,25 +313,29 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n RCLASSEXT_SET_INCLUDER(ext, iclass, RCLASSEXT_INCLUDER(src)); - first_set = RCLASS_SET_NAMESPACE_CLASSEXT(iclass, ns, ext); + VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_BOXABLE)); + + first_set = RCLASS_SET_BOX_CLASSEXT(iclass, box, ext); if (first_set) { RCLASS_SET_PRIME_CLASSEXT_WRITABLE(iclass, false); } } rb_classext_t * -rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace_t *ns) +rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_box_t *box) { VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); rb_classext_t *ext = ZALLOC(rb_classext_t); bool dup_iclass = RB_TYPE_P(klass, T_MODULE) ? true : false; - RCLASSEXT_NS(ext) = ns; + RCLASSEXT_BOX(ext) = box; RCLASSEXT_SUPER(ext) = RCLASSEXT_SUPER(orig); RCLASSEXT_M_TBL(ext) = duplicate_classext_m_tbl(RCLASSEXT_M_TBL(orig), klass, dup_iclass); + RCLASSEXT_ICLASS_IS_ORIGIN(ext) = true; + RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext) = false; if (orig->fields_obj) { RB_OBJ_WRITE(klass, &ext->fields_obj, rb_imemo_fields_clone(orig->fields_obj)); @@ -320,19 +354,25 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace * so initially, it can be NULL and let it be created lazily. * RCLASSEXT_CALLABLE_M_TBL(ext) = NULL; * - * cc_tbl is for method inline cache, and method calls from different namespaces never occur on + * cc_tbl is for method inline cache, and method calls from different boxes never occur on * the same code, so the copied classext should have a different cc_tbl from the prime one. * RCLASSEXT_CC_TBL(copy) = NULL */ - RCLASSEXT_CVC_TBL(ext) = duplicate_classext_id_table(RCLASSEXT_CVC_TBL(orig), dup_iclass); + VALUE cvc_table = RCLASSEXT_CVC_TBL(orig); + if (cvc_table) { + cvc_table = rb_marked_id_table_dup(cvc_table); + } + else if (dup_iclass) { + cvc_table = rb_marked_id_table_new(2); + } + RB_OBJ_WRITE(klass, &RCLASSEXT_CVC_TBL(ext), cvc_table); - // subclasses, subclasses_index - duplicate_classext_subclasses(orig, ext); + // Subclasses/back-pointers are only in the prime classext. RCLASSEXT_SET_ORIGIN(ext, klass, RCLASSEXT_ORIGIN(orig)); /* - * Members not copied to namespace classext values + * Members not copied to box's classext values * * refined_class * * as.class.allocator / as.singleton_class.attached_object * * includer @@ -340,30 +380,37 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace * * variation count */ RCLASSEXT_PERMANENT_CLASSPATH(ext) = RCLASSEXT_PERMANENT_CLASSPATH(orig); - RCLASSEXT_CLONED(ext) = RCLASSEXT_CLONED(orig); RCLASSEXT_CLASSPATH(ext) = RCLASSEXT_CLASSPATH(orig); /* For the usual T_CLASS/T_MODULE, iclass flags are always false */ if (dup_iclass) { - VALUE iclass; /* * ICLASS has the same m_tbl/const_tbl/cvc_tbl with the included module. * So the module's classext is copied, its tables should be also referred - * by the ICLASS's classext for the namespace. + * by the ICLASS's classext for the box. + * + * Subclasses are only in the prime classext, so read from orig. */ - rb_subclass_anchor_t *anchor = RCLASSEXT_SUBCLASSES(ext); - rb_subclass_entry_t *subclass_entry = anchor->head; - while (subclass_entry) { - if (subclass_entry->klass && RB_TYPE_P(subclass_entry->klass, T_ICLASS)) { - iclass = subclass_entry->klass; - if (RBASIC_CLASS(iclass) == klass) { - // Is the subclass an ICLASS including this module into another class - // If so we need to re-associate it under our namespace with the new ext - class_duplicate_iclass_classext(iclass, ext, ns); + VALUE subs_v = RCLASSEXT_SUBCLASSES(orig); + if (subs_v) { + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + VALUE iclass = entries[i]; + if (!iclass) continue; + + /* every node in the subclass list should be an ICLASS built from this module */ + VM_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); + VM_ASSERT(RBASIC_CLASS(iclass) == klass); + + if (FL_TEST_RAW(iclass, RCLASS_BOXABLE)) { + // Non-boxable ICLASSes (included by classes in main/user boxes) can't + // hold per-box classexts, and their includer classes also can't, so + // method lookup through them always uses the prime classext. + class_duplicate_iclass_classext(iclass, ext, box); } } - subclass_entry = subclass_entry->next; } } @@ -423,40 +470,45 @@ rb_class_variation_count(VALUE klass) } static void -push_subclass_entry_to_list(VALUE super, VALUE klass, bool is_module) +push_subclass_entry_to_list(VALUE super, VALUE klass) { - rb_subclass_entry_t *entry, *head; - rb_subclass_anchor_t *anchor; - rb_ns_subclasses_t *ns_subclasses; - struct st_table *tbl; - const rb_namespace_t *ns = rb_current_namespace(); - - entry = ZALLOC(rb_subclass_entry_t); - entry->klass = klass; + RUBY_ASSERT( + (RB_TYPE_P(super, T_MODULE) && RB_TYPE_P(klass, T_ICLASS)) || + (RB_TYPE_P(super, T_CLASS) && RB_TYPE_P(klass, T_CLASS)) || + (RB_TYPE_P(klass, T_ICLASS) && !NIL_P(RCLASS_REFINED_CLASS(klass))) + ); RB_VM_LOCKING() { - anchor = RCLASS_WRITABLE_SUBCLASSES(super); - VM_ASSERT(anchor); - ns_subclasses = (rb_ns_subclasses_t *)anchor->ns_subclasses; - VM_ASSERT(ns_subclasses); - tbl = ns_subclasses->tbl; - VM_ASSERT(tbl); - - head = anchor->head; - if (head->next) { - head->next->prev = entry; - entry->next = head->next; + VALUE subs_v = RCLASS_SUBCLASSES(super); + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + + if (!subs || subs->count == subs->capacity) { + VALUE *old_entries = subs ? rb_imemo_subclasses_entries(subs_v) : NULL; + uint32_t live = 0; + for (uint32_t i = 0; subs && i < subs->count; i++) { + if (old_entries[i]) live++; + } + + uint32_t cap = subs ? subs->capacity : 2; + if (live * 2 >= cap) cap *= 2; + + VALUE new_v = rb_imemo_subclasses_new(cap); + struct rb_subclasses *new_subs = (struct rb_subclasses *)new_v; + VALUE *new_entries = rb_imemo_subclasses_entries(new_v); + for (uint32_t i = 0; subs && i < subs->count; i++) { + VALUE entry = old_entries[i]; + if (entry) { + new_entries[new_subs->count++] = entry; + RB_OBJ_WRITTEN(new_v, Qundef, entry); + } + } + RCLASS_SET_SUBCLASSES(super, new_v); + subs_v = new_v; + subs = new_subs; } - head->next = entry; - entry->prev = head; - st_insert(tbl, namespace_subclasses_tbl_key(ns), (st_data_t)entry); - } - if (is_module) { - RCLASS_WRITE_NS_MODULE_SUBCLASSES(klass, anchor->ns_subclasses); - } - else { - RCLASS_WRITE_NS_SUPER_SUBCLASSES(klass, anchor->ns_subclasses); + rb_imemo_subclasses_entries(subs_v)[subs->count++] = klass; + RB_OBJ_WRITTEN(subs_v, Qundef, klass); } } @@ -464,7 +516,9 @@ void rb_class_subclass_add(VALUE super, VALUE klass) { if (super && !UNDEF_P(super)) { - push_subclass_entry_to_list(super, klass, false); + RUBY_ASSERT(RB_TYPE_P(super, T_CLASS) || RB_TYPE_P(super, T_MODULE)); + RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_ICLASS)); + push_subclass_entry_to_list(super, klass); } } @@ -472,164 +526,33 @@ static void rb_module_add_to_subclasses_list(VALUE module, VALUE iclass) { if (module && !UNDEF_P(module)) { - push_subclass_entry_to_list(module, iclass, true); + RUBY_ASSERT(RB_TYPE_P(module, T_MODULE)); + RUBY_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); + push_subclass_entry_to_list(module, iclass); } } void -rb_class_remove_subclass_head(VALUE klass) // TODO: check this is still used and required -{ - rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - rb_class_classext_free_subclasses(ext, klass); -} - -static struct rb_subclass_entry * -class_get_subclasses_for_ns(struct st_table *tbl, VALUE ns_id) -{ - st_data_t value; - if (st_lookup(tbl, (st_data_t)ns_id, &value)) { - return (struct rb_subclass_entry *)value; - } - return NULL; -} - -static void -remove_class_from_subclasses(struct st_table *tbl, VALUE ns_id, VALUE klass) +rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg) { - rb_subclass_entry_t *entry = class_get_subclasses_for_ns(tbl, ns_id); - bool first_entry = true; - while (entry) { - if (entry->klass == klass) { - rb_subclass_entry_t *prev = entry->prev, *next = entry->next; + VALUE subs_v = RCLASS_SUBCLASSES(klass); + if (!subs_v) return; - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; - } - - xfree(entry); - - if (first_entry) { - if (next) { - st_insert(tbl, ns_id, (st_data_t)next); - } - else { - // no subclass entries in this ns - st_delete(tbl, &ns_id, NULL); - } - } - break; + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + VALUE curklass = entries[i]; + if (curklass) { + f(curklass, arg); } - else if (first_entry) { - first_entry = false; - } - entry = entry->next; - } -} - -void -rb_class_remove_from_super_subclasses(VALUE klass) -{ - rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - rb_ns_subclasses_t *ns_subclasses = RCLASSEXT_NS_SUPER_SUBCLASSES(ext); - - if (!ns_subclasses) return; - remove_class_from_subclasses(ns_subclasses->tbl, namespace_subclasses_tbl_key(RCLASSEXT_NS(ext)), klass); - rb_ns_subclasses_ref_dec(ns_subclasses); - RCLASSEXT_NS_SUPER_SUBCLASSES(ext) = 0; -} - -void -rb_class_remove_from_module_subclasses(VALUE klass) -{ - rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - rb_ns_subclasses_t *ns_subclasses = RCLASSEXT_NS_MODULE_SUBCLASSES(ext); - - if (!ns_subclasses) return; - remove_class_from_subclasses(ns_subclasses->tbl, namespace_subclasses_tbl_key(RCLASSEXT_NS(ext)), klass); - rb_ns_subclasses_ref_dec(ns_subclasses); - RCLASSEXT_NS_MODULE_SUBCLASSES(ext) = 0; -} - -void -rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass) -{ - rb_subclass_anchor_t *anchor = RCLASSEXT_SUBCLASSES(ext); - struct st_table *tbl = anchor->ns_subclasses->tbl; - VALUE ns_id = namespace_subclasses_tbl_key(RCLASSEXT_NS(ext)); - rb_subclass_entry_t *next, *entry = anchor->head; - - while (entry) { - next = entry->next; - xfree(entry); - entry = next; - } - VM_ASSERT( - rb_ns_subclasses_ref_count(anchor->ns_subclasses) > 0, - "ns_subclasses refcount (%p) %ld", anchor->ns_subclasses, rb_ns_subclasses_ref_count(anchor->ns_subclasses)); - st_delete(tbl, &ns_id, NULL); - rb_ns_subclasses_ref_dec(anchor->ns_subclasses); - xfree(anchor); - - if (RCLASSEXT_NS_SUPER_SUBCLASSES(ext)) { - rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_SUPER_SUBCLASSES(ext); - remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); - rb_ns_subclasses_ref_dec(ns_sub); - } - if (RCLASSEXT_NS_MODULE_SUBCLASSES(ext)) { - rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_MODULE_SUBCLASSES(ext); - remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); - rb_ns_subclasses_ref_dec(ns_sub); - } -} - -void -rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg) -{ - rb_subclass_entry_t *tmp; - rb_subclass_entry_t *cur = RCLASS_SUBCLASSES_FIRST(klass); - /* do not be tempted to simplify this loop into a for loop, the order of - operations is important here if `f` modifies the linked list */ - while (cur) { - VALUE curklass = cur->klass; - tmp = cur->next; - // do not trigger GC during f, otherwise the cur will become - // a dangling pointer if the subclass is collected - f(curklass, arg); - cur = tmp; } } static void -class_detach_subclasses(VALUE klass, VALUE arg) -{ - rb_class_remove_from_super_subclasses(klass); -} - -void -rb_class_detach_subclasses(VALUE klass) -{ - rb_class_foreach_subclass(klass, class_detach_subclasses, Qnil); -} - -static void -class_detach_module_subclasses(VALUE klass, VALUE arg) -{ - rb_class_remove_from_module_subclasses(klass); -} - -void -rb_class_detach_module_subclasses(VALUE klass) -{ - rb_class_foreach_subclass(klass, class_detach_module_subclasses, Qnil); -} - -static void class_switch_superclass(VALUE super, VALUE klass) { - class_detach_subclasses(klass, Qnil); + // No need to remove from old super's subclasses list — the GC + // will nullify the weak reference when appropriate. rb_class_subclass_add(super, klass); } @@ -644,41 +567,35 @@ class_switch_superclass(VALUE super, VALUE klass) * @note this function is not Class#allocate. */ static VALUE -class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) +class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) { - rb_ns_subclasses_t *ns_subclasses; - rb_subclass_anchor_t *anchor; - const rb_namespace_t *ns = rb_definition_namespace(); + const rb_box_t *box = rb_current_box(); - if (!ruby_namespace_init_done) { - namespaceable = true; + if (!ruby_box_init_done) { + boxable = true; } size_t alloc_size = sizeof(struct RClass_and_rb_classext_t); - if (namespaceable) { - alloc_size = sizeof(struct RClass_namespaceable); - } - - // class_alloc is supposed to return a new object that is not promoted yet. - // So, we need to avoid GC after NEWOBJ_OF. - // To achieve that, we allocate subclass lists before NEWOBJ_OF. - // - // TODO: Note that this could cause memory leak. - // If NEWOBJ_OF fails with out of memory, these buffers will leak. - ns_subclasses = ZALLOC(rb_ns_subclasses_t); - ns_subclasses->refcount = 1; - ns_subclasses->tbl = st_init_numtable(); - anchor = ZALLOC(rb_subclass_anchor_t); - anchor->ns_subclasses = ns_subclasses; - anchor->head = ZALLOC(rb_subclass_entry_t); + if (boxable) { + alloc_size = sizeof(struct RClass_boxable); + } RUBY_ASSERT(type == T_CLASS || type == T_ICLASS || type == T_MODULE); - VALUE flags = type; - if (RGENGC_WB_PROTECTED_CLASS) flags |= FL_WB_PROTECTED; - if (namespaceable) flags |= RCLASS_NAMESPACEABLE; + 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, 0); + obj->object_id = 0; memset(RCLASS_EXT_PRIME(obj), 0, sizeof(rb_classext_t)); @@ -689,30 +606,46 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) RCLASS_SET_SUPER((VALUE)obj, 0); */ - RCLASS_PRIME_NS((VALUE)obj) = ns; - // Classes/Modules defined in user namespaces are - // writable directly because it exists only in a namespace. - RCLASS_SET_PRIME_CLASSEXT_WRITABLE((VALUE)obj, !namespaceable || NAMESPACE_USER_P(ns)); + if (boxable) { + ((struct RClass_boxable *)obj)->box_classext_tbl = NULL; + } + + RCLASS_PRIME_BOX((VALUE)obj) = box; + // Classes/Modules defined in user boxes are + // writable directly because it exists only in a box. + RCLASS_SET_PRIME_CLASSEXT_WRITABLE((VALUE)obj, !boxable || BOX_USER_P(box)); RCLASS_SET_ORIGIN((VALUE)obj, (VALUE)obj); RCLASS_SET_REFINED_CLASS((VALUE)obj, Qnil); - RCLASS_SET_SUBCLASSES((VALUE)obj, anchor); - return (VALUE)obj; } static VALUE class_alloc(enum ruby_value_type type, VALUE klass) { - return class_alloc0(type, klass, false); + bool boxable = rb_box_available() && BOX_MASTER_P(rb_current_box()); + return class_alloc0(type, klass, boxable); } static VALUE class_associate_super(VALUE klass, VALUE super, bool init) { if (super && !UNDEF_P(super)) { - class_switch_superclass(super, klass); + // Only maintain subclass lists for T_CLASS→T_CLASS relationships. + // Include/prepend inserts ICLASSes into the super chain, but T_CLASS + // subclass lists should track only the immutable T_CLASS→T_CLASS link. + if (RB_TYPE_P(klass, T_CLASS) && RB_TYPE_P(super, T_CLASS)) { + if (RCLASS_SINGLETON_P(klass)) { + // Instead of adding singleton classes to the subclass list, + // just set a flag so that method cache invalidation takes the + // tree path. + FL_SET_RAW(super, RCLASS_HAS_SUBCLASSES); + } + else { + class_switch_superclass(super, klass); + } + } } if (init) { RCLASS_SET_SUPER(klass, super); @@ -744,9 +677,9 @@ class_clear_method_table(VALUE c) } static VALUE -class_boot_namespaceable(VALUE super, bool namespaceable) +class_boot_boxable(VALUE super, bool boxable) { - VALUE klass = class_alloc0(T_CLASS, rb_cClass, namespaceable); + VALUE klass = class_alloc0(T_CLASS, rb_cClass, boxable); // initialize method table prior to class_associate_super() // because class_associate_super() may cause GC and promote klass @@ -754,6 +687,7 @@ class_boot_namespaceable(VALUE super, bool namespaceable) class_associate_super(klass, super, true); if (super && !UNDEF_P(super)) { + RCLASS_SET_ALLOCATOR(klass, RCLASS_ALLOCATOR(super)); rb_class_set_initialized(klass); } @@ -772,7 +706,7 @@ class_boot_namespaceable(VALUE super, bool namespaceable) VALUE rb_class_boot(VALUE super) { - return class_boot_namespaceable(super, false); + return class_boot_boxable(super, false); } static VALUE * @@ -858,11 +792,8 @@ rb_class_new(VALUE super) rb_check_inheritable(super); VALUE klass = rb_class_boot(super); - if (super != rb_cObject && super != rb_cBasicObject) { - RCLASS_SET_MAX_IV_COUNT(klass, RCLASS_MAX_IV_COUNT(super)); - } - - RUBY_ASSERT(getenv("RUBY_NAMESPACE") || RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)); + RCLASS_SET_MAX_IV_COUNT(klass, RCLASS_MAX_IV_COUNT(super)); + RUBY_ASSERT(getenv("RUBY_BOX") || RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)); return klass; } @@ -874,27 +805,20 @@ rb_class_s_alloc(VALUE klass) } static void -clone_method(VALUE old_klass, VALUE new_klass, ID mid, const rb_method_entry_t *me) +clone_method(VALUE new_klass, ID mid, const rb_method_entry_t *me) { - if (me->def->type == VM_METHOD_TYPE_ISEQ) { - rb_cref_t *new_cref = rb_vm_rewrite_cref(me->def->body.iseq.cref, old_klass, new_klass); - rb_add_method_iseq(new_klass, mid, me->def->body.iseq.iseqptr, new_cref, METHOD_ENTRY_VISI(me)); - } - else { - rb_method_entry_set(new_klass, mid, me, METHOD_ENTRY_VISI(me)); - } + rb_method_entry_set(new_klass, mid, me, METHOD_ENTRY_VISI(me)); } struct clone_method_arg { VALUE new_klass; - VALUE old_klass; }; static enum rb_id_table_iterator_result clone_method_i(ID key, VALUE value, void *data) { const struct clone_method_arg *arg = (struct clone_method_arg *)data; - clone_method(arg->old_klass, arg->new_klass, key, (const rb_method_entry_t *)value); + clone_method(arg->new_klass, key, (const rb_method_entry_t *)value); return ID_TABLE_CONTINUE; } @@ -937,9 +861,15 @@ class_init_copy_check(VALUE clone, VALUE orig) struct cvc_table_copy_ctx { VALUE clone; - struct rb_id_table * new_table; + VALUE new_table; }; +static struct rb_cvar_class_tbl_entry * +cvc_table_entry_alloc(void) +{ + return (struct rb_cvar_class_tbl_entry *)SHAREABLE_IMEMO_NEW(struct rb_cvar_class_tbl_entry, imemo_cvar_entry, 0); +} + static enum rb_id_table_iterator_result cvc_table_copy(ID id, VALUE val, void *data) { @@ -949,13 +879,11 @@ cvc_table_copy(ID id, VALUE val, void *data) struct rb_cvar_class_tbl_entry *ent; - ent = ALLOC(struct rb_cvar_class_tbl_entry); - ent->class_value = ctx->clone; - ent->cref = orig_entry->cref; + ent = cvc_table_entry_alloc(); + RB_OBJ_WRITE((VALUE)ent, &ent->class_value, ctx->clone); + RB_OBJ_WRITE(ctx->clone, &ent->cref, orig_entry->cref); ent->global_cvar_state = orig_entry->global_cvar_state; - rb_id_table_insert(ctx->new_table, id, (VALUE)ent); - - RB_OBJ_WRITTEN(ctx->clone, Qundef, ent->cref); + rb_marked_id_table_insert(ctx->new_table, id, (VALUE)ent); return ID_TABLE_CONTINUE; } @@ -968,13 +896,13 @@ copy_tables(VALUE clone, VALUE orig) RCLASS_WRITE_CONST_TBL(clone, 0, false); } if (RCLASS_CVC_TBL(orig)) { - struct rb_id_table *rb_cvc_tbl = RCLASS_CVC_TBL(orig); - struct rb_id_table *rb_cvc_tbl_dup = rb_id_table_create(rb_id_table_size(rb_cvc_tbl)); + VALUE rb_cvc_tbl = RCLASS_CVC_TBL(orig); + VALUE rb_cvc_tbl_dup = rb_marked_id_table_new(rb_marked_id_table_size(rb_cvc_tbl)); struct cvc_table_copy_ctx ctx; ctx.clone = clone; ctx.new_table = rb_cvc_tbl_dup; - rb_id_table_foreach(rb_cvc_tbl, cvc_table_copy, &ctx); + rb_marked_id_table_foreach(rb_cvc_tbl, cvc_table_copy, &ctx); RCLASS_WRITE_CVC_TBL(clone, rb_cvc_tbl_dup); } rb_id_table_free(RCLASS_M_TBL(clone)); @@ -990,6 +918,7 @@ copy_tables(VALUE clone, VALUE orig) arg.klass = clone; rb_id_table_foreach(orig_tbl, clone_const_i, &arg); RCLASS_WRITE_CONST_TBL(clone, const_tbl, false); + rb_gc_writebarrier_remember(clone); } } @@ -1035,12 +964,6 @@ rb_mod_init_copy(VALUE clone, VALUE orig) rb_class_set_initialized(clone); - /* cloned flag is refer at constant inline cache - * see vm_get_const_key_cref() in vm_insnhelper.c - */ - RCLASS_SET_CLONED(clone, true); - RCLASS_SET_CLONED(orig, true); - if (!RCLASS_SINGLETON_P(CLASS_OF(clone))) { RBASIC_SET_CLASS(clone, rb_singleton_class_clone(orig)); rb_singleton_class_attached(METACLASS_OF(clone), (VALUE)clone); @@ -1051,7 +974,6 @@ rb_mod_init_copy(VALUE clone, VALUE orig) copy_tables(clone, orig); if (RCLASS_M_TBL(orig)) { struct clone_method_arg arg; - arg.old_klass = orig; arg.new_klass = clone; class_initialize_method_table(clone); rb_id_table_foreach(RCLASS_M_TBL(orig), clone_method_i, &arg); @@ -1113,7 +1035,6 @@ rb_mod_init_copy(VALUE clone, VALUE orig) copy_tables(clone_origin, orig_origin); if (RCLASS_M_TBL(orig_origin)) { struct clone_method_arg arg; - arg.old_klass = orig; arg.new_klass = clone; class_initialize_method_table(clone_origin); rb_id_table_foreach(RCLASS_M_TBL(orig_origin), clone_method_i, &arg); @@ -1175,7 +1096,7 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) if (RCLASS_CONST_TBL(klass)) { struct clone_const_arg arg; struct rb_id_table *table; - arg.tbl = table = rb_id_table_create(0); + arg.tbl = table = rb_id_table_create(rb_id_table_size(RCLASS_CONST_TBL(klass))); arg.klass = clone; rb_id_table_foreach(RCLASS_CONST_TBL(klass), clone_const_i, &arg); RCLASS_SET_CONST_TBL(clone, table, false); @@ -1185,7 +1106,6 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) } { struct clone_method_arg arg; - arg.old_klass = klass; arg.new_klass = clone; rb_id_table_foreach(RCLASS_M_TBL(klass), clone_method_i, &arg); } @@ -1259,7 +1179,7 @@ static inline VALUE make_metaclass(VALUE klass) { VALUE super; - VALUE metaclass = class_boot_namespaceable(Qundef, FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE)); + VALUE metaclass = class_boot_boxable(Qundef, FL_TEST_RAW(klass, RCLASS_BOXABLE)); FL_SET(metaclass, FL_SINGLETON); rb_singleton_class_attached(metaclass, klass); @@ -1295,12 +1215,18 @@ static inline VALUE make_singleton_class(VALUE obj) { VALUE orig_class = METACLASS_OF(obj); - VALUE klass = class_boot_namespaceable(orig_class, FL_TEST_RAW(orig_class, RCLASS_NAMESPACEABLE)); - + VALUE klass = class_alloc0(T_CLASS, rb_cClass, FL_TEST_RAW(orig_class, RCLASS_BOXABLE)); FL_SET(klass, FL_SINGLETON); + class_initialize_method_table(klass); + class_associate_super(klass, orig_class, true); + if (orig_class && !UNDEF_P(orig_class)) { + rb_class_set_initialized(klass); + } + RBASIC_SET_CLASS(obj, klass); rb_singleton_class_attached(klass, obj); rb_yjit_invalidate_no_singleton_class(orig_class); + rb_zjit_invalidate_no_singleton_class(orig_class); SET_METACLASS_OF(klass, METACLASS_OF(rb_class_real(orig_class))); return klass; @@ -1395,8 +1321,12 @@ void Init_class_hierarchy(void) { rb_cBasicObject = boot_defclass("BasicObject", 0); + RCLASS_SET_ALLOCATOR(rb_cBasicObject, rb_class_allocate_instance); + FL_SET_RAW(rb_cBasicObject, RCLASS_ALLOCATOR_DEFINED); + RCLASS_SET_EXPECT_NO_IVAR(rb_cBasicObject); + rb_cObject = boot_defclass("Object", rb_cBasicObject); - rb_vm_register_global_object(rb_cObject); + RCLASS_SET_EXPECT_NO_IVAR(rb_cObject); /* resolve class name ASAP for order-independence */ rb_set_class_path_string(rb_cObject, rb_cObject, rb_fstring_lit("Object")); @@ -1476,13 +1406,8 @@ VALUE rb_define_class(const char *name, VALUE super) { VALUE klass; - ID id; - const rb_namespace_t *ns = rb_current_namespace(); + ID id = rb_intern(name); - id = rb_intern(name); - if (NAMESPACE_OPTIONAL_P(ns)) { - return rb_define_class_id_under(ns->ns_object, id, super); - } if (rb_const_defined(rb_cObject, id)) { klass = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(klass, T_CLASS)) { @@ -1594,13 +1519,8 @@ VALUE rb_define_module(const char *name) { VALUE module; - ID id; - const rb_namespace_t *ns = rb_current_namespace(); + ID id = rb_intern(name); - id = rb_intern(name); - if (NAMESPACE_OPTIONAL_P(ns)) { - return rb_define_module_id_under(ns->ns_object, id); - } if (rb_const_defined(rb_cObject, id)) { module = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(module, T_MODULE)) { @@ -1701,29 +1621,34 @@ rb_include_module(VALUE klass, VALUE module) rb_raise(rb_eArgError, "cyclic include detected"); if (RB_TYPE_P(klass, T_MODULE)) { - rb_subclass_entry_t *iclass = RCLASS_SUBCLASSES_FIRST(klass); - while (iclass) { - int do_include = 1; - VALUE check_class = iclass->klass; - /* During lazy sweeping, iclass->klass could be a dead object that - * has not yet been swept. */ - if (!rb_objspace_garbage_object_p(check_class)) { - while (check_class) { - RUBY_ASSERT(!rb_objspace_garbage_object_p(check_class)); - - if (RB_TYPE_P(check_class, T_ICLASS) && - (METACLASS_OF(check_class) == module)) { - do_include = 0; + VALUE subs_v = RCLASS_SUBCLASSES(klass); + if (subs_v) { + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + VALUE check_class = entries[i]; + if (!check_class) continue; + + int do_include = 1; + /* During lazy sweeping, the entry could be a dead object that + * has not yet been swept. */ + if (!rb_objspace_garbage_object_p(check_class)) { + VALUE walk = check_class; + while (walk) { + RUBY_ASSERT(!rb_objspace_garbage_object_p(walk)); + + if (RB_TYPE_P(walk, T_ICLASS) && + (METACLASS_OF(walk) == module)) { + do_include = 0; + } + walk = RCLASS_SUPER(walk); } - check_class = RCLASS_SUPER(check_class); - } - if (do_include) { - include_modules_at(iclass->klass, RCLASS_ORIGIN(iclass->klass), module, TRUE); + if (do_include) { + include_modules_at(check_class, RCLASS_ORIGIN(check_class), module, TRUE); + } } } - - iclass = iclass->next; } } } @@ -1956,29 +1881,32 @@ rb_prepend_module(VALUE klass, VALUE module) rb_vm_check_redefinition_by_prepend(klass); } if (RB_TYPE_P(klass, T_MODULE)) { - rb_subclass_entry_t *iclass = RCLASS_SUBCLASSES_FIRST(klass); + VALUE subs_v = RCLASS_SUBCLASSES(klass); VALUE klass_origin = RCLASS_ORIGIN(klass); struct rb_id_table *klass_m_tbl = RCLASS_M_TBL(klass); struct rb_id_table *klass_origin_m_tbl = RCLASS_M_TBL(klass_origin); - while (iclass) { - /* During lazy sweeping, iclass->klass could be a dead object that - * has not yet been swept. */ - if (!rb_objspace_garbage_object_p(iclass->klass)) { - const VALUE subclass = iclass->klass; - if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(subclass)) { - // backfill an origin iclass to handle refinements and future prepends - rb_id_table_foreach(RCLASS_M_TBL(subclass), clear_module_cache_i, (void *)subclass); - RCLASS_WRITE_M_TBL(subclass, klass_m_tbl); - VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(subclass)); - rb_class_set_super(subclass, origin); - RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(subclass)); - RCLASS_WRITE_ORIGIN(subclass, origin); - RICLASS_SET_ORIGIN_SHARED_MTBL(origin); + if (subs_v) { + struct rb_subclasses *subs = (struct rb_subclasses *)subs_v; + VALUE *entries = rb_imemo_subclasses_entries(subs_v); + for (uint32_t i = 0; i < subs->count; i++) { + const VALUE subclass = entries[i]; + if (!subclass) continue; + /* During lazy sweeping, the entry could be a dead object that + * has not yet been swept. */ + if (!rb_objspace_garbage_object_p(subclass)) { + if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(subclass)) { + // backfill an origin iclass to handle refinements and future prepends + rb_id_table_foreach(RCLASS_M_TBL(subclass), clear_module_cache_i, (void *)subclass); + RCLASS_WRITE_M_TBL(subclass, klass_m_tbl); + VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(subclass)); + rb_class_set_super(subclass, origin); + RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(subclass)); + RCLASS_WRITE_ORIGIN(subclass, origin); + RICLASS_SET_ORIGIN_SHARED_MTBL(origin); + } + include_modules_at(subclass, subclass, module, FALSE); } - include_modules_at(subclass, subclass, module, FALSE); } - - iclass = iclass->next; } } } @@ -2108,19 +2036,17 @@ class_descendants_recursive(VALUE klass, VALUE v) { struct subclass_traverse_data *data = (struct subclass_traverse_data *) v; - if (BUILTIN_TYPE(klass) == T_CLASS && !RCLASS_SINGLETON_P(klass)) { + if (RB_TYPE_P(klass, T_ICLASS)) return; // skip refinement ICLASSes + + if (!RCLASS_SINGLETON_P(klass)) { if (data->buffer && data->count < data->maxcount && !rb_objspace_garbage_object_p(klass)) { // assumes that this does not cause GC as long as the length does not exceed the capacity rb_ary_push(data->buffer, klass); } data->count++; - if (!data->immediate_only) { - rb_class_foreach_subclass(klass, class_descendants_recursive, v); - } - } - else { - rb_class_foreach_subclass(klass, class_descendants_recursive, v); + if (data->immediate_only) return; } + rb_class_foreach_subclass(klass, class_descendants_recursive, v); } static VALUE @@ -2593,7 +2519,7 @@ rb_obj_singleton_methods(int argc, const VALUE *argv, VALUE obj) int recur = TRUE; if (rb_check_arity(argc, 0, 1)) recur = RTEST(argv[0]); - if (RCLASS_SINGLETON_P(obj)) { + if (RB_TYPE_P(obj, T_CLASS) && RCLASS_SINGLETON_P(obj)) { rb_singleton_class(obj); } klass = CLASS_OF(obj); @@ -2719,7 +2645,7 @@ rb_special_singleton_class(VALUE obj) * consistency of the metaclass hierarchy. */ static VALUE -singleton_class_of(VALUE obj) +singleton_class_of(VALUE obj, bool ensure_eigenclass) { VALUE klass; @@ -2747,28 +2673,43 @@ singleton_class_of(VALUE obj) } } - klass = METACLASS_OF(obj); - if (!(RCLASS_SINGLETON_P(klass) && - RCLASS_ATTACHED_OBJECT(klass) == obj)) { - klass = rb_make_metaclass(obj, klass); + bool needs_lock = rb_multi_ractor_p() && rb_ractor_shareable_p(obj); + unsigned int lev; + if (needs_lock) { + RB_VM_LOCK_ENTER_LEV(&lev); + } + { + klass = METACLASS_OF(obj); + if (!(RCLASS_SINGLETON_P(klass) && + RCLASS_ATTACHED_OBJECT(klass) == obj)) { + klass = rb_make_metaclass(obj, klass); + } + RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); + if (ensure_eigenclass && RB_TYPE_P(obj, T_CLASS)) { + /* ensures an exposed class belongs to its own eigenclass */ + (void)ENSURE_EIGENCLASS(klass); + } + } + if (needs_lock) { + RB_VM_LOCK_LEAVE_LEV(&lev); } - - RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); return klass; } void -rb_freeze_singleton_class(VALUE x) -{ - /* should not propagate to meta-meta-class, and so on */ - if (!RCLASS_SINGLETON_P(x)) { - VALUE klass = RBASIC_CLASS(x); - if (klass && // no class when hidden from ObjectSpace - FL_TEST_RAW(klass, FL_SINGLETON) && - !OBJ_FROZEN_RAW(klass)) { - OBJ_FREEZE(klass); - } +rb_freeze_singleton_class(VALUE attached_object) +{ + VALUE klass; + + /* Freeze singleton classes of singleton class, as singleton class is frozen, and so on */ + /* In each iteration, check the current object's class pointer is the singleton class of the object. */ + while ((klass = RBASIC_CLASS(attached_object)) && + FL_TEST_RAW(klass, FL_SINGLETON) && + !OBJ_FROZEN_RAW(klass) && + (RCLASS_ATTACHED_OBJECT(klass) == attached_object)) { + attached_object = klass; + OBJ_FREEZE(attached_object); } } @@ -2796,12 +2737,7 @@ rb_singleton_class_get(VALUE obj) VALUE rb_singleton_class(VALUE obj) { - VALUE klass = singleton_class_of(obj); - - /* ensures an exposed class belongs to its own eigenclass */ - if (RB_TYPE_P(obj, T_CLASS)) (void)ENSURE_EIGENCLASS(klass); - - return klass; + return singleton_class_of(obj, true); } /*! @@ -2819,7 +2755,7 @@ rb_singleton_class(VALUE obj) void rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(ANYARGS), int argc) { - rb_define_method(singleton_class_of(obj), name, func, argc); + rb_define_method(singleton_class_of(obj, false), name, func, argc); } #ifdef rb_define_module_function |
