summaryrefslogtreecommitdiff
path: root/variable.c
diff options
context:
space:
mode:
authorSatoshi Tagomori <tagomoris@gmail.com>2025-04-30 13:48:02 +0900
committerSatoshi Tagomori <tagomoris@gmail.com>2025-05-11 23:32:50 +0900
commit382645d440d5da66a0c04557f3ff2ca226de3a27 (patch)
treeb7453449930197237e739d0985561b664f51b0f3 /variable.c
parent49742414f6444960838bb968bab43db27f5872c1 (diff)
namespace on read
Diffstat (limited to 'variable.c')
-rw-r--r--variable.c263
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);
+}