Python/ceval.c (part 32)
Source:
cpython 3.14 @ ab2d84fe1023/Python/ceval.c
This annotation covers super() and attribute load specializations. See python_ceval31_detail for async opcodes and python_ceval18_detail for BINARY_SUBSCR specializations.
Map
| Lines | Symbol | Role |
|---|---|---|
| 1-80 | LOAD_SUPER_ATTR | super().method without constructing a super object |
| 81-180 | LOAD_ATTR_MODULE | Attribute load from a module dict |
| 181-300 | LOAD_ATTR_WITH_HINT | Attribute load using a cached dict offset hint |
| 301-400 | LOAD_ATTR_SLOT | Attribute stored in a __slots__ slot |
| 401-500 | LOAD_ATTR_CLASS | Class-level attribute via descriptor |
Reading
LOAD_SUPER_ATTR
// CPython: Python/ceval.c:2180 LOAD_SUPER_ATTR
inst(LOAD_SUPER_ATTR, (global_super, class, self -- attr)) {
/* Specialization of: super().attr
Avoids allocating a super() object for the common case. */
PyObject *mro = class->tp_mro;
/* Find 'class' in the MRO of type(self) */
PyTypeObject *obj_type = Py_TYPE(self);
PyObject *obj_mro = obj_type->tp_mro;
Py_ssize_t i;
for (i = 0; i < PyTuple_GET_SIZE(obj_mro); i++) {
if (PyTuple_GET_ITEM(obj_mro, i) == (PyObject *)class)
break;
}
/* Walk the rest of the MRO looking for the attribute */
attr = _PySuper_Lookup(obj_type, self, class, name, NULL);
ERROR_IF(attr == NULL, error);
}
super().method() in Python 3.12+ uses LOAD_SUPER_ATTR which bypasses PySuper_Type.__get__ and jumps directly to the MRO walk. The __class__ cell and self are already on the stack from the function's closure.
LOAD_ATTR_MODULE
// CPython: Python/ceval.c:2340 LOAD_ATTR_MODULE
inst(LOAD_ATTR_MODULE, (owner -- attr)) {
/* Cache: module dict version + key + value */
PyObject *dict = ((PyModuleObject *)owner)->md_dict;
DEOPT_IF(((PyDictObject *)dict)->ma_version_tag != cache->version,
LOAD_ATTR);
attr = cache->value;
DEOPT_IF(attr == NULL, LOAD_ATTR);
Py_INCREF(attr);
Py_DECREF(owner);
}
import os; os.path specializes to LOAD_ATTR_MODULE after the first access. The module dict's ma_version_tag is incremented on every modification; a stale tag causes deoptimization back to LOAD_ATTR. Module globals accessed via LOAD_GLOBAL use a similar mechanism.
LOAD_ATTR_WITH_HINT
// CPython: Python/ceval.c:2400 LOAD_ATTR_WITH_HINT
inst(LOAD_ATTR_WITH_HINT, (owner -- attr)) {
/* Cache: type version + dict version + index hint */
PyObject *dict = *_PyObject_GetDictPtr(owner);
DEOPT_IF(dict == NULL, LOAD_ATTR);
DEOPT_IF(((PyDictObject *)dict)->ma_version_tag != cache->dict_version,
LOAD_ATTR);
PyDictObject *dobj = (PyDictObject *)dict;
/* Use cached index to directly access the values array */
Py_ssize_t ix = cache->index;
attr = dobj->ma_values->values[ix];
DEOPT_IF(attr == NULL, LOAD_ATTR);
Py_INCREF(attr);
Py_DECREF(owner);
}
The hint is the index into the split-table values array for instance dicts of the same type. obj.x after specialization is a direct array index, avoiding the hash lookup entirely.
LOAD_ATTR_SLOT
// CPython: Python/ceval.c:2460 LOAD_ATTR_SLOT
inst(LOAD_ATTR_SLOT, (owner -- attr)) {
/* Cache: type version + slot offset */
DEOPT_IF(Py_TYPE(owner)->tp_version_tag != cache->tp_version, LOAD_ATTR);
char *addr = (char *)owner + cache->offset;
attr = *(PyObject **)addr;
DEOPT_IF(attr == NULL, LOAD_ATTR);
Py_INCREF(attr);
Py_DECREF(owner);
}
__slots__ attributes are stored at a fixed offset in the object memory layout. LOAD_ATTR_SLOT reads them via a cached byte offset, matching C struct member access performance.
LOAD_ATTR_CLASS
// CPython: Python/ceval.c:2520 LOAD_ATTR_CLASS
inst(LOAD_ATTR_CLASS, (owner -- attr)) {
/* For non-data descriptors accessed on instances where the
instance dict has no shadowing entry. Cache the descriptor value. */
DEOPT_IF(Py_TYPE(owner)->tp_version_tag != cache->tp_version, LOAD_ATTR);
attr = cache->value;
DEOPT_IF(attr == NULL, LOAD_ATTR);
Py_INCREF(attr);
Py_DECREF(owner);
}
Class-level constants and unbound methods are cached here. On cache hit the value is returned without any dict lookup. method = obj.method compiles to LOAD_ATTR which specializes to LOAD_ATTR_CLASS for unbound methods.
gopy notes
LOAD_SUPER_ATTR calls vm.superLookup in vm/eval_simple.go. LOAD_ATTR_MODULE checks objects.Module.DictVersion. LOAD_ATTR_WITH_HINT uses the objects.DictValues index cache. LOAD_ATTR_SLOT is in vm/eval_specialize.go. All specializations fall back to objects.GetAttr on deopt.