Move object_id in object fields.

And get rid of the `obj_to_id_tbl`

It's no longer needed, the `object_id` is now stored inline
in the object alongside instance variables.

We still need the inverse table in case `_id2ref` is invoked, but
we lazily build it by walking the heap if that happens.

The `object_id` concern is also no longer a GC implementation
concern, but a generic implementation.

Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com>
This commit is contained in:
Jean Boussier 2025-04-21 16:16:07 +09:00
parent d34c150547
commit f48e45d1e9
Notes: git 2025-05-08 05:58:19 +00:00
23 changed files with 1140 additions and 560 deletions

View File

@ -805,29 +805,29 @@ shape_i(rb_shape_t *shape, void *data)
dump_append(dc, ", \"depth\":");
dump_append_sizet(dc, rb_shape_depth(shape));
dump_append(dc, ", \"shape_type\":");
switch((enum shape_type)shape->type) {
case SHAPE_ROOT:
dump_append(dc, "\"ROOT\"");
dump_append(dc, ", \"shape_type\":\"ROOT\"");
break;
case SHAPE_IVAR:
dump_append(dc, "\"IVAR\"");
dump_append(dc, ", \"shape_type\":\"IVAR\"");
dump_append(dc, ",\"edge_name\":");
dump_append_id(dc, shape->edge_name);
break;
case SHAPE_FROZEN:
dump_append(dc, "\"FROZEN\"");
dump_append(dc, ", \"shape_type\":\"FROZEN\"");
break;
case SHAPE_T_OBJECT:
dump_append(dc, "\"T_OBJECT\"");
dump_append(dc, ", \"shape_type\":\"T_OBJECT\"");
break;
case SHAPE_OBJ_TOO_COMPLEX:
dump_append(dc, "\"OBJ_TOO_COMPLEX\"");
dump_append(dc, ", \"shape_type\":\"OBJ_TOO_COMPLEX\"");
break;
case SHAPE_OBJ_ID:
dump_append(dc, ", \"shape_type\":\"OBJ_ID\"");
break;
default:
rb_bug("[objspace] unexpected shape type");
}
dump_append(dc, ", \"edges\":");

387
gc.c
View File

