From 255762c09fe518757bb3e8ce1bb6e5d8eec9f466 Mon Sep 17 00:00:00 2001 From: mpage Date: Thu, 19 Dec 2024 13:03:14 -0800 Subject: [PATCH] gh-127274: Defer nested methods (#128012) 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 | 7 +++++++ Include/cpython/code.h | 3 +++ Include/internal/pycore_symtable.h | 1 + Lib/dis.py | 1 + Lib/inspect.py | 1 + Lib/test/test_monitoring.py | 9 +++------ Lib/test/test_opcache.py | 11 ++++------- .../2024-12-17-13-45-33.gh-issue-127274.deNxNC.rst | 3 +++ Objects/funcobject.c | 6 +++++- Python/compile.c | 2 ++ Python/symtable.c | 7 +++++++ 11 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-13-45-33.gh-issue-127274.deNxNC.rst diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index ca5dac87aff..0085207d305 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -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 diff --git a/Include/cpython/code.h b/Include/cpython/code.h index c3c0165d556..cb6261ddde9 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -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. */ diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 91dac767d58..b7e27429611 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -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 */ diff --git a/Lib/dis.py b/Lib/dis.py index aa22404c668..109c986bbe3 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -162,6 +162,7 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets 256: "ITERABLE_COROUTINE", 512: "ASYNC_GENERATOR", 0x4000000: "HAS_DOCSTRING", + 0x8000000: "METHOD", } def pretty_flags(flags): diff --git a/Lib/inspect.py b/Lib/inspect.py index b7d8271f8a4..5b7c4df8927 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -57,6 +57,7 @@ "CO_VARARGS", "CO_VARKEYWORDS", "CO_HAS_DOCSTRING", + "CO_METHOD", "ClassFoundException", "ClosureVars", "EndOfBlock", diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 087ac8d456b..32b3a6ac049 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -850,12 +850,6 @@ def __init__(self, events): 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 @@ def func(): @requires_specialization_ft def test_no_unwind_for_shim_frame(self): + class ValueErrorRaiser: + def __init__(self): + raise ValueError() def f(): try: diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index ad0b0c487a4..ba111b5117b 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -493,13 +493,6 @@ def f(): 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 @@ def f(x, y): @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 index 00000000000..a4608fbbbf1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-13-45-33.gh-issue-127274.deNxNC.rst @@ -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. diff --git a/Objects/funcobject.c b/Objects/funcobject.c index cca7f014980..7b17a9ba31f 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -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); diff --git a/Python/compile.c b/Python/compile.c index cbfba7f493e..ef470830336 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -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) { diff --git a/Python/symtable.c b/Python/symtable.c index ebddb0b93fc..49bd01ba68a 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -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);