Skip to content

Commit 1eb950c

Browse files
authored
GH-104405: Add missing PEP 523 checks (GH-104406)
1 parent a10b026 commit 1eb950c

File tree

6 files changed

+506
-421
lines changed

6 files changed

+506
-421
lines changed

Lib/test/test_capi/test_misc.py

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1748,28 +1748,80 @@ class Subclass(BaseException, self.module.StateAccessType):
17481748

17491749
class Test_Pep523API(unittest.TestCase):
17501750

1751-
def do_test(self, func):
1752-
calls = []
1751+
def do_test(self, func, names):
1752+
actual_calls = []
17531753
start = SUFFICIENT_TO_DEOPT_AND_SPECIALIZE
17541754
count = start + SUFFICIENT_TO_DEOPT_AND_SPECIALIZE
1755-
for i in range(count):
1756-
if i == start:
1757-
_testinternalcapi.set_eval_frame_record(calls)
1758-
func()
1759-
_testinternalcapi.set_eval_frame_default()
1760-
self.assertEqual(len(calls), SUFFICIENT_TO_DEOPT_AND_SPECIALIZE)
1761-
for name in calls:
1762-
self.assertEqual(name, func.__name__)
1763-
1764-
def test_pep523_with_specialization_simple(self):
1765-
def func1():
1766-
pass
1767-
self.do_test(func1)
1755+
try:
1756+
for i in range(count):
1757+
if i == start:
1758+
_testinternalcapi.set_eval_frame_record(actual_calls)
1759+
func()
1760+
finally:
1761+
_testinternalcapi.set_eval_frame_default()
1762+
expected_calls = names * SUFFICIENT_TO_DEOPT_AND_SPECIALIZE
1763+
self.assertEqual(len(expected_calls), len(actual_calls))
1764+
for expected, actual in zip(expected_calls, actual_calls, strict=True):
1765+
self.assertEqual(expected, actual)
1766+
1767+
def test_inlined_binary_subscr(self):
1768+
class C:
1769+
def __getitem__(self, other):
1770+
return None
1771+
def func():
1772+
C()[42]
1773+
names = ["func", "__getitem__"]
1774+
self.do_test(func, names)
17681775

1769-
def test_pep523_with_specialization_with_default(self):
1770-
def func2(x=None):
1776+
def test_inlined_call(self):
1777+
def inner(x=42):
1778+
pass
1779+
def func():
1780+
inner()
1781+
inner(42)
1782+
names = ["func", "inner", "inner"]
1783+
self.do_test(func, names)
1784+
1785+
def test_inlined_call_function_ex(self):
1786+
def inner(x):
17711787
pass
1772-
self.do_test(func2)
1788+
def func():
1789+
inner(*[42])
1790+
names = ["func", "inner"]
1791+
self.do_test(func, names)
1792+
1793+
def test_inlined_for_iter(self):
1794+
def gen():
1795+
yield 42
1796+
def func():
1797+
for _ in gen():
1798+
pass
1799+
names = ["func", "gen", "gen", "gen"]
1800+
self.do_test(func, names)
1801+
1802+
def test_inlined_load_attr(self):
1803+
class C:
1804+
@property
1805+
def a(self):
1806+
return 42
1807+
class D:
1808+
def __getattribute__(self, name):
1809+
return 42
1810+
def func():
1811+
C().a
1812+
D().a
1813+
names = ["func", "a", "__getattribute__"]
1814+
self.do_test(func, names)
1815+
1816+
def test_inlined_send(self):
1817+
def inner():
1818+
yield 42
1819+
def outer():
1820+
yield from inner()
1821+
def func():
1822+
list(outer())
1823+
names = ["func", "outer", "outer", "inner", "inner", "outer", "inner"]
1824+
self.do_test(func, names)
17731825

17741826

17751827
if __name__ == "__main__":
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an issue where some :term:`bytecode` instructions could ignore
2+
:pep:`523` when "inlining" calls.

Python/bytecodes.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ dummy_func(
494494
}
495495

496496
inst(BINARY_SUBSCR_GETITEM, (unused/1, container, sub -- unused)) {
497+
DEOPT_IF(tstate->interp->eval_frame, BINARY_SUBSCR);
497498
PyTypeObject *tp = Py_TYPE(container);
498499
DEOPT_IF(!PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE), BINARY_SUBSCR);
499500
PyHeapTypeObject *ht = (PyHeapTypeObject *)tp;
@@ -830,8 +831,9 @@ dummy_func(
830831
DECREMENT_ADAPTIVE_COUNTER(cache->counter);
831832
#endif /* ENABLE_SPECIALIZATION */
832833
assert(frame != &entry_frame);
833-
if ((Py_TYPE(receiver) == &PyGen_Type ||
834-
Py_TYPE(receiver) == &PyCoro_Type) && ((PyGenObject *)receiver)->gi_frame_state < FRAME_EXECUTING)
834+
if ((tstate->interp->eval_frame == NULL) &&
835+
(Py_TYPE(receiver) == &PyGen_Type || Py_TYPE(receiver) == &PyCoro_Type) &&
836+
((PyGenObject *)receiver)->gi_frame_state < FRAME_EXECUTING)
835837
{
836838
PyGenObject *gen = (PyGenObject *)receiver;
837839
_PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe;
@@ -867,6 +869,7 @@ dummy_func(
867869
}
868870

869871
inst(SEND_GEN, (unused/1, receiver, v -- receiver, unused)) {
872+
DEOPT_IF(tstate->interp->eval_frame, SEND);
870873
PyGenObject *gen = (PyGenObject *)receiver;
871874
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type &&
872875
Py_TYPE(gen) != &PyCoro_Type, SEND);
@@ -2331,6 +2334,7 @@ dummy_func(
23312334
}
23322335

23332336
inst(FOR_ITER_GEN, (unused/1, iter -- iter, unused)) {
2337+
DEOPT_IF(tstate->interp->eval_frame, FOR_ITER);
23342338
PyGenObject *gen = (PyGenObject *)iter;
23352339
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type, FOR_ITER);
23362340
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING, FOR_ITER);

Python/ceval_macros.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105

106106
#define DISPATCH_INLINED(NEW_FRAME) \
107107
do { \
108+
assert(tstate->interp->eval_frame == NULL); \
108109
_PyFrame_SetStackPointer(frame, stack_pointer); \
109110
frame->prev_instr = next_instr - 1; \
110111
(NEW_FRAME)->previous = frame; \

0 commit comments

Comments
 (0)