Make setting and accessing class ivars lock-free
Now that class fields have been deletated to a T_IMEMO/class_fields when we're in multi-ractor mode, we can read and write class instance variable in an atomic way using Read-Copy-Update (RCU). Note when in multi-ractor mode, we always use RCU. In theory we don't need to, instead if we ensured the field is written before the shape is updated it would be safe. Benchmark: ```ruby Warning[:experimental] = false class Foo @foo = 1 @bar = 2 @baz = 3 @egg = 4 @spam = 5 class << self attr_reader :foo, :bar, :baz, :egg, :spam end end ractors = 8.times.map do Ractor.new do 1_000_000.times do Foo.bar + Foo.baz * Foo.egg - Foo.spam end end end if Ractor.method_defined?(:value) ractors.each(&:value) else ractors.each(&:take) end ``` This branch vs Ruby 3.4: ```bash $ hyperfine -w 1 'ruby --disable-all ../test.rb' './miniruby ../test.rb' Benchmark 1: ruby --disable-all ../test.rb Time (mean ± σ): 3.162 s ± 0.071 s [User: 2.783 s, System: 10.809 s] Range (min … max): 3.093 s … 3.337 s 10 runs Benchmark 2: ./miniruby ../test.rb Time (mean ± σ): 208.7 ms ± 4.6 ms [User: 889.7 ms, System: 6.9 ms] Range (min … max): 202.8 ms … 222.0 ms 14 runs Summary ./miniruby ../test.rb ran 15.15 ± 0.47 times faster than ruby --disable-all ../test.rb ```
This commit is contained in:
parent
8b5ac5abf2
commit
a74c385208
Notes:
git
2025-06-12 12:55:26 +00:00
1
imemo.c
1
imemo.c
@ -156,7 +156,6 @@ rb_imemo_class_fields_clone(VALUE fields_obj)
|
|||||||
if (rb_shape_too_complex_p(shape_id)) {
|
if (rb_shape_too_complex_p(shape_id)) {
|
||||||
clone = rb_imemo_class_fields_new_complex(CLASS_OF(fields_obj), 0);
|
clone = rb_imemo_class_fields_new_complex(CLASS_OF(fields_obj), 0);
|
||||||
RBASIC_SET_SHAPE_ID(clone, shape_id);
|
RBASIC_SET_SHAPE_ID(clone, shape_id);
|
||||||
|
|
||||||
st_table *src_table = rb_imemo_class_fields_complex_tbl(fields_obj);
|
st_table *src_table = rb_imemo_class_fields_complex_tbl(fields_obj);
|
||||||
st_replace(rb_imemo_class_fields_complex_tbl(clone), src_table);
|
st_replace(rb_imemo_class_fields_complex_tbl(clone), src_table);
|
||||||
}
|
}
|
||||||
|
@ -531,13 +531,6 @@ RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(VALUE obj)
|
|||||||
return ext->fields_obj;
|
return ext->fields_obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
|
||||||
RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj)
|
|
||||||
{
|
|
||||||
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
|
|
||||||
RB_OBJ_WRITE(obj, &ext->fields_obj, fields_obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline VALUE
|
static inline VALUE
|
||||||
RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj)
|
RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj)
|
||||||
{
|
{
|
||||||
@ -545,6 +538,16 @@ RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj)
|
|||||||
return RCLASSEXT_FIELDS_OBJ(RCLASS_EXT_WRITABLE(obj));
|
return RCLASSEXT_FIELDS_OBJ(RCLASS_EXT_WRITABLE(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj)
|
||||||
|
{
|
||||||
|
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
|
||||||
|
|
||||||
|
VALUE old_fields_obj = ext->fields_obj;
|
||||||
|
RUBY_ATOMIC_VALUE_SET(ext->fields_obj, fields_obj);
|
||||||
|
RB_OBJ_WRITTEN(obj, old_fields_obj, fields_obj);
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
RCLASS_WRITABLE_SET_FIELDS_OBJ(VALUE obj, VALUE fields_obj)
|
RCLASS_WRITABLE_SET_FIELDS_OBJ(VALUE obj, VALUE fields_obj)
|
||||||
{
|
{
|
||||||
|
@ -79,6 +79,26 @@ class TestRactor < Test::Unit::TestCase
|
|||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_class_instance_variables
|
||||||
|
assert_ractor(<<~'RUBY')
|
||||||
|
# Once we're in multi-ractor mode, the codepaths
|
||||||
|
# for class instance variables are a bit different.
|
||||||
|
Ractor.new {}.value
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
@a = 1
|
||||||
|
@b = 2
|
||||||
|
@c = 3
|
||||||
|
@d = 4
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 4, TestClass.remove_instance_variable(:@d)
|
||||||
|
assert_nil TestClass.instance_variable_get(:@d)
|
||||||
|
assert_equal 4, TestClass.instance_variable_set(:@d, 4)
|
||||||
|
assert_equal 4, TestClass.instance_variable_get(:@d)
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
def test_require_raises_and_no_ractor_belonging_issue
|
def test_require_raises_and_no_ractor_belonging_issue
|
||||||
assert_ractor(<<~'RUBY')
|
assert_ractor(<<~'RUBY')
|
||||||
require "tempfile"
|
require "tempfile"
|
||||||
|
119
variable.c
119
variable.c
@ -1371,16 +1371,18 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
|
|||||||
{
|
{
|
||||||
if (SPECIAL_CONST_P(obj)) return undef;
|
if (SPECIAL_CONST_P(obj)) return undef;
|
||||||
|
|
||||||
if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
|
shape_id_t shape_id;
|
||||||
VALUE val = undef;
|
VALUE *ivar_list;
|
||||||
RB_VM_LOCK_ENTER();
|
|
||||||
|
switch (BUILTIN_TYPE(obj)) {
|
||||||
|
case T_CLASS:
|
||||||
|
case T_MODULE:
|
||||||
{
|
{
|
||||||
|
VALUE val = undef;
|
||||||
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
||||||
if (fields_obj) {
|
if (fields_obj) {
|
||||||
val = rb_ivar_lookup(fields_obj, id, undef);
|
val = rb_ivar_lookup(fields_obj, id, undef);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
RB_VM_LOCK_LEAVE();
|
|
||||||
|
|
||||||
if (val != undef &&
|
if (val != undef &&
|
||||||
rb_is_instance_id(id) &&
|
rb_is_instance_id(id) &&
|
||||||
@ -1391,24 +1393,14 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
|
|||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_id_t shape_id;
|
|
||||||
VALUE * ivar_list;
|
|
||||||
shape_id = RBASIC_SHAPE_ID(obj);
|
|
||||||
|
|
||||||
switch (BUILTIN_TYPE(obj)) {
|
|
||||||
case T_CLASS:
|
|
||||||
case T_MODULE:
|
|
||||||
{
|
|
||||||
rb_bug("Unreachable");
|
|
||||||
}
|
|
||||||
case T_IMEMO:
|
case T_IMEMO:
|
||||||
// Handled like T_OBJECT
|
// Handled like T_OBJECT
|
||||||
{
|
{
|
||||||
RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_class_fields));
|
RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_class_fields));
|
||||||
|
shape_id = RBASIC_SHAPE_ID(obj);
|
||||||
|
|
||||||
if (rb_shape_too_complex_p(shape_id)) {
|
if (rb_shape_too_complex_p(shape_id)) {
|
||||||
st_table * iv_table = rb_imemo_class_fields_complex_tbl(obj);
|
st_table *iv_table = rb_imemo_class_fields_complex_tbl(obj);
|
||||||
VALUE val;
|
VALUE val;
|
||||||
if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
|
if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
|
||||||
return val;
|
return val;
|
||||||
@ -1424,8 +1416,9 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
|
|||||||
}
|
}
|
||||||
case T_OBJECT:
|
case T_OBJECT:
|
||||||
{
|
{
|
||||||
|
shape_id = RBASIC_SHAPE_ID(obj);
|
||||||
if (rb_shape_too_complex_p(shape_id)) {
|
if (rb_shape_too_complex_p(shape_id)) {
|
||||||
st_table * iv_table = ROBJECT_FIELDS_HASH(obj);
|
st_table *iv_table = ROBJECT_FIELDS_HASH(obj);
|
||||||
VALUE val;
|
VALUE val;
|
||||||
if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
|
if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
|
||||||
return val;
|
return val;
|
||||||
@ -1440,6 +1433,7 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
shape_id = RBASIC_SHAPE_ID(obj);
|
||||||
if (FL_TEST_RAW(obj, FL_EXIVAR)) {
|
if (FL_TEST_RAW(obj, FL_EXIVAR)) {
|
||||||
struct gen_fields_tbl *fields_tbl;
|
struct gen_fields_tbl *fields_tbl;
|
||||||
rb_gen_fields_tbl_get(obj, id, &fields_tbl);
|
rb_gen_fields_tbl_get(obj, id, &fields_tbl);
|
||||||
@ -1494,13 +1488,16 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
|
|||||||
|
|
||||||
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
||||||
if (fields_obj) {
|
if (fields_obj) {
|
||||||
RB_VM_LOCK_ENTER();
|
if (rb_multi_ractor_p()) {
|
||||||
{
|
fields_obj = rb_imemo_class_fields_clone(fields_obj);
|
||||||
|
val = rb_ivar_delete(fields_obj, id, undef);
|
||||||
|
RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, fields_obj);
|
||||||
|
}
|
||||||
|
else {
|
||||||
val = rb_ivar_delete(fields_obj, id, undef);
|
val = rb_ivar_delete(fields_obj, id, undef);
|
||||||
}
|
}
|
||||||
RB_VM_LOCK_LEAVE();
|
|
||||||
return val;
|
|
||||||
}
|
}
|
||||||
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_id_t old_shape_id = rb_obj_shape_id(obj);
|
shape_id_t old_shape_id = rb_obj_shape_id(obj);
|
||||||
@ -2127,8 +2124,6 @@ rb_ivar_set_internal(VALUE obj, ID id, VALUE val)
|
|||||||
ivar_set(obj, id, val);
|
ivar_set(obj, id, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
|
rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
|
||||||
{
|
{
|
||||||
@ -2138,8 +2133,8 @@ rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
|
|||||||
break;
|
break;
|
||||||
case T_CLASS:
|
case T_CLASS:
|
||||||
case T_MODULE:
|
case T_MODULE:
|
||||||
ASSERT_vm_locking();
|
// The only field is object_id and T_CLASS handle it differently.
|
||||||
class_field_set(obj, target_shape_id, val);
|
rb_bug("Unreachable");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
generic_field_set(obj, target_shape_id, val);
|
generic_field_set(obj, target_shape_id, val);
|
||||||
@ -2199,7 +2194,7 @@ rb_ivar_defined(VALUE obj, ID id)
|
|||||||
switch (BUILTIN_TYPE(obj)) {
|
switch (BUILTIN_TYPE(obj)) {
|
||||||
case T_CLASS:
|
case T_CLASS:
|
||||||
case T_MODULE:
|
case T_MODULE:
|
||||||
RB_VM_LOCKING() {
|
{
|
||||||
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
||||||
if (fields_obj) {
|
if (fields_obj) {
|
||||||
defined = ivar_defined0(fields_obj, id);
|
defined = ivar_defined0(fields_obj, id);
|
||||||
@ -2452,8 +2447,8 @@ rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg,
|
|||||||
break;
|
break;
|
||||||
case T_CLASS:
|
case T_CLASS:
|
||||||
case T_MODULE:
|
case T_MODULE:
|
||||||
|
{
|
||||||
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0);
|
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0);
|
||||||
RB_VM_LOCKING() {
|
|
||||||
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
||||||
if (fields_obj) {
|
if (fields_obj) {
|
||||||
class_fields_each(fields_obj, func, arg, ivar_only);
|
class_fields_each(fields_obj, func, arg, ivar_only);
|
||||||
@ -4701,15 +4696,16 @@ rb_iv_set(VALUE obj, const char *name, VALUE val)
|
|||||||
return rb_ivar_set(obj, id, val);
|
return rb_ivar_set(obj, id, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static bool
|
||||||
class_ivar_set(VALUE obj, ID id, VALUE val)
|
class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool concurrent, VALUE *new_fields_obj)
|
||||||
{
|
{
|
||||||
bool existing = true;
|
bool existing = true;
|
||||||
const VALUE original_fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
const VALUE original_fields_obj = fields_obj;
|
||||||
VALUE fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_class_fields_new(obj, 1);
|
fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_class_fields_new(klass, 1);
|
||||||
|
|
||||||
shape_id_t next_shape_id = 0;
|
|
||||||
shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj);
|
shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj);
|
||||||
|
shape_id_t next_shape_id = current_shape_id;
|
||||||
|
|
||||||
if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) {
|
if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) {
|
||||||
goto too_complex;
|
goto too_complex;
|
||||||
}
|
}
|
||||||
@ -4726,7 +4722,7 @@ class_ivar_set(VALUE obj, ID id, VALUE val)
|
|||||||
next_shape_id = rb_shape_transition_add_ivar(fields_obj, id);
|
next_shape_id = rb_shape_transition_add_ivar(fields_obj, id);
|
||||||
if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) {
|
if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) {
|
||||||
attr_index_t current_len = RSHAPE_LEN(current_shape_id);
|
attr_index_t current_len = RSHAPE_LEN(current_shape_id);
|
||||||
fields_obj = rb_imemo_class_fields_new_complex(obj, current_len + 1);
|
fields_obj = rb_imemo_class_fields_new_complex(klass, current_len + 1);
|
||||||
if (current_len) {
|
if (current_len) {
|
||||||
rb_obj_copy_fields_to_hash_table(original_fields_obj, rb_imemo_class_fields_complex_tbl(fields_obj));
|
rb_obj_copy_fields_to_hash_table(original_fields_obj, rb_imemo_class_fields_complex_tbl(fields_obj));
|
||||||
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
|
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
|
||||||
@ -4737,10 +4733,12 @@ class_ivar_set(VALUE obj, ID id, VALUE val)
|
|||||||
attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id);
|
attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id);
|
||||||
attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id);
|
attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id);
|
||||||
|
|
||||||
if (UNLIKELY(next_capacity != current_capacity)) {
|
if (concurrent || next_capacity != current_capacity) {
|
||||||
RUBY_ASSERT(next_capacity > current_capacity);
|
RUBY_ASSERT(concurrent || next_capacity > current_capacity);
|
||||||
// We allocate a new fields_obj so that we're embedded as long as possible
|
|
||||||
fields_obj = rb_imemo_class_fields_new(obj, next_capacity);
|
// We allocate a new fields_obj even when concurrency isn't a concern
|
||||||
|
// so that we're embedded as long as possible.
|
||||||
|
fields_obj = rb_imemo_class_fields_new(klass, next_capacity);
|
||||||
if (original_fields_obj) {
|
if (original_fields_obj) {
|
||||||
MEMCPY(rb_imemo_class_fields_ptr(fields_obj), rb_imemo_class_fields_ptr(original_fields_obj), VALUE, RSHAPE_LEN(current_shape_id));
|
MEMCPY(rb_imemo_class_fields_ptr(fields_obj), rb_imemo_class_fields_ptr(original_fields_obj), VALUE, RSHAPE_LEN(current_shape_id));
|
||||||
}
|
}
|
||||||
@ -4752,20 +4750,12 @@ class_ivar_set(VALUE obj, ID id, VALUE val)
|
|||||||
|
|
||||||
VALUE *fields = rb_imemo_class_fields_ptr(fields_obj);
|
VALUE *fields = rb_imemo_class_fields_ptr(fields_obj);
|
||||||
RB_OBJ_WRITE(fields_obj, &fields[index], val);
|
RB_OBJ_WRITE(fields_obj, &fields[index], val);
|
||||||
|
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
|
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields_obj != original_fields_obj) {
|
*new_fields_obj = fields_obj;
|
||||||
RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, 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, next_shape_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
RB_GC_GUARD(fields_obj);
|
|
||||||
return existing;
|
return existing;
|
||||||
|
|
||||||
too_complex:
|
too_complex:
|
||||||
@ -4776,15 +4766,10 @@ too_complex:
|
|||||||
|
|
||||||
if (fields_obj != original_fields_obj) {
|
if (fields_obj != original_fields_obj) {
|
||||||
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
|
RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
|
||||||
RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, 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, next_shape_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RB_GC_GUARD(fields_obj);
|
|
||||||
|
*new_fields_obj = fields_obj;
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4792,25 +4777,27 @@ int
|
|||||||
rb_class_ivar_set(VALUE obj, ID id, VALUE val)
|
rb_class_ivar_set(VALUE obj, ID id, VALUE val)
|
||||||
{
|
{
|
||||||
RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
|
RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
|
||||||
bool existing = false;
|
|
||||||
rb_check_frozen(obj);
|
rb_check_frozen(obj);
|
||||||
|
|
||||||
rb_class_ensure_writable(obj);
|
rb_class_ensure_writable(obj);
|
||||||
|
|
||||||
RB_VM_LOCKING() {
|
const VALUE original_fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
|
||||||
existing = class_ivar_set(obj, id, val);
|
VALUE new_fields_obj = 0;
|
||||||
|
|
||||||
|
bool existing = class_fields_ivar_set(obj, original_fields_obj, id, val, rb_multi_ractor_p(), &new_fields_obj);
|
||||||
|
|
||||||
|
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 existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
|
|
||||||
{
|
|
||||||
RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
|
|
||||||
obj_field_set(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(obj), target_shape_id, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tbl_copy_i(ID key, VALUE val, st_data_t dest)
|
tbl_copy_i(ID key, VALUE val, st_data_t dest)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user