diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index c5399ad8e0497f..70bbcb4e15e8d6 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -121,7 +121,9 @@ PyAPI_FUNC(int) _PyDict_SetItem_KnownHash_LockHeld(PyDictObject *mp, PyObject *k PyAPI_FUNC(int) _PyDict_GetItemRef_KnownHash_LockHeld(PyDictObject *op, PyObject *key, Py_hash_t hash, PyObject **result); extern int _PyDict_GetItemRef_KnownHash(PyDictObject *op, PyObject *key, Py_hash_t hash, PyObject **result); extern int _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **result); +extern int _PyDict_GetItemStackRef_KnownHash(PyDictObject *op, PyObject *key, Py_hash_t hash, _PyStackRef *result); extern int _PyObjectDict_SetItem(PyTypeObject *tp, PyObject *obj, PyObject **dictptr, PyObject *name, PyObject *value); +extern int _PyDict_GetItemStackRef(PyObject *op, PyObject *key, _PyStackRef *result); extern int _PyDict_Pop_KnownHash( PyDictObject *dict, diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index c7af720b1ce43d..3cae612612437e 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -623,6 +623,24 @@ _Py_TryXGetRef(PyObject **ptr) return NULL; } +// This belongs here rather than pycore_stackref.h because including pycore_object.h +// there causes a circular include. +static inline _PyStackRef +_Py_TryXGetStackRef(PyObject **ptr) +{ + PyObject *value = _Py_atomic_load_ptr(ptr); + if (value == NULL) { + return PyStackRef_NULL; + } + if (_Py_IsImmortal(value) || _PyObject_HasDeferredRefcount(value)) { + return (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED }; + } + if (_Py_TryIncrefCompare(ptr, value)) { + return PyStackRef_FromPyObjectSteal(value); + } + return PyStackRef_NULL; +} + /* Like Py_NewRef but also optimistically sets _Py_REF_MAYBE_WEAKREF on objects owned by a different thread. */ static inline PyObject * @@ -816,7 +834,8 @@ extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyObject *name, PyObject *value); extern bool _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr); - +extern bool _PyObject_TryGetInstanceAttributeStackRef(PyObject *obj, PyObject *name, + _PyStackRef *attr); #ifdef Py_GIL_DISABLED # define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-1) # define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-2) @@ -863,6 +882,7 @@ PyAPI_FUNC(PyObject*) _PyObject_LookupSpecialMethod(PyObject *self, PyObject *at extern int _PyObject_IsAbstract(PyObject *); PyAPI_FUNC(int) _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method); +PyAPI_FUNC(int) _PyObject_GetMethodStackRef(PyObject *obj, PyObject *name, _PyStackRef *method, _PyStackRef *spare); extern PyObject* _PyObject_NextNotImplemented(PyObject *); // Pickle support. @@ -900,6 +920,8 @@ PyAPI_DATA(int) _Py_SwappedOp[]; extern void _Py_GetConstant_Init(void); +extern void _PyType_LookupStackRef(PyTypeObject *type, PyObject *name, _PyStackRef *result); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 58e583eabbcc46..d510c692b1ee20 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -739,7 +739,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case LIST_EXTEND: return 1 + (oparg-1); case LOAD_ATTR: - return 1 + (oparg & 1); + return 1 + ((oparg & 1) ? 1 : 0); case LOAD_ATTR_CLASS: return 1 + (oparg & 1); case LOAD_ATTR_CLASS_WITH_METACLASS_CHECK: diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2090008055b7c0..b6a2ea9ef1acf5 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -2336,6 +2336,21 @@ _PyDict_GetItemRef_KnownHash(PyDictObject *op, PyObject *key, Py_hash_t hash, Py return 1; // key is present } +int +_PyDict_GetItemStackRef_KnownHash(PyDictObject *op, PyObject *key, Py_hash_t hash, _PyStackRef *result) +{ + Py_ssize_t ix = _Py_dict_lookup_threadsafe_stackref(op, key, hash, result); + assert(ix >= 0 || PyStackRef_IsNull(*result)); + if (ix == DKIX_ERROR) { + *result = PyStackRef_NULL; + return -1; + } + if (PyStackRef_IsNull(*result)) { + return 0; // missing key + } + return 1; // key is present +} + int PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result) { @@ -2354,6 +2369,24 @@ PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result) return _PyDict_GetItemRef_KnownHash((PyDictObject *)op, key, hash, result); } +int +_PyDict_GetItemStackRef(PyObject *op, PyObject *key, _PyStackRef *result) +{ + if (!PyDict_Check(op)) { + PyErr_BadInternalCall(); + *result = PyStackRef_NULL; + return -1; + } + + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + *result = PyStackRef_NULL; + return -1; + } + + return _PyDict_GetItemStackRef_KnownHash((PyDictObject *)op, key, hash, result); +} + int _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **result) { @@ -7097,6 +7130,81 @@ _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr #endif } +bool +_PyObject_TryGetInstanceAttributeStackRef(PyObject *obj, PyObject *name, _PyStackRef *attr) +{ + assert(PyUnicode_CheckExact(name)); + PyDictValues *values = _PyObject_InlineValues(obj); + if (!FT_ATOMIC_LOAD_UINT8(values->valid)) { + return false; + } + + PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj)); + assert(keys != NULL); + Py_ssize_t ix = _PyDictKeys_StringLookup(keys, name); + if (ix == DKIX_EMPTY) { + *attr = PyStackRef_NULL; + return true; + } + +#ifdef Py_GIL_DISABLED + *attr = _Py_TryXGetStackRef(&values->values[ix]); + if (PyStackRef_AsPyObjectBorrow(*attr) == _Py_atomic_load_ptr_acquire(&values->values[ix])) { + return true; + } + + PyDictObject *dict = _PyObject_GetManagedDict(obj); + if (dict == NULL) { + // No dict, lock the object to prevent one from being + // materialized... + bool success = false; + Py_BEGIN_CRITICAL_SECTION(obj); + + dict = _PyObject_GetManagedDict(obj); + if (dict == NULL) { + // Still no dict, we can read from the values + assert(values->valid); + *attr = _Py_TryXGetStackRef(&values->values[ix]); + success = true; + } + + Py_END_CRITICAL_SECTION(); + + if (success) { + return true; + } + } + + // We have a dictionary, we'll need to lock it to prevent + // the values from being resized. + assert(dict != NULL); + + bool success; + Py_BEGIN_CRITICAL_SECTION(dict); + + if (dict->ma_values == values && FT_ATOMIC_LOAD_UINT8(values->valid)) { + *attr = _Py_TryXGetStackRef(&values->values[ix]); + success = true; + } else { + // Caller needs to lookup from the dictionary + success = false; + } + + Py_END_CRITICAL_SECTION(); + + return success; +#else + PyObject *value = values->values[ix]; + if (value != NULL) { + *attr = PyStackRef_FromPyObjectNew(value); + } + else { + *attr = PyStackRef_NULL; + } + return true; +#endif +} + int _PyObject_IsInstanceDictEmpty(PyObject *obj) { diff --git a/Objects/object.c b/Objects/object.c index 052dea9ad1feff..c11a20049929e4 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1620,6 +1620,129 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) return 0; } +int +_PyObject_GetMethodStackRef(PyObject *obj, PyObject *name, _PyStackRef *method, _PyStackRef *spare) +{ +#ifdef Py_GIL_DISABLED + int meth_found = 0; + + assert(PyStackRef_IsNull(*method)); + + PyTypeObject *tp = Py_TYPE(obj); + if (!_PyType_IsReady(tp)) { + if (PyType_Ready(tp) < 0) { + return 0; + } + } + + if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) { + PyObject *attr_o = PyObject_GetAttr(obj, name); + if (attr_o != NULL) { + *method = PyStackRef_FromPyObjectSteal(attr_o); + } + else { + *method = PyStackRef_NULL; + } + return 0; + } + + _PyType_LookupStackRef(tp, name, method); + *spare = *method; + _PyStackRef descr_st = *method; + descrgetfunc f = NULL; + if (!PyStackRef_IsNull(descr_st)) { + if (_PyType_HasFeature(PyStackRef_TYPE(descr_st), Py_TPFLAGS_METHOD_DESCRIPTOR)) { + meth_found = 1; + } else { + f = PyStackRef_TYPE(descr_st)->tp_descr_get; + if (f != NULL && PyDescr_IsData(PyStackRef_AsPyObjectBorrow(descr_st))) { + PyObject *call_res_o = f(PyStackRef_AsPyObjectBorrow(descr_st), obj, (PyObject *)Py_TYPE(obj)); + if (call_res_o != NULL) { + *method = PyStackRef_FromPyObjectSteal(call_res_o); + } + else { + *method = PyStackRef_NULL; + } + PyStackRef_CLOSE(descr_st); + return 0; + } + } + } + PyObject *dict; + if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) && + _PyObject_TryGetInstanceAttributeStackRef(obj, name, method)) { + if (!PyStackRef_IsNull(*method)) { + PyStackRef_XCLOSE(descr_st); + return 0; + } + dict = NULL; + } + else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) { + dict = (PyObject *)_PyObject_GetManagedDict(obj); + } + else { + PyObject **dictptr = _PyObject_ComputedDictPointer(obj); + if (dictptr != NULL) { + dict = *dictptr; + } + else { + dict = NULL; + } + } + if (dict != NULL) { + Py_INCREF(dict); + if (_PyDict_GetItemStackRef(dict, name, method) != 0) { + // found or error + Py_DECREF(dict); + PyStackRef_CLOSE(descr_st); + return 0; + } + // not found + Py_DECREF(dict); + } + + if (meth_found) { + *method = descr_st; + return 1; + } + + if (f != NULL) { + PyObject *call_res_o = f(PyStackRef_AsPyObjectBorrow(descr_st), obj, (PyObject *)Py_TYPE(obj)); + if (call_res_o != NULL) { + *method = PyStackRef_FromPyObjectSteal(call_res_o); + } + else { + *method = PyStackRef_NULL; + } + PyStackRef_CLOSE(descr_st); + return 0; + } + + if (!PyStackRef_IsNull(descr_st)) { + *method = descr_st; + return 0; + } + + *method = PyStackRef_NULL; + PyErr_Format(PyExc_AttributeError, + "'%.100s' object has no attribute '%U'", + tp->tp_name, name); + + _PyObject_SetAttributeErrorContext(obj, name); + return 0; +#else + PyObject *res = NULL; + int err = _PyObject_GetMethod(obj, name, &res); + if (res == NULL) { + *method = PyStackRef_NULL; + } + else { + *method = PyStackRef_FromPyObjectSteal(res); + } + return err; +#endif +} + /* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */ PyObject * diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a6cf3da542b691..903d881d0756bd 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5450,6 +5450,59 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) return res; } + +static void +find_name_in_mro_stackref(PyTypeObject *type, PyObject *name, int *error, _PyStackRef *result) +{ + ASSERT_TYPE_LOCK_HELD(); + + Py_hash_t hash = _PyObject_HashFast(name); + if (hash == -1) { + *error = -1; + *result = PyStackRef_NULL; + return; + } + + /* Look in tp_dict of types in MRO */ + PyObject *mro = lookup_tp_mro(type); + if (mro == NULL) { + if (!is_readying(type)) { + if (PyType_Ready(type) < 0) { + *error = -1; + *result = PyStackRef_NULL; + return; + } + mro = lookup_tp_mro(type); + } + if (mro == NULL) { + *error = 1; + *result = PyStackRef_NULL; + return; + } + } + + /* Keep a strong reference to mro because type->tp_mro can be replaced + during dict lookup, e.g. when comparing to non-string keys. */ + Py_INCREF(mro); + Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *base = PyTuple_GET_ITEM(mro, i); + PyObject *dict = lookup_tp_dict(_PyType_CAST(base)); + assert(dict && PyDict_Check(dict)); + int code = _PyDict_GetItemStackRef_KnownHash((PyDictObject *)dict, name, hash, result); + if (code < 0) { + *error = -1; + goto done; + } + if (code == 1) { + break; + } + } + *error = 0; +done: + Py_DECREF(mro); +} + /* Check if the "readied" PyUnicode name is a double-underscore special name. */ static int @@ -5622,6 +5675,99 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name) return res; } +void +_PyType_LookupStackRef(PyTypeObject *type, PyObject *name, _PyStackRef *result) +{ + int error; + PyInterpreterState *interp = _PyInterpreterState_GET(); + + unsigned int h = MCACHE_HASH_METHOD(type, name); + struct type_cache *cache = get_type_cache(); + struct type_cache_entry *entry = &cache->hashtable[h]; +#ifdef Py_GIL_DISABLED + // synchronize-with other writing threads by doing an acquire load on the sequence + while (1) { + int sequence = _PySeqLock_BeginRead(&entry->sequence); + uint32_t entry_version = _Py_atomic_load_uint32_relaxed(&entry->version); + uint32_t type_version = _Py_atomic_load_uint32_acquire(&type->tp_version_tag); + if (entry_version == type_version && + _Py_atomic_load_ptr_relaxed(&entry->name) == name) { + OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name)); + OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name)); + PyObject *value = _Py_atomic_load_ptr_relaxed(&entry->value); + if (value == NULL) { + *result = PyStackRef_NULL; + } + else { + *result = PyStackRef_FromPyObjectNew(value); + } + // If the sequence is still valid then we're done + if (_PySeqLock_EndRead(&entry->sequence, sequence)) { + return; + } + *result = PyStackRef_NULL; + } + else { + // cache miss + break; + } + } +#else + if (entry->version == type->tp_version_tag && + entry->name == name) { + assert(type->tp_version_tag); + OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name)); + OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name)); + *result = PyStackRef_FromPyObjectNew(entry->value); + } +#endif + OBJECT_STAT_INC_COND(type_cache_misses, !is_dunder_name(name)); + OBJECT_STAT_INC_COND(type_cache_dunder_misses, is_dunder_name(name)); + + /* We may end up clearing live exceptions below, so make sure it's ours. */ + assert(!PyErr_Occurred()); + + // We need to atomically do the lookup and capture the version before + // anyone else can modify our mro or mutate the type. + + int has_version = 0; + int version = 0; + BEGIN_TYPE_LOCK(); + find_name_in_mro_stackref(type, name, &error, result); + if (MCACHE_CACHEABLE_NAME(name)) { + has_version = assign_version_tag(interp, type); + version = type->tp_version_tag; + } + END_TYPE_LOCK(); + + /* Only put NULL results into cache if there was no error. */ + if (error) { + /* It's not ideal to clear the error condition, + but this function is documented as not setting + an exception, and I don't want to change that. + E.g., when PyType_Ready() can't proceed, it won't + set the "ready" flag, so future attempts to ready + the same type will call it again -- hopefully + in a context that propagates the exception out. + */ + if (error == -1) { + PyErr_Clear(); + } + *result = PyStackRef_NULL; + return; + } + + if (has_version) { +#if Py_GIL_DISABLED + update_cache_gil_disabled(entry, name, version, + PyStackRef_AsPyObjectBorrow(*result)); +#else + PyObject *old_value = update_cache(entry, name, version, PyStackRef_AsPyObjectBorrow(*result)); + Py_DECREF(old_value); +#endif + } +} + PyObject * _PyType_Lookup(PyTypeObject *type, PyObject *name) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 04983fd861ec59..6a62ba1ddf01d9 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2054,20 +2054,19 @@ dummy_func( #endif /* ENABLE_SPECIALIZATION */ } - op(_LOAD_ATTR, (owner -- attr, self_or_null if (oparg & 1))) { + op(_LOAD_ATTR, (owner -- attr[1], self_or_null[1] if (oparg & 1))) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1); - PyObject *attr_o; if (oparg & 1) { /* Designed to work in tandem with CALL, pushes two values. */ - attr_o = NULL; - int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o); + *attr = PyStackRef_NULL; + int is_meth = _PyObject_GetMethodStackRef(PyStackRef_AsPyObjectBorrow(owner), name, attr, self_or_null); if (is_meth) { /* We can bypass temporary bound method object. meth is unbound method and obj is self. meth | self | arg1 | ... | argN */ - assert(attr_o != NULL); // No errors on this branch - self_or_null = owner; // Transfer ownership + assert(!PyStackRef_IsNull(*attr)); // No errors on this branch + *self_or_null = owner; // Transfer ownership DEAD(owner); } else { @@ -2078,19 +2077,24 @@ dummy_func( meth | NULL | arg1 | ... | argN */ DECREF_INPUTS(); - ERROR_IF(attr_o == NULL, error); - self_or_null = PyStackRef_NULL; + ERROR_IF(PyStackRef_IsNull(*attr), error); + *self_or_null = PyStackRef_NULL; } } else { /* Classic, pushes one value. */ - attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); - DECREF_INPUTS(); - ERROR_IF(attr_o == NULL, error); + PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); + if (attr_o == NULL) { + *attr = PyStackRef_NULL; + } + else { + *attr = PyStackRef_FromPyObjectSteal(attr_o); + } /* We need to define self_or_null on all paths */ - self_or_null = PyStackRef_NULL; + *self_or_null = PyStackRef_NULL; + DECREF_INPUTS(); + ERROR_IF(PyStackRef_IsNull(*attr), error); } - attr = PyStackRef_FromPyObjectSteal(attr_o); } macro(LOAD_ATTR) = diff --git a/Python/compile.c b/Python/compile.c index cbfba7f493e07d..82db6749db207d 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1335,6 +1335,11 @@ optimize_and_assemble_code_unit(struct compiler_unit *u, PyObject *const_cache, goto error; } + /* Reserve an extra word on the stack to ensure there is space for uops to + pass at least one item on the stack to a subsequent uop. + */ + stackdepth++; + /** Assembly **/ co = _PyAssemble_MakeCodeObject(&u->u_metadata, const_cache, consts, stackdepth, &optimized_instrs, nlocalsplus, diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 494ace1bd85822..d8abece25071a4 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2470,25 +2470,26 @@ case _LOAD_ATTR: { _PyStackRef owner; - _PyStackRef attr; - _PyStackRef self_or_null = PyStackRef_NULL; + _PyStackRef *attr; + _PyStackRef *self_or_null = NULL; oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; + attr = &stack_pointer[-1]; + self_or_null = &stack_pointer[0]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1); - PyObject *attr_o; if (oparg & 1) { /* Designed to work in tandem with CALL, pushes two values. */ - attr_o = NULL; + *attr = PyStackRef_NULL; _PyFrame_SetStackPointer(frame, stack_pointer); - int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o); + int is_meth = _PyObject_GetMethodStackRef(PyStackRef_AsPyObjectBorrow(owner), name, attr, self_or_null); stack_pointer = _PyFrame_GetStackPointer(frame); if (is_meth) { /* We can bypass temporary bound method object. meth is unbound method and obj is self. meth | self | arg1 | ... | argN */ - assert(attr_o != NULL); // No errors on this branch - self_or_null = owner; // Transfer ownership + assert(!PyStackRef_IsNull(*attr)); // No errors on this branch + *self_or_null = owner; // Transfer ownership } else { /* meth is not an unbound method (but a regular attr, or @@ -2498,24 +2499,27 @@ meth | NULL | arg1 | ... | argN */ PyStackRef_CLOSE(owner); - if (attr_o == NULL) JUMP_TO_ERROR(); - self_or_null = PyStackRef_NULL; + if (PyStackRef_IsNull(*attr)) JUMP_TO_ERROR(); + *self_or_null = PyStackRef_NULL; } } else { /* Classic, pushes one value. */ _PyFrame_SetStackPointer(frame, stack_pointer); - attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); + PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); stack_pointer = _PyFrame_GetStackPointer(frame); - PyStackRef_CLOSE(owner); - if (attr_o == NULL) JUMP_TO_ERROR(); + if (attr_o == NULL) { + *attr = PyStackRef_NULL; + } + else { + *attr = PyStackRef_FromPyObjectSteal(attr_o); + } /* We need to define self_or_null on all paths */ - self_or_null = PyStackRef_NULL; + *self_or_null = PyStackRef_NULL; + PyStackRef_CLOSE(owner); + if (PyStackRef_IsNull(*attr)) JUMP_TO_ERROR(); } - attr = PyStackRef_FromPyObjectSteal(attr_o); - stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = self_or_null; - stack_pointer += (oparg & 1); + stack_pointer += ((oparg & 1) ? 1 : 0); assert(WITHIN_STACK_BOUNDS()); break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 77bf6ad3781f17..a562bb67c9e2da 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -5193,8 +5193,8 @@ _Py_CODEUNIT* const this_instr = next_instr - 10; (void)this_instr; _PyStackRef owner; - _PyStackRef attr; - _PyStackRef self_or_null = PyStackRef_NULL; + _PyStackRef *attr; + _PyStackRef *self_or_null = NULL; // _SPECIALIZE_LOAD_ATTR { owner = stack_pointer[-1]; @@ -5216,21 +5216,22 @@ /* Skip 8 cache entries */ // _LOAD_ATTR { + attr = &stack_pointer[-1]; + self_or_null = &stack_pointer[0]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1); - PyObject *attr_o; if (oparg & 1) { /* Designed to work in tandem with CALL, pushes two values. */ - attr_o = NULL; + *attr = PyStackRef_NULL; _PyFrame_SetStackPointer(frame, stack_pointer); - int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o); + int is_meth = _PyObject_GetMethodStackRef(PyStackRef_AsPyObjectBorrow(owner), name, attr, self_or_null); stack_pointer = _PyFrame_GetStackPointer(frame); if (is_meth) { /* We can bypass temporary bound method object. meth is unbound method and obj is self. meth | self | arg1 | ... | argN */ - assert(attr_o != NULL); // No errors on this branch - self_or_null = owner; // Transfer ownership + assert(!PyStackRef_IsNull(*attr)); // No errors on this branch + *self_or_null = owner; // Transfer ownership } else { /* meth is not an unbound method (but a regular attr, or @@ -5240,25 +5241,28 @@ meth | NULL | arg1 | ... | argN */ PyStackRef_CLOSE(owner); - if (attr_o == NULL) goto pop_1_error; - self_or_null = PyStackRef_NULL; + if (PyStackRef_IsNull(*attr)) goto pop_1_error; + *self_or_null = PyStackRef_NULL; } } else { /* Classic, pushes one value. */ _PyFrame_SetStackPointer(frame, stack_pointer); - attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); + PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); stack_pointer = _PyFrame_GetStackPointer(frame); - PyStackRef_CLOSE(owner); - if (attr_o == NULL) goto pop_1_error; + if (attr_o == NULL) { + *attr = PyStackRef_NULL; + } + else { + *attr = PyStackRef_FromPyObjectSteal(attr_o); + } /* We need to define self_or_null on all paths */ - self_or_null = PyStackRef_NULL; + *self_or_null = PyStackRef_NULL; + PyStackRef_CLOSE(owner); + if (PyStackRef_IsNull(*attr)) goto pop_1_error; } - attr = PyStackRef_FromPyObjectSteal(attr_o); } - stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = self_or_null; - stack_pointer += (oparg & 1); + stack_pointer += ((oparg & 1) ? 1 : 0); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); } diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py index de31d9b232f9df..c6588b1b962c72 100644 --- a/Tools/cases_generator/parsing.py +++ b/Tools/cases_generator/parsing.py @@ -270,8 +270,8 @@ def cache_effect(self) -> CacheEffect | None: @contextual def stack_effect(self) -> StackEffect | None: - # IDENTIFIER [':' IDENTIFIER [TIMES]] ['if' '(' expression ')'] - # | IDENTIFIER '[' expression ']' + # IDENTIFIER [':' IDENTIFIER [[TIMES]] ['if' '(' expression ')'] + # | IDENTIFIER '[' expression ']' ['if' '(' expression ')'] if tkn := self.expect(lx.IDENTIFIER): type_text = "" if self.expect(lx.COLON): @@ -279,12 +279,6 @@ def stack_effect(self) -> StackEffect | None: if self.expect(lx.TIMES): type_text += " *" cond_text = "" - if self.expect(lx.IF): - self.require(lx.LPAREN) - if not (cond := self.expression()): - raise self.make_syntax_error("Expected condition") - self.require(lx.RPAREN) - cond_text = cond.text.strip() size_text = "" if self.expect(lx.LBRACKET): if type_text or cond_text: @@ -293,6 +287,12 @@ def stack_effect(self) -> StackEffect | None: raise self.make_syntax_error("Expected expression") self.require(lx.RBRACKET) size_text = size.text.strip() + if self.expect(lx.IF): + self.require(lx.LPAREN) + if not (cond := self.expression()): + raise self.make_syntax_error("Expected condition") + self.require(lx.RPAREN) + cond_text = cond.text.strip() return StackEffect(tkn.text, type_text, cond_text, size_text) return None