Skip to main content

Python/ceval.c (part 85)

Source:

cpython 3.14 @ ab2d84fe1023/Python/ceval.c

This annotation covers attribute load/store opcodes and their specializations. See python_ceval84_detail for generator opcodes (YIELD_VALUE, RESUME).

Map

LinesSymbolRole
1-80LOAD_ATTRGeneric attribute lookup
81-160LOAD_ATTR_MODULESpecialization for module attribute access
161-240LOAD_ATTR_SLOTSpecialization for slot-based types
241-340LOAD_ATTR_WITH_HINTSpecialization using a dict key hint
341-500STORE_ATTR / STORE_ATTR_SLOTGeneric and specialized attribute store

Reading

LOAD_ATTR

// CPython: Python/ceval.c:4200 LOAD_ATTR
inst(LOAD_ATTR, (owner -- attr)) {
PyObject *name = GETITEM(names, oparg >> 1);
if (oparg & 1) {
/* Push NULL + method for METHOD_CALL optimization */
PyObject *self = NULL;
int meth = _PyObject_GetMethod(owner, name, &attr);
if (meth) {
self = owner;
} else {
Py_DECREF(owner);
}
PUSH(self); /* NULL or owner */
} else {
attr = PyObject_GetAttr(owner, name);
ERROR_IF(attr == NULL, error);
Py_DECREF(owner);
}
}

When oparg & 1, LOAD_ATTR uses _PyObject_GetMethod which, for methods defined in C types, returns the underlying function and pushes the self separately. This avoids creating a bound method object for CALL_METHOD.

LOAD_ATTR_MODULE

// CPython: Python/ceval.c:4260 LOAD_ATTR_MODULE
inst(LOAD_ATTR_MODULE, (owner -- attr)) {
assert(cframe.use_tracing == 0);
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict;
assert(dict != NULL);
PyObject *res = _PyDict_LoadGlobal(dict, (PyObject *)dict->ma_keys,
name);
if (res == NULL) {
/* miss: deoptimize */
DEOPT_IF(true, LOAD_ATTR);
}
attr = Py_NewRef(res);
Py_DECREF(owner);
}

Module attribute access is specialized to bypass the full PyObject_GetAttr path: it reads directly from the module's dict. The specialization is invalidated (deoptimized) if the dict key is missing.

LOAD_ATTR_SLOT

// CPython: Python/ceval.c:4320 LOAD_ATTR_SLOT
inst(LOAD_ATTR_SLOT, (owner -- attr)) {
/* cache[1].index = slot offset in the object */
Py_ssize_t index = read_u16(&next_instr[-1].cache[1].as_index);
PyObject **addr = (PyObject **)((char *)owner + index);
attr = *addr;
if (attr == NULL) {
/* Descriptor not set: AttributeError */
DEOPT_IF(true, LOAD_ATTR);
}
Py_INCREF(attr);
Py_DECREF(owner);
}

LOAD_ATTR_SLOT reads a slot at a fixed offset in the object struct, bypassing all dict lookups. The offset is cached in the inline cache and validated against the type version tag.

STORE_ATTR

// CPython: Python/ceval.c:4380 STORE_ATTR
inst(STORE_ATTR, (owner, value --)) {
PyObject *name = GETITEM(names, oparg);
int err = PyObject_SetAttr(owner, name, value);
Py_DECREF(value);
Py_DECREF(owner);
ERROR_IF(err, error);
}

Generic STORE_ATTR calls PyObject_SetAttr which goes through tp_setattro. Specializations (STORE_ATTR_SLOT, STORE_ATTR_WITH_HINT) write directly to the slot or dict to avoid the overhead.

gopy notes

LOAD_ATTR is in vm/eval_simple.go and calls objects.GetAttr. LOAD_ATTR_MODULE reads from the module's objects.Dict directly. LOAD_ATTR_SLOT uses a field offset stored in the inline cache. STORE_ATTR calls objects.SetAttr.