From ad783989eaf57e0cd5f401e5e83420918ff32210 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Tue, 9 Dec 2025 23:18:48 +0300 Subject: [PATCH 1/9] Store chain_depth earlier and DECREF executor --- Python/optimizer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 9db894f0bf054a..f16b51790f3b92 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -185,12 +185,14 @@ _PyOptimizer_Optimize( else { executor->vm_data.code = NULL; } + executor->vm_data.chain_depth = chain_depth; + assert(executor->vm_data.valid); _PyExitData *exit = _tstate->jit_tracer_state.initial_state.exit; if (exit != NULL) { exit->executor = executor; + } else { + Py_DECREF(executor); } - executor->vm_data.chain_depth = chain_depth; - assert(executor->vm_data.valid); interp->compiling = false; return 1; #else From ae51767ac6e4e0207ef48f13ced79bb34cb4e82d Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Wed, 10 Dec 2025 07:03:35 +0300 Subject: [PATCH 2/9] Add comment regarding reference ownership --- Python/optimizer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/optimizer.c b/Python/optimizer.c index f16b51790f3b92..0078cbfb0b3cda 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -191,6 +191,8 @@ _PyOptimizer_Optimize( if (exit != NULL) { exit->executor = executor; } else { + // An executor inserted into the code object now has a strong reference + // to it from the code object. Thus, we don't need this reference anymore. Py_DECREF(executor); } interp->compiling = false; From bb950c21661202a63a9b63a7d4d569671e1c2501 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Thu, 11 Dec 2025 11:17:43 +0300 Subject: [PATCH 3/9] Log executor refcount and GC links --- Python/optimizer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Python/optimizer.c b/Python/optimizer.c index 07d7d4e33e1667..0a3d9646c7dfee 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -193,6 +193,13 @@ _PyOptimizer_Optimize( } else { // An executor inserted into the code object now has a strong reference // to it from the code object. Thus, we don't need this reference anymore. + PyGC_Head *gc = ((PyGC_Head*)executor) - 1; + printf("DEBUG: executor=%p, refcnt=%zd, gc_next=%p, gc_prev=%p\n", + (void*)executor, + Py_REFCNT(executor), + (void*)(gc->_gc_next), + (void*)(gc->_gc_prev)); + fflush(stdout); Py_DECREF(executor); } interp->compiling = false; From 29b4663fc269dcab63603ea057a3a8fbd9ece133 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Thu, 11 Dec 2025 12:34:43 +0300 Subject: [PATCH 4/9] Update optimizer.c --- Python/optimizer.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 0a3d9646c7dfee..07d7d4e33e1667 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -193,13 +193,6 @@ _PyOptimizer_Optimize( } else { // An executor inserted into the code object now has a strong reference // to it from the code object. Thus, we don't need this reference anymore. - PyGC_Head *gc = ((PyGC_Head*)executor) - 1; - printf("DEBUG: executor=%p, refcnt=%zd, gc_next=%p, gc_prev=%p\n", - (void*)executor, - Py_REFCNT(executor), - (void*)(gc->_gc_next), - (void*)(gc->_gc_prev)); - fflush(stdout); Py_DECREF(executor); } interp->compiling = false; From a166b47d89cf2751a45eb7c1f1970f38abce2500 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sat, 13 Dec 2025 00:27:28 +0300 Subject: [PATCH 5/9] fix --- Python/optimizer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 767a302079de94..5ada0fe8f37cbf 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -314,7 +314,11 @@ add_to_pending_deletion_list(_PyExecutorObject *self) static void uop_dealloc(PyObject *op) { _PyExecutorObject *self = _PyExecutorObject_CAST(op); - _PyObject_GC_UNTRACK(self); + + if (_PyObject_GC_IS_TRACKED(self)) { + _PyObject_GC_UNTRACK(self); + } + assert(self->vm_data.code == NULL); unlink_executor(self); // Once unlinked it becomes impossible to invalidate an executor, so do it here. From f3a763a10a4ddc40ed680a61fe2f7087443b92be Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sat, 13 Dec 2025 01:11:18 +0300 Subject: [PATCH 6/9] blurb add --- .../2025-12-13-01-11-03.gh-issue-142476.44Sp4N.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-13-01-11-03.gh-issue-142476.44Sp4N.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-13-01-11-03.gh-issue-142476.44Sp4N.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-13-01-11-03.gh-issue-142476.44Sp4N.rst new file mode 100644 index 00000000000000..eae1f3a1ce53b6 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-13-01-11-03.gh-issue-142476.44Sp4N.rst @@ -0,0 +1,2 @@ +Fix a memory leak in the experimental Tier 2 optimizer when creating +executors. Patched by Shamil Abdulaev. From 26c577d45eca9aeeb5f3b1e4c9fb58d3e185392e Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sat, 13 Dec 2025 19:53:36 +0300 Subject: [PATCH 7/9] add comment --- Python/optimizer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/optimizer.c b/Python/optimizer.c index 5ada0fe8f37cbf..53d063d5af1818 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -315,6 +315,8 @@ static void uop_dealloc(PyObject *op) { _PyExecutorObject *self = _PyExecutorObject_CAST(op); + // Object might be already untracked if we are in a GC cycle (via tp_clear). + // Avoid double-untracking. if (_PyObject_GC_IS_TRACKED(self)) { _PyObject_GC_UNTRACK(self); } From d8451d065cc4ac05fb1b6a5d90503446e9625bcb Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Fri, 19 Dec 2025 12:41:18 +0300 Subject: [PATCH 8/9] fix --- Python/optimizer.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index f99bad3fdb914a..d7f0559818f9ee 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -328,13 +328,6 @@ add_to_pending_deletion_list(_PyExecutorObject *self) static void uop_dealloc(PyObject *op) { _PyExecutorObject *self = _PyExecutorObject_CAST(op); - - // Object might be already untracked if we are in a GC cycle (via tp_clear). - // Avoid double-untracking. - if (_PyObject_GC_IS_TRACKED(self)) { - _PyObject_GC_UNTRACK(self); - } - executor_invalidate(op); assert(self->vm_data.code == NULL); add_to_pending_deletion_list(self); From 0b6ed2d56dcfdde1f2ddc1e5594fc44e22f7f1ef Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 19 Dec 2025 12:25:28 +0000 Subject: [PATCH 9/9] follow PEP 7 --- Python/optimizer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index d7f0559818f9ee..16abced6edbeec 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -190,7 +190,8 @@ _PyOptimizer_Optimize( _PyExitData *exit = _tstate->jit_tracer_state.initial_state.exit; if (exit != NULL) { exit->executor = executor; - } else { + } + else { // An executor inserted into the code object now has a strong reference // to it from the code object. Thus, we don't need this reference anymore. Py_DECREF(executor);