From 6ee879ffa1cbea78b0a0c4eff7e532fd6a2959ac Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 18 May 2026 08:37:12 +0200 Subject: [PATCH 1/3] gh-138325: steal list items in `INTRINSIC_LIST_TO_TUPLE` opcode (#149960) --- .../2026-05-17-12-00-00.gh-issue-138325.h2XSfe.rst | 3 +++ Python/intrinsics.c | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-17-12-00-00.gh-issue-138325.h2XSfe.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-17-12-00-00.gh-issue-138325.h2XSfe.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-17-12-00-00.gh-issue-138325.h2XSfe.rst new file mode 100644 index 000000000000000..0eb528c68d8d6f0 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-17-12-00-00.gh-issue-138325.h2XSfe.rst @@ -0,0 +1,3 @@ +Speed up converting a list to a tuple in the ``tuple(genexpr)`` fast path +and in starred tuple displays (e.g. ``(*a, *b)``) by stealing the list's +items into the tuple instead of copying them. diff --git a/Python/intrinsics.c b/Python/intrinsics.c index 9cfc285c6a59259..9f994950f2721db 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -7,6 +7,8 @@ #include "pycore_genobject.h" // _PyAsyncGenValueWrapperNew #include "pycore_interpframe.h" // _PyFrame_GetLocals() #include "pycore_intrinsics.h" // INTRINSIC_PRINT +#include "pycore_list.h" // _PyList_AsTupleAndClear() +#include "pycore_object.h" // _PyObject_IsUniquelyReferenced() #include "pycore_pyerrors.h" // _PyErr_SetString() #include "pycore_runtime.h" // _Py_ID() #include "pycore_typevarobject.h" // _Py_make_typevar() @@ -190,8 +192,12 @@ unary_pos(PyThreadState* unused, PyObject *value) static PyObject * list_to_tuple(PyThreadState* unused, PyObject *v) { - assert(PyList_Check(v)); - return PyTuple_FromArray(((PyListObject *)v)->ob_item, Py_SIZE(v)); + /* INTRINSIC_LIST_TO_TUPLE is only emitted by the compiler for a + freshly-built, uniquely-referenced temporary list, so steal its items + into the tuple instead of copying them. */ + assert(PyList_CheckExact(v)); + assert(_PyObject_IsUniquelyReferenced(v)); + return _PyList_AsTupleAndClear((PyListObject *)v); } static PyObject * From 18281db0d0350ceca2b74afb2c88ed644a737369 Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Mon, 18 May 2026 07:50:42 +0100 Subject: [PATCH 2/3] gh-144140: Optimize len for frozen dict/set constants in optimizer (#149969) --- Lib/test/test_capi/test_opt.py | 6 ++++-- Python/optimizer_bytecodes.c | 8 +++++++- Python/optimizer_cases.c.h | 11 ++++++----- Tools/cases_generator/analyzer.py | 3 +++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2f606c2c6eba2d6..790e965d6e5ff2f 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3154,7 +3154,7 @@ def testfunc(n): uops = get_opnames(ex) self.assertNotIn("_CHECK_IS_NOT_PY_CALLABLE_KW", uops) - def test_call_len_string(self): + def test_call_len_string_frozen_set_dict(self): def testfunc(n): for _ in range(n): _ = len("abc") @@ -3162,12 +3162,14 @@ def testfunc(n): _ = len(d) _ = len(b"def") _ = len(b"") + _ = len(FROZEN_SET_CONST) + _ = len(FROZEN_DICT_CONST) _, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) self.assertNotIn("_CALL_LEN", uops) - self.assertGreaterEqual(count_ops(ex, "_LOAD_CONST_INLINE_BORROW"), 8) + self.assertGreaterEqual(count_ops(ex, "_LOAD_CONST_INLINE_BORROW"), 10) def test_call_len_known_length_small_int(self): # Make sure that len(t) is optimized for a tuple of length 5. diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 96dbaea5a5797ef..c968185d77c3317 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -2378,7 +2378,7 @@ dummy_func(void) { res = sym_new_type(ctx, &PyLong_Type); Py_ssize_t length = sym_tuple_length(arg); - // Not a tuple, check if it's a const string + // Not a tuple, check if it's another immutable const with known length if (length < 0 && sym_is_const(ctx, arg)) { PyObject *const_val = sym_get_const(ctx, arg); if (const_val != NULL) { @@ -2388,6 +2388,12 @@ dummy_func(void) { else if (PyBytes_CheckExact(const_val)) { length = PyBytes_GET_SIZE(const_val); } + else if (PyFrozenDict_CheckExact(const_val)) { + length = PyDict_GET_SIZE(const_val); + } + else if (PyFrozenSet_CheckExact(const_val)) { + length = PySet_GET_SIZE(const_val); + } } } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index f336549d2ed2440..d52ebb9804197da 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -4636,12 +4636,13 @@ length = PyUnicode_GET_LENGTH(const_val); } else if (PyBytes_CheckExact(const_val)) { - CHECK_STACK_BOUNDS(-2); - stack_pointer[-3] = res; - stack_pointer += -2; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); length = PyBytes_GET_SIZE(const_val); - stack_pointer += 2; + } + else if (PyFrozenDict_CheckExact(const_val)) { + length = PyDict_GET_SIZE(const_val); + } + else if (PyFrozenSet_CheckExact(const_val)) { + length = PySet_GET_SIZE(const_val); } } } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 59bca201a947e35..6f0ddeaeaabf098 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -616,6 +616,9 @@ def has_error_without_pop(op: parser.CodeDef) -> bool: "PyStackRef_RefcountOnObject", "PyStackRef_TYPE", "PyStackRef_True", + "PyBytes_GET_SIZE", + "PyDict_GET_SIZE", + "PySet_GET_SIZE", "PyTuple_GET_ITEM", "PyTuple_GET_SIZE", "PyType_HasFeature", From 0ed497a350d76dd20de1a1689c84426c7c1d6e22 Mon Sep 17 00:00:00 2001 From: Nezuko Agent Date: Mon, 18 May 2026 15:28:28 +0700 Subject: [PATCH 3/3] gh-149953: Fix null pointer dereference order in `code_objects.c` (#149956) Move check before --- Modules/_remote_debugging/code_objects.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_remote_debugging/code_objects.c b/Modules/_remote_debugging/code_objects.c index 7b95c0f2d4fa8da..97c6ba772e88f1d 100644 --- a/Modules/_remote_debugging/code_objects.c +++ b/Modules/_remote_debugging/code_objects.c @@ -432,7 +432,7 @@ parse_code_object(RemoteUnwinderObject *unwinder, #ifdef Py_GIL_DISABLED // Handle thread-local bytecode (TLBC) in free threading builds - if (ctx->tlbc_index == 0 || unwinder->debug_offsets.code_object.co_tlbc == 0 || unwinder == NULL) { + if (ctx->tlbc_index == 0 || unwinder == NULL || unwinder->debug_offsets.code_object.co_tlbc == 0) { // No TLBC or no unwinder - use main bytecode directly addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive; goto done_tlbc;