diff options
| author | Satoshi Tagomori <tagomoris@gmail.com> | 2025-04-30 13:48:02 +0900 |
|---|---|---|
| committer | Satoshi Tagomori <tagomoris@gmail.com> | 2025-05-11 23:32:50 +0900 |
| commit | 382645d440d5da66a0c04557f3ff2ca226de3a27 (patch) | |
| tree | b7453449930197237e739d0985561b664f51b0f3 /variable.c | |
| parent | 49742414f6444960838bb968bab43db27f5872c1 (diff) | |
namespace on read
Diffstat (limited to 'variable.c')
| -rw-r--r-- | variable.c | 263 |
1 files changed, 228 insertions, 35 deletions
diff --git a/variable.c b/variable.c index 6315df759e..2fe6d45a69 100644 --- a/variable.c +++ b/variable.c @@ -25,6 +25,7 @@ #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" @@ -109,10 +110,10 @@ classname(VALUE klass, bool *permanent) { *permanent = false; - VALUE classpath = RCLASS_EXT(klass)->classpath; + VALUE classpath = RCLASS_CLASSPATH(klass); if (classpath == 0) return Qnil; - *permanent = RCLASS_EXT(klass)->permanent_classpath; + *permanent = RCLASS_PERMANENT_CLASSPATH_P(klass); return classpath; } @@ -210,7 +211,7 @@ static enum rb_id_table_iterator_result set_sub_temporary_name_i(ID id, VALUE val, void *data) { val = ((rb_const_entry_t *)val)->value; - if (rb_namespace_p(val) && !RCLASS_EXT(val)->permanent_classpath) { + if (rb_namespace_p(val) && !RCLASS_PERMANENT_CLASSPATH_P(val)) { VALUE arg = (VALUE)data; struct sub_temporary_name_args *args = data; args->last = id; @@ -222,7 +223,7 @@ set_sub_temporary_name_i(ID id, VALUE val, void *data) static void set_sub_temporary_name_foreach(VALUE mod, struct sub_temporary_name_args *args, VALUE name) { - RCLASS_SET_CLASSPATH(mod, name, FALSE); + RCLASS_WRITE_CLASSPATH(mod, name, FALSE); struct rb_id_table *tbl = RCLASS_CONST_TBL(mod); if (!tbl) return; if (!name) { @@ -296,7 +297,7 @@ VALUE rb_mod_set_temporary_name(VALUE mod, VALUE name) { // We don't allow setting the name if the classpath is already permanent: - if (RCLASS_EXT(mod)->permanent_classpath) { + if (RCLASS_PERMANENT_CLASSPATH_P(mod)) { rb_raise(rb_eRuntimeError, "can't change permanent name"); } @@ -529,6 +530,7 @@ struct rb_global_variable { rb_gvar_marker_t *marker; rb_gvar_compact_t *compactor; struct trace_var *trace; + bool namespace_ready; }; struct rb_global_entry { @@ -604,6 +606,13 @@ rb_gvar_ractor_local(const char *name) entry->ractor_local = true; } +void +rb_gvar_namespace_ready(const char *name) +{ + struct rb_global_entry *entry = rb_find_global_entry(rb_intern(name)); + entry->var->namespace_ready = true; +} + static void rb_gvar_undef_compactor(void *var) { @@ -629,6 +638,7 @@ rb_global_entry(ID id) var->block_trace = 0; var->trace = 0; + var->namespace_ready = false; rb_id_table_insert(rb_global_tbl, id, (VALUE)entry); } return entry; @@ -982,13 +992,27 @@ 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)) + VALUE rb_gvar_set(ID id, VALUE val) { + VALUE retval; struct rb_global_entry *entry; + const rb_namespace_t *ns = rb_current_namespace(); + entry = rb_global_entry(id); - return rb_gvar_set_entry(entry, val); + if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { + rb_hash_aset(ns->gvar_tbl, rb_id2sym(entry->id), val); + retval = val; + // TODO: think about trace + } else { + retval = rb_gvar_set_entry(entry, val); + } + return retval; } VALUE @@ -1000,9 +1024,27 @@ rb_gv_set(const char *name, VALUE val) 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; - return (*var->getter)(entry->id, var->data); + 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); + } + rb_hash_aset(gvars, key, retval); + } + } else { + retval = (*var->getter)(entry->id, var->data); + } + return retval; } VALUE @@ -1056,6 +1098,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 */ rb_id_table_foreach(rb_global_tbl, gvar_i, (void *)ary); if (!NIL_P(backref)) { @@ -1335,7 +1378,7 @@ rb_obj_field_get(VALUE obj, rb_shape_t *target_shape) case T_CLASS: case T_MODULE: ASSERT_vm_locking(); - fields = RCLASS_FIELDS(obj); + fields = RCLASS_PRIME_FIELDS(obj); break; case T_OBJECT: fields = ROBJECT_FIELDS(obj); @@ -1392,7 +1435,7 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) found = rb_shape_get_iv_index(shape, id, &index); if (found) { - ivar_list = RCLASS_FIELDS(obj); + ivar_list = RCLASS_PRIME_FIELDS(obj); RUBY_ASSERT(ivar_list); val = ivar_list[index]; @@ -1501,7 +1544,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - table = RCLASS_FIELDS_HASH(obj); + table = RCLASS_WRITABLE_FIELDS_HASH(obj); break; case T_OBJECT: @@ -1551,7 +1594,7 @@ obj_transition_too_complex(VALUE obj, st_table *table) break; case T_CLASS: case T_MODULE: - old_fields = RCLASS_FIELDS(obj); + old_fields = RCLASS_PRIME_FIELDS(obj); rb_shape_set_shape_id(obj, shape_id); RCLASS_SET_FIELDS_HASH(obj, table); break; @@ -2124,7 +2167,7 @@ rb_ivar_defined(VALUE obj, ID id) switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - table = (st_table *)RCLASS_FIELDS(obj); + table = (st_table *)RCLASS_FIELDS_HASH(obj); break; case T_OBJECT: @@ -2188,7 +2231,8 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu break; case T_CLASS: case T_MODULE: - iv_list = RCLASS_FIELDS(itr_data->obj); + 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; @@ -2280,7 +2324,7 @@ class_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, }; if (rb_shape_obj_too_complex_p(obj)) { - rb_st_foreach(RCLASS_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data); + rb_st_foreach(RCLASS_WRITABLE_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data); } else { iterate_over_shapes_with_callback(shape, func, &itr_data); @@ -2701,7 +2745,7 @@ autoload_data(VALUE mod, ID id) // If we are called with a non-origin ICLASS, fetch the autoload data from // the original module. if (RB_TYPE_P(mod, T_ICLASS)) { - if (FL_TEST_RAW(mod, RICLASS_IS_ORIGIN)) { + if (RICLASS_IS_ORIGIN_P(mod)) { return 0; } else { @@ -2729,6 +2773,10 @@ 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 module we are loading a constant into. VALUE module; @@ -2866,6 +2914,70 @@ get_autoload_data(VALUE autoload_const_value, struct autoload_const **autoload_c return autoload_data; } +struct autoload_copy_table_data { + VALUE dst_tbl_value; + struct st_table *dst_tbl; + const rb_namespace_t *ns; +}; + +static int +autoload_copy_table_for_namespace_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; + + VALUE src_value = (VALUE)value; + struct autoload_const *src_const = rb_check_typeddata(src_value, &autoload_const_type); + // autoload_data can be shared between copies because the feature is equal between copies. + VALUE autoload_data_value = src_const->autoload_data_value; + 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; + autoload_const->name = src_const->name; + autoload_const->value = src_const->value; + autoload_const->flag = src_const->flag; + 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); + RB_OBJ_WRITTEN(tbl_value, Qundef, new_value); + + return ST_CONTINUE; +} + +void +rb_autoload_copy_table_for_namespace(st_table *iv_ptr, const rb_namespace_t *ns) +{ + struct st_table *src_tbl, *dst_tbl; + VALUE src_tbl_value, dst_tbl_value; + if (!rb_st_lookup(iv_ptr, (st_data_t)autoload, (st_data_t *)&src_tbl_value)) { + // the class has no autoload table yet. + return; + } + if (!RTEST(src_tbl_value) || !(src_tbl = check_autoload_table(src_tbl_value))) { + // the __autoload__ ivar value isn't autoload table value. + return; + } + src_tbl = check_autoload_table(src_tbl_value); + + dst_tbl_value = TypedData_Wrap_Struct(0, &autoload_table_type, NULL); + RTYPEDDATA_DATA(dst_tbl_value) = dst_tbl = st_init_numtable(); + + struct autoload_copy_table_data data = { + .dst_tbl_value = dst_tbl_value, + .dst_tbl = dst_tbl, + .ns = ns, + }; + + st_foreach(src_tbl, autoload_copy_table_for_namespace_i, (st_data_t)&data); + st_insert(iv_ptr, (st_data_t)autoload, (st_data_t)dst_tbl_value); +} + void rb_autoload(VALUE module, ID name, const char *feature) { @@ -2883,6 +2995,7 @@ struct autoload_arguments { VALUE module; ID name; VALUE feature; + VALUE namespace; }; static VALUE @@ -2952,6 +3065,7 @@ 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; autoload_const->name = arguments->name; autoload_const->value = Qundef; @@ -2968,6 +3082,9 @@ 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); + if (!rb_is_const_id(name)) { rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(name)); } @@ -2981,6 +3098,7 @@ rb_autoload_str(VALUE module, ID name, VALUE feature) .module = module, .name = name, .feature = feature, + .namespace = current_namespace, }; VALUE result = rb_mutex_synchronize(autoload_mutex, autoload_synchronized, (VALUE)&arguments); @@ -3241,22 +3359,82 @@ 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) { + VALUE receiver = rb_vm_top_self(); + struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments; struct autoload_const *autoload_const = arguments->autoload_const; + VALUE autoload_namespace = autoload_const->namespace; // We save this for later use in autoload_apply_constants: arguments->autoload_data = rb_check_typeddata(autoload_const->autoload_data_value, &autoload_data_type); - VALUE result = rb_funcall(rb_vm_top_self(), rb_intern("require"), 1, arguments->autoload_data->feature); + 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 (RTEST(autoload_namespace) && NAMESPACE_OPTIONAL_P(rb_get_namespace_t(autoload_namespace))) { + receiver = autoload_namespace; + } + + VALUE result = rb_funcall(receiver, rb_intern("require"), 1, arguments->autoload_data->feature); if (RTEST(result)) { return rb_mutex_synchronize(autoload_mutex, autoload_apply_constants, _arguments); } - return result; } @@ -3580,6 +3758,8 @@ rb_mod_remove_const(VALUE mod, VALUE name) return rb_const_remove(mod, id); } +static rb_const_entry_t * const_lookup(struct rb_id_table *tbl, ID id); + VALUE rb_const_remove(VALUE mod, ID id) { @@ -3589,7 +3769,7 @@ rb_const_remove(VALUE mod, ID id) rb_check_frozen(mod); ce = rb_const_lookup(mod, id); - if (!ce || !rb_id_table_delete(RCLASS_CONST_TBL(mod), id)) { + if (!ce || !rb_id_table_delete(RCLASS_WRITABLE_CONST_TBL(mod), id)) { if (rb_const_defined_at(mod, id)) { rb_name_err_raise("cannot remove %2$s::%1$s", mod, ID2SYM(id)); } @@ -3607,7 +3787,11 @@ rb_const_remove(VALUE mod, ID id) val = Qnil; } - ruby_xfree(ce); + if (ce == const_lookup(RCLASS_PRIME_CONST_TBL(mod), id)) { + // skip free'ing the ce because it still exists in the prime classext + } else { + ruby_xfree(ce); + } return val; } @@ -3829,8 +4013,8 @@ set_namespace_path_i(ID id, VALUE v, void *payload) } set_namespace_path(value, build_const_path(parental_path, id)); - if (!RCLASS_EXT(value)->permanent_classpath) { - RCLASS_SET_CLASSPATH(value, 0, false); + if (!RCLASS_PERMANENT_CLASSPATH_P(value)) { + RCLASS_WRITE_CLASSPATH(value, 0, false); } return ID_TABLE_CONTINUE; @@ -3848,7 +4032,7 @@ set_namespace_path(VALUE named_namespace, VALUE namespace_path) RB_VM_LOCK_ENTER(); { - RCLASS_SET_CLASSPATH(named_namespace, namespace_path, true); + RCLASS_WRITE_CLASSPATH(named_namespace, namespace_path, true); if (const_table) { rb_id_table_foreach(const_table, set_namespace_path_i, &namespace_path); @@ -3884,9 +4068,10 @@ const_set(VALUE klass, ID id, VALUE val) RB_VM_LOCK_ENTER(); { - struct rb_id_table *tbl = RCLASS_CONST_TBL(klass); + struct rb_id_table *tbl = RCLASS_WRITABLE_CONST_TBL(klass); if (!tbl) { - RCLASS_CONST_TBL(klass) = tbl = rb_id_table_create(0); + tbl = rb_id_table_create(0); + RCLASS_WRITE_CONST_TBL(klass, tbl, false); rb_clear_constant_cache_for_id(id); ce = ZALLOC(rb_const_entry_t); rb_id_table_insert(tbl, id, (VALUE)ce); @@ -4010,6 +4195,7 @@ const_tbl_update(struct autoload_const *ac, int autoload_force) setup_const_entry(ce, klass, val, visibility); } else { + tbl = RCLASS_WRITABLE_CONST_TBL(klass); rb_clear_constant_cache_for_id(id); ce = ZALLOC(rb_const_entry_t); @@ -4172,7 +4358,7 @@ static int cvar_lookup_at(VALUE klass, ID id, st_data_t *v) { if (RB_TYPE_P(klass, T_ICLASS)) { - if (FL_TEST_RAW(klass, RICLASS_IS_ORIGIN)) { + if (RICLASS_IS_ORIGIN_P(klass)) { return 0; } else { @@ -4277,10 +4463,11 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) int result = rb_class_ivar_set(target, id, val); - struct rb_id_table *rb_cvc_tbl = RCLASS_CVC_TBL(target); + struct rb_id_table *rb_cvc_tbl = RCLASS_WRITABLE_CVC_TBL(target); if (!rb_cvc_tbl) { - rb_cvc_tbl = RCLASS_CVC_TBL(target) = rb_id_table_create(2); + rb_cvc_tbl = rb_id_table_create(2); + RCLASS_WRITE_CVC_TBL(target, rb_cvc_tbl); } struct rb_cvar_class_tbl_entry *ent; @@ -4304,7 +4491,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) // cvar in this lookup. if (result == 0) { if (RB_TYPE_P(target, T_CLASS)) { - if (RCLASS_SUBCLASSES(target)) { + if (RCLASS_SUBCLASSES_FIRST(target)) { rb_class_foreach_subclass(target, check_for_cvar_table, id); } } @@ -4538,13 +4725,13 @@ class_ivar_set_shape_fields(VALUE obj, void *_data) { RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - return RCLASS_FIELDS(obj); + return RCLASS_PRIME_FIELDS(obj); } 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_FIELDS(obj), VALUE, new_capa); + REALLOC_N(RCLASS_PRIME_FIELDS(obj), VALUE, new_capa); } static void @@ -4564,7 +4751,7 @@ class_ivar_set_too_complex_table(VALUE obj, void *_data) { RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); - return RCLASS_FIELDS_HASH(obj); + return RCLASS_WRITABLE_FIELDS_HASH(obj); } int @@ -4574,6 +4761,8 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val) bool existing = false; rb_check_frozen(obj); + rb_class_ensure_writable(obj); + RB_VM_LOCK_ENTER(); { existing = general_ivar_set(obj, id, val, NULL, @@ -4620,11 +4809,9 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) rb_ivar_foreach(src, tbl_copy_i, dst); } -rb_const_entry_t * -rb_const_lookup(VALUE klass, ID id) +static rb_const_entry_t * +const_lookup(struct rb_id_table *tbl, ID id) { - struct rb_id_table *tbl = RCLASS_CONST_TBL(klass); - if (tbl) { VALUE val; bool r; @@ -4638,3 +4825,9 @@ rb_const_lookup(VALUE klass, ID id) } return NULL; } + +rb_const_entry_t * +rb_const_lookup(VALUE klass, ID id) +{ + return const_lookup(RCLASS_CONST_TBL(klass), id); +} |
