diff options
-rw-r--r-- | Include/internal/pycore_uop_ids.h | 83 | ||||
-rw-r--r-- | Include/internal/pycore_uop_metadata.h | 4 | ||||
-rw-r--r-- | Lib/test/test_capi/test_opt.py | 12 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst | 2 | ||||
-rw-r--r-- | Python/bytecodes.c | 9 | ||||
-rw-r--r-- | Python/executor_cases.c.h | 34 | ||||
-rw-r--r-- | Python/optimizer_bytecodes.c | 12 | ||||
-rw-r--r-- | Python/optimizer_cases.c.h | 18 |
8 files changed, 121 insertions, 53 deletions
diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 481ec9307db..322f3cc093f 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -265,73 +265,74 @@ extern "C" { #define _MONITOR_JUMP_BACKWARD 481 #define _MONITOR_RESUME 482 #define _NOP NOP +#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW 483 #define _POP_EXCEPT POP_EXCEPT -#define _POP_JUMP_IF_FALSE 483 -#define _POP_JUMP_IF_TRUE 484 +#define _POP_JUMP_IF_FALSE 484 +#define _POP_JUMP_IF_TRUE 485 #define _POP_TOP POP_TOP -#define _POP_TOP_LOAD_CONST_INLINE 485 -#define _POP_TOP_LOAD_CONST_INLINE_BORROW 486 -#define _POP_TWO_LOAD_CONST_INLINE_BORROW 487 +#define _POP_TOP_LOAD_CONST_INLINE 486 +#define _POP_TOP_LOAD_CONST_INLINE_BORROW 487 +#define _POP_TWO_LOAD_CONST_INLINE_BORROW 488 #define _PUSH_EXC_INFO PUSH_EXC_INFO -#define _PUSH_FRAME 488 +#define _PUSH_FRAME 489 #define _PUSH_NULL PUSH_NULL -#define _PUSH_NULL_CONDITIONAL 489 -#define _PY_FRAME_GENERAL 490 -#define _PY_FRAME_KW 491 -#define _QUICKEN_RESUME 492 -#define _REPLACE_WITH_TRUE 493 +#define _PUSH_NULL_CONDITIONAL 490 +#define _PY_FRAME_GENERAL 491 +#define _PY_FRAME_KW 492 +#define _QUICKEN_RESUME 493 +#define _REPLACE_WITH_TRUE 494 #define _RESUME_CHECK RESUME_CHECK #define _RETURN_GENERATOR RETURN_GENERATOR #define _RETURN_VALUE RETURN_VALUE -#define _SAVE_RETURN_OFFSET 494 -#define _SEND 495 -#define _SEND_GEN_FRAME 496 +#define _SAVE_RETURN_OFFSET 495 +#define _SEND 496 +#define _SEND_GEN_FRAME 497 #define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE #define _SET_UPDATE SET_UPDATE -#define _START_EXECUTOR 497 -#define _STORE_ATTR 498 -#define _STORE_ATTR_INSTANCE_VALUE 499 -#define _STORE_ATTR_SLOT 500 -#define _STORE_ATTR_WITH_HINT 501 +#define _START_EXECUTOR 498 +#define _STORE_ATTR 499 +#define _STORE_ATTR_INSTANCE_VALUE 500 +#define _STORE_ATTR_SLOT 501 +#define _STORE_ATTR_WITH_HINT 502 #define _STORE_DEREF STORE_DEREF -#define _STORE_FAST 502 -#define _STORE_FAST_0 503 -#define _STORE_FAST_1 504 -#define _STORE_FAST_2 505 -#define _STORE_FAST_3 506 -#define _STORE_FAST_4 507 -#define _STORE_FAST_5 508 -#define _STORE_FAST_6 509 -#define _STORE_FAST_7 510 +#define _STORE_FAST 503 +#define _STORE_FAST_0 504 +#define _STORE_FAST_1 505 +#define _STORE_FAST_2 506 +#define _STORE_FAST_3 507 +#define _STORE_FAST_4 508 +#define _STORE_FAST_5 509 +#define _STORE_FAST_6 510 +#define _STORE_FAST_7 511 #define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST #define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME -#define _STORE_SLICE 511 -#define _STORE_SUBSCR 512 -#define _STORE_SUBSCR_DICT 513 -#define _STORE_SUBSCR_LIST_INT 514 +#define _STORE_SLICE 512 +#define _STORE_SUBSCR 513 +#define _STORE_SUBSCR_DICT 514 +#define _STORE_SUBSCR_LIST_INT 515 #define _SWAP SWAP -#define _TIER2_RESUME_CHECK 515 -#define _TO_BOOL 516 +#define _TIER2_RESUME_CHECK 516 +#define _TO_BOOL 517 #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT -#define _TO_BOOL_LIST 517 +#define _TO_BOOL_LIST 518 #define _TO_BOOL_NONE TO_BOOL_NONE -#define _TO_BOOL_STR 518 +#define _TO_BOOL_STR 519 #define _UNARY_INVERT UNARY_INVERT #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 519 -#define _UNPACK_SEQUENCE_LIST 520 -#define _UNPACK_SEQUENCE_TUPLE 521 -#define _UNPACK_SEQUENCE_TWO_TUPLE 522 +#define _UNPACK_SEQUENCE 520 +#define _UNPACK_SEQUENCE_LIST 521 +#define _UNPACK_SEQUENCE_TUPLE 522 +#define _UNPACK_SEQUENCE_TWO_TUPLE 523 #define _WITH_EXCEPT_START WITH_EXCEPT_START #define _YIELD_VALUE YIELD_VALUE -#define MAX_UOP_ID 522 +#define MAX_UOP_ID 523 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index d90b074b409..b334dd840c8 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -305,6 +305,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, [_POP_TOP_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_CHECK_FUNCTION] = HAS_DEOPT_FLAG, [_START_EXECUTOR] = 0, [_MAKE_WARM] = 0, @@ -553,6 +554,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_MAYBE_EXPAND_METHOD] = "_MAYBE_EXPAND_METHOD", [_MAYBE_EXPAND_METHOD_KW] = "_MAYBE_EXPAND_METHOD_KW", [_NOP] = "_NOP", + [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = "_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", [_POP_EXCEPT] = "_POP_EXCEPT", [_POP_TOP] = "_POP_TOP", [_POP_TOP_LOAD_CONST_INLINE] = "_POP_TOP_LOAD_CONST_INLINE", @@ -1192,6 +1194,8 @@ int _PyUop_num_popped(int opcode, int oparg) return 1; case _POP_TWO_LOAD_CONST_INLINE_BORROW: return 2; + case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW: + return 4; case _CHECK_FUNCTION: return 0; case _START_EXECUTOR: diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 305c53ffd81..f7a4200e1ee 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1955,9 +1955,10 @@ class TestUopsOptimization(unittest.TestCase): self.assertEqual(res, TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_CALL_ISINSTANCE", uops) self.assertNotIn("_GUARD_THIRD_NULL", uops) self.assertNotIn("_GUARD_CALLABLE_ISINSTANCE", uops) + self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) def test_call_list_append(self): def testfunc(n): @@ -1987,9 +1988,10 @@ class TestUopsOptimization(unittest.TestCase): self.assertEqual(res, TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_CALL_ISINSTANCE", uops) self.assertNotIn("_TO_BOOL_BOOL", uops) self.assertNotIn("_GUARD_IS_TRUE_POP", uops) + self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) def test_call_isinstance_is_false(self): def testfunc(n): @@ -2004,9 +2006,10 @@ class TestUopsOptimization(unittest.TestCase): self.assertEqual(res, TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_CALL_ISINSTANCE", uops) self.assertNotIn("_TO_BOOL_BOOL", uops) self.assertNotIn("_GUARD_IS_FALSE_POP", uops) + self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) def test_call_isinstance_subclass(self): def testfunc(n): @@ -2021,9 +2024,10 @@ class TestUopsOptimization(unittest.TestCase): self.assertEqual(res, TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_CALL_ISINSTANCE", uops) + self.assertNotIn("_CALL_ISINSTANCE", uops) self.assertNotIn("_TO_BOOL_BOOL", uops) self.assertNotIn("_GUARD_IS_TRUE_POP", uops) + self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) def test_call_isinstance_unknown_object(self): def testfunc(n): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst new file mode 100644 index 00000000000..98d770cf054 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst @@ -0,0 +1,2 @@ +Add ``_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW`` and use it to further +optimize ``CALL_ISINSTANCE``. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index c10327916fc..477a84b170a 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -5342,6 +5342,15 @@ dummy_func( value = PyStackRef_FromPyObjectImmortal(ptr); } + tier2 pure op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, pop1, pop2 -- value)) { + PyStackRef_CLOSE(pop2); + PyStackRef_CLOSE(pop1); + (void)null; // Silence compiler warnings about unused variables + DEAD(null); + PyStackRef_CLOSE(callable); + value = PyStackRef_FromPyObjectImmortal(ptr); + } + tier2 op(_CHECK_FUNCTION, (func_version/2 -- )) { assert(PyStackRef_FunctionCheck(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(frame->f_funcobj); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index c5e481932e7..c3da7a63b9e 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -7103,6 +7103,40 @@ break; } + case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW: { + _PyStackRef pop2; + _PyStackRef pop1; + _PyStackRef null; + _PyStackRef callable; + _PyStackRef value; + pop2 = stack_pointer[-1]; + pop1 = stack_pointer[-2]; + null = stack_pointer[-3]; + callable = stack_pointer[-4]; + PyObject *ptr = (PyObject *)CURRENT_OPERAND0(); + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(pop2); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(pop1); + stack_pointer = _PyFrame_GetStackPointer(frame); + (void)null; + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(callable); + stack_pointer = _PyFrame_GetStackPointer(frame); + value = PyStackRef_FromPyObjectImmortal(ptr); + stack_pointer[0] = value; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); + break; + } + case _CHECK_FUNCTION: { uint32_t func_version = (uint32_t)CURRENT_OPERAND0(); assert(PyStackRef_FunctionCheck(frame->f_funcobj)); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 708b436f6e4..9811e9f348c 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -550,6 +550,10 @@ dummy_func(void) { value = sym_new_const(ctx, ptr); } + op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused, unused, unused -- value)) { + value = sym_new_const(ctx, ptr); + } + op(_COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) { assert(oparg > 0); top = bottom; @@ -901,12 +905,12 @@ dummy_func(void) { // 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)) { - sym_set_const(res, Py_True); - } - else { - sym_set_const(res, Py_False); + out = Py_True; } + sym_set_const(res, out); + REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out); } } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 3ac99e5c1b1..6e19319fbae 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2143,12 +2143,12 @@ PyTypeObject *inst_type = sym_get_type(instance); PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, cls); if (inst_type && cls_o && sym_matches_type(cls, &PyType_Type)) { + PyObject *out = Py_False; if (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o)) { - sym_set_const(res, Py_True); - } - else { - sym_set_const(res, Py_False); + out = Py_True; } + sym_set_const(res, out); + REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out); } stack_pointer[-4] = res; stack_pointer += -3; @@ -2556,6 +2556,16 @@ break; } + case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW: { + JitOptSymbol *value; + PyObject *ptr = (PyObject *)this_instr->operand0; + value = sym_new_const(ctx, ptr); + stack_pointer[-4] = value; + stack_pointer += -3; + assert(WITHIN_STACK_BOUNDS()); + break; + } + case _CHECK_FUNCTION: { break; } |