Skip to main content

Python/ceval.c (part 44)

Source:

cpython 3.14 @ ab2d84fe1023/Python/ceval.c

This annotation covers attribute store specializations. See python_ceval43_detail for LOAD_ATTR specializations.

Map

LinesSymbolRole
1-80STORE_ATTR baseGeneric attribute store with inline cache
81-160STORE_ATTR_SLOTStore to a __slots__ offset
161-240STORE_ATTR_INSTANCE_VALUEStore to instance __dict__ at known offset
241-340STORE_ATTR_WITH_HINTStore using a cached dict key index hint
341-500Cache invalidationHow type version changes trigger deopt

Reading

STORE_ATTR_SLOT

// CPython: Python/ceval.c:5480 STORE_ATTR_SLOT
inst(STORE_ATTR_SLOT, (unused/2, owner, v --)) {
/* self.x = v where x is a __slots__ member */
Py_ssize_t index = cache->index;
DEOPT_IF(!PyObject_TypeCheck(owner, cache->type), STORE_ATTR);
PyObject **slot = (PyObject **)((char *)owner + index);
PyObject *old = *slot;
*slot = v; /* store new value */
Py_XDECREF(old);
Py_DECREF(owner);
}

STORE_ATTR_SLOT writes directly to a fixed byte offset in the object's memory. This is the fastest possible attribute store: no dict lookup, no hash, just a pointer store with a type check.

STORE_ATTR_INSTANCE_VALUE

// CPython: Python/ceval.c:5540 STORE_ATTR_INSTANCE_VALUE
inst(STORE_ATTR_INSTANCE_VALUE, (unused/2, owner, value --)) {
/* self.x = value for instances with split __dict__ */
DEOPT_IF(!PyObject_TypeCheck(owner, cache->type), STORE_ATTR);
PyDictObject *dict = (PyDictObject *)(((PyObject **)owner)[0]);
DEOPT_IF(dict == NULL, STORE_ATTR);
DEOPT_IF(dict->ma_keys->dk_version != cache->keys_version, STORE_ATTR);
PyObject **values = dict->ma_values->values;
Py_ssize_t index = cache->index;
PyObject *old = values[index];
values[index] = value; /* store directly into split-dict values */
Py_XDECREF(old);
Py_DECREF(owner);
}

For split-dict instances (all instances of the same class share a key table), self.x = v stores v at a known index in the per-instance values array. The key version check ensures the dict layout hasn't changed (e.g., due to del self.x).

STORE_ATTR_WITH_HINT

// CPython: Python/ceval.c:5600 STORE_ATTR_WITH_HINT
inst(STORE_ATTR_WITH_HINT, (unused/2, owner, value --)) {
/* Use a hint to find the key in the dict faster than full hash lookup */
PyDictObject *dict = ...;
Py_ssize_t hint = cache->index;
PyObject *res = NULL;
if (_PyDict_SetItem_KnownHash_LockHeld(dict, name, value, hash) < 0) {
...
}
}

The hint is a cached index that avoids a full hash table probe when the key has been seen before. If the hint is stale, it falls back to a full lookup and updates the hint.

Cache invalidation

// CPython: Python/ceval.c:5660 DEOPT_IF logic
/* DEOPT_IF(cond, opname) checks a condition and if true:
1. Resets the opcode to the generic form (e.g., STORE_ATTR)
2. Jumps to the specialization entry to re-specialize

Type version in the cache: incremented whenever the type is modified
(attribute added/deleted, method replaced). A mismatch means
specialization is stale and must be discarded. */

The inline cache stores the tp_version_tag of the type at specialization time. On every STORE_ATTR_SLOT execution, the type version is checked. If it changed (e.g., Foo.x = property(...) was called), the cache is stale and DEOPT_IF falls back to the generic STORE_ATTR.

gopy notes

STORE_ATTR_SLOT is in vm/eval_simple.go; it writes to objects.Instance.Slots[index]. STORE_ATTR_INSTANCE_VALUE writes to objects.Instance.DictValues[index]. Cache invalidation uses objects.Type.Version which is incremented on every type.__setattr__ call.