From cc46500742bad60f3a99beda9e0640fb578773b1 Mon Sep 17 00:00:00 2001 From: cocolato Date: Thu, 7 May 2026 11:12:18 +0800 Subject: [PATCH 1/3] fix _LOAD_SPECIAL deopt --- Lib/test/test_capi/test_opt.py | 14 ++++++++++++++ Python/optimizer_bytecodes.c | 9 +++++++++ Python/optimizer_cases.c.h | 5 +++++ 3 files changed, 28 insertions(+) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index d80fec9a8a0d2b..839e1143852f7f 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -6137,6 +6137,20 @@ def __init__(self, x): C(0) if i else str(0) """)) + def test_load_special_type_guard_deopt(self): + script_helper.assert_python_ok("-s", "-c", textwrap.dedent(f""" + def f1(): + class Context: + def __enter__(self): ... + def __exit__(self, e, v, t): ... + + with Context(): + pass + + for _ in range({TIER2_THRESHOLD + 5}): + f1() + """), PYTHON_JIT="1") + def global_identity(x): return x diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index e10a096baa3318..c14171c840edec 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -2041,7 +2041,16 @@ dummy_func(void) { PyObject *name = _Py_SpecialMethods[oparg].name; PyObject *descr = _PyType_Lookup(type, name); if (descr != NULL && (Py_TYPE(descr)->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR)) { + /* LOAD_SPECIAL expands to _RECORD_TOS_TYPE + _INSERT_NULL + + * _LOAD_SPECIAL. Emit _GUARD_TYPE_VERSION before _INSERT_NULL + * so deopt sees the original stack shape. */ + assert(uop_buffer_last(&ctx->out_buffer)->opcode == _INSERT_NULL); + assert(uop_buffer_last(&ctx->out_buffer)->target == this_instr->target); + _PyUOpInstruction insert_null = *uop_buffer_last(&ctx->out_buffer); + ctx->out_buffer.next--; ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + *(ctx->out_buffer.next++) = insert_null; + bool immortal = _Py_IsImmortal(descr) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)descr); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 01ecb3790aa2cd..e41e113f4e09c5 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3895,7 +3895,12 @@ PyObject *name = _Py_SpecialMethods[oparg].name; PyObject *descr = _PyType_Lookup(type, name); if (descr != NULL && (Py_TYPE(descr)->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR)) { + assert(uop_buffer_last(&ctx->out_buffer)->opcode == _INSERT_NULL); + assert(uop_buffer_last(&ctx->out_buffer)->target == this_instr->target); + _PyUOpInstruction insert_null = *uop_buffer_last(&ctx->out_buffer); + ctx->out_buffer.next--; ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + *(ctx->out_buffer.next++) = insert_null; bool immortal = _Py_IsImmortal(descr) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)descr); From 05b8b15484389d7806e94bfb2ea168550b03e1b3 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 03:19:02 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-05-07-03-18-59.gh-issue-149459.5fhAqP.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-07-03-18-59.gh-issue-149459.5fhAqP.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-07-03-18-59.gh-issue-149459.5fhAqP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-07-03-18-59.gh-issue-149459.5fhAqP.rst new file mode 100644 index 00000000000000..4cd0a148df3c70 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-07-03-18-59.gh-issue-149459.5fhAqP.rst @@ -0,0 +1 @@ +Fix a crash in the JIT optimizer when a specialized ``LOAD_SPECIAL`` guard deoptimized after inserting the synthetic ``NULL`` stack entry. From 1229b80f09c48e379a0563f72a175598c803373b Mon Sep 17 00:00:00 2001 From: cocolato Date: Thu, 7 May 2026 13:06:28 +0800 Subject: [PATCH 3/3] replace with REPLACE_OP --- Python/optimizer_bytecodes.c | 16 ++++++++-------- Python/optimizer_cases.c.h | 11 +++++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index c14171c840edec..76020c66c7be58 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -2042,14 +2042,14 @@ dummy_func(void) { PyObject *descr = _PyType_Lookup(type, name); if (descr != NULL && (Py_TYPE(descr)->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR)) { /* LOAD_SPECIAL expands to _RECORD_TOS_TYPE + _INSERT_NULL + - * _LOAD_SPECIAL. Emit _GUARD_TYPE_VERSION before _INSERT_NULL - * so deopt sees the original stack shape. */ - assert(uop_buffer_last(&ctx->out_buffer)->opcode == _INSERT_NULL); - assert(uop_buffer_last(&ctx->out_buffer)->target == this_instr->target); - _PyUOpInstruction insert_null = *uop_buffer_last(&ctx->out_buffer); - ctx->out_buffer.next--; - ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); - *(ctx->out_buffer.next++) = insert_null; + * _LOAD_SPECIAL. Insert _GUARD_TYPE_VERSION before the + * already-emitted _INSERT_NULL so deopt sees the original + * stack shape.*/ + _PyUOpInstruction *insert_null = uop_buffer_last(&ctx->out_buffer); + assert(insert_null->opcode == _INSERT_NULL); + assert(insert_null->target == this_instr->target); + REPLACE_OP(insert_null, _GUARD_TYPE_VERSION, 0, type->tp_version_tag); + ADD_OP(_INSERT_NULL, 0, 0); bool immortal = _Py_IsImmortal(descr) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index e41e113f4e09c5..5b803b0ac0d241 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3895,12 +3895,11 @@ PyObject *name = _Py_SpecialMethods[oparg].name; PyObject *descr = _PyType_Lookup(type, name); if (descr != NULL && (Py_TYPE(descr)->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR)) { - assert(uop_buffer_last(&ctx->out_buffer)->opcode == _INSERT_NULL); - assert(uop_buffer_last(&ctx->out_buffer)->target == this_instr->target); - _PyUOpInstruction insert_null = *uop_buffer_last(&ctx->out_buffer); - ctx->out_buffer.next--; - ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); - *(ctx->out_buffer.next++) = insert_null; + _PyUOpInstruction *insert_null = uop_buffer_last(&ctx->out_buffer); + assert(insert_null->opcode == _INSERT_NULL); + assert(insert_null->target == this_instr->target); + REPLACE_OP(insert_null, _GUARD_TYPE_VERSION, 0, type->tp_version_tag); + ADD_OP(_INSERT_NULL, 0, 0); bool immortal = _Py_IsImmortal(descr) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)descr);