self.assertEqual(uops.count("_PUSH_FRAME"), 2)
# Type version propagation: one guard covers both method lookups
self.assertEqual(uops.count("_GUARD_TYPE_VERSION"), 1)
- # Function checks eliminated (type info resolves the callable)
- self.assertNotIn("_CHECK_FUNCTION_VERSION", uops)
- self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)
+ # Function checks cannot be eliminated for safety reasons.
+ self.assertIn("_CHECK_FUNCTION_VERSION", uops)
def test_method_chain_guard_elimination(self):
"""
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_PUSH_FRAME", uops)
- # Both should be not present, as this is a call
- # to a simple function with a known function version.
- self.assertNotIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
- self.assertNotIn("_CHECK_FUNCTION_VERSION", uops)
+ self.assertIn("_CHECK_FUNCTION_VERSION", uops)
# Removed guard
self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)
PYTHON_JIT="1", PYTHON_JIT_STRESS="1")
self.assertEqual(result[0].rc, 0, result)
+ def test_func_version_guarded_on_change(self):
+ def testfunc(n):
+ for i in range(n):
+ # Only works on functions promoted to constants
+ global_identity_code_will_be_modified(i)
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_PUSH_FRAME", uops)
+ self.assertIn("_CHECK_FUNCTION_VERSION", uops)
+
+ global_identity_code_will_be_modified.__code__ = (lambda a: 0xdeadead).__code__
+ _testinternalcapi.clear_executor_deletion_list()
+ ex = get_first_executor(testfunc)
+ self.assertIsNone(ex)
+ # JItted code should've deopted.
+ self.assertEqual(global_identity_code_will_be_modified(None), 0xdeadead)
+
def test_call_super(self):
class A:
def method1(self):
def global_identity(x):
return x
+def global_identity_code_will_be_modified(x):
+ return x
+
class TestObject:
def test(self, *args, **kwargs):
return args[0]
}
op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
- if (sym_get_func_version(callable) == func_version) {
- REPLACE_OP(this_instr, _NOP, 0, 0);
- }
- else {
- sym_set_func_version(ctx, callable, func_version);
+ PyObject *func = sym_get_probable_value(callable);
+ if (func == NULL || !PyFunction_Check(func) || ((PyFunctionObject *)func)->func_version != func_version) {
+ ctx->contradiction = true;
+ ctx->done = true;
+ break;
}
+ // Guarded on this, so it can be promoted.
+ sym_set_const(callable, func);
+ _Py_BloomFilter_Add(dependencies, func);
}
op(_CHECK_METHOD_VERSION, (func_version/2, callable, null, unused[oparg] -- callable, null, unused[oparg])) {
if (co->co_version == version) {
_Py_BloomFilter_Add(dependencies, co);
// Functions derive their version from code objects.
- if (sym_get_func_version(ctx->frame->callable) == version) {
+ PyFunctionObject *func = (PyFunctionObject *)sym_get_const(ctx, ctx->frame->callable);
+ if (func != NULL && func->func_version == version) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
}
op(_GUARD_IP__PUSH_FRAME, (ip/4 --)) {
(void)ip;
stack_pointer = sym_set_stack_depth((int)this_instr->operand1, stack_pointer);
- if (sym_get_func_version(ctx->frame->callable) != 0 &&
+ PyFunctionObject *func = (PyFunctionObject *)sym_get_const(ctx, ctx->frame->callable);
+ if (func != NULL && func->func_version != 0 &&
// We can remove this guard for simple function call targets.
(((PyCodeObject *)ctx->frame->func->func_code)->co_flags &
(CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) == 0) {
| TYPE_VERSION | | |
| | | | | <- Anything below this level has a known type.
| KNOWN_CLASS--+ | | |
-| | | | | | PREDICATE RECORDED_VALUE(known type)
-| | | INT* | | | |
-| | | | | | | | <- Anything below this level has a known truthiness.
-| | | | FUNC_VERSION | | |
-| TUPLE | | | TRUTHINESS | |
-| | | | | | | | <- Anything below this level is a known constant.
+| | | | | PREDICATE RECORDED_VALUE(known type)
+| | | INT* | | |
+| | | | | | | <- Anything below this level has a known truthiness.
+| | | | | | |
+| TUPLE | | TRUTHINESS | |
+| | | | | | | <- Anything below this level is a known constant.
| KNOWN_VALUE--+-------+----+------+
| | <- Anything below this level is unreachable.
BOTTOM
case JIT_SYM_TYPE_VERSION_TAG:
printf("<v%u at %p>", sym->version.version, (void *)sym);
break;
- case JIT_SYM_FUNC_VERSION_TAG:
- printf("<function version=%u>", sym->func_version.func_version);
- break;
case JIT_SYM_KNOWN_CLASS_TAG:
printf("<%s at %p>", sym->cls.type->tp_name, (void *)sym);
break;
sym_set_bottom(ctx, sym);
}
return;
- case JIT_SYM_FUNC_VERSION_TAG:
- if (typ != &PyFunction_Type) {
- sym_set_bottom(ctx, sym);
- }
- return;
case JIT_SYM_KNOWN_VALUE_TAG:
if (Py_TYPE(sym->value.value) != typ) {
Py_CLEAR(sym->value.value);
return false;
}
return true;
- case JIT_SYM_FUNC_VERSION_TAG:
- if (version != PyFunction_Type.tp_version_tag) {
- sym_set_bottom(ctx, sym);
- return false;
- }
- return true;
case JIT_SYM_BOTTOM_TAG:
return false;
case JIT_SYM_NON_NULL_TAG:
Py_UNREACHABLE();
}
-bool
-_Py_uop_sym_set_func_version(JitOptContext *ctx, JitOptRef ref, uint32_t version)
-{
- JitOptSymbol *sym = PyJitRef_Unwrap(ref);
- JitSymType tag = sym->tag;
- switch(tag) {
- case JIT_SYM_NULL_TAG:
- sym_set_bottom(ctx, sym);
- return false;
- case JIT_SYM_KNOWN_CLASS_TAG:
- if (sym->cls.type != &PyFunction_Type) {
- sym_set_bottom(ctx, sym);
- return false;
- }
- sym->tag = JIT_SYM_FUNC_VERSION_TAG;
- sym->version.version = version;
- return true;
- case JIT_SYM_KNOWN_VALUE_TAG:
- if (Py_TYPE(sym->value.value) != &PyFunction_Type ||
- ((PyFunctionObject *)sym->value.value)->func_version != version) {
- Py_CLEAR(sym->value.value);
- sym_set_bottom(ctx, sym);
- return false;
- }
- return true;
- case JIT_SYM_TYPE_VERSION_TAG:
- if (sym->version.version != PyFunction_Type.tp_version_tag) {
- sym_set_bottom(ctx, sym);
- return false;
- }
- sym->tag = JIT_SYM_FUNC_VERSION_TAG;
- sym->version.version = version;
- return true;
- case JIT_SYM_FUNC_VERSION_TAG:
- if (sym->func_version.func_version != version) {
- sym_set_bottom(ctx, sym);
- return false;
- }
- return true;
- case JIT_SYM_BOTTOM_TAG:
- return false;
- case JIT_SYM_NON_NULL_TAG:
- case JIT_SYM_UNKNOWN_TAG:
- sym->tag = JIT_SYM_FUNC_VERSION_TAG;
- sym->func_version.func_version = version;
- return true;
- case JIT_SYM_RECORDED_GEN_FUNC_TAG:
- case JIT_SYM_COMPACT_INT:
- case JIT_SYM_TUPLE_TAG:
- case JIT_SYM_PREDICATE_TAG:
- case JIT_SYM_TRUTHINESS_TAG:
- sym_set_bottom(ctx, sym);
- return false;
- case JIT_SYM_RECORDED_VALUE_TAG: {
- PyObject *val = sym->recorded_value.value;
- if (Py_TYPE(val) != &PyFunction_Type ||
- ((PyFunctionObject *)sym->recorded_value.value)->func_version != version) {
- sym_set_bottom(ctx, sym);
- return false;
- }
- // Promote to known value, as we have guarded/checked on it.
- sym->tag = JIT_SYM_KNOWN_VALUE_TAG;
- // New ownership. We need to NewRef here, as
- // it's originally kept alive by the trace buffer.
- sym->value.value = Py_NewRef(val);
- return true;
- }
- case JIT_SYM_RECORDED_TYPE_TAG:
- if (sym->recorded_type.type == &PyFunction_Type) {
- sym->tag = JIT_SYM_FUNC_VERSION_TAG;
- sym->func_version.func_version = version;
- return true;
- }
- else {
- sym_set_bottom(ctx, sym);
- return false;
- }
- }
- Py_UNREACHABLE();
-}
-
void
_Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef ref, PyObject *const_val)
{
}
make_const(sym, const_val);
return;
- case JIT_SYM_FUNC_VERSION_TAG:
- if (Py_TYPE(const_val) != &PyFunction_Type ||
- ((PyFunctionObject *)const_val)->func_version != sym->func_version.func_version) {
- sym_set_bottom(ctx, sym);
- return;
- }
- make_const(sym, const_val);
- return;
case JIT_SYM_BOTTOM_TAG:
return;
case JIT_SYM_RECORDED_VALUE_TAG:
return Py_TYPE(sym->value.value);
case JIT_SYM_TYPE_VERSION_TAG:
return _PyType_LookupByVersion(sym->version.version);
- case JIT_SYM_FUNC_VERSION_TAG:
- return &PyFunction_Type;
case JIT_SYM_TUPLE_TAG:
return &PyTuple_Type;
case JIT_SYM_PREDICATE_TAG:
case JIT_SYM_NON_NULL_TAG:
case JIT_SYM_UNKNOWN_TAG:
case JIT_SYM_TYPE_VERSION_TAG:
- case JIT_SYM_FUNC_VERSION_TAG:
case JIT_SYM_TUPLE_TAG:
case JIT_SYM_PREDICATE_TAG:
case JIT_SYM_TRUTHINESS_TAG:
return 0;
case JIT_SYM_TYPE_VERSION_TAG:
return sym->version.version;
- case JIT_SYM_FUNC_VERSION_TAG:
- return PyFunction_Type.tp_version_tag;
case JIT_SYM_KNOWN_CLASS_TAG:
return sym->cls.version;
case JIT_SYM_KNOWN_VALUE_TAG:
Py_UNREACHABLE();
}
-uint32_t
-_Py_uop_sym_get_func_version(JitOptRef ref)
-{
- JitOptSymbol *sym = PyJitRef_Unwrap(ref);
- JitSymType tag = sym->tag;
- switch(tag) {
- case JIT_SYM_NULL_TAG:
- case JIT_SYM_BOTTOM_TAG:
- case JIT_SYM_NON_NULL_TAG:
- case JIT_SYM_UNKNOWN_TAG:
- case JIT_SYM_RECORDED_VALUE_TAG:
- case JIT_SYM_RECORDED_TYPE_TAG:
- case JIT_SYM_TYPE_VERSION_TAG:
- case JIT_SYM_KNOWN_CLASS_TAG:
- case JIT_SYM_TUPLE_TAG:
- case JIT_SYM_PREDICATE_TAG:
- case JIT_SYM_TRUTHINESS_TAG:
- case JIT_SYM_COMPACT_INT:
- case JIT_SYM_RECORDED_GEN_FUNC_TAG:
- return 0;
- case JIT_SYM_FUNC_VERSION_TAG:
- return sym->func_version.func_version;
- case JIT_SYM_KNOWN_VALUE_TAG:
- if (Py_TYPE(sym->value.value) == &PyFunction_Type) {
- return ((PyFunctionObject *)sym->value.value)->func_version;
- }
- return 0;
- }
- Py_UNREACHABLE();
-}
-
bool
_Py_uop_sym_has_type(JitOptRef sym)
case JIT_SYM_UNKNOWN_TAG:
case JIT_SYM_RECORDED_TYPE_TAG:
case JIT_SYM_TYPE_VERSION_TAG:
- case JIT_SYM_FUNC_VERSION_TAG:
case JIT_SYM_TUPLE_TAG:
case JIT_SYM_PREDICATE_TAG:
case JIT_SYM_TRUTHINESS_TAG:
return -1;
case JIT_SYM_KNOWN_VALUE_TAG:
break;
- case JIT_SYM_FUNC_VERSION_TAG:
- return 1;
case JIT_SYM_TUPLE_TAG:
return sym->tuple.length != 0;
case JIT_SYM_TRUTHINESS_TAG:
sym_set_bottom(ctx, sym);
}
return;
- case JIT_SYM_FUNC_VERSION_TAG:
case JIT_SYM_TUPLE_TAG:
case JIT_SYM_PREDICATE_TAG:
case JIT_SYM_TRUTHINESS_TAG:
case JIT_SYM_PREDICATE_TAG:
case JIT_SYM_TRUTHINESS_TAG:
case JIT_SYM_COMPACT_INT:
- case JIT_SYM_FUNC_VERSION_TAG:
return;
}
Py_UNREACHABLE();
case JIT_SYM_PREDICATE_TAG:
case JIT_SYM_TRUTHINESS_TAG:
case JIT_SYM_COMPACT_INT:
- case JIT_SYM_FUNC_VERSION_TAG:
- sym_set_bottom(ctx, sym);
- return;
case JIT_SYM_BOTTOM_TAG:
return;
case JIT_SYM_NON_NULL_TAG:
case JIT_SYM_TRUTHINESS_TAG:
case JIT_SYM_COMPACT_INT:
case JIT_SYM_RECORDED_GEN_FUNC_TAG:
- case JIT_SYM_FUNC_VERSION_TAG:
return;
}
Py_UNREACHABLE();
TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "43 is not an int");
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref_int) == val_43, "43 isn't 43");
- // Test func version's important transitions.
- JitOptRef func_version = _Py_uop_sym_new_not_null(ctx);
- TEST_PREDICATE(_Py_uop_sym_get_func_version(func_version) == 0, "func version should be unset");
- _Py_uop_sym_set_func_version(ctx, func_version, 172);
- TEST_PREDICATE(_Py_uop_sym_get_func_version(func_version) == 172, "func version should be set");
- func_version = _Py_uop_sym_new_type(ctx, &PyFunction_Type);
- _Py_uop_sym_set_func_version(ctx, func_version, 192);
- TEST_PREDICATE(_Py_uop_sym_get_func_version(func_version) == 192, "func version should be set");
-
// Test recorded values
/* Test that recorded values aren't treated as known values*/