Skip to content

Commit 48c70b8

Browse files
authored
gh-115999: Enable BINARY_SUBSCR_GETITEM for free-threaded build (gh-127737)
1 parent f802c8b commit 48c70b8

12 files changed

+118
-62
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_typeobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ typedef int (*_py_validate_type)(PyTypeObject *);
278278
// and if the validation is passed, it will set the ``tp_version`` as valid
279279
// tp_version_tag from the ``ty``.
280280
extern int _PyType_Validate(PyTypeObject *ty, _py_validate_type validate, unsigned int *tp_version);
281+
extern int _PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject *descriptor, uint32_t tp_version);
281282

282283
#ifdef __cplusplus
283284
}

Include/internal/pycore_uop_metadata.h

Lines changed: 1 addition & 1 deletion
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: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ def write(items):
10691069
opname = "STORE_SUBSCR_LIST_INT"
10701070
self.assert_races_do_not_crash(opname, get_items, read, write)
10711071

1072-
@requires_specialization
1072+
@requires_specialization_ft
10731073
def test_unpack_sequence_list(self):
10741074
def get_items():
10751075
items = []
@@ -1245,6 +1245,14 @@ def f(o, n):
12451245
f(test_obj, 1)
12461246
self.assertEqual(test_obj.b, 0)
12471247

1248+
# gh-127274: BINARY_SUBSCR_GETITEM will only cache __getitem__ methods that
1249+
# are deferred. We only defer functions defined at the top-level.
1250+
class CGetItem:
1251+
def __init__(self, val):
1252+
self.val = val
1253+
def __getitem__(self, item):
1254+
return self.val
1255+
12481256

12491257
class TestSpecializer(TestBase):
12501258

@@ -1520,6 +1528,15 @@ def binary_subscr_str_int():
15201528
self.assert_specialized(binary_subscr_str_int, "BINARY_SUBSCR_STR_INT")
15211529
self.assert_no_opcode(binary_subscr_str_int, "BINARY_SUBSCR")
15221530

1531+
def binary_subscr_getitems():
1532+
items = [CGetItem(i) for i in range(100)]
1533+
for i in range(100):
1534+
self.assertEqual(items[i][i], i)
1535+
1536+
binary_subscr_getitems()
1537+
self.assert_specialized(binary_subscr_getitems, "BINARY_SUBSCR_GETITEM")
1538+
self.assert_no_opcode(binary_subscr_getitems, "BINARY_SUBSCR")
1539+
15231540

15241541
if __name__ == "__main__":
15251542
unittest.main()

Objects/typeobject.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5679,6 +5679,31 @@ _PyType_CacheInitForSpecialization(PyHeapTypeObject *type, PyObject *init,
56795679
return can_cache;
56805680
}
56815681

5682+
int
5683+
_PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject *descriptor, uint32_t tp_version)
5684+
{
5685+
if (!descriptor || !tp_version) {
5686+
return 0;
5687+
}
5688+
int can_cache;
5689+
BEGIN_TYPE_LOCK();
5690+
can_cache = ((PyTypeObject*)ht)->tp_version_tag == tp_version;
5691+
// This pointer is invalidated by PyType_Modified (see the comment on
5692+
// struct _specialization_cache):
5693+
PyFunctionObject *func = (PyFunctionObject *)descriptor;
5694+
uint32_t version = _PyFunction_GetVersionForCurrentState(func);
5695+
can_cache = can_cache && _PyFunction_IsVersionValid(version);
5696+
#ifdef Py_GIL_DISABLED
5697+
can_cache = can_cache && _PyObject_HasDeferredRefcount(descriptor);
5698+
#endif
5699+
if (can_cache) {
5700+
FT_ATOMIC_STORE_PTR_RELEASE(ht->_spec_cache.getitem, descriptor);
5701+
FT_ATOMIC_STORE_UINT32_RELAXED(ht->_spec_cache.getitem_version, version);
5702+
}
5703+
END_TYPE_LOCK();
5704+
return can_cache;
5705+
}
5706+
56825707
static void
56835708
set_flags(PyTypeObject *self, unsigned long mask, unsigned long flags)
56845709
{

Programs/test_frozenmain.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/bytecodes.c

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -865,26 +865,24 @@ dummy_func(
865865
res = PyStackRef_FromPyObjectSteal(res_o);
866866
}
867867

868-
op(_BINARY_SUBSCR_CHECK_FUNC, (container, unused -- container, unused)) {
868+
op(_BINARY_SUBSCR_CHECK_FUNC, (container, unused -- container, unused, getitem)) {
869869
PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(container));
870870
DEOPT_IF(!PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE));
871871
PyHeapTypeObject *ht = (PyHeapTypeObject *)tp;
872-
PyObject *getitem = ht->_spec_cache.getitem;
873-
DEOPT_IF(getitem == NULL);
874-
assert(PyFunction_Check(getitem));
875-
uint32_t cached_version = ht->_spec_cache.getitem_version;
876-
DEOPT_IF(((PyFunctionObject *)getitem)->func_version != cached_version);
877-
PyCodeObject *code = (PyCodeObject *)PyFunction_GET_CODE(getitem);
872+
PyObject *getitem_o = FT_ATOMIC_LOAD_PTR_ACQUIRE(ht->_spec_cache.getitem);
873+
DEOPT_IF(getitem_o == NULL);
874+
assert(PyFunction_Check(getitem_o));
875+
uint32_t cached_version = FT_ATOMIC_LOAD_UINT32_RELAXED(ht->_spec_cache.getitem_version);
876+
DEOPT_IF(((PyFunctionObject *)getitem_o)->func_version != cached_version);
877+
PyCodeObject *code = (PyCodeObject *)PyFunction_GET_CODE(getitem_o);
878878
assert(code->co_argcount == 2);
879879
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize));
880+
getitem = PyStackRef_FromPyObjectNew(getitem_o);
880881
STAT_INC(BINARY_SUBSCR, hit);
881882
}
882883

883-
op(_BINARY_SUBSCR_INIT_CALL, (container, sub -- new_frame: _PyInterpreterFrame* )) {
884-
PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(container));
885-
PyHeapTypeObject *ht = (PyHeapTypeObject *)tp;
886-
PyObject *getitem = ht->_spec_cache.getitem;
887-
new_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(getitem), 2, frame);
884+
op(_BINARY_SUBSCR_INIT_CALL, (container, sub, getitem -- new_frame: _PyInterpreterFrame* )) {
885+
new_frame = _PyFrame_PushUnchecked(tstate, getitem, 2, frame);
888886
new_frame->localsplus[0] = container;
889887
new_frame->localsplus[1] = sub;
890888
INPUTS_DEAD();

Python/executor_cases.c.h

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

Python/generated_cases.c.h

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

Python/optimizer_bytecodes.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,10 @@ dummy_func(void) {
349349
GETLOCAL(this_instr->operand0) = res;
350350
}
351351

352-
op(_BINARY_SUBSCR_INIT_CALL, (container, sub -- new_frame: _Py_UOpsAbstractFrame *)) {
352+
op(_BINARY_SUBSCR_INIT_CALL, (container, sub, getitem -- new_frame: _Py_UOpsAbstractFrame *)) {
353353
(void)container;
354354
(void)sub;
355+
(void)getitem;
355356
new_frame = NULL;
356357
ctx->done = true;
357358
}

0 commit comments

Comments
 (0)