summaryrefslogtreecommitdiff
path: root/class.c
diff options
context:
space:
mode:
Diffstat (limited to 'class.c')
-rw-r--r--class.c1076
1 files changed, 751 insertions, 325 deletions
diff --git a/class.c b/class.c
index b39e693837..69194ccab2 100644
--- a/class.c
+++ b/class.c
@@ -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