@ -380,10 +380,11 @@ uint32_t
rb_gc_rebuild_shape(VALUE obj, size_t heap_id)
{
shape_id_t orig_shape_id = rb_shape_get_shape_id(obj);
if (rb_shape_id_too_complex_p(orig_shape_id)) {
return (uint32_t)orig_shape_id;
}
rb_shape_t *orig_shape = rb_shape_get_shape_by_id(orig_shape_id);
if (rb_shape_too_complex_p(orig_shape)) return orig_shape_id;
rb_shape_t *initial_shape = rb_shape_get_shape_by_id((shape_id_t)(heap_id + FIRST_T_OBJECT_SHAPE_ID));
rb_shape_t *new_shape = rb_shape_traverse_from_new_root(initial_shape, orig_shape);
@ -841,9 +842,6 @@ ruby_modular_gc_init(void)
load_modular_gc_func(undefine_finalizer);
load_modular_gc_func(copy_finalizer);
load_modular_gc_func(shutdown_call_finalizer);
// Object ID
load_modular_gc_func(object_id);
load_modular_gc_func(object_id_to_ref);
// Forking
load_modular_gc_func(before_fork);
load_modular_gc_func(after_fork);
@ -924,9 +922,6 @@ ruby_modular_gc_init(void)
# define rb_gc_impl_undefine_finalizer rb_gc_functions.undefine_finalizer
# define rb_gc_impl_copy_finalizer rb_gc_functions.copy_finalizer
# define rb_gc_impl_shutdown_call_finalizer rb_gc_functions.shutdown_call_finalizer
// Object ID
# define rb_gc_impl_object_id rb_gc_functions.object_id
# define rb_gc_impl_object_id_to_ref rb_gc_functions.object_id_to_ref
// Forking
# define rb_gc_impl_before_fork rb_gc_functions.before_fork
# define rb_gc_impl_after_fork rb_gc_functions.after_fork
@ -1214,40 +1209,6 @@ rb_data_free(void *objspace, VALUE obj)
return true;
}
void
rb_gc_obj_free_vm_weak_references(VALUE obj)
{
if (FL_TEST_RAW(obj, FL_EXIVAR)) {
rb_free_generic_ivar((VALUE)obj);
FL_UNSET(obj, FL_EXIVAR);
}
switch (BUILTIN_TYPE(obj)) {
case T_STRING:
if (FL_TEST_RAW(obj, RSTRING_FSTR)) {
rb_gc_free_fstring(obj);
}
break;
case T_SYMBOL:
rb_gc_free_dsymbol(obj);
break;
case T_IMEMO:
switch (imemo_type(obj)) {
case imemo_callinfo:
rb_vm_ci_free((const struct rb_callinfo *)obj);
break;
case imemo_ment:
rb_free_method_entry_vm_weak_references((const rb_method_entry_t *)obj);
break;
default:
break;
}
break;
default:
break;
}
}
bool
rb_gc_obj_free(void *objspace, VALUE obj)
{
@ -1760,6 +1721,237 @@ rb_gc_pointer_to_heap_p(VALUE obj)
return rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj);
}
#define OBJ_ID_INCREMENT (RUBY_IMMEDIATE_MASK + 1)
#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT)
static unsigned long long next_object_id = OBJ_ID_INITIAL;
static VALUE id_to_obj_value = 0;
static st_table *id_to_obj_tbl = NULL;
void
rb_gc_obj_id_moved(VALUE obj)
{
if (UNLIKELY(id_to_obj_tbl)) {
st_insert(id_to_obj_tbl, (st_data_t)rb_obj_id(obj), (st_data_t)obj);
}
}
static int
object_id_cmp(st_data_t x, st_data_t y)
{
if (RB_TYPE_P(x, T_BIGNUM)) {
return !rb_big_eql(x, y);
}
else {
return x != y;
}
}
static st_index_t
object_id_hash(st_data_t n)
{
return FIX2LONG(rb_hash((VALUE)n));
}
static const struct st_hash_type object_id_hash_type = {
object_id_cmp,
object_id_hash,
};
static void gc_mark_tbl_no_pin(st_table *table);
static void
id_to_obj_tbl_mark(void *data)
{
st_table *table = (st_table *)data;
if (UNLIKELY(!RB_POSFIXABLE(next_object_id))) {
// It's very unlikely, but if enough object ids were generated, keys may be T_BIGNUM
rb_mark_set(table);
}
// We purposedly don't mark values, as they are weak references.
// rb_gc_obj_free_vm_weak_references takes care of cleaning them up.
}
static size_t
id_to_obj_tbl_memsize(const void *data)
{
return rb_st_memsize(data);
}
static void
id_to_obj_tbl_compact(void *data)
{
st_table *table = (st_table *)data;
if (LIKELY(RB_POSFIXABLE(next_object_id))) {
// We know keys are all FIXNUM, so no need to update them.
gc_ref_update_table_values_only(table);
}
else {
gc_update_table_refs(table);
}
}
static void
id_to_obj_tbl_free(void *data)
{
id_to_obj_tbl = NULL; // clear global ref
st_table *table = (st_table *)data;
st_free_table(table);
}
static const rb_data_type_t id_to_obj_tbl_type = {
.wrap_struct_name = "VM/id_to_obj_table",
.function = {
.dmark = id_to_obj_tbl_mark,
.dfree = id_to_obj_tbl_free,
.dsize = id_to_obj_tbl_memsize,
.dcompact = id_to_obj_tbl_compact,
},
.flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
};
static VALUE
object_id(VALUE obj)
{
VALUE id = Qfalse;
rb_shape_t *shape = rb_shape_get_shape(obj);
unsigned int lock_lev;
// We could avoid locking if the object isn't shareable
// but we'll lock anyway to lookup the next shape, and
// we'd at least need to generate the object_id using atomics.
lock_lev = rb_gc_vm_lock();
if (rb_shape_too_complex_p(shape)) {
st_table *table = ROBJECT_FIELDS_HASH(obj);
if (rb_shape_has_object_id(shape)) {
st_lookup(table, (st_data_t)ruby_internal_object_id, (st_data_t *)&id);
RUBY_ASSERT(id, "object_id missing");
rb_gc_vm_unlock(lock_lev);
return id;
}
id = ULL2NUM(next_object_id);
next_object_id += OBJ_ID_INCREMENT;
rb_shape_t *object_id_shape = rb_shape_object_id_shape(obj);
st_insert(table, (st_data_t)ruby_internal_object_id, (st_data_t)id);
rb_shape_set_shape(obj, object_id_shape);
if (RB_UNLIKELY(id_to_obj_tbl)) {
st_insert(id_to_obj_tbl, (st_data_t)id, (st_data_t)obj);
}
}
else if (rb_shape_has_object_id(shape)) {
rb_shape_t *object_id_shape = rb_shape_object_id_shape(obj);
id = rb_field_get(obj, object_id_shape);
}
else {
id = ULL2NUM(next_object_id);
next_object_id += OBJ_ID_INCREMENT;
rb_shape_t *object_id_shape = rb_shape_object_id_shape(obj);
rb_obj_field_set(obj, object_id_shape, id);
if (RB_UNLIKELY(id_to_obj_tbl)) {
st_insert(id_to_obj_tbl, (st_data_t)id, (st_data_t)obj);
}
}
rb_gc_vm_unlock(lock_lev);
return id;
}
static void
build_id_to_obj_i(VALUE obj, void *data)
{
st_table *id_to_obj_tbl = (st_table *)data;
if (rb_shape_obj_has_id(obj)) {
st_insert(id_to_obj_tbl, rb_obj_id(obj), obj);
}
}
static VALUE
object_id_to_ref(void *objspace_ptr, VALUE object_id)
{
rb_objspace_t *objspace = objspace_ptr;
unsigned int lev = rb_gc_vm_lock();
if (!id_to_obj_tbl) {
rb_gc_vm_barrier(); // stop other ractors
id_to_obj_tbl = st_init_table(&object_id_hash_type);
id_to_obj_value = TypedData_Wrap_Struct(0, &id_to_obj_tbl_type, id_to_obj_tbl);
rb_gc_impl_each_object(objspace, build_id_to_obj_i, (void *)id_to_obj_tbl);
}
VALUE obj;
bool found = st_lookup(id_to_obj_tbl, object_id, &obj) && !rb_gc_impl_garbage_object_p(objspace, obj);
rb_gc_vm_unlock(lev);
if (found) {
return obj;
}
if (rb_funcall(object_id, rb_intern(">="), 1, ULL2NUM(next_object_id))) {
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not an id value", rb_funcall(object_id, rb_intern("to_s"), 1, INT2FIX(10)));
}
else {
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is a recycled object", rb_funcall(object_id, rb_intern("to_s"), 1, INT2FIX(10)));
}
}
static inline void
obj_free_object_id(VALUE obj)
{
if (RB_UNLIKELY(id_to_obj_tbl)) {
if (rb_shape_obj_has_id(obj)) {
VALUE obj_id = object_id(obj);
RUBY_ASSERT(FIXNUM_P(obj_id) || RB_TYPE_P(obj, T_BIGNUM));
if (!st_delete(id_to_obj_tbl, (st_data_t *)&obj_id, NULL)) {
rb_bug("Object ID seen, but not in id_to_obj table: object_id=%llu object=%s", NUM2ULL(obj_id), rb_obj_info(obj));
}
}
}
}
void
rb_gc_obj_free_vm_weak_references(VALUE obj)
{
obj_free_object_id(obj);
if (FL_TEST_RAW(obj, FL_EXIVAR)) {
rb_free_generic_ivar((VALUE)obj);
FL_UNSET_RAW(obj, FL_EXIVAR);
}
switch (BUILTIN_TYPE(obj)) {
case T_STRING:
if (FL_TEST_RAW(obj, RSTRING_FSTR)) {
rb_gc_free_fstring(obj);
}
break;
case T_SYMBOL:
rb_gc_free_dsymbol(obj);
break;
case T_IMEMO:
switch (imemo_type(obj)) {
case imemo_callinfo:
rb_vm_ci_free((const struct rb_callinfo *)obj);
break;
case imemo_ment:
rb_free_method_entry_vm_weak_references((const rb_method_entry_t *)obj);
break;
default:
break;
}
break;
default:
break;
}
}
/*
* call-seq:
* ObjectSpace._id2ref(object_id) -> an_object
@ -1807,7 +1999,7 @@ id2ref(VALUE objid)
}
}
VALUE obj = rb_gc_impl_object_id_to_ref(rb_gc_get_objspace(), objid);
VALUE obj = object_id_to_ref(rb_gc_get_objspace(), objid);
if (!rb_multi_ractor_p() || rb_ractor_shareable_p(obj)) {
return obj;
}
@ -1824,7 +2016,7 @@ os_id2ref(VALUE os, VALUE objid)
}
static VALUE
rb_find_object_id(void *objspace, VALUE obj, VALUE (*get_heap_object_id)(void *, VALUE))
rb_find_object_id(void *objspace, VALUE obj, VALUE (*get_heap_object_id)(VALUE))
{
if (SPECIAL_CONST_P(obj)) {
#if SIZEOF_LONG == SIZEOF_VOIDP
@ -1834,11 +2026,11 @@ rb_find_object_id(void *objspace, VALUE obj, VALUE (*get_heap_object_id)(void *,
#endif
}
return get_heap_object_id(objspace, obj);
return get_heap_object_id(obj);
}
static VALUE
nonspecial_obj_id(void *_objspace, VALUE obj)
nonspecial_obj_id(VALUE obj)
{
#if SIZEOF_LONG == SIZEOF_VOIDP
return (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG);
@ -1889,7 +2081,13 @@ rb_obj_id(VALUE obj)
* Otherwise, the object ID is a Numeric that is a non-zero multiple of
* (RUBY_IMMEDIATE_MASK + 1) which guarantees that it does not collide with
* any immediates. */
return rb_find_object_id(rb_gc_get_objspace(), obj, rb_gc_impl_object_id);
return rb_find_object_id(rb_gc_get_objspace(), obj, object_id);
}
bool
rb_obj_id_p(VALUE obj)
{
return rb_shape_obj_has_id(obj);
}
static enum rb_id_table_iterator_result
@ -3461,6 +3659,73 @@ vm_weak_table_gen_fields_foreach_too_complex_replace_i(st_data_t *_key, st_data_
struct st_table *rb_generic_fields_tbl_get(void);
static int
vm_weak_table_id_to_obj_foreach(st_data_t key, st_data_t value, st_data_t data)
{
struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data;
int ret = iter_data->callback((VALUE)value, iter_data->data);
switch (ret) {
case ST_CONTINUE:
return ret;
case ST_DELETE:
GC_ASSERT(rb_shape_obj_has_id((VALUE)value));
return ST_DELETE;
case ST_REPLACE: {
VALUE new_value = (VALUE)value;
ret = iter_data->update_callback(&new_value, iter_data->data);
if (value != new_value) {
DURING_GC_COULD_MALLOC_REGION_START();
{
st_insert(id_to_obj_tbl, key, (st_data_t)new_value);
}
DURING_GC_COULD_MALLOC_REGION_END();
}
return ST_CONTINUE;
}
}
return ret;
}
static int
vm_weak_table_id_to_obj_keys_foreach(st_data_t key, st_data_t value, st_data_t data)
{
struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data;
if (LIKELY(FIXNUM_P((VALUE)key))) {
return ST_CONTINUE;
}
int ret = iter_data->callback((VALUE)key, iter_data->data);
switch (ret) {
case ST_CONTINUE:
return ret;
case ST_DELETE:
return ST_DELETE;
case ST_REPLACE: {
VALUE new_key = (VALUE)key;
ret = iter_data->update_callback(&new_key, iter_data->data);
if (key != new_key) ret = ST_DELETE;
DURING_GC_COULD_MALLOC_REGION_START();
{
st_insert(id_to_obj_tbl, (st_data_t)new_key, value);
}
DURING_GC_COULD_MALLOC_REGION_END();
key = (st_data_t)new_key;
break;
}
}
return ret;
}
static int
vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data)
{
@ -3588,6 +3853,26 @@ rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback,
}
break;
}
case RB_GC_VM_ID_TO_OBJ_TABLE: {
if (id_to_obj_tbl) {
st_foreach(
id_to_obj_tbl,
vm_weak_table_id_to_obj_foreach,
(st_data_t)&foreach_data
);
}
break;
}
case RB_GC_VM_ID_TO_OBJ_TABLE_KEYS: {
if (id_to_obj_tbl && !RB_POSFIXABLE(next_object_id)) {
st_foreach(
id_to_obj_tbl,
vm_weak_table_id_to_obj_keys_foreach,
(st_data_t)&foreach_data
);
}
break;
}
case RB_GC_VM_GENERIC_FIELDS_TABLE: {
st_table *generic_fields_tbl = rb_generic_fields_tbl_get();
if (generic_fields_tbl) {
@ -3607,8 +3892,8 @@ rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback,
);
break;
}
default:
rb_bug("rb_gc_vm_weak_table_foreach: unknown table %d", table);
case RB_GC_VM_WEAK_TABLE_COUNT:
rb_bug("Unreacheable");
}
}
@ -4993,6 +5278,8 @@ void
Init_GC(void)
{
#undef rb_intern
rb_gc_register_address(&id_to_obj_value);
malloc_offset = gc_compute_malloc_offset();
rb_mGC = rb_define_module("GC");

View File

@ -472,7 +472,6 @@ typedef struct rb_objspace {
} flags;
rb_event_flag_t hook_events;
unsigned long long next_object_id;
rb_heap_t heaps[HEAP_COUNT];
size_t empty_pages_count;
@ -591,9 +590,6 @@ typedef struct rb_objspace {
size_t step_slots;
} rincgc;
st_table *id_to_obj_tbl;
st_table *obj_to_id_tbl;
#if GC_DEBUG_STRESS_TO_CLASS
VALUE stress_to_class;
#endif
@ -1512,31 +1508,6 @@ minimum_slots_for_heap(rb_objspace_t *objspace, rb_heap_t *heap)
return gc_params.heap_init_slots[heap_idx];
}
static int
object_id_cmp(st_data_t x, st_data_t y)
{
if (RB_TYPE_P(x, T_BIGNUM)) {
return !rb_big_eql(x, y);
}
else {
return x != y;
}
}
static st_index_t
object_id_hash(st_data_t n)
{
return FIX2LONG(rb_hash((VALUE)n));
}
#define OBJ_ID_INCREMENT (RUBY_IMMEDIATE_MASK + 1)
#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT)
static const struct st_hash_type object_id_hash_type = {
object_id_cmp,
object_id_hash,
};
/* garbage objects will be collected soon. */
bool
rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
@ -1562,76 +1533,6 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
!RVALUE_MARKED(objspace, ptr);
}
VALUE
rb_gc_impl_object_id(void *objspace_ptr, VALUE obj)
{
VALUE id;
rb_objspace_t *objspace = objspace_ptr;
unsigned int lev = rb_gc_vm_lock();
if (FL_TEST(obj, FL_SEEN_OBJ_ID)) {
st_data_t val;
if (st_lookup(objspace->obj_to_id_tbl, (st_data_t)obj, &val)) {
id = (VALUE)val;
}
else {
rb_bug("rb_gc_impl_object_id: FL_SEEN_OBJ_ID flag set but not found in table");
}
}
else {
GC_ASSERT(!st_lookup(objspace->obj_to_id_tbl, (st_data_t)obj, NULL));
id = ULL2NUM(objspace->next_object_id);
objspace->next_object_id += OBJ_ID_INCREMENT;
st_insert(objspace->obj_to_id_tbl, (st_data_t)obj, (st_data_t)id);
if (RB_UNLIKELY(objspace->id_to_obj_tbl)) {
st_insert(objspace->id_to_obj_tbl, (st_data_t)id, (st_data_t)obj);
}
FL_SET(obj, FL_SEEN_OBJ_ID);
}
rb_gc_vm_unlock(lev);
return id;
}
static int
build_id_to_obj_i(st_data_t key, st_data_t value, st_data_t data)
{
st_table *id_to_obj_tbl = (st_table *)data;
st_insert(id_to_obj_tbl, value, key);
return ST_CONTINUE;
}
VALUE
rb_gc_impl_object_id_to_ref(void *objspace_ptr, VALUE object_id)
{
rb_objspace_t *objspace = objspace_ptr;
unsigned int lev = rb_gc_vm_lock();
if (!objspace->id_to_obj_tbl) {
objspace->id_to_obj_tbl = st_init_table_with_size(&object_id_hash_type, st_table_size(objspace->obj_to_id_tbl));
st_foreach(objspace->obj_to_id_tbl, build_id_to_obj_i, (st_data_t)objspace->id_to_obj_tbl);
}
VALUE obj;
bool found = st_lookup(objspace->id_to_obj_tbl, object_id, &obj) && !rb_gc_impl_garbage_object_p(objspace, obj);
rb_gc_vm_unlock(lev);
if (found) {
return obj;
}
if (rb_funcall(object_id, rb_intern(">="), 1, ULL2NUM(objspace->next_object_id))) {
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not an id value", rb_funcall(object_id, rb_intern("to_s"), 1, INT2FIX(10)));
}
else {
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is a recycled object", rb_funcall(object_id, rb_intern("to_s"), 1, INT2FIX(10)));
}
}
static void free_stack_chunks(mark_stack_t *);
static void mark_stack_free_cache(mark_stack_t *);
static void heap_page_free(rb_objspace_t *objspace, struct heap_page *page);
@ -2666,25 +2567,6 @@ rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), voi
page->heap->final_slots_count++;
}
static void
obj_free_object_id(rb_objspace_t *objspace, VALUE obj)
{
st_data_t o = (st_data_t)obj, id;
GC_ASSERT(BUILTIN_TYPE(obj) == T_NONE || FL_TEST(obj, FL_SEEN_OBJ_ID));
FL_UNSET(obj, FL_SEEN_OBJ_ID);
if (st_delete(objspace->obj_to_id_tbl, &o, &id)) {
GC_ASSERT(id);
if (RB_UNLIKELY(objspace->id_to_obj_tbl)) {
st_delete(objspace->id_to_obj_tbl, &id, NULL);
}
}
else {
rb_bug("Object ID seen, but not in mapping table: %s", rb_obj_info(obj));
}
}
typedef int each_obj_callback(void *, void *, size_t, void *);
typedef int each_page_callback(struct heap_page *, void *);
@ -2868,7 +2750,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
rb_ary_push(table, block);
}
else {
table = rb_ary_new3(2, rb_gc_impl_object_id(objspace, obj), block);
table = rb_ary_new3(2, rb_obj_id(obj), block);
rb_obj_hide(table);
st_add_direct(finalizer_table, obj, table);
}
@ -3558,9 +3440,6 @@ gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bit
rb_gc_event_hook(vp, RUBY_INTERNAL_EVENT_FREEOBJ);
if (FL_TEST_RAW(vp, FL_SEEN_OBJ_ID)) {
obj_free_object_id(objspace, vp);
}
rb_gc_obj_free_vm_weak_references(vp);
if (rb_gc_obj_free(objspace, vp)) {
// always add free slots back to the swept pages freelist,
@ -4622,8 +4501,6 @@ mark_roots(rb_objspace_t *objspace, const char **categoryp)
st_foreach(finalizer_table, pin_value, (st_data_t)objspace);
}
st_foreach(objspace->obj_to_id_tbl, gc_mark_tbl_no_pin_i, (st_data_t)objspace);
if (stress_to_class) rb_gc_mark(stress_to_class);
rb_gc_save_machine_context();
@ -6247,7 +6124,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
if (RVALUE_MARKING(objspace, obj)) SET_ENTRY(marking, Qtrue);
if (RVALUE_MARKED(objspace, obj)) SET_ENTRY(marked, Qtrue);
if (RVALUE_PINNED(objspace, obj)) SET_ENTRY(pinned, Qtrue);
if (FL_TEST(obj, FL_SEEN_OBJ_ID)) SET_ENTRY(object_id, rb_obj_id(obj));
if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
object_metadata_entries[n].name = 0;
@ -6978,27 +6855,6 @@ gc_move(rb_objspace_t *objspace, VALUE src, VALUE dest, size_t src_slot_size, si
CLEAR_IN_BITMAP(GET_HEAP_UNCOLLECTIBLE_BITS(src), src);
CLEAR_IN_BITMAP(GET_HEAP_PAGE(src)->remembered_bits, src);
if (FL_TEST_RAW(src, FL_SEEN_OBJ_ID)) {
/* If the source object's object_id has been seen, we need to update
* the object to object id mapping. */
st_data_t srcid = (st_data_t)src, id;
gc_report(4, objspace, "Moving object with seen id: %p -> %p\n", (void *)src, (void *)dest);
/* Resizing the st table could cause a malloc */
DURING_GC_COULD_MALLOC_REGION_START();
{
if (!st_delete(objspace->obj_to_id_tbl, &srcid, &id)) {
rb_bug("gc_move: object ID seen, but not in mapping table: %s", rb_obj_info((VALUE)src));
}
st_insert(objspace->obj_to_id_tbl, (st_data_t)dest, id);
}
DURING_GC_COULD_MALLOC_REGION_END();
}
else {
GC_ASSERT(!st_lookup(objspace->obj_to_id_tbl, (st_data_t)src, NULL));
}
/* Move the object */
memcpy((void *)dest, (void *)src, MIN(src_slot_size, slot_size));
@ -7203,10 +7059,7 @@ gc_update_references(rb_objspace_t *objspace)
}
}
}
gc_ref_update_table_values_only(objspace->obj_to_id_tbl);
if (RB_UNLIKELY(objspace->id_to_obj_tbl)) {
gc_update_table_refs(objspace->id_to_obj_tbl);
}
gc_update_table_refs(finalizer_table);
rb_gc_update_vm_references((void *)objspace);
@ -9320,12 +9173,6 @@ rb_gc_impl_objspace_free(void *objspace_ptr)
heap->total_slots = 0;
}
if (objspace->id_to_obj_tbl) {
st_free_table(objspace->id_to_obj_tbl);
}
st_free_table(objspace->obj_to_id_tbl);
free_stack_chunks(&objspace->mark_stack);
mark_stack_free_cache(&objspace->mark_stack);
@ -9465,9 +9312,6 @@ rb_gc_impl_objspace_init(void *objspace_ptr)
/* Need to determine if we can use mmap at runtime. */
heap_page_alloc_use_mmap = INIT_HEAP_PAGE_ALLOC_USE_MMAP;
#endif
objspace->next_object_id = OBJ_ID_INITIAL;
objspace->id_to_obj_tbl = NULL;
objspace->obj_to_id_tbl = st_init_numtable();
#if RGENGC_ESTIMATE_OLDMALLOC
objspace->rgengc.oldmalloc_increase_limit = gc_params.oldmalloc_limit_min;
#endif

