Skip to main content

FAQ

Q: CinderX vs Cinder

Cinder was a fork of the CPython runtime developed at Meta. It included runtime optimizations (e.g. JIT) and was specifically targeted at the Instagram Django codebase. For Python 3.10, Meta decided to turn it into a Python extension to improve compatibility with newer Python versions. This extension is now known as CinderX ("the X" is for "extension").

Historically, for Python versions 3.10 through 3.12, CinderX depended on patches to Meta's fork of the Python runtime. Python 3.14 is the first version of stock CPython that CinderX supports.

Q: How does Static Python compilation work?

In a single sentence, Static Python is a bytecode compiler which uses existing type annotations to make Python code more efficient. Internally, it consists of a few components. Broadly, we have runtime components and compile-time components.

Compilation Steps

High level description of each step in the flowchart.

StepPhaseDescription
Source CodePre-compileThis is just the raw text source code. Technically, it’s not even a step, but it occurs in the diagram, therefore has its own entry.
Declaration VisitCompile TimeWhen a module is being loaded, we need to know the symbols that are declared within it. Some of our analyses rely on this information (before even getting to the actual code generation steps).
Strict Modules analysis + rewriteCompile TimeThis is where we run the Strict Module analyzer and rewriter. The rewriter is an AST transformation, and includes features such as inserting non-changeable builtin functions (e.g len(), into the module namespace. The analyzer is an abstract interpreter which checks for import time side effects (and other things too, but that’s outside the scope of this document).
AST OptimizationsCompile TimeThis step performs common optimizations on the AST, such as folding constant operations, optimizing immutable data structures, etc.
Type Checker (Type Binder)Compile TimeThis is the magic step where we perform type analysis on the given code. We build a mapping from AST Node to Types, while also checking for correctness. In a few cases, we also perform type inference, to improve the experience of writing typed code. The Type Checker can also be run in a linting mode, where we output a list of detected type errors.
Code GenerationCompile TimeIn this step, we actually construct Python bytecode, by walking the AST. We take advantage of all the type information from the previous step, to generate efficient bytecode. In addition to opcodes in “normal” Python, Static Python uses a new specialized set of opcodes, which remove a lot of overhead associated with checking types at runtime. Whenever a type cannot be guaranteed by the Type Checker, we treat it as “dynamic” (or “Any”), and fall back to “normal” opcodes. In this way, our generated bytecode is fully compatible with untyped code as well!
Peephole optimizerCompile TimeThis performs further optimizations on the generated bytecode. E.g: removing bytecode that is unreachable.
Strict/Static import loaderCompile Time + RuntimeThe Import loader is an implementation of importlib.abc.SourceFileLoader. It is responsible for stuff like checking whether a module is strict/static, and then running the appropriate kinds of compilation steps on it. It is used at compile time, as well as runtime. During compilation, the Loader creates .pyc files. These can then be packaged and deployed on servers. At runtime, the loader imports and executes this bytecode. After this step, the bytecode may be executed by the interpreter (the eval loop), or may be further compiled by the Cinder JIT.
Interpreter ExtensionsRuntimeThis refers to the new set of opcodes introduced by Static Python (as mentioned above). These are very closely related with the classloader, which we will discuss separately.
JITRuntimeThe JIT is vast enough to require its own set of high-level documentation. For the purposes of Static Python, we can think of it has having three compilation steps: - HIR (High level IR) - LIR (Low level IR) - Assembly (Generation of assembly code) Each of the above steps has its own optimization and analysis passes. Additionally, the JIT interacts heavily with Static Python through its support for primitive types. Needless to say, a majority of Static Python optimizations are enabled by the JIT.