@@ -973,43 +973,21 @@ PyType_Unwatch(int watcher_id, PyObject* obj)
973973 return 0 ;
974974}
975975
976- #ifdef Py_GIL_DISABLED
977-
978976static void
979- type_modification_starting_unlocked (PyTypeObject * type )
977+ set_version_unlocked (PyTypeObject * tp , unsigned int version )
980978{
981979 ASSERT_TYPE_LOCK_HELD ();
982-
983- /* Clear version tags on all types, but leave the valid
984- version tag intact. This prepares for a modification so that
985- any concurrent readers of the type cache will not see invalid
986- values.
987- */
988- if (!_PyType_HasFeature (type , Py_TPFLAGS_VALID_VERSION_TAG )) {
989- return ;
980+ #ifndef Py_GIL_DISABLED
981+ if (version ) {
982+ tp -> tp_versions_used ++ ;
990983 }
991-
992- PyObject * subclasses = lookup_tp_subclasses (type );
993- if (subclasses != NULL ) {
994- assert (PyDict_CheckExact (subclasses ));
995-
996- Py_ssize_t i = 0 ;
997- PyObject * ref ;
998- while (PyDict_Next (subclasses , & i , NULL , & ref )) {
999- PyTypeObject * subclass = type_from_ref (ref );
1000- if (subclass == NULL ) {
1001- continue ;
1002- }
1003- type_modification_starting_unlocked (subclass );
1004- Py_DECREF (subclass );
1005- }
984+ #else
985+ if (version ) {
986+ _Py_atomic_add_uint16 (& tp -> tp_versions_used , 1 );
1006987 }
1007-
1008- /* 0 is not a valid version tag */
1009- _Py_atomic_store_uint32_release (& type -> tp_version_tag , 0 );
1010- }
1011-
1012988#endif
989+ FT_ATOMIC_STORE_UINT32_RELAXED (tp -> tp_version_tag , version );
990+ }
1013991
1014992static void
1015993type_modified_unlocked (PyTypeObject * type )
@@ -1020,16 +998,16 @@ type_modified_unlocked(PyTypeObject *type)
1020998
1021999 Invariants:
10221000
1023- - before Py_TPFLAGS_VALID_VERSION_TAG can be set on a type,
1001+ - before tp_version_tag can be set on a type,
10241002 it must first be set on all super types.
10251003
1026- This function clears the Py_TPFLAGS_VALID_VERSION_TAG of a
1004+ This function clears the tp_version_tag of a
10271005 type (so it must first clear it on all subclasses). The
1028- tp_version_tag value is meaningless unless this flag is set .
1006+ tp_version_tag value is meaningless when equal to zero .
10291007 We don't assign new version tags eagerly, but only as
10301008 needed.
10311009 */
1032- if (! _PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) ) {
1010+ if (type -> tp_version_tag == 0 ) {
10331011 return ;
10341012 }
10351013
@@ -1069,8 +1047,7 @@ type_modified_unlocked(PyTypeObject *type)
10691047 }
10701048 }
10711049
1072- type -> tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG ;
1073- FT_ATOMIC_STORE_UINT32_RELAXED (type -> tp_version_tag , 0 ); /* 0 is not a valid version tag */
1050+ set_version_unlocked (type , 0 ); /* 0 is not a valid version tag */
10741051 if (PyType_HasFeature (type , Py_TPFLAGS_HEAPTYPE )) {
10751052 // This field *must* be invalidated if the type is modified (see the
10761053 // comment on struct _specialization_cache):
@@ -1082,7 +1059,7 @@ void
10821059PyType_Modified (PyTypeObject * type )
10831060{
10841061 // Quick check without the lock held
1085- if (! _PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) ) {
1062+ if (type -> tp_version_tag == 0 ) {
10861063 return ;
10871064 }
10881065
@@ -1146,8 +1123,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11461123
11471124 clear :
11481125 assert (!(type -> tp_flags & _Py_TPFLAGS_STATIC_BUILTIN ));
1149- type -> tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG ;
1150- FT_ATOMIC_STORE_UINT32_RELAXED (type -> tp_version_tag , 0 ); /* 0 is not a valid version tag */
1126+ set_version_unlocked (type , 0 ); /* 0 is not a valid version tag */
11511127 if (PyType_HasFeature (type , Py_TPFLAGS_HEAPTYPE )) {
11521128 // This field *must* be invalidated if the type is modified (see the
11531129 // comment on struct _specialization_cache):
@@ -1162,12 +1138,11 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
11621138{
11631139 ASSERT_TYPE_LOCK_HELD ();
11641140
1165- /* Ensure that the tp_version_tag is valid and set
1166- Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this
1167- must first be done on all super classes. Return 0 if this
1168- cannot be done, 1 if Py_TPFLAGS_VALID_VERSION_TAG.
1141+ /* Ensure that the tp_version_tag is valid.
1142+ * To respect the invariant, this must first be done on all super classes.
1143+ * Return 0 if this cannot be done, 1 if tp_version_tag is set.
11691144 */
1170- if (_PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) ) {
1145+ if (type -> tp_version_tag != 0 ) {
11711146 return 1 ;
11721147 }
11731148 if (!_PyType_HasFeature (type , Py_TPFLAGS_READY )) {
@@ -1176,15 +1151,22 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
11761151 if (type -> tp_versions_used >= MAX_VERSIONS_PER_CLASS ) {
11771152 return 0 ;
11781153 }
1179- type -> tp_versions_used ++ ;
1154+
1155+ PyObject * bases = lookup_tp_bases (type );
1156+ Py_ssize_t n = PyTuple_GET_SIZE (bases );
1157+ for (Py_ssize_t i = 0 ; i < n ; i ++ ) {
1158+ PyObject * b = PyTuple_GET_ITEM (bases , i );
1159+ if (!assign_version_tag (interp , _PyType_CAST (b ))) {
1160+ return 0 ;
1161+ }
1162+ }
11801163 if (type -> tp_flags & Py_TPFLAGS_IMMUTABLETYPE ) {
11811164 /* static types */
11821165 if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG ) {
11831166 /* We have run out of version numbers */
11841167 return 0 ;
11851168 }
1186- FT_ATOMIC_STORE_UINT32_RELAXED (type -> tp_version_tag ,
1187- NEXT_GLOBAL_VERSION_TAG ++ );
1169+ set_version_unlocked (type , NEXT_GLOBAL_VERSION_TAG ++ );
11881170 assert (type -> tp_version_tag <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG );
11891171 }
11901172 else {
@@ -1193,19 +1175,9 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
11931175 /* We have run out of version numbers */
11941176 return 0 ;
11951177 }
1196- FT_ATOMIC_STORE_UINT32_RELAXED (type -> tp_version_tag ,
1197- NEXT_VERSION_TAG (interp )++ );
1178+ set_version_unlocked (type , NEXT_VERSION_TAG (interp )++ );
11981179 assert (type -> tp_version_tag != 0 );
11991180 }
1200-
1201- PyObject * bases = lookup_tp_bases (type );
1202- Py_ssize_t n = PyTuple_GET_SIZE (bases );
1203- for (Py_ssize_t i = 0 ; i < n ; i ++ ) {
1204- PyObject * b = PyTuple_GET_ITEM (bases , i );
1205- if (!assign_version_tag (interp , _PyType_CAST (b )))
1206- return 0 ;
1207- }
1208- type -> tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG ;
12091181 return 1 ;
12101182}
12111183
@@ -3126,7 +3098,7 @@ mro_internal_unlocked(PyTypeObject *type, int initial, PyObject **p_old_mro)
31263098 else {
31273099 /* For static builtin types, this is only called during init
31283100 before the method cache has been populated. */
3129- assert (_PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) );
3101+ assert (type -> tp_version_tag );
31303102 }
31313103
31323104 if (p_old_mro != NULL )
@@ -5275,7 +5247,7 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name)
52755247#else
52765248 if (entry -> version == type -> tp_version_tag &&
52775249 entry -> name == name ) {
5278- assert (_PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) );
5250+ assert (type -> tp_version_tag );
52795251 OBJECT_STAT_INC_COND (type_cache_hits , !is_dunder_name (name ));
52805252 OBJECT_STAT_INC_COND (type_cache_dunder_hits , is_dunder_name (name ));
52815253 Py_XINCREF (entry -> value );
@@ -5298,7 +5270,6 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name)
52985270 if (MCACHE_CACHEABLE_NAME (name )) {
52995271 has_version = assign_version_tag (interp , type );
53005272 version = type -> tp_version_tag ;
5301- assert (!has_version || _PyType_HasFeature (type , Py_TPFLAGS_VALID_VERSION_TAG ));
53025273 }
53035274 END_TYPE_LOCK ()
53045275
@@ -5582,23 +5553,15 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
55825553 return -1 ;
55835554 }
55845555
5585- #ifdef Py_GIL_DISABLED
5586- // In free-threaded builds readers can race with the lock-free portion
5587- // of the type cache and the assignment into the dict. We clear all of the
5588- // versions initially so no readers will succeed in the lock-free case.
5589- // They'll then block on the type lock until the update below is done.
5590- type_modification_starting_unlocked (type );
5591- #endif
5592-
5593- res = _PyDict_SetItem_LockHeld ((PyDictObject * )dict , name , value );
5594-
55955556 /* Clear the VALID_VERSION flag of 'type' and all its
55965557 subclasses. This could possibly be unified with the
55975558 update_subclasses() recursion in update_slot(), but carefully:
55985559 they each have their own conditions on which to stop
55995560 recursing into subclasses. */
56005561 type_modified_unlocked (type );
56015562
5563+ res = _PyDict_SetItem_LockHeld ((PyDictObject * )dict , name , value );
5564+
56025565 if (res == 0 ) {
56035566 if (is_dunder_name (name )) {
56045567 res = update_slot (type , name );
@@ -5710,7 +5673,6 @@ fini_static_type(PyInterpreterState *interp, PyTypeObject *type,
57105673
57115674 if (final ) {
57125675 type -> tp_flags &= ~Py_TPFLAGS_READY ;
5713- type -> tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG ;
57145676 type -> tp_version_tag = 0 ;
57155677 }
57165678
@@ -8329,12 +8291,11 @@ init_static_type(PyInterpreterState *interp, PyTypeObject *self,
83298291
83308292 assert (NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG );
83318293 self -> tp_version_tag = NEXT_GLOBAL_VERSION_TAG ++ ;
8332- self -> tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG ;
83338294 }
83348295 else {
83358296 assert (!initial );
83368297 assert (self -> tp_flags & _Py_TPFLAGS_STATIC_BUILTIN );
8337- assert (self -> tp_flags & Py_TPFLAGS_VALID_VERSION_TAG );
8298+ assert (self -> tp_version_tag != 0 );
83388299 }
83398300
83408301 managed_static_type_state_init (interp , self , isbuiltin , initial );
0 commit comments