| # The bytecode interpreter | |
| This document describes the workings and implementation of the bytecode | |
| interpreter, the part of python that executes compiled Python code. Its | |
| entry point is in [Python/ceval.c](../Python/ceval.c). | |
| At a high level, the interpreter consists of a loop that iterates over the | |
| bytecode instructions, executing each of them via a switch statement that | |
| has a case implementing each opcode. This switch statement is generated | |
| from the instruction definitions in [Python/bytecodes.c](../Python/bytecodes.c) | |
| which are written in [a DSL](../Tools/cases_generator/interpreter_definition.md) | |
| developed for this purpose. | |
| Recall that the [Python Compiler](compiler.md) produces a [`CodeObject`](code_objects.md), | |
| which contains the bytecode instructions along with static data that is required to execute them, | |
| such as the consts list, variable names, | |
| [exception table](exception_handling.md#format-of-the-exception-table), and so on. | |
| When the interpreter's | |
| [`PyEval_EvalCode()`](https://docs.python.org/3.14/c-api/veryhigh.html#c.PyEval_EvalCode) | |
| function is called to execute a `CodeObject`, it constructs a [`Frame`](frames.md) and calls | |
| [`_PyEval_EvalFrame()`](https://docs.python.org/3.14/c-api/veryhigh.html#c.PyEval_EvalCode) | |
| to execute the code object in this frame. The frame holds the dynamic state of the | |
| `CodeObject`'s execution, including the instruction pointer, the globals and builtins. | |
| It also has a reference to the `CodeObject` itself. | |
| In addition to the frame, `_PyEval_EvalFrame()` also receives a | |
| [`Thread State`](https://docs.python.org/3/c-api/init.html#c.PyThreadState) | |
| object, `tstate`, which includes things like the exception state and the | |
| recursion depth. The thread state also provides access to the per-interpreter | |
| state (`tstate->interp`), which has a pointer to the per-runtime (that is, | |
| truly global) state (`tstate->interp->runtime`). | |
| Finally, `_PyEval_EvalFrame()` receives an integer argument `throwflag` | |
| which, when nonzero, indicates that the interpreter should just raise the current exception | |
| (this is used in the implementation of | |
| [`gen.throw`](https://docs.python.org/3.14/reference/expressions.html#generator.throw). | |
| By default, [`_PyEval_EvalFrame()`](https://docs.python.org/3.14/c-api/veryhigh.html#c.PyEval_EvalCode) | |
| simply calls [`_PyEval_EvalFrameDefault()`] to execute the frame. However, as per | |
| [`PEP 523`](https://peps.python.org/pep-0523/) this is configurable by setting | |
| `interp->eval_frame`. In the following, we describe the default function, | |
| `_PyEval_EvalFrameDefault()`. | |
| ## Instruction decoding | |
| The first task of the interpreter is to decode the bytecode instructions. | |
| Bytecode is stored as an array of 16-bit code units (`_Py_CODEUNIT`). | |
| Each code unit contains an 8-bit `opcode` and an 8-bit argument (`oparg`), both unsigned. | |
| In order to make the bytecode format independent of the machine byte order when stored on disk, | |
| `opcode` is always the first byte and `oparg` is always the second byte. | |
| Macros are used to extract the `opcode` and `oparg` from a code unit | |
| (`_Py_OPCODE(word)` and `_Py_OPARG(word)`). | |
| Some instructions (for example, `NOP` or `POP_TOP`) have no argument -- in this case | |
| we ignore `oparg`. | |
| A simplified version of the interpreter's main loop looks like this: | |
| ```c | |
| _Py_CODEUNIT *first_instr = code->co_code_adaptive; | |
| _Py_CODEUNIT *next_instr = first_instr; | |
| while (1) { | |
| _Py_CODEUNIT word = *next_instr++; | |
| unsigned char opcode = _Py_OPCODE(word); | |
| unsigned int oparg = _Py_OPARG(word); | |
| switch (opcode) { | |
| // ... A case for each opcode ... | |
| } | |
| } | |
| ``` | |
| This loop iterates over the instructions, decoding each into its `opcode` | |
| and `oparg`, and then executes the switch case that implements this `opcode`. | |
| The instruction format supports 256 different opcodes, which is sufficient. | |
| However, it also limits `oparg` to 8-bit values, which is too restrictive. | |
| To overcome this, the `EXTENDED_ARG` opcode allows us to prefix any instruction | |
| with one or more additional data bytes, which combine into a larger oparg. | |
| For example, this sequence of code units: | |
| EXTENDED_ARG 1 | |
| EXTENDED_ARG 0 | |
| LOAD_CONST 2 | |
| would set `opcode` to `LOAD_CONST` and `oparg` to `65538` (that is, `0x1_00_02`). | |
| The compiler should limit itself to at most three `EXTENDED_ARG` prefixes, to allow the | |
| resulting `oparg` to fit in 32 bits, but the interpreter does not check this. | |
| In the following, a `code unit` is always two bytes, while an `instruction` is a | |
| sequence of code units consisting of zero to three `EXTENDED_ARG` opcodes followed by | |
| a primary opcode. | |
| The following loop, to be inserted just above the `switch` statement, will make the above | |
| snippet decode a complete instruction: | |
| ```c | |
| while (opcode == EXTENDED_ARG) { | |
| word = *next_instr++; | |
| opcode = _Py_OPCODE(word); | |
| oparg = (oparg << 8) | _Py_OPARG(word); | |
| } | |
| ``` | |
| For various reasons we'll get to later (mostly efficiency, given that `EXTENDED_ARG` | |
| is rare) the actual code is different. | |
| ## Jumps | |
| Note that when the `switch` statement is reached, `next_instr` (the "instruction offset") | |
| already points to the next instruction. | |
| Thus, jump instructions can be implemented by manipulating `next_instr`: | |
| - A jump forward (`JUMP_FORWARD`) sets `next_instr += oparg`. | |
| - A jump backward (`JUMP_BACKWARD`) sets `next_instr -= oparg`. | |
| ## Inline cache entries | |
| Some (specialized or specializable) instructions have an associated "inline cache". | |
| The inline cache consists of one or more two-byte entries included in the bytecode | |
| array as additional words following the `opcode`/`oparg` pair. | |
| The size of the inline cache for a particular instruction is fixed by its `opcode`. | |
| Moreover, the inline cache size for all instructions in a | |
| [family of specialized/specializable instructions](#Specialization) | |
| (for example, `LOAD_ATTR`, `LOAD_ATTR_SLOT`, `LOAD_ATTR_MODULE`) must all be | |
| the same. Cache entries are reserved by the compiler and initialized with zeros. | |
| Although they are represented by code units, cache entries do not conform to the | |
| `opcode` / `oparg` format. | |
| If an instruction has an inline cache, the layout of its cache is described in | |
| the instruction's definition in [`Python/bytecodes.c`](../Python/bytecodes.c). | |
| The structs defined in [`pycore_code.h`](../Include/internal/pycore_code.h) | |
| allow us to access the cache by casting `next_instr` to a pointer to the relevant | |
| `struct`. The size of such a `struct` must be independent of the machine | |
| architecture, word size and alignment requirements. For a 32-bit field, the | |
| `struct` should use `_Py_CODEUNIT field[2]`. | |
| The instruction implementation is responsible for advancing `next_instr` past the inline cache. | |
| For example, if an instruction's inline cache is four bytes (that is, two code units) in size, | |
| the code for the instruction must contain `next_instr += 2;`. | |
| This is equivalent to a relative forward jump by that many code units. | |
| (In the interpreter definition DSL, this is coded as `JUMPBY(n)`, where `n` is the number | |
| of code units to jump, typically given as a named constant.) | |
| Serializing non-zero cache entries would present a problem because the serialization | |
| (:mod:`marshal`) format must be independent of the machine byte order. | |
| More information about the use of inline caches can be found in | |
| [PEP 659](https://peps.python.org/pep-0659/#ancillary-data). | |
| ## The evaluation stack | |
| Most instructions read or write some data in the form of object references (`PyObject *`). | |
| The CPython bytecode interpreter is a stack machine, meaning that its instructions operate | |
| by pushing data onto and popping it off the stack. | |
| The stack forms part of the frame for the code object. Its maximum depth is calculated | |
| by the compiler and stored in the `co_stacksize` field of the code object, so that the | |
| stack can be pre-allocated as a contiguous array of `PyObject*` pointers, when the frame | |
| is created. | |
| The stack effects of each instruction are also exposed through the | |
| [opcode metadata](../Include/internal/pycore_opcode_metadata.h) through two | |
| functions that report how many stack elements the instructions consumes, | |
| and how many it produces (`_PyOpcode_num_popped` and `_PyOpcode_num_pushed`). | |
| For example, the `BINARY_OP` instruction pops two objects from the stack and pushes the | |
| result back onto the stack. | |
| The stack grows up in memory; the operation `PUSH(x)` is equivalent to `*stack_pointer++ = x`, | |
| whereas `x = POP()` means `x = *--stack_pointer`. | |
| Overflow and underflow checks are active in debug mode, but are otherwise optimized away. | |
| At any point during execution, the stack level is knowable based on the instruction pointer | |
| alone, and some properties of each item on the stack are also known. | |
| In particular, only a few instructions may push a `NULL` onto the stack, and the positions | |
| that may be `NULL` are known. | |
| A few other instructions (`GET_ITER`, `FOR_ITER`) push or pop an object that is known to | |
| be an iterator. | |
| Instruction sequences that do not allow statically knowing the stack depth are deemed illegal; | |
| the bytecode compiler never generates such sequences. | |
| For example, the following sequence is illegal, because it keeps pushing items on the stack: | |
| LOAD_FAST 0 | |
| JUMP_BACKWARD 2 | |
| > [!NOTE] | |
| > Do not confuse the evaluation stack with the call stack, which is used to implement calling | |
| > and returning from functions. | |
| ## Error handling | |
| When the implementation of an opcode raises an exception, it jumps to the | |
| `exception_unwind` label in [Python/ceval.c](../Python/ceval.c). | |
| The exception is then handled as described in the | |
| [`exception handling documentation`](exception_handling.md#handling-exceptions). | |
| ## Python-to-Python calls | |
| The `_PyEval_EvalFrameDefault()` function is recursive, because sometimes | |
| the interpreter calls some C function that calls back into the interpreter. | |
| In 3.10 and before, this was the case even when a Python function called | |
| another Python function: | |
| The `CALL` opcode would call the `tp_call` dispatch function of the | |
| callee, which would extract the code object, create a new frame for the call | |
| stack, and then call back into the interpreter. This approach is very general | |
| but consumes several C stack frames for each nested Python call, thereby | |
| increasing the risk of an (unrecoverable) C stack overflow. | |
| Since 3.11, the `CALL` instruction special-cases function objects to "inline" | |
| the call. When a call gets inlined, a new frame gets pushed onto the call | |
| stack and the interpreter "jumps" to the start of the callee's bytecode. | |
| When an inlined callee executes a `RETURN_VALUE` instruction, the frame is | |
| popped off the call stack and the interpreter returns to its caller, | |
| by popping a frame off the call stack and "jumping" to the return address. | |
| There is a flag in the frame (`frame->is_entry`) that indicates whether | |
| the frame was inlined (set if it wasn't). | |
| If `RETURN_VALUE` finds this flag set, it performs the usual cleanup and | |
| returns from `_PyEval_EvalFrameDefault()` altogether, to a C caller. | |
| A similar check is performed when an unhandled exception occurs. | |
| ## The call stack | |
| Up through 3.10, the call stack was implemented as a singly-linked list of | |
| [frame objects](frames.md). This was expensive because each call would require a | |
| heap allocation for the stack frame. | |
| Since 3.11, frames are no longer fully-fledged objects. Instead, a leaner internal | |
| `_PyInterpreterFrame` structure is used. Most frames are allocated contiguously in a | |
| per-thread stack (see `_PyThreadState_PushFrame` in [Python/pystate.c](../Python/pystate.c)), | |
| which improves memory locality and reduces overhead. | |
| If the current `datastack_chunk` has enough space (`_PyThreadState_HasStackSpace`) | |
| then the lightweight `_PyFrame_PushUnchecked` can be used instead of `_PyThreadState_PushFrame`. | |
| Sometimes an actual `PyFrameObject` is needed, such as when Python code calls | |
| `sys._getframe()` or an extension module calls | |
| [`PyEval_GetFrame()`](https://docs.python.org/3/c-api/reflection.html#c.PyEval_GetFrame). | |
| In this case we allocate a proper `PyFrameObject` and initialize it from the | |
| `_PyInterpreterFrame`. | |
| Things get more complicated when generators are involved, since those do not | |
| follow the push/pop model. This includes async functions, which are based on | |
| the same mechanism. A generator object has space for a `_PyInterpreterFrame` | |
| structure, including the variable-size part (used for locals and the eval stack). | |
| When a generator (or async) function is first called, a special opcode | |
| `RETURN_GENERATOR` is executed, which is responsible for creating the | |
| generator object. The generator object's `_PyInterpreterFrame` is initialized | |
| with a copy of the current stack frame. The current stack frame is then popped | |
| off the frame stack and the generator object is returned. | |
| (Details differ depending on the `is_entry` flag.) | |
| When the generator is resumed, the interpreter pushes its `_PyInterpreterFrame` | |
| onto the frame stack and resumes execution. | |
| See also the [generators](generators.md) section. | |
| <!-- | |
| ## All sorts of variables | |
| The bytecode compiler determines the scope in which each variable name is defined, | |
| and generates instructions accordingly. For example, loading a local variable | |
| onto the stack is done using `LOAD_FAST`, while loading a global is done using | |
| `LOAD_GLOBAL`. | |
| The key types of variables are: | |
| - fast locals: used in functions | |
| - (slow or regular) locals: used in classes and at the top level | |
| - globals and builtins: the compiler cannot distinguish between globals and | |
| builtins (though at runtime, the specializing interpreter can) | |
| - cells: used for nonlocal references | |
| (TODO: Write the rest of this section. Alas, the author got distracted and won't have time to continue this for a while.) | |
| --> | |
| <!-- | |
| Other topics | |
| ------------ | |
| (TODO: Each of the following probably deserves its own section.) | |
| - co_consts, co_names, co_varnames, and their ilk | |
| - How calls work (how args are transferred, return, exceptions) | |
| - Eval breaker (interrupts, GIL) | |
| - Tracing | |
| - Setting the current lineno (debugger-induced jumps) | |
| - Specialization, inline caches etc. | |
| --> | |
| ## Introducing a new bytecode instruction | |
| It is occasionally necessary to add a new opcode in order to implement | |
| a new feature or change the way that existing features are compiled. | |
| This section describes the changes required to do this. | |
| First, you must choose a name for the bytecode, implement it in | |
| [`Python/bytecodes.c`](../Python/bytecodes.c) and add a documentation | |
| entry in [`Doc/library/dis.rst`](../Doc/library/dis.rst). | |
| Then run `make regen-cases` to assign a number for it (see | |
| [`Include/opcode_ids.h`](../Include/opcode_ids.h)) and regenerate a | |
| number of files with the actual implementation of the bytecode in | |
| [`Python/generated_cases.c.h`](../Python/generated_cases.c.h) and | |
| metadata about it in additional files. | |
| With a new bytecode you must also change what is called the "magic number" for | |
| .pyc files: bump the value of the variable `MAGIC_NUMBER` in | |
| [`Lib/importlib/_bootstrap_external.py`](../Lib/importlib/_bootstrap_external.py). | |
| Changing this number will lead to all .pyc files with the old `MAGIC_NUMBER` | |
| to be recompiled by the interpreter on import. Changes to | |
| [`Lib/importlib/_bootstrap_external.py`](../Lib/importlib/_bootstrap_external.py) | |
| will take effect only after running `make regen-importlib`. | |
| > [!NOTE] | |
| > Running `make regen-importlib` before adding the new bytecode target to | |
| > [`Python/bytecodes.c`](../Python/bytecodes.c) | |
| > (followed by `make regen-cases`) will result in an error. You should only run | |
| > `make regen-importlib` after the new bytecode target has been added. | |
| > [!NOTE] | |
| > On Windows, running the `./build.bat` script will automatically | |
| > regenerate the required files without requiring additional arguments. | |
| Finally, you need to introduce the use of the new bytecode. Update | |
| [`Python/codegen.c`](../Python/codegen.c) to emit code with this bytecode. | |
| Optimizations in [`Python/flowgraph.c`](../Python/flowgraph.c) may also | |
| need to be updated. If the new opcode affects a control flow or the block | |
| stack, you may have to update the `frame_setlineno()` function in | |
| [`Objects/frameobject.c`](../Objects/frameobject.c). It may also be necessary | |
| to update [`Lib/dis.py`](../Lib/dis.py) if the new opcode interprets its | |
| argument in a special way (like `FORMAT_VALUE` or `MAKE_FUNCTION`). | |
| If you make a change here that can affect the output of bytecode that | |
| is already in existence and you do not change the magic number, make | |
| sure to delete your old .py(c|o) files! Even though you will end up changing | |
| the magic number if you change the bytecode, while you are debugging your work | |
| you may be changing the bytecode output without constantly bumping up the | |
| magic number. This can leave you with stale .pyc files that will not be | |
| recreated. | |
| Running `find . -name '*.py[co]' -exec rm -f '{}' +` should delete all .pyc | |
| files you have, forcing new ones to be created and thus allow you test out your | |
| new bytecode properly. Run `make regen-importlib` for updating the | |
| bytecode of frozen importlib files. You have to run `make` again after this | |
| to recompile the generated C files. | |
| ## Specialization | |
| Bytecode specialization, which was introduced in | |
| [PEP 659](https://peps.python.org/pep-0659/), speeds up program execution by | |
| rewriting instructions based on runtime information. This is done by replacing | |
| a generic instruction with a faster version that works for the case that this | |
| program encounters. Each specializable instruction is responsible for rewriting | |
| itself, using its [inline caches](#inline-cache-entries) for | |
| bookkeeping. | |
| When an adaptive instruction executes, it may attempt to specialize itself, | |
| depending on the argument and the contents of its cache. This is done | |
| by calling one of the `_Py_Specialize_XXX` functions in | |
| [`Python/specialize.c`](../Python/specialize.c). | |
| The specialized instructions are responsible for checking that the special-case | |
| assumptions still apply, and de-optimizing back to the generic version if not. | |
| ## Families of instructions | |
| A *family* of instructions consists of an adaptive instruction along with the | |
| specialized instructions that it can be replaced by. | |
| It has the following fundamental properties: | |
| * It corresponds to a single instruction in the code | |
| generated by the bytecode compiler. | |
| * It has a single adaptive instruction that records an execution count and, | |
| at regular intervals, attempts to specialize itself. If not specializing, | |
| it executes the base implementation. | |
| * It has at least one specialized form of the instruction that is tailored | |
| for a particular value or set of values at runtime. | |
| * All members of the family must have the same number of inline cache entries, | |
| to ensure correct execution. | |
| Individual family members do not need to use all of the entries, | |
| but must skip over any unused entries when executing. | |
| The current implementation also requires the following, | |
| although these are not fundamental and may change: | |
| * All families use one or more inline cache entries, | |
| the first entry is always the counter. | |
| * All instruction names should start with the name of the adaptive | |
| instruction. | |
| * Specialized forms should have names describing their specialization. | |
| ## Example family | |
| The `LOAD_GLOBAL` instruction (in [Python/bytecodes.c](../Python/bytecodes.c)) | |
| already has an adaptive family that serves as a relatively simple example. | |
| The `LOAD_GLOBAL` instruction performs adaptive specialization, | |
| calling `_Py_Specialize_LoadGlobal()` when the counter reaches zero. | |
| There are two specialized instructions in the family, `LOAD_GLOBAL_MODULE` | |
| which is specialized for global variables in the module, and | |
| `LOAD_GLOBAL_BUILTIN` which is specialized for builtin variables. | |
| ## Performance analysis | |
| The benefit of a specialization can be assessed with the following formula: | |
| `Tbase/Tadaptive`. | |
| Where `Tbase` is the mean time to execute the base instruction, | |
| and `Tadaptive` is the mean time to execute the specialized and adaptive forms. | |
| `Tadaptive = (sum(Ti*Ni) + Tmiss*Nmiss)/(sum(Ni)+Nmiss)` | |
| `Ti` is the time to execute the `i`th instruction in the family and `Ni` is | |
| the number of times that instruction is executed. | |
| `Tmiss` is the time to process a miss, including de-optimzation | |
| and the time to execute the base instruction. | |
| The ideal situation is where misses are rare and the specialized | |
| forms are much faster than the base instruction. | |
| `LOAD_GLOBAL` is near ideal, `Nmiss/sum(Ni) ≈ 0`. | |
| In which case we have `Tadaptive ≈ sum(Ti*Ni)`. | |
| Since we can expect the specialized forms `LOAD_GLOBAL_MODULE` and | |
| `LOAD_GLOBAL_BUILTIN` to be much faster than the adaptive base instruction, | |
| we would expect the specialization of `LOAD_GLOBAL` to be profitable. | |
| ## Design considerations | |
| While `LOAD_GLOBAL` may be ideal, instructions like `LOAD_ATTR` and | |
| `CALL_FUNCTION` are not. For maximum performance we want to keep `Ti` | |
| low for all specialized instructions and `Nmiss` as low as possible. | |
| Keeping `Nmiss` low means that there should be specializations for almost | |
| all values seen by the base instruction. Keeping `sum(Ti*Ni)` low means | |
| keeping `Ti` low which means minimizing branches and dependent memory | |
| accesses (pointer chasing). These two objectives may be in conflict, | |
| requiring judgement and experimentation to design the family of instructions. | |
| The size of the inline cache should as small as possible, | |
| without impairing performance, to reduce the number of | |
| `EXTENDED_ARG` jumps, and to reduce pressure on the CPU's data cache. | |
| ### Gathering data | |
| Before choosing how to specialize an instruction, it is important to gather | |
| some data. What are the patterns of usage of the base instruction? | |
| Data can best be gathered by instrumenting the interpreter. Since a | |
| specialization function and adaptive instruction are going to be required, | |
| instrumentation can most easily be added in the specialization function. | |
| ### Choice of specializations | |
| The performance of the specializing adaptive interpreter relies on the | |
| quality of specialization and keeping the overhead of specialization low. | |
| Specialized instructions must be fast. In order to be fast, | |
| specialized instructions should be tailored for a particular | |
| set of values that allows them to: | |
| 1. Verify that incoming value is part of that set with low overhead. | |
| 2. Perform the operation quickly. | |
| This requires that the set of values is chosen such that membership can be | |
| tested quickly and that membership is sufficient to allow the operation to be | |
| performed quickly. | |
| For example, `LOAD_GLOBAL_MODULE` is specialized for `globals()` | |
| dictionaries that have a keys with the expected version. | |
| This can be tested quickly: | |
| * `globals->keys->dk_version == expected_version` | |
| and the operation can be performed quickly: | |
| * `value = entries[cache->index].me_value;`. | |
| Because it is impossible to measure the performance of an instruction without | |
| also measuring unrelated factors, the assessment of the quality of a | |
| specialization will require some judgement. | |
| As a general rule, specialized instructions should be much faster than the | |
| base instruction. | |
| ### Implementation of specialized instructions | |
| In general, specialized instructions should be implemented in two parts: | |
| 1. A sequence of guards, each of the form | |
| `DEOPT_IF(guard-condition-is-false, BASE_NAME)`. | |
| 2. The operation, which should ideally have no branches and | |
| a minimum number of dependent memory accesses. | |
| In practice, the parts may overlap, as data required for guards | |
| can be re-used in the operation. | |
| If there are branches in the operation, then consider further specialization | |
| to eliminate the branches. | |
| ### Maintaining stats | |
| Finally, take care that stats are gathered correctly. | |
| After the last `DEOPT_IF` has passed, a hit should be recorded with | |
| `STAT_INC(BASE_INSTRUCTION, hit)`. | |
| After an optimization has been deferred in the adaptive instruction, | |
| that should be recorded with `STAT_INC(BASE_INSTRUCTION, deferred)`. | |
| ## Interpreter types | |
| There are three different types of interpreters to choose from based on compiler support: | |
| * traditional switch-case interpreter | |
| Supported by all compilers covered in PEP 7. | |
| * computed-gotos interpreter | |
| Enabled using configure option `--with-computed-gotos` and used by default on supported compilers. | |
| It uses [Labels as Values](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html) | |
| for more efficient dispatching. | |
| * tail-calling interpreter | |
| Enabled using configure option `--with-tail-call-interp` (or `--tail-call-interp` for build.bat on Windows). | |
| It uses [tail calls](https://clang.llvm.org/docs/AttributeReference.html#musttail) and the | |
| [preserve_none](https://clang.llvm.org/docs/AttributeReference.html#preserve-none) | |
| calling convention between the small C functions that implement individual Python opcodes. | |
| Not all compilers support these and if they do not all targets might be supported (for example, | |
| MSVC currently only supports x64 and only in optimized builds). | |
| In addition, compilers must do [escape analysis](https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-musttail) | |
| of the lifetimes of automatic variables, function parameters, and temporaries to ensure proper tail-calls. They | |
| emit a compile error in case of a violation or detection failure. The ability to detect this varies depending on the compiler and | |
| also on the optimization level. Following techniques are particularly helpful to the MSVC compiler in this regard | |
| * [Introducing additional scopes](https://github.com/python/cpython/blob/3908593039bde9d4b591ab09919003ee57418d64/Python/bytecodes.c#L2526) | |
| * [extracting problematic code paths into a separate function](https://github.com/python/cpython/pull/143068/files#diff-729a985b0cb8b431cb291f1edb561bbbfea22e3f8c262451cd83328a0936a342R3724) | |
| * [returning a pointer instead of taking it as an output parameter](https://github.com/python/cpython/blob/3908593039bde9d4b591ab09919003ee57418d64/Include/internal/pycore_ceval.h#L489-L492) | |
| Using `restrict` is another (currently unused) remedy. | |
| Additional resources | |
| -------------------- | |
| * Brandt Bucher's talk about the specializing interpreter at PyCon US 2023. | |
| [Slides](https://github.com/brandtbucher/brandtbucher/blob/master/2023/04/21/inside_cpython_311s_new_specializing_adaptive_interpreter.pdf) | |
| [Video](https://www.youtube.com/watch?v=PGZPSWZSkJI&t=1470s) |