Skip to content

Commit 083cbe9

Browse files
committed
Enable specialization of STORE_ATTR free-threaded builds.
1 parent 3291656 commit 083cbe9

File tree

8 files changed

+413
-124
lines changed

8 files changed

+413
-124
lines changed

Include/internal/pycore_opcode_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_free_threading/test_races.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,112 @@ def mutate():
129129
# with the cell binding being changed).
130130
do_race(access, mutate)
131131

132+
def test_racing_to_bool(self):
133+
134+
seq = [1]
135+
136+
class C:
137+
def __bool__(self):
138+
return False
139+
140+
def access():
141+
if seq:
142+
return 1
143+
else:
144+
return 2
145+
146+
def mutate():
147+
nonlocal seq
148+
seq = [1]
149+
time.sleep(0)
150+
seq = C()
151+
time.sleep(0)
152+
153+
do_race(access, mutate)
154+
155+
def test_racing_store_attr_slot(self):
156+
class C:
157+
__slots__ = ['x', '__dict__']
158+
159+
c = C()
160+
161+
def set_slot():
162+
for i in range(10):
163+
c.x = i
164+
time.sleep(0)
165+
166+
def change_type():
167+
def set_x(self, x):
168+
pass
169+
170+
def get_x(self):
171+
pass
172+
173+
C.x = property(get_x, set_x)
174+
time.sleep(0)
175+
del C.x
176+
time.sleep(0)
177+
178+
do_race(set_slot, change_type)
179+
180+
def set_getattribute():
181+
C.__getattribute__ = lambda self, x: x
182+
time.sleep(0)
183+
del C.__getattribute__
184+
time.sleep(0)
185+
186+
do_race(set_slot, set_getattribute)
187+
188+
def test_racing_store_attr_instance_value(self):
189+
class C:
190+
pass
191+
192+
c = C()
193+
194+
def set_value():
195+
for i in range(100):
196+
c.x = i
197+
198+
set_value()
199+
200+
def read():
201+
x = c.x
202+
203+
def mutate():
204+
# Adding a property for 'x' should unspecialize it.
205+
C.x = property(lambda self: None, lambda self, x: None)
206+
time.sleep(0)
207+
del C.x
208+
time.sleep(0)
209+
210+
do_race(read, mutate)
211+
212+
def test_racing_store_attr_with_hint(self):
213+
class C:
214+
pass
215+
216+
c = C()
217+
for i in range(29):
218+
setattr(c, f"_{i}", None)
219+
220+
def set_value():
221+
for i in range(100):
222+
c.x = i
223+
224+
set_value()
225+
226+
def read():
227+
x = c.x
228+
229+
def mutate():
230+
# Adding a property for 'x' should unspecialize it.
231+
C.x = property(lambda self: None, lambda self, x: None)
232+
time.sleep(0)
233+
del C.x
234+
time.sleep(0)
235+
236+
do_race(read, mutate)
237+
132238

133239
if __name__ == "__main__":
134240
unittest.main()

Lib/test/test_opcache.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,74 @@ def send_yield_from():
13751375
self.assert_specialized(send_yield_from, "SEND_GEN")
13761376
self.assert_no_opcode(send_yield_from, "SEND")
13771377

1378+
@cpython_only
1379+
@requires_specialization_ft
1380+
def test_store_attr_slot(self):
1381+
class C:
1382+
__slots__ = ['x']
1383+
1384+
def set_slot():
1385+
c = C()
1386+
for i in range(100):
1387+
c.x = i
1388+
1389+
set_slot()
1390+
1391+
#dis.dis(set_slot, adaptive=True)
1392+
1393+
self.assert_specialized(set_slot, "STORE_ATTR_SLOT")
1394+
self.assert_no_opcode(set_slot, "STORE_ATTR")
1395+
1396+
# Adding a property for 'x' should unspecialize it.
1397+
C.x = property(lambda self: None, lambda self, x: None)
1398+
set_slot()
1399+
self.assert_no_opcode(set_slot, "STORE_ATTR_SLOT")
1400+
1401+
@cpython_only
1402+
@requires_specialization_ft
1403+
def test_store_attr_instance_value(self):
1404+
class C:
1405+
pass
1406+
1407+
def set_value():
1408+
c = C()
1409+
for i in range(100):
1410+
c.x = i
1411+
1412+
set_value()
1413+
1414+
self.assert_specialized(set_value, "STORE_ATTR_INSTANCE_VALUE")
1415+
self.assert_no_opcode(set_value, "STORE_ATTR")
1416+
1417+
# Adding a property for 'x' should unspecialize it.
1418+
C.x = property(lambda self: None, lambda self, x: None)
1419+
set_value()
1420+
self.assert_no_opcode(set_value, "STORE_ATTR_INSTANCE_VALUE")
1421+
1422+
@cpython_only
1423+
@requires_specialization_ft
1424+
def test_store_attr_with_hint(self):
1425+
class C:
1426+
pass
1427+
1428+
c = C()
1429+
for i in range(29):
1430+
setattr(c, f"_{i}", None)
1431+
1432+
def set_value():
1433+
for i in range(100):
1434+
c.x = i
1435+
1436+
set_value()
1437+
1438+
self.assert_specialized(set_value, "STORE_ATTR_WITH_HINT")
1439+
self.assert_no_opcode(set_value, "STORE_ATTR")
1440+
1441+
# Adding a property for 'x' should unspecialize it.
1442+
C.x = property(lambda self: None, lambda self, x: None)
1443+
set_value()
1444+
self.assert_no_opcode(set_value, "STORE_ATTR_WITH_HINT")
1445+
13781446
@cpython_only
13791447
@requires_specialization_ft
13801448
def test_to_bool(self):