View File

@ -28,6 +28,8 @@ enum rb_gc_vm_weak_tables {
RB_GC_VM_CI_TABLE,
RB_GC_VM_OVERLOADED_CME_TABLE,
RB_GC_VM_GLOBAL_SYMBOLS_TABLE,
RB_GC_VM_ID_TO_OBJ_TABLE,
RB_GC_VM_ID_TO_OBJ_TABLE_KEYS,
RB_GC_VM_GENERIC_FIELDS_TABLE,
RB_GC_VM_FROZEN_STRINGS_TABLE,
RB_GC_VM_WEAK_TABLE_COUNT
@ -70,6 +72,7 @@ size_t rb_obj_memsize_of(VALUE obj);
void rb_gc_prepare_heap_process_object(VALUE obj);
bool ruby_free_at_exit_p(void);
bool rb_memerror_reentered(void);
bool rb_obj_id_p(VALUE);
#if USE_MODULAR_GC
bool rb_gc_event_hook_required_p(rb_event_flag_t event);

View File

@ -100,9 +100,6 @@ GC_IMPL_FN VALUE rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALU
GC_IMPL_FN void rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj);
GC_IMPL_FN void rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj);
GC_IMPL_FN void rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr);
// Object ID
GC_IMPL_FN VALUE rb_gc_impl_object_id(void *objspace_ptr, VALUE obj);
GC_IMPL_FN VALUE rb_gc_impl_object_id_to_ref(void *objspace_ptr, VALUE object_id);
// Forking
GC_IMPL_FN void rb_gc_impl_before_fork(void *objspace_ptr);
GC_IMPL_FN void rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid);

View File

@ -24,10 +24,6 @@ struct objspace {
size_t total_gc_time;
size_t total_allocated_objects;
st_table *id_to_obj_tbl;
st_table *obj_to_id_tbl;
unsigned long long next_object_id;
st_table *finalizer_table;
struct MMTk_final_job *finalizer_jobs;
rb_postponed_job_handle_t finalizer_postponed_job;
@ -227,8 +223,6 @@ rb_mmtk_scan_objspace(void)
st_foreach(objspace->finalizer_table, pin_value, (st_data_t)objspace);
}
st_foreach(objspace->obj_to_id_tbl, gc_mark_tbl_no_pin_i, (st_data_t)objspace);
struct MMTk_final_job *job = objspace->finalizer_jobs;
while (job != NULL) {
switch (job->kind) {
@ -336,41 +330,6 @@ rb_mmtk_update_table_i(VALUE val, void *data)
return ST_CONTINUE;
}
static int
rb_mmtk_update_obj_id_tables_obj_to_id_i(st_data_t key, st_data_t val, st_data_t data)
{
RUBY_ASSERT(RB_FL_TEST(key, FL_SEEN_OBJ_ID));
if (!mmtk_is_reachable((MMTk_ObjectReference)key)) {
return ST_DELETE;
}
return ST_CONTINUE;
}
static int
rb_mmtk_update_obj_id_tables_id_to_obj_i(st_data_t key, st_data_t val, st_data_t data)
{
RUBY_ASSERT(RB_FL_TEST(val, FL_SEEN_OBJ_ID));
if (!mmtk_is_reachable((MMTk_ObjectReference)val)) {
return ST_DELETE;
}
return ST_CONTINUE;
}
static void
rb_mmtk_update_obj_id_tables(void)
{
struct objspace *objspace = rb_gc_get_objspace();
st_foreach(objspace->obj_to_id_tbl, rb_mmtk_update_obj_id_tables_obj_to_id_i, 0);
if (objspace->id_to_obj_tbl) {
st_foreach(objspace->id_to_obj_tbl, rb_mmtk_update_obj_id_tables_id_to_obj_i, 0);
}
}
static int
rb_mmtk_global_tables_count(void)
{
@ -403,7 +362,6 @@ MMTk_RubyUpcalls ruby_upcalls = {
rb_mmtk_update_global_tables,
rb_mmtk_global_tables_count,
rb_mmtk_update_finalizer_table,
rb_mmtk_update_obj_id_tables,
};
// Use max 80% of the available memory by default for MMTk
@ -432,7 +390,6 @@ rb_gc_impl_objspace_alloc(void)
return calloc(1, sizeof(struct objspace));
}
static void objspace_obj_id_init(struct objspace *objspace);
static void gc_run_finalizers(void *data);
void
@ -442,8 +399,6 @@ rb_gc_impl_objspace_init(void *objspace_ptr)
objspace->measure_gc_time = true;
objspace_obj_id_init(objspace);
objspace->finalizer_table = st_init_numtable();
objspace->finalizer_postponed_job = rb_postponed_job_preregister(0, gc_run_finalizers, objspace);
@ -1069,111 +1024,6 @@ rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
gc_run_finalizers(objspace);
}
// Object ID
static int
object_id_cmp(st_data_t x, st_data_t y)
{
if (RB_TYPE_P(x, T_BIGNUM)) {
return !rb_big_eql(x, y);
}
else {
return x != y;
}
}
static st_index_t
object_id_hash(st_data_t n)
{
return FIX2LONG(rb_hash((VALUE)n));
}
#define OBJ_ID_INCREMENT (RUBY_IMMEDIATE_MASK + 1)
#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT)
static const struct st_hash_type object_id_hash_type = {
object_id_cmp,
object_id_hash,
};
static void
objspace_obj_id_init(struct objspace *objspace)
{
objspace->id_to_obj_tbl = NULL;
objspace->obj_to_id_tbl = st_init_numtable();
objspace->next_object_id = OBJ_ID_INITIAL;
}
VALUE
rb_gc_impl_object_id(void *objspace_ptr, VALUE obj)
{
VALUE id;
struct objspace *objspace = objspace_ptr;
unsigned int lev = rb_gc_vm_lock();
if (FL_TEST(obj, FL_SEEN_OBJ_ID)) {
st_data_t val;
if (st_lookup(objspace->obj_to_id_tbl, (st_data_t)obj, &val)) {
id = (VALUE)val;
}
else {
rb_bug("rb_gc_impl_object_id: FL_SEEN_OBJ_ID flag set but not found in table");
}
}
else {
RUBY_ASSERT(!st_lookup(objspace->obj_to_id_tbl, (st_data_t)obj, NULL));
id = ULL2NUM(objspace->next_object_id);
objspace->next_object_id += OBJ_ID_INCREMENT;
st_insert(objspace->obj_to_id_tbl, (st_data_t)obj, (st_data_t)id);
if (RB_UNLIKELY(objspace->id_to_obj_tbl)) {
st_insert(objspace->id_to_obj_tbl, (st_data_t)id, (st_data_t)obj);
}
FL_SET(obj, FL_SEEN_OBJ_ID);
}
rb_gc_vm_unlock(lev);
return id;
}
static int
build_id_to_obj_i(st_data_t key, st_data_t value, st_data_t data)
{
st_table *id_to_obj_tbl = (st_table *)data;
st_insert(id_to_obj_tbl, value, key);
return ST_CONTINUE;
}
VALUE
rb_gc_impl_object_id_to_ref(void *objspace_ptr, VALUE object_id)
{
struct objspace *objspace = objspace_ptr;
unsigned int lev = rb_gc_vm_lock();
if (!objspace->id_to_obj_tbl) {
objspace->id_to_obj_tbl = st_init_table_with_size(&object_id_hash_type, st_table_size(objspace->obj_to_id_tbl));
st_foreach(objspace->obj_to_id_tbl, build_id_to_obj_i, (st_data_t)objspace->id_to_obj_tbl);
}
VALUE obj;
bool found = st_lookup(objspace->id_to_obj_tbl, object_id, &obj) && !rb_gc_impl_garbage_object_p(objspace, obj);
rb_gc_vm_unlock(lev);
if (found) {
return obj;
}
if (rb_funcall(object_id, rb_intern(">="), 1, ULL2NUM(objspace->next_object_id))) {
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not an id value", rb_funcall(object_id, rb_intern("to_s"), 1, INT2FIX(10)));
}
else {
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is a recycled object", rb_funcall(object_id, rb_intern("to_s"), 1, INT2FIX(10)));
}
}
// Forking
void
@ -1364,7 +1214,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
n++; \
} while (0)
if (FL_TEST(obj, FL_SEEN_OBJ_ID)) SET_ENTRY(object_id, rb_obj_id(obj));
if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
object_metadata_entries[n].name = 0;
object_metadata_entries[n].val = 0;

