]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-127274: Defer nested methods (#128012)
authormpage <mpage@meta.com>
Thu, 19 Dec 2024 21:03:14 +0000 (13:03 -0800)
committerGitHub <noreply@github.com>
Thu, 19 Dec 2024 21:03:14 +0000 (13:03 -0800)
Methods (functions defined in class scope) are likely to be cleaned
up by the GC anyway.

Add a new code flag, `CO_METHOD`, that is set for functions defined
in a class scope. Use that when deciding to defer functions.

Doc/library/inspect.rst
Include/cpython/code.h
Include/internal/pycore_symtable.h
Lib/dis.py
Lib/inspect.py
Lib/test/test_monitoring.py
Lib/test/test_opcache.py
Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-13-45-33.gh-issue-127274.deNxNC.rst [new file with mode: 0644]
Objects/funcobject.c
Python/compile.c
Python/symtable.c

index ca5dac87aff2b4c5a21d0f3916c744843ce7d578..0085207d3055f282c60e54cd19e1ce9cf968a681 100644 (file)
@@ -1708,6 +1708,13 @@ which is a bitmap of the following flags:
 
    .. versionadded:: 3.14
 
+.. data:: CO_METHOD
+
+   The flag is set when the code object is a function defined in class
+   scope.
+
+   .. versionadded:: 3.14
+
 .. note::
    The flags are specific to CPython, and may not be defined in other
    Python implementations.  Furthermore, the flags are an implementation
index c3c0165d556ead693cf6b4c3d828d8d86c56f656..cb6261ddde941b102d1ee322279bf47527f5cbf7 100644 (file)
@@ -199,6 +199,9 @@ struct PyCodeObject _PyCode_DEF(1);
 */
 #define CO_HAS_DOCSTRING 0x4000000
 
+/* A function defined in class scope */
+#define CO_METHOD  0x8000000
+
 /* This should be defined if a future statement modifies the syntax.
    For example, when a keyword is added.
 */
index 91dac767d5885b082ae2accacae375d8e0dd7eac..b7e274296112aada2baa2a1566cfc4809f6de2bb 100644 (file)
@@ -124,6 +124,7 @@ typedef struct _symtable_entry {
     unsigned ste_can_see_class_scope : 1; /* true if this block can see names bound in an
                                              enclosing class scope */
     unsigned ste_has_docstring : 1; /* true if docstring present */
+    unsigned ste_method : 1; /* true if block is a function block defined in class scope */
     int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
     _Py_SourceLocation ste_loc; /* source location of block */
     struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */
index aa22404c6687e165c363aacbdc254d69bab1bc6b..109c986bbe3d7d944ad3cc7a3eff9a31fba72143 100644 (file)
@@ -162,6 +162,7 @@ COMPILER_FLAG_NAMES = {
           256: "ITERABLE_COROUTINE",
           512: "ASYNC_GENERATOR",
     0x4000000: "HAS_DOCSTRING",
+    0x8000000: "METHOD",
 }
 
 def pretty_flags(flags):
index b7d8271f8a471f886c7f44b39f78d125d2166e93..5b7c4df8927c878a8bb30ad50dd36d20f1576e30 100644 (file)
@@ -57,6 +57,7 @@ __all__ = [
     "CO_VARARGS",
     "CO_VARKEYWORDS",
     "CO_HAS_DOCSTRING",
+    "CO_METHOD",
     "ClassFoundException",
     "ClosureVars",
     "EndOfBlock",
index 087ac8d456b84351a6345139c4777304dba4771c..32b3a6ac049e2891086007d995dabea57b56fddc 100644 (file)
@@ -850,12 +850,6 @@ class ReturnRecorder:
     def __call__(self, code, offset, val):
         self.events.append(("return", code.co_name, val))
 
-# gh-127274: CALL_ALLOC_AND_ENTER_INIT will only cache __init__ methods that
-# are deferred. We only defer functions defined at the top-level.
-class ValueErrorRaiser:
-    def __init__(self):
-        raise ValueError()
-
 
 class ExceptionMonitoringTest(CheckEvents):
 
@@ -1054,6 +1048,9 @@ class ExceptionMonitoringTest(CheckEvents):
 
     @requires_specialization_ft
     def test_no_unwind_for_shim_frame(self):
+        class ValueErrorRaiser:
+            def __init__(self):
+                raise ValueError()
 
         def f():
             try:
index ad0b0c487a411830adb98881660f8af6d3176dec..ba111b5117b41d7072d716d1e63918757bf97b4a 100644 (file)
@@ -493,13 +493,6 @@ class TestLoadMethodCache(unittest.TestCase):
             self.assertFalse(f())
 
 
-# gh-127274: CALL_ALLOC_AND_ENTER_INIT will only cache __init__ methods that
-# are deferred. We only defer functions defined at the top-level.
-class MyClass:
-    def __init__(self):
-        pass
-
-
 class InitTakesArg:
     def __init__(self, arg):
         self.arg = arg
@@ -536,6 +529,10 @@ class TestCallCache(TestBase):
     @disabling_optimizer
     @requires_specialization_ft
     def test_assign_init_code(self):
+        class MyClass:
+            def __init__(self):
+                pass
+
         def instantiate():
             return MyClass()
 
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-13-45-33.gh-issue-127274.deNxNC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-13-45-33.gh-issue-127274.deNxNC.rst
new file mode 100644 (file)
index 0000000..a4608fb
--- /dev/null
@@ -0,0 +1,3 @@
+Add a new flag, ``CO_METHOD``, to :attr:`~codeobject.co_flags` that
+indicates whether the code object belongs to a function defined in class
+scope.
index cca7f01498013ec45cf2da33e6bef541bd60c95a..7b17a9ba31fac4eaa244ae5f170bd3e71d41067c 100644 (file)
@@ -210,10 +210,14 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
     op->func_typeparams = NULL;
     op->vectorcall = _PyFunction_Vectorcall;
     op->func_version = FUNC_VERSION_UNSET;
-    if ((code_obj->co_flags & CO_NESTED) == 0) {
+    if (((code_obj->co_flags & CO_NESTED) == 0) ||
+        (code_obj->co_flags & CO_METHOD)) {
         // Use deferred reference counting for top-level functions, but not
         // nested functions because they are more likely to capture variables,
         // which makes prompt deallocation more important.
+        //
+        // Nested methods (functions defined in class scope) are also deferred,
+        // since they will likely be cleaned up by GC anyway.
         _PyObject_SetDeferredRefcount((PyObject *)op);
     }
     _PyObject_GC_TRACK(op);
index cbfba7f493e07d8c9eb5e8290e812044f2320376..ef470830336dde8012891da3127453530a01b25c 100644 (file)
@@ -1289,6 +1289,8 @@ compute_code_flags(compiler *c)
             flags |= CO_VARKEYWORDS;
         if (ste->ste_has_docstring)
             flags |= CO_HAS_DOCSTRING;
+        if (ste->ste_method)
+            flags |= CO_METHOD;
     }
 
     if (ste->ste_coroutine && !ste->ste_generator) {
index ebddb0b93fca0a6f5386222d2cfa85713f2360d3..49bd01ba68ac9ea28b51cf75e8d7b14a10b22450 100644 (file)
@@ -138,6 +138,13 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,
 
     ste->ste_has_docstring = 0;
 
+    ste->ste_method = 0;
+    if (st->st_cur != NULL &&
+        st->st_cur->ste_type == ClassBlock &&
+        block == FunctionBlock) {
+        ste->ste_method = 1;
+    }
+
     ste->ste_symbols = PyDict_New();
     ste->ste_varnames = PyList_New(0);
     ste->ste_children = PyList_New(0);