Skip to content

Commit

Permalink
gh-127274: Defer nested methods (#128012)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mpage authored Dec 19, 2024
1 parent e163e8d commit 255762c
Show file tree
Hide file tree
Showing 11 changed files with 37 additions and 14 deletions.
7 changes: 7 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_symtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"CO_VARARGS",
"CO_VARKEYWORDS",
"CO_HAS_DOCSTRING",
"CO_METHOD",
"ClassFoundException",
"ClosureVars",
"EndOfBlock",
Expand Down
9 changes: 3 additions & 6 deletions Lib/test/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down Expand Up @@ -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:
Expand Down
11 changes: 4 additions & 7 deletions Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 5 additions & 1 deletion Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 255762c

Please sign in to comment.