GH-104405: Add missing PEP 523 checks (GH-104406)

This commit is contained in:
Brandt Bucher 2023-05-12 15:23:13 -07:00 committed by GitHub
parent a10b026f0f
commit 1eb950ca55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 505 additions and 420 deletions

View File

@ -1748,28 +1748,80 @@ SUFFICIENT_TO_DEOPT_AND_SPECIALIZE = 100
class Test_Pep523API(unittest.TestCase):
def do_test(self, func):
calls = []
def do_test(self, func, names):
actual_calls = []
start = SUFFICIENT_TO_DEOPT_AND_SPECIALIZE
count = start + SUFFICIENT_TO_DEOPT_AND_SPECIALIZE
for i in range(count):
if i == start:
_testinternalcapi.set_eval_frame_record(calls)
func()
_testinternalcapi.set_eval_frame_default()
self.assertEqual(len(calls), SUFFICIENT_TO_DEOPT_AND_SPECIALIZE)
for name in calls:
self.assertEqual(name, func.__name__)
try:
for i in range(count):
if i == start:
_testinternalcapi.set_eval_frame_record(actual_calls)
func()
finally:
_testinternalcapi.set_eval_frame_default()
expected_calls = names * SUFFICIENT_TO_DEOPT_AND_SPECIALIZE
self.assertEqual(len(expected_calls), len(actual_calls))
for expected, actual in zip(expected_calls, actual_calls, strict=True):
self.assertEqual(expected, actual)
def test_pep523_with_specialization_simple(self):
def func1():
pass
self.do_test(func1)
def test_inlined_binary_subscr(self):
class C:
def __getitem__(self, other):
return None
def func():
C()[42]
names = ["func", "__getitem__"]
self.do_test(func, names)
def test_pep523_with_specialization_with_default(self):
def func2(x=None):
def test_inlined_call(self):
def inner(x=42):
pass
self.do_test(func2)
def func():
inner()
inner(42)
names = ["func", "inner", "inner"]
self.do_test(func, names)
def test_inlined_call_function_ex(self):
def inner(x):
pass
def func():
inner(*[42])
names = ["func", "inner"]
self.do_test(func, names)
def test_inlined_for_iter(self):
def gen():
yield 42
def func():
for _ in gen():
pass
names = ["func", "gen", "gen", "gen"]
self.do_test(func, names)
def test_inlined_load_attr(self):
class C:
@property
def a(self):
return 42
class D:
def __getattribute__(self, name):
return 42
def func():
C().a
D().a
names = ["func", "a", "__getattribute__"]
self.do_test(func, names)
def test_inlined_send(self):
def inner():
yield 42
def outer():
yield from inner()
def func():
list(outer())
names = ["func", "outer", "outer", "inner", "inner", "outer", "inner"]
self.do_test(func, names)
if __name__ == "__main__":

View File

@ -0,0 +1,2 @@
Fix an issue where some :term:`bytecode` instructions could ignore
:pep:`523` when "inlining" calls.

View File