View File

@ -68,7 +68,6 @@ typedef struct MMTk_RubyUpcalls {
void (*update_global_tables)(int tbl_idx);
int (*global_tables_count)(void);
void (*update_finalizer_table)(void);
void (*update_obj_id_tables)(void);
} MMTk_RubyUpcalls;
typedef struct MMTk_RawVecOfObjRef {

View File

@ -322,7 +322,6 @@ pub struct RubyUpcalls {
pub update_global_tables: extern "C" fn(tbl_idx: c_int),
pub global_tables_count: extern "C" fn() -> c_int,
pub update_finalizer_table: extern "C" fn(),
pub update_obj_id_tables: extern "C" fn(),
}
unsafe impl Sync for RubyUpcalls {}

View File

@ -185,7 +185,6 @@ struct UpdateFinalizerObjIdTables;
impl GlobalTableProcessingWork for UpdateFinalizerObjIdTables {
fn process_table(&mut self) {
(crate::upcalls().update_finalizer_table)();
(crate::upcalls().update_obj_id_tables)();
}
}
impl GCWork<Ruby> for UpdateFinalizerObjIdTables {

View File

@ -280,24 +280,12 @@ ruby_fl_type {
= 0,
/**
* This flag has something to do with object IDs. Unlike in the old days,
* an object's object ID (that a user can query using `Object#object_id`)
* is no longer its physical address represented using Ruby level integers.
* It is now a monotonic-increasing integer unrelated to the underlying
* memory arrangement. Object IDs are assigned when necessary; objects are
* born without one, and will eventually have such property when queried.
* The interpreter has to manage which one is which. This is the flag that
* helps the management. Objects with this flag set are the ones with
* object IDs assigned.
*
* @internal
*
* But honestly, @shyouhei doesn't think this flag should be visible from
* 3rd parties. It must be an implementation detail that they should never
* know. Might better be hidden.
*/
RUBY_FL_SEEN_OBJ_ID = (1<<9),
/**
* This flag is no longer in use
*
* @internal
*/
RUBY_FL_UNUSED9 = (1<<9),
/**
* This flag has something to do with instance variables. 3rd parties need

View File

@ -201,6 +201,7 @@ RUBY_ATTR_MALLOC void *rb_xcalloc_mul_add_mul(size_t, size_t, size_t, size_t);
static inline void *ruby_sized_xrealloc_inlined(void *ptr, size_t new_size, size_t old_size) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2));
static inline void *ruby_sized_xrealloc2_inlined(void *ptr, size_t new_count, size_t elemsiz, size_t old_count) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2, 3));
static inline void ruby_sized_xfree_inlined(void *ptr, size_t size);
void rb_gc_obj_id_moved(VALUE obj);
void *rb_gc_ractor_cache_alloc(rb_ractor_t *ractor);
void rb_gc_ractor_cache_free(void *cache);

View File

@ -48,8 +48,12 @@ VALUE rb_mod_set_temporary_name(VALUE, VALUE);
struct gen_fields_tbl;
int rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl);
void rb_obj_copy_ivs_to_hash_table(VALUE obj, st_table *table);
void rb_obj_convert_to_too_complex(VALUE obj, st_table *table);
void rb_obj_init_too_complex(VALUE obj, st_table *table);
void rb_evict_ivars_to_hash(VALUE obj);
void rb_evict_fields_to_hash(VALUE obj);
VALUE rb_field_get(VALUE obj, rb_shape_t *target_shape);
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
void rb_obj_field_set(VALUE obj, rb_shape_t *target_shape, VALUE val);
RUBY_SYMBOL_EXPORT_BEGIN
/* variable.c (export) */

View File

@ -151,7 +151,7 @@ rb_class_allocate_instance(VALUE klass)
VALUE
rb_obj_setup(VALUE obj, VALUE klass, VALUE type)
{
VALUE ignored_flags = RUBY_FL_PROMOTED | RUBY_FL_SEEN_OBJ_ID;
VALUE ignored_flags = RUBY_FL_PROMOTED;
RBASIC(obj)->flags = (type & ~ignored_flags) | (RBASIC(obj)->flags & ignored_flags);
RBASIC_SET_CLASS(obj, klass);
return obj;
@ -329,33 +329,27 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj)
RUBY_ASSERT(!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE));
RUBY_ASSERT(BUILTIN_TYPE(dest) == BUILTIN_TYPE(obj));
rb_shape_t *src_shape = rb_shape_get_shape(obj);
if (rb_shape_obj_too_complex(obj)) {
// obj is TOO_COMPLEX so we can copy its iv_hash
st_table *table = st_copy(ROBJECT_FIELDS_HASH(obj));
rb_obj_convert_to_too_complex(dest, table);
return;
}
uint32_t src_num_ivs = RBASIC_FIELDS_COUNT(obj);
rb_shape_t *shape_to_set_on_dest = src_shape;
VALUE * src_buf;
VALUE * dest_buf;
unsigned long src_num_ivs = rb_ivar_count(obj);
if (!src_num_ivs) {
return;
}
// The copy should be mutable, so we don't want the frozen shape
if (rb_shape_frozen_shape_p(src_shape)) {
shape_to_set_on_dest = rb_shape_get_parent(src_shape);
rb_shape_t *src_shape = rb_shape_get_shape(obj);
if (rb_shape_too_complex_p(src_shape)) {
// obj is TOO_COMPLEX so we can copy its iv_hash
st_table *table = st_copy(ROBJECT_FIELDS_HASH(obj));
if (rb_shape_has_object_id(src_shape)) {
st_data_t id = (st_data_t)ruby_internal_object_id;
st_delete(table, &id, NULL);
}
rb_obj_init_too_complex(dest, table);
return;
}
src_buf = ROBJECT_FIELDS(obj);
dest_buf = ROBJECT_FIELDS(dest);
rb_shape_t *shape_to_set_on_dest = src_shape;
rb_shape_t *initial_shape = rb_shape_get_shape(dest);
if (initial_shape->heap_index != src_shape->heap_index || !rb_shape_canonical_p(src_shape)) {
@ -363,25 +357,44 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj)
shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape);
if (UNLIKELY(rb_shape_too_complex_p(shape_to_set_on_dest))) {
st_table * table = rb_st_init_numtable_with_size(src_num_ivs);
st_table *table = rb_st_init_numtable_with_size(src_num_ivs);
rb_obj_copy_ivs_to_hash_table(obj, table);
rb_obj_convert_to_too_complex(dest, table);
rb_obj_init_too_complex(dest, table);
return;
}
}
RUBY_ASSERT(src_num_ivs <= shape_to_set_on_dest->capacity || rb_shape_too_complex_p(shape_to_set_on_dest));
VALUE *src_buf = ROBJECT_FIELDS(obj);
VALUE *dest_buf = ROBJECT_FIELDS(dest);
RUBY_ASSERT(src_num_ivs <= shape_to_set_on_dest->capacity);
if (initial_shape->capacity < shape_to_set_on_dest->capacity) {
rb_ensure_iv_list_size(dest, initial_shape->capacity, shape_to_set_on_dest->capacity);
dest_buf = ROBJECT_FIELDS(dest);
}
MEMCPY(dest_buf, src_buf, VALUE, src_num_ivs);
if (src_shape->next_field_index == shape_to_set_on_dest->next_field_index) {
// Happy path, we can just memcpy the fields content
MEMCPY(dest_buf, src_buf, VALUE, src_num_ivs);
// Fire write barriers
for (uint32_t i = 0; i < src_num_ivs; i++) {
RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]);
// Fire write barriers
for (uint32_t i = 0; i < src_num_ivs; i++) {
RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]);
}
}
else {
rb_shape_t *dest_shape = shape_to_set_on_dest;
while (src_shape->parent_id != INVALID_SHAPE_ID) {
if (src_shape->type == SHAPE_IVAR) {
while (dest_shape->edge_name != src_shape->edge_name) {
dest_shape = rb_shape_get_shape_by_id(dest_shape->parent_id);
}
RB_OBJ_WRITE(dest, &dest_buf[dest_shape->next_field_index - 1], src_buf[src_shape->next_field_index - 1]);
}
src_shape = rb_shape_get_shape_by_id(src_shape->parent_id);
}
}
rb_shape_set_shape(dest, shape_to_set_on_dest);

View File

@ -3575,10 +3575,11 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data)
{
size_t size = rb_gc_obj_slot_size(obj);
memcpy((void *)data->replacement, (void *)obj, size);
FL_UNSET_RAW(data->replacement, FL_SEEN_OBJ_ID);
void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c
rb_gc_obj_id_moved(data->replacement);
if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
rb_replace_generic_ivar(data->replacement, obj);
}
@ -3586,7 +3587,7 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data)
// Avoid mutations using bind_call, etc.
// We keep FL_SEEN_OBJ_ID so GC later clean the obj_id_table.
MEMZERO((char *)obj + sizeof(struct RBasic), char, size - sizeof(struct RBasic));
RBASIC(obj)->flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_SEEN_OBJ_ID);
RBASIC(obj)->flags = T_OBJECT | FL_FREEZE;
RBASIC_SET_CLASS_RAW(obj, rb_cRactorMovedObject);
return traverse_cont;
}

165
shape.c
View File

