diff options
Diffstat (limited to 'class.c')
| -rw-r--r-- | class.c | 1076 |
1 files changed, 751 insertions, 325 deletions
@@ -21,6 +21,7 @@ #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" @@ -29,7 +30,9 @@ #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 * @@ -38,24 +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_SUPERCLASSES_INCLUDE_SELF - * The RCLASS_SUPERCLASSES contains the class as the last element. - * This means that this class owns the RCLASS_SUPERCLASSES list. - * if !SHAPE_IN_BASIC_FLAGS - * 4-19: SHAPE_FLAG_MASK - * Shape ID for the class. - * endif + * 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_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. */ /* Flags of T_ICLASS * - * 0: RICLASS_IS_ORIGIN - * 3: RICLASS_ORIGIN_SHARED_MTBL - * The T_ICLASS does not own the method table. - * if !SHAPE_IN_BASIC_FLAGS - * 4-19: SHAPE_FLAG_MASK - * Shape ID. This is set but not used. - * endif + * 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 @@ -63,209 +64,626 @@ * 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_ALLOCATED_BUT_NOT_INITIALIZED - * Module has not been initialized. - * 2: RCLASS_SUPERCLASSES_INCLUDE_SELF - * See RCLASS_SUPERCLASSES_INCLUDE_SELF in T_CLASS. - * 3: RMODULE_IS_REFINEMENT + * 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_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. + * 5: RMODULE_IS_REFINEMENT * Module is used for refinements. - * if !SHAPE_IN_BASIC_FLAGS - * 4-19: SHAPE_FLAG_MASK - * Shape ID for the module. - * endif */ #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; +} -static rb_subclass_entry_t * -push_subclass_entry_to_list(VALUE super, VALUE klass) +void +rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) { - rb_subclass_entry_t *entry, *head; + struct rb_id_table *tbl; - entry = ZALLOC(rb_subclass_entry_t); - entry->klass = klass; + rb_id_table_free(RCLASSEXT_M_TBL(ext)); - head = RCLASS_SUBCLASSES(super); - if (!head) { - head = ZALLOC(rb_subclass_entry_t); - RCLASS_SUBCLASSES(super) = head; + if (!RCLASSEXT_SHARED_CONST_TBL(ext) && (tbl = RCLASSEXT_CONST_TBL(ext)) != NULL) { + rb_free_const_table(tbl); } - entry->next = head->next; - entry->prev = head; - if (head->next) { - head->next->prev = entry; + 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); } - head->next = entry; - return entry; + if (!is_prime) { // the prime classext will be freed with RClass + SIZED_FREE(ext); + } } void -rb_class_subclass_add(VALUE super, VALUE klass) +rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) { - if (super && !UNDEF_P(super)) { - rb_subclass_entry_t *entry = push_subclass_entry_to_list(super, klass); - RCLASS_SUBCLASS_ENTRY(klass) = entry; + 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 -rb_module_add_to_subclasses_list(VALUE module, VALUE iclass) +iclass_free_orphan_classext(VALUE klass, rb_classext_t *ext) { - rb_subclass_entry_t *entry = push_subclass_entry_to_list(module, iclass); - RCLASS_MODULE_SUBCLASS_ENTRY(iclass) = entry; + 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); } -void -rb_class_remove_subclass_head(VALUE klass) +struct rb_class_set_box_classext_args { + VALUE obj; + rb_classext_t *ext; +}; + +static int +set_box_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t a, int existing) { - rb_subclass_entry_t *head = RCLASS_SUBCLASSES(klass); + struct rb_class_set_box_classext_args *args = (struct rb_class_set_box_classext_args *)a; - if (head) { - if (head->next) { - head->next->prev = NULL; + 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"); } - RCLASS_SUBCLASSES(klass) = NULL; - xfree(head); } + + *val_ptr = (st_data_t)args->ext; + + return ST_CONTINUE; } void -rb_class_remove_from_super_subclasses(VALUE klass) +rb_class_set_box_classext(VALUE obj, const rb_box_t *box, rb_classext_t *ext) { - rb_subclass_entry_t *entry = RCLASS_SUBCLASS_ENTRY(klass); + struct rb_class_set_box_classext_args args = { + .obj = obj, + .ext = ext, + }; - if (entry) { - rb_subclass_entry_t *prev = entry->prev, *next = entry->next; + VM_ASSERT(BOX_MUTABLE_P(box)); - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; - } + 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); +} - xfree(entry); +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) +{ + struct duplicate_id_tbl_data *arg = (struct duplicate_id_tbl_data *)data; + rb_method_entry_t *me = (rb_method_entry_t *)value; + rb_method_table_insert0(arg->klass, arg->tbl, key, me, false); + return ID_TABLE_CONTINUE; +} + +static struct rb_id_table * +duplicate_classext_m_tbl(struct rb_id_table *orig, VALUE klass, 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)); + struct duplicate_id_tbl_data data = { + .tbl = tbl, + .klass = klass, + }; + rb_id_table_foreach(orig, duplicate_classext_m_tbl_i, &data); + return tbl; +} + +static rb_const_entry_t * +duplicate_classext_const_entry(rb_const_entry_t *src, VALUE klass) +{ + // See also: setup_const_entry (variable.c) + rb_const_entry_t *dst = ZALLOC(rb_const_entry_t); - RCLASS_SUBCLASS_ENTRY(klass) = NULL; + dst->flag = src->flag; + dst->line = src->line; + RB_OBJ_WRITE(klass, &dst->value, src->value); + RB_OBJ_WRITE(klass, &dst->file, src->file); + + return dst; } -void -rb_class_remove_from_module_subclasses(VALUE klass) +static enum rb_id_table_iterator_result +duplicate_classext_const_tbl_i(ID key, VALUE value, void *data) { - rb_subclass_entry_t *entry = RCLASS_MODULE_SUBCLASS_ENTRY(klass); + struct duplicate_id_tbl_data *arg = (struct duplicate_id_tbl_data *)data; + rb_const_entry_t *entry = duplicate_classext_const_entry((rb_const_entry_t *)value, arg->klass); - if (entry) { - rb_subclass_entry_t *prev = entry->prev, *next = entry->next; + rb_id_table_insert(arg->tbl, key, (VALUE)entry); - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; - } + return ID_TABLE_CONTINUE; +} + +static struct rb_id_table * +duplicate_classext_const_tbl(struct rb_id_table *src, VALUE klass) +{ + struct rb_id_table *dst; + + if (!src) + return NULL; + + dst = rb_id_table_create(rb_id_table_size(src)); + + struct duplicate_id_tbl_data data = { + .tbl = dst, + .klass = klass, + }; + rb_id_table_foreach(src, duplicate_classext_const_tbl_i, (void *)&data); - xfree(entry); + return dst; +} + +static void +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, box); + int first_set = 0; + + if (ext) { + // iclass classext for the ns is only for cc/callable_m_tbl if it's created earlier than module's one + rb_invalidate_method_caches(RCLASSEXT_CALLABLE_M_TBL(ext), RCLASSEXT_CC_TBL(ext)); + } + + ext = ZALLOC(rb_classext_t); + + RCLASSEXT_BOX(ext) = box; + + RCLASSEXT_SUPER(ext) = RCLASSEXT_SUPER(src); + + // See also: rb_include_class_new() + if (RCLASSEXT_ICLASS_IS_ORIGIN(src) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(src)) { + RCLASSEXT_M_TBL(ext) = duplicate_classext_m_tbl(RCLASSEXT_M_TBL(src), iclass, true); + } + else { + RCLASSEXT_M_TBL(ext) = RCLASSEXT_M_TBL(mod_ext); } - RCLASS_MODULE_SUBCLASS_ENTRY(klass) = NULL; + RCLASSEXT_CONST_TBL(ext) = RCLASSEXT_CONST_TBL(mod_ext); + RCLASSEXT_CVC_TBL(ext) = RCLASSEXT_CVC_TBL(mod_ext); + + // Those are cache and should be recreated when methods are called + // RCLASSEXT_CALLABLE_M_TBL(ext) = NULL; + // RCLASSEXT_CC_TBL(ext) = NULL; + + // 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); + RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext) = RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(src); + + RCLASSEXT_SET_INCLUDER(ext, iclass, RCLASSEXT_INCLUDER(src)); + + 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); + } } -void -rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg) +rb_classext_t * +rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_box_t *box) { - // RCLASS_SUBCLASSES should always point to our head element which has NULL klass - rb_subclass_entry_t *cur = RCLASS_SUBCLASSES(klass); - // if we have a subclasses list, then the head is a placeholder with no valid - // class. So ignore it and use the next element in the list (if one exists) - if (cur) { - RUBY_ASSERT(!cur->klass); - cur = cur->next; + 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_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)); + } + + if (RCLASSEXT_SHARED_CONST_TBL(orig)) { + RCLASSEXT_CONST_TBL(ext) = RCLASSEXT_CONST_TBL(orig); + RCLASSEXT_SHARED_CONST_TBL(ext) = true; } + else { + RCLASSEXT_CONST_TBL(ext) = duplicate_classext_const_tbl(RCLASSEXT_CONST_TBL(orig), klass); + RCLASSEXT_SHARED_CONST_TBL(ext) = false; + } + /* + * callable_m_tbl is for `super` chain, and entries will be created when the super chain is called. + * 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 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 + */ - /* 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; - cur = cur->next; - // do not trigger GC during f, otherwise the cur will become - // a dangling pointer if the subclass is collected - f(curklass, arg); + 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/back-pointers are only in the prime classext. + + RCLASSEXT_SET_ORIGIN(ext, klass, RCLASSEXT_ORIGIN(orig)); + /* + * Members not copied to box's classext values + * * refined_class + * * as.class.allocator / as.singleton_class.attached_object + * * includer + * * max IV count + * * variation count + */ + RCLASSEXT_PERMANENT_CLASSPATH(ext) = RCLASSEXT_PERMANENT_CLASSPATH(orig); + RCLASSEXT_CLASSPATH(ext) = RCLASSEXT_CLASSPATH(orig); + + /* For the usual T_CLASS/T_MODULE, iclass flags are always false */ + + if (dup_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 box. + * + * Subclasses are only in the prime classext, so read from orig. + */ + 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); + } + } + } + } + + return ext; +} + +void +rb_class_ensure_writable(VALUE klass) +{ + VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); + RCLASS_EXT_WRITABLE(klass); +} + +struct class_classext_foreach_arg { + rb_class_classext_foreach_callback_func *func; + void * callback_arg; +}; + +static int +class_classext_foreach_i(st_data_t key, st_data_t value, st_data_t arg) +{ + struct class_classext_foreach_arg *foreach_arg = (struct class_classext_foreach_arg *)arg; + rb_class_classext_foreach_callback_func *func = foreach_arg->func; + func((rb_classext_t *)value, false, (VALUE)key, foreach_arg->callback_arg); + return ST_CONTINUE; +} + +void +rb_class_classext_foreach(VALUE klass, rb_class_classext_foreach_callback_func *func, void *arg) +{ + st_table *tbl = RCLASS_CLASSEXT_TBL(klass); + struct class_classext_foreach_arg foreach_arg; + if (tbl) { + foreach_arg.func = func; + foreach_arg.callback_arg = arg; + rb_st_foreach(tbl, class_classext_foreach_i, (st_data_t)&foreach_arg); + } + func(RCLASS_EXT_PRIME(klass), true, (VALUE)NULL, arg); +} + +VALUE +rb_class_super_of(VALUE klass) +{ + return RCLASS_SUPER(klass); +} + +VALUE +rb_class_singleton_p(VALUE klass) +{ + return RCLASS_SINGLETON_P(klass); +} + +unsigned char +rb_class_variation_count(VALUE klass) +{ + return RCLASS_VARIATION_COUNT(klass); } static void -class_detach_subclasses(VALUE klass, VALUE arg) +push_subclass_entry_to_list(VALUE super, VALUE klass) { - rb_class_remove_from_super_subclasses(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() { + 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; + } + + rb_imemo_subclasses_entries(subs_v)[subs->count++] = klass; + RB_OBJ_WRITTEN(subs_v, Qundef, klass); + } } void -rb_class_detach_subclasses(VALUE klass) +rb_class_subclass_add(VALUE super, VALUE klass) { - rb_class_foreach_subclass(klass, class_detach_subclasses, Qnil); + if (super && !UNDEF_P(super)) { + 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); + } } static void -class_detach_module_subclasses(VALUE klass, VALUE arg) +rb_module_add_to_subclasses_list(VALUE module, VALUE iclass) { - rb_class_remove_from_module_subclasses(klass); + if (module && !UNDEF_P(module)) { + 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_detach_module_subclasses(VALUE klass) +rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg) { - rb_class_foreach_subclass(klass, class_detach_module_subclasses, Qnil); + VALUE subs_v = RCLASS_SUBCLASSES(klass); + if (!subs_v) return; + + 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); + } + } +} + +static void +class_switch_superclass(VALUE super, VALUE klass) +{ + // 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); } /** - * Allocates a struct RClass for a new class. + * Allocates a struct RClass for a new class, iclass, or module. * - * @param flags initial value for basic.flags of the returned class. - * @param klass the class of the returned class. - * @return an uninitialized Class object. - * @pre `klass` must refer `Class` class or an ancestor of Class. - * @pre `(flags | T_CLASS) != 0` - * @post the returned class can safely be `#initialize` 'd. + * @param type The type of the RClass (T_CLASS, T_ICLASS, or T_MODULE) + * @param klass value for basic.klass of the returned object. + * @return an uninitialized Class/IClass/Module object. + * @pre `klass` must refer to a class or module * * @note this function is not Class#allocate. */ static VALUE -class_alloc(VALUE flags, VALUE klass) +class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) { - size_t alloc_size = sizeof(struct RClass) + sizeof(rb_classext_t); + const rb_box_t *box = rb_current_box(); + + if (!ruby_box_init_done) { + boxable = true; + } + + size_t alloc_size = sizeof(struct RClass_and_rb_classext_t); + if (boxable) { + alloc_size = sizeof(struct RClass_boxable); + } + + RUBY_ASSERT(type == T_CLASS || type == T_ICLASS || type == T_MODULE); + + VALUE flags = type | FL_SHAREABLE; + if (boxable) flags |= RCLASS_BOXABLE; - flags &= T_MASK; - if (RGENGC_WB_PROTECTED_CLASS) flags |= FL_WB_PROTECTED; - NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size, 0); + NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size); - memset(RCLASS_EXT(obj), 0, sizeof(rb_classext_t)); + obj->object_id = 0; + + memset(RCLASS_EXT_PRIME(obj), 0, sizeof(rb_classext_t)); /* ZALLOC RCLASS_CONST_TBL(obj) = 0; RCLASS_M_TBL(obj) = 0; - RCLASS_IV_INDEX_TBL(obj) = 0; + RCLASS_FIELDS(obj) = 0; RCLASS_SET_SUPER((VALUE)obj, 0); - RCLASS_SUBCLASSES(obj) = NULL; - RCLASS_PARENT_SUBCLASSES(obj) = NULL; - RCLASS_MODULE_SUBCLASSES(obj) = NULL; */ + + 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); - RB_OBJ_WRITE(obj, &RCLASS_REFINED_CLASS(obj), Qnil); - RCLASS_SET_ALLOCATOR((VALUE)obj, 0); + RCLASS_SET_REFINED_CLASS((VALUE)obj, Qnil); return (VALUE)obj; } +static VALUE +class_alloc(enum ruby_value_type type, VALUE klass) +{ + 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)) { + // 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); + } + else { + RCLASS_WRITE_SUPER(klass, super); + } + rb_class_update_superclasses(klass); + return super; +} + +VALUE +rb_class_set_super(VALUE klass, VALUE super) +{ + return class_associate_super(klass, super, false); +} + +static void +class_initialize_method_table(VALUE c) +{ + // initialize the prime classext m_tbl + RCLASS_SET_M_TBL(c, rb_id_table_create(0)); +} + static void -RCLASS_M_TBL_INIT(VALUE c) +class_clear_method_table(VALUE c) +{ + RCLASS_WRITE_M_TBL(c, rb_id_table_create(0)); +} + +static VALUE +class_boot_boxable(VALUE super, bool boxable) { - RCLASS_M_TBL(c) = rb_id_table_create(0); + 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 + class_initialize_method_table(klass); + + class_associate_super(klass, super, true); + if (super && !UNDEF_P(super)) { + RCLASS_SET_ALLOCATOR(klass, RCLASS_ALLOCATOR(super)); + rb_class_set_initialized(klass); + } + + return (VALUE)klass; } /** @@ -280,18 +698,13 @@ RCLASS_M_TBL_INIT(VALUE c) VALUE rb_class_boot(VALUE super) { - VALUE klass = class_alloc(T_CLASS, rb_cClass); - - RCLASS_SET_SUPER(klass, super); - RCLASS_M_TBL_INIT(klass); - - return (VALUE)klass; + return class_boot_boxable(super, false); } static VALUE * class_superclasses_including_self(VALUE klass) { - if (FL_TEST_RAW(klass, RCLASS_SUPERCLASSES_INCLUDE_SELF)) + if (RCLASS_SUPERCLASSES_WITH_SELF_P(klass)) return RCLASS_SUPERCLASSES(klass); size_t depth = RCLASS_SUPERCLASS_DEPTH(klass); @@ -300,14 +713,14 @@ class_superclasses_including_self(VALUE klass) memcpy(superclasses, RCLASS_SUPERCLASSES(klass), sizeof(VALUE) * depth); superclasses[depth] = klass; - RCLASS_SUPERCLASSES(klass) = superclasses; - FL_SET_RAW(klass, RCLASS_SUPERCLASSES_INCLUDE_SELF); return superclasses; } void rb_class_update_superclasses(VALUE klass) { + VALUE *superclasses; + size_t super_depth; VALUE super = RCLASS_SUPER(klass); if (!RB_TYPE_P(klass, T_CLASS)) return; @@ -336,8 +749,17 @@ rb_class_update_superclasses(VALUE klass) return; } - RCLASS_SUPERCLASSES(klass) = class_superclasses_including_self(super); - RCLASS_SUPERCLASS_DEPTH(klass) = RCLASS_SUPERCLASS_DEPTH(super) + 1; + super_depth = RCLASS_SUPERCLASS_DEPTH(super); + if (RCLASS_SUPERCLASSES_WITH_SELF_P(super)) { + superclasses = RCLASS_SUPERCLASSES(super); + } + else { + superclasses = class_superclasses_including_self(super); + RCLASS_WRITE_SUPERCLASSES(super, super_depth, superclasses, true); + } + + size_t depth = super_depth == RCLASS_MAX_SUPERCLASS_DEPTH ? super_depth : super_depth + 1; + RCLASS_WRITE_SUPERCLASSES(klass, depth, superclasses, false); } void @@ -362,9 +784,8 @@ rb_class_new(VALUE super) rb_check_inheritable(super); VALUE klass = rb_class_boot(super); - if (super != rb_cObject && super != rb_cBasicObject) { - RCLASS_EXT(klass)->max_iv_count = RCLASS_EXT(super)->max_iv_count; - } + RCLASS_SET_MAX_IV_COUNT(klass, RCLASS_MAX_IV_COUNT(super)); + RUBY_ASSERT(getenv("RUBY_BOX") || RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)); return klass; } @@ -376,28 +797,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, &new_cref); - 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; } @@ -430,7 +843,7 @@ class_init_copy_check(VALUE clone, VALUE orig) if (orig == rb_cBasicObject) { rb_raise(rb_eTypeError, "can't copy the root class"); } - if (RCLASS_SUPER(clone) != 0 || clone == rb_cBasicObject) { + if (RCLASS_INITIALIZED_P(clone)) { rb_raise(rb_eTypeError, "already initialized class"); } if (RCLASS_SINGLETON_P(orig)) { @@ -440,9 +853,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) { @@ -452,13 +871,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; } @@ -468,62 +885,49 @@ copy_tables(VALUE clone, VALUE orig) { if (RCLASS_CONST_TBL(clone)) { rb_free_const_table(RCLASS_CONST_TBL(clone)); - RCLASS_CONST_TBL(clone) = 0; + 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); - RCLASS_CVC_TBL(clone) = rb_cvc_tbl_dup; + 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)); - RCLASS_M_TBL(clone) = 0; + RCLASS_WRITE_M_TBL(clone, 0); if (!RB_TYPE_P(clone, T_ICLASS)) { - st_data_t id; - - rb_iv_tbl_copy(clone, orig); - CONST_ID(id, "__tmp_classpath__"); - rb_attr_delete(clone, id); - CONST_ID(id, "__classpath__"); - rb_attr_delete(clone, id); + rb_fields_tbl_copy(clone, orig); } if (RCLASS_CONST_TBL(orig)) { struct clone_const_arg arg; - - arg.tbl = RCLASS_CONST_TBL(clone) = rb_id_table_create(0); + struct rb_id_table *const_tbl; + struct rb_id_table *orig_tbl = RCLASS_CONST_TBL(orig); + arg.tbl = const_tbl = rb_id_table_create(rb_id_table_size(orig_tbl)); arg.klass = clone; - rb_id_table_foreach(RCLASS_CONST_TBL(orig), clone_const_i, &arg); + rb_id_table_foreach(orig_tbl, clone_const_i, &arg); + RCLASS_WRITE_CONST_TBL(clone, const_tbl, false); + rb_gc_writebarrier_remember(clone); } } static bool ensure_origin(VALUE klass); -/** - * If this flag is set, that module is allocated but not initialized yet. - */ -enum {RMODULE_ALLOCATED_BUT_NOT_INITIALIZED = RUBY_FL_USER1}; - -static inline bool -RMODULE_UNINITIALIZED(VALUE module) -{ - return FL_TEST_RAW(module, RMODULE_ALLOCATED_BUT_NOT_INITIALIZED); -} - void -rb_module_set_initialized(VALUE mod) +rb_class_set_initialized(VALUE klass) { - FL_UNSET_RAW(mod, RMODULE_ALLOCATED_BUT_NOT_INITIALIZED); + RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE)); + FL_SET_RAW(klass, RCLASS_IS_INITIALIZED); /* no more re-initialization */ } void rb_module_check_initializable(VALUE mod) { - if (!RMODULE_UNINITIALIZED(mod)) { + if (RCLASS_INITIALIZED_P(mod)) { rb_raise(rb_eTypeError, "already initialized module"); } } @@ -532,9 +936,11 @@ rb_module_check_initializable(VALUE mod) VALUE rb_mod_init_copy(VALUE clone, VALUE orig) { + /* Only class or module is valid here, but other classes may enter here and + * only hit an exception on the OBJ_INIT_COPY checks + */ switch (BUILTIN_TYPE(clone)) { case T_CLASS: - case T_ICLASS: class_init_copy_check(clone, orig); break; case T_MODULE: @@ -545,28 +951,28 @@ rb_mod_init_copy(VALUE clone, VALUE orig) } if (!OBJ_INIT_COPY(clone, orig)) return clone; - /* cloned flag is refer at constant inline cache - * see vm_get_const_key_cref() in vm_insnhelper.c - */ - RCLASS_EXT(clone)->cloned = true; - RCLASS_EXT(orig)->cloned = true; + RUBY_ASSERT(RB_TYPE_P(orig, T_CLASS) || RB_TYPE_P(orig, T_MODULE)); + RUBY_ASSERT(BUILTIN_TYPE(clone) == BUILTIN_TYPE(orig)); + + rb_class_set_initialized(clone); 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); } - RCLASS_SET_ALLOCATOR(clone, RCLASS_ALLOCATOR(orig)); + if (BUILTIN_TYPE(clone) == T_CLASS) { + RCLASS_SET_ALLOCATOR(clone, RCLASS_ALLOCATOR(orig)); + } copy_tables(clone, orig); if (RCLASS_M_TBL(orig)) { struct clone_method_arg arg; - arg.old_klass = orig; arg.new_klass = clone; - RCLASS_M_TBL_INIT(clone); + class_initialize_method_table(clone); rb_id_table_foreach(RCLASS_M_TBL(orig), clone_method_i, &arg); } if (RCLASS_ORIGIN(orig) == orig) { - RCLASS_SET_SUPER(clone, RCLASS_SUPER(orig)); + rb_class_set_super(clone, RCLASS_SUPER(orig)); } else { VALUE p = RCLASS_SUPER(orig); @@ -586,15 +992,11 @@ rb_mod_init_copy(VALUE clone, VALUE orig) if (BUILTIN_TYPE(p) != T_ICLASS) { rb_bug("non iclass between module/class and origin"); } - clone_p = class_alloc(RBASIC(p)->flags, METACLASS_OF(p)); - /* We should set the m_tbl right after allocation before anything - * that can trigger GC to avoid clone_p from becoming old and - * needing to fire write barriers. */ + clone_p = class_alloc(T_ICLASS, METACLASS_OF(p)); RCLASS_SET_M_TBL(clone_p, RCLASS_M_TBL(p)); - RCLASS_SET_SUPER(prev_clone_p, clone_p); + rb_class_set_super(prev_clone_p, clone_p); prev_clone_p = clone_p; - RCLASS_CONST_TBL(clone_p) = RCLASS_CONST_TBL(p); - RCLASS_SET_ALLOCATOR(clone_p, RCLASS_ALLOCATOR(p)); + RCLASS_SET_CONST_TBL(clone_p, RCLASS_CONST_TBL(p), false); if (RB_TYPE_P(clone, T_CLASS)) { RCLASS_SET_INCLUDER(clone_p, clone); } @@ -606,8 +1008,8 @@ rb_mod_init_copy(VALUE clone, VALUE orig) } else if ((origin_len = RARRAY_LEN(origin_stack)) > 1 && RARRAY_AREF(origin_stack, origin_len - 1) == p) { - RCLASS_SET_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), clone_p); - RICLASS_SET_ORIGIN_SHARED_MTBL(clone_p); + RCLASS_WRITE_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), clone_p); + RICLASS_WRITE_ORIGIN_SHARED_MTBL(clone_p); rb_ary_resize(origin_stack, origin_len); add_subclass = FALSE; } @@ -619,15 +1021,14 @@ rb_mod_init_copy(VALUE clone, VALUE orig) if (p == orig_origin) { if (clone_p) { - RCLASS_SET_SUPER(clone_p, clone_origin); - RCLASS_SET_SUPER(clone_origin, RCLASS_SUPER(orig_origin)); + rb_class_set_super(clone_p, clone_origin); + rb_class_set_super(clone_origin, RCLASS_SUPER(orig_origin)); } 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; - RCLASS_M_TBL_INIT(clone_origin); + class_initialize_method_table(clone_origin); rb_id_table_foreach(RCLASS_M_TBL(orig_origin), clone_method_i, &arg); } } @@ -664,7 +1065,8 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) else { /* copy singleton(unnamed) class */ bool klass_of_clone_is_new; - VALUE clone = class_alloc(RBASIC(klass)->flags, 0); + RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS)); + VALUE clone = class_alloc(T_CLASS, 0); if (BUILTIN_TYPE(obj) == T_CLASS) { klass_of_clone_is_new = true; @@ -678,21 +1080,24 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) RBASIC_SET_CLASS(clone, klass_metaclass_clone); } - RCLASS_SET_SUPER(clone, RCLASS_SUPER(klass)); - rb_iv_tbl_copy(clone, klass); + // initialize method table before any GC chance + class_initialize_method_table(clone); + + rb_class_set_super(clone, RCLASS_SUPER(klass)); + rb_fields_tbl_copy(clone, klass); if (RCLASS_CONST_TBL(klass)) { struct clone_const_arg arg; - arg.tbl = RCLASS_CONST_TBL(clone) = rb_id_table_create(0); + struct rb_id_table *table; + 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); } if (!UNDEF_P(attach)) { rb_singleton_class_attached(clone, attach); } - RCLASS_M_TBL_INIT(clone); { 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); } @@ -766,7 +1171,7 @@ static inline VALUE make_metaclass(VALUE klass) { VALUE super; - VALUE metaclass = rb_class_boot(Qundef); + VALUE metaclass = class_boot_boxable(Qundef, FL_TEST_RAW(klass, RCLASS_BOXABLE)); FL_SET(metaclass, FL_SINGLETON); rb_singleton_class_attached(metaclass, klass); @@ -783,7 +1188,8 @@ make_metaclass(VALUE klass) super = RCLASS_SUPER(klass); while (RB_TYPE_P(super, T_ICLASS)) super = RCLASS_SUPER(super); - RCLASS_SET_SUPER(metaclass, super ? ENSURE_EIGENCLASS(super) : rb_cClass); + class_associate_super(metaclass, super ? ENSURE_EIGENCLASS(super) : rb_cClass, true); + rb_class_set_initialized(klass); // Full class ancestry may not have been filled until we reach here. rb_class_update_superclasses(METACLASS_OF(metaclass)); @@ -801,12 +1207,18 @@ static inline VALUE make_singleton_class(VALUE obj) { VALUE orig_class = METACLASS_OF(obj); - VALUE klass = rb_class_boot(orig_class); - + 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; @@ -901,8 +1313,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")); @@ -982,9 +1398,8 @@ VALUE rb_define_class(const char *name, VALUE super) { VALUE klass; - ID id; + ID id = rb_intern(name); - id = rb_intern(name); if (rb_const_defined(rb_cObject, id)) { klass = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(klass, T_CLASS)) { @@ -1061,8 +1476,7 @@ VALUE rb_module_s_alloc(VALUE klass) { VALUE mod = class_alloc(T_MODULE, klass); - RCLASS_M_TBL_INIT(mod); - FL_SET(mod, RMODULE_ALLOCATED_BUT_NOT_INITIALIZED); + class_initialize_method_table(mod); return mod; } @@ -1070,7 +1484,7 @@ static inline VALUE module_new(VALUE klass) { VALUE mdl = class_alloc(T_MODULE, klass); - RCLASS_M_TBL_INIT(mdl); + class_initialize_method_table(mdl); return (VALUE)mdl; } @@ -1097,9 +1511,8 @@ VALUE rb_define_module(const char *name) { VALUE module; - ID id; + ID id = rb_intern(name); - id = rb_intern(name); if (rb_const_defined(rb_cObject, id)) { module = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(module, T_MODULE)) { @@ -1152,21 +1565,24 @@ rb_include_class_new(VALUE module, VALUE super) { VALUE klass = class_alloc(T_ICLASS, rb_cClass); - RCLASS_SET_M_TBL(klass, RCLASS_M_TBL(module)); + RCLASS_SET_M_TBL(klass, RCLASS_WRITABLE_M_TBL(module)); RCLASS_SET_ORIGIN(klass, klass); if (BUILTIN_TYPE(module) == T_ICLASS) { module = METACLASS_OF(module); } RUBY_ASSERT(!RB_TYPE_P(module, T_ICLASS)); - if (!RCLASS_CONST_TBL(module)) { - RCLASS_CONST_TBL(module) = rb_id_table_create(0); + if (RCLASS_WRITABLE_CONST_TBL(module)) { + RCLASS_SET_CONST_TBL(klass, RCLASS_WRITABLE_CONST_TBL(module), true); + } + else { + RCLASS_WRITE_CONST_TBL(module, rb_id_table_create(0), false); + RCLASS_SET_CONST_TBL(klass, RCLASS_WRITABLE_CONST_TBL(module), true); } - RCLASS_CVC_TBL(klass) = RCLASS_CVC_TBL(module); - RCLASS_CONST_TBL(klass) = RCLASS_CONST_TBL(module); + RCLASS_SET_CVC_TBL(klass, RCLASS_WRITABLE_CVC_TBL(module)); - RCLASS_SET_SUPER(klass, super); + class_associate_super(klass, super, true); RBASIC_SET_CLASS(klass, module); return (VALUE)klass; @@ -1179,7 +1595,7 @@ ensure_includable(VALUE klass, VALUE module) { rb_class_modify_check(klass); Check_Type(module, T_MODULE); - rb_module_set_initialized(module); + rb_class_set_initialized(module); if (!NIL_P(rb_refinement_module_get_refined_class(module))) { rb_raise(rb_eArgError, "refinement module is not allowed"); } @@ -1197,35 +1613,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(klass); - // skip the placeholder subclass entry at the head of the list - if (iclass) { - RUBY_ASSERT(!iclass->klass); - iclass = iclass->next; - } - - 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; } } } @@ -1334,7 +1749,7 @@ do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super // setup T_ICLASS for the include/prepend module iclass = rb_include_class_new(module, super_class); - c = RCLASS_SET_SUPER(c, iclass); + c = rb_class_set_super(c, iclass); RCLASS_SET_INCLUDER(iclass, klass); if (module != RCLASS_ORIGIN(module)) { if (!origin_stack) origin_stack = rb_ary_hidden_new(2); @@ -1343,8 +1758,8 @@ do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super } else if (origin_stack && (origin_len = RARRAY_LEN(origin_stack)) > 1 && RARRAY_AREF(origin_stack, origin_len - 1) == module) { - RCLASS_SET_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), iclass); - RICLASS_SET_ORIGIN_SHARED_MTBL(iclass); + RCLASS_WRITE_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), iclass); + RICLASS_WRITE_ORIGIN_SHARED_MTBL(iclass); rb_ary_resize(origin_stack, origin_len); } @@ -1383,7 +1798,7 @@ move_refined_method(ID key, VALUE value, void *data) if (me->def->type == VM_METHOD_TYPE_REFINED) { VALUE klass = (VALUE)data; - struct rb_id_table *tbl = RCLASS_M_TBL(klass); + struct rb_id_table *tbl = RCLASS_WRITABLE_M_TBL(klass); if (me->def->body.refined.orig_me) { const rb_method_entry_t *orig_me = me->def->body.refined.orig_me, *new_me; @@ -1425,10 +1840,15 @@ ensure_origin(VALUE klass) if (origin == klass) { origin = class_alloc(T_ICLASS, klass); RCLASS_SET_M_TBL(origin, RCLASS_M_TBL(klass)); - RCLASS_SET_SUPER(origin, RCLASS_SUPER(klass)); - RCLASS_SET_SUPER(klass, origin); - RCLASS_SET_ORIGIN(klass, origin); - RCLASS_M_TBL_INIT(klass); + rb_class_set_super(origin, RCLASS_SUPER(klass)); + rb_class_set_super(klass, origin); // writes origin into RCLASS_SUPER(klass) + RCLASS_WRITE_ORIGIN(klass, origin); + + // RCLASS_WRITE_ORIGIN marks origin as an origin, so this is the first + // point that it sees M_TBL and may mark it + rb_gc_writebarrier_remember(origin); + + class_clear_method_table(klass); rb_id_table_foreach(RCLASS_M_TBL(origin), cache_clear_refined_method, (void *)klass); rb_id_table_foreach(RCLASS_M_TBL(origin), move_refined_method, (void *)klass); return true; @@ -1453,35 +1873,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(klass); - // skip the placeholder subclass entry at the head of the list if it exists - if (iclass) { - RUBY_ASSERT(!iclass->klass); - iclass = iclass->next; - } - + 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_M_TBL(subclass) = klass_m_tbl; - VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(subclass)); - RCLASS_SET_SUPER(subclass, origin); - RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(subclass)); - RCLASS_SET_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; } } } @@ -1551,7 +1968,7 @@ rb_mod_include_p(VALUE mod, VALUE mod2) Check_Type(mod2, T_MODULE); for (p = RCLASS_SUPER(mod); p; p = RCLASS_SUPER(p)) { - if (BUILTIN_TYPE(p) == T_ICLASS && !FL_TEST(p, RICLASS_IS_ORIGIN)) { + if (BUILTIN_TYPE(p) == T_ICLASS && !RICLASS_IS_ORIGIN_P(p)) { if (METACLASS_OF(p) == mod2) return Qtrue; } } @@ -1611,19 +2028,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 @@ -2096,7 +2511,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); @@ -2222,7 +2637,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; @@ -2250,27 +2665,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(klass, (FL_SINGLETON|FL_FREEZE)) == FL_SINGLETON) { - 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); } } @@ -2298,12 +2729,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); } /*! @@ -2321,7 +2747,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 |
