From 2ffae6f6ca1f1369537a2c1c4f571612c3f66a3f Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sun, 4 Jan 2026 00:02:07 +0000 Subject: [PATCH 1/3] Check for unique reference for _BINARY_OP_INPLACE_ADD_UNICODE --- Python/bytecodes.c | 10 +++++----- Python/executor_cases.c.h | 7 +++++++ Python/generated_cases.c.h | 5 +++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 366bca44f83f16..c328c0504e9f25 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -788,17 +788,17 @@ dummy_func( _PyStackRef *target_local = &GETLOCAL(next_oparg); assert(PyUnicode_CheckExact(left_o)); DEOPT_IF(PyStackRef_AsPyObjectBorrow(*target_local) != left_o); + /* gh-143401: The local should be uniquely referenced to modify this in-place. + * This check is required as 3.14 no longer creates new references when pushing + * values to the stack in some cases. + */ + DEOPT_IF(!_PyObject_IsUniquelyReferenced(PyStackRef_AsPyObjectBorrow(*target_local))); STAT_INC(BINARY_OP, hit); /* Handle `left = left + right` or `left += right` for str. * * When possible, extend `left` in place rather than * allocating a new PyUnicodeObject. This attempts to avoid * quadratic behavior when one neglects to use str.join(). - * - * If `left` has only two references remaining (one from - * the stack, one in the locals), DECREFing `left` leaves - * only the locals reference, so PyUnicode_Append knows - * that the string is safe to mutate. */ assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left)); PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 3fe7a3200269c3..10cc0e1d91df21 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -4954,6 +4954,13 @@ SET_CURRENT_CACHED_VALUES(2); JUMP_TO_JUMP_TARGET(); } + if (!_PyObject_IsUniquelyReferenced(PyStackRef_AsPyObjectBorrow(*target_local))) { + UOP_STAT_INC(uopcode, miss); + _tos_cache1 = right; + _tos_cache0 = left; + SET_CURRENT_CACHED_VALUES(2); + JUMP_TO_JUMP_TARGET(); + } STAT_INC(BINARY_OP, hit); assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left)); PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a1d5a98255c405..a213508facdc97 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -425,6 +425,11 @@ assert(_PyOpcode_Deopt[opcode] == (BINARY_OP)); JUMP_TO_PREDICTED(BINARY_OP); } + if (!_PyObject_IsUniquelyReferenced(PyStackRef_AsPyObjectBorrow(*target_local))) { + UPDATE_MISS_STATS(BINARY_OP); + assert(_PyOpcode_Deopt[opcode] == (BINARY_OP)); + JUMP_TO_PREDICTED(BINARY_OP); + } STAT_INC(BINARY_OP, hit); assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left)); PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); From b65ae8e689228ec0eb79c2b3cd048d4c91b6a226 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sun, 4 Jan 2026 00:03:42 +0000 Subject: [PATCH 2/3] move error case up --- Python/bytecodes.c | 2 +- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index c328c0504e9f25..e7220dc3469c8f 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -808,9 +808,9 @@ dummy_func( DEAD(right); PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); DEAD(left); + *target_local = PyStackRef_NULL; ERROR_IF(temp == NULL); res = PyStackRef_FromPyObjectSteal(temp); - *target_local = PyStackRef_NULL; } op(_GUARD_BINARY_OP_EXTEND, (descr/4, left, right -- left, right)) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 10cc0e1d91df21..f9d3f9b64090ad 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -4974,6 +4974,7 @@ stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); + *target_local = PyStackRef_NULL; if (temp == NULL) { stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); @@ -4981,7 +4982,6 @@ JUMP_TO_ERROR(); } res = PyStackRef_FromPyObjectSteal(temp); - *target_local = PyStackRef_NULL; _tos_cache0 = res; _tos_cache1 = PyStackRef_ZERO_BITS; _tos_cache2 = PyStackRef_ZERO_BITS; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a213508facdc97..20c19a4f86d297 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -439,11 +439,11 @@ stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); + *target_local = PyStackRef_NULL; if (temp == NULL) { JUMP_TO_LABEL(pop_2_error); } res = PyStackRef_FromPyObjectSteal(temp); - *target_local = PyStackRef_NULL; } stack_pointer[-2] = res; stack_pointer += -1; From cb87293ff30b33f7f7cb8f7fdb172391b2442a7d Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:05:01 +0000 Subject: [PATCH 3/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-01-04-00-04-53.gh-issue-143401.m8lb4b.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-01-04-00-04-53.gh-issue-143401.m8lb4b.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-04-00-04-53.gh-issue-143401.m8lb4b.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-04-00-04-53.gh-issue-143401.m8lb4b.rst new file mode 100644 index 00000000000000..39060b52c4dc06 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-04-00-04-53.gh-issue-143401.m8lb4b.rst @@ -0,0 +1 @@ +Fix an incorrect optimization regarding string concatenation.