From 798fcb057346cd211515207c4c0a57e3ded69661 Mon Sep 17 00:00:00 2001 From: Yi Yang Date: Thu, 22 Jan 2026 04:54:58 -0500 Subject: [PATCH 1/4] gh-144140: Optimize len for string constants in optimizer --- Lib/test/test_capi/test_opt.py | 14 ++++++++++++++ Python/optimizer_bytecodes.c | 19 ++++++++++++++++--- Python/optimizer_cases.c.h | 32 ++++++++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 79c7f530b8ae89..cc7f217e9a6c53 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2269,6 +2269,20 @@ def testfunc(n): self.assertNotIn("_GUARD_NOS_INT", uops) self.assertNotIn("_GUARD_TOS_INT", uops) self.assertIn("_POP_TOP_NOP", uops) + + def test_call_len_string(self): + def testfunc(n): + for _ in range(n): + _ = len("abc") + d = '' + _ = len(d) + + _, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + print(uops) + self.assertNotIn("_CALL_LEN", uops) + self.assertIn("_SHUFFLE_3_LOAD_CONST_INLINE_BORROW", uops) 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 1a64810b50a3a4..cd769479c22637 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1394,12 +1394,25 @@ dummy_func(void) { op(_CALL_LEN, (callable, null, arg -- res, a, c)) { res = sym_new_type(ctx, &PyLong_Type); - Py_ssize_t tuple_length = sym_tuple_length(arg); - if (tuple_length >= 0) { - PyObject *temp = PyLong_FromSsize_t(tuple_length); + PyObject *temp = NULL; + + Py_ssize_t length = sym_tuple_length(arg); + if (length >= 0) { + temp = PyLong_FromSsize_t(length); if (temp == NULL) { goto error; } + } + else if (sym_is_const(ctx, arg)) { + PyObject *const_val = sym_get_const(ctx, arg); + if (const_val != NULL && PyUnicode_CheckExact(const_val)) { + temp = PyLong_FromSsize_t(PyUnicode_GET_LENGTH(const_val)); + if (temp == NULL) { + goto error; + } + } + } + if (temp != NULL) { if (_Py_IsImmortal(temp)) { REPLACE_OP(this_instr, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index f3bc7213fcce3f..7d9f93fe02f6df 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3287,13 +3287,37 @@ stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); Py_DECREF(temp); - stack_pointer += 2; + } + else if (sym_is_const(ctx, arg)) { + PyObject *const_val = sym_get_const(ctx, arg); + if (const_val != NULL && PyUnicode_CheckExact(const_val)) { + Py_ssize_t length = PyUnicode_GET_LENGTH(const_val); + PyObject *temp = PyLong_FromSsize_t(length); + if (temp == NULL) { + goto error; + } + if (_Py_IsImmortal(temp)) { + REPLACE_OP(this_instr, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, + 0, (uintptr_t)temp); + } + res = sym_new_const(ctx, temp); + CHECK_STACK_BOUNDS(-2); + stack_pointer[-3] = res; + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + Py_DECREF(temp); + stack_pointer += 2; + } + stack_pointer += -2; } a = arg; c = callable; - stack_pointer[-3] = res; - stack_pointer[-2] = a; - stack_pointer[-1] = c; + CHECK_STACK_BOUNDS(2); + stack_pointer[-1] = res; + stack_pointer[0] = a; + stack_pointer[1] = c; + stack_pointer += 2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); break; } From d5ab2a20c5ef2651df7888dc1d44166f2357ca06 Mon Sep 17 00:00:00 2001 From: Yi Yang Date: Thu, 22 Jan 2026 05:56:46 -0500 Subject: [PATCH 2/4] whitespace; regen --- Lib/test/test_capi/test_opt.py | 2 +- Python/optimizer_cases.c.h | 50 +++++++++++++--------------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index cc7f217e9a6c53..3ff43cd7cd008a 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2269,7 +2269,7 @@ def testfunc(n): self.assertNotIn("_GUARD_NOS_INT", uops) self.assertNotIn("_GUARD_TOS_INT", uops) self.assertIn("_POP_TOP_NOP", uops) - + def test_call_len_string(self): def testfunc(n): for _ in range(n): diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 7d9f93fe02f6df..9985f01a476574 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3271,12 +3271,24 @@ arg = stack_pointer[-1]; callable = stack_pointer[-3]; res = sym_new_type(ctx, &PyLong_Type); - Py_ssize_t tuple_length = sym_tuple_length(arg); - if (tuple_length >= 0) { - PyObject *temp = PyLong_FromSsize_t(tuple_length); + PyObject *temp = NULL; + Py_ssize_t length = sym_tuple_length(arg); + if (length >= 0) { + temp = PyLong_FromSsize_t(length); if (temp == NULL) { goto error; } + } + else if (sym_is_const(ctx, arg)) { + PyObject *const_val = sym_get_const(ctx, arg); + if (const_val != NULL && PyUnicode_CheckExact(const_val)) { + temp = PyLong_FromSsize_t(PyUnicode_GET_LENGTH(const_val)); + if (temp == NULL) { + goto error; + } + } + } + if (temp != NULL) { if (_Py_IsImmortal(temp)) { REPLACE_OP(this_instr, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); @@ -3287,37 +3299,13 @@ stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); Py_DECREF(temp); - } - else if (sym_is_const(ctx, arg)) { - PyObject *const_val = sym_get_const(ctx, arg); - if (const_val != NULL && PyUnicode_CheckExact(const_val)) { - Py_ssize_t length = PyUnicode_GET_LENGTH(const_val); - PyObject *temp = PyLong_FromSsize_t(length); - if (temp == NULL) { - goto error; - } - if (_Py_IsImmortal(temp)) { - REPLACE_OP(this_instr, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, - 0, (uintptr_t)temp); - } - res = sym_new_const(ctx, temp); - CHECK_STACK_BOUNDS(-2); - stack_pointer[-3] = res; - stack_pointer += -2; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - Py_DECREF(temp); - stack_pointer += 2; - } - stack_pointer += -2; + stack_pointer += 2; } a = arg; c = callable; - CHECK_STACK_BOUNDS(2); - stack_pointer[-1] = res; - stack_pointer[0] = a; - stack_pointer[1] = c; - stack_pointer += 2; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + stack_pointer[-3] = res; + stack_pointer[-2] = a; + stack_pointer[-1] = c; break; } From adcc3b3aeba1719a72903c2e3678e1e1fe9bb1eb Mon Sep 17 00:00:00 2001 From: Yi Yang Date: Thu, 22 Jan 2026 21:46:33 -0500 Subject: [PATCH 3/4] obsolete print --- Lib/test/test_capi/test_opt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index c9ad7c9e393ed6..18a192bb1d8ca4 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2297,7 +2297,6 @@ def testfunc(n): _, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) - print(uops) self.assertNotIn("_CALL_LEN", uops) self.assertIn("_SHUFFLE_3_LOAD_CONST_INLINE_BORROW", uops) From 4f523dc4329e7502c723a1f922b5d907e9b081f7 Mon Sep 17 00:00:00 2001 From: Yi Yang Date: Fri, 23 Jan 2026 02:24:06 -0500 Subject: [PATCH 4/4] byte string --- Lib/test/test_capi/test_opt.py | 4 +++- Python/optimizer_bytecodes.c | 32 ++++++++++++++++---------------- Python/optimizer_cases.c.h | 33 ++++++++++++++++++--------------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 18a192bb1d8ca4..29b5da299e148c 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2293,12 +2293,14 @@ def testfunc(n): _ = len("abc") d = '' _ = len(d) + _ = len(b"def") + _ = len(b"") _, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) self.assertNotIn("_CALL_LEN", uops) - self.assertIn("_SHUFFLE_3_LOAD_CONST_INLINE_BORROW", uops) + self.assertEqual(count_ops(ex, "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW"), 4) 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 13f9acd50e3f38..82c1375d1d5200 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1417,28 +1417,28 @@ dummy_func(void) { op(_CALL_LEN, (callable, null, arg -- res, a, c)) { res = sym_new_type(ctx, &PyLong_Type); - PyObject *temp = NULL; - Py_ssize_t length = sym_tuple_length(arg); - if (length >= 0) { - temp = PyLong_FromSsize_t(length); - if (temp == NULL) { - goto error; - } - } - else if (sym_is_const(ctx, arg)) { + + // Not a tuple, check if it's a const string + if (length < 0 && sym_is_const(ctx, arg)) { PyObject *const_val = sym_get_const(ctx, arg); - if (const_val != NULL && PyUnicode_CheckExact(const_val)) { - temp = PyLong_FromSsize_t(PyUnicode_GET_LENGTH(const_val)); - if (temp == NULL) { - goto error; + if (const_val != NULL) { + if (PyUnicode_CheckExact(const_val)) { + length = PyUnicode_GET_LENGTH(const_val); + } + else if (PyBytes_CheckExact(const_val)) { + length = PyBytes_GET_SIZE(const_val); } } } - if (temp != NULL) { + + if (length >= 0) { + PyObject *temp = PyLong_FromSsize_t(length); + if (temp == NULL) { + goto error; + } if (_Py_IsImmortal(temp)) { - ADD_OP(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, - 0, (uintptr_t)temp); + ADD_OP(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); } res = sym_new_const(ctx, temp); Py_DECREF(temp); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index eafe06caa6aa82..ac7b8b6dd549d3 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3271,27 +3271,30 @@ arg = stack_pointer[-1]; callable = stack_pointer[-3]; res = sym_new_type(ctx, &PyLong_Type); - PyObject *temp = NULL; Py_ssize_t length = sym_tuple_length(arg); - if (length >= 0) { - temp = PyLong_FromSsize_t(length); - if (temp == NULL) { - goto error; - } - } - else if (sym_is_const(ctx, arg)) { + if (length < 0 && sym_is_const(ctx, arg)) { PyObject *const_val = sym_get_const(ctx, arg); - if (const_val != NULL && PyUnicode_CheckExact(const_val)) { - temp = PyLong_FromSsize_t(PyUnicode_GET_LENGTH(const_val)); - if (temp == NULL) { - goto error; + if (const_val != NULL) { + if (PyUnicode_CheckExact(const_val)) { + 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; } } } - if (temp != NULL) { + if (length >= 0) { + PyObject *temp = PyLong_FromSsize_t(length); + if (temp == NULL) { + goto error; + } if (_Py_IsImmortal(temp)) { - ADD_OP(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, - 0, (uintptr_t)temp); + ADD_OP(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); } res = sym_new_const(ctx, temp); CHECK_STACK_BOUNDS(-2);