@@ -1879,6 +1879,61 @@ static const rb_data_type_t id2ref_tbl_type = {
18791879 .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
18801880};
18811881
1882+ static VALUE obj_to_id_value = 0 ;
1883+ static st_table * obj_to_id_tbl = NULL ;
1884+
1885+ static void mark_hash_values (st_table * tbl );
1886+
1887+ static void
1888+ obj_to_id_tbl_mark (void * data )
1889+ {
1890+ st_table * table = (st_table * )data ;
1891+ if (UNLIKELY (!RB_POSFIXABLE (LAST_OBJECT_ID ()))) {
1892+ // It's very unlikely, but if enough object ids were generated, keys may be T_BIGNUM
1893+ mark_hash_values (table );
1894+ }
1895+ // We purposedly don't mark keys, as they are weak references.
1896+ // rb_gc_obj_free_vm_weak_references takes care of cleaning them up.
1897+ }
1898+
1899+ static size_t
1900+ obj_to_id_tbl_memsize (const void * data )
1901+ {
1902+ return rb_st_memsize (data );
1903+ }
1904+
1905+ static void
1906+ obj_to_id_tbl_compact (void * data )
1907+ {
1908+ st_table * table = (st_table * )data ;
1909+ if (LIKELY (RB_POSFIXABLE (LAST_OBJECT_ID ()))) {
1910+ // We know values are all FIXNUM, so no need to update them.
1911+ gc_ref_update_table_keys_only (table );
1912+ }
1913+ else {
1914+ gc_update_table_refs (table );
1915+ }
1916+ }
1917+
1918+ static void
1919+ obj_to_id_tbl_free (void * data )
1920+ {
1921+ id2ref_tbl = NULL ; // clear global ref
1922+ st_table * table = (st_table * )data ;
1923+ st_free_table (table );
1924+ }
1925+
1926+ static const rb_data_type_t obj_to_id_tbl_type = {
1927+ .wrap_struct_name = "VM/obj_to_id_table" ,
1928+ .function = {
1929+ .dmark = obj_to_id_tbl_mark ,
1930+ .dfree = obj_to_id_tbl_free ,
1931+ .dsize = obj_to_id_tbl_memsize ,
1932+ .dcompact = obj_to_id_tbl_compact ,
1933+ },
1934+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
1935+ };
1936+
18821937#define RUBY_ATOMIC_VALUE_LOAD (x ) (VALUE)(RUBY_ATOMIC_PTR_LOAD(x))
18831938
18841939static VALUE
@@ -1901,23 +1956,46 @@ class_object_id(VALUE klass)
19011956}
19021957
19031958static VALUE
1904- object_id0 (VALUE obj )
1959+ object_id0 (VALUE obj , bool shareable )
19051960{
19061961 VALUE id = Qfalse ;
19071962
1908- if (rb_shape_has_object_id (rb_obj_shape (obj ))) {
1909- shape_id_t object_id_shape_id = rb_shape_transition_object_id (obj );
1910- id = rb_obj_field_get (obj , object_id_shape_id );
1963+ rb_shape_t * current_shape = rb_obj_shape (obj );
1964+
1965+ if (rb_shape_has_object_id (current_shape )) {
1966+ shape_id_t object_id_shape_id = rb_shape_object_id (obj );
1967+
1968+ if (RB_UNLIKELY (shareable && RSHAPE (object_id_shape_id )-> type == SHAPE_EXTERNAL_OBJ_ID )) {
1969+ RUBY_ASSERT (obj_to_id_tbl );
1970+ st_lookup (obj_to_id_tbl , obj , & id );
1971+ }
1972+ else {
1973+ id = rb_obj_field_get (obj , object_id_shape_id );
1974+ }
19111975 RUBY_ASSERT (id , "object_id missing" );
19121976 return id ;
19131977 }
19141978
1915- // rb_shape_object_id_shape may lock if the current shape has
1916- // multiple children.
1917- shape_id_t object_id_shape_id = rb_shape_transition_object_id (obj );
1918-
19191979 id = generate_next_object_id ();
1920- rb_obj_field_set (obj , object_id_shape_id , id );
1980+ // If the object is shareable and doesn't have free capacity we can't safely
1981+ // resize it. So we have to store the object_id externally.
1982+ if (shareable && current_shape -> next_field_index == current_shape -> capacity ) {
1983+ shape_id_t object_id_shape_id = rb_shape_transition_external_object_id (obj );
1984+
1985+ if (RB_UNLIKELY (!obj_to_id_tbl )) {
1986+ obj_to_id_tbl = st_init_numtable ();
1987+ obj_to_id_value = TypedData_Wrap_Struct (0 , & obj_to_id_tbl_type , obj_to_id_tbl );
1988+ }
1989+ st_insert (obj_to_id_tbl , obj , id );
1990+ rb_shape_set_shape_id (obj , object_id_shape_id );
1991+ }
1992+ else {
1993+ // rb_shape_object_id_shape may lock if the current shape has
1994+ // multiple children.
1995+ shape_id_t object_id_shape_id = rb_shape_transition_object_id (obj );
1996+ rb_obj_field_set (obj , object_id_shape_id , id );
1997+ }
1998+
19211999 if (RB_UNLIKELY (id2ref_tbl )) {
19222000 st_insert (id2ref_tbl , (st_data_t )id , (st_data_t )obj );
19232001 }
@@ -1940,12 +2018,12 @@ object_id(VALUE obj)
19402018
19412019 if (UNLIKELY (rb_gc_multi_ractor_p () && rb_ractor_shareable_p (obj ))) {
19422020 unsigned int lock_lev = rb_gc_vm_lock ();
1943- VALUE id = object_id0 (obj );
2021+ VALUE id = object_id0 (obj , true );
19442022 rb_gc_vm_unlock (lock_lev );
19452023 return id ;
19462024 }
19472025
1948- return object_id0 (obj );
2026+ return object_id0 (obj , false );
19492027}
19502028
19512029static void
@@ -2717,6 +2795,14 @@ mark_key(st_data_t key, st_data_t value, st_data_t data)
27172795 return ST_CONTINUE ;
27182796}
27192797
2798+ static int
2799+ mark_value (st_data_t key , st_data_t value , st_data_t data )
2800+ {
2801+ gc_mark_internal ((VALUE )value );
2802+
2803+ return ST_CONTINUE ;
2804+ }
2805+
27202806void
27212807rb_mark_set (st_table * tbl )
27222808{
@@ -2725,6 +2811,14 @@ rb_mark_set(st_table *tbl)
27252811 st_foreach (tbl , mark_key , (st_data_t )rb_gc_get_objspace ());
27262812}
27272813
2814+ static void
2815+ mark_hash_values (st_table * tbl )
2816+ {
2817+ if (!tbl ) return ;
2818+
2819+ st_foreach (tbl , mark_value , 0 );
2820+ }
2821+
27282822static int
27292823mark_keyvalue (st_data_t key , st_data_t value , st_data_t data )
27302824{
@@ -5512,6 +5606,7 @@ Init_GC(void)
55125606{
55135607#undef rb_intern
55145608 rb_gc_register_address (& id2ref_value );
5609+ rb_gc_register_address (& obj_to_id_value );
55155610
55165611 malloc_offset = gc_compute_malloc_offset ();
55175612
0 commit comments