Skip to content

Commit 0c6a801

Browse files
committed
Object#hash: use the object address for T_OBJECT
Rather than to generate an object_id, we can use the object address. However we have to record that we did, so that if the object is later moved, we know to record its original address inline, so that it keeps a stable hash. We can't use the same strategy for other objects because they delegate their fields to another IMEMO/fields object. So if they don't yet have fields or if the fields object is full, during compaction we'd need to allocate another one but that not allowed during GC.
1 parent d314673 commit 0c6a801

12 files changed

Lines changed: 278 additions & 57 deletions

File tree

ext/objspace/objspace_dump.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,8 @@ shape_id_i(shape_id_t shape_id, void *data)
820820
break;
821821
case SHAPE_OBJ_ID:
822822
dump_append(dc, ", \"shape_type\":\"OBJ_ID\"");
823+
case SHAPE_OLD_ADDRESS:
824+
dump_append(dc, ", \"shape_type\":\"OLD_ADDRESS\"");
823825
break;
824826
}
825827

gc.c

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3320,13 +3320,19 @@ rb_gc_obj_optimal_size(VALUE obj)
33203320
return rb_ary_size_as_embedded(obj);
33213321

33223322
case T_OBJECT:
3323-
if (rb_shape_obj_too_complex_p(obj)) {
3324-
return sizeof(struct RObject);
3325-
}
3326-
else {
3327-
return rb_obj_embedded_size(ROBJECT_FIELDS_CAPACITY(obj));
3323+
{
3324+
shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
3325+
if (rb_shape_too_complex_p(shape_id)) {
3326+
return sizeof(struct RObject);
3327+
}
3328+
else {
3329+
attr_index_t fields_count = ROBJECT_FIELDS_COUNT(obj);
3330+
if (FL_TEST_RAW(obj, RUBY_FL_ADDRESS_SEEN) && !rb_shape_has_old_address(shape_id)) {
3331+
fields_count++;
3332+
}
3333+
return rb_obj_embedded_size(fields_count);
3334+
}
33283335
}
3329-
33303336
case T_STRING:
33313337
return rb_str_size_as_embedded(obj);
33323338

@@ -4139,6 +4145,18 @@ rb_gc_update_vm_references(void *objspace)
41394145
#endif
41404146
}
41414147

4148+
void
4149+
rb_gc_update_moved_object(void *objspace, VALUE dest, VALUE src)
4150+
{
4151+
if (RB_TYPE_P(dest, T_IMEMO)) {
4152+
return;
4153+
}
4154+
4155+
if (FL_TEST_RAW(dest, RUBY_FL_ADDRESS_SEEN)) {
4156+
rb_obj_set_stable_address(dest, src);
4157+
}
4158+
}
4159+
41424160
void
41434161
rb_gc_update_object_references(void *objspace, VALUE obj)
41444162
{

gc/default/default.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6122,7 +6122,7 @@ rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
61226122
struct rb_gc_object_metadata_names {
61236123
// Must be ID only
61246124
ID ID_wb_protected, ID_age, ID_old, ID_uncollectible, ID_marking,
6125-
ID_marked, ID_pinned, ID_object_id, ID_shareable;
6125+
ID_marked, ID_pinned, ID_object_id, ID_address_seen, ID_shareable;
61266126
};
61276127

61286128
#define RB_GC_OBJECT_METADATA_ENTRY_COUNT (sizeof(struct rb_gc_object_metadata_names) / sizeof(ID))
@@ -6145,6 +6145,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
61456145
I(marked);
61466146
I(pinned);
61476147
I(object_id);
6148+
I(address_seen);
61486149
I(shareable);
61496150
#undef I
61506151
}
@@ -6164,6 +6165,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj)
61646165
if (RVALUE_MARKED(objspace, obj)) SET_ENTRY(marked, Qtrue);
61656166
if (RVALUE_PINNED(objspace, obj)) SET_ENTRY(pinned, Qtrue);
61666167
if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj));
6168+
if (FL_TEST_RAW(obj, RUBY_FL_ADDRESS_SEEN)) SET_ENTRY(address_seen, Qtrue);
61676169
if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue);
61686170