@ -30,6 +30,8 @@
#define SHAPE_BUFFER_SIZE 0x8000
#endif
#define ROOT_TOO_COMPLEX_SHAPE_ID 0x2
#define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32)
/* This depends on that the allocated memory by Ruby's allocator or
@ -45,15 +47,18 @@
static ID id_frozen;
static ID id_t_object;
ID ruby_internal_object_id; // extern
#define LEAF 0
#define BLACK 0x0
#define RED 0x1
enum shape_flags {
SHAPE_FL_FROZEN = 1 << 0,
SHAPE_FL_FROZEN = 1 << 0,
SHAPE_FL_HAS_OBJECT_ID = 1 << 1,
SHAPE_FL_TOO_COMPLEX = 1 << 2,
SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN,
SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID,
};
static redblack_node_t *
@ -472,6 +477,9 @@ rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type)
rb_shape_t *new_shape = rb_shape_alloc(id, shape, shape_type);
switch (shape_type) {
case SHAPE_OBJ_ID:
new_shape->flags |= SHAPE_FL_HAS_OBJECT_ID;
// fallthrough
case SHAPE_IVAR:
if (UNLIKELY(shape->next_field_index >= shape->capacity)) {
RUBY_ASSERT(shape->next_field_index == shape->capacity);
@ -497,13 +505,15 @@ rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type)
return new_shape;
}
static rb_shape_t *shape_transition_too_complex(rb_shape_t *original_shape);
static rb_shape_t *
get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed)
{
rb_shape_t *res = NULL;
// There should never be outgoing edges from "too complex"
RUBY_ASSERT(!rb_shape_too_complex_p(shape));
// There should never be outgoing edges from "too complex", except for SHAPE_FROZEN and SHAPE_OBJ_ID
RUBY_ASSERT(!rb_shape_too_complex_p(shape) || shape_type == SHAPE_FROZEN || shape_type == SHAPE_OBJ_ID);
*variation_created = false;
@ -547,7 +557,7 @@ get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bo
// If we're not allowed to create a new variation, of if we're out of shapes
// we return TOO_COMPLEX_SHAPE.
if (!new_variations_allowed || GET_SHAPE_TREE()->next_shape_id > MAX_SHAPE_ID) {
res = rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID);
res = shape_transition_too_complex(shape);
}
else {
rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
@ -694,7 +704,7 @@ rb_shape_transition_shape_frozen(VALUE obj)
RUBY_ASSERT(shape);
RUBY_ASSERT(RB_OBJ_FROZEN(obj));
if (rb_shape_frozen_shape_p(shape) || rb_shape_obj_too_complex(obj)) {
if (rb_shape_frozen_shape_p(shape)) {
return shape;
}
@ -705,17 +715,72 @@ rb_shape_transition_shape_frozen(VALUE obj)
}
bool dont_care;
next_shape = get_next_shape_internal(shape, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true);
next_shape = get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true);
RUBY_ASSERT(next_shape);
return next_shape;
}
static rb_shape_t *
shape_transition_too_complex(rb_shape_t *original_shape)
{
rb_shape_t *next_shape = rb_shape_get_shape_by_id(ROOT_TOO_COMPLEX_SHAPE_ID);
if (original_shape->flags & SHAPE_FL_FROZEN) {
bool dont_care;
next_shape = get_next_shape_internal(next_shape, id_frozen, SHAPE_FROZEN, &dont_care, false);
}
if (original_shape->flags & SHAPE_FL_HAS_OBJECT_ID) {
bool dont_care;
next_shape = get_next_shape_internal(next_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, false);
}
return next_shape;
}
rb_shape_t *
rb_shape_transition_shape_too_complex(VALUE obj)
{
return rb_shape_get_shape_by_id(OBJ_TOO_COMPLEX_SHAPE_ID);
rb_shape_t *original_shape = rb_shape_get_shape(obj);
return shape_transition_too_complex(original_shape);
}
bool
rb_shape_has_object_id(rb_shape_t *shape)
{
return shape->flags & SHAPE_FL_HAS_OBJECT_ID;
}
attr_index_t
rb_shape_object_id_index(rb_shape_t *shape)
{
RUBY_ASSERT(shape->flags & SHAPE_FL_HAS_OBJECT_ID);
while (shape->type != SHAPE_OBJ_ID) {
shape = rb_shape_get_parent(shape);
}
return shape->next_field_index - 1;
}
rb_shape_t *
rb_shape_object_id_shape(VALUE obj)
{
rb_shape_t* shape = rb_shape_get_shape(obj);
RUBY_ASSERT(shape);
if (shape->flags & SHAPE_FL_HAS_OBJECT_ID) {
while (shape->type != SHAPE_OBJ_ID) {
shape = rb_shape_get_parent(shape);
}
return shape;
}
bool dont_care;
rb_shape_t* next_shape = get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
RUBY_ASSERT(next_shape);
return next_shape;
}
/*
* This function is used for assertions where we don't want to increment
* max_iv_count
@ -864,6 +929,7 @@ shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
case SHAPE_T_OBJECT:
return false;
case SHAPE_OBJ_TOO_COMPLEX:
case SHAPE_OBJ_ID:
case SHAPE_FROZEN:
rb_bug("Ivar should not exist on transition");
}
@ -906,7 +972,7 @@ rb_shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
{
// It doesn't make sense to ask for the index of an IV that's stored
// on an object that is "too complex" as it uses a hash for storing IVs
RUBY_ASSERT(rb_shape_id(shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
RUBY_ASSERT(rb_shape_id(shape) != ROOT_TOO_COMPLEX_SHAPE_ID);
if (!shape_cache_get_iv_index(shape, id, value)) {
// If it wasn't in the ancestor cache, then don't do a linear search
@ -948,6 +1014,7 @@ rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shap
switch ((enum shape_type)dest_shape->type) {
case SHAPE_IVAR:
case SHAPE_OBJ_ID:
case SHAPE_FROZEN:
if (!next_shape->edges) {
return NULL;
@ -985,20 +1052,20 @@ rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shap
// Rebuild a similar shape with the same ivars but starting from
// a different SHAPE_T_OBJECT, and don't cary over non-canonical transitions
// such as SHAPE_FROZEN.
// such as SHAPE_FROZEN or SHAPE_OBJ_ID.
rb_shape_t *
rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
{
RUBY_ASSERT(rb_shape_id(initial_shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
RUBY_ASSERT(rb_shape_id(dest_shape) != OBJ_TOO_COMPLEX_SHAPE_ID);
RUBY_ASSERT(rb_shape_id(initial_shape) != ROOT_TOO_COMPLEX_SHAPE_ID);
RUBY_ASSERT(rb_shape_id(dest_shape) != ROOT_TOO_COMPLEX_SHAPE_ID);
rb_shape_t *midway_shape;
RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT);
RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT || initial_shape->type == SHAPE_ROOT);
if (dest_shape->type != initial_shape->type) {
midway_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_parent(dest_shape));
if (UNLIKELY(rb_shape_id(midway_shape) == OBJ_TOO_COMPLEX_SHAPE_ID)) {
if (UNLIKELY(rb_shape_id(midway_shape) == ROOT_TOO_COMPLEX_SHAPE_ID)) {
return midway_shape;
}
}
@ -1010,6 +1077,7 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
case SHAPE_IVAR:
midway_shape = rb_shape_get_next_iv_shape(midway_shape, dest_shape->edge_name);
break;
case SHAPE_OBJ_ID:
case SHAPE_ROOT:
case SHAPE_FROZEN:
case SHAPE_T_OBJECT:
@ -1025,19 +1093,19 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
RUBY_FUNC_EXPORTED bool
rb_shape_obj_too_complex(VALUE obj)
{
return rb_shape_get_shape_id(obj) == OBJ_TOO_COMPLEX_SHAPE_ID;
}
bool
rb_shape_too_complex_p(rb_shape_t *shape)
{
return rb_shape_id(shape) == OBJ_TOO_COMPLEX_SHAPE_ID;
return rb_shape_too_complex_p(rb_shape_get_shape(obj));
}
bool
rb_shape_id_too_complex_p(shape_id_t shape_id)
{
return shape_id == OBJ_TOO_COMPLEX_SHAPE_ID;
return rb_shape_too_complex_p(rb_shape_get_shape_by_id(shape_id));
}
bool
rb_shape_too_complex_p(rb_shape_t *shape)
{
return shape->flags & SHAPE_FL_TOO_COMPLEX;
}
size_t
@ -1070,16 +1138,27 @@ rb_shape_memsize(rb_shape_t *shape)
*/
static VALUE
rb_shape_too_complex(VALUE self)
shape_too_complex(VALUE self)
{
rb_shape_t *shape;
shape = rb_shape_get_shape_by_id(NUM2INT(rb_struct_getmember(self, rb_intern("id"))));
if (rb_shape_id(shape) == OBJ_TOO_COMPLEX_SHAPE_ID) {
return Qtrue;
}
else {
return Qfalse;
}
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id);
return RBOOL(rb_shape_too_complex_p(shape));
}
static VALUE
shape_frozen(VALUE self)
{
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id);
return RBOOL(rb_shape_frozen_shape_p(shape));
}
static VALUE
shape_has_object_id(VALUE self)
{
shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id")));
rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id);
return RBOOL(rb_shape_has_object_id(shape));
}
static VALUE
@ -1285,6 +1364,7 @@ Init_default_shapes(void)
id_frozen = rb_make_internal_id();
id_t_object = rb_make_internal_id();
ruby_internal_object_id = rb_make_internal_id();
#ifdef HAVE_MMAP
size_t shape_cache_mmap_size = rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError);
@ -1317,16 +1397,17 @@ Init_default_shapes(void)
#if RUBY_DEBUG
rb_shape_t *special_const_shape =
#endif
get_next_shape_internal(root, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true);
get_next_shape_internal(root, id_frozen, SHAPE_FROZEN, &dont_care, true);
RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID);
RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
RUBY_ASSERT(rb_shape_frozen_shape_p(special_const_shape));
rb_shape_t *too_complex_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID);
too_complex_shape->type = SHAPE_OBJ_TOO_COMPLEX;
too_complex_shape->flags |= SHAPE_FL_TOO_COMPLEX;
too_complex_shape->heap_index = 0;
RUBY_ASSERT(OBJ_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
RUBY_ASSERT(rb_shape_id(too_complex_shape) == OBJ_TOO_COMPLEX_SHAPE_ID);
RUBY_ASSERT(ROOT_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1));
RUBY_ASSERT(rb_shape_id(too_complex_shape) == ROOT_TOO_COMPLEX_SHAPE_ID);
// Make shapes for T_OBJECT
size_t *sizes = rb_gc_heap_sizes();
@ -1339,6 +1420,15 @@ Init_default_shapes(void)
t_object_shape->ancestor_index = LEAF;
RUBY_ASSERT(rb_shape_id(t_object_shape) == (shape_id_t)(i + FIRST_T_OBJECT_SHAPE_ID));
}
// Prebuild TOO_COMPLEX variations so that they already exist if we ever need them after we
// ran out of shapes.
rb_shape_t *shape;
shape = get_next_shape_internal(too_complex_shape, id_frozen, SHAPE_FROZEN, &dont_care, true);
get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
shape = get_next_shape_internal(too_complex_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true);
}
void
@ -1360,7 +1450,10 @@ Init_shape(void)
rb_define_method(rb_cShape, "parent", rb_shape_parent, 0);
rb_define_method(rb_cShape, "edges", rb_shape_edges, 0);
rb_define_method(rb_cShape, "depth", rb_shape_export_depth, 0);
rb_define_method(rb_cShape, "too_complex?", rb_shape_too_complex, 0);
rb_define_method(rb_cShape, "too_complex?", shape_too_complex, 0);
rb_define_method(rb_cShape, "shape_frozen?", shape_frozen, 0);
rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id, 0);
rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT));
rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR));
rb_define_const(rb_cShape, "SHAPE_T_OBJECT", INT2NUM(SHAPE_T_OBJECT));
@ -1368,7 +1461,7 @@ Init_shape(void)
rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS));
rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT));
rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID));
rb_define_const(rb_cShape, "OBJ_TOO_COMPLEX_SHAPE_ID", INT2NUM(OBJ_TOO_COMPLEX_SHAPE_ID));
rb_define_const(rb_cShape, "ROOT_TOO_COMPLEX_SHAPE_ID", INT2NUM(ROOT_TOO_COMPLEX_SHAPE_ID));
rb_define_const(rb_cShape, "FIRST_T_OBJECT_SHAPE_ID", INT2NUM(FIRST_T_OBJECT_SHAPE_ID));
rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS));
rb_define_const(rb_cShape, "SIZEOF_RB_SHAPE_T", INT2NUM(sizeof(rb_shape_t)));

20
shape.h
View File

