Skip to main content

Python/specialize.c

Source:

cpython 3.14 @ ab2d84fe1023/Python/specialize.c

Python/specialize.c implements CPython's adaptive specializer. Starting in Python 3.11, every instruction carries a counter. When the counter reaches zero the interpreter calls a specialization function from this file, which rewrites the instruction in the bytecode array with a faster variant (e.g. LOAD_ATTR_INSTANCE_VALUE) if the runtime types match a known pattern. If specialization fails the instruction is set to a "miss" variant that re-enters the generic path and increments a backoff counter.

Map

LinesSymbolRole
1-200helpers, _PyAdaptiveEntryCache entry structs, stats counters
201-600_Py_Specialize_LoadAttrSpecialize LOAD_ATTR for instances, modules, types, slots
601-900_Py_Specialize_StoreAttrSpecialize STORE_ATTR for instance dict and slots
901-1200_Py_Specialize_BinaryOpSpecialize BINARY_OP for int/float/str/bytes
1201-1600_Py_Specialize_CallSpecialize CALL for Python functions, C functions, classes
1601-2200_Py_Specialize_CompareOp, miscComparison and remaining opcodes

Reading

Inline cache layout

Each specializable instruction reserves inline cache entries immediately after itself in the bytecode array. The number of cache entries per instruction is recorded in Include/internal/pycore_opcode_metadata.h. Specializers write the observed type version tag and offset into these entries.

// Python/specialize.c:1 _PyAdaptiveEntry (cache entry)
typedef struct {
uint32_t tp_version; // PyTypeObject.tp_version_tag
uint16_t index; // dict offset or slot index
uint16_t keys_version; // dict keys version (for instance attrs)
} _PyAttrCache;

_Py_Specialize_LoadAttr

The most complex specializer. It checks whether the attribute lives in the instance __dict__ at a known offset (LOAD_ATTR_INSTANCE_VALUE), in the type's __dict__ as a data descriptor (LOAD_ATTR_WITH_HINT), or in a split dict. If the object's type has been modified since the cache was written, the version tag check fails and the interpreter deoptimizes.

// Python/specialize.c:201 _Py_Specialize_LoadAttr
int
_Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
{
PyTypeObject *type = Py_TYPE(owner);
if (tp_version_tag == 0) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
return -1;
}
...
if (/* instance dict offset fits in uint16 */) {
_Py_SET_OPCODE(*instr, LOAD_ATTR_INSTANCE_VALUE);
cache->index = (uint16_t)index;
cache->tp_version = type->tp_version_tag;
return 0;
}
...
}

Deoptimization and backoff

When a specialized instruction observes a type mismatch it rewrites itself to the _ADAPTIVE variant (e.g. LOAD_ATTR_ADAPTIVE) and increments a backoff counter. After enough misses the counter stops decrementing and the instruction stays in the slow path permanently for that site.

// Python/specialize.c (deopt helper)
static void
SPECIALIZATION_FAIL(int opcode, int kind) {
#ifdef Py_STATS
_py_stats->opcode_stats[opcode].specialization.failure++;
_py_stats->opcode_stats[opcode].specialization.failure_kinds[kind]++;
#endif
}

gopy notes

The gopy tier-2 optimizer in vm/ dispatches on type version tags using Go type switches rather than inline cache rewriting. The inline cache mechanism in specialize.c is not directly ported; gopy instead relies on Go's native type dispatch for the equivalent fast paths. Relevant gopy files: vm/eval_gen.go, vm/eval_call.go.