Skip to content

Commit 27620c2

Browse files
Merge branch 'main' into gh-148438/record-bound-method
2 parents faf487f + 6f7bb29 commit 27620c2

File tree

8 files changed

+64
-189
lines changed

8 files changed

+64
-189
lines changed

Include/internal/pycore_optimizer.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,6 @@ extern PyCodeObject *_Py_uop_sym_get_probable_func_code(JitOptRef sym);
424424
extern PyObject *_Py_uop_sym_get_probable_value(JitOptRef sym);
425425
extern PyTypeObject *_Py_uop_sym_get_probable_type(JitOptRef sym);
426426
extern JitOptRef *_Py_uop_sym_set_stack_depth(JitOptContext *ctx, int stack_depth, JitOptRef *current_sp);
427-
extern uint32_t _Py_uop_sym_get_func_version(JitOptRef ref);
428-
bool _Py_uop_sym_set_func_version(JitOptContext *ctx, JitOptRef ref, uint32_t version);
429427

430428
extern void _Py_uop_abstractcontext_init(JitOptContext *ctx, _PyBloomFilter *dependencies);
431429
extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx);

Include/internal/pycore_optimizer_types.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ 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_FUNC_VERSION_TAG = 6,
4039
JIT_SYM_KNOWN_CLASS_TAG = 7,
4140
JIT_SYM_KNOWN_VALUE_TAG = 8,
4241
JIT_SYM_TUPLE_TAG = 9,

Lib/test/test_capi/test_opt.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,9 +1622,8 @@ def testfunc(n):
16221622
self.assertEqual(uops.count("_PUSH_FRAME"), 2)
16231623
# Type version propagation: one guard covers both method lookups
16241624
self.assertEqual(uops.count("_GUARD_TYPE_VERSION"), 1)
1625-
# Function checks eliminated (type info resolves the callable)
1626-
self.assertNotIn("_CHECK_FUNCTION_VERSION", uops)
1627-
self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)
1625+
# Function checks cannot be eliminated for safety reasons.
1626+
self.assertIn("_CHECK_FUNCTION_VERSION", uops)
16281627

16291628
def test_method_chain_guard_elimination(self):
16301629
"""
@@ -1669,10 +1668,7 @@ def testfunc(n):
16691668
self.assertIsNotNone(ex)
16701669
uops = get_opnames(ex)
16711670
self.assertIn("_PUSH_FRAME", uops)
1672-
# Both should be not present, as this is a call
1673-
# to a simple function with a known function version.
1674-
self.assertNotIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
1675-
self.assertNotIn("_CHECK_FUNCTION_VERSION", uops)
1671+
self.assertIn("_CHECK_FUNCTION_VERSION", uops)
16761672
# Removed guard
16771673
self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)
16781674

@@ -5221,6 +5217,27 @@ def g():
52215217
PYTHON_JIT="1", PYTHON_JIT_STRESS="1")
52225218
self.assertEqual(result[0].rc, 0, result)
52235219

5220+
def test_func_version_guarded_on_change(self):
5221+
def testfunc(n):
5222+
for i in range(n):
5223+
# Only works on functions promoted to constants
5224+
global_identity_code_will_be_modified(i)
5225+
5226+
testfunc(TIER2_THRESHOLD)
5227+
5228+
ex = get_first_executor(testfunc)
5229+
self.assertIsNotNone(ex)
5230+
uops = get_opnames(ex)
5231+
self.assertIn("_PUSH_FRAME", uops)
5232+
self.assertIn("_CHECK_FUNCTION_VERSION", uops)
5233+
5234+
global_identity_code_will_be_modified.__code__ = (lambda a: 0xdeadead).__code__
5235+
_testinternalcapi.clear_executor_deletion_list()
5236+
ex = get_first_executor(testfunc)
5237+
self.assertIsNone(ex)
5238+
# JItted code should've deopted.
5239+
self.assertEqual(global_identity_code_will_be_modified(None), 0xdeadead)
5240+
52245241
def test_call_super(self):
52255242
class A:
52265243
def method1(self):
@@ -5267,6 +5284,9 @@ def testfunc(n):
52675284
def global_identity(x):
52685285
return x
52695286

5287+
def global_identity_code_will_be_modified(x):
5288+
return x
5289+
52705290
class TestObject:
52715291
def test(self, *args, **kwargs):
52725292
return args[0]

Objects/funcobject.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
99
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
1010
#include "pycore_object_deferred.h" // _PyObject_SetDeferredRefcount()
11+
#include "pycore_optimizer.h" // _Py_Executors_InvalidateDependency()
1112
#include "pycore_pyerrors.h" // _PyErr_Occurred()
1213
#include "pycore_setobject.h" // _PySet_NextEntry()
1314
#include "pycore_stats.h"
@@ -63,6 +64,13 @@ handle_func_event(PyFunction_WatchEvent event, PyFunctionObject *func,
6364
case PyFunction_EVENT_MODIFY_DEFAULTS:
6465
case PyFunction_EVENT_MODIFY_KWDEFAULTS:
6566
case PyFunction_EVENT_MODIFY_QUALNAME:
67+
#if _Py_TIER2
68+
// Note: we only invalidate JIT code if a function version changes.
69+
// Not when the function is deallocated.
70+
// Function deallocation occurs frequently (think: lambdas),
71+
// so we want to minimize dependency invalidation there.
72+
_Py_Executors_InvalidateDependency(interp, func, 1);
73+
#endif
6674
RARE_EVENT_INTERP_INC(interp, func_modification);
6775
break;
6876
default:

Python/optimizer_analysis.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,6 @@ add_op(JitOptContext *ctx, _PyUOpInstruction *this_instr,
283283
#define sym_get_probable_func_code _Py_uop_sym_get_probable_func_code
284284
#define sym_get_probable_value _Py_uop_sym_get_probable_value
285285
#define sym_set_stack_depth(DEPTH, SP) _Py_uop_sym_set_stack_depth(ctx, DEPTH, SP)
286-
#define sym_get_func_version _Py_uop_sym_get_func_version
287-
#define sym_set_func_version _Py_uop_sym_set_func_version
288286

289287
/* Comparison oparg masks */
290288
#define COMPARE_LT_MASK 2

Python/optimizer_bytecodes.c

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,12 +1011,15 @@ dummy_func(void) {
10111011
}
10121012

10131013
op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
1014-
if (sym_get_func_version(callable) == func_version) {
1015-
REPLACE_OP(this_instr, _NOP, 0, 0);
1016-
}
1017-
else {
1018-
sym_set_func_version(ctx, callable, func_version);
1014+
PyObject *func = sym_get_probable_value(callable);
1015+
if (func == NULL || !PyFunction_Check(func) || ((PyFunctionObject *)func)->func_version != func_version) {
1016+
ctx->contradiction = true;
1017+
ctx->done = true;
1018+
break;
10191019
}
1020+
// Guarded on this, so it can be promoted.
1021+
sym_set_const(callable, func);
1022+
_Py_BloomFilter_Add(dependencies, func);
10201023
}
10211024

10221025
op(_CHECK_METHOD_VERSION, (func_version/2, callable, null, unused[oparg] -- callable, null, unused[oparg])) {
@@ -2268,7 +2271,8 @@ dummy_func(void) {
22682271
if (co->co_version == version) {
22692272
_Py_BloomFilter_Add(dependencies, co);
22702273
// Functions derive their version from code objects.
2271-
if (sym_get_func_version(ctx->frame->callable) == version) {
2274+
PyFunctionObject *func = (PyFunctionObject *)sym_get_const(ctx, ctx->frame->callable);
2275+
if (func != NULL && func->func_version == version) {
22722276
REPLACE_OP(this_instr, _NOP, 0, 0);
22732277
}
22742278
}
@@ -2301,7 +2305,8 @@ dummy_func(void) {
23012305
op(_GUARD_IP__PUSH_FRAME, (ip/4 --)) {
23022306
(void)ip;
23032307
stack_pointer = sym_set_stack_depth((int)this_instr->operand1, stack_pointer);
2304-
if (sym_get_func_version(ctx->frame->callable) != 0 &&
2308+
PyFunctionObject *func = (PyFunctionObject *)sym_get_const(ctx, ctx->frame->callable);
2309+
if (func != NULL && func->func_version != 0 &&
23052310
// We can remove this guard for simple function call targets.
23062311
(((PyCodeObject *)ctx->frame->func->func_code)->co_flags &
23072312
(CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) == 0) {

Python/optimizer_cases.c.h

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

0 commit comments

Comments
 (0)