diff options
author | Sam Gross <colesbury@gmail.com> | 2024-09-12 16:37:06 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-12 16:37:06 (GMT) |
commit | b2afe2aae487ebf89897e22c01d9095944fd334f (patch) | |
tree | 3ffa3ebfe3c69cd21968ce76d8d7cb2f325ff6d3 /Python | |
parent | 4ed7d1d6acc22807bfb5983c98fd59f7cb5061db (diff) | |
download | cpython-b2afe2aae487ebf89897e22c01d9095944fd334f.zip cpython-b2afe2aae487ebf89897e22c01d9095944fd334f.tar.gz cpython-b2afe2aae487ebf89897e22c01d9095944fd334f.tar.bz2 |
gh-123923: Defer refcounting for `f_executable` in `_PyInterpreterFrame` (#123924)
Use a `_PyStackRef` and defer the reference to `f_executable` when
possible. This avoids some reference count contention in the common case
of executing the same code object from multiple threads concurrently in
the free-threaded build.
Diffstat (limited to 'Python')
-rw-r--r-- | Python/bytecodes.c | 4 | ||||
-rw-r--r-- | Python/ceval.c | 6 | ||||
-rw-r--r-- | Python/executor_cases.c.h | 4 | ||||
-rw-r--r-- | Python/frame.c | 11 | ||||
-rw-r--r-- | Python/gc.c | 7 | ||||
-rw-r--r-- | Python/gc_free_threading.c | 137 | ||||
-rw-r--r-- | Python/generated_cases.c.h | 2 | ||||
-rw-r--r-- | Python/legacy_tracing.c | 3 | ||||
-rw-r--r-- | Python/tracemalloc.c | 4 |
9 files changed, 102 insertions, 76 deletions
diff --git a/Python/bytecodes.c b/Python/bytecodes.c index e8cf636..078f06d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3598,7 +3598,7 @@ dummy_func( op(_CREATE_INIT_FRAME, (self, init, args[oparg] -- init_frame: _PyInterpreterFrame *)) { _PyInterpreterFrame *shim = _PyFrame_PushTrampolineUnchecked( tstate, (PyCodeObject *)&_Py_InitCleanup, 1, frame); - assert(_PyCode_CODE((PyCodeObject *)shim->f_executable)[0].op.code == EXIT_INIT_CHECK); + assert(_PyCode_CODE(_PyFrame_GetCode(shim))[0].op.code == EXIT_INIT_CHECK); /* Push self onto stack of shim */ shim->localsplus[0] = PyStackRef_DUP(self); PyFunctionObject *init_func = (PyFunctionObject *)PyStackRef_AsPyObjectSteal(init); @@ -4798,7 +4798,7 @@ dummy_func( #endif _PyExecutorObject *executor; if (target->op.code == ENTER_EXECUTOR) { - PyCodeObject *code = (PyCodeObject *)frame->f_executable; + PyCodeObject *code = _PyFrame_GetCode(frame); executor = code->co_executors->executors[target->op.arg]; Py_INCREF(executor); } diff --git a/Python/ceval.c b/Python/ceval.c index 0ebd5bb..252833a 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -194,7 +194,7 @@ static void lltrace_resume_frame(_PyInterpreterFrame *frame) { PyObject *fobj = frame->f_funcobj; - if (!PyCode_Check(frame->f_executable) || + if (!PyStackRef_CodeCheck(frame->f_executable) || fobj == NULL || !PyFunction_Check(fobj) ) { @@ -784,7 +784,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int entry_frame.f_globals = (PyObject*)0xaaa3; entry_frame.f_builtins = (PyObject*)0xaaa4; #endif - entry_frame.f_executable = Py_None; + entry_frame.f_executable = PyStackRef_None; entry_frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1; entry_frame.stackpointer = entry_frame.localsplus; entry_frame.owner = FRAME_OWNED_BY_CSTACK; @@ -1681,7 +1681,7 @@ clear_thread_frame(PyThreadState *tstate, _PyInterpreterFrame * frame) tstate->c_recursion_remaining--; assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame); _PyFrame_ClearExceptCode(frame); - Py_DECREF(frame->f_executable); + PyStackRef_CLEAR(frame->f_executable); tstate->c_recursion_remaining++; _PyThreadState_PopFrame(tstate, frame); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index b9f532f..b36fff9 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -4143,7 +4143,7 @@ self = stack_pointer[-2 - oparg]; _PyInterpreterFrame *shim = _PyFrame_PushTrampolineUnchecked( tstate, (PyCodeObject *)&_Py_InitCleanup, 1, frame); - assert(_PyCode_CODE((PyCodeObject *)shim->f_executable)[0].op.code == EXIT_INIT_CHECK); + assert(_PyCode_CODE(_PyFrame_GetCode(shim))[0].op.code == EXIT_INIT_CHECK); /* Push self onto stack of shim */ shim->localsplus[0] = PyStackRef_DUP(self); PyFunctionObject *init_func = (PyFunctionObject *)PyStackRef_AsPyObjectSteal(init); @@ -5413,7 +5413,7 @@ #endif _PyExecutorObject *executor; if (target->op.code == ENTER_EXECUTOR) { - PyCodeObject *code = (PyCodeObject *)frame->f_executable; + PyCodeObject *code = _PyFrame_GetCode(frame); executor = code->co_executors->executors[target->op.arg]; Py_INCREF(executor); } diff --git a/Python/frame.c b/Python/frame.c index 3192968..d7bb298 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -14,7 +14,10 @@ _PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg) Py_VISIT(frame->frame_obj); Py_VISIT(frame->f_locals); Py_VISIT(frame->f_funcobj); - Py_VISIT(_PyFrame_GetCode(frame)); + int err = _PyGC_VisitStackRef(&frame->f_executable, visit, arg); + if (err) { + return err; + } return _PyGC_VisitFrameStack(frame, visit, arg); } @@ -53,10 +56,10 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame) assert(frame->owner != FRAME_OWNED_BY_FRAME_OBJECT); assert(frame->owner != FRAME_CLEARED); Py_ssize_t size = ((char*)frame->stackpointer) - (char *)frame; - Py_INCREF(_PyFrame_GetCode(frame)); memcpy((_PyInterpreterFrame *)f->_f_frame_data, frame, size); frame = (_PyInterpreterFrame *)f->_f_frame_data; frame->stackpointer = (_PyStackRef *)(((char *)frame) + size); + frame->f_executable = PyStackRef_DUP(frame->f_executable); f->f_frame = frame; frame->owner = FRAME_OWNED_BY_FRAME_OBJECT; if (_PyFrame_IsIncomplete(frame)) { @@ -131,9 +134,7 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) PyObject * PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame) { - PyObject *code = frame->f_executable; - Py_INCREF(code); - return code; + return PyStackRef_AsPyObjectNew(frame->f_executable); } int diff --git a/Python/gc.c b/Python/gc.c index 3d36792..024d041 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -535,6 +535,13 @@ visit_decref(PyObject *op, void *parent) } int +_PyGC_VisitStackRef(_PyStackRef *ref, visitproc visit, void *arg) +{ + Py_VISIT(PyStackRef_AsPyObjectBorrow(*ref)); + return 0; +} + +int _PyGC_VisitFrameStack(_PyInterpreterFrame *frame, visitproc visit, void *arg) { _PyStackRef *ref = _PyFrame_GetLocalsArray(frame); diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 54de0c2..e981f87 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -161,39 +161,6 @@ gc_decref(PyObject *op) op->ob_tid -= 1; } -static void -disable_deferred_refcounting(PyObject *op) -{ - if (!_PyObject_HasDeferredRefcount(op)) { - return; - } - - op->ob_gc_bits &= ~_PyGC_BITS_DEFERRED; - op->ob_ref_shared -= _Py_REF_SHARED(_Py_REF_DEFERRED, 0); - - if (PyType_Check(op)) { - // Disable thread-local refcounting for heap types - PyTypeObject *type = (PyTypeObject *)op; - if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - _PyType_ReleaseId((PyHeapTypeObject *)op); - } - } - else if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { - // Ensure any non-refcounted pointers in locals are converted to - // strong references. This ensures that the generator/coroutine is not - // freed before its locals. - PyGenObject *gen = (PyGenObject *)op; - struct _PyInterpreterFrame *frame = &gen->gi_iframe; - assert(frame->stackpointer != NULL); - for (_PyStackRef *ref = frame->localsplus; ref < frame->stackpointer; ref++) { - if (!PyStackRef_IsNull(*ref) && PyStackRef_IsDeferred(*ref)) { - // Convert a deferred reference to a strong reference. - *ref = PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(*ref)); - } - } - } -} - static Py_ssize_t merge_refcount(PyObject *op, Py_ssize_t extra) { @@ -214,6 +181,51 @@ merge_refcount(PyObject *op, Py_ssize_t extra) } static void +frame_disable_deferred_refcounting(_PyInterpreterFrame *frame) +{ + // Convert locals, variables, and the executable object to strong + // references from (possibly) deferred references. + assert(frame->stackpointer != NULL); + frame->f_executable = PyStackRef_AsStrongReference(frame->f_executable); + for (_PyStackRef *ref = frame->localsplus; ref < frame->stackpointer; ref++) { + if (!PyStackRef_IsNull(*ref) && PyStackRef_IsDeferred(*ref)) { + *ref = PyStackRef_AsStrongReference(*ref); + } + } +} + +static void +disable_deferred_refcounting(PyObject *op) +{ + if (_PyObject_HasDeferredRefcount(op)) { + op->ob_gc_bits &= ~_PyGC_BITS_DEFERRED; + op->ob_ref_shared -= _Py_REF_SHARED(_Py_REF_DEFERRED, 0); + merge_refcount(op, 0); + } + + // Heap types also use thread-local refcounting -- disable it here. + if (PyType_Check(op)) { + // Disable thread-local refcounting for heap types + PyTypeObject *type = (PyTypeObject *)op; + if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + _PyType_ReleaseId((PyHeapTypeObject *)op); + } + } + + // Generators and frame objects may contain deferred references to other + // objects. If the pointed-to objects are part of cyclic trash, we may + // have disabled deferred refcounting on them and need to ensure that we + // use strong references, in case the generator or frame object is + // resurrected by a finalizer. + if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { + frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe); + } + else if (PyFrame_Check(op)) { + frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame); + } +} + +static void gc_restore_tid(PyObject *op) { assert(_PyInterpreterState_GET()->stoptheworld.world_stopped); @@ -349,16 +361,18 @@ gc_visit_thread_stacks(PyInterpreterState *interp) { HEAD_LOCK(&_PyRuntime); for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { - _PyInterpreterFrame *f = p->current_frame; - while (f != NULL) { - if (f->f_executable != NULL && PyCode_Check(f->f_executable)) { - PyCodeObject *co = (PyCodeObject *)f->f_executable; - int max_stack = co->co_nlocalsplus + co->co_stacksize; - for (int i = 0; i < max_stack; i++) { - gc_visit_stackref(f->localsplus[i]); - } + for (_PyInterpreterFrame *f = p->current_frame; f != NULL; f = f->previous) { + PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable); + if (executable == NULL || !PyCode_Check(executable)) { + continue; + } + + PyCodeObject *co = (PyCodeObject *)executable; + int max_stack = co->co_nlocalsplus + co->co_stacksize; + gc_visit_stackref(f->f_executable); + for (int i = 0; i < max_stack; i++) { + gc_visit_stackref(f->localsplus[i]); } - f = f->previous; } } HEAD_UNLOCK(&_PyRuntime); @@ -623,23 +637,19 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, else { worklist_push(&state->unreachable, op); } + return true; } - else if (state->reason == _Py_GC_REASON_SHUTDOWN && - _PyObject_HasDeferredRefcount(op)) - { + + if (state->reason == _Py_GC_REASON_SHUTDOWN) { // Disable deferred refcounting for reachable objects as well during // interpreter shutdown. This ensures that these objects are collected // immediately when their last reference is removed. disable_deferred_refcounting(op); - merge_refcount(op, 0); - state->long_lived_total++; - } - else { - // object is reachable, restore `ob_tid`; we're done with these objects - gc_restore_tid(op); - state->long_lived_total++; } + // object is reachable, restore `ob_tid`; we're done with these objects + gc_restore_tid(op); + state->long_lived_total++; return true; } @@ -952,19 +962,28 @@ visit_decref_unreachable(PyObject *op, void *data) } int +_PyGC_VisitStackRef(_PyStackRef *ref, visitproc visit, void *arg) +{ + // This is a bit tricky! We want to ignore deferred references when + // computing the incoming references, but otherwise treat them like + // regular references. + if (!PyStackRef_IsDeferred(*ref) || + (visit != visit_decref && visit != visit_decref_unreachable)) + { + Py_VISIT(PyStackRef_AsPyObjectBorrow(*ref)); + } + return 0; +} + +int _PyGC_VisitFrameStack(_PyInterpreterFrame *frame, visitproc visit, void *arg) { _PyStackRef *ref = _PyFrame_GetLocalsArray(frame); /* locals and stack */ for (; ref < frame->stackpointer; ref++) { - // This is a bit tricky! We want to ignore deferred references when - // computing the incoming references, but otherwise treat them like - // regular references. - if (PyStackRef_IsDeferred(*ref) && - (visit == visit_decref || visit == visit_decref_unreachable)) { - continue; + if (_PyGC_VisitStackRef(ref, visit, arg) < 0) { + return -1; } - Py_VISIT(PyStackRef_AsPyObjectBorrow(*ref)); } return 0; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 09a14ee..3e9f039 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1018,7 +1018,7 @@ { _PyInterpreterFrame *shim = _PyFrame_PushTrampolineUnchecked( tstate, (PyCodeObject *)&_Py_InitCleanup, 1, frame); - assert(_PyCode_CODE((PyCodeObject *)shim->f_executable)[0].op.code == EXIT_INIT_CHECK); + assert(_PyCode_CODE(_PyFrame_GetCode(shim))[0].op.code == EXIT_INIT_CHECK); /* Push self onto stack of shim */ shim->localsplus[0] = PyStackRef_DUP(self); PyFunctionObject *init_func = (PyFunctionObject *)PyStackRef_AsPyObjectSteal(init); diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c index 9cc3af1..1436921 100644 --- a/Python/legacy_tracing.c +++ b/Python/legacy_tracing.c @@ -143,9 +143,8 @@ _PyEval_SetOpcodeTrace( bool enable ) { assert(frame != NULL); - assert(PyCode_Check(frame->f_frame->f_executable)); - PyCodeObject *code = (PyCodeObject *)frame->f_frame->f_executable; + PyCodeObject *code = _PyFrame_GetCode(frame->f_frame); _PyMonitoringEventSet events = 0; if (_PyMonitoring_GetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, &events) < 0) { diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c index e58b60d..f661d69 100644 --- a/Python/tracemalloc.c +++ b/Python/tracemalloc.c @@ -250,7 +250,7 @@ hashtable_compare_traceback(const void *key1, const void *key2) static void tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame) { - assert(PyCode_Check(pyframe->f_executable)); + assert(PyStackRef_CodeCheck(pyframe->f_executable)); frame->filename = &_Py_STR(anon_unknown); int lineno = PyUnstable_InterpreterFrame_GetLine(pyframe); if (lineno < 0) { @@ -258,7 +258,7 @@ tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame) } frame->lineno = (unsigned int)lineno; - PyObject *filename = filename = ((PyCodeObject *)pyframe->f_executable)->co_filename; + PyObject *filename = filename = _PyFrame_GetCode(pyframe)->co_filename; if (filename == NULL) { #ifdef TRACE_DEBUG |