61696171
object_metadata_entries[n].name = 0;
@@ -7020,9 +7022,11 @@ gc_ref_update(void *vstart, void *vend, size_t stride, rb_objspace_t *objspace,
70207022
asan_unpoisoning_object(v) {
70217023
switch (BUILTIN_TYPE(v)) {
70227024
case T_NONE:
7023-
case T_MOVED:
70247025
case T_ZOMBIE:
70257026
break;
7027+
case T_MOVED:
7028+
rb_gc_update_moved_object(objspace, rb_gc_location(v), v);
7029+
break;
70267030
default:
70277031
if (RVALUE_WB_UNPROTECTED(objspace, v)) {
70287032
page->flags.has_uncollectible_wb_unprotected_objects = TRUE;

gc/gc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ MODULAR_GC_FN size_t rb_gc_obj_optimal_size(VALUE obj);
7575
MODULAR_GC_FN void rb_gc_mark_children(void *objspace, VALUE obj);
7676
MODULAR_GC_FN void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, void *data, bool weak_only, enum rb_gc_vm_weak_tables table);
7777
MODULAR_GC_FN void rb_gc_update_object_references(void *objspace, VALUE obj);
78+
MODULAR_GC_FN void rb_gc_update_moved_object(void *objspace, VALUE dest, VALUE src);
7879
MODULAR_GC_FN void rb_gc_update_vm_references(void *objspace);
7980
MODULAR_GC_FN void rb_gc_event_hook(VALUE obj, rb_event_flag_t event);
8081
MODULAR_GC_FN void *rb_gc_get_objspace(void);

hash.c

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,17 +312,25 @@ rb_objid_hash(st_index_t index)
312312
}
313313

314314
static st_index_t
315-
objid_hash(VALUE obj)
315+
stable_obj_address_hash(VALUE obj)
316316
{
317-
VALUE object_id = rb_obj_id(obj);
318-
if (!FIXNUM_P(object_id))
319-
object_id = rb_big_hash(object_id);
317+
if (RB_TYPE_P(obj, T_OBJECT)) {
318+
return (st_index_t)st_index_hash(rb_obj_stable_address(obj));
319+
}
320+
else {
321+
VALUE object_id = rb_obj_id(obj);
322+
if (UNLIKELY(!FIXNUM_P(object_id))) {
323+
object_id = rb_big_hash(object_id);
324+
}
320325

321326
#if SIZEOF_LONG == SIZEOF_VOIDP
322-
return (st_index_t)st_index_hash((st_index_t)NUM2LONG(object_id));
327+
return (st_index_t)NUM2LONG(object_id);
323328
#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
324-
return (st_index_t)st_index_hash((st_index_t)NUM2LL(object_id));
329+
return (st_index_t)NUM2LL(object_id);
330+
#else
331+
#error "Unexpected VALUE size"
325332
#endif
333+
}
326334
}
327335

328336
/**
@@ -363,7 +371,7 @@ objid_hash(VALUE obj)
363371
VALUE
364372
rb_obj_hash(VALUE obj)
365373
{
366-
long hnum = any_hash(obj, objid_hash);
374+
long hnum = any_hash(obj, stable_obj_address_hash);
367375
return ST2FIX(hnum);
368376
}
369377

include/ruby/internal/core/rtypeddata.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ rbimpl_typeddata_flags {
182182
/**
183183
* This flag no longer in use
184184
*/
185-
RUBY_TYPED_UNUSED = RUBY_FL_UNUSED6,
185+
RUBY_TYPED_UNUSED = 0,
186186

187187
/**
188188
* This flag determines whether marking and compaction should be carried out

include/ruby/internal/fl_type.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,15 @@ ruby_fl_type {
216216
*/
217217
RUBY_FL_PROMOTED = (1<<5),
218218

219-
/**
220-
* This flag is no longer in use
221-
*
222-
* @internal
223-
*/
224-
RUBY_FL_UNUSED6 = (1<<6),
219+
220+
/**
221+
* This flag is used to mark when an object address has been observed by
222+
* `rb_obj_hash` or similar. This indicates to the GC that the old address
223+
* must be recorded inside the moved object to keep `rb_obj_hash` stable.
224+
*
225+
* @internal
226+
*/
227+
RUBY_FL_ADDRESS_SEEN = (1<<6),
225228

226229
/**
227230
* This flag has something to do with finalisers. A ruby object can have

internal/variable.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ shape_id_t rb_evict_fields_to_hash(VALUE obj);
5454
VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id);
5555
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
5656
void rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val);
57+
st_index_t rb_obj_stable_address(VALUE obj);
58+
void rb_obj_set_stable_address(VALUE obj, VALUE old_address);
5759

5860
RUBY_SYMBOL_EXPORT_BEGIN
5961
/* variable.c (export) */

shape.c

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
3434
#define ANCESTOR_SEARCH_MAX_DEPTH 2
3535

36-
static ID id_object_id;
36+
static ID id_object_id, id_old_address;
3737

3838
#define LEAF 0
3939
#define BLACK 0x0
@@ -504,6 +504,7 @@ rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type)
504504

505505
switch (shape_type) {
506506
case SHAPE_OBJ_ID:
507+
case SHAPE_OLD_ADDRESS:
507508
case SHAPE_IVAR:
508509
if (UNLIKELY(shape->next_field_index >= shape->capacity)) {
509510
RUBY_ASSERT(shape->next_field_index == shape->capacity);
@@ -725,6 +726,21 @@ rb_shape_transition_object_id(VALUE obj)
725726
return shape_transition_object_id(RBASIC_SHAPE_ID(obj));
726727
}
727728

729+
shape_id_t
730+
rb_shape_transition_old_address(shape_id_t original_shape_id)
731+
{
732+
RUBY_ASSERT(!rb_shape_has_old_address(original_shape_id));
733+
734+
bool dont_care;
735+
rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), id_old_address, SHAPE_OLD_ADDRESS, &dont_care, true);
736+
if (!shape) {
737+
shape = RSHAPE(ROOT_SHAPE_WITH_OLD_ADDRESS);
738+
}
739+
740+
RUBY_ASSERT(shape);
741+
return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OLD_ADDRESS;
742+
}
743+
728744
shape_id_t
729745
rb_shape_object_id(shape_id_t original_shape_id)
730746
{
@@ -741,6 +757,22 @@ rb_shape_object_id(shape_id_t original_shape_id)
741757
return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID;
742758
}
743759

