https://github.com/python/cpython/commit/1281be1caf9357ee2a68f7370a88b5cff0110e15
commit: 1281be1caf9357ee2a68f7370a88b5cff0110e15
branch: main
author: Mikhail Efimov <[email protected]>
committer: Fidget-Spinner <[email protected]>
date: 2025-11-14T21:38:39Z
summary:

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

Co-authored-by: Ken Jin <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-11-11-13-40-45.gh-issue-141367.I5KY7F.rst
M Include/internal/pycore_code.h
M Lib/test/test_opcache.py
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/generated_cases.c.h
M Python/specialize.c

diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index 9748e036bf2874..cb9c0aa27a1785 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -311,8 +311,8 @@ PyAPI_FUNC(void) _Py_Specialize_LoadGlobal(PyObject 
*globals, PyObject *builtins
                                       _Py_CODEUNIT *instr, PyObject *name);
 PyAPI_FUNC(void) _Py_Specialize_StoreSubscr(_PyStackRef container, _PyStackRef 
sub,
                                        _Py_CODEUNIT *instr);
-PyAPI_FUNC(void) _Py_Specialize_Call(_PyStackRef callable, _Py_CODEUNIT *instr,
-                                int nargs);
+PyAPI_FUNC(void) _Py_Specialize_Call(_PyStackRef callable, _PyStackRef 
self_or_null,
+                                _Py_CODEUNIT *instr, int nargs);
 PyAPI_FUNC(void) _Py_Specialize_CallKw(_PyStackRef callable, _Py_CODEUNIT 
*instr,
                                   int nargs);
 PyAPI_FUNC(void) _Py_Specialize_BinaryOp(_PyStackRef lhs, _PyStackRef rhs, 
_Py_CODEUNIT *instr,
diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py
index f23f8c053e8431..c7eea75117de8c 100644
--- a/Lib/test/test_opcache.py
+++ b/Lib/test/test_opcache.py
@@ -1872,6 +1872,33 @@ def for_iter_generator():
         self.assert_specialized(for_iter_generator, "FOR_ITER_GEN")
         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__":
     unittest.main()
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-11-13-40-45.gh-issue-141367.I5KY7F.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-11-13-40-45.gh-issue-141367.I5KY7F.rst
new file mode 100644
index 00000000000000..cb830fcd9e1270
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-11-13-40-45.gh-issue-141367.I5KY7F.rst
@@ -0,0 +1,2 @@
+Specialize ``CALL_LIST_APPEND`` instruction only for lists, not for list
+subclasses, to avoid unnecessary deopt. Patch by Mikhail Efimov.
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 2c798855a71f55..8a7b784bb9eec2 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -3689,7 +3689,7 @@ dummy_func(
             #if ENABLE_SPECIALIZATION_FT
             if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
                 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();
             }
             OPCODE_DEFERRED_INC(CALL);
@@ -4395,7 +4395,6 @@ dummy_func(
             assert(oparg == 1);
             PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
 
-            DEOPT_IF(!PyList_CheckExact(self_o));
             DEOPT_IF(!LOCK_OBJECT(self_o));
             STAT_INC(CALL, hit);
             int err = _PyList_AppendTakeRef((PyListObject *)self_o, 
PyStackRef_AsPyObjectSteal(arg));
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 7ba2e9d0d92999..6796abf84ac5f4 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -6037,10 +6037,6 @@
             callable = stack_pointer[-3];
             assert(oparg == 1);
             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)) {
                 UOP_STAT_INC(uopcode, miss);
                 JUMP_TO_JUMP_TARGET();
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index a984da6dc912a2..01f65d9dd375f7 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -1533,7 +1533,7 @@
                 if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
                     next_instr = this_instr;
                     _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);
                     DISPATCH_SAME_OPARG();
                 }
@@ -3470,11 +3470,6 @@
                 self = nos;
                 assert(oparg == 1);
                 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)) {
                     UPDATE_MISS_STATS(CALL);
                     assert(_PyOpcode_Deopt[opcode] == (CALL));
diff --git a/Python/specialize.c b/Python/specialize.c
index 2193596a331d3c..19433bc7a74319 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -1602,8 +1602,8 @@ specialize_class_call(PyObject *callable, _Py_CODEUNIT 
*instr, int nargs)
 }
 
 static int
-specialize_method_descriptor(PyMethodDescrObject *descr, _Py_CODEUNIT *instr,
-                             int nargs)
+specialize_method_descriptor(PyMethodDescrObject *descr, PyObject 
*self_or_null,
+                             _Py_CODEUNIT *instr, int nargs)
 {
     switch (descr->d_method->ml_flags &
         (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);
             int oparg = instr->op.arg;
             if ((PyObject *)descr == list_append && oparg == 1 && pop) {
-                specialize(instr, CALL_LIST_APPEND);
-                return 0;
+                assert(self_or_null != NULL);
+                if (PyList_CheckExact(self_or_null)) {
+                    specialize(instr, CALL_LIST_APPEND);
+                    return 0;
+                }
             }
             specialize(instr, CALL_METHOD_DESCRIPTOR_O);
             return 0;
@@ -1766,7 +1769,7 @@ specialize_c_call(PyObject *callable, _Py_CODEUNIT 
*instr, int nargs)
 }
 
 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);
 
@@ -1784,7 +1787,9 @@ _Py_Specialize_Call(_PyStackRef callable_st, _Py_CODEUNIT 
*instr, int nargs)
         fail = specialize_class_call(callable, instr, nargs);
     }
     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)) {
         PyObject *func = ((PyMethodObject *)callable)->im_func;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to