|
29 | 29 | #include "opcode.h"
|
30 | 30 | #include "pydtrace.h"
|
31 | 31 | #include "setobject.h"
|
| 32 | +#include "structmember.h" // struct PyMemberDef, T_OFFSET_EX |
32 | 33 |
|
33 | 34 | #include <ctype.h>
|
34 | 35 |
|
@@ -3169,114 +3170,165 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
|
3169 | 3170 | if (co_opcache != NULL && PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
|
3170 | 3171 | {
|
3171 | 3172 | if (co_opcache->optimized > 0) {
|
3172 |
| - /* Fast path -- cache hit makes LOAD_ATTR ~30% faster */ |
| 3173 | + // Fast path -- cache hit makes LOAD_ATTR ~30% faster. |
3173 | 3174 | la = &co_opcache->u.la;
|
3174 | 3175 | if (la->type == type && la->tp_version_tag == type->tp_version_tag)
|
3175 | 3176 | {
|
3176 |
| - assert(type->tp_dict != NULL); |
3177 |
| - assert(type->tp_dictoffset > 0); |
3178 |
| - |
3179 |
| - dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset); |
3180 |
| - <
8000
span class=pl-s1>dict = *dictptr; |
3181 |
| - if (dict != NULL && PyDict_CheckExact(dict)) { |
3182 |
| - Py_ssize_t hint = la->hint; |
3183 |
| - Py_INCREF(dict); |
3184 |
| - res = NULL; |
3185 |
| - la->hint = _PyDict_GetItemHint((PyDictObject*)dict, name, hint, &res); |
3186 |
| - |
| 3177 | + // Hint >= 0 is a dict index; hint == -1 is a dict miss. |
| 3178 | + // Hint < -1 is an inverted slot offset: offset is strictly > 0, |
| 3179 | + // so ~offset is strictly < -1 (assuming 2's complement). |
| 3180 | + if (la->hint < -1) { |
| 3181 | + // Even faster path -- slot hint. |
| 3182 | + Py_ssize_t offset = ~la->hint; |
| 3183 | + // fprintf(stderr, "Using hint for offset %zd\n", offset); |
| 3184 | + char *addr = (char *)owner + offset; |
| 3185 | + res = *(PyObject **)addr; |
3187 | 3186 | if (res != NULL) {
|
3188 |
| - if (la->hint == hint && hint >= 0) { |
3189 |
| - /* Our hint has helped -- cache hit. */ |
3190 |
| - OPCACHE_STAT_ATTR_HIT(); |
3191 |
| - } else { |
3192 |
| - /* The hint we provided didn't work. |
3193 |
| - Maybe next time? */ |
3194 |
| - OPCACHE_MAYBE_DEOPT_LOAD_ATTR(); |
3195 |
| - } |
3196 |
| - |
3197 | 3187 | Py_INCREF(res);
|
3198 | 3188 | SET_TOP(res);
|
3199 | 3189 | Py_DECREF(owner);
|
3200 |
| - Py_DECREF(dict); |
3201 | 3190 | DISPATCH();
|
| 3191 | + } |
| 3192 | + // Else slot is NULL. Fall through to slow path to raise AttributeError(name). |
| 3193 | + // Don't DEOPT, since the slot is still there. |
| 3194 | + } else { |
| 3195 | + // Fast path for dict. |
| 3196 | + assert(type->tp_dict != NULL); |
| 3197 | + assert(type->tp_dictoffset > 0); |
| 3198 | + |
| 3199 | + dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset); |
| 3200 | + dict = *dictptr; |
| 3201 | + if (dict != NULL && PyDict_CheckExact(dict)) { |
| 3202 | + Py_ssize_t hint = la->hint; |
| 3203 | + Py_INCREF(dict); |
| 3204 | + res = NULL; |
| 3205 | + la->hint = _PyDict_GetItemHint((PyDictObject*)dict, name, hint, &res); |
| 3206 | + |
| 3207 | + if (res != NULL) { |
| 3208 | + if (la->hint == hint && hint >= 0) { |
| 3209 | + // Our hint has helped -- cache hit. |
| 3210 | + OPCACHE_STAT_ATTR_HIT(); |
| 3211 | + } else { |
| 3212 | + // The hint we provided didn't work. |
| 3213 | + // Maybe next time? |
| 3214 | + OPCACHE_MAYBE_DEOPT_LOAD_ATTR(); |
| 3215 | + } |
| 3216 | + |
| 3217 | + Py_INCREF(res); |
| 3218 | + SET_TOP(res); |
| 3219 | + Py_DECREF(owner); |
| 3220 | + Py_DECREF(dict); |
| 3221 | + DISPATCH(); |
| 3222 | + } else { |
| 3223 | + // This attribute can be missing sometimes; |
| 3224 | + // we don't want to optimize this lookup. |
| 3225 | + OPCACHE_DEOPT_LOAD_ATTR(); |
| 3226 | + Py_DECREF(dict); |
| 3227 | + } |
3202 | 3228 | } else {
|
3203 |
| - // This attribute can be missing sometimes -- we |
3204 |
| - // don't want to optimize this lookup. |
| 3229 | + // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact. |
3205 | 3230 | OPCACHE_DEOPT_LOAD_ATTR();
|
3206 |
| - Py_DECREF(dict); |
3207 | 3231 | }
|
3208 |
| - } else { |
3209 |
| - // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact |
3210 |
| - OPCACHE_DEOPT_LOAD_ATTR(); |
3211 | 3232 | }
|
3212 | 3233 | } else {
|
3213 | 3234 | // The type of the object has either been updated,
|
3214 | 3235 | // or is different. Maybe it will stabilize?
|
3215 | 3236 | OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
|
3216 | 3237 | }
|
3217 |
| - |
3218 | 3238 | OPCACHE_STAT_ATTR_MISS();
|
3219 | 3239 | }
|
3220 | 3240 |
|
3221 |
| - if (co_opcache != NULL && /* co_opcache can be NULL after a DEOPT() call. */ |
| 3241 | + if (co_opcache != NULL && // co_opcache can be NULL after a DEOPT() call. |
3222 | 3242 | type->tp_getattro == PyObject_GenericGetAttr)
|
3223 | 3243 | {
|
3224 |
| - Py_ssize_t ret; |
3225 |
| - |
3226 |
| - if (type->tp_dictoffset > 0) { |
3227 |
| - if (type->tp_dict == NULL) { |
3228 |
| - if (PyType_Ready(type) < 0) { |
3229 |
| - Py_DECREF(owner); |
3230 |
| - SET_TOP(NULL); |
3231 |
| - goto error; |
3232 |
| - } |
| 3244 | + if (type->tp_dict == NULL) { |
| 3245 | + if (PyType_Ready(type) < 0) { |
| 3246 | + Py_DECREF(owner); |
| 3247 | + SET_TOP(NULL); |
| 3248 | + goto error; |
3233 | 3249 | }
|
3234 |
| - if (_PyType_Lookup(type, name) == NULL) { |
3235 |
| - dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset); |
3236 |
| - dict = *dictptr; |
| 3250 | + } |
| 3251 | + PyObject *descr = _PyType_Lookup(type, name); |
| 3252 | + if (descr != NULL) { |
| 3253 | + // We found an attribute with a data-like descriptor. |
| 3254 | + PyTypeObject *dtype = Py_TYPE(descr); |
| 3255 | + if (dtype == &PyMemberDescr_Type) { // It's a slot |
| 3256 | + PyMemberDescrObject *member = (PyMemberDescrObject *)descr; |
| 3257 | + struct PyMemberDef *dmem = member->d_member; |
| 3258 | + if (dmem->type == T_OBJECT_EX) { |
| 3259 | + Py_ssize_t offset = dmem->offset; |
| 3260 | + assert(offset > 0); // 0 would be confused with dict hint == -1 (miss). |
| 3261 | + |
| 3262 | + if (co_opcache->optimized == 0) { |
| 3263 | + // First time we optimize this opcode. |
| 3264 | + OPCACHE_STAT_ATTR_OPT(); |
| 3265 | + co_opcache->optimized = OPCODE_CACHE_MAX_TRIES; |
| 3266 | + // fprintf(stderr, "Setting hint for %s, offset %zd\n", dmem->name, offset); |
| 3267 | + } |
3237 | 3268 |
|
3238 |
| - if (dict != NULL && PyDict_CheckExact(dict)) { |
3239 |
| - Py_INCREF(dict); |
3240 |
| - res = NULL; |
3241 |
| - ret = _PyDict_GetItemHint((PyDictObject*)dict, name, -1, &res); |
| 3269 | + la = &co_opcache->u.la; |
| 3270 | + la->type = type; |
| 3271 | + la->tp_version_tag = type->tp_version_tag; |
| 3272 | + la->hint = ~offset; |
| 3273 | + |
| 3274 | + char *addr = (char *)owner + offset; |
| 3275 | + res = *(PyObject **)addr; |
3242 | 3276 | if (res != NULL) {
|
3243 | 3277 | Py_INCREF(res);
|
3244 |
| - Py_DECREF(dict); |
3245 | 3278 | Py_DECREF(owner);
|
3246 | 3279 | SET_TOP(res);
|
3247 | 3280 |
|
3248 |
| - if (co_opcache->optimized == 0) { |
3249 |
| - // First time we optimize this opcode. */ |
3250 |
| - OPCACHE_STAT_ATTR_OPT(); |
3251 |
| - co_opcache->optimized = OPCODE_CACHE_MAX_TRIES; |
3252 |
| - } |
3253 |
| - |
3254 |
| - la = &co_opcache->u.la; |
3255 |
| - la->type = type; |
3256 |
| - la->tp_version_tag = type->tp_version_tag; |
3257 |
| - la->hint = ret; |
3258 |
| - |
3259 | 3281 | DISPATCH();
|
3260 | 3282 | }
|
| 3283 | + // Else slot is NULL. Fall through to slow path to raise AttributeError(name). |
| 3284 | + } |
| 3285 | + // Else it's a slot of a different type. We don't handle those. |
| 3286 | + } |
| 3287 | + // Else it's some other kind of descriptor that we don't handle. |
| 3288 | + OPCACHE_DEOPT_LOAD_ATTR(); |
| 3289 | + } else if (type->tp_dictoffset > 0) { |
| 3290 | + // We found an instance with a __dict__. |
| 3291 | + dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset); |
| 3292 | + dict = *dictptr; |
| 3293 | + |
| 3294 | + if (dict != NULL && PyDict_CheckExact(dict)) { |
| 3295 | + Py_INCREF(dict); |
| 3296 | + res = NULL; |
| 3297 | + Py_ssize_t hint = _PyDict_GetItemHint((PyDictObject*)dict, name, -1, &res); |
| 3298 | + if (re
F987
s != NULL) { |
| 3299 | + Py_INCREF(res); |
3261 | 3300 | Py_DECREF(dict);
|
3262 |
| - } else { |
3263 |
| - // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact |
3264 |
| - OPCACHE_DEOPT_LOAD_ATTR(); |
| 3301 | + Py_DECREF(owner); |
| 3302 | + SET_TOP(res); |
| 3303 | + |
| 3304 | + if (co_opcache->optimized == 0) { |
| 3305 | + // First time we optimize this opcode. |
| 3306 | + OPCACHE_STAT_ATTR_OPT(); |
| 3307 | + co_opcache->optimized = OPCODE_CACHE_MAX_TRIES; |
| 3308 | + } |
| 3309 | + |
| 3310 | + la = &co_opcache->u.la; |
| 3311 | + la->type = type; |
| 3312 | + la->tp_version_tag = type->tp_version_tag; |
| 3313 | + la->hint = hint; |
| 3314 | + |
| 3315 | + DISPATCH(); |
3265 | 3316 | }
|
| 3317 | + Py_DECREF(dict); |
3266 | 3318 | } else {
|
3267 |
| - // We failed to find an attribute without a data-like descriptor |
| 3319 | + // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact. |
3268 | 3320 | OPCACHE_DEOPT_LOAD_ATTR();
|
3269 | 3321 | }
|
3270 | 3322 | } else {
|
3271 |
| - // The object's class does not have a tp_dictoffset we can use |
| 3323 | + // The object's class does not have a tp_dictoffset we can use. |
3272 | 3324 | OPCACHE_DEOPT_LOAD_ATTR();
|
3273 | 3325 | }
|
3274 | 3326 | } else if (type->tp_getattro != PyObject_GenericGetAttr) {
|
3275 | 3327 | OPCACHE_DEOPT_LOAD_ATTR();
|
3276 | 3328 | }
|
3277 | 3329 | }
|
3278 | 3330 |
|
3279 |
| - /* slow path */ |
| 3331 | + // Slow path. |
3280 | 3332 | res = PyObject_GetAttr(owner, name);
|
3281 | 3333 | Py_DECREF(owner);
|
3282 | 3334 | SET_TOP(res);
|
|
0 commit comments