Skip to content

Commit 1d68a10

Browse files
doc: add guide on how to add a bytecode specialization (#1286)
1 parent 9d481ef commit 1d68a10

3 files changed

Lines changed: 178 additions & 0 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
.. _bytecode-specialization:
2+
3+
================================
4+
Adding a Bytecode Specialization
5+
================================
6+
7+
CPython uses an adaptive, specializing interpreter (PEP 659) to optimize bytecode execution dynamically at runtime. When generic opcodes (such as ``CONTAINS_OP`` or ``BINARY_OP``) execute frequently with predictable operand types, CPython morphs them into specialized fast-path micro-ops (uops) to bypass generic type dispatch.
8+
9+
This guide describes the step-by-step process of introducing a new bytecode specialization in CPython.
10+
11+
.. note::
12+
13+
This guide uses the specialization of ``CONTAINS_OP`` (from `CPython PR #116385 <https://github.com/python/cpython/pull/116385>`_) as a reference example.
14+
15+
16+
Overview of Steps
17+
-----------------
18+
19+
Adding a new bytecode specialization involves coordinated changes across several files in the CPython source tree:
20+
21+
1. :ref:`spec-step-bytecodes`
22+
2. :ref:`spec-step-uop`
23+
3. :ref:`spec-step-macro`
24+
4. :ref:`spec-step-cache-struct`
25+
5. :ref:`spec-step-specialize-fn`
26+
6. :ref:`spec-step-stats`
27+
7. :ref:`spec-step-opcode-py`
28+
8. :ref:`spec-step-magic-num`
29+
9. :ref:`spec-step-regen`
30+
31+
32+
.. _spec-step-bytecodes:
33+
34+
1. Modify the Opcode Definition in ``Python/bytecodes.c``
35+
---------------------------------------------------------
36+
37+
In ``Python/bytecodes.c``, locate the existing generic instruction. Change its definition from a top-level instruction (``inst``) to a micro-op (``op``) and prefix its name with an underscore.
38+
39+
For example, convert ``CONTAINS_OP`` into ``_CONTAINS_OP``:
40+
41+
.. code-block:: c
42+
43+
op(_CONTAINS_OP, (left, right -- res)) {
44+
// Implementation of generic operation
45+
int res_val = PySequence_Contains(right, left);
46+
if (res_val < 0) goto error;
47+
res = res_val ? Py_True : Py_False;
48+
}
49+
50+
51+
.. _spec-step-uop:
52+
53+
2. Add the Specializing Micro-Op (uop)
54+
--------------------------------------
55+
56+
Add a new uop definition in ``Python/bytecodes.c`` that triggers the specialization check when the execution counter reaches zero.
57+
58+
.. code-block:: c
59+
60+
op(_SPECIALIZE_CONTAINS_OP, (left, right -- left, right)) {
61+
_PySpecializer_Requestation(this_instr, _SPECIALIZE_CONTAINS_OP);
62+
}
63+
64+
65+
.. _spec-step-macro:
66+
67+
3. Define the Macro Instruction
68+
-------------------------------
69+
70+
Combine the specializing uop and the original uop into a macro instruction using ``macro`` syntax in ``Python/bytecodes.c``:
71+
72+
.. code-block:: c
73+
74+
macro(CONTAINS_OP) = _SPECIALIZE_CONTAINS_OP + _CONTAINS_OP;
75+
76+
77+
.. _spec-step-cache-struct:
78+
79+
4. Define the Cache Structure in ``Include/internal/pycore_code.h``
80+
-------------------------------------------------------------------
81+
82+
Define a C struct for the instruction's inline cache in ``Include/internal/pycore_code.h``. Every cache entry must include at least a 16-bit specialization counter (``counter``), plus any specialized metadata or version pointers needed.
83+
84+
.. code-block:: c
85+
86+
typedef struct {
87+
_PySpecializationCacheTop counter;
88+
// Additional cache fields if needed (e.g., version or type pointers)
89+
} _PyContainsOpCache;
90+
91+
92+
.. _spec-step-specialize-fn:
93+
94+
5. Write the Specializing Function in ``Python/specialize.c``
95+
-------------------------------------------------------------
96+
97+
Implement the specializing logic in ``Python/specialize.c``. This function inspects the runtime operand types, checks whether they qualify for a fast path, updates the cache, and rewrites the opcode if appropriate.
98+
99+
.. code-block:: c
100+
101+
void
102+
_Py_Specialize_ContainsOp(PyObject *left, PyObject *right, _Py_CODEUNIT *instr)
103+
{
104+
_PyContainsOpCache *cache = (_PyContainsOpCache *)instr;
105+
if (PySet_CheckExact(right)) {
106+
// Specialize for set containment
107+
instr->op.code = _BINARY_OP_CONTAINS_SET;
108+
}
109+
else {
110+
// Fallback / UNSTATISFIED
111+
STAT_INC(CONTAINS_OP, failure);
112+
}
113+
}
114+
115+
116+
.. _spec-step-stats:
117+
118+
6. Update Operation Statistics in ``Python/specialize.c``
119+
---------------------------------------------------------
120+
121+
Track specialization hits, misses, and execution counts by calling ``add_stat_dict()`` or incrementing statistic counters in ``Python/specialize.c``:
122+
123+
.. code-block:: c
124+
125+
STAT_INC(CONTAINS_OP, hit);
126+
127+
128+
.. _spec-step-opcode-py:
129+
130+
7. Add Cache Layout in ``Lib/opcode.py``
131+
----------------------------------------
132+
133+
Update ``Lib/opcode.py`` to inform Python's ``dis`` (disassembler) module about the size and structure of the new instruction's inline cache entries:
134+
135+
.. code-block:: python
136+
137+
_specialized_opcodes["CONTAINS_OP"] = {
138+
"counter": 1,
139+
}
140+
141+
142+
.. _spec-step-magic-num:
143+
144+
8. Bump the Magic Number in ``Include/internal/pycore_magic_number.h``
145+
----------------------------------------------------------------------
146+
147+
Because adding or altering inline cache structures changes the bytecode format, increment ``MAGIC_NUMBER`` in ``Include/internal/pycore_magic_number.h``. This ensures older ``.pyc`` files are invalidated and recompiled.
148+
149+
150+
.. _spec-step-regen:
151+
152+
9. Regenerate Code Files
153+
------------------------
154+
155+
Run the code generators to update auto-generated files (like ``opcode_targets.h``, ``executor_cases.c``, etc.):
156+
157+
On Linux / macOS:
158+
159+
.. code-block:: bash
160+
161+
make regen-all
162+
163+
On Windows:
164+
165+
.. code-block:: bash
166+
167+
build.bat --regen
168+
169+
170+
See Also
171+
--------
172+
173+
* `PEP 659 -- Specializing Adaptive Interpreter <https://peps.python.org/pep-0659/>`_
174+
* `CPython PR #116385 <https://github.com/python/cpython/pull/116385>`_
175+
* :ref:`dev-workflow`

developer-workflow/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ Development workflow
1515
c-api
1616
grammar
1717
porting
18+
bytecode-specialization
19+

index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ contributors through the process.
4343
* :ref:`communication`
4444
* :ref:`gitbootcamp`
4545
* :ref:`devcycle`
46+
* :ref:`bytecode-specialization`
4647
* :ref:`ai-tools`
4748
-
4849
* :ref:`tracker`

0 commit comments

Comments
 (0)