@ -33,11 +33,13 @@ typedef uint32_t redblack_id_t;
# define SHAPE_MAX_VARIATIONS 8
# define INVALID_SHAPE_ID SHAPE_MASK
# define ROOT_SHAPE_ID 0x0
# define SPECIAL_CONST_SHAPE_ID (ROOT_SHAPE_ID + 1)
# define OBJ_TOO_COMPLEX_SHAPE_ID (SPECIAL_CONST_SHAPE_ID + 1)
# define FIRST_T_OBJECT_SHAPE_ID (OBJ_TOO_COMPLEX_SHAPE_ID + 1)
#define ROOT_SHAPE_ID 0x0
#define SPECIAL_CONST_SHAPE_ID 0x1
// ROOT_TOO_COMPLEX_SHAPE_ID 0x2
#define FIRST_T_OBJECT_SHAPE_ID 0x3
extern ID ruby_internal_object_id;
typedef struct redblack_node redblack_node_t;
@ -65,6 +67,7 @@ struct redblack_node {
enum shape_type {
SHAPE_ROOT,
SHAPE_IVAR,
SHAPE_OBJ_ID,
SHAPE_FROZEN,
SHAPE_T_OBJECT,
SHAPE_OBJ_TOO_COMPLEX,
@ -169,6 +172,9 @@ rb_shape_t *rb_shape_transition_shape_too_complex(VALUE obj);
bool rb_shape_transition_shape_remove_ivar(VALUE obj, ID id, rb_shape_t *shape, VALUE *removed);
rb_shape_t *rb_shape_get_next(rb_shape_t *shape, VALUE obj, ID id);
rb_shape_t *rb_shape_get_next_no_warnings(rb_shape_t *shape, VALUE obj, ID id);
rb_shape_t *rb_shape_object_id_shape(VALUE obj);
bool rb_shape_has_object_id(rb_shape_t *shape);
attr_index_t rb_shape_object_id_index(rb_shape_t *shape);
rb_shape_t *rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape);
@ -229,6 +235,12 @@ rb_shape_t *rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_
bool rb_shape_set_shape_id(VALUE obj, shape_id_t shape_id);
static inline bool
rb_shape_obj_has_id(VALUE obj)
{
return rb_shape_has_object_id(rb_shape_get_shape(obj));
}
VALUE rb_obj_debug_shape(VALUE self, VALUE obj);
// For ext/objspace

View File

@ -45,6 +45,7 @@
#include "ruby/thread.h"
#include "ruby/util.h"
#include "ruby_assert.h"
#include "shape.h"
#include "vm_sync.h"
#include "ruby/internal/attr/nonstring.h"
@ -384,7 +385,16 @@ fstring_hash(VALUE str)
#define fstring_hash rb_str_hash
#endif
#define BARE_STRING_P(str) (!FL_ANY_RAW(str, FL_EXIVAR) && RBASIC_CLASS(str) == rb_cString)
static inline bool
BARE_STRING_P(VALUE str)
{
if (RBASIC_CLASS(str) != rb_cString) return false;
if (FL_TEST_RAW(str, FL_EXIVAR)) {
return rb_ivar_count(str) == 0;
}
return true;
}
static inline st_index_t
str_do_hash(VALUE str)
@ -873,7 +883,6 @@ register_fstring(VALUE str, bool copy, bool force_precompute_hash)
RUBY_ASSERT(RB_TYPE_P(result, T_STRING));
RUBY_ASSERT(OBJ_FROZEN(result));
RUBY_ASSERT(!FL_TEST_RAW(result, STR_FAKESTR));
RUBY_ASSERT(!FL_TEST_RAW(result, FL_EXIVAR));
RUBY_ASSERT(RBASIC_CLASS(result) == rb_cString);
return result;

177
test/ruby/test_object_id.rb Normal file
View File

@ -0,0 +1,177 @@
require 'test/unit'
class TestObjectId < Test::Unit::TestCase
def setup
@obj = Object.new
end
def test_dup_new_id
id = @obj.object_id
refute_equal id, @obj.dup.object_id
end
def test_dup_with_ivar_and_id
id = @obj.object_id
@obj.instance_variable_set(:@foo, 42)
copy = @obj.dup
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
end
def test_dup_with_id_and_ivar
@obj.instance_variable_set(:@foo, 42)
id = @obj.object_id
copy = @obj.dup
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
end
def test_dup_with_id_and_ivar_and_frozen
@obj.instance_variable_set(:@foo, 42)
@obj.freeze
id = @obj.object_id
copy = @obj.dup
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
refute_predicate copy, :frozen?
end
def test_clone_new_id
id = @obj.object_id
refute_equal id, @obj.clone.object_id
end
def test_clone_with_ivar_and_id
id = @obj.object_id
@obj.instance_variable_set(:@foo, 42)
copy = @obj.clone
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
end
def test_clone_with_id_and_ivar
@obj.instance_variable_set(:@foo, 42)
id = @obj.object_id
copy = @obj.clone
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
end
def test_clone_with_id_and_ivar_and_frozen
@obj.instance_variable_set(:@foo, 42)
@obj.freeze
id = @obj.object_id
copy = @obj.clone
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
assert_predicate copy, :frozen?
end
def test_marshal_new_id
return pass if @obj.is_a?(Module)
id = @obj.object_id
refute_equal id, Marshal.load(Marshal.dump(@obj)).object_id
end
def test_marshal_with_ivar_and_id
return pass if @obj.is_a?(Module)
id = @obj.object_id
@obj.instance_variable_set(:@foo, 42)
copy = Marshal.load(Marshal.dump(@obj))
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
end
def test_marshal_with_id_and_ivar
return pass if @obj.is_a?(Module)
@obj.instance_variable_set(:@foo, 42)
id = @obj.object_id
copy = Marshal.load(Marshal.dump(@obj))
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
end
def test_marshal_with_id_and_ivar_and_frozen
return pass if @obj.is_a?(Module)
@obj.instance_variable_set(:@foo, 42)
@obj.freeze
id = @obj.object_id
copy = Marshal.load(Marshal.dump(@obj))
refute_equal id, copy.object_id
assert_equal 42, copy.instance_variable_get(:@foo)
refute_predicate copy, :frozen?
end
end
class TestObjectIdClass < TestObjectId
def setup
@obj = Class.new
end
end
class TestObjectIdGeneric < TestObjectId
def setup
@obj = Array.new
end
end
class TestObjectIdTooComplex < TestObjectId
class TooComplex
end
def setup
if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
end
8.times do |i|
TooComplex.new.instance_variable_set("@a#{i}", 1)
end
@obj = TooComplex.new
@obj.instance_variable_set(:@test, 1)
end
end
class TestObjectIdTooComplexClass < TestObjectId
class TooComplex < Module
end
def setup
if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
end
8.times do |i|
TooComplex.new.instance_variable_set("@a#{i}", 1)
end
@obj = TooComplex.new
@obj.instance_variable_set(:@test, 1)
end
end
class TestObjectIdTooComplexGeneric < TestObjectId
class TooComplex < Array
end
def setup
if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
end
8.times do |i|
TooComplex.new.instance_variable_set("@a#{i}", 1)
end
@obj = TooComplex.new
@obj.instance_variable_set(:@test, 1)
end
end

View File

@ -622,6 +622,73 @@ class TestShapes < Test::Unit::TestCase
end;
end
def test_too_complex_and_frozen
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
$VERBOSE = nil
class TooComplex
attr_reader :very_unique
end
RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
end
tc = TooComplex.new
tc.instance_variable_set(:"@very_unique", 3)
shape = RubyVM::Shape.of(tc)
assert_predicate shape, :too_complex?
refute_predicate shape, :shape_frozen?
tc.freeze
frozen_shape = RubyVM::Shape.of(tc)
refute_equal shape.id, frozen_shape.id
assert_predicate frozen_shape, :too_complex?
assert_predicate frozen_shape, :shape_frozen?
assert_equal 3, tc.very_unique
assert_equal 3, Ractor.make_shareable(tc).very_unique
end;
end
def test_too_complex_and_frozen_and_object_id
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
$VERBOSE = nil
class TooComplex
attr_reader :very_unique
end
RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
end
tc = TooComplex.new
tc.instance_variable_set(:"@very_unique", 3)
shape = RubyVM::Shape.of(tc)
assert_predicate shape, :too_complex?
refute_predicate shape, :shape_frozen?
tc.freeze
frozen_shape = RubyVM::Shape.of(tc)
refute_equal shape.id, frozen_shape.id
assert_predicate frozen_shape, :too_complex?
assert_predicate frozen_shape, :shape_frozen?
refute_predicate frozen_shape, :has_object_id?
tc.object_id
id_shape = RubyVM::Shape.of(tc)
refute_equal frozen_shape.id, id_shape.id
assert_predicate id_shape, :too_complex?
assert_predicate id_shape, :shape_frozen?
assert_predicate id_shape, :has_object_id?
assert_equal 3, tc.very_unique
assert_equal 3, Ractor.make_shareable(tc).very_unique
end;
end
def test_too_complex_obj_ivar_ractor_share
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;

View File

@ -26,6 +26,7 @@
#include "internal/eval.h"
#include "internal/hash.h"
#include "internal/object.h"
#include "internal/gc.h"
#include "internal/re.h"
#include "internal/symbol.h"
#include "internal/thread.h"
@ -63,6 +64,9 @@ 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 st_table *generic_fields_tbl_;
typedef int rb_ivar_foreach_callback_func(ID key, VALUE val, st_data_t arg);
static void rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only);
void
Init_var_tables(void)
{
@ -1294,6 +1298,31 @@ gen_fields_tbl_count(VALUE obj, const struct gen_fields_tbl *fields_tbl)
return n;
}
VALUE
rb_field_get(VALUE obj, rb_shape_t *target_shape)
{
RUBY_ASSERT(!SPECIAL_CONST_P(obj));
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
RUBY_ASSERT(target_shape->type == SHAPE_IVAR || target_shape->type == SHAPE_OBJ_ID);
attr_index_t attr_index = target_shape->next_field_index - 1;
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
ASSERT_vm_locking();
return RCLASS_FIELDS(obj)[attr_index];
case T_OBJECT:
return ROBJECT_FIELDS(obj)[attr_index];
default:
RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR));
struct gen_fields_tbl *fields_tbl;
rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl);
RUBY_ASSERT(fields_tbl);
return fields_tbl->as.shape.fields[attr_index];
}
}
VALUE
rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
{
@ -1439,7 +1468,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
if (!rb_shape_transition_shape_remove_ivar(obj, id, shape, &val)) {
if (!rb_shape_obj_too_complex(obj)) {
rb_evict_ivars_to_hash(obj);
rb_evict_fields_to_hash(obj);
}
st_table *table = NULL;
@ -1478,11 +1507,11 @@ rb_attr_delete(VALUE obj, ID id)
return rb_ivar_delete(obj, id, Qnil);
}
void
rb_obj_convert_to_too_complex(VALUE obj, st_table *table)
static void
obj_transition_too_complex(VALUE obj, st_table *table)
{
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
rb_shape_t *too_complex_shape = rb_shape_transition_shape_too_complex(obj);
shape_id_t shape_id = rb_shape_id(rb_shape_transition_shape_too_complex(obj));
VALUE *old_fields = NULL;
@ -1491,13 +1520,13 @@ rb_obj_convert_to_too_complex(VALUE obj, st_table *table)
if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) {
old_fields = ROBJECT_FIELDS(obj);
}
rb_shape_set_shape(obj, too_complex_shape);
rb_shape_set_shape_id(obj, shape_id);
ROBJECT_SET_FIELDS_HASH(obj, table);
break;
case T_CLASS:
case T_MODULE:
old_fields = RCLASS_FIELDS(obj);
rb_shape_set_shape(obj, too_complex_shape);
rb_shape_set_shape_id(obj, shape_id);
RCLASS_SET_FIELDS_HASH(obj, table);
break;
default:
@ -1514,9 +1543,9 @@ rb_obj_convert_to_too_complex(VALUE obj, st_table *table)
* compaction. We want the table to be updated rather than
* the original fields. */
#if SHAPE_IN_BASIC_FLAGS
rb_shape_set_shape(obj, too_complex_shape);
rb_shape_set_shape_id(obj, shape_id);
#else
old_fields_tbl->shape_id = rb_shape_id(too_complex_shape);
old_fields_tbl->shape_id = shape_id;
#endif
old_fields_tbl->as.complex.table = table;
old_fields = (VALUE *)old_fields_tbl;
@ -1525,11 +1554,11 @@ rb_obj_convert_to_too_complex(VALUE obj, st_table *table)
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);
#if SHAPE_IN_BASIC_FLAGS
rb_shape_set_shape(obj, too_complex_shape);
rb_shape_set_shape_id(obj, shape_id);
#else
fields_tbl->shape_id = rb_shape_id(too_complex_shape);
fields_tbl->shape_id = shape_id;
#endif
}
RB_VM_LOCK_LEAVE();
@ -1538,6 +1567,33 @@ rb_obj_convert_to_too_complex(VALUE obj, st_table *table)
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(obj));
RUBY_ASSERT(rb_shape_canonical_p(rb_shape_get_shape(obj)));
RUBY_ASSERT(rb_shape_get_shape(obj)->next_field_index == 0);
obj_transition_too_complex(obj, table);
}
// Copy all object fields, including ivars and internal object_id, etc
void
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(obj));
rb_shape_t *shape = rb_shape_get_shape(obj);
st_table *table = st_init_numtable_with_size(shape->next_field_index);
rb_obj_copy_fields_to_hash_table(obj, table);
obj_transition_too_complex(obj, table);
RUBY_ASSERT(rb_shape_obj_too_complex(obj));
}
void
rb_evict_ivars_to_hash(VALUE obj)
{
@ -1547,7 +1603,7 @@ rb_evict_ivars_to_hash(VALUE obj)
// Evacuate all previous values from shape into id_table
rb_obj_copy_ivs_to_hash_table(obj, table);
rb_obj_convert_to_too_complex(obj, table);
obj_transition_too_complex(obj, table);
RUBY_ASSERT(rb_shape_obj_too_complex(obj));
}
@ -1618,6 +1674,42 @@ too_complex:
return result;
}
static void
general_field_set(VALUE obj, rb_shape_t *target_shape, 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_func)(VALUE, rb_shape_t *, void *),
void (*transition_too_complex_func)(VALUE, void *),
st_table *(*too_complex_table_func)(VALUE, void *))
{
rb_shape_t *current_shape = rb_shape_get_shape(obj);
if (UNLIKELY(rb_shape_too_complex_p(target_shape))) {
if (UNLIKELY(!rb_shape_too_complex_p(current_shape))) {
transition_too_complex_func(obj, data);
}
set_shape_func(obj, target_shape, data);
st_table *table = too_complex_table_func(obj, data);
st_insert(table, (st_data_t)target_shape->edge_name, (st_data_t)val);
RB_OBJ_WRITTEN(obj, Qundef, val);
}
else {
attr_index_t index = target_shape->next_field_index - 1;
if (index >= current_shape->capacity) {
shape_resize_fields_func(obj, current_shape->capacity, target_shape->capacity, data);
}
if (target_shape->next_field_index > current_shape->next_field_index) {
set_shape_func(obj, target_shape, data);
}
VALUE *table = shape_fields_func(obj, data);
RB_OBJ_WRITE(obj, &table[index], val);
}
}
struct gen_fields_lookup_ensure_size {
VALUE obj;
ID id;
@ -1627,34 +1719,34 @@ struct gen_fields_lookup_ensure_size {
};
static int
generic_ivar_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing)
generic_fields_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing)
{
ASSERT_vm_locking();
struct gen_fields_lookup_ensure_size *ivar_lookup = (struct gen_fields_lookup_ensure_size *)u;
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 || ivar_lookup->resize) {
if (!existing || fields_lookup->resize) {
if (existing) {
RUBY_ASSERT(ivar_lookup->shape->type == SHAPE_IVAR);
RUBY_ASSERT(rb_shape_get_shape_by_id(ivar_lookup->shape->parent_id)->capacity < ivar_lookup->shape->capacity);
RUBY_ASSERT(fields_lookup->shape->type == SHAPE_IVAR || fields_lookup->shape->type == SHAPE_OBJ_ID);
RUBY_ASSERT(rb_shape_get_shape_by_id(fields_lookup->shape->parent_id)->capacity < fields_lookup->shape->capacity);
}
else {
FL_SET_RAW((VALUE)*k, FL_EXIVAR);
}
fields_tbl = gen_fields_tbl_resize(fields_tbl, ivar_lookup->shape->capacity);
fields_tbl = gen_fields_tbl_resize(fields_tbl, fields_lookup->shape->capacity);
*v = (st_data_t)fields_tbl;
}
RUBY_ASSERT(FL_TEST((VALUE)*k, FL_EXIVAR));
ivar_lookup->fields_tbl = fields_tbl;
if (ivar_lookup->shape) {
fields_lookup->fields_tbl = fields_tbl;
if (fields_lookup->shape) {
#if SHAPE_IN_BASIC_FLAGS
rb_shape_set_shape(ivar_lookup->obj, ivar_lookup->shape);
rb_shape_set_shape(fields_lookup->obj, fields_lookup->shape);
#else
fields_tbl->shape_id = rb_shape_id(ivar_lookup->shape);
fields_tbl->shape_id = rb_shape_id(fields_lookup->shape);
#endif
}
@ -1666,46 +1758,46 @@ generic_ivar_set_shape_fields(VALUE obj, void *data)
{
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
struct gen_fields_lookup_ensure_size *ivar_lookup = data;
struct gen_fields_lookup_ensure_size *fields_lookup = data;
RB_VM_LOCK_ENTER();
{
st_update(generic_fields_tbl(obj, ivar_lookup->id, false), (st_data_t)obj, generic_ivar_lookup_ensure_size, (st_data_t)ivar_lookup);
st_update(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, generic_fields_lookup_ensure_size, (st_data_t)fields_lookup);
}
RB_VM_LOCK_LEAVE();
FL_SET_RAW(obj, FL_EXIVAR);
return ivar_lookup->fields_tbl->as.shape.fields;
return fields_lookup->fields_tbl->as.shape.fields;
}
static void
generic_ivar_set_shape_resize_fields(VALUE obj, attr_index_t _old_capa, attr_index_t new_capa, void *data)
{
struct gen_fields_lookup_ensure_size *ivar_lookup = data;
struct gen_fields_lookup_ensure_size *fields_lookup = data;
ivar_lookup->resize = true;
fields_lookup->resize = true;
}
static void
generic_ivar_set_set_shape(VALUE obj, rb_shape_t *shape, void *data)
{
struct gen_fields_lookup_ensure_size *ivar_lookup = data;
struct gen_fields_lookup_ensure_size *fields_lookup = data;
ivar_lookup->shape = shape;
fields_lookup->shape = shape;
}
static void
generic_ivar_set_transition_too_complex(VALUE obj, void *_data)
{
rb_evict_ivars_to_hash(obj);
rb_evict_fields_to_hash(obj);
FL_SET_RAW(obj, FL_EXIVAR);
}
static st_table *
generic_ivar_set_too_complex_table(VALUE obj, void *data)
{
struct gen_fields_lookup_ensure_size *ivar_lookup = data;
struct gen_fields_lookup_ensure_size *fields_lookup = data;
struct gen_fields_tbl *fields_tbl;
if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) {
@ -1717,7 +1809,7 @@ generic_ivar_set_too_complex_table(VALUE obj, void *data)
RB_VM_LOCK_ENTER();
{
st_insert(generic_fields_tbl(obj, ivar_lookup->id, false), (st_data_t)obj, (st_data_t)fields_tbl);
st_insert(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, (st_data_t)fields_tbl);
}
RB_VM_LOCK_LEAVE();
@ -1732,14 +1824,14 @@ generic_ivar_set_too_complex_table(VALUE obj, void *data)
static void
generic_ivar_set(VALUE obj, ID id, VALUE val)
{
struct gen_fields_lookup_ensure_size ivar_lookup = {
struct gen_fields_lookup_ensure_size fields_lookup = {
.obj = obj,
.id = id,
.resize = false,
.shape = NULL,
};
general_ivar_set(obj, id, val, &ivar_lookup,
general_ivar_set(obj, id, val, &fields_lookup,
generic_ivar_set_shape_fields,
generic_ivar_set_shape_resize_fields,
generic_ivar_set_set_shape,
@ -1747,6 +1839,23 @@ generic_ivar_set(VALUE obj, ID id, VALUE val)
generic_ivar_set_too_complex_table);
}
static void
generic_field_set(VALUE obj, rb_shape_t *target_shape, VALUE val)
{
struct gen_fields_lookup_ensure_size fields_lookup = {
.obj = obj,
.resize = false,
.shape = NULL,
};
general_field_set(obj, target_shape, val, &fields_lookup,
generic_ivar_set_shape_fields,
generic_ivar_set_shape_resize_fields,
generic_ivar_set_set_shape,
generic_ivar_set_transition_too_complex,
generic_ivar_set_too_complex_table);
}
void
rb_ensure_iv_list_size(VALUE obj, uint32_t current_capacity, uint32_t new_capacity)
{
@ -1779,6 +1888,12 @@ rb_obj_copy_ivs_to_hash_table(VALUE obj, st_table *table)
rb_ivar_foreach(obj, rb_obj_copy_ivs_to_hash_table_i, (st_data_t)table);
}
void
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)
{
@ -1802,7 +1917,7 @@ obj_ivar_set_set_shape(VALUE obj, rb_shape_t *shape, void *_data)
static void
obj_ivar_set_transition_too_complex(VALUE obj, void *_data)
{
rb_evict_ivars_to_hash(obj);
rb_evict_fields_to_hash(obj);
}
static st_table *
@ -1824,6 +1939,17 @@ rb_obj_ivar_set(VALUE obj, ID id, VALUE val)
obj_ivar_set_too_complex_table).index;
}
static void
obj_field_set(VALUE obj, rb_shape_t *target_shape, VALUE val)
{
general_field_set(obj, target_shape, val, NULL,
obj_ivar_set_shape_fields,
obj_ivar_set_shape_resize_fields,
obj_ivar_set_set_shape,
obj_ivar_set_transition_too_complex,
obj_ivar_set_too_complex_table);
}
/* Set the instance variable +val+ on object +obj+ at ivar name +id+.
* This function only works with T_OBJECT objects, so make sure
* +obj+ is of type T_OBJECT before using this function.
@ -1888,8 +2014,8 @@ void rb_obj_freeze_inline(VALUE 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_obj_too_complex(x) && rb_shape_too_complex_p(next_shape)) {
rb_evict_ivars_to_hash(x);
if (rb_shape_too_complex_p(next_shape) && !rb_shape_obj_too_complex(x)) {
rb_evict_fields_to_hash(x);
}
rb_shape_set_shape(x, next_shape);
@ -1939,6 +2065,26 @@ rb_ivar_set_internal(VALUE obj, ID id, VALUE val)
ivar_set(obj, id, val);
}
static void class_field_set(VALUE obj, rb_shape_t *target_shape, VALUE val);
void
rb_obj_field_set(VALUE obj, rb_shape_t *target_shape, VALUE val)
{
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
obj_field_set(obj, target_shape, val);
break;
case T_CLASS:
case T_MODULE:
ASSERT_vm_locking();
class_field_set(obj, target_shape, val);
break;
default:
generic_field_set(obj, target_shape, val);
break;
}
}
VALUE
rb_ivar_defined(VALUE obj, ID id)
{
@ -1978,29 +2124,35 @@ rb_ivar_defined(VALUE obj, ID id)
}
}
typedef int rb_ivar_foreach_callback_func(ID key, VALUE val, st_data_t arg);
struct iv_itr_data {
VALUE obj;
struct gen_fields_tbl * fields_tbl;
struct gen_fields_tbl *fields_tbl;
st_data_t arg;
rb_ivar_foreach_callback_func *func;
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)
iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_func *callback, struct iv_itr_data *itr_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(rb_shape_get_parent(shape), callback, itr_data);
}
// fallthrough
case SHAPE_IVAR:
ASSUME(callback);
if (iterate_over_shapes_with_callback(rb_shape_get_parent(shape), callback, itr_data))
if (iterate_over_shapes_with_callback(rb_shape_get_parent(shape), callback, itr_data)) {
return true;
}
VALUE * iv_list;
switch (BUILTIN_TYPE(itr_data->obj)) {
case T_OBJECT:
@ -2044,14 +2196,17 @@ each_hash_iv(st_data_t id, st_data_t val, st_data_t data)
}
static void
obj_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
obj_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only)
{
struct iv_itr_data itr_data = {
.obj = obj,
.arg = arg,
.func = func,
.ivar_only = ivar_only,
};
rb_shape_t *shape = rb_shape_get_shape(obj);
struct iv_itr_data itr_data;
itr_data.obj = obj;
itr_data.arg = arg;
itr_data.func = func;
if (rb_shape_obj_too_complex(obj)) {
if (rb_shape_too_complex_p(shape)) {
rb_st_foreach(ROBJECT_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data);
}
else {
@ -2060,17 +2215,20 @@ obj_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
}
static void
gen_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
gen_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only)
{
rb_shape_t *shape = rb_shape_get_shape(obj);
struct gen_fields_tbl *fields_tbl;
if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) return;
struct iv_itr_data itr_data;
itr_data.obj = obj;
itr_data.fields_tbl = fields_tbl;
itr_data.arg = arg;
itr_data.func = func;
struct iv_itr_data itr_data = {
.obj = obj,
.fields_tbl = fields_tbl,
.arg = arg,
.func = func,
.ivar_only = ivar_only,
};
if (rb_shape_obj_too_complex(obj)) {
rb_st_foreach(fields_tbl->as.complex.table, each_hash_iv, (st_data_t)&itr_data);
}
@ -2080,15 +2238,18 @@ gen_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
}
static void
class_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
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));
rb_shape_t *shape = rb_shape_get_shape(obj);
struct iv_itr_data itr_data;
itr_data.obj = obj;
itr_data.arg = arg;
itr_data.func = func;
struct iv_itr_data itr_data = {
.obj = obj,
.arg = arg,
.func = func,
.ivar_only = ivar_only,
};
if (rb_shape_obj_too_complex(obj)) {
rb_st_foreach(RCLASS_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data);
}
@ -2098,35 +2259,89 @@ class_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
}
void
rb_copy_generic_ivar(VALUE clone, VALUE obj)
rb_copy_generic_ivar(VALUE dest, VALUE obj)
{
struct gen_fields_tbl *obj_fields_tbl;
struct gen_fields_tbl *new_fields_tbl;
rb_check_frozen(clone);
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;
}
rb_shape_t *src_shape = rb_shape_get_shape(obj);
if (rb_gen_fields_tbl_get(obj, 0, &obj_fields_tbl)) {
if (gen_fields_tbl_count(obj, obj_fields_tbl) == 0)
goto clear;
FL_SET(clone, FL_EXIVAR);
FL_SET(dest, FL_EXIVAR);
if (rb_shape_obj_too_complex(obj)) {
new_fields_tbl = xmalloc(sizeof(struct gen_fields_tbl));
#if !SHAPE_IN_BASIC_FLAGS
new_fields_tbl->shape_id = old_fields_tbl->shape_id;
#endif
new_fields_tbl->as.complex.table = st_copy(obj_fields_tbl->as.complex.table);
if (rb_shape_too_complex_p(src_shape)) {
// obj is TOO_COMPLEX so we can copy its iv_hash
st_table *table = st_copy(obj_fields_tbl->as.complex.table);
if (rb_shape_has_object_id(src_shape)) {
st_data_t id = (st_data_t)ruby_internal_object_id;
st_delete(table, &id, NULL);
}
rb_obj_init_too_complex(dest, table);
return;
}
rb_shape_t *shape_to_set_on_dest = src_shape;
rb_shape_t *initial_shape = rb_shape_get_shape(dest);
if (!rb_shape_canonical_p(src_shape)) {
RUBY_ASSERT(initial_shape->type == SHAPE_ROOT);
shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape);
if (UNLIKELY(rb_shape_too_complex_p(shape_to_set_on_dest))) {
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 (!shape_to_set_on_dest->capacity) {
rb_shape_set_shape(dest, shape_to_set_on_dest);
FL_UNSET(dest, FL_EXIVAR);
return;
}
new_fields_tbl = gen_fields_tbl_resize(0, shape_to_set_on_dest->capacity);
VALUE *src_buf = obj_fields_tbl->as.shape.fields;
VALUE *dest_buf = new_fields_tbl->as.shape.fields;
if (src_shape->next_field_index == shape_to_set_on_dest->next_field_index) {
// Happy path, we can just memcpy the ivptr content
MEMCPY(dest_buf, src_buf, VALUE, shape_to_set_on_dest->next_field_index);
// Fire write barriers
for (uint32_t i = 0; i < shape_to_set_on_dest->next_field_index; i++) {
RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]);
}
}
else {
new_fields_tbl = gen_fields_tbl_resize(0, obj_fields_tbl->as.shape.fields_count);
rb_shape_t *dest_shape = shape_to_set_on_dest;
while (src_shape->parent_id != INVALID_SHAPE_ID) {
if (src_shape->type == SHAPE_IVAR) {
while (dest_shape->edge_name != src_shape->edge_name) {
dest_shape = rb_shape_get_shape_by_id(dest_shape->parent_id);
}
for (uint32_t i=0; i<obj_fields_tbl->as.shape.fields_count; i++) {
RB_OBJ_WRITE(clone, &new_fields_tbl->as.shape.fields[i], obj_fields_tbl->as.shape.fields[i]);
RB_OBJ_WRITE(dest, &dest_buf[dest_shape->next_field_index - 1], src_buf[src_shape->next_field_index - 1]);
}
src_shape = rb_shape_get_shape_by_id(src_shape->parent_id);
}
}
@ -2136,25 +2351,19 @@ rb_copy_generic_ivar(VALUE clone, VALUE obj)
*/
RB_VM_LOCK_ENTER();
{
generic_fields_tbl_no_ractor_check(clone);
st_insert(generic_fields_tbl_no_ractor_check(obj), (st_data_t)clone, (st_data_t)new_fields_tbl);
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_VM_LOCK_LEAVE();
rb_shape_t *obj_shape = rb_shape_get_shape(obj);
if (rb_shape_frozen_shape_p(obj_shape)) {
rb_shape_set_shape_id(clone, obj_shape->parent_id);
}
else {
rb_shape_set_shape(clone, obj_shape);
}
rb_shape_set_shape(dest, shape_to_set_on_dest);
}
return;
clear:
if (FL_TEST(clone, FL_EXIVAR)) {
rb_free_generic_ivar(clone);
FL_UNSET(clone, FL_EXIVAR);
if (FL_TEST(dest, FL_EXIVAR)) {
rb_free_generic_ivar(dest);
FL_UNSET(dest, FL_EXIVAR);
}
}
@ -2180,52 +2389,66 @@ rb_replace_generic_ivar(VALUE clone, VALUE obj)
}
void
rb_ivar_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only)
{
if (SPECIAL_CONST_P(obj)) return;
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
obj_ivar_each(obj, func, arg);
obj_fields_each(obj, func, arg, ivar_only);
break;
case T_CLASS:
case T_MODULE:
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0);
RB_VM_LOCK_ENTER();
{
class_ivar_each(obj, func, arg);
class_fields_each(obj, func, arg, ivar_only);
}
RB_VM_LOCK_LEAVE();
break;
default:
if (FL_TEST(obj, FL_EXIVAR)) {
gen_fields_each(obj, func, arg);
gen_fields_each(obj, func, arg, ivar_only);
}
break;
}
}
void
rb_ivar_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
{
rb_field_foreach(obj, func, arg, true);
}
st_index_t
rb_ivar_count(VALUE obj)
{
if (SPECIAL_CONST_P(obj)) return 0;
st_index_t iv_count = 0;
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
return ROBJECT_FIELDS_COUNT(obj);
iv_count = ROBJECT_FIELDS_COUNT(obj);
break;
case T_CLASS:
case T_MODULE:
return RCLASS_FIELDS_COUNT(obj);
iv_count = RCLASS_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)) {
return gen_fields_tbl_count(obj, fields_tbl);
iv_count = gen_fields_tbl_count(obj, fields_tbl);
}
}
break;
}
return 0;
if (rb_shape_obj_has_id(obj)) {
iv_count--;
}
return iv_count;
}
static int
@ -4304,7 +4527,7 @@ class_ivar_set_set_shape(VALUE obj, rb_shape_t *shape, void *_data)
static void
class_ivar_set_transition_too_complex(VALUE obj, void *_data)
{
rb_evict_ivars_to_hash(obj);
rb_evict_fields_to_hash(obj);
}
static st_table *
@ -4336,6 +4559,18 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val)
return existing;
}
static void
class_field_set(VALUE obj, rb_shape_t *target_shape, VALUE val)
{
RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
general_field_set(obj, target_shape, val, NULL,
class_ivar_set_shape_fields,
class_ivar_set_shape_resize_fields,
class_ivar_set_set_shape,
class_ivar_set_transition_too_complex,
class_ivar_set_too_complex_table);
}
static int
tbl_copy_i(ID key, VALUE val, st_data_t dest)
{

View File

@ -1517,7 +1517,7 @@ vm_setivar(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_index_t i
VM_ASSERT(!rb_ractor_shareable_p(obj) || rb_obj_frozen_p(obj));
shape_id_t shape_id = ROBJECT_SHAPE_ID(obj);
RUBY_ASSERT(!rb_shape_id_too_complex_p(dest_shape_id));
RUBY_ASSERT(dest_shape_id == INVALID_SHAPE_ID || !rb_shape_id_too_complex_p(dest_shape_id));
if (LIKELY(shape_id == dest_shape_id)) {
RUBY_ASSERT(dest_shape_id != INVALID_SHAPE_ID && shape_id != INVALID_SHAPE_ID);

View File

@ -227,7 +227,7 @@ pub const RUBY_FL_FINALIZE: ruby_fl_type = 128;
pub const RUBY_FL_TAINT: ruby_fl_type = 0;
pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256;
pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0;
pub const RUBY_FL_SEEN_OBJ_ID: ruby_fl_type = 512;
pub const RUBY_FL_UNUSED9: ruby_fl_type = 512;
pub const RUBY_FL_EXIVAR: ruby_fl_type = 1024;
pub const RUBY_FL_FREEZE: ruby_fl_type = 2048;
pub const RUBY_FL_USER0: ruby_fl_type = 4096;
@ -651,6 +651,7 @@ pub struct rb_shape {
pub capacity: attr_index_t,
pub type_: u8,
pub heap_index: u8,
pub flags: u8,
pub parent_id: shape_id_t,
pub ancestor_index: *mut redblack_node_t,
}

View File

@ -103,7 +103,7 @@ pub const RUBY_FL_FINALIZE: ruby_fl_type = 128;
pub const RUBY_FL_TAINT: ruby_fl_type = 0;
pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256;
pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0;
pub const RUBY_FL_SEEN_OBJ_ID: ruby_fl_type = 512;
pub const RUBY_FL_UNUSED9: ruby_fl_type = 512;
pub const RUBY_FL_EXIVAR: ruby_fl_type = 1024;
pub const RUBY_FL_FREEZE: ruby_fl_type = 2048;
pub const RUBY_FL_USER0: ruby_fl_type = 4096;
@ -408,6 +408,7 @@ pub struct rb_shape {
pub capacity: attr_index_t,
pub type_: u8,
pub heap_index: u8,
pub flags: u8,
pub parent_id: shape_id_t,
pub ancestor_index: *mut redblack_node_t,
}