760+
shape_id_t
761+
rb_shape_old_address(shape_id_t original_shape_id)
762+
{
763+
RUBY_ASSERT(rb_shape_has_old_address(original_shape_id));
764+
765+
rb_shape_t *shape = RSHAPE(original_shape_id);
766+
while (shape->type != SHAPE_OLD_ADDRESS) {
767+
if (UNLIKELY(shape->parent_id == INVALID_SHAPE_ID)) {
768+
rb_bug("Missing old_address in shape tree");
769+
}
770+
shape = RSHAPE(shape->parent_id);
771+
}
772+
773+
return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OLD_ADDRESS;
774+
}
775+
744776
static inline shape_id_t
745777
transition_complex(shape_id_t shape_id)
746778
{
@@ -860,6 +892,7 @@ shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value)
860892
case SHAPE_ROOT:
861893
return false;
862894
case SHAPE_OBJ_ID:
895+
case SHAPE_OLD_ADDRESS:
863896
rb_bug("Ivar should not exist on transition");
864897
}
865898
}
@@ -1111,6 +1144,7 @@ shape_rebuild(rb_shape_t *initial_shape, rb_shape_t *dest_shape)
11111144
midway_shape = shape_get_next_iv_shape(midway_shape, dest_shape->edge_name);
11121145
break;
11131146
case SHAPE_OBJ_ID:
1147+
case SHAPE_OLD_ADDRESS:
11141148
case SHAPE_ROOT:
11151149
break;
11161150
}
@@ -1242,11 +1276,16 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
12421276
rb_shape_t *shape = RSHAPE(shape_id);
12431277

