@@ -973,43 +973,37 @@ PyType_Unwatch(int watcher_id, PyObject* obj)
973
973
return 0 ;
974
974
}
975
975
976
- #ifdef Py_GIL_DISABLED
977
-
978
976
static void
979
- type_modification_starting_unlocked (PyTypeObject * type )
977
+ set_version_unlocked (PyTypeObject * tp , unsigned int version )
980
978
{
981
979
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
+ PyInterpreterState * interp = _PyInterpreterState_GET ();
982
+ // lookup the old version and set to null
983
+ if ( tp -> tp_version_tag != 0 ) {
984
+ PyTypeObject * * slot =
985
+ interp -> types . type_version_cache
986
+ + ( tp -> tp_version_tag % TYPE_VERSION_CACHE_SIZE );
987
+ * slot = NULL ;
990
988
}
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
- }
989
+ if (version ) {
990
+ tp -> tp_versions_used ++ ;
991
+ }
992
+ #else
993
+ if (version ) {
994
+ _Py_atomic_add_uint16 (& tp -> tp_versions_used , 1 );
1006
995
}
1007
-
1008
- /* 0 is not a valid version tag */
1009
- _Py_atomic_store_uint32_release (& type -> tp_version_tag , 0 );
1010
- }
1011
-
1012
996
#endif
997
+ FT_ATOMIC_STORE_UINT32_RELAXED (tp -> tp_version_tag , version );
998
+ #ifndef Py_GIL_DISABLED
999
+ if (version != 0 ) {
1000
+ PyTypeObject * * slot =
1001
+ interp -> types .type_version_cache
1002
+ + (version % TYPE_VERSION_CACHE_SIZE );
1003
+ * slot = tp ;
1004
+ }
1005
+ #endif
1006
+ }
1013
1007
1014
1008
static void
1015
1009
type_modified_unlocked (PyTypeObject * type )
@@ -1020,16 +1014,16 @@ type_modified_unlocked(PyTypeObject *type)
1020
1014
1021
1015
Invariants:
1022
1016
1023
- - before Py_TPFLAGS_VALID_VERSION_TAG can be set on a type,
1017
+ - before tp_version_tag can be set on a type,
1024
1018
it must first be set on all super types.
1025
1019
1026
- This function clears the Py_TPFLAGS_VALID_VERSION_TAG of a
1020
+ This function clears the tp_version_tag of a
1027
1021
type (so it must first clear it on all subclasses). The
1028
- tp_version_tag value is meaningless unless this flag is set .
1022
+ tp_version_tag value is meaningless when equal to zero .
1029
1023
We don't assign new version tags eagerly, but only as
1030
1024
needed.
1031
1025
*/
1032
- if (! _PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) ) {
1026
+ if (type -> tp_version_tag == 0 ) {
1033
1027
return ;
1034
1028
}
1035
1029
@@ -1069,8 +1063,7 @@ type_modified_unlocked(PyTypeObject *type)
1069
1063
}
1070
1064
}
1071
1065
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 */
1066
+ set_version_unlocked (type , 0 ); /* 0 is not a valid version tag */
1074
1067
if (PyType_HasFeature (type , Py_TPFLAGS_HEAPTYPE )) {
1075
1068
// This field *must* be invalidated if the type is modified (see the
1076
1069
// comment on struct _specialization_cache):
@@ -1082,7 +1075,7 @@ void
1082
1075
PyType_Modified (PyTypeObject * type )
1083
1076
{
1084
1077
// Quick check without the lock held
1085
- if (! _PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) ) {
1078
+ if (type -> tp_version_tag == 0 ) {
1086
1079
return ;
1087
1080
}
1088
1081
@@ -1146,8 +1139,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
1146
1139
1147
1140
clear :
1148
1141
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 */
1142
+ set_version_unlocked (type , 0 ); /* 0 is not a valid version tag */
1151
1143
if (PyType_HasFeature (type , Py_TPFLAGS_HEAPTYPE )) {
1152
1144
// This field *must* be invalidated if the type is modified (see the
1153
1145
// comment on struct _specialization_cache):
@@ -1162,12 +1154,11 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
1162
1154
{
1163
1155
ASSERT_TYPE_LOCK_HELD ();
1164
1156
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.
1157
+ /* Ensure that the tp_version_tag is valid.
1158
+ * To respect the invariant, this must first be done on all super classes.
1159
+ * Return 0 if this cannot be done, 1 if tp_version_tag is set.
1169
1160
*/
1170
- if (_PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) ) {
1161
+ if (type -> tp_version_tag != 0 ) {
1171
1162
return 1 ;
1172
1163
}
1173
1164
if (!_PyType_HasFeature (type , Py_TPFLAGS_READY )) {
@@ -1176,15 +1167,22 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
1176
1167
if (type -> tp_versions_used >= MAX_VERSIONS_PER_CLASS ) {
1177
1168
return 0 ;
1178
1169
}
1179
- type -> tp_versions_used ++ ;
1170
+
1171
+ PyObject * bases = lookup_tp_bases (type );
1172
+ Py_ssize_t n = PyTuple_GET_SIZE (bases );
1173
+ for (Py_ssize_t i = 0 ; i < n ; i ++ ) {
1174
+ PyObject * b = PyTuple_GET_ITEM (bases , i );
1175
+ if (!assign_version_tag (interp , _PyType_CAST (b ))) {
1176
+ return 0 ;
1177
+ }
1178
+ }
1180
1179
if (type -> tp_flags & Py_TPFLAGS_IMMUTABLETYPE ) {
1181
1180
/* static types */
1182
1181
if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG ) {
1183
1182
/* We have run out of version numbers */
1184
1183
return 0 ;
1185
1184
}
1186
- FT_ATOMIC_STORE_UINT32_RELAXED (type -> tp_version_tag ,
1187
- NEXT_GLOBAL_VERSION_TAG ++ );
1185
+ set_version_unlocked (type , NEXT_GLOBAL_VERSION_TAG ++ );
1188
1186
assert (type -> tp_version_tag <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG );
1189
1187
}
1190
1188
else {
@@ -1193,19 +1191,9 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
1193
1191
/* We have run out of version numbers */
1194
1192
return 0 ;
1195
1193
}
1196
- FT_ATOMIC_STORE_UINT32_RELAXED (type -> tp_version_tag ,
1197
- NEXT_VERSION_TAG (interp )++ );
1194
+ set_version_unlocked (type , NEXT_VERSION_TAG (interp )++ );
1198
1195
assert (type -> tp_version_tag != 0 );
1199
1196
}
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 ;
1209
1197
return 1 ;
1210
1198
}
1211
1199
@@ -3126,7 +3114,7 @@ mro_internal_unlocked(PyTypeObject *type, int initial, PyObject **p_old_mro)
3126
3114
else {
3127
3115
/* For static builtin types, this is only called during init
3128
3116
before the method cache has been populated. */
3129
- assert (_PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) );
3117
+ assert (type -> tp_version_tag );
3130
3118
}
3131
3119
3132
3120
if (p_old_mro != NULL )
@@ -5275,7 +5263,7 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name)
5275
5263
#else
5276
5264
if (entry -> version == type -> tp_version_tag &&
5277
5265
entry -> name == name ) {
5278
- assert (_PyType_HasFeature ( type , Py_TPFLAGS_VALID_VERSION_TAG ) );
5266
+ assert (type -> tp_version_tag );
5279
5267
OBJECT_STAT_INC_COND (type_cache_hits , !is_dunder_name (name ));
5280
5268
OBJECT_STAT_INC_COND (type_cache_dunder_hits , is_dunder_name (name ));
5281
5269
Py_XINCREF (entry -> value );
@@ -5298,7 +5286,6 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name)
5298
5286
if (MCACHE_CACHEABLE_NAME (name )) {
5299
5287
has_version = assign_version_tag (interp , type );
5300
5288
version = type -> tp_version_tag ;
5301
- assert (!has_version || _PyType_HasFeature (type , Py_TPFLAGS_VALID_VERSION_TAG ));
5302
5289
}
5303
5290
END_TYPE_LOCK ()
5304
5291
@@ -5582,23 +5569,15 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
5582
5569
return -1 ;
5583
5570
}
5584
5571
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
-
5595
5572
/* Clear the VALID_VERSION flag of 'type' and all its
5596
5573
subclasses. This could possibly be unified with the
5597
5574
update_subclasses() recursion in update_slot(), but carefully:
5598
5575
they each have their own conditions on which to stop
5599
5576
recursing into subclasses. */
5600
5577
type_modified_unlocked (type );
5601
5578
5579
+ res = _PyDict_SetItem_LockHeld ((PyDictObject * )dict , name , value );
5580
+
5602
5581
if (res == 0 ) {
5603
5582
if (is_dunder_name (name )) {
5604
5583
res = update_slot (type , name );
@@ -5710,7 +5689,6 @@ fini_static_type(PyInterpreterState *interp, PyTypeObject *type,
5710
5689
5711
5690
if (final ) {
5712
5691
type -> tp_flags &= ~Py_TPFLAGS_READY ;
5713
- type -> tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG ;
5714
5692
type -> tp_version_tag = 0 ;
5715
5693
}
5716
5694
@@ -8329,12 +8307,11 @@ init_static_type(PyInterpreterState *interp, PyTypeObject *self,
8329
8307
8330
8308
assert (NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG );
8331
8309
self -> tp_version_tag = NEXT_GLOBAL_VERSION_TAG ++ ;
8332
- self -> tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG ;
8333
8310
}
8334
8311
else {
8335
8312
assert (!initial );
8336
8313
assert (self -> tp_flags & _Py_TPFLAGS_STATIC_BUILTIN );
8337
- assert (self -> tp_flags & Py_TPFLAGS_VALID_VERSION_TAG );
8314
+ assert (self -> tp_version_tag != 0 );
8338
8315
}
8339
8316
8340
8317
managed_static_type_state_init (interp , self , isbuiltin , initial );
0 commit comments