gh-148510: restore func_version check in _LOAD_ATTR_PROPERTY_FRAME (GH-148528)

This commit is contained in:
Neko Asakura 2026-04-14 10:44:39 -04:00 committed by GitHub
parent 4af46b4ab5
commit 52a7f1b7f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 603 additions and 429 deletions

View file

@ -2919,14 +2919,12 @@ dummy_func(
_LOAD_ATTR_CLASS +
_PUSH_NULL_CONDITIONAL;
op(_LOAD_ATTR_PROPERTY_FRAME, (fget/4, owner -- new_frame)) {
op(_LOAD_ATTR_PROPERTY_FRAME, (func_version/2, fget/4, owner -- new_frame)) {
assert((oparg & 1) == 0);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
EXIT_IF(f->func_version != func_version);
PyCodeObject *code = (PyCodeObject *)f->func_code;
EXIT_IF((code->co_flags & (CO_VARKEYWORDS | CO_VARARGS | CO_OPTIMIZED)) != CO_OPTIMIZED);
EXIT_IF(code->co_kwonlyargcount);
EXIT_IF(code->co_argcount != 1);
EXIT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize));
STAT_INC(LOAD_ATTR, hit);
_PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
@ -2940,7 +2938,6 @@ dummy_func(
_RECORD_TOS_TYPE +
_GUARD_TYPE_VERSION +
_CHECK_PEP_523 +
unused/2 +
_LOAD_ATTR_PROPERTY_FRAME +
_SAVE_RETURN_OFFSET +
_PUSH_FRAME;

View file

@ -11450,6 +11450,41 @@
break;
}
case _LOAD_ATTR_PROPERTY_FRAME_r01: {
CHECK_CURRENT_CACHED_VALUES(0);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef owner;
_PyStackRef new_frame;
oparg = CURRENT_OPARG();
owner = stack_pointer[-1];
uint32_t func_version = (uint32_t)CURRENT_OPERAND0_32();
PyObject *fget = (PyObject *)CURRENT_OPERAND1_64();
assert((oparg & 1) == 0);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
if (f->func_version != func_version) {
UOP_STAT_INC(uopcode, miss);
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_JUMP_TARGET();
}
PyCodeObject *code = (PyCodeObject *)f->func_code;
if (!_PyThreadState_HasStackSpace(tstate, code->co_framesize)) {
UOP_STAT_INC(uopcode, miss);
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_JUMP_TARGET();
}
STAT_INC(LOAD_ATTR, hit);
_PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
pushed_frame->localsplus[0] = owner;
new_frame = PyStackRef_Wrap(pushed_frame);
_tos_cache0 = new_frame;
SET_CURRENT_CACHED_VALUES(1);
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _LOAD_ATTR_PROPERTY_FRAME_r11: {
CHECK_CURRENT_CACHED_VALUES(1);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
@ -11458,29 +11493,18 @@
_PyStackRef _stack_item_0 = _tos_cache0;
oparg = CURRENT_OPARG();
owner = _stack_item_0;
PyObject *fget = (PyObject *)CURRENT_OPERAND0_64();
uint32_t func_version = (uint32_t)CURRENT_OPERAND0_32();
PyObject *fget = (PyObject *)CURRENT_OPERAND1_64();
assert((oparg & 1) == 0);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
if (f->func_version != func_version) {
UOP_STAT_INC(uopcode, miss);
_tos_cache0 = owner;
SET_CURRENT_CACHED_VALUES(1);
JUMP_TO_JUMP_TARGET();
}
PyCodeObject *code = (PyCodeObject *)f->func_code;
if ((code->co_flags & (CO_VARKEYWORDS | CO_VARARGS | CO_OPTIMIZED)) != CO_OPTIMIZED) {
UOP_STAT_INC(uopcode, miss);
_tos_cache0 = owner;
SET_CURRENT_CACHED_VALUES(1);
JUMP_TO_JUMP_TARGET();
}
if (code->co_kwonlyargcount) {
UOP_STAT_INC(uopcode, miss);
_tos_cache0 = owner;
SET_CURRENT_CACHED_VALUES(1);
JUMP_TO_JUMP_TARGET();
}
if (code->co_argcount != 1) {
UOP_STAT_INC(uopcode, miss);
_tos_cache0 = owner;
SET_CURRENT_CACHED_VALUES(1);
JUMP_TO_JUMP_TARGET();
}
if (!_PyThreadState_HasStackSpace(tstate, code->co_framesize)) {
UOP_STAT_INC(uopcode, miss);
_tos_cache0 = owner;
@ -11492,13 +11516,95 @@
pushed_frame->localsplus[0] = owner;
new_frame = PyStackRef_Wrap(pushed_frame);
_tos_cache0 = new_frame;
_tos_cache1 = PyStackRef_ZERO_BITS;
_tos_cache2 = PyStackRef_ZERO_BITS;
SET_CURRENT_CACHED_VALUES(1);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _LOAD_ATTR_PROPERTY_FRAME_r22: {
CHECK_CURRENT_CACHED_VALUES(2);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef owner;
_PyStackRef new_frame;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
oparg = CURRENT_OPARG();
owner = _stack_item_1;
uint32_t func_version = (uint32_t)CURRENT_OPERAND0_32();
PyObject *fget = (PyObject *)CURRENT_OPERAND1_64();
assert((oparg & 1) == 0);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
if (f->func_version != func_version) {
UOP_STAT_INC(uopcode, miss);
_tos_cache1 = owner;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(2);
JUMP_TO_JUMP_TARGET();
}
PyCodeObject *code = (PyCodeObject *)f->func_code;
if (!_PyThreadState_HasStackSpace(tstate, code->co_framesize)) {
UOP_STAT_INC(uopcode, miss);
_tos_cache1 = owner;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(2);
JUMP_TO_JUMP_TARGET();
}
STAT_INC(LOAD_ATTR, hit);
_PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
pushed_frame->localsplus[0] = owner;
new_frame = PyStackRef_Wrap(pushed_frame);
_tos_cache1 = new_frame;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(2);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _LOAD_ATTR_PROPERTY_FRAME_r33: {
CHECK_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef owner;
_PyStackRef new_frame;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
_PyStackRef _stack_item_2 = _tos_cache2;
oparg = CURRENT_OPARG();
owner = _stack_item_2;
uint32_t func_version = (uint32_t)CURRENT_OPERAND0_32();
PyObject *fget = (PyObject *)CURRENT_OPERAND1_64();
assert((oparg & 1) == 0);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
if (f->func_version != func_version) {
UOP_STAT_INC(uopcode, miss);
_tos_cache2 = owner;
_tos_cache1 = _stack_item_1;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(3);
JUMP_TO_JUMP_TARGET();
}
PyCodeObject *code = (PyCodeObject *)f->func_code;
if (!_PyThreadState_HasStackSpace(tstate, code->co_framesize)) {
UOP_STAT_INC(uopcode, miss);
_tos_cache2 = owner;
_tos_cache1 = _stack_item_1;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(3);
JUMP_TO_JUMP_TARGET();
}
STAT_INC(LOAD_ATTR, hit);
_PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
pushed_frame->localsplus[0] = owner;
new_frame = PyStackRef_Wrap(pushed_frame);
_tos_cache2 = new_frame;
_tos_cache1 = _stack_item_1;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
/* _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN is not a viable micro-op for tier 2 because it has too many cache entries */
case _GUARD_DORV_NO_DICT_r01: {

View file

@ -8839,29 +8839,19 @@
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
/* Skip 2 cache entries */
// _LOAD_ATTR_PROPERTY_FRAME
{
uint32_t func_version = read_u32(&this_instr[4].cache);
PyObject *fget = read_obj(&this_instr[6].cache);
assert((oparg & 1) == 0);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
if (f->func_version != func_version) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
PyCodeObject *code = (PyCodeObject *)f->func_code;
if ((code->co_flags & (CO_VARKEYWORDS | CO_VARARGS | CO_OPTIMIZED)) != CO_OPTIMIZED) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
if (code->co_kwonlyargcount) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
if (code->co_argcount != 1) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
if (!_PyThreadState_HasStackSpace(tstate, code->co_framesize)) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));

View file

@ -257,6 +257,7 @@ add_op(JitOptContext *ctx, _PyUOpInstruction *this_instr,
#define sym_get_probable_type _Py_uop_sym_get_probable_type
#define sym_matches_type _Py_uop_sym_matches_type
#define sym_matches_type_version _Py_uop_sym_matches_type_version
#define sym_get_type_version _Py_uop_sym_get_type_version
#define sym_set_null(SYM) _Py_uop_sym_set_null(ctx, SYM)
#define sym_set_non_null(SYM) _Py_uop_sym_set_non_null(ctx, SYM)
#define sym_set_type(SYM, TYPE) _Py_uop_sym_set_type(ctx, SYM, TYPE)

View file

@ -20,6 +20,7 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame;
#define sym_new_null _Py_uop_sym_new_null
#define sym_matches_type _Py_uop_sym_matches_type
#define sym_matches_type_version _Py_uop_sym_matches_type_version
#define sym_get_type_version _Py_uop_sym_get_type_version
#define sym_get_type _Py_uop_sym_get_type
#define sym_has_type _Py_uop_sym_has_type
#define sym_set_null(SYM) _Py_uop_sym_set_null(ctx, SYM)
@ -138,15 +139,24 @@ dummy_func(void) {
assert(type_version);
if (sym_matches_type_version(owner, type_version)) {
ADD_OP(_NOP, 0, 0);
} else {
}
else {
PyTypeObject *probable_type = sym_get_probable_type(owner);
if (probable_type->tp_version_tag == type_version && sym_set_type_version(owner, type_version)) {
if (probable_type != NULL &&
probable_type->tp_version_tag == type_version) {
// Promote the probable type version to a known one.
sym_set_type(owner, probable_type);
sym_set_type_version(owner, type_version);
if ((probable_type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) == 0) {
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)probable_type);
_Py_BloomFilter_Add(dependencies, probable_type);
}
}
else {
ctx->contradiction = true;
ctx->done = true;
break;
}
}
}
@ -239,20 +249,20 @@ dummy_func(void) {
assert(this_instr[-1].opcode == _RECORD_TOS_TYPE);
if (sym_matches_type_version(owner, type_version)) {
ADD_OP(_NOP, 0, 0);
} else {
// add watcher so that whenever the type changes we invalidate this
PyTypeObject *type = _PyType_LookupByVersion(type_version);
// if the type is null, it was not found in the cache (there was a conflict)
// with the key, in which case we can't trust the version
if (type) {
// if the type version was set properly, then add a watcher
// if it wasn't this means that the type version was previously set to something else
// and we set the owner to bottom, so we don't need to add a watcher because we must have
// already added one earlier.
if (sym_set_type_version(owner, type_version)) {
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type);
_Py_BloomFilter_Add(dependencies, type);
}
}
else {
PyTypeObject *probable_type = sym_get_probable_type(owner);
if (probable_type != NULL &&
probable_type->tp_version_tag == type_version) {
sym_set_type(owner, probable_type);
sym_set_type_version(owner, type_version);
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)probable_type);
_Py_BloomFilter_Add(dependencies, probable_type);
}
else {
ctx->contradiction = true;
ctx->done = true;
break;
}
}
}
@ -983,15 +993,22 @@ dummy_func(void) {
_LOAD_CONST_INLINE, _SWAP);
}
op(_LOAD_ATTR_PROPERTY_FRAME, (fget/4, owner -- new_frame)) {
// + 1 for _SAVE_RETURN_OFFSET
// FIX ME -- This needs a version check and function watcher
PyCodeObject *co = (PyCodeObject *)((PyFunctionObject *)fget)->func_code;
op(_LOAD_ATTR_PROPERTY_FRAME, (func_version/2, fget/4, owner -- new_frame)) {
PyFunctionObject *func = (PyFunctionObject *)fget;
if (sym_get_type_version(owner) == 0 ||
func->func_version != func_version) {
ctx->contradiction = true;
ctx->done = true;
break;
}
_Py_BloomFilter_Add(dependencies, fget);
PyCodeObject *co = (PyCodeObject *)func->func_code;
_Py_UOpsAbstractFrame *f = frame_new(ctx, co, NULL, 0);
if (f == NULL) {
break;
}
f->locals[0] = owner;
f->func = func;
new_frame = PyJitRef_WrapInvalid(f);
}

View file

@ -2429,13 +2429,20 @@
assert(this_instr[-1].opcode == _RECORD_TOS_TYPE);
if (sym_matches_type_version(owner, type_version)) {
ADD_OP(_NOP, 0, 0);
} else {
PyTypeObject *type = _PyType_LookupByVersion(type_version);
if (type) {
if (sym_set_type_version(owner, type_version)) {
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type);
_Py_BloomFilter_Add(dependencies, type);
}
}
else {
PyTypeObject *probable_type = sym_get_probable_type(owner);
if (probable_type != NULL &&
probable_type->tp_version_tag == type_version) {
sym_set_type(owner, probable_type);
sym_set_type_version(owner, type_version);
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)probable_type);
_Py_BloomFilter_Add(dependencies, probable_type);
}
else {
ctx->contradiction = true;
ctx->done = true;
break;
}
}
break;
@ -2448,14 +2455,23 @@
assert(type_version);
if (sym_matches_type_version(owner, type_version)) {
ADD_OP(_NOP, 0, 0);
} else {
}
else {
PyTypeObject *probable_type = sym_get_probable_type(owner);
if (probable_type->tp_version_tag == type_version && sym_set_type_version(owner, type_version)) {
if (probable_type != NULL &&
probable_type->tp_version_tag == type_version) {
sym_set_type(owner, probable_type);
sym_set_type_version(owner, type_version);
if ((probable_type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) == 0) {
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)probable_type);
_Py_BloomFilter_Add(dependencies, probable_type);
}
}
else {
ctx->contradiction = true;
ctx->done = true;
break;
}
}
break;
}
@ -2598,13 +2614,23 @@
JitOptRef owner;
JitOptRef new_frame;
owner = stack_pointer[-1];
PyObject *fget = (PyObject *)this_instr->operand0;
PyCodeObject *co = (PyCodeObject *)((PyFunctionObject *)fget)->func_code;
uint32_t func_version = (uint32_t)this_instr->operand0;
PyObject *fget = (PyObject *)this_instr->operand1;
PyFunctionObject *func = (PyFunctionObject *)fget;
if (sym_get_type_version(owner) == 0 ||
func->func_version != func_version) {
ctx->contradiction = true;
ctx->done = true;
break;
}
_Py_BloomFilter_Add(dependencies, fget);
PyCodeObject *co = (PyCodeObject *)func->func_code;
_Py_UOpsAbstractFrame *f = frame_new(ctx, co, NULL, 0);
if (f == NULL) {
break;
}
f->locals[0] = owner;
f->func = func;
new_frame = PyJitRef_WrapInvalid(f);
stack_pointer[-1] = new_frame;
break;

View file

@ -846,8 +846,13 @@ do_specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject*
return -1;
}
#endif
uint32_t func_version = function_get_version(fget, LOAD_ATTR);
if (func_version == 0) {
return -1;
}
assert(tp_version != 0);
write_u32(lm_cache->type_version, tp_version);
write_u32(lm_cache->keys_version, func_version);
/* borrowed */
write_ptr(lm_cache->descr, fget);
specialize(instr, LOAD_ATTR_PROPERTY);