bpo-42093: Add opcode cache for LOAD_ATTR (GH-22803)

diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 6c3fc62..8e74962 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -1437,6 +1437,71 @@ PyDict_GetItem(PyObject *op, PyObject *key)
     return value;
 }
 
+Py_ssize_t
+_PyDict_GetItemHint(PyDictObject *mp, PyObject *key,
+                     Py_ssize_t hint, PyObject **value)
+{
+    Py_hash_t hash;
+    PyThreadState *tstate;
+
+    assert(*value == NULL);
+    assert(PyDict_CheckExact((PyObject*)mp));
+    assert(PyUnicode_CheckExact(key));
+
+    if (hint >= 0 && hint < _PyDict_KeysSize(mp->ma_keys)) {
+        PyObject *res = NULL;
+
+        PyDictKeyEntry *ep = DK_ENTRIES(mp->ma_keys) + (size_t)hint;
+        if (ep->me_key == key) {
+            if (mp->ma_keys->dk_lookup == lookdict_split) {
+                assert(mp->ma_values != NULL);
+                res = mp->ma_values[(size_t)hint];
+            }
+            else {
+                res = ep->me_value;
+            }
+            if (res != NULL) {
+                *value = res;
+                return hint;
+            }
+        }
+    }
+
+    if ((hash = ((PyASCIIObject *) key)->hash) == -1)
+    {
+        hash = PyObject_Hash(key);
+        if (hash == -1) {
+            PyErr_Clear();
+            return -1;
+        }
+    }
+
+    // We can arrive here with a NULL tstate during initialization: try
+    // running "python -Wi" for an example related to string interning
+    tstate = _PyThreadState_UncheckedGet();
+    Py_ssize_t ix = 0;
+    if (tstate != NULL && tstate->curexc_type != NULL) {
+        /* preserve the existing exception */
+        PyObject *err_type, *err_value, *err_tb;
+        PyErr_Fetch(&err_type, &err_value, &err_tb);
+        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, value);
+        /* ignore errors */
+        PyErr_Restore(err_type, err_value, err_tb);
+        if (ix < 0) {
+            return -1;
+        }
+    }
+    else {
+        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, value);
+        if (ix < 0) {
+            PyErr_Clear();
+            return -1;
+        }
+    }
+
+    return ix;
+}
+
 /* Same as PyDict_GetItemWithError() but with hash supplied by caller.
    This returns NULL *with* an exception set if an exception occurred.
    It returns NULL *without* an exception set if the key wasn't present.