https://github.com/python/cpython/commit/63492628bef6b21b13d9a36c5dac8d8872f7d42e
commit: 63492628bef6b21b13d9a36c5dac8d8872f7d42e
branch: main
author: Neko Asakura <[email protected]>
committer: Fidget-Spinner <[email protected]>
date: 2026-04-13T02:57:55+08:00
summary:
gh-148438: implement `_RECORD_BOUND_METHOD` in JIT (GH-148457)
files:
M Lib/test/test_capi/test_opt.py
M Python/bytecodes.c
M Python/optimizer_bytecodes.c
M Python/optimizer_cases.c.h
M Python/record_functions.c.h
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index 4a26bbc96ce9a4..03ec53b93a3ba9 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -1723,6 +1723,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 351a50b5d53787..a274fbf7403af6 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -6160,8 +6160,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 ab9d23aef5eacf..957575dcfaccce 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);
@@ -2226,6 +2261,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 b9895a63f214dc..553d819fcdeefb 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;
@@ -5212,6 +5249,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);
}
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]