@@ -5356,6 +5356,58 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
53565356 return res ;
53575357}
53585358
5359+
5360+ static void
5361+ find_name_in_mro_stackref (PyTypeObject * type , PyObject * name , int * error , _PyStackRef * result )
5362+ {
5363+ ASSERT_TYPE_LOCK_HELD ();
5364+
5365+ Py_hash_t hash = _PyObject_HashFast (name );
5366+ if (hash == -1 ) {
5367+ * error = -1 ;
5368+ * result = PyStackRef_NULL ;
5369+ return ;
5370+ }
5371+
5372+ /* Look in tp_dict of types in MRO */
5373+ PyObject * mro = lookup_tp_mro (type );
5374+ if (mro == NULL ) {
5375+ if (!is_readying (type )) {
5376+ if (PyType_Ready (type ) < 0 ) {
5377+ * error = -1 ;
5378+ * result = PyStackRef_NULL ;
5379+ return ;
5380+ }
5381+ mro = lookup_tp_mro (type );
5382+ }
5383+ if (mro == NULL ) {
5384+ * error = 1 ;
5385+ * result = PyStackRef_NULL ;
5386+ return ;
5387+ }
5388+ }
5389+
5390+ /* Keep a strong reference to mro because type->tp_mro can be replaced
5391+ during dict lookup, e.g. when comparing to non-string keys. */
5392+ Py_INCREF (mro );
5393+ Py_ssize_t n = PyTuple_GET_SIZE (mro );
5394+ for (Py_ssize_t i = 0 ; i < n ; i ++ ) {
5395+ PyObject * base = PyTuple_GET_ITEM (mro , i );
5396+ PyObject * dict = lookup_tp_dict (_PyType_CAST (base ));
5397+ assert (dict && PyDict_Check (dict ));
5398+ if (_PyDict_GetItem_KnownHash_StackRef ((PyDictObject * )dict , name , hash , result ) < 0 ) {
5399+ * error = -1 ;
5400+ goto done ;
5401+ }
5402+ if (!PyStackRef_IsNull (* result )) {
5403+ break ;
5404+ }
5405+ }
5406+ * error = 0 ;
5407+ done :
5408+ Py_DECREF (mro );
5409+ }
5410+
53595411/* Check if the "readied" PyUnicode name
53605412 is a double-underscore special name. */
53615413static int
@@ -5528,6 +5580,95 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name)
55285580 return res ;
55295581}
55305582
5583+ void
5584+ _PyType_LookupStackRef (PyTypeObject * type , PyObject * name , _PyStackRef * result )
5585+ {
5586+ int error ;
5587+ PyInterpreterState * interp = _PyInterpreterState_GET ();
5588+
5589+ unsigned int h = MCACHE_HASH_METHOD (type , name );
5590+ struct type_cache * cache = get_type_cache ();
5591+ struct type_cache_entry * entry = & cache -> hashtable [h ];
5592+ #ifdef Py_GIL_DISABLED
5593+ // synchronize-with other writing threads by doing an acquire load on the sequence
5594+ while (1 ) {
5595+ int sequence = _PySeqLock_BeginRead (& entry -> sequence );
5596+ uint32_t entry_version = _Py_atomic_load_uint32_relaxed (& entry -> version );
5597+ uint32_t type_version = _Py_atomic_load_uint32_acquire (& type -> tp_version_tag );
5598+ if (entry_version == type_version &&
5599+ _Py_atomic_load_ptr_relaxed (& entry -> name ) == name ) {
5600+ OBJECT_STAT_INC_COND (type_cache_hits , !is_dunder_name (name ));
5601+ OBJECT_STAT_INC_COND (type_cache_dunder_hits , is_dunder_name (name ));
5602+ PyObject * value = _Py_atomic_load_ptr_relaxed (& entry -> value );
5603+ * result = PyStackRef_FromPyObjectNew (value );
5604+ // If the sequence is still valid then we're done
5605+ if (_PySeqLock_EndRead (& entry -> sequence , sequence )) {
5606+ return ;
5607+ }
5608+ * result = PyStackRef_NULL ;
5609+ }
5610+ else {
5611+ // cache miss
5612+ break ;
5613+ }
5614+ }
5615+ #else
5616+ if (entry -> version == type -> tp_version_tag &&
5617+ entry -> name == name ) {
5618+ assert (type -> tp_version_tag );
5619+ OBJECT_STAT_INC_COND (type_cache_hits , !is_dunder_name (name ));
5620+ OBJECT_STAT_INC_COND (type_cache_dunder_hits , is_dunder_name (name ));
5621+ Py_XINCREF (entry -> value );
5622+ return entry -> value ;
5623+ }
5624+ #endif
5625+ OBJECT_STAT_INC_COND (type_cache_misses , !is_dunder_name (name ));
5626+ OBJECT_STAT_INC_COND (type_cache_dunder_misses , is_dunder_name (name ));
5627+
5628+ /* We may end up clearing live exceptions below, so make sure it's ours. */
5629+ assert (!PyErr_Occurred ());
5630+
5631+ // We need to atomically do the lookup and capture the version before
5632+ // anyone else can modify our mro or mutate the type.
5633+
5634+ int has_version = 0 ;
5635+ int version = 0 ;
5636+ BEGIN_TYPE_LOCK ();
5637+ find_name_in_mro_stackref (type , name , & error , result );
5638+ if (MCACHE_CACHEABLE_NAME (name )) {
5639+ has_version = assign_version_tag (interp , type );
5640+ version = type -> tp_version_tag ;
5641+ }
5642+ END_TYPE_LOCK ();
5643+
5644+ /* Only put NULL results into cache if there was no error. */
5645+ if (error ) {
5646+ /* It's not ideal to clear the error condition,
5647+ but this function is documented as not setting
5648+ an exception, and I don't want to change that.
5649+ E.g., when PyType_Ready() can't proceed, it won't
5650+ set the "ready" flag, so future attempts to ready
5651+ the same type will call it again -- hopefully
5652+ in a context that propagates the exception out.
5653+ */
5654+ if (error == -1 ) {
5655+ PyErr_Clear ();
5656+ }
5657+ * result = PyStackRef_NULL ;
5658+ return ;
5659+ }
5660+
5661+ if (has_version ) {
5662+ #if Py_GIL_DISABLED
5663+ update_cache_gil_disabled (entry , name , version ,
5664+ PyStackRef_AsPyObjectBorrow (* result ));
5665+ #else
5666+ PyObject * old_value = update_cache (entry , name , version , PyStackRef_AsPyObjectBorrow (* result ));
5667+ Py_DECREF (old_value );
5668+ #endif
5669+ }
5670+ }
5671+
55315672PyObject *
55325673_PyType_Lookup (PyTypeObject * type , PyObject * name )
55335674{
0 commit comments