12441278
bool has_object_id = false;
1279+
bool has_old_address = false;
1280+
12451281
while (shape->parent_id != INVALID_SHAPE_ID) {
12461282
if (shape->type == SHAPE_OBJ_ID) {
12471283
has_object_id = true;
1248-
break;
12491284
}
1285+
else if (shape->type == SHAPE_OLD_ADDRESS) {
1286+
has_old_address = true;
1287+
}
1288+
12501289
shape = RSHAPE(shape->parent_id);
12511290
}
12521291

@@ -1263,6 +1302,19 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
12631302
}
12641303
}
12651304

1305+
if (rb_shape_has_old_address(shape_id)) {
1306+
if (!has_old_address) {
1307+
rb_p(obj);
1308+
rb_bug("shape_id claim having has_old_address but doesn't shape_id=%u, obj=%s", shape_id, rb_obj_info(obj));
1309+
}
1310+
}
1311+
else {
1312+
if (has_old_address) {
1313+
rb_p(obj);
1314+
rb_bug("shape_id claim not having has_old_address but it does shape_id=%u, obj=%s", shape_id, rb_obj_info(obj));
1315+
}
1316+
}
1317+
12661318
// Make sure SHAPE_ID_HAS_IVAR_MASK is valid.
12671319
if (rb_shape_too_complex_p(shape_id)) {
12681320
RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK);
@@ -1272,6 +1324,11 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
12721324
if (has_object_id) {
12731325
ivar_count--;
12741326
}
1327+
1328+
if (has_old_address) {
1329+
ivar_count--;
1330+
}
1331+
12751332
if (ivar_count) {
12761333
RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK);
12771334
}
@@ -1543,6 +1600,7 @@ Init_default_shapes(void)
15431600
}
15441601

15451602
id_object_id = rb_make_internal_id();
1603+
id_old_address = rb_make_internal_id();
15461604

15471605
#ifdef HAVE_MMAP
15481606
size_t shape_cache_mmap_size = rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError);
@@ -1581,6 +1639,22 @@ Init_default_shapes(void)
15811639
RUBY_ASSERT(root_with_obj_id->next_field_index == 1);
15821640
RUBY_ASSERT(!(raw_shape_id(root_with_obj_id) & SHAPE_ID_HAS_IVAR_MASK));
15831641
(void)root_with_obj_id;
1642+
1643+
rb_shape_t *root_with_old_address = get_next_shape_internal(root, id_old_address, SHAPE_OLD_ADDRESS, &dontcare, true);
1644+
RUBY_ASSERT(raw_shape_id(root_with_old_address) == ROOT_SHAPE_WITH_OLD_ADDRESS);
1645+
RUBY_ASSERT(root_with_old_address->type == SHAPE_OLD_ADDRESS);
1646+
RUBY_ASSERT(root_with_old_address->edge_name == id_old_address);
1647+
RUBY_ASSERT(root_with_old_address->next_field_index == 1);
1648+
RUBY_ASSERT(!(raw_shape_id(root_with_obj_id) & SHAPE_ID_HAS_IVAR_MASK));
1649+
(void)root_with_old_address;
1650+
1651+
rb_shape_t *root_with_obj_id_and_old_address = get_next_shape_internal(root_with_obj_id, id_old_address, SHAPE_OLD_ADDRESS, &dontcare, true);
1652+
RUBY_ASSERT(raw_shape_id(root_with_obj_id_and_old_address) == ROOT_SHAPE_WITH_OBJ_ID_AND_OLD_ADDRESS);
1653+
RUBY_ASSERT(root_with_obj_id_and_old_address->type == SHAPE_OLD_ADDRESS);
1654+
RUBY_ASSERT(root_with_obj_id_and_old_address->edge_name == id_old_address);
1655+
RUBY_ASSERT(root_with_obj_id_and_old_address->next_field_index == 2);
1656+
RUBY_ASSERT(!(raw_shape_id(root_with_obj_id_and_old_address) & SHAPE_ID_HAS_IVAR_MASK));
1657+
(void)root_with_obj_id_and_old_address;
15841658
}
15851659

15861660
void

0 commit comments

Comments
 (0)