mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +00:00 
			
		
		
		
	gh-115999: Specialize STORE_ATTR in free-threaded builds. (gh-127838)
				
					
				
			* Add `_PyDictKeys_StringLookupSplit` which does locking on dict keys and use in place of `_PyDictKeys_StringLookup`. * Change `_PyObject_TryGetInstanceAttribute` to use that function in the case of split keys. * Add `unicodekeys_lookup_split` helper which allows code sharing between `_Py_dict_lookup` and `_PyDictKeys_StringLookupSplit`. * Fix locking for `STORE_ATTR_INSTANCE_VALUE`. Create `_GUARD_TYPE_VERSION_AND_LOCK` uop so that object stays locked and `tp_version_tag` cannot change. * Pass `tp_version_tag` to `specialize_dict_access()`, ensuring the version we store on the cache is the correct one (in case of it changing during the specalize analysis). * Split `analyze_descriptor` into `analyze_descriptor_load` and `analyze_descriptor_store` since those don't share much logic. Add `descriptor_is_class` helper function. * In `specialize_dict_access`, double check `_PyObject_GetManagedDict()` in case we race and dict was materialized before the lock. * Avoid borrowed references in `_Py_Specialize_StoreAttr()`. * Use `specialize()` and `unspecialize()` helpers. * Add unit tests to ensure specializing happens as expected in FT builds. * Add unit tests to attempt to trigger data races (useful for running under TSAN). * Add `has_split_table` function to `_testinternalcapi`.
This commit is contained in:
		
							parent
							
								
									d2f1d917e8
								
							
						
					
					
						commit
						1b15c89a17
					
				
					 13 changed files with 716 additions and 297 deletions
				
			
		
							
								
								
									
										86
									
								
								Python/generated_cases.c.h
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										86
									
								
								Python/generated_cases.c.h
									
										
									
										generated
									
									
									
								
							|  | @ -5319,7 +5319,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[4].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             // _LOAD_ATTR_CLASS
 | ||||
