gh-141367: Use CALL_LIST_APPEND instruction only for lists, not for list subclasses (GH-141398)

Co-authored-by: Ken Jin <kenjin4096@gmail.com>
This commit is contained in:
Mikhail Efimov
2025-11-15 00:38:39 +03:00
committed by GitHub
parent f26ed455d5
commit 1281be1caf
7 changed files with 44 additions and 20 deletions

View File

@@ -311,8 +311,8 @@ PyAPI_FUNC(void) _Py_Specialize_LoadGlobal(PyObject *globals, PyObject *builtins
_Py_CODEUNIT *instr, PyObject *name); _Py_CODEUNIT *instr, PyObject *name);
PyAPI_FUNC(void) _Py_Specialize_StoreSubscr(_PyStackRef container, _PyStackRef sub, PyAPI_FUNC(void) _Py_Specialize_StoreSubscr(_PyStackRef container, _PyStackRef sub,
_Py_CODEUNIT *instr); _Py_CODEUNIT *instr);
PyAPI_FUNC(void) _Py_Specialize_Call(_PyStackRef callable, _Py_CODEUNIT *instr, PyAPI_FUNC(void) _Py_Specialize_Call(_PyStackRef callable, _PyStackRef self_or_null,
int nargs); _Py_CODEUNIT *instr, int nargs);
PyAPI_FUNC(void) _Py_Specialize_CallKw(_PyStackRef callable, _Py_CODEUNIT *instr, PyAPI_FUNC(void) _Py_Specialize_CallKw(_PyStackRef callable, _Py_CODEUNIT *instr,
int nargs); int nargs);
PyAPI_FUNC(void) _Py_Specialize_BinaryOp(_PyStackRef lhs, _PyStackRef rhs, _Py_CODEUNIT *instr, PyAPI_FUNC(void) _Py_Specialize_BinaryOp(_PyStackRef lhs, _PyStackRef rhs, _Py_CODEUNIT *instr,

View File

@@ -1872,6 +1872,33 @@ class TestSpecializer(TestBase):
self.assert_specialized(for_iter_generator, "FOR_ITER_GEN") self.assert_specialized(for_iter_generator, "FOR_ITER_GEN")
self.assert_no_opcode(for_iter_generator, "FOR_ITER") self.assert_no_opcode(for_iter_generator, "FOR_ITER")
@cpython_only
@requires_specialization_ft
def test_call_list_append(self):
# gh-141367: only exact lists should use
# CALL_LIST_APPEND instruction after specialization.
r = range(_testinternalcapi.SPECIALIZATION_THRESHOLD)
def list_append(l):
for _ in r:
l.append(1)
list_append([])
self.assert_specialized(list_append, "CALL_LIST_APPEND")
self.assert_no_opcode(list_append, "CALL_METHOD_DESCRIPTOR_O")
self.assert_no_opcode(list_append, "CALL")
def my_list_append(l):
for _ in r:
l.append(1)
class MyList(list): pass
my_list_append(MyList())
self.assert_specialized(my_list_append, "CALL_METHOD_DESCRIPTOR_O")
self.assert_no_opcode(my_list_append, "CALL_LIST_APPEND")
self.assert_no_opcode(my_list_append, "CALL")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -0,0 +1,2 @@
Specialize ``CALL_LIST_APPEND`` instruction only for lists, not for list
subclasses, to avoid unnecessary deopt. Patch by Mikhail Efimov.

View File

@@ -3689,7 +3689,7 @@ dummy_func(
#if ENABLE_SPECIALIZATION_FT #if ENABLE_SPECIALIZATION_FT
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) { if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
next_instr = this_instr; next_instr = this_instr;
_Py_Specialize_Call(callable, next_instr, oparg + !PyStackRef_IsNull(self_or_null)); _Py_Specialize_Call(callable, self_or_null, next_instr, oparg + !PyStackRef_IsNull(self_or_null));
DISPATCH_SAME_OPARG(); DISPATCH_SAME_OPARG();
} }
OPCODE_DEFERRED_INC(CALL); OPCODE_DEFERRED_INC(CALL);
@@ -4395,7 +4395,6 @@ dummy_func(
assert(oparg == 1); assert(oparg == 1);
PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
DEOPT_IF(!PyList_CheckExact(self_o));
DEOPT_IF(!LOCK_OBJECT(self_o)); DEOPT_IF(!LOCK_OBJECT(self_o));
STAT_INC(CALL, hit); STAT_INC(CALL, hit);
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));

View File

