diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index ba7bcb4540a451..674a05b1c6c9db 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1925,6 +1925,51 @@ def testfunc(n): self.assertNotIn("_GUARD_NOS_INT", uops) self.assertNotIn("_GUARD_TOS_INT", uops) + def test_get_len_with_const_tuple(self): + def testfunc(n): + x = "" + for _ in range(n): + match (1, 2, 3, 4): + case [_, _, _, _]: + x += "1" + return x + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(len(res), TIER2_THRESHOLD) + uops = get_opnames(ex) + self.assertNotIn("_GUARD_NOS_INT", uops) + self.assertIn("_GET_LEN", uops) + self.assertIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops) + + def test_get_len_with_non_const_tuple(self): + def testfunc(n): + x = "" + for _ in range(n): + match (object(), object()): + case [_, _]: + x += "1" + return x + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(len(res), TIER2_THRESHOLD) + uops = get_opnames(ex) + self.assertNotIn("_GUARD_NOS_INT", uops) + self.assertIn("_GET_LEN", uops) + self.assertIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops) + + def test_get_len_with_non_tuple(self): + def testfunc(n): + x = "" + for _ in range(n): + match [1, 2, 3, 4]: + case [_, _, _, _]: + x += "1" + return x + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(len(res), TIER2_THRESHOLD) + uops = get_opnames(ex) + self.assertNotIn("_GUARD_NOS_INT", uops) + self.assertIn("_GET_LEN", uops) + self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops) + def test_binary_op_subscr_tuple_int(self): def testfunc(n): x = 0 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-22-31-53.gh-issue-131798.fQ0ato.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-22-31-53.gh-issue-131798.fQ0ato.rst new file mode 100644 index 00000000000000..f322d43b30a9f3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-22-31-53.gh-issue-131798.fQ0ato.rst @@ -0,0 +1,2 @@ +Allow the JIT to remove int guards after ``_GET_LEN`` by setting the return +type to int. diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index e99421a3aff4ba..b9efb031311c02 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1088,6 +1088,22 @@ dummy_func(void) { res = sym_new_type(ctx, &PyLong_Type); } + op(_GET_LEN, (obj -- obj, len)) { + int tuple_length = sym_tuple_length(obj); + if (tuple_length == -1) { + len = sym_new_type(ctx, &PyLong_Type); + } + else { + assert(tuple_length >= 0); + PyObject *temp = PyLong_FromLong(tuple_length); + if (temp == NULL) { + goto error; + } + len = sym_new_const(ctx, temp); + Py_DECREF(temp); + } + } + op(_GUARD_CALLABLE_LEN, (callable, unused, unused -- callable, unused, unused)) { PyObject *len = _PyInterpreterState_GET()->callable_cache.len; if (sym_get_const(ctx, callable) == len) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 56b4b9983fbaaa..ef66f1437fa845 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1469,8 +1469,26 @@ } case _GET_LEN: { + JitOptSymbol *obj; JitOptSymbol *len; - len = sym_new_not_null(ctx); + obj = stack_pointer[-1]; + int tuple_length = sym_tuple_length(obj); + if (tuple_length == -1) { + len = sym_new_type(ctx, &PyLong_Type); + } + else { + assert(tuple_length >= 0); + PyObject *temp = PyLong_FromLong(tuple_length); + if (temp == NULL) { + goto error; + } + len = sym_new_const(ctx, temp); + stack_pointer[0] = len; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); + Py_DECREF(temp); + stack_pointer += -1; + } stack_pointer[0] = len; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS());