Skip to content

Commit acfb452

Browse files
gh-146099: Optimize _GUARD_CODE_VERSION+IP via function version symbols (GH-146101)
1 parent 4507d49 commit acfb452

File tree

7 files changed

+279
-40
lines changed

7 files changed

+279
-40
lines changed

Include/internal/pycore_optimizer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ extern PyCodeObject *_Py_uop_sym_get_probable_func_code(JitOptRef sym);
389389
extern PyObject *_Py_uop_sym_get_probable_value(JitOptRef sym);
390390
extern PyTypeObject *_Py_uop_sym_get_probable_type(JitOptRef sym);
391391
extern JitOptRef *_Py_uop_sym_set_stack_depth(JitOptContext *ctx, int stack_depth, JitOptRef *current_sp);
392+
extern uint32_t _Py_uop_sym_get_func_version(JitOptRef ref);
393+
bool _Py_uop_sym_set_func_version(JitOptContext *ctx, JitOptRef ref, uint32_t version);
392394

393395
extern void _Py_uop_abstractcontext_init(JitOptContext *ctx, _PyBloomFilter *dependencies);
394396
extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx);

Include/internal/pycore_optimizer_types.h

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,16 @@ typedef enum _JitSymType {
3636
JIT_SYM_NON_NULL_TAG = 3,
3737
JIT_SYM_BOTTOM_TAG = 4,
3838
JIT_SYM_TYPE_VERSION_TAG = 5,
39-
JIT_SYM_KNOWN_CLASS_TAG = 6,
40-
JIT_SYM_KNOWN_VALUE_TAG = 7,
41-
JIT_SYM_TUPLE_TAG = 8,
42-
JIT_SYM_TRUTHINESS_TAG = 9,
43-
JIT_SYM_COMPACT_INT = 10,
44-
JIT_SYM_PREDICATE_TAG = 11,
45-
JIT_SYM_RECORDED_VALUE_TAG = 12,
46-
JIT_SYM_RECORDED_TYPE_TAG = 13,
47-
JIT_SYM_RECORDED_GEN_FUNC_TAG = 14,
39+
JIT_SYM_FUNC_VERSION_TAG = 6,
40+
JIT_SYM_KNOWN_CLASS_TAG = 7,
41+
JIT_SYM_KNOWN_VALUE_TAG = 8,
42+
JIT_SYM_TUPLE_TAG = 9,
43+
JIT_SYM_TRUTHINESS_TAG = 10,
44+
JIT_SYM_COMPACT_INT = 11,
45+
JIT_SYM_PREDICATE_TAG = 12,
46+
JIT_SYM_RECORDED_VALUE_TAG = 13,
47+
JIT_SYM_RECORDED_TYPE_TAG = 14,
48+
JIT_SYM_RECORDED_GEN_FUNC_TAG = 15,
4849
} JitSymType;
4950

