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:
parent
d34c150547
commit
f48e45d1e9
Notes:
git
2025-05-08 05:58:19 +00:00
@ -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
387
gc.c
@ -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");
|
||||
|
@ -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
|
||||
|
3
gc/gc.h
3
gc/gc.h
@ -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);
|
||||
|
@ -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);
|
||||
|
152
gc/mmtk/mmtk.c
152
gc/mmtk/mmtk.c
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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 {}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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) */
|
||||
|
69
object.c
69
object.c
@ -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);
|
||||
|
5
ractor.c
5
ractor.c
@ -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
165
shape.c
@ -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
20
shape.h
@ -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
|
||||
|
13
string.c
13
string.c
@ -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
177
test/ruby/test_object_id.rb
Normal 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
|
@ -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;
|
||||
|
419
variable.c
419
variable.c
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
3
yjit/src/cruby_bindings.inc.rs
generated
3
yjit/src/cruby_bindings.inc.rs
generated
@ -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,
|
||||
}
|
||||
|
3
zjit/src/cruby_bindings.inc.rs
generated
3
zjit/src/cruby_bindings.inc.rs
generated
@ -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,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user