@@ -6037,10 +6037,6 @@
callable = stack_pointer[-3]; callable = stack_pointer[-3];
assert(oparg == 1); assert(oparg == 1);
PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
if (!PyList_CheckExact(self_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
if (!LOCK_OBJECT(self_o)) { if (!LOCK_OBJECT(self_o)) {
UOP_STAT_INC(uopcode, miss); UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET(); JUMP_TO_JUMP_TARGET();

View File

@@ -1533,7 +1533,7 @@
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) { if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
next_instr = this_instr; next_instr = this_instr;
_PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_SetStackPointer(frame, stack_pointer);
_Py_Specialize_Call(callable, next_instr, oparg + !PyStackRef_IsNull(self_or_null)); _Py_Specialize_Call(callable, self_or_null, next_instr, oparg + !PyStackRef_IsNull(self_or_null));
stack_pointer = _PyFrame_GetStackPointer(frame); stack_pointer = _PyFrame_GetStackPointer(frame);
DISPATCH_SAME_OPARG(); DISPATCH_SAME_OPARG();
} }
@@ -3470,11 +3470,6 @@
self = nos; self = nos;
assert(oparg == 1); assert(oparg == 1);
PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
if (!PyList_CheckExact(self_o)) {
UPDATE_MISS_STATS(CALL);
assert(_PyOpcode_Deopt[opcode] == (CALL));
JUMP_TO_PREDICTED(CALL);
}
if (!LOCK_OBJECT(self_o)) { if (!LOCK_OBJECT(self_o)) {
UPDATE_MISS_STATS(CALL); UPDATE_MISS_STATS(CALL);
assert(_PyOpcode_Deopt[opcode] == (CALL)); assert(_PyOpcode_Deopt[opcode] == (CALL));

View File

@@ -1602,8 +1602,8 @@ generic:
} }
static int static int
specialize_method_descriptor(PyMethodDescrObject *descr, _Py_CODEUNIT *instr, specialize_method_descriptor(PyMethodDescrObject *descr, PyObject *self_or_null,
int nargs) _Py_CODEUNIT *instr, int nargs)
{ {
switch (descr->d_method->ml_flags & switch (descr->d_method->ml_flags &
(METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O |
@@ -1627,8 +1627,11 @@ specialize_method_descriptor(PyMethodDescrObject *descr, _Py_CODEUNIT *instr,
bool pop = (next.op.code == POP_TOP); bool pop = (next.op.code == POP_TOP);
int oparg = instr->op.arg; int oparg = instr->op.arg;
if ((PyObject *)descr == list_append && oparg == 1 && pop) { if ((PyObject *)descr == list_append && oparg == 1 && pop) {
specialize(instr, CALL_LIST_APPEND); assert(self_or_null != NULL);
return 0; if (PyList_CheckExact(self_or_null)) {
specialize(instr, CALL_LIST_APPEND);
return 0;
}
} }
specialize(instr, CALL_METHOD_DESCRIPTOR_O); specialize(instr, CALL_METHOD_DESCRIPTOR_O);
return 0; return 0;
@@ -1766,7 +1769,7 @@ specialize_c_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs)
} }
Py_NO_INLINE void Py_NO_INLINE void
_Py_Specialize_Call(_PyStackRef callable_st, _Py_CODEUNIT *instr, int nargs) _Py_Specialize_Call(_PyStackRef callable_st, _PyStackRef self_or_null_st, _Py_CODEUNIT *instr, int nargs)
{ {
PyObject *callable = PyStackRef_AsPyObjectBorrow(callable_st); PyObject *callable = PyStackRef_AsPyObjectBorrow(callable_st);
@@ -1784,7 +1787,9 @@ _Py_Specialize_Call(_PyStackRef callable_st, _Py_CODEUNIT *instr, int nargs)
fail = specialize_class_call(callable, instr, nargs); fail = specialize_class_call(callable, instr, nargs);
} }
else if (Py_IS_TYPE(callable, &PyMethodDescr_Type)) { else if (Py_IS_TYPE(callable, &PyMethodDescr_Type)) {
fail = specialize_method_descriptor((PyMethodDescrObject *)callable, instr, nargs); PyObject *self_or_null = PyStackRef_AsPyObjectBorrow(self_or_null_st);
fail = specialize_method_descriptor((PyMethodDescrObject *)callable,
self_or_null, instr, nargs);
} }
else if (PyMethod_Check(callable)) { else if (PyMethod_Check(callable)) {
PyObject *func = ((PyMethodObject *)callable)->im_func; PyObject *func = ((PyMethodObject *)callable)->im_func;