Skip to content

Commit 7e3de44

Browse files
committed
Enable specialization of STORE_ATTR free-threaded builds.
1 parent bc262de commit 7e3de44

File tree

8 files changed

+402
-122
lines changed

8 files changed

+402
-122
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
@@ -1466,7 +1466,7 @@ dummy_func(
14661466
};
14671467

14681468
specializing op(_SPECIALIZE_STORE_ATTR, (counter/1, owner -- owner)) {
1469-
#if ENABLE_SPECIALIZATION
1469+
#if ENABLE_SPECIALIZATION_FT
14701470
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
14711471
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
14721472
next_instr = this_instr;
@@ -1475,7 +1475,7 @@ dummy_func(
14751475
}
14761476
OPCODE_DEFERRED_INC(STORE_ATTR);
14771477
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
1478-
#endif /* ENABLE_SPECIALIZATION */
1478+
#endif /* ENABLE_SPECIALIZATION_FT */
14791479
}
14801480

14811481
op(_STORE_ATTR, (v, owner --)) {
@@ -2128,7 +2128,7 @@ dummy_func(
21282128
op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner)) {
21292129
PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner));
21302130
assert(type_version != 0);
2131-
EXIT_IF(tp->tp_version_tag != type_version);
2131+
EXIT_IF(FT_ATOMIC_LOAD_UINT32_RELAXED(tp->tp_version_tag) != type_version);
21322132
}
21332133

21342134
op(_CHECK_MANAGED_OBJECT_HAS_VALUES, (owner -- owner)) {
@@ -2326,25 +2326,27 @@ dummy_func(
23262326
assert(Py_TYPE(owner_o)->tp_dictoffset < 0);
23272327
assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
23282328
EXIT_IF(_PyObject_GetManagedDict(owner_o));
2329-
EXIT_IF(_PyObject_InlineValues(owner_o)->valid == 0);
2329+
EXIT_IF(FT_ATOMIC_LOAD_UINT8(_PyObject_InlineValues(owner_o)->valid) == 0);
23302330
}
23312331

23322332
op(_STORE_ATTR_INSTANCE_VALUE, (offset/1, value, owner --)) {
23332333
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
2334-
2334+
DEOPT_IF(!LOCK_OBJECT(owner_o));
2335+
if (_PyObject_GetManagedDict(owner_o) == NULL) {
2336+
UNLOCK_OBJECT(owner_o);
2337+
DEOPT_IF(true);
2338+
}
23352339
STAT_INC(STORE_ATTR, hit);
2336-
assert(_PyObject_GetManagedDict(owner_o) == NULL);
23372340
PyObject **value_ptr = (PyObject**)(((char *)owner_o) + offset);
23382341
PyObject *old_value = *value_ptr;
2339-
*value_ptr = PyStackRef_AsPyObjectSteal(value);
2342+
FT_ATOMIC_STORE_PTR_RELEASE(*value_ptr, PyStackRef_AsPyObjectSteal(value));
23402343
if (old_value == NULL) {
23412344
PyDictValues *values = _PyObject_InlineValues(owner_o);
23422345
Py_ssize_t index = value_ptr - values->values;
23432346
_PyDictValues_AddToInsertionOrder(values, index);
23442347
}
2345-
else {
2346-
Py_DECREF(old_value);
2347-
}
2348+
UNLOCK_OBJECT(owner_o);
2349+
Py_XDECREF(old_value);
23482350
PyStackRef_CLOSE(owner);
23492351
}
23502352

@@ -2359,16 +2361,33 @@ dummy_func(
23592361
assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
23602362
PyDictObject *dict = _PyObject_GetManagedDict(owner_o);
23612363
DEOPT_IF(dict == NULL);
2364+
2365+
DEOPT_IF(!LOCK_OBJECT(dict));
2366+
if (dict == NULL) {
2367+
UNLOCK_OBJECT(dict);
2368+
DEOPT_IF(true);
2369+
}
23622370
assert(PyDict_CheckExact((PyObject *)dict));
23632371
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
2364-
DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries);
2365-
DEOPT_IF(!DK_IS_UNICODE(dict->ma_keys));
2372+
if (hint >= (size_t)dict->ma_keys->dk_nentries ||
2373+
!DK_IS_UNICODE(dict->ma_keys)) {
2374+
UNLOCK_OBJECT(dict);
2375+
DEOPT_IF(true);
2376+
}
23662377
PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + hint;
2367-
DEOPT_IF(ep->me_key != name);
2378+
if (ep->me_key != name) {
2379+
UNLOCK_OBJECT(dict);
2380+
DEOPT_IF(true);
2381+
}
23682382
PyObject *old_value = ep->me_value;
2369-
DEOPT_IF(old_value == NULL);
2383+
if (old_value == NULL) {
2384+
UNLOCK_OBJECT(dict);
2385+
DEOPT_IF(true);
2386+
}
23702387
_PyDict_NotifyEvent(tstate->interp, PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
2371-
ep->me_value = PyStackRef_AsPyObjectSteal(value);
2388+
FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value));
2389+
UNLOCK_OBJECT(dict);
2390+
23722391
// old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
23732392
// when dict only holds the strong reference to value in ep->me_value.
23742393
Py_XDECREF(old_value);
@@ -2384,10 +2403,12 @@ dummy_func(
23842403
op(_STORE_ATTR_SLOT, (index/1, value, owner --)) {
23852404
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
23862405

2406+
DEOPT_IF(!LOCK_OBJECT(owner_o));
23872407
char *addr = (char *)owner_o + index;
23882408
STAT_INC(STORE_ATTR, hit);
23892409
PyObject *old_value = *(PyObject **)addr;
2390-
*(PyObject **)addr = PyStackRef_AsPyObjectSteal(value);
2410+
FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, PyStackRef_AsPyObjectSteal(value));
2411+
UNLOCK_OBJECT(owner_o);
23912412
Py_XDECREF(old_value);
23922413
PyStackRef_CLOSE(owner);
23932414
}

0 commit comments

Comments
 (0)