diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 7be1c9eebb3bf9..0409a0709048c8 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2191,17 +2191,154 @@ def testfunc(n): self.assertNotIn("_TO_BOOL_BOOL", uops) self.assertIn("_GUARD_IS_TRUE_POP", uops) - def test_call_isinstance_tuple_of_classes(self): + def test_call_isinstance_tuple_of_classes_is_true(self): def testfunc(n): x = 0 for _ in range(n): - # A tuple of classes is currently not optimized, - # so this is only narrowed to bool: y = isinstance(42, (int, str)) if y: x += 1 return x + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_TO_BOOL_BOOL", uops) + self.assertNotIn("_GUARD_IS_TRUE_POP", uops) + self.assertIn("_BUILD_TUPLE", uops) + self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) + + def test_call_isinstance_tuple_of_classes_is_false(self): + def testfunc(n): + x = 0 + for _ in range(n): + y = isinstance(42, (bool, str)) + if not y: + x += 1 + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_TO_BOOL_BOOL", uops) + self.assertNotIn("_GUARD_IS_FALSE_POP", uops) + self.assertIn("_BUILD_TUPLE", uops) + self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) + + def test_call_isinstance_tuple_of_classes_true_unknown_1(self): + def testfunc(n): + x = 0 + for _ in range(n): + # One of the classes is unknown, but it comes + # after a known class, so we can narrow to True and + # remove the isinstance call. + y = isinstance(42, (int, eval('str'))) + if y: + x += 1 + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_TO_BOOL_BOOL", uops) + self.assertNotIn("_GUARD_IS_TRUE_POP", uops) + self.assertIn("_BUILD_TUPLE", uops) + self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) + + def test_call_isinstance_tuple_of_classes_true_unknown_2(self): + def testfunc(n): + x = 0 + for _ in range(n): + # We can narrow to True, but since the unknown class comes + # first and could potentially trigger an __instancecheck__, + # we can't remove the isinstance call. + y = isinstance(42, (eval('str'), int)) + if y: + x += 1 + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_TO_BOOL_BOOL", uops) + self.assertNotIn("_GUARD_IS_TRUE_POP", uops) + + def test_call_isinstance_tuple_of_classes_true_unknown_3(self): + def testfunc(n): + x = 0 + for _ in range(n): + # We can only narrow to bool here + y = isinstance(42, (str, eval('int'))) + if y: + x += 1 + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_TO_BOOL_BOOL", uops) + self.assertIn("_GUARD_IS_TRUE_POP", uops) + + def test_call_isinstance_tuple_of_classes_true_unknown_4(self): + def testfunc(n): + x = 0 + for _ in range(n): + # We can only narrow to bool here + y = isinstance(42, (eval('int'), str)) + if y: + x += 1 + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_TO_BOOL_BOOL", uops) + self.assertIn("_GUARD_IS_TRUE_POP", uops) + + def test_call_isinstance_empty_tuple(self): + def testfunc(n): + x = 0 + for _ in range(n): + y = isinstance(42, ()) + if not y: + x += 1 + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_TO_BOOL_BOOL", uops) + self.assertNotIn("_GUARD_IS_FALSE_POP", uops) + self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops) + self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops) + self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops) + self.assertNotIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) + + def test_call_isinstance_tuple_unknown_length(self): + def testfunc(n): + x = 0 + for _ in range(n): + # tuple with an unknown length, we only narrow to bool + tup = tuple(eval('(int, str)')) + y = isinstance(42, tup) + if y: + x += 1 + return x + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) self.assertEqual(res, TIER2_THRESHOLD) self.assertIsNotNone(ex) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-22-19-56.gh-issue-131798.kxRt1-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-22-19-56.gh-issue-131798.kxRt1-.rst new file mode 100644 index 00000000000000..66295ebecdf18f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-22-19-56.gh-issue-131798.kxRt1-.rst @@ -0,0 +1,2 @@ +Optimize ``_CALL_ISINSTANCE`` in the JIT when the second argument is a tuple +of classes. diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index aeff76affd8ace..cc4a816c9843fb 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -956,7 +956,6 @@ dummy_func(void) { // isinstance(inst, cls) where both inst and cls have // known types, meaning we can deduce either True or False - // The below check is equivalent to PyObject_TypeCheck(inst, cls) PyObject *out = Py_False; if (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o)) { out = Py_True; @@ -964,6 +963,45 @@ dummy_func(void) { sym_set_const(res, out); REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out); } + else if (inst_type && sym_matches_type(cls, &PyTuple_Type)) { + // isinstance(inst, tup) where inst has a known type and tup is a tuple. + // We can deduce True if inst is an instance of at least one of + // the items in the tuple. + // We can deduce False if all items in the tuple have known types and + // inst is not an instance of any of them. + + int length = sym_tuple_length(cls); + if (length != -1) { + // We cannot do anything about tuples with unknown length + bool can_replace_op = true; + PyObject *out = Py_False; + for (int i = 0; i < length; i++) { + JitOptRef item = sym_tuple_getitem(ctx, cls, i); + if (!sym_has_type(item)) { + // There is an unknown item in the tuple. + // It could potentially define its own __instancecheck__ + // so it is no longer possible to replace the op with a const load. + out = NULL; + can_replace_op = false; + continue; + } + PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, item); + if (cls_o && + sym_matches_type(item, &PyType_Type) && + (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o))) + { + out = Py_True; + break; + } + } + if (out) { + sym_set_const(res, out); + if (can_replace_op) { + REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out); + } + } + } + } } op(_GUARD_IS_TRUE_POP, (flag -- )) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 41402200c1683e..48108b8471c33e 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2514,6 +2514,35 @@ sym_set_const(res, out); REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out); } + else if (inst_type && sym_matches_type(cls, &PyTuple_Type)) { + int length = sym_tuple_length(cls); + if (length != -1) { + bool can_replace_op = true; + PyObject *out = Py_False; + for (int i = 0; i < length; i++) { + JitOptRef item = sym_tuple_getitem(ctx, cls, i); + if (!sym_has_type(item)) { + out = NULL; + can_replace_op = false; + continue; + } + PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, item); + if (cls_o && + sym_matches_type(item, &PyType_Type) && + (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o))) + { + out = Py_True; + break; + } + } + if (out) { + sym_set_const(res, out); + if (can_replace_op) { + REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out); + } + } + } + } stack_pointer[-4] = res; stack_pointer += -3; assert(WITHIN_STACK_BOUNDS()); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy