@@ -2060,58 +2060,49 @@ vm_ccs_verify(struct rb_class_cc_entries *ccs, ID mid, VALUE klass)
20602060
20612061const rb_callable_method_entry_t * rb_check_overloaded_cme (const rb_callable_method_entry_t * cme , const struct rb_callinfo * const ci );
20622062
2063- static const struct rb_callcache *
2064- vm_search_cc ( const VALUE klass , const struct rb_callinfo * const ci )
2063+ static void
2064+ vm_evict_cc ( VALUE klass , VALUE cc_tbl , ID mid )
20652065{
2066- const ID mid = vm_ci_mid (ci );
2067- VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL (klass );
2068- struct rb_class_cc_entries * ccs = NULL ;
2069- VALUE ccs_data ;
2066+ ASSERT_vm_locking ();
20702067
2071- if (cc_tbl ) {
2072- // CCS data is keyed on method id, so we don't need the method id
2073- // for doing comparisons in the `for` loop below.
2074- if (rb_managed_id_table_lookup (cc_tbl , mid , & ccs_data )) {
2075- ccs = (struct rb_class_cc_entries * )ccs_data ;
2076- const int ccs_len = ccs -> len ;
2077-
2078- if (UNLIKELY (METHOD_ENTRY_INVALIDATED (ccs -> cme ))) {
2079- rb_managed_id_table_delete (cc_tbl , mid );
2080- rb_vm_ccs_free (ccs );
2081- ccs = NULL ;
2082- }
2083- else {
2084- VM_ASSERT (vm_ccs_verify (ccs , mid , klass ));
2068+ if (rb_multi_ractor_p ()) {
2069+ if (RCLASS_WRITABLE_CC_TBL (klass ) != cc_tbl ) {
2070+ // Another ractor updated the CC table while we were waiting on the VM lock.
2071+ // We have to retry.
2072+ return ;
2073+ }
20852074
2086- // We already know the method id is correct because we had
2087- // to look up the ccs_data by method id. All we need to
2088- // compare is argc and flag
2089- unsigned int argc = vm_ci_argc (ci );
2090- unsigned int flag = vm_ci_flag (ci );
2075+ struct rb_class_cc_entries * ccs = NULL ;
2076+ rb_managed_id_table_lookup (cc_tbl , mid , (VALUE * )& ccs );
20912077
2092- for ( int i = 0 ; i < ccs_len ; i ++ ) {
2093- unsigned int ccs_ci_argc = ccs -> entries [ i ]. argc ;
2094- unsigned int ccs_ci_flag = ccs -> entries [ i ]. flag ;
2095- const struct rb_callcache * ccs_cc = ccs -> entries [ i ]. cc ;
2078+ if (! ccs || ! METHOD_ENTRY_INVALIDATED ( ccs -> cme ) ) {
2079+ // Another ractor replaced that entry while we were waiting on the VM lock.
2080+ return ;
2081+ }
20962082
2097- VM_ASSERT (IMEMO_TYPE_P (ccs_cc , imemo_callcache ));
2083+ VALUE new_table = rb_vm_cc_table_dup (cc_tbl );
2084+ rb_vm_cc_table_delete (new_table , mid );
2085+ RB_OBJ_ATOMIC_WRITE (klass , & RCLASS_WRITABLE_CC_TBL (klass ), new_table );
2086+ }
2087+ else {
2088+ rb_vm_cc_table_delete (cc_tbl , mid );
2089+ }
2090+ }
20982091
2099- if (ccs_ci_argc == argc && ccs_ci_flag == flag ) {
2100- RB_DEBUG_COUNTER_INC (cc_found_in_ccs );
2092+ static const struct rb_callcache *
2093+ vm_populate_cc (VALUE klass , const struct rb_callinfo * const ci , ID mid )
2094+ {
2095+ ASSERT_vm_locking ();
21012096
2102- VM_ASSERT ( vm_cc_cme ( ccs_cc ) -> called_id == mid );
2103- VM_ASSERT ( ccs_cc -> klass == klass ) ;
2104- VM_ASSERT (! METHOD_ENTRY_INVALIDATED ( vm_cc_cme ( ccs_cc ))) ;
2097+ VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL ( klass );
2098+ const VALUE original_cc_table = cc_tbl ;
2099+ struct rb_class_cc_entries * ccs = NULL ;
21052100
2106- return ccs_cc ;
2107- }
2108- }
2109- }
2110- }
2101+ if (!cc_tbl ) {
2102+ cc_tbl = rb_vm_cc_table_create (1 );
21112103 }
2112- else {
2113- cc_tbl = rb_vm_cc_table_create (2 );
2114- RCLASS_WRITE_CC_TBL (klass , cc_tbl );
2104+ else if (rb_multi_ractor_p ()) {
2105+ cc_tbl = rb_vm_cc_table_dup (cc_tbl );
21152106 }
21162107
21172108 RB_DEBUG_COUNTER_INC (cc_not_found_in_ccs );
@@ -2143,11 +2134,7 @@ vm_search_cc(const VALUE klass, const struct rb_callinfo * const ci)
21432134 if (ccs == NULL ) {
21442135 VM_ASSERT (cc_tbl );
21452136
2146- if (LIKELY (rb_managed_id_table_lookup (cc_tbl , mid , & ccs_data ))) {
2147- // rb_callable_method_entry() prepares ccs.
2148- ccs = (struct rb_class_cc_entries * )ccs_data ;
2149- }
2150- else {
2137+ if (!LIKELY (rb_managed_id_table_lookup (cc_tbl , mid , (VALUE * )& ccs ))) {
21512138 // TODO: required?
21522139 ccs = vm_ccs_create (klass , cc_tbl , mid , cme );
21532140 }
@@ -2162,6 +2149,91 @@ vm_search_cc(const VALUE klass, const struct rb_callinfo * const ci)
21622149 VM_ASSERT (cme -> called_id == mid );
21632150 VM_ASSERT (vm_cc_cme (cc )-> called_id == mid );
21642151
2152+ if (original_cc_table != cc_tbl ) {
2153+ RB_OBJ_ATOMIC_WRITE (klass , & RCLASS_WRITABLE_CC_TBL (klass ), cc_tbl );
2154+ }
2155+
2156+ return cc ;
2157+ }
2158+
2159+ static const struct rb_callcache *
2160+ vm_lookup_cc (const VALUE klass , const struct rb_callinfo * const ci , ID mid )
2161+ {
2162+ VALUE cc_tbl ;
2163+ struct rb_class_cc_entries * ccs ;
2164+ retry :
2165+ cc_tbl = RUBY_ATOMIC_VALUE_LOAD (RCLASS_WRITABLE_CC_TBL (klass ));
2166+ ccs = NULL ;
2167+
2168+ if (cc_tbl ) {
2169+ // CCS data is keyed on method id, so we don't need the method id
2170+ // for doing comparisons in the `for` loop below.
2171+
2172+ if (rb_managed_id_table_lookup (cc_tbl , mid , (VALUE * )& ccs )) {
2173+ const int ccs_len = ccs -> len ;
2174+
2175+ if (UNLIKELY (METHOD_ENTRY_INVALIDATED (ccs -> cme ))) {
2176+ RB_VM_LOCKING () {
2177+ vm_evict_cc (klass , cc_tbl , mid );
2178+ }
2179+ goto retry ;
2180+ }
2181+ else {
2182+ VM_ASSERT (vm_ccs_verify (ccs , mid , klass ));
2183+
2184+ // We already know the method id is correct because we had
2185+ // to look up the ccs_data by method id. All we need to
2186+ // compare is argc and flag
2187+ unsigned int argc = vm_ci_argc (ci );
2188+ unsigned int flag = vm_ci_flag (ci );
2189+
2190+ for (int i = 0 ; i < ccs_len ; i ++ ) {
2191+ unsigned int ccs_ci_argc = ccs -> entries [i ].argc ;
2192+ unsigned int ccs_ci_flag = ccs -> entries [i ].flag ;
2193+ const struct rb_callcache * ccs_cc = ccs -> entries [i ].cc ;
2194+
2195+ VM_ASSERT (IMEMO_TYPE_P (ccs_cc , imemo_callcache ));
2196+
2197+ if (ccs_ci_argc == argc && ccs_ci_flag == flag ) {
2198+ RB_DEBUG_COUNTER_INC (cc_found_in_ccs );
2199+
2200+ VM_ASSERT (vm_cc_cme (ccs_cc )-> called_id == mid );
2201+ VM_ASSERT (ccs_cc -> klass == klass );
2202+ VM_ASSERT (!METHOD_ENTRY_INVALIDATED (vm_cc_cme (ccs_cc )));
2203+
2204+ return ccs_cc ;
2205+ }
2206+ }
2207+ }
2208+ }
2209+ }
2210+
2211+ RB_GC_GUARD (cc_tbl );
2212+ return NULL ;
2213+ }
2214+
2215+ static const struct rb_callcache *
2216+ vm_search_cc (const VALUE klass , const struct rb_callinfo * const ci )
2217+ {
2218+ const ID mid = vm_ci_mid (ci );
2219+
2220+ const struct rb_callcache * cc = vm_lookup_cc (klass , ci , mid );
2221+ if (cc ) {
2222+ return cc ;
2223+ }
2224+
2225+ RB_VM_LOCKING () {
2226+ if (rb_multi_ractor_p ()) {
2227+ // The CC may have been populated by another ractor while we were waiting on the lock,
2228+ // so we must lookup a second time.
2229+ cc = vm_lookup_cc (klass , ci , mid );
2230+ }
2231+
2232+ if (!cc ) {
2233+ cc = vm_populate_cc (klass , ci , mid );
2234+ }
2235+ }
2236+
21652237 return cc ;
21662238}
21672239
@@ -2172,16 +2244,14 @@ rb_vm_search_method_slowpath(const struct rb_callinfo *ci, VALUE klass)
21722244
21732245 VM_ASSERT_TYPE2 (klass , T_CLASS , T_ICLASS );
21742246
2175- RB_VM_LOCKING () {
2176- cc = vm_search_cc (klass , ci );
2247+ cc = vm_search_cc (klass , ci );
21772248
2178- VM_ASSERT (cc );
2179- VM_ASSERT (IMEMO_TYPE_P (cc , imemo_callcache ));
2180- VM_ASSERT (cc == vm_cc_empty () || cc -> klass == klass );
2181- VM_ASSERT (cc == vm_cc_empty () || callable_method_entry_p (vm_cc_cme (cc )));
2182- VM_ASSERT (cc == vm_cc_empty () || !METHOD_ENTRY_INVALIDATED (vm_cc_cme (cc )));
2183- VM_ASSERT (cc == vm_cc_empty () || vm_cc_cme (cc )-> called_id == vm_ci_mid (ci ));
2184- }
2249+ VM_ASSERT (cc );
2250+ VM_ASSERT (IMEMO_TYPE_P (cc , imemo_callcache ));
2251+ VM_ASSERT (cc == vm_cc_empty () || cc -> klass == klass );
2252+ VM_ASSERT (cc == vm_cc_empty () || callable_method_entry_p (vm_cc_cme (cc )));
2253+ VM_ASSERT (cc == vm_cc_empty () || !METHOD_ENTRY_INVALIDATED (vm_cc_cme (cc )));
2254+ VM_ASSERT (cc == vm_cc_empty () || vm_cc_cme (cc )-> called_id == vm_ci_mid (ci ));
21852255
21862256 return cc ;
21872257}
0 commit comments