Skip to content

Commit de2f7da

Browse files
authored
gh-115999: Add free-threaded specialization for FOR_ITER (#128798)
Add free-threaded versions of existing specialization for FOR_ITER (list, tuples, fast range iterators and generators), without significantly affecting their thread-safety. (Iterating over shared lists/tuples/ranges should be fine like before. Reusing iterators between threads is not fine, like before. Sharing generators between threads is a recipe for significant crashes, like before.)
1 parent db27aee commit de2f7da

File tree

13 files changed

+469
-125
lines changed

13 files changed

+469
-125
lines changed

Include/internal/pycore_list.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
#ifdef Py_GIL_DISABLED
12+
#include "pycore_stackref.h"
13+
#endif
14+
1115
PyAPI_FUNC(PyObject*) _PyList_Extend(PyListObject *, PyObject *);
1216
extern void _PyList_DebugMallocStats(FILE *out);
1317
// _PyList_GetItemRef should be used only when the object is known as a list
1418
// because it doesn't raise TypeError when the object is not a list, whereas PyList_GetItemRef does.
1519
extern PyObject* _PyList_GetItemRef(PyListObject *, Py_ssize_t i);
20+
#ifdef Py_GIL_DISABLED
21+
// Returns -1 in case of races with other threads.
22+
extern int _PyList_GetItemRefNoLock(PyListObject *, Py_ssize_t, _PyStackRef *);
23+
#endif
1624

1725
#define _PyList_ITEMS(op) _Py_RVALUE(_PyList_CAST(op)->ob_item)
1826

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_ids.h

Lines changed: 77 additions & 76 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: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_opcache.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,5 +1802,44 @@ def unused(): pass
18021802
self.assert_specialized(load_const, "LOAD_CONST_MORTAL")
18031803
self.assert_no_opcode(load_const, "LOAD_CONST")
18041804

1805+
@cpython_only
1806+
@requires_specialization_ft
1807+
def test_for_iter(self):
1808+
L = list(range(10))
1809+
def for_iter_list():
1810+
for i in L:
1811+
self.assertIn(i, L)
1812+
1813+
for_iter_list()
1814+
self.assert_specialized(for_iter_list, "FOR_ITER_LIST")
1815+
self.assert_no_opcode(for_iter_list, "FOR_ITER")
1816+
1817+
t = tuple(range(10))
1818+
def for_iter_tuple():
1819+
for i in t:
1820+
self.assertIn(i, t)
1821+
1822+
for_iter_tuple()
1823+
self.assert_specialized(for_iter_tuple, "FOR_ITER_TUPLE")
1824+
self.assert_no_opcode(for_iter_tuple, "FOR_ITER")
1825+
1826+
r = range(10)
1827+
def for_iter_range():
1828+
for i in r:
1829+
self.assertIn(i, r)
1830+
1831+
for_iter_range()
1832+
self.assert_specialized(for_iter_range, "FOR_ITER_RANGE")
1833+
self.assert_no_opcode(for_iter_range, "FOR_ITER")
1834+
1835+
def for_iter_generator():
1836+
for i in (i for i in range(10)):
1837+
i + 1
1838+
1839+
for_iter_generator()
1840+
self.assert_specialized(for_iter_generator, "FOR_ITER_GEN")
1841+
self.assert_no_opcode(for_iter_generator, "FOR_ITER")
1842+
1843+
18051844
if __name__ == "__main__":
18061845
unittest.main()

Objects/listobject.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,32 @@ _PyList_GetItemRef(PyListObject *list, Py_ssize_t i)
415415
return list_get_item_ref(list, i);
416416
}
417417

418+
#ifdef Py_GIL_DISABLED
419+
int
420+
_PyList_GetItemRefNoLock(PyListObject *list, Py_ssize_t i, _PyStackRef *result)
421+
{
422+
assert(_Py_IsOwnedByCurrentThread((PyObject *)list) ||
423+
_PyObject_GC_IS_SHARED(list));
424+
if (!valid_index(i, PyList_GET_SIZE(list))) {
425+
return 0;
426+
}
427+
PyObject **ob_item = _Py_atomic_load_ptr(&list->ob_item);
428+
if (ob_item == NULL) {
429+
return 0;
430+
}
431+
Py_ssize_t cap = list_capacity(ob_item);
432+
assert(cap != -1);
433+
if (!valid_index(i, cap)) {
434+
return 0;
435+
}
436+
PyObject *obj = _Py_atomic_load_ptr(&ob_item[i]);
437+
if (obj == NULL || !_Py_TryIncrefCompareStackRef(&ob_item[i], obj, result)) {
438+
return -1;
439+
}
440+
return 1;
441+
}
442+
#endif
443+
418444
int
419445
PyList_SetItem(PyObject *op, Py_ssize_t i,
420446
PyObject *newitem)

0 commit comments

Comments
 (0)