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. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 366bca44f83f16..e7220dc3469c8f 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); @@ -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 3fe7a3200269c3..f9d3f9b64090ad 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); @@ -4967,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__); @@ -4974,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 a1d5a98255c405..20c19a4f86d297 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); @@ -434,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;