}
op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
- if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) {
- assert(PyFunction_Check(sym_get_const(ctx, callable)));
- ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
- uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)sym_get_const(ctx, callable);
+ if (sym_get_func_version(callable) == func_version) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ else {
+ sym_set_func_version(ctx, callable, func_version);
}
- sym_set_type(callable, &PyFunction_Type);
}
op(_CHECK_METHOD_VERSION, (func_version/2, callable, null, unused[oparg] -- callable, null, unused[oparg])) {
sym_set_recorded_gen_func(nos, func);
}
+ op(_GUARD_CODE_VERSION__PUSH_FRAME, (version/2 -- )) {
+ PyCodeObject *co = get_current_code_object(ctx);
+ 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) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ }
+ else {
+ ctx->done = true;
+ }
+ }
+
+ op(_GUARD_CODE_VERSION_RETURN_VALUE, (version/2 -- )) {
+ if (ctx->frame->caller) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ }
+
+ op(_GUARD_CODE_VERSION_YIELD_VALUE, (version/2 -- )) {
+ if (ctx->frame->caller) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ }
+
+ op(_GUARD_CODE_VERSION_RETURN_GENERATOR, (version/2 -- )) {
+ if (ctx->frame->caller) {
+ 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);
- // TO DO
- // Normal function calls to known functions
- // do not need an IP guard.
+ if (sym_get_func_version(ctx->frame->callable) != 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) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
}
op(_GUARD_IP_YIELD_VALUE, (ip/4 --)) {
JitOptRef callable;
callable = stack_pointer[-2 - oparg];
uint32_t func_version = (uint32_t)this_instr->operand0;
- if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) {
- assert(PyFunction_Check(sym_get_const(ctx, callable)));
- ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
- uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)sym_get_const(ctx, callable);
+ if (sym_get_func_version(callable) == func_version) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ else {
+ sym_set_func_version(ctx, callable, func_version);
}
- sym_set_type(callable, &PyFunction_Type);
break;
}
}
case _GUARD_CODE_VERSION__PUSH_FRAME: {
+ uint32_t version = (uint32_t)this_instr->operand0;
+ PyCodeObject *co = get_current_code_object(ctx);
+ if (co->co_version == version) {
+ _Py_BloomFilter_Add(dependencies, co);
+ if (sym_get_func_version(ctx->frame->callable) == version) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ }
+ else {
+ ctx->done = true;
+ }
break;
}
case _GUARD_CODE_VERSION_YIELD_VALUE: {
+ uint32_t version = (uint32_t)this_instr->operand0;
+ if (ctx->frame->caller) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
break;
}
case _GUARD_CODE_VERSION_RETURN_VALUE: {
+ uint32_t version = (uint32_t)this_instr->operand0;
+ if (ctx->frame->caller) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
break;
}
case _GUARD_CODE_VERSION_RETURN_GENERATOR: {
+ uint32_t version = (uint32_t)this_instr->operand0;
+ if (ctx->frame->caller) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
break;
}
PyObject *ip = (PyObject *)this_instr->operand0;
(void)ip;
stack_pointer = sym_set_stack_depth((int)this_instr->operand1, stack_pointer);
+ if (sym_get_func_version(ctx->frame->callable) != 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) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
break;
}
information. Though symbols logically progress through all intermediate nodes,
we often skip in-between states for convenience:
- UNKNOWN-------------------+------+
- | | | |
-NULL | | RECORDED_VALUE*
-| | | | <- Anything below this level is an object.
-| NON_NULL-+ | |
-| | | | | <- Anything below this level has a known type version.
-| 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.
-| TUPLE | | | TRUTHINESS | |
-| | | | | | | | <- Anything below this level is a known constant.
-| KNOWN_VALUE--+----------+------+
-| | <- Anything below this level is unreachable.
+ UNKNOWN---------------------+------+
+ | | | |
+NULL | | RECORDED_VALUE*
+| | | | <- Anything below this level is an object.
+| NON_NULL---------+ | |
+| | | | | <- Anything below this level has a known type version.
+| 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.
+| 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 true;
+ 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:
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();
frame->func = func;
}
assert(frame->stack_pointer != NULL);
+ frame->callable = callable;
return frame;
}
frame->locals[i] = local;
}
+ frame->callable = _Py_uop_sym_new_not_null(ctx);
+
/* Most optimizations rely on code objects being immutable (including sys._getframe modifications),
* and up to date for instrumentation. */
_Py_BloomFilter_Add(ctx->dependencies, co);
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*/