Skip to content

Commit 6349262

Browse files
authored
pythongh-148438: implement _RECORD_BOUND_METHOD in JIT (pythonGH-148457)
1 parent 18d7d90 commit 6349262

File tree

5 files changed

+128
-8
lines changed

5 files changed

+128
-8
lines changed

Lib/test/test_capi/test_opt.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,49 @@ def testfunc(n):
17231723
self.assertIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
17241724
self.assertNotIn("_CHECK_METHOD_VERSION", uops)
17251725

1726+
def test_record_bound_method_general(self):
1727+
class MyClass:
1728+
def method(self, *args):
1729+
return args[0] + 1
1730+
1731+
def testfunc(n):
1732+
obj = MyClass()
1733+
bound = obj.method
1734+
result = 0
1735+
for i in range(n):
1736+
result += bound(i)
1737+
return result
1738+
1739+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1740+
self.assertEqual(
1741+
res, sum(i + 1 for i in range(TIER2_THRESHOLD))
1742+
)
1743+
self.assertIsNotNone(ex)
1744+
uops = get_opnames(ex)
1745+
self.assertIn("_PUSH_FRAME", uops)
1746+
1747+
def test_record_bound_method_exact_args(self):
1748+
class MyClass:
1749+
def method(self, x):
1750+
return x + 1
1751+
1752+
def testfunc(n):
1753+
obj = MyClass()
1754+
bound = obj.method
1755+
result = 0
1756+
for i in range(n):
1757+
result += bound(i)
1758+
return result
1759+
1760+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1761+
self.assertEqual(
1762+
res, sum(i + 1 for i in range(TIER2_THRESHOLD))
1763+
)
1764+
self.assertIsNotNone(ex)
1765+
uops = get_opnames(ex)
1766+
self.assertIn("_PUSH_FRAME", uops)
1767+
self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)
1768+
17261769
def test_jit_error_pops(self):
17271770
"""
17281771
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
@@ -6160,8 +6160,7 @@ dummy_func(
61606160
tier2 op(_RECORD_BOUND_METHOD, (callable, self, args[oparg] -- callable, self, args[oparg])) {
61616161
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
61626162
if (Py_TYPE(callable_o) == &PyMethod_Type) {
6163-
PyObject *func = ((PyMethodObject *)callable_o)->im_func;
6164-
RECORD_VALUE(func);
6163+
RECORD_VALUE(callable_o);
61656164
}
61666165
}
61676166

Python/optimizer_bytecodes.c

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -996,8 +996,18 @@ 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);
1000-
self_or_null = sym_new_not_null(ctx);
999+
PyObject *bound_method = sym_get_probable_value(callable);
1000+
if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) {
1001+
PyMethodObject *method = (PyMethodObject *)bound_method;
1002+
callable = sym_new_not_null(ctx);
1003+
sym_set_recorded_value(callable, method->im_func);
1004+
self_or_null = sym_new_not_null(ctx);
1005+
sym_set_recorded_value(self_or_null, method->im_self);
1006+
}
1007+
else {
1008+
callable = sym_new_not_null(ctx);
1009+
self_or_null = sym_new_not_null(ctx);
1010+
}
10011011
}
10021012

10031013
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) {
10191029
ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
10201030
uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)method->im_func;
10211031
}
1032+
else {
1033+
// Guarding on the bound method, safe to promote.
1034+
PyObject *bound_method = sym_get_probable_value(callable);
1035+
if (bound_method != NULL && Py_TYPE(bound_method) == &PyMethod_Type) {
1036+
PyMethodObject *method = (PyMethodObject *)bound_method;
1037+
PyObject *func = method->im_func;
1038+
if (PyFunction_Check(func) &&
1039+
((PyFunctionObject *)func)->func_version == func_version) {
1040+
_Py_BloomFilter_Add(dependencies, func);
1041+
sym_set_const(callable, bound_method);
1042+
}
1043+
}
1044+
}
10221045
sym_set_type(callable, &PyMethod_Type);
10231046
}
10241047

@@ -1057,6 +1080,18 @@ dummy_func(void) {
10571080
}
10581081
}
10591082

1083+
op(_EXPAND_METHOD, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
1084+
if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyMethod_Type)) {
1085+
PyMethodObject *method = (PyMethodObject *)sym_get_const(ctx, callable);
1086+
callable = sym_new_const(ctx, method->im_func);
1087+
self_or_null = sym_new_const(ctx, method->im_self);
1088+
}
1089+
else {
1090+
callable = sym_new_not_null(ctx);
1091+
self_or_null = sym_new_not_null(ctx);
1092+
}
1093+
}
1094+
10601095
op(_MAYBE_EXPAND_METHOD, (callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) {
10611096
(void)args;
10621097
callable = sym_new_not_null(ctx);
@@ -2226,6 +2261,10 @@ dummy_func(void) {
22262261
sym_set_recorded_value(func, (PyObject *)this_instr->operand0);
22272262
}
22282263

2264+
op(_RECORD_BOUND_METHOD, (callable, self, args[oparg] -- callable, self, args[oparg])) {
2265+
sym_set_recorded_value(callable, (PyObject *)this_instr->operand0);
2266+
}
2267+
22292268
op(_RECORD_NOS_GEN_FUNC, (nos, tos -- nos, tos)) {
22302269
PyFunctionObject *func = (PyFunctionObject *)this_instr->operand0;
22312270
assert(func == NULL || PyFunction_Check(func));

Python/optimizer_cases.c.h

Lines changed: 42 additions & 2 deletions
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)