5051
typedef struct _jit_opt_known_class {
@@ -58,6 +59,11 @@ typedef struct _jit_opt_known_version {
5859
uint32_t version;
5960
} JitOptKnownVersion;
6061

62+
typedef struct _jit_opt_known_func_version {
63+
uint8_t tag;
64+
uint32_t func_version;
65+
} JitOptKnownFuncVersion;
66+
6167
typedef struct _jit_opt_known_value {
6268
uint8_t tag;
6369
PyObject *value;
@@ -118,6 +124,7 @@ typedef union _jit_opt_symbol {
118124
JitOptKnownClass cls;
119125
JitOptKnownValue value;
120126
JitOptKnownVersion version;
127+
JitOptKnownFuncVersion func_version;
121128
JitOptTuple tuple;
122129
JitOptTruthiness truthiness;
123130
JitOptCompactInt compact;
@@ -140,6 +147,7 @@ typedef struct _Py_UOpsAbstractFrame {
140147
int stack_len;
141148
int locals_len;
142149
bool caller; // We have made a call from this frame during the trace
150+
JitOptRef callable;
143151
PyFunctionObject *func;
144152
PyCodeObject *code;
145153

Lib/test/test_capi/test_opt.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,8 @@ def dummy(x):
630630
self.assertIn("_PUSH_FRAME", uops)
631631
self.assertIn("_BINARY_OP_ADD_INT", uops)
632632
self.assertNotIn("_CHECK_PEP_523", uops)
633+
self.assertNotIn("_GUARD_CODE_VERSION__PUSH_FRAME", uops)
634+
self.assertNotIn("_GUARD_IP__PUSH_FRAME", uops)
633635

634636
def test_int_type_propagate_through_range(self):
635637
def testfunc(n):
@@ -1540,8 +1542,9 @@ def testfunc(n):
15401542
self.assertIsNotNone(ex)
15411543
uops = get_opnames(ex)
15421544
self.assertIn("_PUSH_FRAME", uops)
1543-
# Strength reduced version
1544-
self.assertIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
1545+
# Both should be not present, as this is a call
1546+
# to a simple function with a known function version.
1547+
self.assertNotIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
15451548
self.assertNotIn("_CHECK_FUNCTION_VERSION", uops)
15461549
# Removed guard
15471550
self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)

Python/optimizer_analysis.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,8 @@ add_op(JitOptContext *ctx, _PyUOpInstruction *this_instr,
291291
#define sym_get_probable_func_code _Py_uop_sym_get_probable_func_code
292292
#define sym_get_probable_value _Py_uop_sym_get_probable_value
293293
#define sym_set_stack_depth(DEPTH, SP) _Py_uop_sym_set_stack_depth(ctx, DEPTH, SP)
294+
#define sym_get_func_version _Py_uop_sym_get_func_version
295+
#define sym_set_func_version _Py_uop_sym_set_func_version
294296

295297
/* Comparison oparg masks */
296298
#define COMPARE_LT_MASK 2

Python/optimizer_bytecodes.c

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -864,12 +864,12 @@ dummy_func(void) {
864864
}
865865

866866
op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
867-
if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) {
868-
assert(PyFunction_Check(sym_get_const(ctx, callable)));
869-
ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
870-
uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)sym_get_const(ctx, callable);
867+
if (sym_get_func_version(callable) == func_version) {
868+
REPLACE_OP(this_instr, _NOP, 0, 0);
869+
}
870+
else {
871+
sym_set_func_version(ctx, callable, func_version);
871872
}
872-
sym_set_type(callable, &PyFunction_Type);
873873
}
874874

875875
op(_CHECK_METHOD_VERSION, (func_version/2, callable, null, unused[oparg] -- callable, null, unused[oparg])) {
@@ -1779,12 +1779,47 @@ dummy_func(void) {
17791779
sym_set_recorded_gen_func(nos, func);
17801780
}
17811781

1782+
op(_GUARD_CODE_VERSION__PUSH_FRAME, (version/2 -- )) {
1783+
PyCodeObject *co = get_current_code_object(ctx);
1784+
if (co->co_version == version) {
1785+
_Py_BloomFilter_Add(dependencies, co);
1786+
// Functions derive their version from code objects.
1787+
if (sym_get_func_version(ctx->frame->callable) == version) {
1788+
REPLACE_OP(this_instr, _NOP, 0, 0);
1789+
}
1790+
}
1791+
else {
1792+
ctx->done = true;
1793+
}
1794+
}
1795+
1796+
op(_GUARD_CODE_VERSION_RETURN_VALUE, (version/2 -- )) {
1797+
if (ctx->frame->caller) {
1798+
REPLACE_OP(this_instr, _NOP, 0, 0);
1799+
}
1800+
}
1801+
1802+
op(_GUARD_CODE_VERSION_YIELD_VALUE, (version/2 -- )) {
1803+
if (ctx->frame->caller) {
1804+
REPLACE_OP(this_instr, _NOP, 0, 0);
1805+
}
1806+
}
1807+
1808+
op(_GUARD_CODE_VERSION_RETURN_GENERATOR, (version/2 -- )) {
1809+
if (ctx->frame->caller) {
1810+
REPLACE_OP(this_instr, _NOP, 0, 0);
1811+
}
1812+
}
1813+
17821814
op(_GUARD_IP__PUSH_FRAME, (ip/4 --)) {
17831815
(void)ip;
17841816
stack_pointer = sym_set_stack_depth((int)this_instr->operand1, stack_pointer);
1785-
// TO DO
1786-
// Normal function calls to known functions
1787-
// do not need an IP guard.
1817+
if (sym_get_func_version(ctx->frame->callable) != 0 &&
1818+
// We can remove this guard for simple function call targets.
1819+
(((PyCodeObject *)ctx->frame->func->func_code)->co_flags &
1820+
(CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) == 0) {
1821+
REPLACE_OP(this_instr, _NOP, 0, 0);
1822+
}
17881823
}
17891824

17901825
op(_GUARD_IP_YIELD_VALUE, (ip/4 --)) {

Python/optimizer_cases.c.h

Lines changed: 34 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)