Skip to content

Commit e8740df

Browse files
committed
gh-148438: implement _RECORD_BOUND_METHOD in JIT
1 parent 03d2f03 commit e8740df

File tree

5 files changed

+125
-6
lines changed

5 files changed

+125
-6
lines changed

Lib/test/test_capi/test_opt.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,51 @@ def testfunc(n):
16911691
self.assertIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
16921692
self.assertNotIn("_CHECK_METHOD_VERSION", uops)
16931693

1694+
def test_record_bound_method_general(self):
1695+
class MyClass:
1696+
def method(self, *args):
1697+
return args[0] + 1
1698+
1699+
def testfunc(n):
1700+
obj = MyClass()
1701+
bound = obj.method
1702+
result = 0
1703+
for i in range(n):
1704+
result += bound(i)
1705+
return result
1706+
1707+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1708+
self.assertEqual(
1709+
res, sum(i + 1 for i in range(TIER2_THRESHOLD))
1710+
)
1711+
self.assertIsNotNone(ex)
1712+
uops = get_opnames(ex)
1713+
self.assertIn("_PUSH_FRAME", uops)
1714+
self.assertIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
1715+
self.assertNotIn("_CHECK_METHOD_VERSION", uops)
1716+
1717+
def test_record_bound_method_exact_args(self):
1718+
class MyClass:
1719+
def method(self, x):
1720+
return x + 1
1721+
1722+
def testfunc(n):
1723+
obj = MyClass()
1724+
bound = obj.method
1725+
result = 0
1726+
for i in range(n):
1727+
result += bound(i)
1728+
return result
1729+
1730+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1731+
self.assertEqual(
1732+
res, sum(i + 1 for i in range(TIER2_THRESHOLD))
1733+
)
1734+
self.assertIsNotNone(ex)
1735+
uops = get_opnames(ex)
1736+
self.assertIn("_PUSH_FRAME", uops)
1737+
self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)
1738+
16941739
def test_jit_error_pops(self):
16951740
"""
16961741
Tests that the correct number of pops are inserted into the

Python/bytecodes.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6151,8 +6151,7 @@ dummy_func(
61516151
tier2 op(_RECORD_BOUND_METHOD, (callable, self, args[oparg] -- callable, self, args[oparg])) {
61526152
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
61536153
if (Py_TYPE(callable_o) == &PyMethod_Type) {
6154-
PyObject *func = ((PyMethodObject *)callable_o)->im_func;
6155-
RECORD_VALUE(func);
6154+
RECORD_VALUE(callable_o);
61566155
}
61576156
}
61586157

Python/optimizer_bytecodes.c

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,8 +996,16 @@ dummy_func(void) {
996996
}
997997

998998
op(_INIT_CALL_BOUND_METHOD_EXACT_ARGS, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
999-
callable = sym_new_not_null(ctx);
999+
PyObject *bound_method = sym_get_probable_value(callable);
10001000
self_or_null = sym_new_not_null(ctx);
1001+
if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) {
1002+
PyMethodObject *method = (PyMethodObject *)bound_method;
1003+
callable = sym_new_not_null(ctx);
1004+
sym_set_recorded_value(callable, method->im_func);
1005+
}
1006+
else {
1007+
callable = sym_new_not_null(ctx);
1008+
}
10011009
}
10021010

10031011
op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
@@ -1016,6 +1024,19 @@ dummy_func(void) {
10161024
ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
10171025
uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)method->im_func;
10181026
}
1027+
else {
1028+
PyObject *bound_method = sym_get_probable_value(callable);
1029+
if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) {
1030+
PyMethodObject *method = (PyMethodObject *)bound_method;
1031+
PyObject *func = method->im_func;
1032+
if (PyFunction_Check(func) &&
1033+
((PyFunctionObject *)func)->func_version == func_version) {
1034+
sym_set_const(callable, bound_method);
1035+
ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
1036+
uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)func;
1037+
}
1038+
}
1039+
}
10191040
sym_set_type(callable, &PyMethod_Type);
10201041
}
10211042

@@ -1054,6 +1075,18 @@ dummy_func(void) {
10541075
}
10551076
}
10561077

1078+
op(_EXPAND_METHOD, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
1079+
if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyMethod_Type)) {
1080+
PyMethodObject *method = (PyMethodObject *)sym_get_const(ctx, callable);
1081+
callable = sym_new_const(ctx, method->im_func);
1082+
self_or_null = sym_new_const(ctx, method->im_self);
1083+
}
1084+
else {
1085+
callable = sym_new_not_null(ctx);
1086+
self_or_null = sym_new_not_null(ctx);
1087+
}
1088+
}
1089+
10571090
op(_MAYBE_EXPAND_METHOD, (callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) {
10581091
(void)args;
10591092
callable = sym_new_not_null(ctx);
@@ -2212,6 +2245,10 @@ dummy_func(void) {
22122245
sym_set_recorded_value(func, (PyObject *)this_instr->operand0);
22132246
}
22142247

2248+
op(_RECORD_BOUND_METHOD, (callable, self, args[oparg] -- callable, self, args[oparg])) {
2249+
sym_set_recorded_value(callable, (PyObject *)this_instr->operand0);
2250+
}
2251+
22152252
op(_RECORD_NOS_GEN_FUNC, (nos, tos -- nos, tos)) {
22162253
PyFunctionObject *func = (PyFunctionObject *)this_instr->operand0;
22172254
assert(func == NULL || PyFunction_Check(func));

Python/optimizer_cases.c.h

Lines changed: 40 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/record_functions.c.h

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)