Python/bytecodes.c

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,7 @@ dummy_func(
14671467
};
14681468

14691469
specializing op(_SPECIALIZE_STORE_ATTR, (counter/1, owner -- owner)) {
1470-
#if ENABLE_SPECIALIZATION
1470+
#if ENABLE_SPECIALIZATION_FT
14711471
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
14721472
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
14731473
next_instr = this_instr;
@@ -1476,7 +1476,7 @@ dummy_func(
14761476
}
14771477
OPCODE_DEFERRED_INC(STORE_ATTR);
14781478
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
1479-
#endif /* ENABLE_SPECIALIZATION */
1479+
#endif /* ENABLE_SPECIALIZATION_FT */
14801480
}
14811481

14821482
op(_STORE_ATTR, (v, owner --)) {
@@ -2129,7 +2129,7 @@ dummy_func(
21292129
op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner)) {
21302130
PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner));
21312131
assert(type_version != 0);
2132-
EXIT_IF(tp->tp_version_tag != type_version);
2132+
EXIT_IF(FT_ATOMIC_LOAD_UINT32_RELAXED(tp->tp_version_tag) != type_version);
21332133
}
21342134

21352135
op(_CHECK_MANAGED_OBJECT_HAS_VALUES, (owner -- owner)) {
@@ -2337,25 +2337,27 @@ dummy_func(
23372337
assert(Py_TYPE(owner_o)->tp_dictoffset < 0);
23382338
assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
23392339
EXIT_IF(_PyObject_GetManagedDict(owner_o));
2340-
EXIT_IF(_PyObject_InlineValues(owner_o)->valid == 0);
2340+
EXIT_IF(FT_ATOMIC_LOAD_UINT8(_PyObject_InlineValues(owner_o)->valid) == 0);
23412341
}
23422342

23432343
op(_STORE_ATTR_INSTANCE_VALUE, (offset/1, value, owner --)) {
23442344
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
2345-
2345+
DEOPT_IF(!LOCK_OBJECT(owner_o));
2346+
if (_PyObject_GetManagedDict(owner_o) == NULL) {
2347+
UNLOCK_OBJECT(owner_o);
2348+
DEOPT_IF(true);
2349+
}
23462350
STAT_INC(STORE_ATTR, hit);
2347-
assert(_PyObject_GetManagedDict(owner_o) == NULL);
23482351
PyObject **value_ptr = (PyObject**)(((char *)owner_o) + offset);
23492352
PyObject *old_value = *value_ptr;
2350-
*value_ptr = PyStackRef_AsPyObjectSteal(value);
2353+
FT_ATOMIC_STORE_PTR_RELEASE(*value_ptr, PyStackRef_AsPyObjectSteal(value));
23512354
if (old_value == NULL) {
23522355
PyDictValues *values = _PyObject_InlineValues(owner_o);
23532356
Py_ssize_t index = value_ptr - values->values;
23542357
_PyDictValues_AddToInsertionOrder(values, index);
23552358
}
2356-
else {
2357-
Py_DECREF(old_value);
2358-
}
2359+
UNLOCK_OBJECT(owner_o);
2360+
Py_XDECREF(old_value);
23592361
PyStackRef_CLOSE(owner);
23602362
}
23612363

@@ -2370,16 +2372,33 @@ dummy_func(
23702372
assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
23712373
PyDictObject *dict = _PyObject_GetManagedDict(owner_o);
23722374
DEOPT_IF(dict == NULL);
2375+
2376+
DEOPT_IF(!LOCK_OBJECT(dict));
2377+
if (dict == NULL) {
2378+
UNLOCK_OBJECT(dict);
2379+
DEOPT_IF(true);
2380+
}
23732381
assert(PyDict_CheckExact((PyObject *)dict));
23742382
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
2375-
DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries);
2376-
DEOPT_IF(!DK_IS_UNICODE(dict->ma_keys));
2383+
if (hint >= (size_t)dict->ma_keys->dk_nentries ||
2384+
!DK_IS_UNICODE(dict->ma_keys)) {
2385+
UNLOCK_OBJECT(dict);
2386+
DEOPT_IF(true);
2387+
}
23772388
PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + hint;
2378-
DEOPT_IF(ep->me_key != name);
2389+
if (ep->me_key != name) {
2390+
UNLOCK_OBJECT(dict);
2391+
DEOPT_IF(true);
2392+
}
23792393
PyObject *old_value = ep->me_value;
2380-
DEOPT_IF(old_value == NULL);
2394+
if (old_value == NULL) {
2395+
UNLOCK_OBJECT(dict);
2396+
DEOPT_IF(true);
2397+
}
23812398
_PyDict_NotifyEvent(tstate->interp, PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
2382-
ep->me_value = PyStackRef_AsPyObjectSteal(value);
2399+
FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value));
2400+
UNLOCK_OBJECT(dict);
2401+
23832402
// old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
23842403
// when dict only holds the strong reference to value in ep->me_value.
23852404
Py_XDECREF(old_value);
@@ -2395,10 +2414,12 @@ dummy_func(
23952414
op(_STORE_ATTR_SLOT, (index/1, value, owner --)) {
23962415
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
23972416

2417+
DEOPT_IF(!LOCK_OBJECT(owner_o));
23982418
char *addr = (char *)owner_o + index;
23992419
STAT_INC(STORE_ATTR, hit);
24002420
PyObject *old_value = *(PyObject **)addr;
2401-
*(PyObject **)addr = PyStackRef_AsPyObjectSteal(value);
2421+
FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, PyStackRef_AsPyObjectSteal(value));
2422+
UNLOCK_OBJECT(owner_o);
24022423
Py_XDECREF(old_value);
24032424
PyStackRef_CLOSE(owner);
24042425
}

0 commit comments

Comments
 (0)