diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index bc2114c9a85493..edbc2c418ecb4b 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1687,6 +1687,49 @@ def testfunc(n): self.assertIn("_CHECK_FUNCTION_VERSION_INLINE", uops) self.assertNotIn("_CHECK_METHOD_VERSION", uops) + def test_record_bound_method_general(self): + class MyClass: + def method(self, *args): + return args[0] + 1 + + def testfunc(n): + obj = MyClass() + bound = obj.method + result = 0 + for i in range(n): + result += bound(i) + return result + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual( + res, sum(i + 1 for i in range(TIER2_THRESHOLD)) + ) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_PUSH_FRAME", uops) + + def test_record_bound_method_exact_args(self): + class MyClass: + def method(self, x): + return x + 1 + + def testfunc(n): + obj = MyClass() + bound = obj.method + result = 0 + for i in range(n): + result += bound(i) + return result + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual( + res, sum(i + 1 for i in range(TIER2_THRESHOLD)) + ) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_PUSH_FRAME", uops) + self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops) + def test_jit_error_pops(self): """ Tests that the correct number of pops are inserted into the diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 34e3885a93c8bd..63c3012bd34681 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -6151,8 +6151,7 @@ dummy_func( tier2 op(_RECORD_BOUND_METHOD, (callable, self, args[oparg] -- callable, self, args[oparg])) { PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable); if (Py_TYPE(callable_o) == &PyMethod_Type) { - PyObject *func = ((PyMethodObject *)callable_o)->im_func; - RECORD_VALUE(func); + RECORD_VALUE(callable_o); } } diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index fd1dfa017de4c4..3c9f0c13413650 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -996,8 +996,18 @@ dummy_func(void) { } op(_INIT_CALL_BOUND_METHOD_EXACT_ARGS, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { - callable = sym_new_not_null(ctx); - self_or_null = sym_new_not_null(ctx); + PyObject *bound_method = sym_get_probable_value(callable); + if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) { + PyMethodObject *method = (PyMethodObject *)bound_method; + callable = sym_new_not_null(ctx); + sym_set_recorded_value(callable, method->im_func); + self_or_null = sym_new_not_null(ctx); + sym_set_recorded_value(self_or_null, method->im_self); + } + else { + callable = sym_new_not_null(ctx); + self_or_null = sym_new_not_null(ctx); + } } op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { @@ -1019,6 +1029,19 @@ dummy_func(void) { ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version); uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)method->im_func; } + else { + // Guarding on the bound method, safe to promote. + PyObject *bound_method = sym_get_probable_value(callable); + if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) { + PyMethodObject *method = (PyMethodObject *)bound_method; + PyObject *func = method->im_func; + if (PyFunction_Check(func) && + ((PyFunctionObject *)func)->func_version == func_version) { + _Py_BloomFilter_Add(dependencies, func); + sym_set_const(callable, bound_method); + } + } + } sym_set_type(callable, &PyMethod_Type); } @@ -1057,6 +1080,18 @@ dummy_func(void) { } } + op(_EXPAND_METHOD, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { + if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyMethod_Type)) { + PyMethodObject *method = (PyMethodObject *)sym_get_const(ctx, callable); + callable = sym_new_const(ctx, method->im_func); + self_or_null = sym_new_const(ctx, method->im_self); + } + else { + callable = sym_new_not_null(ctx); + self_or_null = sym_new_not_null(ctx); + } + } + op(_MAYBE_EXPAND_METHOD, (callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) { (void)args; callable = sym_new_not_null(ctx); @@ -2215,6 +2250,10 @@ dummy_func(void) { sym_set_recorded_value(func, (PyObject *)this_instr->operand0); } + op(_RECORD_BOUND_METHOD, (callable, self, args[oparg] -- callable, self, args[oparg])) { + sym_set_recorded_value(callable, (PyObject *)this_instr->operand0); + } + op(_RECORD_NOS_GEN_FUNC, (nos, tos -- nos, tos)) { PyFunctionObject *func = (PyFunctionObject *)this_instr->operand0; assert(func == NULL || PyFunction_Check(func)); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index dc2d5891f9c3e1..3d7928160700c8 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3644,11 +3644,38 @@ ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version); uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)method->im_func; } + else { + PyObject *bound_method = sym_get_probable_value(callable); + if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) { + PyMethodObject *method = (PyMethodObject *)bound_method; + PyObject *func = method->im_func; + if (PyFunction_Check(func) && + ((PyFunctionObject *)func)->func_version == func_version) { + _Py_BloomFilter_Add(dependencies, func); + sym_set_const(callable, bound_method); + } + } + } sym_set_type(callable, &PyMethod_Type); break; } case _EXPAND_METHOD: { + JitOptRef self_or_null; + JitOptRef callable; + self_or_null = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyMethod_Type)) { + PyMethodObject *method = (PyMethodObject *)sym_get_const(ctx, callable); + callable = sym_new_const(ctx, method->im_func); + self_or_null = sym_new_const(ctx, method->im_self); + } + else { + callable = sym_new_not_null(ctx); + self_or_null = sym_new_not_null(ctx); + } + stack_pointer[-2 - oparg] = callable; + stack_pointer[-1 - oparg] = self_or_null; break; } @@ -3687,8 +3714,18 @@ JitOptRef callable; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - callable = sym_new_not_null(ctx); - self_or_null = sym_new_not_null(ctx); + PyObject *bound_method = sym_get_probable_value(callable); + if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) { + PyMethodObject *method = (PyMethodObject *)bound_method; + callable = sym_new_not_null(ctx); + sym_set_recorded_value(callable, method->im_func); + self_or_null = sym_new_not_null(ctx); + sym_set_recorded_value(self_or_null, method->im_self); + } + else { + callable = sym_new_not_null(ctx); + self_or_null = sym_new_not_null(ctx); + } stack_pointer[-2 - oparg] = callable; stack_pointer[-1 - oparg] = self_or_null; break; @@ -5196,6 +5233,9 @@ } case _RECORD_BOUND_METHOD: { + JitOptRef callable; + callable = stack_pointer[-2 - oparg]; + sym_set_recorded_value(callable, (PyObject *)this_instr->operand0); break; } diff --git a/Python/record_functions.c.h b/Python/record_functions.c.h index 02b8538bc902b5..2c89e3d4dfa6da 100644 --- a/Python/record_functions.c.h +++ b/Python/record_functions.c.h @@ -74,8 +74,7 @@ void _PyOpcode_RecordFunction_BOUND_METHOD(_PyInterpreterFrame *frame, _PyStackR callable = stack_pointer[-2 - oparg]; PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable); if (Py_TYPE(callable_o) == &PyMethod_Type) { - PyObject *func = ((PyMethodObject *)callable_o)->im_func; - *recorded_value = (PyObject *)func; + *recorded_value = (PyObject *)callable_o; Py_INCREF(*recorded_value); } }