Skip to main content

Python/ceval.c (part 33)

Source:

cpython 3.14 @ ab2d84fe1023/Python/ceval.c

This annotation covers attribute write specializations. See python_ceval32_detail for LOAD_SUPER_ATTR and LOAD_ATTR specializations.

Map

LinesSymbolRole
1-80STORE_ATTR (generic)Write an attribute, looking up via the descriptor protocol
81-180STORE_ATTR_INSTANCE_VALUEWrite to an instance's __dict__ at a cached index
181-280STORE_ATTR_WITH_HINTWrite to instance dict using an offset hint
281-380STORE_ATTR_SLOTWrite to a __slots__ slot at a cached offset
381-500Cache populationHow STORE_ATTR specializes on first observed pattern

Reading

STORE_ATTR_INSTANCE_VALUE

// CPython: Python/ceval.c:2620 STORE_ATTR_INSTANCE_VALUE
inst(STORE_ATTR_INSTANCE_VALUE, (owner, value --)) {
/* Cache: tp_version + index into the split-table values array */
DEOPT_IF(Py_TYPE(owner)->tp_version_tag != cache->tp_version, STORE_ATTR);
PyDictObject *dict = (PyDictObject *)*_PyObject_GetDictPtr(owner);
/* If the instance dict has not yet been allocated, allocate it */
if (dict == NULL) {
dict = (PyDictObject *)PyObject_GenericGetDict(owner, NULL);
DEOPT_IF(dict == NULL, STORE_ATTR);
}
/* Write directly to the values array */
PyObject *old = dict->ma_values->values[cache->index];
dict->ma_values->values[cache->index] = value;
dict->ma_version_tag++; /* Invalidate LOAD_ATTR caches */
Py_XDECREF(old);
Py_DECREF(owner);
}

For classes with a consistent __dict__ layout (same keys across instances — the "split table" optimization), attribute writes go directly to the values array at a cached index. No hash lookup needed.

STORE_ATTR_WITH_HINT

// CPython: Python/ceval.c:2680 STORE_ATTR_WITH_HINT
inst(STORE_ATTR_WITH_HINT, (owner, value --)) {
/* Like LOAD_ATTR_WITH_HINT but for writes.
Cache: tp_version + dict_version + index hint */
PyObject *dict = *_PyObject_GetDictPtr(owner);
DEOPT_IF(dict == NULL, STORE_ATTR);
DEOPT_IF(((PyDictObject *)dict)->ma_version_tag != cache->dict_version,
STORE_ATTR);
PyDictObject *dobj = (PyDictObject *)dict;
Py_SETREF(dobj->ma_values->values[cache->index], value);
dobj->ma_version_tag++;
Py_DECREF(owner);
}

obj.x = value after specialization is a direct array write. The dict_version check guards against dict modifications (adding new keys changes the layout and invalidates the hint).

STORE_ATTR_SLOT

// CPython: Python/ceval.c:2740 STORE_ATTR_SLOT
inst(STORE_ATTR_SLOT, (owner, value --)) {
/* Cache: tp_version + byte offset */
DEOPT_IF(Py_TYPE(owner)->tp_version_tag != cache->tp_version, STORE_ATTR);
char *addr = (char *)owner + cache->offset;
Py_SETREF(*(PyObject **)addr, value);
Py_DECREF(owner);
}

__slots__ attributes have fixed offsets in the object layout. STORE_ATTR_SLOT writes to obj + offset directly, matching C struct member write performance. Py_SETREF atomically decrements the old value and stores the new one.

Cache population

// CPython: Python/ceval.c:2560 _Py_Specialize_StoreAttr
void
_Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
{
PyTypeObject *type = Py_TYPE(owner);
/* Check for __slots__ */
PyObject *descr = _PyType_Lookup(type, name);
if (descr != NULL && PyObject_TypeCheck(descr, &PyMemberDescr_Type)) {
/* Slot: use STORE_ATTR_SLOT */
cache->offset = ((PyMemberDescrObject *)descr)->d_member->offset;
instr->op.code = STORE_ATTR_SLOT;
return;
}
/* Check for split table instance dict */
if (type->tp_dictoffset > 0 && !_PyType_HasFeature(type, Py_TPFLAGS_MANAGED_DICT)) {
...
instr->op.code = STORE_ATTR_INSTANCE_VALUE;
return;
}
/* Use hint-based store */
instr->op.code = STORE_ATTR_WITH_HINT;
}

Specialization is triggered after STORE_ATTR is executed a few times. The adaptive interpreter profiles the actual types seen and picks the most efficient variant.

gopy notes

STORE_ATTR_INSTANCE_VALUE is in vm/eval_specialize.go and calls objects.DictSetItemIndex. STORE_ATTR_WITH_HINT uses objects.DictSetItemHint. STORE_ATTR_SLOT writes via unsafe.Pointer. _Py_Specialize_StoreAttr is vm.specializeStoreAttr which sets the opcode in the inline cache.