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:
Neil Schemenauer 2024-12-19 10:21:17 -08:00 committed by GitHub
parent d2f1d917e8
commit 1b15c89a17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 716 additions and 297 deletions

View file

@ -1383,6 +1383,72 @@ class TestSpecializer(TestBase):
self.assert_specialized(send_yield_from, "SEND_GEN")
self.assert_no_opcode(send_yield_from, "SEND")
@cpython_only
@requires_specialization_ft
def test_store_attr_slot(self):
class C:
__slots__ = ['x']
def set_slot():
c = C()
for i in range(100):
c.x = i
set_slot()
self.assert_specialized(set_slot, "STORE_ATTR_SLOT")
self.assert_no_opcode(set_slot, "STORE_ATTR")
# Adding a property for 'x' should unspecialize it.
C.x = property(lambda self: None, lambda self, x: None)
set_slot()
self.assert_no_opcode(set_slot, "STORE_ATTR_SLOT")
@cpython_only
@requires_specialization_ft
def test_store_attr_instance_value(self):
class C:
pass
def set_value():
c = C()
for i in range(100):
c.x = i
set_value()
self.assert_specialized(set_value, "STORE_ATTR_INSTANCE_VALUE")
self.assert_no_opcode(set_value, "STORE_ATTR")
# Adding a property for 'x' should unspecialize it.
C.x = property(lambda self: None, lambda self, x: None)
set_value()
self.assert_no_opcode(set_value, "STORE_ATTR_INSTANCE_VALUE")
@cpython_only
@requires_specialization_ft
def test_store_attr_with_hint(self):
class C:
pass
c = C()
for i in range(29):
setattr(c, f"_{i}", None)
def set_value():
for i in range(100):
c.x = i
set_value()
self.assert_specialized(set_value, "STORE_ATTR_WITH_HINT")
self.assert_no_opcode(set_value, "STORE_ATTR")
# Adding a property for 'x' should unspecialize it.
C.x = property(lambda self: None, lambda self, x: None)
set_value()
self.assert_no_opcode(set_value, "STORE_ATTR_WITH_HINT")
@cpython_only
@requires_specialization_ft
def test_to_bool(self):