diff options
Diffstat (limited to 'variable.c')
| -rw-r--r-- | variable.c | 1997 |
1 files changed, 948 insertions, 1049 deletions
diff --git a/variable.c b/variable.c index f7ba33e089..ff8d24d78a 100644 --- a/variable.c +++ b/variable.c @@ -20,15 +20,16 @@ #include "id.h" #include "id_table.h" #include "internal.h" +#include "internal/box.h" #include "internal/class.h" #include "internal/compilers.h" #include "internal/error.h" #include "internal/eval.h" #include "internal/hash.h" -#include "internal/namespace.h" #include "internal/object.h" #include "internal/gc.h" #include "internal/re.h" +#include "internal/struct.h" #include "internal/symbol.h" #include "internal/thread.h" #include "internal/variable.h" @@ -62,7 +63,7 @@ static VALUE autoload_mutex; static void check_before_mod_set(VALUE, ID, VALUE, const char *); static void setup_const_entry(rb_const_entry_t *, VALUE, VALUE, rb_const_flag_t); -static VALUE rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility); +static VALUE rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility, VALUE *found_in); static st_table *generic_fields_tbl_; typedef int rb_ivar_foreach_callback_func(ID key, VALUE val, st_data_t arg); @@ -278,7 +279,7 @@ set_sub_temporary_name(VALUE mod, VALUE name) * m.name #=> nil * * c = Class.new - * c.set_temporary_name("MyClass(with description)") + * c.set_temporary_name("MyClass(with description)") # => MyClass(with description) * * c.new # => #<MyClass(with description):0x0....> * @@ -303,7 +304,9 @@ rb_mod_set_temporary_name(VALUE mod, VALUE name) if (NIL_P(name)) { // Set the temporary classpath to NULL (anonymous): - RB_VM_LOCKING() { set_sub_temporary_name(mod, 0);} + RB_VM_LOCKING() { + set_sub_temporary_name(mod, 0); + } } else { // Ensure the name is a string: @@ -318,9 +321,12 @@ rb_mod_set_temporary_name(VALUE mod, VALUE name) } name = rb_str_new_frozen(name); + RB_OBJ_SET_SHAREABLE(name); // Set the temporary classpath to the given name: - RB_VM_LOCKING() { set_sub_temporary_name(mod, name);} + RB_VM_LOCKING() { + set_sub_temporary_name(mod, name); + } } return mod; @@ -427,6 +433,7 @@ rb_set_class_path_string(VALUE klass, VALUE under, VALUE name) str = build_const_pathname(str, name); } + RB_OBJ_SET_SHAREABLE(str); RCLASS_SET_CLASSPATH(klass, str, permanent); } @@ -466,7 +473,7 @@ rb_path_to_class(VALUE pathname) if (!id) { goto undefined_class; } - c = rb_const_search(c, id, TRUE, FALSE, FALSE); + c = rb_const_search(c, id, TRUE, FALSE, FALSE, NULL); if (UNDEF_P(c)) goto undefined_class; if (!rb_namespace_p(c)) { rb_raise(rb_eTypeError, "%"PRIsVALUE" does not refer to class/module", @@ -526,7 +533,7 @@ struct rb_global_variable { rb_gvar_marker_t *marker; rb_gvar_compact_t *compactor; struct trace_var *trace; - bool namespace_ready; + bool box_ready; }; struct rb_global_entry { @@ -580,16 +587,18 @@ rb_find_global_entry(ID id) struct rb_global_entry *entry; VALUE data; - if (!rb_id_table_lookup(rb_global_tbl, id, &data)) { - entry = NULL; - } - else { - entry = (struct rb_global_entry *)data; - RUBY_ASSERT(entry != NULL); + RB_VM_LOCKING() { + if (!rb_id_table_lookup(rb_global_tbl, id, &data)) { + entry = NULL; + } + else { + entry = (struct rb_global_entry *)data; + RUBY_ASSERT(entry != NULL); + } } if (UNLIKELY(!rb_ractor_main_p()) && (!entry || !entry->ractor_local)) { - rb_raise(rb_eRactorIsolationError, "can not access global variables %s from non-main Ractors", rb_id2name(id)); + rb_raise(rb_eRactorIsolationError, "can not access global variable %s from non-main Ractor", rb_id2name(id)); } return entry; @@ -603,10 +612,10 @@ rb_gvar_ractor_local(const char *name) } void -rb_gvar_namespace_ready(const char *name) +rb_gvar_box_ready(const char *name) { struct rb_global_entry *entry = rb_find_global_entry(rb_intern(name)); - entry->var->namespace_ready = true; + entry->var->box_ready = true; } static void @@ -617,25 +626,28 @@ rb_gvar_undef_compactor(void *var) static struct rb_global_entry* rb_global_entry(ID id) { - struct rb_global_entry *entry = rb_find_global_entry(id); - if (!entry) { - struct rb_global_variable *var; - entry = ALLOC(struct rb_global_entry); - var = ALLOC(struct rb_global_variable); - entry->id = id; - entry->var = var; - entry->ractor_local = false; - var->counter = 1; - var->data = 0; - var->getter = rb_gvar_undef_getter; - var->setter = rb_gvar_undef_setter; - var->marker = rb_gvar_undef_marker; - var->compactor = rb_gvar_undef_compactor; - - var->block_trace = 0; - var->trace = 0; - var->namespace_ready = false; - rb_id_table_insert(rb_global_tbl, id, (VALUE)entry); + struct rb_global_entry *entry; + RB_VM_LOCKING() { + entry = rb_find_global_entry(id); + if (!entry) { + struct rb_global_variable *var; + entry = ALLOC(struct rb_global_entry); + var = ALLOC(struct rb_global_variable); + entry->id = id; + entry->var = var; + entry->ractor_local = false; + var->counter = 1; + var->data = 0; + var->getter = rb_gvar_undef_getter; + var->setter = rb_gvar_undef_setter; + var->marker = rb_gvar_undef_marker; + var->compactor = rb_gvar_undef_compactor; + + var->block_trace = 0; + var->trace = 0; + var->box_ready = false; + rb_id_table_insert(rb_global_tbl, id, (VALUE)entry); + } } return entry; } @@ -850,7 +862,7 @@ rb_define_virtual_variable( static void rb_trace_eval(VALUE cmd, VALUE val) { - rb_eval_cmd_kw(cmd, rb_ary_new3(1, val), RB_NO_KEYWORDS); + rb_eval_cmd_call_kw(cmd, 1, &val, RB_NO_KEYWORDS); } VALUE @@ -988,25 +1000,30 @@ rb_gvar_set_entry(struct rb_global_entry *entry, VALUE val) return val; } -#define USE_NAMESPACE_GVAR_TBL(ns,entry) \ - (NAMESPACE_OPTIONAL_P(ns) && \ - (!entry || !entry->var->namespace_ready || entry->var->setter != rb_gvar_readonly_setter)) +#define USE_BOX_GVAR_TBL(ns,entry) \ + (BOX_USER_P(ns) && \ + (!entry || !entry->var->box_ready || entry->var->setter != rb_gvar_readonly_setter)) VALUE rb_gvar_set(ID id, VALUE val) { VALUE retval; struct rb_global_entry *entry; - const rb_namespace_t *ns = rb_current_namespace(); + const rb_box_t *box = rb_current_box(); + bool use_box_tbl = false; - entry = rb_global_entry(id); + RB_VM_LOCKING() { + entry = rb_global_entry(id); - if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { - rb_hash_aset(ns->gvar_tbl, rb_id2sym(entry->id), val); - retval = val; - // TODO: think about trace + if (USE_BOX_GVAR_TBL(box, entry)) { + use_box_tbl = true; + rb_hash_aset(box->gvar_tbl, rb_id2sym(entry->id), val); + retval = val; + // TODO: think about trace + } } - else { + + if (!use_box_tbl) { retval = rb_gvar_set_entry(entry, val); } return retval; @@ -1022,25 +1039,36 @@ VALUE rb_gvar_get(ID id) { VALUE retval, gvars, key; - struct rb_global_entry *entry = rb_global_entry(id); - struct rb_global_variable *var = entry->var; - const rb_namespace_t *ns = rb_current_namespace(); - - if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { - gvars = ns->gvar_tbl; - key = rb_id2sym(entry->id); - if (RTEST(rb_hash_has_key(gvars, key))) { // this gvar is already cached - retval = rb_hash_aref(gvars, key); - } - else { - retval = (*var->getter)(entry->id, var->data); - if (rb_obj_respond_to(retval, rb_intern("clone"), 1)) { - retval = rb_funcall(retval, rb_intern("clone"), 0); + const rb_box_t *box = rb_current_box(); + bool use_box_tbl = false; + struct rb_global_entry *entry; + struct rb_global_variable *var; + // TODO: use lock-free rb_id_table when it's available for use (doesn't yet exist) + RB_VM_LOCKING() { + entry = rb_global_entry(id); + var = entry->var; + + if (USE_BOX_GVAR_TBL(box, entry)) { + use_box_tbl = true; + gvars = box->gvar_tbl; + key = rb_id2sym(entry->id); + if (RTEST(rb_hash_has_key(gvars, key))) { // this gvar is already cached + retval = rb_hash_aref(gvars, key); + } + else { + RB_VM_UNLOCK(); + { + retval = (*var->getter)(entry->id, var->data); + if (rb_obj_respond_to(retval, rb_intern("clone"), 1)) { + retval = rb_funcall(retval, rb_intern("clone"), 0); + } + } + RB_VM_LOCK(); + rb_hash_aset(gvars, key, retval); } - rb_hash_aset(gvars, key, retval); } } - else { + if (!use_box_tbl) { retval = (*var->getter)(entry->id, var->data); } return retval; @@ -1097,7 +1125,7 @@ rb_f_global_variables(void) if (!rb_ractor_main_p()) { rb_raise(rb_eRactorIsolationError, "can not access global variables from non-main Ractors"); } - /* gvar access (get/set) in namespaces creates gvar entries globally */ + /* gvar access (get/set) in boxes creates gvar entries globally */ rb_id_table_foreach(rb_global_tbl, gvar_i, (void *)ary); if (!NIL_P(backref)) { @@ -1124,7 +1152,7 @@ rb_f_global_variables(void) void rb_alias_variable(ID name1, ID name2) { - struct rb_global_entry *entry1, *entry2; + struct rb_global_entry *entry1 = NULL, *entry2; VALUE data1; struct rb_id_table *gtbl = rb_global_tbl; @@ -1132,27 +1160,29 @@ rb_alias_variable(ID name1, ID name2) rb_raise(rb_eRactorIsolationError, "can not access global variables from non-main Ractors"); } - entry2 = rb_global_entry(name2); - if (!rb_id_table_lookup(gtbl, name1, &data1)) { - entry1 = ALLOC(struct rb_global_entry); - entry1->id = name1; - rb_id_table_insert(gtbl, name1, (VALUE)entry1); - } - else if ((entry1 = (struct rb_global_entry *)data1)->var != entry2->var) { - struct rb_global_variable *var = entry1->var; - if (var->block_trace) { - rb_raise(rb_eRuntimeError, "can't alias in tracer"); + RB_VM_LOCKING() { + entry2 = rb_global_entry(name2); + if (!rb_id_table_lookup(gtbl, name1, &data1)) { + entry1 = ZALLOC(struct rb_global_entry); + entry1->id = name1; + rb_id_table_insert(gtbl, name1, (VALUE)entry1); } - var->counter--; - if (var->counter == 0) { - free_global_variable(var); + else if ((entry1 = (struct rb_global_entry *)data1)->var != entry2->var) { + struct rb_global_variable *var = entry1->var; + if (var->block_trace) { + RB_VM_UNLOCK(); + rb_raise(rb_eRuntimeError, "can't alias in tracer"); + } + var->counter--; + if (var->counter == 0) { + free_global_variable(var); + } + } + if (entry1->var != entry2->var) { + entry2->var->counter++; + entry1->var = entry2->var; } } - else { - return; - } - entry2->var->counter++; - entry1->var = entry2->var; } static void @@ -1165,30 +1195,32 @@ IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(ID id) } } -#define CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR() \ - if (UNLIKELY(!rb_ractor_main_p())) { \ - rb_raise(rb_eRactorIsolationError, "can not access class variables from non-main Ractors"); \ - } - -static inline struct st_table * -generic_fields_tbl(VALUE obj, ID id, bool force_check_ractor) +static void +CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(VALUE klass, ID id) { - ASSERT_vm_locking(); + if (UNLIKELY(!rb_ractor_main_p())) { + rb_raise(rb_eRactorIsolationError, "can not access class variables from non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")", rb_id2str(id), klass); + } +} - if ((force_check_ractor || LIKELY(rb_is_instance_id(id)) /* not internal ID */ ) && +static inline void +ivar_ractor_check(VALUE obj, ID id) +{ + if (LIKELY(rb_is_instance_id(id)) /* not internal ID */ && !RB_OBJ_FROZEN_RAW(obj) && UNLIKELY(!rb_ractor_main_p()) && UNLIKELY(rb_ractor_shareable_p(obj))) { rb_raise(rb_eRactorIsolationError, "can not access instance variables of shareable objects from non-main Ractors"); } - return generic_fields_tbl_; } static inline struct st_table * -generic_fields_tbl_no_ractor_check(VALUE obj) +generic_fields_tbl_no_ractor_check(void) { - return generic_fields_tbl(obj, 0, false); + ASSERT_vm_locking(); + + return generic_fields_tbl_; } struct st_table * @@ -1197,176 +1229,205 @@ rb_generic_fields_tbl_get(void) return generic_fields_tbl_; } -int -rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl) +void +rb_mark_generic_ivar(VALUE obj) { - RUBY_ASSERT(!RB_TYPE_P(obj, T_ICLASS)); - - st_data_t data; - int r = 0; - - RB_VM_LOCKING() { - if (st_lookup(generic_fields_tbl(obj, id, false), (st_data_t)obj, &data)) { - *fields_tbl = (struct gen_fields_tbl *)data; - r = 1; - } + VALUE data; + // Bypass ASSERT_vm_locking() check because marking may happen concurrently with mmtk + if (st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&data)) { + rb_gc_mark_movable(data); } - - return r; } -int -rb_ivar_generic_fields_tbl_lookup(VALUE obj, struct gen_fields_tbl **fields_tbl) -{ - return rb_gen_fields_tbl_get(obj, 0, fields_tbl); -} - -static size_t -gen_fields_tbl_bytes(size_t n) +VALUE +rb_obj_fields_generic_uncached(VALUE obj) { - return offsetof(struct gen_fields_tbl, as.shape.fields) + n * sizeof(VALUE); + VALUE fields_obj = 0; + RB_VM_LOCKING() { + if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) { + rb_bug("Object is missing entry in generic_fields_tbl"); + } + } + return fields_obj; } -static struct gen_fields_tbl * -gen_fields_tbl_resize(struct gen_fields_tbl *old, uint32_t n) +VALUE +rb_obj_fields(VALUE obj, ID field_name) { - RUBY_ASSERT(n > 0); - - uint32_t len = old ? old->as.shape.fields_count : 0; - struct gen_fields_tbl *fields_tbl = xrealloc(old, gen_fields_tbl_bytes(n)); + RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO)); + ivar_ractor_check(obj, field_name); - fields_tbl->as.shape.fields_count = n; - for (; len < n; len++) { - fields_tbl->as.shape.fields[len] = Qundef; + VALUE fields_obj = 0; + if (rb_shape_obj_has_fields(obj)) { + switch (BUILTIN_TYPE(obj)) { + case T_DATA: + if (LIKELY(RTYPEDDATA_P(obj))) { + fields_obj = RTYPEDDATA(obj)->fields_obj; + break; + } + goto generic_fields; + case T_STRUCT: + if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { + fields_obj = RSTRUCT_FIELDS_OBJ(obj); + break; + } + goto generic_fields; + default: + generic_fields: + { + rb_execution_context_t *ec = GET_EC(); + if (ec->gen_fields_cache.obj == obj && !UNDEF_P(ec->gen_fields_cache.fields_obj) && rb_imemo_fields_owner(ec->gen_fields_cache.fields_obj) == obj) { + fields_obj = ec->gen_fields_cache.fields_obj; + RUBY_ASSERT(fields_obj == rb_obj_fields_generic_uncached(obj)); + } + else { + fields_obj = rb_obj_fields_generic_uncached(obj); + ec->gen_fields_cache.fields_obj = fields_obj; + ec->gen_fields_cache.obj = obj; + } + } + } } - - return fields_tbl; + return fields_obj; } void -rb_mark_generic_ivar(VALUE obj) +rb_free_generic_ivar(VALUE obj) { - st_data_t data; - if (st_lookup(generic_fields_tbl_no_ractor_check(obj), (st_data_t)obj, &data)) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)data; - if (rb_shape_obj_too_complex_p(obj)) { - rb_mark_tbl_no_pin(fields_tbl->as.complex.table); - } - else { - for (uint32_t i = 0; i < fields_tbl->as.shape.fields_count; i++) { - rb_gc_mark_movable(fields_tbl->as.shape.fields[i]); + if (rb_obj_gen_fields_p(obj)) { + st_data_t key = (st_data_t)obj, value; + switch (BUILTIN_TYPE(obj)) { + case T_DATA: + if (LIKELY(RTYPEDDATA_P(obj))) { + RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, 0); + break; + } + goto generic_fields; + case T_STRUCT: + if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { + RSTRUCT_SET_FIELDS_OBJ(obj, 0); + break; + } + goto generic_fields; + default: + generic_fields: + { + // Other EC may have stale caches, so fields_obj should be + // invalidated and the GC will replace with Qundef + rb_execution_context_t *ec = GET_EC(); + if (ec->gen_fields_cache.obj == obj) { + ec->gen_fields_cache.obj = Qundef; + ec->gen_fields_cache.fields_obj = Qundef; + } + RB_VM_LOCKING() { + if (!st_delete(generic_fields_tbl_no_ractor_check(), &key, &value)) { + rb_bug("Object is missing entry in generic_fields_tbl"); + } + } } } + RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID); } } -void -rb_free_generic_ivar(VALUE obj) +static void +rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fields_obj) { - st_data_t key = (st_data_t)obj, value; + ivar_ractor_check(obj, field_name); - bool too_complex = rb_shape_obj_too_complex_p(obj); + if (!fields_obj) { + RUBY_ASSERT(original_fields_obj); + rb_free_generic_ivar(obj); + rb_imemo_fields_clear(original_fields_obj); + return; + } - RB_VM_LOCKING() { - if (st_delete(generic_fields_tbl_no_ractor_check(obj), &key, &value)) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)value; + RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields)); + RUBY_ASSERT(!original_fields_obj || IMEMO_TYPE_P(original_fields_obj, imemo_fields)); - if (UNLIKELY(too_complex)) { - st_free_table(fields_tbl->as.complex.table); + if (fields_obj != original_fields_obj) { + switch (BUILTIN_TYPE(obj)) { + case T_DATA: + if (LIKELY(RTYPEDDATA_P(obj))) { + RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, fields_obj); + break; + } + goto generic_fields; + case T_STRUCT: + if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { + RSTRUCT_SET_FIELDS_OBJ(obj, fields_obj); + break; } + goto generic_fields; + default: + generic_fields: + { + RB_VM_LOCKING() { + st_insert(generic_fields_tbl_, (st_data_t)obj, (st_data_t)fields_obj); + } + RB_OBJ_WRITTEN(obj, original_fields_obj, fields_obj); - xfree(fields_tbl); + rb_execution_context_t *ec = GET_EC(); + if (ec->gen_fields_cache.fields_obj != fields_obj) { + ec->gen_fields_cache.obj = obj; + ec->gen_fields_cache.fields_obj = fields_obj; + } + } } - } -} -size_t -rb_generic_ivar_memsize(VALUE obj) -{ - struct gen_fields_tbl *fields_tbl; - - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - if (rb_shape_obj_too_complex_p(obj)) { - return sizeof(struct gen_fields_tbl) + st_memsize(fields_tbl->as.complex.table); - } - else { - return gen_fields_tbl_bytes(fields_tbl->as.shape.fields_count); + if (original_fields_obj) { + // Clear root shape to avoid triggering cleanup such as free_object_id. + rb_imemo_fields_clear(original_fields_obj); } } - return 0; + + RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(fields_obj)); } -static size_t -gen_fields_tbl_count(VALUE obj, const struct gen_fields_tbl *fields_tbl) +void +rb_obj_replace_fields(VALUE obj, VALUE fields_obj) { - uint32_t i; - size_t n = 0; - - if (rb_shape_obj_too_complex_p(obj)) { - n = st_table_size(fields_tbl->as.complex.table); - } - else { - for (i = 0; i < fields_tbl->as.shape.fields_count; i++) { - if (!UNDEF_P(fields_tbl->as.shape.fields[i])) { - n++; - } - } + RB_VM_LOCKING() { + VALUE original_fields_obj = rb_obj_fields_no_ractor_check(obj); + rb_obj_set_fields(obj, fields_obj, 0, original_fields_obj); } - - return n; } VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id) { RUBY_ASSERT(!SPECIAL_CONST_P(obj)); - RUBY_ASSERT(RSHAPE(target_shape_id)->type == SHAPE_IVAR || RSHAPE(target_shape_id)->type == SHAPE_OBJ_ID); + RUBY_ASSERT(RSHAPE_TYPE_P(target_shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(target_shape_id, SHAPE_OBJ_ID)); - if (rb_shape_too_complex_p(target_shape_id)) { - st_table *fields_hash; - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - ASSERT_vm_locking(); - fields_hash = RCLASS_FIELDS_HASH(obj); - break; - case T_OBJECT: - fields_hash = ROBJECT_FIELDS_HASH(obj); - break; - default: - RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR)); - struct gen_fields_tbl *fields_tbl = NULL; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); - RUBY_ASSERT(fields_tbl); - fields_hash = fields_tbl->as.complex.table; - break; - } - VALUE value = Qundef; - st_lookup(fields_hash, RSHAPE(target_shape_id)->edge_name, &value); - RUBY_ASSERT(!UNDEF_P(value)); - return value; - } + VALUE fields_obj; - attr_index_t attr_index = RSHAPE(target_shape_id)->next_field_index - 1; - VALUE *fields; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - ASSERT_vm_locking(); - fields = RCLASS_PRIME_FIELDS(obj); + fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); break; case T_OBJECT: - fields = ROBJECT_FIELDS(obj); + fields_obj = obj; + break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields_obj = obj; break; default: - RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR)); - struct gen_fields_tbl *fields_tbl = NULL; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); - RUBY_ASSERT(fields_tbl); - fields = fields_tbl->as.shape.fields; + fields_obj = rb_obj_fields(obj, RSHAPE_EDGE_NAME(target_shape_id)); break; } - return fields[attr_index]; + + if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { + st_table *fields_hash = rb_imemo_fields_complex_tbl(fields_obj); + VALUE value = Qundef; + st_lookup(fields_hash, RSHAPE_EDGE_NAME(target_shape_id), &value); + RUBY_ASSERT(!UNDEF_P(value)); + return value; + } + + attr_index_t index = RSHAPE_INDEX(target_shape_id); + return rb_imemo_fields_ptr(fields_obj)[index]; } VALUE @@ -1374,94 +1435,54 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) { if (SPECIAL_CONST_P(obj)) return undef; - shape_id_t shape_id; - VALUE * ivar_list; - shape_id = RBASIC_SHAPE_ID(obj); + VALUE fields_obj; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: { - bool found = false; - VALUE val; - - RB_VM_LOCKING() { - if (rb_shape_too_complex_p(shape_id)) { - st_table * iv_table = RCLASS_FIELDS_HASH(obj); - if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { - found = true; - } - else { - val = undef; - } - } - else { - attr_index_t index = 0; - found = rb_shape_get_iv_index(shape_id, id, &index); - - if (found) { - ivar_list = RCLASS_PRIME_FIELDS(obj); - RUBY_ASSERT(ivar_list); - - val = ivar_list[index]; - } - else { - val = undef; - } - } - } - - if (found && + VALUE val = rb_ivar_lookup(RCLASS_WRITABLE_FIELDS_OBJ(obj), id, undef); + if (val != undef && rb_is_instance_id(id) && UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) { rb_raise(rb_eRactorIsolationError, - "can not get unshareable values from instance variables of classes/modules from non-main Ractors"); + "can not get unshareable values from instance variables of classes/modules from non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")", + rb_id2str(id), obj); } return val; } + case T_IMEMO: + // Handled like T_OBJECT + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields_obj = obj; + break; case T_OBJECT: - { - if (rb_shape_too_complex_p(shape_id)) { - st_table * iv_table = ROBJECT_FIELDS_HASH(obj); - VALUE val; - if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { - return val; - } - else { - return undef; - } - } - - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - ivar_list = ROBJECT_FIELDS(obj); - break; - } + fields_obj = obj; + break; default: - if (FL_TEST_RAW(obj, FL_EXIVAR)) { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); - - if (rb_shape_obj_too_complex_p(obj)) { - VALUE val; - if (rb_st_lookup(fields_tbl->as.complex.table, (st_data_t)id, (st_data_t *)&val)) { - return val; - } - else { - return undef; - } - } - ivar_list = fields_tbl->as.shape.fields; - } - else { - return undef; - } + fields_obj = rb_obj_fields(obj, id); break; } + if (!fields_obj) { + return undef; + } + + shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj); + + if (UNLIKELY(rb_shape_too_complex_p(shape_id))) { + st_table *iv_table = rb_imemo_fields_complex_tbl(fields_obj); + VALUE val; + if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { + return val; + } + return undef; + } + attr_index_t index = 0; if (rb_shape_get_iv_index(shape_id, id, &index)) { - return ivar_list[index]; + return rb_imemo_fields_ptr(fields_obj)[index]; } return undef; @@ -1476,208 +1497,115 @@ rb_ivar_get(VALUE obj, ID id) } VALUE -rb_attr_get(VALUE obj, ID id) -{ - return rb_ivar_lookup(obj, id, Qnil); -} - -static VALUE -rb_ivar_delete(VALUE obj, ID id, VALUE undef) +rb_ivar_get_at(VALUE obj, attr_index_t index, ID id) { - rb_check_frozen(obj); - - bool locked = false; - unsigned int lev = 0; - VALUE val = undef; - if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { - IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); - RB_VM_LOCK_ENTER_LEV(&lev); - locked = true; - } + RUBY_ASSERT(rb_is_instance_id(id)); + // Used by JITs, but never for T_OBJECT. - shape_id_t old_shape_id = rb_obj_shape_id(obj); - if (rb_shape_too_complex_p(old_shape_id)) { - goto too_complex; - } + switch (BUILTIN_TYPE(obj)) { + case T_OBJECT: + UNREACHABLE_RETURN(Qundef); + case T_CLASS: + case T_MODULE: + { + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + VALUE val = rb_imemo_fields_ptr(fields_obj)[index]; - shape_id_t removed_shape_id = 0; - shape_id_t next_shape_id = rb_shape_transition_remove_ivar(obj, id, &removed_shape_id); + if (UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) { + rb_raise(rb_eRactorIsolationError, + "can not get unshareable values from instance variables of classes/modules from non-main Ractors"); + } - if (next_shape_id == old_shape_id) { - if (locked) { - RB_VM_LOCK_LEAVE_LEV(&lev); + return val; + } + default: + { + VALUE fields_obj = rb_obj_fields(obj, id); + return rb_imemo_fields_ptr(fields_obj)[index]; } - return undef; - } - - if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { - rb_evict_fields_to_hash(obj); - goto too_complex; } +} - RUBY_ASSERT(RSHAPE(next_shape_id)->next_field_index == RSHAPE(old_shape_id)->next_field_index - 1); +VALUE +rb_ivar_get_at_no_ractor_check(VALUE obj, attr_index_t index) +{ + // Used by JITs, but never for T_OBJECT. - VALUE *fields; - switch(BUILTIN_TYPE(obj)) { + VALUE fields_obj; + switch (BUILTIN_TYPE(obj)) { + case T_OBJECT: + UNREACHABLE_RETURN(Qundef); case T_CLASS: case T_MODULE: - fields = RCLASS_PRIME_FIELDS(obj); + fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); break; - case T_OBJECT: - fields = ROBJECT_FIELDS(obj); - break; - default: { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); - fields = fields_tbl->as.shape.fields; + default: + fields_obj = rb_obj_fields_no_ractor_check(obj); break; - } } - - RUBY_ASSERT(removed_shape_id != INVALID_SHAPE_ID); - - attr_index_t new_fields_count = RSHAPE(next_shape_id)->next_field_index; - - attr_index_t removed_index = RSHAPE(removed_shape_id)->next_field_index - 1; - val = fields[removed_index]; - size_t trailing_fields = new_fields_count - removed_index; - - MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields); - - if (RB_TYPE_P(obj, T_OBJECT) && - !RB_FL_TEST_RAW(obj, ROBJECT_EMBED) && - rb_obj_embedded_size(new_fields_count) <= rb_gc_obj_slot_size(obj)) { - // Re-embed objects when instances become small enough - // This is necessary because YJIT assumes that objects with the same shape - // have the same embeddedness for efficiency (avoid extra checks) - RB_FL_SET_RAW(obj, ROBJECT_EMBED); - MEMCPY(ROBJECT_FIELDS(obj), fields, VALUE, new_fields_count); - xfree(fields); - } - rb_obj_set_shape_id(obj, next_shape_id); - - if (locked) { - RB_VM_LOCK_LEAVE_LEV(&lev); - } - - return val; - -too_complex: - { - st_table *table = NULL; - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - table = RCLASS_WRITABLE_FIELDS_HASH(obj); - break; - - case T_OBJECT: - table = ROBJECT_FIELDS_HASH(obj); - break; - - default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; - } - break; - } - } - - if (table) { - if (!st_delete(table, (st_data_t *)&id, (st_data_t *)&val)) { - val = undef; - } - } - } - - if (locked) { - RB_VM_LOCK_LEAVE_LEV(&lev); - } - - return val; + return rb_imemo_fields_ptr(fields_obj)[index]; } VALUE -rb_attr_delete(VALUE obj, ID id) +rb_attr_get(VALUE obj, ID id) { - return rb_ivar_delete(obj, id, Qnil); + return rb_ivar_lookup(obj, id, Qnil); } -static void +void rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table); +static VALUE imemo_fields_complex_from_obj(VALUE owner, VALUE source_fields_obj, shape_id_t shape_id); + +static shape_id_t obj_transition_too_complex(VALUE obj, st_table *table) { RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); shape_id_t shape_id = rb_shape_transition_complex(obj); - VALUE *old_fields = NULL; - switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { - old_fields = ROBJECT_FIELDS(obj); + { + VALUE *old_fields = NULL; + if (FL_TEST_RAW(obj, ROBJECT_HEAP)) { + old_fields = ROBJECT_FIELDS(obj); + } + else { + FL_SET_RAW(obj, ROBJECT_HEAP); + } + RBASIC_SET_SHAPE_ID(obj, shape_id); + ROBJECT_SET_FIELDS_HASH(obj, table); + if (old_fields) { + xfree(old_fields); + } } - rb_obj_set_shape_id(obj, shape_id); - ROBJECT_SET_FIELDS_HASH(obj, table); break; case T_CLASS: case T_MODULE: - old_fields = RCLASS_PRIME_FIELDS(obj); - rb_obj_set_shape_id(obj, shape_id); - RCLASS_SET_FIELDS_HASH(obj, table); + case T_IMEMO: + UNREACHABLE; break; default: - RB_VM_LOCKING() { - struct st_table *gen_ivs = generic_fields_tbl_no_ractor_check(obj); - - struct gen_fields_tbl *old_fields_tbl = NULL; - st_lookup(gen_ivs, (st_data_t)obj, (st_data_t *)&old_fields_tbl); - - if (old_fields_tbl) { - /* We need to modify old_fields_tbl to have the too complex shape - * and hold the table because the xmalloc could trigger a GC - * compaction. We want the table to be updated rather than - * the original fields. */ - rb_obj_set_shape_id(obj, shape_id); - old_fields_tbl->as.complex.table = table; - old_fields = (VALUE *)old_fields_tbl; - } - - struct gen_fields_tbl *fields_tbl = xmalloc(sizeof(struct gen_fields_tbl)); - fields_tbl->as.complex.table = table; - st_insert(gen_ivs, (st_data_t)obj, (st_data_t)fields_tbl); - - rb_obj_set_shape_id(obj, shape_id); + { + VALUE fields_obj = rb_imemo_fields_new_complex_tbl(obj, table, RB_OBJ_SHAREABLE_P(obj)); + RBASIC_SET_SHAPE_ID(fields_obj, shape_id); + rb_obj_replace_fields(obj, fields_obj); } } - xfree(old_fields); -} - -void -rb_obj_init_too_complex(VALUE obj, st_table *table) -{ - // This method is meant to be called on newly allocated object. - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - RUBY_ASSERT(rb_shape_canonical_p(RBASIC_SHAPE_ID(obj))); - RUBY_ASSERT(RSHAPE_LEN(RBASIC_SHAPE_ID(obj)) == 0); - - obj_transition_too_complex(obj, table); + return shape_id; } // Copy all object fields, including ivars and internal object_id, etc -void +static shape_id_t rb_evict_fields_to_hash(VALUE obj) { - void rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table); - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); st_table *table = st_init_numtable_with_size(RSHAPE_LEN(RBASIC_SHAPE_ID(obj))); rb_obj_copy_fields_to_hash_table(obj, table); - obj_transition_too_complex(obj, table); + shape_id_t new_shape_id = obj_transition_too_complex(obj, table); RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + return new_shape_id; } void @@ -1694,258 +1622,276 @@ rb_evict_ivars_to_hash(VALUE obj) RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); } -struct general_ivar_set_result { - attr_index_t index; - bool existing; -}; - -static struct general_ivar_set_result -general_ivar_set(VALUE obj, ID id, VALUE val, void *data, - VALUE *(*shape_fields_func)(VALUE, void *), - void (*shape_resize_fields_func)(VALUE, attr_index_t, attr_index_t, void *), - void (*set_shape_id_func)(VALUE, shape_id_t, void *), - void (*transition_too_complex_func)(VALUE, void *), - st_table *(*too_complex_table_func)(VALUE, void *)) -{ - struct general_ivar_set_result result = { - .index = 0, - .existing = true - }; - - shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); - - if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) { - goto too_complex; - } +static VALUE +rb_ivar_delete(VALUE obj, ID id, VALUE undef) +{ + rb_check_frozen(obj); - attr_index_t index; - if (!rb_shape_get_iv_index(current_shape_id, id, &index)) { - result.existing = false; + VALUE val = undef; + VALUE fields_obj; + bool concurrent = false; + int type = BUILTIN_TYPE(obj); - index = RSHAPE_LEN(current_shape_id); - if (index >= SHAPE_MAX_FIELDS) { - rb_raise(rb_eArgError, "too many instance variables"); - } + switch(type) { + case T_CLASS: + case T_MODULE: + IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); - shape_id_t next_shape_id = rb_shape_transition_add_ivar(obj, id); - if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { - transition_too_complex_func(obj, data); - goto too_complex; - } - else if (UNLIKELY(RSHAPE_CAPACITY(next_shape_id) != RSHAPE_CAPACITY(current_shape_id))) { - RUBY_ASSERT(RSHAPE_CAPACITY(next_shape_id) > RSHAPE_CAPACITY(current_shape_id)); - shape_resize_fields_func(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(next_shape_id), data); + fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (rb_multi_ractor_p()) { + concurrent = true; } - - RUBY_ASSERT(RSHAPE_TYPE_P(next_shape_id, SHAPE_IVAR)); - RUBY_ASSERT(index == (RSHAPE_INDEX(next_shape_id))); - set_shape_id_func(obj, next_shape_id, data); + break; + case T_OBJECT: + fields_obj = obj; + break; + default: { + fields_obj = rb_obj_fields(obj, id); + break; + } } - VALUE *table = shape_fields_func(obj, data); - RB_OBJ_WRITE(obj, &table[index], val); - - result.index = index; - return result; - -too_complex: - { - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + if (!fields_obj) { + return undef; + } - st_table *table = too_complex_table_func(obj, data); - result.existing = st_insert(table, (st_data_t)id, (st_data_t)val); - result.index = 0; - RB_OBJ_WRITTEN(obj, Qundef, val); + const VALUE original_fields_obj = fields_obj; + if (concurrent) { + fields_obj = rb_imemo_fields_clone(fields_obj); } - return result; -} -static void -general_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val, void *data, - VALUE *(*shape_fields_func)(VALUE, void *), - void (*shape_resize_fields_func)(VALUE, attr_index_t, attr_index_t, void *), - void (*set_shape_id_func)(VALUE, shape_id_t, void *), - void (*transition_too_complex_func)(VALUE, void *), - st_table *(*too_complex_table_func)(VALUE, void *)) -{ - shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); + shape_id_t old_shape_id = RBASIC_SHAPE_ID(fields_obj); + shape_id_t removed_shape_id; + shape_id_t next_shape_id = rb_shape_transition_remove_ivar(fields_obj, id, &removed_shape_id); - if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { - if (UNLIKELY(!rb_shape_too_complex_p(current_shape_id))) { - transition_too_complex_func(obj, data); + if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { + if (UNLIKELY(!rb_shape_too_complex_p(old_shape_id))) { + if (type == T_OBJECT) { + rb_evict_fields_to_hash(obj); + } + else { + fields_obj = imemo_fields_complex_from_obj(obj, fields_obj, next_shape_id); + } } - - st_table *table = too_complex_table_func(obj, data); - if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { - set_shape_id_func(obj, target_shape_id, data); + st_data_t key = id; + if (!st_delete(rb_imemo_fields_complex_tbl(fields_obj), &key, (st_data_t *)&val)) { + val = undef; } - - st_insert(table, (st_data_t)RSHAPE_EDGE_NAME(target_shape_id), (st_data_t)val); - RB_OBJ_WRITTEN(obj, Qundef, val); } else { - attr_index_t index = RSHAPE_INDEX(target_shape_id); - if (index >= RSHAPE_CAPACITY(current_shape_id)) { - shape_resize_fields_func(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(target_shape_id), data); + if (next_shape_id == old_shape_id) { + return undef; } - if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { - set_shape_id_func(obj, target_shape_id, data); - } + RUBY_ASSERT(removed_shape_id != INVALID_SHAPE_ID); + RUBY_ASSERT(RSHAPE_LEN(next_shape_id) == RSHAPE_LEN(old_shape_id) - 1); - VALUE *table = shape_fields_func(obj, data); - RB_OBJ_WRITE(obj, &table[index], val); - } -} + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + attr_index_t removed_index = RSHAPE_INDEX(removed_shape_id); + val = fields[removed_index]; -struct gen_fields_lookup_ensure_size { - VALUE obj; - ID id; - struct gen_fields_tbl *fields_tbl; - shape_id_t shape_id; - bool resize; -}; + attr_index_t new_fields_count = RSHAPE_LEN(next_shape_id); + if (new_fields_count) { + size_t trailing_fields = new_fields_count - removed_index; -static int -generic_fields_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing) -{ - ASSERT_vm_locking(); + MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields); + RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); - struct gen_fields_lookup_ensure_size *fields_lookup = (struct gen_fields_lookup_ensure_size *)u; - struct gen_fields_tbl *fields_tbl = existing ? (struct gen_fields_tbl *)*v : NULL; - - if (!existing || fields_lookup->resize) { - if (existing) { - RUBY_ASSERT(RSHAPE(fields_lookup->shape_id)->type == SHAPE_IVAR || RSHAPE(fields_lookup->shape_id)->type == SHAPE_OBJ_ID); - RUBY_ASSERT(RSHAPE(RSHAPE(fields_lookup->shape_id)->parent_id)->capacity < RSHAPE(fields_lookup->shape_id)->capacity); + if (FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP) && rb_obj_embedded_size(new_fields_count) <= rb_gc_obj_slot_size(fields_obj)) { + // Re-embed objects when instances become small enough + // This is necessary because YJIT assumes that objects with the same shape + // have the same embeddedness for efficiency (avoid extra checks) + FL_UNSET_RAW(fields_obj, ROBJECT_HEAP); + MEMCPY(rb_imemo_fields_ptr(fields_obj), fields, VALUE, new_fields_count); + xfree(fields); + } } else { - FL_SET_RAW((VALUE)*k, FL_EXIVAR); + fields_obj = 0; + rb_free_generic_ivar(obj); } - - fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE(fields_lookup->shape_id)->capacity); - *v = (st_data_t)fields_tbl; } - RUBY_ASSERT(FL_TEST((VALUE)*k, FL_EXIVAR)); - - fields_lookup->fields_tbl = fields_tbl; - if (fields_lookup->shape_id) { - rb_obj_set_shape_id(fields_lookup->obj, fields_lookup->shape_id); + RBASIC_SET_SHAPE_ID(obj, next_shape_id); + if (fields_obj != original_fields_obj) { + switch (type) { + case T_OBJECT: + break; + case T_CLASS: + case T_MODULE: + RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, fields_obj); + break; + default: + rb_obj_set_fields(obj, fields_obj, id, original_fields_obj); + break; + } } - return ST_CONTINUE; + return val; } -static VALUE * -generic_ivar_set_shape_fields(VALUE obj, void *data) +VALUE +rb_attr_delete(VALUE obj, ID id) { - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - - struct gen_fields_lookup_ensure_size *fields_lookup = data; - - RB_VM_LOCKING() { - st_update(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, generic_fields_lookup_ensure_size, (st_data_t)fields_lookup); - } + return rb_ivar_delete(obj, id, Qnil); +} - FL_SET_RAW(obj, FL_EXIVAR); +void +rb_obj_init_too_complex(VALUE obj, st_table *table) +{ + // This method is meant to be called on newly allocated object. + RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); + RUBY_ASSERT(rb_shape_canonical_p(RBASIC_SHAPE_ID(obj))); + RUBY_ASSERT(RSHAPE_LEN(RBASIC_SHAPE_ID(obj)) == 0); - return fields_lookup->fields_tbl->as.shape.fields; + obj_transition_too_complex(obj, table); } -static void -generic_ivar_set_shape_resize_fields(VALUE obj, attr_index_t _old_capa, attr_index_t new_capa, void *data) +static int +imemo_fields_complex_from_obj_i(ID key, VALUE val, st_data_t arg) { - struct gen_fields_lookup_ensure_size *fields_lookup = data; + VALUE fields = (VALUE)arg; + st_table *table = rb_imemo_fields_complex_tbl(fields); + + RUBY_ASSERT(!st_lookup(table, (st_data_t)key, NULL)); + st_add_direct(table, (st_data_t)key, (st_data_t)val); + RB_OBJ_WRITTEN(fields, Qundef, val); - fields_lookup->resize = true; + return ST_CONTINUE; } -static void -generic_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *data) +static VALUE +imemo_fields_complex_from_obj(VALUE owner, VALUE source_fields_obj, shape_id_t shape_id) { - struct gen_fields_lookup_ensure_size *fields_lookup = data; + attr_index_t len = source_fields_obj ? RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj)) : 0; + VALUE fields_obj = rb_imemo_fields_new_complex(owner, len + 1, RB_OBJ_SHAREABLE_P(owner)); + + rb_field_foreach(source_fields_obj, imemo_fields_complex_from_obj_i, (st_data_t)fields_obj, false); + RBASIC_SET_SHAPE_ID(fields_obj, shape_id); - fields_lookup->shape_id = shape_id; + return fields_obj; } -static void -generic_ivar_set_transition_too_complex(VALUE obj, void *_data) -{ - rb_evict_fields_to_hash(obj); - FL_SET_RAW(obj, FL_EXIVAR); +static VALUE +imemo_fields_copy_capa(VALUE owner, VALUE source_fields_obj, attr_index_t new_size) +{ + VALUE fields_obj = rb_imemo_fields_new(owner, new_size, RB_OBJ_SHAREABLE_P(owner)); + if (source_fields_obj) { + attr_index_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj)); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + MEMCPY(fields, rb_imemo_fields_ptr(source_fields_obj), VALUE, fields_count); + RBASIC_SET_SHAPE_ID(fields_obj, RBASIC_SHAPE_ID(source_fields_obj)); + for (attr_index_t i = 0; i < fields_count; i++) { + RB_OBJ_WRITTEN(fields_obj, Qundef, fields[i]); + } + } + return fields_obj; } -static st_table * -generic_ivar_set_too_complex_table(VALUE obj, void *data) +static VALUE +imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID field_name, VALUE val, bool concurrent) { - struct gen_fields_lookup_ensure_size *fields_lookup = data; + const VALUE original_fields_obj = fields_obj; + shape_id_t current_shape_id = fields_obj ? RBASIC_SHAPE_ID(fields_obj) : ROOT_SHAPE_ID; - struct gen_fields_tbl *fields_tbl; - if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - fields_tbl = xmalloc(sizeof(struct gen_fields_tbl)); - fields_tbl->as.complex.table = st_init_numtable_with_size(1); - - RB_VM_LOCKING() { - st_insert(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, (st_data_t)fields_tbl); + if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { + if (rb_shape_too_complex_p(current_shape_id)) { + if (concurrent) { + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. + fields_obj = rb_imemo_fields_clone(fields_obj); + } } + else { + fields_obj = imemo_fields_complex_from_obj(owner, original_fields_obj, target_shape_id); + current_shape_id = target_shape_id; + } + + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); - FL_SET_RAW(obj, FL_EXIVAR); + RUBY_ASSERT(field_name); + st_insert(table, (st_data_t)field_name, (st_data_t)val); + RB_OBJ_WRITTEN(fields_obj, Qundef, val); + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); } + else { + attr_index_t index = RSHAPE_INDEX(target_shape_id); + if (concurrent || index >= RSHAPE_CAPACITY(current_shape_id)) { + fields_obj = imemo_fields_copy_capa(owner, original_fields_obj, RSHAPE_CAPACITY(target_shape_id)); + } - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + VALUE *table = rb_imemo_fields_ptr(fields_obj); + RB_OBJ_WRITE(fields_obj, &table[index], val); + + if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + } + } - return fields_tbl->as.complex.table; + return fields_obj; } -static void -generic_ivar_set(VALUE obj, ID id, VALUE val) +static attr_index_t +generic_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val) { - struct gen_fields_lookup_ensure_size fields_lookup = { - .obj = obj, - .id = id, - .resize = false, - }; + if (!field_name) { + field_name = RSHAPE_EDGE_NAME(target_shape_id); + RUBY_ASSERT(field_name); + } + + const VALUE original_fields_obj = rb_obj_fields(obj, field_name); + VALUE fields_obj = imemo_fields_set(obj, original_fields_obj, target_shape_id, field_name, val, false); - general_ivar_set(obj, id, val, &fields_lookup, - generic_ivar_set_shape_fields, - generic_ivar_set_shape_resize_fields, - generic_ivar_set_set_shape_id, - generic_ivar_set_transition_too_complex, - generic_ivar_set_too_complex_table); + rb_obj_set_fields(obj, fields_obj, field_name, original_fields_obj); + return rb_shape_too_complex_p(target_shape_id) ? ATTR_INDEX_NOT_SET : RSHAPE_INDEX(target_shape_id); } -static void -generic_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) +static shape_id_t +generic_shape_ivar(VALUE obj, ID id, bool *new_ivar_out) { - struct gen_fields_lookup_ensure_size fields_lookup = { - .obj = obj, - .resize = false, - }; + bool new_ivar = false; + shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); + shape_id_t target_shape_id = current_shape_id; + + if (!rb_shape_too_complex_p(current_shape_id)) { + if (!rb_shape_find_ivar(current_shape_id, id, &target_shape_id)) { + if (RSHAPE_LEN(current_shape_id) >= SHAPE_MAX_FIELDS) { + rb_raise(rb_eArgError, "too many instance variables"); + } + + new_ivar = true; + target_shape_id = rb_shape_transition_add_ivar(obj, id); + } + } + + *new_ivar_out = new_ivar; + return target_shape_id; +} - general_field_set(obj, target_shape_id, val, &fields_lookup, - generic_ivar_set_shape_fields, - generic_ivar_set_shape_resize_fields, - generic_ivar_set_set_shape_id, - generic_ivar_set_transition_too_complex, - generic_ivar_set_too_complex_table); +static attr_index_t +generic_ivar_set(VALUE obj, ID id, VALUE val) +{ + bool dontcare; + shape_id_t target_shape_id = generic_shape_ivar(obj, id, &dontcare); + return generic_field_set(obj, target_shape_id, id, val); } void -rb_ensure_iv_list_size(VALUE obj, uint32_t current_capacity, uint32_t new_capacity) +rb_ensure_iv_list_size(VALUE obj, uint32_t current_len, uint32_t new_capacity) { RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - if (RBASIC(obj)->flags & ROBJECT_EMBED) { + if (FL_TEST_RAW(obj, ROBJECT_HEAP)) { + REALLOC_N(ROBJECT(obj)->as.heap.fields, VALUE, new_capacity); + } + else { VALUE *ptr = ROBJECT_FIELDS(obj); VALUE *newptr = ALLOC_N(VALUE, new_capacity); - MEMCPY(newptr, ptr, VALUE, current_capacity); - RB_FL_UNSET_RAW(obj, ROBJECT_EMBED); + MEMCPY(newptr, ptr, VALUE, current_len); + FL_SET_RAW(obj, ROBJECT_HEAP); ROBJECT(obj)->as.heap.fields = newptr; } - else { - REALLOC_N(ROBJECT(obj)->as.heap.fields, VALUE, new_capacity); - } } static int @@ -1969,60 +1915,52 @@ rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table) rb_field_foreach(obj, rb_obj_copy_ivs_to_hash_table_i, (st_data_t)table, false); } -static VALUE * -obj_ivar_set_shape_fields(VALUE obj, void *_data) +static attr_index_t +obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val) { - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); + shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); - return ROBJECT_FIELDS(obj); -} + if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { + if (UNLIKELY(!rb_shape_too_complex_p(current_shape_id))) { + current_shape_id = rb_evict_fields_to_hash(obj); + } -static void -obj_ivar_set_shape_resize_fields(VALUE obj, attr_index_t old_capa, attr_index_t new_capa, void *_data) -{ - rb_ensure_iv_list_size(obj, old_capa, new_capa); -} + if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { + RBASIC_SET_SHAPE_ID(obj, target_shape_id); + } -static void -obj_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *_data) -{ - rb_obj_set_shape_id(obj, shape_id); -} + if (!field_name) { + field_name = RSHAPE_EDGE_NAME(target_shape_id); + RUBY_ASSERT(field_name); + } -static void -obj_ivar_set_transition_too_complex(VALUE obj, void *_data) -{ - rb_evict_fields_to_hash(obj); -} + st_insert(ROBJECT_FIELDS_HASH(obj), (st_data_t)field_name, (st_data_t)val); + RB_OBJ_WRITTEN(obj, Qundef, val); -static st_table * -obj_ivar_set_too_complex_table(VALUE obj, void *_data) -{ - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + return ATTR_INDEX_NOT_SET; + } + else { + attr_index_t index = RSHAPE_INDEX(target_shape_id); - return ROBJECT_FIELDS_HASH(obj); -} + if (index >= RSHAPE_LEN(current_shape_id)) { + if (UNLIKELY(index >= RSHAPE_CAPACITY(current_shape_id))) { + rb_ensure_iv_list_size(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(target_shape_id)); + } + RBASIC_SET_SHAPE_ID(obj, target_shape_id); + } -attr_index_t -rb_obj_ivar_set(VALUE obj, ID id, VALUE val) -{ - return general_ivar_set(obj, id, val, NULL, - obj_ivar_set_shape_fields, - obj_ivar_set_shape_resize_fields, - obj_ivar_set_set_shape_id, - obj_ivar_set_transition_too_complex, - obj_ivar_set_too_complex_table).index; + RB_OBJ_WRITE(obj, &ROBJECT_FIELDS(obj)[index], val); + + return index; + } } -static void -obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) +static attr_index_t +obj_ivar_set(VALUE obj, ID id, VALUE val) { - general_field_set(obj, target_shape_id, val, NULL, - obj_ivar_set_shape_fields, - obj_ivar_set_shape_resize_fields, - obj_ivar_set_set_shape_id, - obj_ivar_set_transition_too_complex, - obj_ivar_set_too_complex_table); + bool dontcare; + shape_id_t target_shape_id = generic_shape_ivar(obj, id, &dontcare); + return obj_field_set(obj, target_shape_id, id, val); } /* Set the instance variable +val+ on object +obj+ at ivar name +id+. @@ -2033,21 +1971,10 @@ VALUE rb_vm_set_ivar_id(VALUE obj, ID id, VALUE val) { rb_check_frozen(obj); - rb_obj_ivar_set(obj, id, val); + obj_ivar_set(obj, id, val); return val; } -bool -rb_obj_set_shape_id(VALUE obj, shape_id_t shape_id) -{ - if (rb_obj_shape_id(obj) == shape_id) { - return false; - } - - RBASIC_SET_SHAPE_ID(obj, shape_id); - return true; -} - void rb_obj_freeze_inline(VALUE x) { if (RB_FL_ABLE(x)) { @@ -2056,14 +1983,7 @@ void rb_obj_freeze_inline(VALUE x) RB_FL_UNSET_RAW(x, FL_USER2 | FL_USER3); // STR_CHILLED } - shape_id_t next_shape_id = rb_shape_transition_frozen(x); - - // If we're transitioning from "not complex" to "too complex" - // then evict ivars. This can happen if we run out of shapes - if (rb_shape_too_complex_p(next_shape_id) && !rb_shape_obj_too_complex_p(x)) { - rb_evict_fields_to_hash(x); - } - rb_obj_set_shape_id(x, next_shape_id); + RB_SET_SHAPE_ID(x, rb_shape_transition_frozen(x)); if (RBASIC_CLASS(x)) { rb_freeze_singleton_class(x); @@ -2071,26 +1991,25 @@ void rb_obj_freeze_inline(VALUE x) } } -static void +static attr_index_t class_ivar_set(VALUE obj, ID id, VALUE val, bool *new_ivar); + +static attr_index_t ivar_set(VALUE obj, ID id, VALUE val) { RB_DEBUG_COUNTER_INC(ivar_set_base); switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - { - rb_obj_ivar_set(obj, id, val); - break; - } + return obj_ivar_set(obj, id, val); case T_CLASS: case T_MODULE: - IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); - rb_class_ivar_set(obj, id, val); - - break; + { + IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); + bool dontcare; + return class_ivar_set(obj, id, val, &dontcare); + } default: - generic_ivar_set(obj, id, val); - break; + return generic_ivar_set(obj, id, val); } } @@ -2102,6 +2021,12 @@ rb_ivar_set(VALUE obj, ID id, VALUE val) return val; } +attr_index_t +rb_ivar_set_index(VALUE obj, ID id, VALUE val) +{ + return ivar_set(obj, id, val); +} + void rb_ivar_set_internal(VALUE obj, ID id, VALUE val) { @@ -2111,39 +2036,39 @@ rb_ivar_set_internal(VALUE obj, ID id, VALUE val) ivar_set(obj, id, val); } -static void class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val); - -void -rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) +attr_index_t +rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val) { switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - obj_field_set(obj, target_shape_id, val); - break; + return obj_field_set(obj, target_shape_id, field_name, val); case T_CLASS: case T_MODULE: - ASSERT_vm_locking(); - class_field_set(obj, target_shape_id, val); + // The only field is object_id and T_CLASS handle it differently. + rb_bug("Unreachable"); break; default: - generic_field_set(obj, target_shape_id, val); - break; + return generic_field_set(obj, target_shape_id, field_name, val); } } -VALUE -rb_ivar_defined(VALUE obj, ID id) +static VALUE +ivar_defined0(VALUE obj, ID id) { attr_index_t index; - if (SPECIAL_CONST_P(obj)) return Qfalse; if (rb_shape_obj_too_complex_p(obj)) { VALUE idx; st_table *table = NULL; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - table = (st_table *)RCLASS_FIELDS_HASH(obj); + rb_bug("Unreachable"); + break; + + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + table = rb_imemo_fields_complex_tbl(obj); break; case T_OBJECT: @@ -2151,11 +2076,8 @@ rb_ivar_defined(VALUE obj, ID id) break; default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; - } - break; + VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); // defined? doesn't require ractor checks + table = rb_imemo_fields_complex_tbl(fields_obj); } } @@ -2170,70 +2092,74 @@ rb_ivar_defined(VALUE obj, ID id) } } +VALUE +rb_ivar_defined(VALUE obj, ID id) +{ + if (SPECIAL_CONST_P(obj)) return Qfalse; + + VALUE defined = Qfalse; + switch (BUILTIN_TYPE(obj)) { + case T_CLASS: + case T_MODULE: + { + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + defined = ivar_defined0(fields_obj, id); + } + } + break; + default: + defined = ivar_defined0(obj, id); + break; + } + return defined; +} + struct iv_itr_data { VALUE obj; struct gen_fields_tbl *fields_tbl; st_data_t arg; rb_ivar_foreach_callback_func *func; + VALUE *fields; bool ivar_only; }; -/* - * Returns a flag to stop iterating depending on the result of +callback+. - */ -static bool -iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_func *callback, struct iv_itr_data *itr_data) +static int +iterate_over_shapes_callback(shape_id_t shape_id, void *data) { - switch ((enum shape_type)shape->type) { - case SHAPE_ROOT: - case SHAPE_T_OBJECT: - return false; - case SHAPE_OBJ_ID: - if (itr_data->ivar_only) { - return iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data); - } - // fallthrough - case SHAPE_IVAR: - ASSUME(callback); - if (iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data)) { - return true; - } + struct iv_itr_data *itr_data = data; - VALUE * iv_list; - switch (BUILTIN_TYPE(itr_data->obj)) { - case T_OBJECT: - RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); - iv_list = ROBJECT_FIELDS(itr_data->obj); - break; - case T_CLASS: - case T_MODULE: - RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); - iv_list = RCLASS_PRIME_FIELDS(itr_data->obj); - break; - default: - iv_list = itr_data->fields_tbl->as.shape.fields; - break; - } - VALUE val = iv_list[shape->next_field_index - 1]; - if (!UNDEF_P(val)) { - switch (callback(shape->edge_name, val, itr_data->arg)) { - case ST_CHECK: - case ST_CONTINUE: - break; - case ST_STOP: - return true; - default: - rb_bug("unreachable"); - } - } - return false; - case SHAPE_FROZEN: - return iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data); - case SHAPE_OBJ_TOO_COMPLEX: + if (itr_data->ivar_only && !RSHAPE_TYPE_P(shape_id, SHAPE_IVAR)) { + return ST_CONTINUE; + } + + VALUE *fields; + switch (BUILTIN_TYPE(itr_data->obj)) { + case T_OBJECT: + RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); + fields = ROBJECT_FIELDS(itr_data->obj); + break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(itr_data->obj, imemo_fields)); + RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); + + fields = rb_imemo_fields_ptr(itr_data->obj); + break; default: rb_bug("Unreachable"); - UNREACHABLE_RETURN(false); } + + VALUE val = fields[RSHAPE_INDEX(shape_id)]; + return itr_data->func(RSHAPE_EDGE_NAME(shape_id), val, itr_data->arg); +} + +/* + * Returns a flag to stop iterating depending on the result of +callback+. + */ +static void +iterate_over_shapes(shape_id_t shape_id, rb_ivar_foreach_callback_func *callback, struct iv_itr_data *itr_data) +{ + rb_shape_foreach_field(shape_id, iterate_over_shapes_callback, itr_data); } static int @@ -2241,6 +2167,9 @@ each_hash_iv(st_data_t id, st_data_t val, st_data_t data) { struct iv_itr_data * itr_data = (struct iv_itr_data *)data; rb_ivar_foreach_callback_func *callback = itr_data->func; + if (is_internal_id((ID)id)) { + return ST_CONTINUE; + } return callback((ID)id, (VALUE)val, itr_data->arg); } @@ -2259,81 +2188,55 @@ obj_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, b rb_st_foreach(ROBJECT_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data); } else { - iterate_over_shapes_with_callback(RSHAPE(shape_id), func, &itr_data); + itr_data.fields = ROBJECT_FIELDS(obj); + iterate_over_shapes(shape_id, func, &itr_data); } } static void -gen_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) +imemo_fields_each(VALUE fields_obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) { - struct gen_fields_tbl *fields_tbl; - if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) return; + IMEMO_TYPE_P(fields_obj, imemo_fields); struct iv_itr_data itr_data = { - .obj = obj, - .fields_tbl = fields_tbl, + .obj = fields_obj, .arg = arg, .func = func, .ivar_only = ivar_only, }; - shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj); if (rb_shape_too_complex_p(shape_id)) { - rb_st_foreach(fields_tbl->as.complex.table, each_hash_iv, (st_data_t)&itr_data); + rb_st_foreach(rb_imemo_fields_complex_tbl(fields_obj), each_hash_iv, (st_data_t)&itr_data); } else { - iterate_over_shapes_with_callback(RSHAPE(shape_id), func, &itr_data); - } -} - -static void -class_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) -{ - RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - - struct iv_itr_data itr_data = { - .obj = obj, - .arg = arg, - .func = func, - .ivar_only = ivar_only, - }; - - shape_id_t shape_id = RBASIC_SHAPE_ID(obj); - if (rb_shape_too_complex_p(shape_id)) { - rb_st_foreach(RCLASS_WRITABLE_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data); - } - else { - iterate_over_shapes_with_callback(RSHAPE(shape_id), func, &itr_data); + itr_data.fields = rb_imemo_fields_ptr(fields_obj); + iterate_over_shapes(shape_id, func, &itr_data); } } void rb_copy_generic_ivar(VALUE dest, VALUE obj) { - struct gen_fields_tbl *obj_fields_tbl; - struct gen_fields_tbl *new_fields_tbl; + VALUE new_fields_obj; rb_check_frozen(dest); - if (!FL_TEST(obj, FL_EXIVAR)) { - goto clear; - } - - unsigned long src_num_ivs = rb_ivar_count(obj); - if (!src_num_ivs) { - goto clear; + if (!rb_obj_gen_fields_p(obj)) { + return; } shape_id_t src_shape_id = rb_obj_shape_id(obj); - if (rb_gen_fields_tbl_get(obj, 0, &obj_fields_tbl)) { - if (gen_fields_tbl_count(obj, obj_fields_tbl) == 0) + VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); + if (fields_obj) { + unsigned long src_num_ivs = rb_ivar_count(fields_obj); + if (!src_num_ivs) { goto clear; - - FL_SET(dest, FL_EXIVAR); + } if (rb_shape_too_complex_p(src_shape_id)) { - rb_shape_copy_complex_ivars(dest, obj, src_shape_id, obj_fields_tbl->as.complex.table); + rb_shape_copy_complex_ivars(dest, obj, src_shape_id, rb_imemo_fields_complex_tbl(fields_obj)); return; } @@ -2341,64 +2244,44 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) shape_id_t initial_shape_id = rb_obj_shape_id(dest); if (!rb_shape_canonical_p(src_shape_id)) { - RUBY_ASSERT(RSHAPE(initial_shape_id)->type == SHAPE_ROOT); + RUBY_ASSERT(RSHAPE_TYPE_P(initial_shape_id, SHAPE_ROOT)); dest_shape_id = rb_shape_rebuild(initial_shape_id, src_shape_id); if (UNLIKELY(rb_shape_too_complex_p(dest_shape_id))) { st_table *table = rb_st_init_numtable_with_size(src_num_ivs); rb_obj_copy_ivs_to_hash_table(obj, table); rb_obj_init_too_complex(dest, table); - return; } } - if (!RSHAPE(dest_shape_id)->capacity) { - rb_obj_set_shape_id(dest, dest_shape_id); - FL_UNSET(dest, FL_EXIVAR); + if (!RSHAPE_LEN(dest_shape_id)) { + RBASIC_SET_SHAPE_ID(dest, dest_shape_id); return; } - new_fields_tbl = gen_fields_tbl_resize(0, RSHAPE(dest_shape_id)->capacity); - - VALUE *src_buf = obj_fields_tbl->as.shape.fields; - VALUE *dest_buf = new_fields_tbl->as.shape.fields; + new_fields_obj = rb_imemo_fields_new(dest, RSHAPE_CAPACITY(dest_shape_id), RB_OBJ_SHAREABLE_P(dest)); + VALUE *src_buf = rb_imemo_fields_ptr(fields_obj); + VALUE *dest_buf = rb_imemo_fields_ptr(new_fields_obj); + rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, src_buf, src_shape_id); + RBASIC_SET_SHAPE_ID(new_fields_obj, dest_shape_id); - rb_shape_copy_fields(dest, dest_buf, dest_shape_id, obj, src_buf, src_shape_id); - - /* - * c.fields_tbl may change in gen_fields_copy due to realloc, - * no need to free - */ - RB_VM_LOCKING() { - generic_fields_tbl_no_ractor_check(dest); - st_insert(generic_fields_tbl_no_ractor_check(obj), (st_data_t)dest, (st_data_t)new_fields_tbl); - } - - rb_obj_set_shape_id(dest, dest_shape_id); + rb_obj_replace_fields(dest, new_fields_obj); } return; clear: - if (FL_TEST(dest, FL_EXIVAR)) { - RBASIC_SET_SHAPE_ID(dest, ROOT_SHAPE_ID); - rb_free_generic_ivar(dest); - FL_UNSET(dest, FL_EXIVAR); - } + rb_free_generic_ivar(dest); } void rb_replace_generic_ivar(VALUE clone, VALUE obj) { - RUBY_ASSERT(FL_TEST(obj, FL_EXIVAR)); - RB_VM_LOCKING() { st_data_t fields_tbl, obj_data = (st_data_t)obj; if (st_delete(generic_fields_tbl_, &obj_data, &fields_tbl)) { - FL_UNSET_RAW(obj, FL_EXIVAR); - st_insert(generic_fields_tbl_, (st_data_t)clone, fields_tbl); - FL_SET_RAW(clone, FL_EXIVAR); + RB_OBJ_WRITTEN(clone, Qundef, fields_tbl); } else { rb_bug("unreachable"); @@ -2411,19 +2294,30 @@ rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, { if (SPECIAL_CONST_P(obj)) return; switch (BUILTIN_TYPE(obj)) { + case T_IMEMO: + if (IMEMO_TYPE_P(obj, imemo_fields)) { + imemo_fields_each(obj, func, arg, ivar_only); + } + break; case T_OBJECT: obj_fields_each(obj, func, arg, ivar_only); break; case T_CLASS: case T_MODULE: - IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0); - RB_VM_LOCKING() { - class_fields_each(obj, func, arg, ivar_only); + { + IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0); + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + imemo_fields_each(fields_obj, func, arg, ivar_only); + } } break; default: - if (FL_TEST(obj, FL_EXIVAR)) { - gen_fields_each(obj, func, arg, ivar_only); + { + VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); + if (fields_obj) { + imemo_fields_each(fields_obj, func, arg, ivar_only); + } } break; } @@ -2445,16 +2339,44 @@ rb_ivar_count(VALUE obj) case T_OBJECT: iv_count = ROBJECT_FIELDS_COUNT(obj); break; + case T_CLASS: case T_MODULE: - iv_count = RCLASS_FIELDS_COUNT(obj); + { + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (!fields_obj) { + return 0; + } + if (rb_shape_obj_too_complex_p(fields_obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + } + else { + iv_count = RBASIC_FIELDS_COUNT(fields_obj); + } + } + break; + + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + + if (rb_shape_obj_too_complex_p(obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(obj)); + } + else { + iv_count = RBASIC_FIELDS_COUNT(obj); + } break; - default: - if (FL_TEST(obj, FL_EXIVAR)) { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - iv_count = gen_fields_tbl_count(obj, fields_tbl); + default: + { + VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); + if (fields_obj) { + if (rb_shape_obj_too_complex_p(fields_obj)) { + rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + } + else { + iv_count = RBASIC_FIELDS_COUNT(obj); + } } } break; @@ -2716,9 +2638,9 @@ struct autoload_const { // The shared "autoload_data" if multiple constants are defined from the same feature. VALUE autoload_data_value; - // The namespace object when the autoload is called in a user namespace - // Otherwise, Qnil means the builtin namespace, Qfalse means unspecified. - VALUE namespace; + // The box object when the autoload is called in a user box + // Otherwise, Qnil means the root box + VALUE box_value; // The module we are loading a constant into. VALUE module; @@ -2753,21 +2675,12 @@ struct autoload_data { }; static void -autoload_data_compact(void *ptr) -{ - struct autoload_data *p = ptr; - - p->feature = rb_gc_location(p->feature); - p->mutex = rb_gc_location(p->mutex); -} - -static void -autoload_data_mark(void *ptr) +autoload_data_mark_and_move(void *ptr) { struct autoload_data *p = ptr; - rb_gc_mark_movable(p->feature); - rb_gc_mark_movable(p->mutex); + rb_gc_mark_and_move(&p->feature); + rb_gc_mark_and_move(&p->mutex); } static void @@ -2791,32 +2704,20 @@ autoload_data_memsize(const void *ptr) static const rb_data_type_t autoload_data_type = { "autoload_data", - {autoload_data_mark, autoload_data_free, autoload_data_memsize, autoload_data_compact}, + {autoload_data_mark_and_move, autoload_data_free, autoload_data_memsize, autoload_data_mark_and_move}, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; static void -autoload_const_compact(void *ptr) +autoload_const_mark_and_move(void *ptr) { struct autoload_const *ac = ptr; - ac->module = rb_gc_location(ac->module); - ac->autoload_data_value = rb_gc_location(ac->autoload_data_value); - ac->value = rb_gc_location(ac->value); - ac->file = rb_gc_location(ac->file); - ac->namespace = rb_gc_location(ac->namespace); -} - -static void -autoload_const_mark(void *ptr) -{ - struct autoload_const *ac = ptr; - - rb_gc_mark_movable(ac->module); - rb_gc_mark_movable(ac->autoload_data_value); - rb_gc_mark_movable(ac->value); - rb_gc_mark_movable(ac->file); - rb_gc_mark_movable(ac->namespace); + rb_gc_mark_and_move(&ac->module); + rb_gc_mark_and_move(&ac->autoload_data_value); + rb_gc_mark_and_move(&ac->value); + rb_gc_mark_and_move(&ac->file); + rb_gc_mark_and_move(&ac->box_value); } static size_t @@ -2836,8 +2737,8 @@ autoload_const_free(void *ptr) static const rb_data_type_t autoload_const_type = { "autoload_const", - {autoload_const_mark, autoload_const_free, autoload_const_memsize, autoload_const_compact,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + {autoload_const_mark_and_move, autoload_const_free, autoload_const_memsize, autoload_const_mark_and_move,}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; static struct autoload_data * @@ -2862,17 +2763,17 @@ get_autoload_data(VALUE autoload_const_value, struct autoload_const **autoload_c struct autoload_copy_table_data { VALUE dst_tbl_value; struct st_table *dst_tbl; - const rb_namespace_t *ns; + const rb_box_t *box; }; static int -autoload_copy_table_for_namespace_i(st_data_t key, st_data_t value, st_data_t arg) +autoload_copy_table_for_box_i(st_data_t key, st_data_t value, st_data_t arg) { struct autoload_const *autoload_const; struct autoload_copy_table_data *data = (struct autoload_copy_table_data *)arg; struct st_table *tbl = data->dst_tbl; VALUE tbl_value = data->dst_tbl_value; - const rb_namespace_t *ns = data->ns; + const rb_box_t *box = data->box; VALUE src_value = (VALUE)value; struct autoload_const *src_const = rb_check_typeddata(src_value, &autoload_const_type); @@ -2881,12 +2782,12 @@ autoload_copy_table_for_namespace_i(st_data_t key, st_data_t value, st_data_t ar struct autoload_data *autoload_data = rb_check_typeddata(autoload_data_value, &autoload_data_type); VALUE new_value = TypedData_Make_Struct(0, struct autoload_const, &autoload_const_type, autoload_const); - autoload_const->namespace = rb_get_namespace_object((rb_namespace_t *)ns); - autoload_const->module = src_const->module; + RB_OBJ_WRITE(new_value, &autoload_const->box_value, rb_get_box_object((rb_box_t *)box)); + RB_OBJ_WRITE(new_value, &autoload_const->module, src_const->module); autoload_const->name = src_const->name; - autoload_const->value = src_const->value; + RB_OBJ_WRITE(new_value, &autoload_const->value, src_const->value); autoload_const->flag = src_const->flag; - autoload_const->autoload_data_value = autoload_data_value; + RB_OBJ_WRITE(new_value, &autoload_const->autoload_data_value, autoload_data_value); ccan_list_add_tail(&autoload_data->constants, &autoload_const->cnode); st_insert(tbl, (st_data_t)autoload_const->name, (st_data_t)new_value); @@ -2896,7 +2797,7 @@ autoload_copy_table_for_namespace_i(st_data_t key, st_data_t value, st_data_t ar } void -rb_autoload_copy_table_for_namespace(st_table *iv_ptr, const rb_namespace_t *ns) +rb_autoload_copy_table_for_box(st_table *iv_ptr, const rb_box_t *box) { struct st_table *src_tbl, *dst_tbl; VALUE src_tbl_value, dst_tbl_value; @@ -2916,10 +2817,10 @@ rb_autoload_copy_table_for_namespace(st_table *iv_ptr, const rb_namespace_t *ns) struct autoload_copy_table_data data = { .dst_tbl_value = dst_tbl_value, .dst_tbl = dst_tbl, - .ns = ns, + .box = box, }; - st_foreach(src_tbl, autoload_copy_table_for_namespace_i, (st_data_t)&data); + st_foreach(src_tbl, autoload_copy_table_for_box_i, (st_data_t)&data); st_insert(iv_ptr, (st_data_t)autoload, (st_data_t)dst_tbl_value); } @@ -2940,7 +2841,7 @@ struct autoload_arguments { VALUE module; ID name; VALUE feature; - VALUE namespace; + VALUE box_value; }; static VALUE @@ -3010,12 +2911,12 @@ autoload_synchronized(VALUE _arguments) { struct autoload_const *autoload_const; VALUE autoload_const_value = TypedData_Make_Struct(0, struct autoload_const, &autoload_const_type, autoload_const); - autoload_const->namespace = arguments->namespace; - autoload_const->module = arguments->module; + RB_OBJ_WRITE(autoload_const_value, &autoload_const->box_value, arguments->box_value); + RB_OBJ_WRITE(autoload_const_value, &autoload_const->module, arguments->module); autoload_const->name = arguments->name; autoload_const->value = Qundef; autoload_const->flag = CONST_PUBLIC; - autoload_const->autoload_data_value = autoload_data_value; + RB_OBJ_WRITE(autoload_const_value, &autoload_const->autoload_data_value, autoload_data_value); ccan_list_add_tail(&autoload_data->constants, &autoload_const->cnode); st_insert(autoload_table, (st_data_t)arguments->name, (st_data_t)autoload_const_value); RB_OBJ_WRITTEN(autoload_table_value, Qundef, autoload_const_value); @@ -3027,8 +2928,8 @@ autoload_synchronized(VALUE _arguments) void rb_autoload_str(VALUE module, ID name, VALUE feature) { - const rb_namespace_t *ns = rb_current_namespace(); - VALUE current_namespace = rb_get_namespace_object((rb_namespace_t *)ns); + const rb_box_t *box = rb_current_box(); + VALUE current_box_value = rb_get_box_object((rb_box_t *)box); if (!rb_is_const_id(name)) { rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(name)); @@ -3043,7 +2944,7 @@ rb_autoload_str(VALUE module, ID name, VALUE feature) .module = module, .name = name, .feature = feature, - .namespace = current_namespace, + .box_value = current_box_value, }; VALUE result = rb_mutex_synchronize(autoload_mutex, autoload_synchronized, (VALUE)&arguments); @@ -3302,43 +3203,6 @@ autoload_apply_constants(VALUE _arguments) return Qtrue; } -struct autoload_feature_require_data { - struct autoload_load_arguments *arguments; - VALUE receiver; - VALUE feature; -}; - -static VALUE -autoload_feature_require_in_builtin(VALUE arg) -{ - struct autoload_feature_require_data *data = (struct autoload_feature_require_data *)arg; - - VALUE result = rb_funcall(data->receiver, rb_intern("require"), 1, data->feature); - if (RTEST(result)) { - return rb_mutex_synchronize(autoload_mutex, autoload_apply_constants, (VALUE)data->arguments); - } - return Qnil; -} - -static VALUE -autoload_feature_require_ensure_in_builtin(VALUE _arg) -{ - /* - * The gccct should be cleared again after the rb_funcall() to remove - * the inconsistent cache entry against the current namespace. - */ - rb_gccct_clear_table(Qnil); - rb_namespace_disable_builtin(); - return Qnil; -} - -static VALUE -autoload_feature_require_in_builtin_wrap(VALUE arg) -{ - return rb_ensure(autoload_feature_require_in_builtin, arg, - autoload_feature_require_ensure_in_builtin, Qnil); -} - static VALUE autoload_feature_require(VALUE _arguments) { @@ -3347,31 +3211,21 @@ autoload_feature_require(VALUE _arguments) struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments; struct autoload_const *autoload_const = arguments->autoload_const; - VALUE autoload_namespace = autoload_const->namespace; + VALUE autoload_box_value = autoload_const->box_value; // We save this for later use in autoload_apply_constants: arguments->autoload_data = rb_check_typeddata(autoload_const->autoload_data_value, &autoload_data_type); - if (NIL_P(autoload_namespace)) { - rb_namespace_enable_builtin(); - /* - * Clear the global cc cache table because the require method can be different from the current - * namespace's one and it may cause inconsistent cc-cme states. - * For example, the assertion below may fail in gccct_method_search(); - * VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))) - */ - rb_gccct_clear_table(Qnil); - struct autoload_feature_require_data data = { - .arguments = arguments, - .receiver = receiver, - .feature = arguments->autoload_data->feature, - }; - return rb_namespace_exec(rb_builtin_namespace(), autoload_feature_require_in_builtin_wrap, (VALUE)&data); - } + if (rb_box_available() && BOX_OBJ_P(autoload_box_value)) + receiver = autoload_box_value; - if (RTEST(autoload_namespace) && NAMESPACE_OPTIONAL_P(rb_get_namespace_t(autoload_namespace))) { - receiver = autoload_namespace; - } + /* + * Clear the global cc cache table because the require method can be different from the current + * box's one and it may cause inconsistent cc-cme states. + * For example, the assertion below may fail in gccct_method_search(); + * VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))) + */ + rb_gccct_clear_table(Qnil); VALUE result = rb_funcall(receiver, rb_intern("require"), 1, arguments->autoload_data->feature); @@ -3498,11 +3352,12 @@ rb_const_warn_if_deprecated(const rb_const_entry_t *ce, VALUE klass, ID id) static VALUE rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) { - VALUE c = rb_const_search(klass, id, exclude, recurse, visibility); + VALUE found_in; + VALUE c = rb_const_search(klass, id, exclude, recurse, visibility, &found_in); if (!UNDEF_P(c)) { if (UNLIKELY(!rb_ractor_main_p())) { if (!rb_ractor_shareable_p(c)) { - rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%s by non-main Ractor.", rb_class_path(klass), rb_id2name(id)); + rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main Ractor.", rb_class_path(found_in), rb_id2str(id)); } } return c; @@ -3511,7 +3366,7 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) } static VALUE -rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibility) +rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibility, VALUE *found_in) { VALUE value, current; bool first_iteration = true; @@ -3548,13 +3403,17 @@ rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibilit if (am == tmp) break; am = tmp; ac = autoloading_const_entry(tmp, id); - if (ac) return ac->value; + if (ac) { + if (found_in) { *found_in = tmp; } + return ac->value; + } rb_autoload_load(tmp, id); continue; } if (exclude && tmp == rb_cObject) { goto not_found; } + if (found_in) { *found_in = tmp; } return value; } if (!recurse) break; @@ -3566,17 +3425,17 @@ rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibilit } static VALUE -rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility) +rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility, VALUE *found_in) { VALUE value; if (klass == rb_cObject) exclude = FALSE; - value = rb_const_search_from(klass, id, exclude, recurse, visibility); + value = rb_const_search_from(klass, id, exclude, recurse, visibility, found_in); if (!UNDEF_P(value)) return value; if (exclude) return value; if (BUILTIN_TYPE(klass) != T_MODULE) return value; /* search global const too, if klass is a module */ - return rb_const_search_from(rb_cObject, id, FALSE, recurse, visibility); + return rb_const_search_from(rb_cObject, id, FALSE, recurse, visibility, found_in); } VALUE @@ -3712,7 +3571,8 @@ rb_const_remove(VALUE mod, ID id) rb_check_frozen(mod); ce = rb_const_lookup(mod, id); - if (!ce || !rb_id_table_delete(RCLASS_WRITABLE_CONST_TBL(mod), id)) { + + if (!ce) { if (rb_const_defined_at(mod, id)) { rb_name_err_raise("cannot remove %2$s::%1$s", mod, ID2SYM(id)); } @@ -3720,6 +3580,14 @@ rb_const_remove(VALUE mod, ID id) undefined_constant(mod, ID2SYM(id)); } + VALUE writable_ce = 0; + if (rb_id_table_lookup(RCLASS_WRITABLE_CONST_TBL(mod), id, &writable_ce)) { + rb_id_table_delete(RCLASS_WRITABLE_CONST_TBL(mod), id); + if ((rb_const_entry_t *)writable_ce != ce) { + xfree((rb_const_entry_t *)writable_ce); + } + } + rb_const_warn_if_deprecated(ce, mod, id); rb_clear_constant_cache_for_id(id); @@ -3967,6 +3835,7 @@ static void set_namespace_path(VALUE named_namespace, VALUE namespace_path) { struct rb_id_table *const_table = RCLASS_CONST_TBL(named_namespace); + RB_OBJ_SET_SHAREABLE(namespace_path); RB_VM_LOCKING() { RCLASS_WRITE_CLASSPATH(named_namespace, namespace_path, true); @@ -4045,7 +3914,8 @@ const_set(VALUE klass, ID id, VALUE val) set_namespace_path(val, build_const_path(parental_path, id)); } else if (!parental_path_permanent && NIL_P(val_path)) { - RCLASS_SET_CLASSPATH(val, build_const_path(parental_path, id), false); + VALUE path = build_const_path(parental_path, id); + RCLASS_SET_CLASSPATH(val, path, false); } } } @@ -4059,21 +3929,21 @@ rb_const_set(VALUE klass, ID id, VALUE val) const_added(klass, id); } -static struct autoload_data * -autoload_data_for_named_constant(VALUE module, ID name, struct autoload_const **autoload_const_pointer) +static VALUE +autoload_const_value_for_named_constant(VALUE module, ID name, struct autoload_const **autoload_const_pointer) { - VALUE autoload_data_value = autoload_data(module, name); - if (!autoload_data_value) return 0; + VALUE autoload_const_value = autoload_data(module, name); + if (!autoload_const_value) return Qfalse; - struct autoload_data *autoload_data = get_autoload_data(autoload_data_value, autoload_const_pointer); - if (!autoload_data) return 0; + struct autoload_data *autoload_data = get_autoload_data(autoload_const_value, autoload_const_pointer); + if (!autoload_data) return Qfalse; /* for autoloading thread, keep the defined value to autoloading storage */ if (autoload_by_current(autoload_data)) { - return autoload_data; + return autoload_const_value; } - return 0; + return Qfalse; } static void @@ -4093,13 +3963,13 @@ const_tbl_update(struct autoload_const *ac, int autoload_force) RUBY_ASSERT_CRITICAL_SECTION_ENTER(); VALUE file = ac->file; int line = ac->line; - struct autoload_data *ele = autoload_data_for_named_constant(klass, id, &ac); + VALUE autoload_const_value = autoload_const_value_for_named_constant(klass, id, &ac); - if (!autoload_force && ele) { + if (!autoload_force && autoload_const_value) { rb_clear_constant_cache_for_id(id); - ac->value = val; /* autoload_data is non-WB-protected */ - ac->file = rb_source_location(&ac->line); + RB_OBJ_WRITE(autoload_const_value, &ac->value, val); + RB_OBJ_WRITE(autoload_const_value, &ac->file, rb_source_location(&ac->line)); } else { /* otherwise autoloaded constant, allow to override */ @@ -4193,15 +4063,12 @@ set_const_visibility(VALUE mod, int argc, const VALUE *argv, ce->flag &= ~mask; ce->flag |= flag; if (UNDEF_P(ce->value)) { - struct autoload_data *ele; - - ele = autoload_data_for_named_constant(mod, id, &ac); - if (ele) { + if (autoload_const_value_for_named_constant(mod, id, &ac)) { ac->flag &= ~mask; ac->flag |= flag; } } - rb_clear_constant_cache_for_id(id); + rb_clear_constant_cache_for_id(id); } else { undefined_constant(mod, ID2SYM(id)); @@ -4344,7 +4211,7 @@ cvar_overtaken(VALUE front, VALUE target, ID id) } #define CVAR_LOOKUP(v,r) do {\ - CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(); \ + CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(klass, id); \ if (cvar_lookup_at(klass, id, (v))) {r;}\ CVAR_FOREACH_ANCESTORS(klass, v, r);\ } while(0) @@ -4395,7 +4262,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) } check_before_mod_set(target, id, val, "class variable"); - int result = rb_class_ivar_set(target, id, val); + bool new_cvar = rb_class_ivar_set(target, id, val); struct rb_id_table *rb_cvc_tbl = RCLASS_WRITABLE_CVC_TBL(target); @@ -4423,7 +4290,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) // Break the cvar cache if this is a new class variable // and target is a module or a subclass with the same // cvar in this lookup. - if (result == 0) { + if (new_cvar) { if (RB_TYPE_P(target, T_CLASS)) { if (RCLASS_SUBCLASSES_FIRST(target)) { rb_class_foreach_subclass(target, check_for_cvar_table, id); @@ -4654,79 +4521,110 @@ rb_iv_set(VALUE obj, const char *name, VALUE val) return rb_ivar_set(obj, id, val); } -static VALUE * -class_ivar_set_shape_fields(VALUE obj, void *_data) +static attr_index_t +class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool concurrent, VALUE *new_fields_obj, bool *new_ivar_out) { - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); + const VALUE original_fields_obj = fields_obj; + fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(klass, 1, true); - return RCLASS_PRIME_FIELDS(obj); -} + shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj); + shape_id_t next_shape_id = current_shape_id; // for too_complex + if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) { + goto too_complex; + } -static void -class_ivar_set_shape_resize_fields(VALUE obj, attr_index_t _old_capa, attr_index_t new_capa, void *_data) -{ - REALLOC_N(RCLASS_PRIME_FIELDS(obj), VALUE, new_capa); -} + bool new_ivar; + next_shape_id = generic_shape_ivar(fields_obj, id, &new_ivar); -static void -class_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *_data) -{ - rb_obj_set_shape_id(obj, shape_id); -} + if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { + fields_obj = imemo_fields_complex_from_obj(klass, fields_obj, next_shape_id); + goto too_complex; + } -static void -class_ivar_set_transition_too_complex(VALUE obj, void *_data) -{ - rb_evict_fields_to_hash(obj); -} + attr_index_t index = RSHAPE_INDEX(next_shape_id); + if (new_ivar) { + if (index >= RSHAPE_CAPACITY(current_shape_id)) { + // We allocate a new fields_obj even when concurrency isn't a concern + // so that we're embedded as long as possible. + fields_obj = imemo_fields_copy_capa(klass, fields_obj, RSHAPE_CAPACITY(next_shape_id)); + } + } -static st_table * -class_ivar_set_too_complex_table(VALUE obj, void *_data) -{ - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); - return RCLASS_WRITABLE_FIELDS_HASH(obj); -} + if (concurrent && original_fields_obj == fields_obj) { + // In the concurrent case, if we're mutating the existing + // fields_obj, we must use an atomic write, because if we're + // adding a new field, the shape_id must be written after the field + // and if we're updating an existing field, we at least need a relaxed + // write to avoid reaping. + RB_OBJ_ATOMIC_WRITE(fields_obj, &fields[index], val); + } + else { + RB_OBJ_WRITE(fields_obj, &fields[index], val); + } -int -rb_class_ivar_set(VALUE obj, ID id, VALUE val) -{ - RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - bool existing = false; - rb_check_frozen(obj); + if (new_ivar) { + RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); + } - rb_class_ensure_writable(obj); + *new_fields_obj = fields_obj; + *new_ivar_out = new_ivar; + return index; - RB_VM_LOCKING() { - existing = general_ivar_set(obj, id, val, NULL, - class_ivar_set_shape_fields, - class_ivar_set_shape_resize_fields, - class_ivar_set_set_shape_id, - class_ivar_set_transition_too_complex, - class_ivar_set_too_complex_table).existing; +too_complex: + { + if (concurrent && fields_obj == original_fields_obj) { + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. + fields_obj = rb_imemo_fields_clone(fields_obj); + } + + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); + new_ivar = !st_insert(table, (st_data_t)id, (st_data_t)val); + RB_OBJ_WRITTEN(fields_obj, Qundef, val); + + if (fields_obj != original_fields_obj) { + RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); + } } - return existing; + *new_fields_obj = fields_obj; + *new_ivar_out = new_ivar; + return ATTR_INDEX_NOT_SET; } -static void -class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) +static attr_index_t +class_ivar_set(VALUE obj, ID id, VALUE val, bool *new_ivar) { - RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - general_field_set(obj, target_shape_id, val, NULL, - class_ivar_set_shape_fields, - class_ivar_set_shape_resize_fields, - class_ivar_set_set_shape_id, - class_ivar_set_transition_too_complex, - class_ivar_set_too_complex_table); + rb_class_ensure_writable(obj); + + const VALUE original_fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + VALUE new_fields_obj = 0; + + attr_index_t index = class_fields_ivar_set(obj, original_fields_obj, id, val, rb_multi_ractor_p(), &new_fields_obj, new_ivar); + + if (new_fields_obj != original_fields_obj) { + RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, new_fields_obj); + } + + // TODO: What should we set as the T_CLASS shape_id? + // In most case we can replicate the single `fields_obj` shape + // but in namespaced case? Perhaps INVALID_SHAPE_ID? + RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); + return index; } -static int -tbl_copy_i(ID key, VALUE val, st_data_t dest) +bool +rb_class_ivar_set(VALUE obj, ID id, VALUE val) { - rb_class_ivar_set((VALUE)dest, key, val); + RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); + rb_check_frozen(obj); - return ST_CONTINUE; + bool new_ivar; + class_ivar_set(obj, id, val, &new_ivar); + return new_ivar; } void @@ -4734,11 +4632,13 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) { RUBY_ASSERT(rb_type(dst) == rb_type(src)); RUBY_ASSERT(RB_TYPE_P(dst, T_CLASS) || RB_TYPE_P(dst, T_MODULE)); - RUBY_ASSERT(RSHAPE_TYPE_P(RBASIC_SHAPE_ID(dst), SHAPE_ROOT)); - RUBY_ASSERT(!RCLASS_PRIME_FIELDS(dst)); - rb_ivar_foreach(src, tbl_copy_i, dst); + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src); + if (fields_obj) { + RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj)); + RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src)); + } } static rb_const_entry_t * @@ -4761,4 +4661,3 @@ rb_const_lookup(VALUE klass, ID id) { return const_lookup(RCLASS_CONST_TBL(klass), id); } - |