@ -494,6 +494,7 @@ dummy_func(
}
inst(BINARY_SUBSCR_GETITEM, (unused/1, container, sub -- unused)) {
DEOPT_IF(tstate->interp->eval_frame, BINARY_SUBSCR);
PyTypeObject *tp = Py_TYPE(container);
DEOPT_IF(!PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE), BINARY_SUBSCR);
PyHeapTypeObject *ht = (PyHeapTypeObject *)tp;
@ -830,8 +831,9 @@ dummy_func(
DECREMENT_ADAPTIVE_COUNTER(cache->counter);
#endif /* ENABLE_SPECIALIZATION */
assert(frame != &entry_frame);
if ((Py_TYPE(receiver) == &PyGen_Type ||
Py_TYPE(receiver) == &PyCoro_Type) && ((PyGenObject *)receiver)->gi_frame_state < FRAME_EXECUTING)
if ((tstate->interp->eval_frame == NULL) &&
(Py_TYPE(receiver) == &PyGen_Type || Py_TYPE(receiver) == &PyCoro_Type) &&
((PyGenObject *)receiver)->gi_frame_state < FRAME_EXECUTING)
{
PyGenObject *gen = (PyGenObject *)receiver;
_PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe;
@ -867,6 +869,7 @@ dummy_func(
}
inst(SEND_GEN, (unused/1, receiver, v -- receiver, unused)) {
DEOPT_IF(tstate->interp->eval_frame, SEND);
PyGenObject *gen = (PyGenObject *)receiver;
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type &&
Py_TYPE(gen) != &PyCoro_Type, SEND);
@ -2331,6 +2334,7 @@ dummy_func(
}
inst(FOR_ITER_GEN, (unused/1, iter -- iter, unused)) {
DEOPT_IF(tstate->interp->eval_frame, FOR_ITER);
PyGenObject *gen = (PyGenObject *)iter;
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type, FOR_ITER);
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING, FOR_ITER);

View File

@ -105,6 +105,7 @@
#define DISPATCH_INLINED(NEW_FRAME) \
do { \
assert(tstate->interp->eval_frame == NULL); \
_PyFrame_SetStackPointer(frame, stack_pointer); \
frame->prev_instr = next_instr - 1; \
(NEW_FRAME)->previous = frame; \

File diff suppressed because it is too large Load Diff

View File

@ -783,6 +783,10 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
if (version == 0) {
goto fail;
}
if (_PyInterpreterState_GET()->eval_frame) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER);
goto fail;
}
write_u32(lm_cache->keys_version, version);
assert(type->tp_version_tag != 0);
write_u32(lm_cache->type_version, type->tp_version_tag);
@ -845,6 +849,10 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
if (version == 0) {
goto fail;
}
if (_PyInterpreterState_GET()->eval_frame) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER);
goto fail;
}
write_u32(lm_cache->keys_version, version);
/* borrowed */
write_obj(lm_cache->descr, descr);
@ -1371,6 +1379,10 @@ _Py_Specialize_BinarySubscr(
SPECIALIZATION_FAIL(BINARY_SUBSCR, SPEC_FAIL_OUT_OF_VERSIONS);
goto fail;
}
if (_PyInterpreterState_GET()->eval_frame) {
SPECIALIZATION_FAIL(BINARY_SUBSCR, SPEC_FAIL_OTHER);
goto fail;
}
PyHeapTypeObject *ht = (PyHeapTypeObject *)container_type;
// This pointer is invalidated by PyType_Modified (see the comment on
// struct _specialization_cache):
@ -2192,11 +2204,16 @@ _Py_Specialize_ForIter(PyObject *iter, _Py_CODEUNIT *instr, int oparg)
assert(instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == END_FOR ||
instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == INSTRUMENTED_END_FOR
);
if (_PyInterpreterState_GET()->eval_frame) {
SPECIALIZATION_FAIL(FOR_ITER, SPEC_FAIL_OTHER);
goto failure;
}
instr->op.code = FOR_ITER_GEN;
goto success;
}
SPECIALIZATION_FAIL(FOR_ITER,
_PySpecialization_ClassifyIterator(iter));
failure:
STAT_INC(FOR_ITER, failure);
instr->op.code = FOR_ITER;
cache->counter = adaptive_counter_backoff(cache->counter);
@ -2214,11 +2231,16 @@ _Py_Specialize_Send(PyObject *receiver, _Py_CODEUNIT *instr)
_PySendCache *cache = (_PySendCache *)(instr + 1);
PyTypeObject *tp = Py_TYPE(receiver);
if (tp == &PyGen_Type || tp == &PyCoro_Type) {
if (_PyInterpreterState_GET()->eval_frame) {
SPECIALIZATION_FAIL(SEND, SPEC_FAIL_OTHER);
goto failure;
}
instr->op.code = SEND_GEN;
goto success;
}
SPECIALIZATION_FAIL(SEND,
_PySpecialization_ClassifyIterator(receiver));
failure:
STAT_INC(SEND, failure);
instr->op.code = SEND;
cache->counter = adaptive_counter_backoff(cache->counter);