summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2021-06-10 07:46:01 (GMT)
committerGitHub <noreply@github.com>2021-06-10 07:46:01 (GMT)
commite117c0283705943189e6b1aef668a1f68f3f00a4 (patch)
tree2f0ed87b3a6ee853b65b7db260b39d62337e87bd /Python
parent309ab616020f8504ced8ca64f7d7abc2df25a37f (diff)
downloadcpython-e117c0283705943189e6b1aef668a1f68f3f00a4.zip
cpython-e117c0283705943189e6b1aef668a1f68f3f00a4.tar.gz
cpython-e117c0283705943189e6b1aef668a1f68f3f00a4.tar.bz2
bpo-44337: Port LOAD_ATTR to PEP 659 adaptive interpreter (GH-26595)
* Specialize LOAD_ATTR with LOAD_ATTR_SLOT and LOAD_ATTR_SPLIT_KEYS * Move dict-common.h to internal/pycore_dict.h * Add LOAD_ATTR_WITH_HINT specialized opcode. * Quicken in function if loopy * Specialize LOAD_ATTR for module attributes. * Add specialization stats
Diffstat (limited to 'Python')
-rw-r--r--Python/ceval.c348
-rwxr-xr-xPython/makeopcodetargets.py5
-rw-r--r--Python/opcode_targets.h12
-rw-r--r--Python/specialize.c177
4 files changed, 350 insertions, 192 deletions
diff --git a/Python/ceval.c b/Python/ceval.c
index a8abead..46133c9 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -16,6 +16,7 @@
#include "pycore_code.h" // _PyCode_InitOpcache()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_object.h" // _PyObject_GC_TRACK()
+#include "pycore_moduleobject.h"
#include "pycore_pyerrors.h" // _PyErr_Fetch()
#include "pycore_pylifecycle.h" // _PyErr_Print()
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
@@ -24,6 +25,7 @@
#include "pycore_tuple.h" // _PyTuple_ITEMS()
#include "code.h"
+#include "pycore_dict.h"
#include "dictobject.h"
#include "frameobject.h"
#include "pycore_frame.h"
@@ -1554,8 +1556,15 @@ eval_frame_handle_pending(PyThreadState *tstate)
#define OPCACHE_STAT_ATTR_DEOPT()
#define OPCACHE_STAT_ATTR_TOTAL()
+#define JUMP_TO_INSTRUCTION(op) goto PREDICT_ID(op)
+
+#define GET_CACHE() \
+ _GetSpecializedCacheEntryForInstruction(first_instr, INSTR_OFFSET(), oparg)
+
#endif
+#define DEOPT_IF(cond, instname) if (cond) { goto instname ## _miss; }
+
#define GLOBALS() specials[FRAME_SPECIALS_GLOBALS_OFFSET]
#define BUILTINS() specials[FRAME_SPECIALS_BUILTINS_OFFSET]
#define LOCALS() specials[FRAME_SPECIALS_LOCALS_OFFSET]
@@ -1574,7 +1583,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
int lastopcode = 0;
#endif
PyObject **stack_pointer; /* Next free slot in value stack */
- const _Py_CODEUNIT *next_instr;
+ _Py_CODEUNIT *next_instr;
int opcode; /* Current opcode */
int oparg; /* Current opcode argument, if any */
PyObject **localsplus, **specials;
@@ -1582,7 +1591,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
_Py_atomic_int * const eval_breaker = &tstate->interp->ceval.eval_breaker;
PyCodeObject *co;
- const _Py_CODEUNIT *first_instr;
+ _Py_CODEUNIT *first_instr;
PyObject *names;
PyObject *consts;
_PyOpcache *co_opcache;
@@ -3443,196 +3452,129 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
}
case TARGET(LOAD_ATTR): {
+ PREDICTED(LOAD_ATTR);
PyObject *name = GETITEM(names, oparg);
PyObject *owner = TOP();
+ PyObject *res = PyObject_GetAttr(owner, name);
+ if (res == NULL) {
+ goto error;
+ }
+ Py_DECREF(owner);
+ SET_TOP(res);
+ DISPATCH();
+ }
- PyTypeObject *type = Py_TYPE(owner);
- PyObject *res;
- PyObject **dictptr;
- PyObject *dict;
- _PyOpCodeOpt_LoadAttr *la;
-
- OPCACHE_STAT_ATTR_TOTAL();
-
- OPCACHE_CHECK();
- if (co_opcache != NULL && PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
- {
- if (co_opcache->optimized > 0) {
- // Fast path -- cache hit makes LOAD_ATTR ~30% faster.
- la = &co_opcache->u.la;
- if (la->type == type && la->tp_version_tag == type->tp_version_tag)
- {
- // Hint >= 0 is a dict index; hint == -1 is a dict miss.
- // Hint < -1 is an inverted slot offset: offset is strictly > 0,
- // so ~offset is strictly < -1 (assuming 2's complement).
- if (la->hint < -1) {
- // Even faster path -- slot hint.
- Py_ssize_t offset = ~la->hint;
- // fprintf(stderr, "Using hint for offset %zd\n", offset);
- char *addr = (char *)owner + offset;
- res = *(PyObject **)addr;
- if (res != NULL) {
- Py_INCREF(res);
- SET_TOP(res);
- Py_DECREF(owner);
- DISPATCH();
- }
- // Else slot is NULL. Fall through to slow path to raise AttributeError(name).
- // Don't DEOPT, since the slot is still there.
- } else {
- // Fast path for dict.
- assert(type->tp_dict != NULL);
- assert(type->tp_dictoffset > 0);
-
- dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
- dict = *dictptr;
- if (dict != NULL && PyDict_CheckExact(dict)) {
- Py_ssize_t hint = la->hint;
- Py_INCREF(dict);
- res = NULL;
- assert(!_PyErr_Occurred(tstate));
- la->hint = _PyDict_GetItemHint((PyDictObject*)dict, name, hint, &res);
- if (res != NULL) {
- assert(la->hint >= 0);
- if (la->hint == hint && hint >= 0) {
- // Our hint has helped -- cache hit.
- OPCACHE_STAT_ATTR_HIT();
- } else {
- // The hint we provided didn't work.
- // Maybe next time?
- OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
- }
-
- Py_INCREF(res);
- SET_TOP(res);
- Py_DECREF(owner);
- Py_DECREF(dict);
- DISPATCH();
- }
- else {
- _PyErr_Clear(tstate);
- // This attribute can be missing sometimes;
- // we don't want to optimize this lookup.
- OPCACHE_DEOPT_LOAD_ATTR();
- Py_DECREF(dict);
- }
- }
- else {
- // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact.
- OPCACHE_DEOPT_LOAD_ATTR();
- }
- }
- }
- else {
- // The type of the object has either been updated,
- // or is different. Maybe it will stabilize?
- OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
- }
- OPCACHE_STAT_ATTR_MISS();
- }
-
- if (co_opcache != NULL && // co_opcache can be NULL after a DEOPT() call.
- type->tp_getattro == PyObject_GenericGetAttr)
- {
- if (type->tp_dict == NULL) {
- if (PyType_Ready(type) < 0) {
- Py_DECREF(owner);
- SET_TOP(NULL);
- goto error;
- }
- }
- PyObject *descr = _PyType_Lookup(type, name);
- if (descr != NULL) {
- // We found an attribute with a data-like descriptor.
- PyTypeObject *dtype = Py_TYPE(descr);
- if (dtype == &PyMemberDescr_Type) { // It's a slot
- PyMemberDescrObject *member = (PyMemberDescrObject *)descr;
- struct PyMemberDef *dmem = member->d_member;
- if (dmem->type == T_OBJECT_EX) {
- Py_ssize_t offset = dmem->offset;
- assert(offset > 0); // 0 would be confused with dict hint == -1 (miss).
-
- if (co_opcache->optimized == 0) {
- // First time we optimize this opcode.
- OPCACHE_STAT_ATTR_OPT();
- co_opcache->optimized = OPCODE_CACHE_MAX_TRIES;
- // fprintf(stderr, "Setting hint for %s, offset %zd\n", dmem->name, offset);
- }
-
- la = &co_opcache->u.la;
- la->type = type;
- la->tp_version_tag = type->tp_version_tag;
- la->hint = ~offset;
-
- char *addr = (char *)owner + offset;
- res = *(PyObject **)addr;
- if (res != NULL) {
- Py_INCREF(res);
- Py_DECREF(owner);
- SET_TOP(res);
-
- DISPATCH();
- }
- // Else slot is NULL. Fall through to slow path to raise AttributeError(name).
- }
- // Else it's a slot of a different type. We don't handle those.
- }
- // Else it's some other kind of descriptor that we don't handle.
- OPCACHE_DEOPT_LOAD_ATTR();
- }
- else if (type->tp_dictoffset > 0) {
- // We found an instance with a __dict__.
- dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
- dict = *dictptr;
-
- if (dict != NULL && PyDict_CheckExact(dict)) {
- Py_INCREF(dict);
- res = NULL;
- assert(!_PyErr_Occurred(tstate));
- Py_ssize_t hint = _PyDict_GetItemHint((PyDictObject*)dict, name, -1, &res);
- if (res != NULL) {
- Py_INCREF(res);
- Py_DECREF(dict);
- Py_DECREF(owner);
- SET_TOP(res);
-
- if (co_opcache->optimized == 0) {
- // First time we optimize this opcode.
- OPCACHE_STAT_ATTR_OPT();
- co_opcache->optimized = OPCODE_CACHE_MAX_TRIES;
- }
-
- la = &co_opcache->u.la;
- la->type = type;
- la->tp_version_tag = type->tp_version_tag;
- assert(hint >= 0);
- la->hint = hint;
-
- DISPATCH();
- }
- else {
- _PyErr_Clear(tstate);
- }
- Py_DECREF(dict);
- } else {
- // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact.
- OPCACHE_DEOPT_LOAD_ATTR();
- }
- } else {
- // The object's class does not have a tp_dictoffset we can use.
- OPCACHE_DEOPT_LOAD_ATTR();
- }
- } else if (type->tp_getattro != PyObject_GenericGetAttr) {
- OPCACHE_DEOPT_LOAD_ATTR();
+ case TARGET(LOAD_ATTR_ADAPTIVE): {
+ SpecializedCacheEntry *cache = GET_CACHE();
+ if (cache->adaptive.counter == 0) {
+ PyObject *owner = TOP();
+ PyObject *name = GETITEM(names, cache->adaptive.original_oparg);
+ next_instr--;
+ if (_Py_Specialize_LoadAttr(owner, next_instr, name, cache) < 0) {
+ goto error;
}
+ DISPATCH();
}
+ else {
+ STAT_INC(loadattr_deferred);
+ cache->adaptive.counter--;
+ oparg = cache->adaptive.original_oparg;
+ JUMP_TO_INSTRUCTION(LOAD_ATTR);
+ }
+ }
+
+ case TARGET(LOAD_ATTR_SPLIT_KEYS): {
+ PyObject *owner = TOP();
+ PyObject *res;
+ PyTypeObject *tp = Py_TYPE(owner);
+ SpecializedCacheEntry *caches = GET_CACHE();
+ _PyAdaptiveEntry *cache0 = &caches[0].adaptive;
+ _PyLoadAttrCache *cache1 = &caches[-1].load_attr;
+ assert(cache1->tp_version != 0);
+ DEOPT_IF(tp->tp_version_tag != cache1->tp_version, LOAD_ATTR);
+ assert(tp->tp_dictoffset > 0);
+ PyDictObject *dict = *(PyDictObject **)(((char *)owner) + tp->tp_dictoffset);
+ DEOPT_IF(dict == NULL, LOAD_ATTR);
+ assert(PyDict_CheckExact((PyObject *)dict));
+ DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, LOAD_ATTR);
+ res = dict->ma_values[cache0->index];
+ DEOPT_IF(res == NULL, LOAD_ATTR);
+ STAT_INC(loadattr_hit);
+ record_cache_hit(cache0);
+ STAT_INC(loadattr_hit);
+ Py_INCREF(res);
+ SET_TOP(res);
+ Py_DECREF(owner);
+ DISPATCH();
+ }
- // Slow path.
- res = PyObject_GetAttr(owner, name);
+ case TARGET(LOAD_ATTR_MODULE): {
+ PyObject *owner = TOP();
+ PyObject *res;
+ SpecializedCacheEntry *caches = GET_CACHE();
+ _PyAdaptiveEntry *cache0 = &caches[0].adaptive;
+ _PyLoadAttrCache *cache1 = &caches[-1].load_attr;
+ DEOPT_IF(!PyModule_CheckExact(owner), LOAD_ATTR);
+ PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict;
+ DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, LOAD_ATTR);
+ assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE);
+ assert(cache0->index < dict->ma_keys->dk_nentries);
+ PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + cache0->index;
+ res = ep->me_value;
+ DEOPT_IF(res == NULL, LOAD_ATTR);
+ STAT_INC(loadattr_hit);
+ record_cache_hit(cache0);
+ Py_INCREF(res);
+ SET_TOP(res);
Py_DECREF(owner);
+ DISPATCH();
+ }
+
+ case TARGET(LOAD_ATTR_WITH_HINT): {
+ PyObject *owner = TOP();
+ PyObject *res;
+ PyTypeObject *tp = Py_TYPE(owner);
+ SpecializedCacheEntry *caches = GET_CACHE();
+ _PyAdaptiveEntry *cache0 = &caches[0].adaptive;
+ _PyLoadAttrCache *cache1 = &caches[-1].load_attr;
+ assert(cache1->tp_version != 0);
+ DEOPT_IF(tp->tp_version_tag != cache1->tp_version, LOAD_ATTR);
+ assert(tp->tp_dictoffset > 0);
+ PyDictObject *dict = *(PyDictObject **)(((char *)owner) + tp->tp_dictoffset);
+ DEOPT_IF(dict == NULL, LOAD_ATTR);
+ assert(PyDict_CheckExact((PyObject *)dict));
+ PyObject *name = GETITEM(names, cache0->original_oparg);
+ uint32_t hint = cache1->dk_version_or_hint;
+ DEOPT_IF(hint >= dict->ma_keys->dk_nentries, LOAD_ATTR);
+ PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + hint;
+ DEOPT_IF(ep->me_key != name, LOAD_ATTR);
+ res = ep->me_value;
+ DEOPT_IF(res == NULL, LOAD_ATTR);
+ STAT_INC(loadattr_hit);
+ record_cache_hit(cache0);
+ Py_INCREF(res);
SET_TOP(res);
- if (res == NULL)
- goto error;
+ Py_DECREF(owner);
+ DISPATCH();
+ }
+
+ case TARGET(LOAD_ATTR_SLOT): {
+ PyObject *owner = TOP();
+ PyObject *res;
+ PyTypeObject *tp = Py_TYPE(owner);
+ SpecializedCacheEntry *caches = GET_CACHE();
+ _PyAdaptiveEntry *cache0 = &caches[0].adaptive;
+ _PyLoadAttrCache *cache1 = &caches[-1].load_attr;
+ assert(cache1->tp_version != 0);
+ DEOPT_IF(tp->tp_version_tag != cache1->tp_version, LOAD_ATTR);
+ char *addr = (char *)owner + cache0->index;
+ res = *(PyObject **)addr;
+ DEOPT_IF(res == NULL, LOAD_ATTR);
+ STAT_INC(loadattr_hit);
+ record_cache_hit(cache0);
+ Py_INCREF(res);
+ SET_TOP(res);
+ Py_DECREF(owner);
DISPATCH();
}
@@ -3879,6 +3821,27 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
case TARGET(JUMP_ABSOLUTE): {
PREDICTED(JUMP_ABSOLUTE);
+ if (oparg < INSTR_OFFSET()) {
+ /* Increment the warmup counter and quicken if warm enough
+ * _Py_Quicken is idempotent so we don't worry about overflow */
+ if (!PyCodeObject_IsWarmedUp(co)) {
+ PyCodeObject_IncrementWarmup(co);
+ if (PyCodeObject_IsWarmedUp(co)) {
+ if (_Py_Quicken(co)) {
+ goto error;
+ }
+ int nexti = INSTR_OFFSET();
+ first_instr = co->co_firstinstr;
+ next_instr = first_instr + nexti;
+ }
+ }
+ }
+ JUMPTO(oparg);
+ CHECK_EVAL_BREAKER();
+ DISPATCH();
+ }
+
+ case TARGET(JUMP_ABSOLUTE_QUICK): {
JUMPTO(oparg);
CHECK_EVAL_BREAKER();
DISPATCH();
@@ -4494,6 +4457,22 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
or goto error. */
Py_UNREACHABLE();
+/* Cache misses */
+
+LOAD_ATTR_miss:
+ {
+ STAT_INC(loadattr_miss);
+ _PyAdaptiveEntry *cache = &GET_CACHE()->adaptive;
+ record_cache_miss(cache);
+ if (too_many_cache_misses(cache)) {
+ next_instr[-1] = _Py_MAKECODEUNIT(LOAD_ATTR_ADAPTIVE, _Py_OPARG(next_instr[-1]));
+ STAT_INC(loadattr_deopt);
+ cache_backoff(cache);
+ }
+ oparg = cache->original_oparg;
+ JUMP_TO_INSTRUCTION(LOAD_ATTR);
+ }
+
error:
/* Double-check exception status. */
#ifdef NDEBUG
@@ -4515,6 +4494,7 @@ error:
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj,
tstate, f, &trace_info);
}
+
exception_unwind:
f->f_state = FRAME_UNWINDING;
/* We can't use f->f_lasti here, as RERAISE may have set it */
diff --git a/Python/makeopcodetargets.py b/Python/makeopcodetargets.py
index 023c9e6..189d72a 100755
--- a/Python/makeopcodetargets.py
+++ b/Python/makeopcodetargets.py
@@ -34,6 +34,11 @@ def write_contents(f):
targets = ['_unknown_opcode'] * 256
for opname, op in opcode.opmap.items():
targets[op] = "TARGET_%s" % opname
+ next_op = 1
+ for opname in opcode._specialized_instructions:
+ while targets[next_op] != '_unknown_opcode':
+ next_op += 1
+ targets[next_op] = "TARGET_%s" % opname
f.write("static void *opcode_targets[256] = {\n")
f.write(",\n".join([" &&%s" % s for s in targets]))
f.write("\n};\n")
diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h
index 94b2a7c..47beee7 100644
--- a/Python/opcode_targets.h
+++ b/Python/opcode_targets.h
@@ -6,21 +6,21 @@ static void *opcode_targets[256] = {
&&TARGET_DUP_TOP,
&&TARGET_DUP_TOP_TWO,
&&TARGET_ROT_FOUR,
- &&_unknown_opcode,
- &&_unknown_opcode,
+ &&TARGET_JUMP_ABSOLUTE_QUICK,
+ &&TARGET_LOAD_ATTR_ADAPTIVE,
&&TARGET_NOP,
&&TARGET_UNARY_POSITIVE,
&&TARGET_UNARY_NEGATIVE,
&&TARGET_UNARY_NOT,
- &&_unknown_opcode,
- &&_unknown_opcode,
+ &&TARGET_LOAD_ATTR_SPLIT_KEYS,
+ &&TARGET_LOAD_ATTR_WITH_HINT,
&&TARGET_UNARY_INVERT,
&&TARGET_BINARY_MATRIX_MULTIPLY,
&&TARGET_INPLACE_MATRIX_MULTIPLY,
- &&_unknown_opcode,
+ &&TARGET_LOAD_ATTR_SLOT,
&&TARGET_BINARY_POWER,
&&TARGET_BINARY_MULTIPLY,
- &&_unknown_opcode,
+ &&TARGET_LOAD_ATTR_MODULE,
&&TARGET_BINARY_MODULO,
&&TARGET_BINARY_ADD,
&&TARGET_BINARY_SUBTRACT,
diff --git a/Python/specialize.c b/Python/specialize.c
index 07152d8..1801e66 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -1,7 +1,10 @@
#include "Python.h"
#include "pycore_code.h"
+#include "pycore_dict.h"
+#include "pycore_moduleobject.h"
#include "opcode.h"
+#include "structmember.h" // struct PyMemberDef, T_OFFSET_EX
/* We layout the quickened data as a bi-directional array:
@@ -29,6 +32,22 @@
*/
Py_ssize_t _Py_QuickenedCount = 0;
+#if SPECIALIZATION_STATS
+SpecializationStats _specialization_stats = { 0 };
+
+#define PRINT_STAT(name) fprintf(stderr, #name " : %" PRIu64" \n", _specialization_stats.name);
+void
+_Py_PrintSpecializationStats(void)
+{
+ PRINT_STAT(specialization_success);
+ PRINT_STAT(specialization_failure);
+ PRINT_STAT(loadattr_hit);
+ PRINT_STAT(loadattr_deferred);
+ PRINT_STAT(loadattr_miss);
+ PRINT_STAT(loadattr_deopt);
+}
+
+#endif
static SpecializedCacheOrInstruction *
allocate(int cache_count, int instruction_count)
@@ -56,10 +75,14 @@ get_cache_count(SpecializedCacheOrInstruction *quickened) {
/* Map from opcode to adaptive opcode.
Values of zero are ignored. */
-static uint8_t adaptive_opcodes[256] = { 0 };
+static uint8_t adaptive_opcodes[256] = {
+ [LOAD_ATTR] = LOAD_ATTR_ADAPTIVE,
+};
/* The number of cache entries required for a "family" of instructions. */
-static uint8_t cache_requirements[256] = { 0 };
+static uint8_t cache_requirements[256] = {
+ [LOAD_ATTR] = 2,
+};
/* Return the oparg for the cache_offset and instruction index.
*
@@ -158,6 +181,9 @@ optimize(SpecializedCacheOrInstruction *quickened, int len)
/* Super instructions don't use the cache,
* so no need to update the offset. */
switch (opcode) {
+ case JUMP_ABSOLUTE:
+ instructions[i] = _Py_MAKECODEUNIT(JUMP_ABSOLUTE_QUICK, oparg);
+ break;
/* Insert superinstructions here
E.g.
case LOAD_FAST:
@@ -195,3 +221,150 @@ _Py_Quicken(PyCodeObject *code) {
return 0;
}
+static int
+specialize_module_load_attr(
+ PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
+ _PyAdaptiveEntry *cache0, _PyLoadAttrCache *cache1)
+{
+ PyModuleObject *m = (PyModuleObject *)owner;
+ PyObject *value = NULL;
+ PyObject *getattr;
+ _Py_IDENTIFIER(__getattr__);
+ PyDictObject *dict = (PyDictObject *)m->md_dict;
+ if (dict == NULL) {
+ return -1;
+ }
+ if (dict->ma_keys->dk_kind != DICT_KEYS_UNICODE) {
+ return -1;
+ }
+ getattr = _PyUnicode_FromId(&PyId___getattr__); /* borrowed */
+ if (getattr == NULL) {
+ PyErr_Clear();
+ return -1;
+ }
+ Py_ssize_t index = _PyDict_GetItemHint(dict, getattr, -1, &value);
+ assert(index != DKIX_ERROR);
+ if (index != DKIX_EMPTY) {
+ return -1;
+ }
+ index = _PyDict_GetItemHint(dict, name, -1, &value);
+ assert (index != DKIX_ERROR);
+ if (index != (uint16_t)index) {
+ return -1;
+ }
+ uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(dict);
+ if (keys_version == 0) {
+ return -1;
+ }
+ cache1->dk_version_or_hint = keys_version;
+ cache0->index = (uint16_t)index;
+ *instr = _Py_MAKECODEUNIT(LOAD_ATTR_MODULE, _Py_OPARG(*instr));
+ return 0;
+}
+
+int
+_Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache)
+{
+ _PyAdaptiveEntry *cache0 = &cache->adaptive;
+ _PyLoadAttrCache *cache1 = &cache[-1].load_attr;
+ if (PyModule_CheckExact(owner)) {
+ int err = specialize_module_load_attr(owner, instr, name, cache0, cache1);
+ if (err) {
+ goto fail;
+ }
+ goto success;
+ }
+ PyTypeObject *type = Py_TYPE(owner);
+ if (type->tp_getattro != PyObject_GenericGetAttr) {
+ goto fail;
+ }
+ if (type->tp_dict == NULL) {
+ if (PyType_Ready(type) < 0) {
+ return -1;
+ }
+ }
+ PyObject *descr = _PyType_Lookup(type, name);
+ if (descr != NULL) {
+ // We found an attribute with a data-like descriptor.
+ PyTypeObject *dtype = Py_TYPE(descr);
+ if (dtype != &PyMemberDescr_Type) {
+ goto fail;
+ }
+ // It's a slot
+ PyMemberDescrObject *member = (PyMemberDescrObject *)descr;
+ struct PyMemberDef *dmem = member->d_member;
+ if (dmem->type != T_OBJECT_EX) {
+ // It's a slot of a different type. We don't handle those.
+ goto fail;
+ }
+ Py_ssize_t offset = dmem->offset;
+ if (offset != (uint16_t)offset) {
+ goto fail;
+ }
+ assert(offset > 0);
+ cache0->index = (uint16_t)offset;
+ cache1->tp_version = type->tp_version_tag;
+ *instr = _Py_MAKECODEUNIT(LOAD_ATTR_SLOT, _Py_OPARG(*instr));
+ goto success;
+ }
+ // No desciptor
+ if (type->tp_dictoffset <= 0) {
+ // No dictionary, or computed offset dictionary
+ goto fail;
+ }
+ PyObject **dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
+ if (*dictptr == NULL || !PyDict_CheckExact(*dictptr)) {
+ goto fail;
+ }
+ // We found an instance with a __dict__.
+ PyDictObject *dict = (PyDictObject *)*dictptr;
+ if ((type->tp_flags & Py_TPFLAGS_HEAPTYPE)
+ && dict->ma_keys == ((PyHeapTypeObject*)type)->ht_cached_keys
+ ) {
+ // Keys are shared
+ assert(PyUnicode_CheckExact(name));
+ Py_hash_t hash = PyObject_Hash(name);
+ if (hash == -1) {
+ return -1;
+ }
+ PyObject *value;
+ Py_ssize_t index = _Py_dict_lookup(dict, name, hash, &value);
+ assert (index != DKIX_ERROR);
+ if (index != (uint16_t)index) {
+ goto fail;
+ }
+ uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(dict);
+ if (keys_version == 0) {
+ goto fail;
+ }
+ cache1->dk_version_or_hint = keys_version;
+ cache1->tp_version = type->tp_version_tag;
+ cache0->index = (uint16_t)index;
+ *instr = _Py_MAKECODEUNIT(LOAD_ATTR_SPLIT_KEYS, _Py_OPARG(*instr));
+ goto success;
+ }
+ else {
+ PyObject *value = NULL;
+ Py_ssize_t hint =
+ _PyDict_GetItemHint(dict, name, -1, &value);
+ if (hint != (uint32_t)hint) {
+ goto fail;
+ }
+ cache1->dk_version_or_hint = (uint32_t)hint;
+ cache1->tp_version = type->tp_version_tag;
+ *instr = _Py_MAKECODEUNIT(LOAD_ATTR_WITH_HINT, _Py_OPARG(*instr));
+ goto success;
+ }
+
+fail:
+ STAT_INC(specialization_failure);
+ assert(!PyErr_Occurred());
+ cache_backoff(cache0);
+ return 0;
+success:
+ STAT_INC(specialization_success);
+ assert(!PyErr_Occurred());
+ cache0->counter = saturating_start();
+ return 0;
+}
+