|             { | ||||
|  | @ -5388,7 +5388,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             // _CHECK_MANAGED_OBJECT_HAS_VALUES
 | ||||
|             { | ||||
|  | @ -5433,7 +5433,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             // _CHECK_ATTR_METHOD_LAZY_DICT
 | ||||
|             { | ||||
|  | @ -5476,7 +5476,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             /* Skip 2 cache entries */ | ||||
|             // _LOAD_ATTR_METHOD_NO_DICT
 | ||||
|  | @ -5512,7 +5512,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             // _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT
 | ||||
|             { | ||||
|  | @ -5611,7 +5611,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             /* Skip 2 cache entries */ | ||||
|             // _LOAD_ATTR_NONDESCRIPTOR_NO_DICT
 | ||||
|  | @ -5642,7 +5642,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             // _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT
 | ||||
|             { | ||||
|  | @ -5688,7 +5688,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             /* Skip 2 cache entries */ | ||||
|             // _LOAD_ATTR_PROPERTY_FRAME
 | ||||
|  | @ -5750,7 +5750,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             // _LOAD_ATTR_SLOT
 | ||||
|             { | ||||
|  | @ -5787,7 +5787,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, LOAD_ATTR); | ||||
|             } | ||||
|             // _CHECK_ATTR_WITH_HINT
 | ||||
|             { | ||||
|  | @ -7314,7 +7314,7 @@ | |||
|                 owner = stack_pointer[-1]; | ||||
|                 uint16_t counter = read_u16(&this_instr[1].cache); | ||||
|                 (void)counter; | ||||
|                 #if ENABLE_SPECIALIZATION | ||||
|                 #if ENABLE_SPECIALIZATION_FT | ||||
|                 if (ADAPTIVE_COUNTER_TRIGGERS(counter)) { | ||||
|                     PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); | ||||
|                     next_instr = this_instr; | ||||
|  | @ -7325,7 +7325,7 @@ | |||
|                 } | ||||
|                 OPCODE_DEFERRED_INC(STORE_ATTR); | ||||
|                 ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter); | ||||
|                 #endif  /* ENABLE_SPECIALIZATION */ | ||||
|                 #endif  /* ENABLE_SPECIALIZATION_FT */ | ||||
|             } | ||||
|             /* Skip 3 cache entries */ | ||||
|             // _STORE_ATTR
 | ||||
|  | @ -7353,21 +7353,29 @@ | |||
|             _PyStackRef owner; | ||||
|             _PyStackRef value; | ||||
|             /* Skip 1 cache entry */ | ||||
|             // _GUARD_TYPE_VERSION
 | ||||
|             // _GUARD_TYPE_VERSION_AND_LOCK
 | ||||
|             { | ||||
|                 owner = stack_pointer[-1]; | ||||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, STORE_ATTR); | ||||
|                 DEOPT_IF(!LOCK_OBJECT(owner_o), STORE_ATTR); | ||||
|                 PyTypeObject *tp = Py_TYPE(owner_o); | ||||
|                 if (FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version) { | ||||
|                     UNLOCK_OBJECT(owner_o); | ||||
|                     DEOPT_IF(true, STORE_ATTR); | ||||
|                 } | ||||
|             } | ||||
|             // _GUARD_DORV_NO_DICT
 | ||||
|             { | ||||
|                 PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); | ||||
|                 assert(Py_TYPE(owner_o)->tp_dictoffset < 0); | ||||
|                 assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_INLINE_VALUES); | ||||
|                 DEOPT_IF(_PyObject_GetManagedDict(owner_o), STORE_ATTR); | ||||
|                 DEOPT_IF(_PyObject_InlineValues(owner_o)->valid == 0, STORE_ATTR); | ||||
|                 if (_PyObject_GetManagedDict(owner_o) || | ||||
|                     !FT_ATOMIC_LOAD_UINT8(_PyObject_InlineValues(owner_o)->valid)) { | ||||
|                     UNLOCK_OBJECT(owner_o); | ||||
|                     DEOPT_IF(true, STORE_ATTR); | ||||
|                 } | ||||
|             } | ||||
|             // _STORE_ATTR_INSTANCE_VALUE
 | ||||
|             { | ||||
|  | @ -7378,15 +7386,14 @@ | |||
|                 assert(_PyObject_GetManagedDict(owner_o) == NULL); | ||||
|                 PyObject **value_ptr = (PyObject**)(((char *)owner_o) + offset); | ||||
|                 PyObject *old_value = *value_ptr; | ||||
|                 *value_ptr = PyStackRef_AsPyObjectSteal(value); | ||||
|                 FT_ATOMIC_STORE_PTR_RELEASE(*value_ptr, PyStackRef_AsPyObjectSteal(value)); | ||||
|                 if (old_value == NULL) { | ||||
|                     PyDictValues *values = _PyObject_InlineValues(owner_o); | ||||
|                     Py_ssize_t index = value_ptr - values->values; | ||||
|                     _PyDictValues_AddToInsertionOrder(values, index); | ||||
|                 } | ||||
|                 else { | ||||
|                     Py_DECREF(old_value); | ||||
|                 } | ||||
|                 UNLOCK_OBJECT(owner_o); | ||||
|                 Py_XDECREF(old_value); | ||||
|                 PyStackRef_CLOSE(owner); | ||||
|             } | ||||
|             stack_pointer += -2; | ||||
|  | @ -7408,17 +7415,19 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, STORE_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, STORE_ATTR); | ||||
|             } | ||||
|             // _STORE_ATTR_SLOT
 | ||||
|             { | ||||
|                 value = stack_pointer[-2]; | ||||
|                 uint16_t index = read_u16(&this_instr[4].cache); | ||||
|                 PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); | ||||
|                 DEOPT_IF(!LOCK_OBJECT(owner_o), STORE_ATTR); | ||||
|                 char *addr = (char *)owner_o + index; | ||||
|                 STAT_INC(STORE_ATTR, hit); | ||||
|                 PyObject *old_value = *(PyObject **)addr; | ||||
|                 *(PyObject **)addr = PyStackRef_AsPyObjectSteal(value); | ||||
|                 FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, PyStackRef_AsPyObjectSteal(value)); | ||||
|                 UNLOCK_OBJECT(owner_o); | ||||
|                 Py_XDECREF(old_value); | ||||
|                 PyStackRef_CLOSE(owner); | ||||
|             } | ||||
|  | @ -7441,7 +7450,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, STORE_ATTR); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, STORE_ATTR); | ||||
|             } | ||||
|             // _STORE_ATTR_WITH_HINT
 | ||||
|             { | ||||
|  | @ -7451,18 +7460,35 @@ | |||
|                 assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_MANAGED_DICT); | ||||
|                 PyDictObject *dict = _PyObject_GetManagedDict(owner_o); | ||||
|                 DEOPT_IF(dict == NULL, STORE_ATTR); | ||||
|                 DEOPT_IF(!LOCK_OBJECT(dict), STORE_ATTR); | ||||
|                 #ifdef Py_GIL_DISABLED | ||||
|                 if (dict != _PyObject_GetManagedDict(owner_o)) { | ||||
|                     UNLOCK_OBJECT(dict); | ||||
|                     DEOPT_IF(true, STORE_ATTR); | ||||
|                 } | ||||
|                 #endif | ||||
|                 assert(PyDict_CheckExact((PyObject *)dict)); | ||||
|                 PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); | ||||
|                 DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries, STORE_ATTR); | ||||
|                 DEOPT_IF(!DK_IS_UNICODE(dict->ma_keys), STORE_ATTR); | ||||
|                 if (hint >= (size_t)dict->ma_keys->dk_nentries || | ||||
|                     !DK_IS_UNICODE(dict->ma_keys)) { | ||||
|                     UNLOCK_OBJECT(dict); | ||||
|                     DEOPT_IF(true, STORE_ATTR); | ||||
|                 } | ||||
|                 PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + hint; | ||||
|                 DEOPT_IF(ep->me_key != name, STORE_ATTR); | ||||
|                 if (ep->me_key != name) { | ||||
|                     UNLOCK_OBJECT(dict); | ||||
|                     DEOPT_IF(true, STORE_ATTR); | ||||
|                 } | ||||
|                 PyObject *old_value = ep->me_value; | ||||
|                 DEOPT_IF(old_value == NULL, STORE_ATTR); | ||||
|                 if (old_value == NULL) { | ||||
|                     UNLOCK_OBJECT(dict); | ||||
|                     DEOPT_IF(true, STORE_ATTR); | ||||
|                 } | ||||
|                 _PyFrame_SetStackPointer(frame, stack_pointer); | ||||
|                 _PyDict_NotifyEvent(tstate->interp, PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value)); | ||||
|                 stack_pointer = _PyFrame_GetStackPointer(frame); | ||||
|                 ep->me_value = PyStackRef_AsPyObjectSteal(value); | ||||
|                 FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value)); | ||||
|                 UNLOCK_OBJECT(dict); | ||||
|                 // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
 | ||||
|                 // when dict only holds the strong reference to value in ep->me_value.
 | ||||
|                 Py_XDECREF(old_value); | ||||
|  | @ -7815,7 +7841,7 @@ | |||
|                 uint32_t type_version = read_u32(&this_instr[2].cache); | ||||
|                 PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); | ||||
|                 assert(type_version != 0); | ||||
|                 DEOPT_IF(tp->tp_version_tag != type_version, TO_BOOL); | ||||
|                 DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(tp->tp_version_tag) != type_version, TO_BOOL); | ||||
|             } | ||||
|             // _REPLACE_WITH_TRUE
 | ||||
|             { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Neil Schemenauer
						Neil Schemenauer