Fast Python JIT compiler using LLVM ORC - lowers Python bytecode to native machine code
Project description
JustJIT
A high-performance Just-In-Time compiler for Python that leverages LLVM's ORC JIT infrastructure to compile Python bytecode to native machine code at runtime.
Table of Contents
- Overview
- Features
- Installation
- Quick Start
- Architecture
- Supported Python Features
- Generator and Coroutine Support
- Control Flow Graph (CFG) Implementation
- Exception Handling
- Current Limitations
- Building from Source
- API Reference
- Contributing
- License
Overview
JustJIT transforms Python functions into optimized native machine code using LLVM. Unlike traditional interpreters that execute bytecode instruction-by-instruction, JustJIT analyzes Python bytecode and generates equivalent LLVM IR, which is then optimized and compiled to native code.
How It Works
- Bytecode Extraction: Parse Python function bytecode using the
dismodule - IR Generation: Translate each opcode to LLVM IR, maintaining Python semantics
- Optimization: Apply LLVM optimization passes (O0-O3)
- Compilation: JIT compile to native machine code
- Execution: Call the native function with Python object arguments
Features
Core Features
- Python 3.10+ Bytecode Support: Full support for Python 3.10-3.13 bytecode formats
- LLVM ORC JIT v2: Modern JIT infrastructure with lazy compilation support
- Multiple Compilation Modes:
objectmode: Full Python object semanticsintmode: Pure integer arithmetic (10x faster for numeric code)automode: Automatic detection based on function analysis
Advanced Features
- Generator Support: Full state machine implementation for
yield - Coroutine Support: Async/await with proper awaitable protocol
- Exception Handling: Python 3.11+ exception table parsing
- Pattern Matching:
match/casestatements with CFG analysis - Closure Support: Captured variables from enclosing scopes
- Comprehensions: List, dict, and set comprehensions
Installation
From PyPI (Recommended)
pip install justjit
From Source
See Building from Source for detailed instructions.
Quick Start
Basic Usage
from justjit import jit
@jit
def fibonacci(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(n - 1):
a, b = b, a + b
return b
result = fibonacci(40) # Runs as native code
Generator Functions
from justjit import jit
@jit
def count_up(n):
i = 0
while i < n:
yield i
i += 1
for value in count_up(10):
print(value)
Async Functions
from justjit import jit
import asyncio
@jit
async def fetch_data():
await asyncio.sleep(0.1)
return "data"
result = asyncio.run(fetch_data())
Integer Mode (Maximum Performance)
from justjit import jit
@jit(mode='int')
def sum_range(n):
total = 0
for i in range(n):
total += i
return total
# Pure LLVM i64 arithmetic - no Python object overhead
result = sum_range(1000000)
Architecture
Project Structure
justjit/
├── src/
│ ├── jit_core.cpp # Core JIT engine (~12,000 lines)
│ │ # - Opcode handlers for all Python bytecode
│ │ # - Generator state machine compiler
│ │ # - Coroutine compiler with await handling
│ │ # - CFG analysis and PHI node generation
│ │ # - Exception table parsing and handling
│ ├── jit_core.h # JIT engine header
│ │ # - JITGeneratorObject and JITCoroutineObject types
│ │ # - BasicBlockInfo and CFGStackState for CFG
│ │ # - Python C API function declarations
│ ├── bindings.cpp # nanobind Python bindings
│ ├── opcodes.h # Python 3.13 opcode definitions
│ └── justjit/
│ └── __init__.py # Python interface (~800 lines)
│ # - @jit decorator
│ # - Bytecode extraction
│ # - Exception table parsing
│ # - Fallback handling
├── docs/
│ ├── OPCODES_REFERENCE.md # Detailed opcode documentation
│ └── ASYNC_IMPLEMENTATION_RESEARCH.md
├── CMakeLists.txt # Build configuration
├── pyproject.toml # Python package metadata
├── OPCODE_STATUS.md # Implementation status tracker
└── CFG_IMPLEMENTATION_PLAN.md # CFG architecture design
Core Components
| Component | Description |
|---|---|
| JITCore | Main compiler class managing LLVM context, module, and JIT engine |
| compile_function | Compiles regular Python functions to native code |
| compile_generator | Transforms generators into state machine step functions |
| compile_coroutine | Handles async functions with await/send protocol |
| CFG Analysis | Control flow graph with PHI nodes for SSA form |
Supported Python Features
Fully Implemented (95+ opcodes)
Control Flow
if/elif/elsestatementsforloops withrange(), lists, tupleswhileloopsbreak/continuereturnstatements
Data Types
- Integers, floats, strings, booleans, None
- Lists, tuples, dicts, sets
- Slicing (
x[1:3],x[::2]) - Comprehensions (list, dict, set)
Operations
- All arithmetic:
+,-,*,/,//,%,**,@ - All bitwise:
&,|,^,~,<<,>> - All comparison:
<,<=,>,>=,==,!= - Membership:
in,not in - Identity:
is,is not - Boolean:
and,or,not - Augmented assignment:
+=,-=,*=, etc.
Functions
- Function calls with positional and keyword arguments
*argsand**kwargs- Default arguments
- Closures and nested functions
- Lambda expressions (via nested function)
Classes
- Attribute access (
obj.attr) - Method calls
super()calls__build_class__for class creation
Exception Handling
try/except/finallyraisewith optional cause- Exception type matching
- Stack unwinding with proper cleanup
Pattern Matching (Python 3.10+)
match/casestatements- Sequence patterns:
case [a, b, c] - Mapping patterns:
case {"key": value} - Class patterns:
case Point(x, y) - Wildcard:
case _ - Guards:
case x if x > 0
Imports
import modulefrom module import namefrom module import *
String Formatting
- f-strings:
f"Hello, {name}!" - Format specs:
f"{value:.2f}" - Conversions:
!r,!s,!a
Generator and Coroutine Support
Generator Implementation
JustJIT compiles generators into state machine step functions. Each yield creates a suspension point with a unique state ID.
// Generator step function signature
PyObject* step_func(int32_t* state, PyObject** locals, PyObject* sent_value);
// State values:
// 0 = initial (not started)
// 1..N = resume points after each yield
// -1 = completed (returned)
// -2 = error occurred
Generator Object Structure
struct JITGeneratorObject {
PyObject_HEAD
int32_t state; // Current suspension state
PyObject** locals; // Persistent local variables
Py_ssize_t num_locals; // Including stack spill slots
GeneratorStepFunc step_func; // Compiled native function
PyObject* name; // For __name__
PyObject* qualname; // For __qualname__
};
How Generator Compilation Works
- State Identification: Each
YIELD_VALUEopcode gets a unique state ID - State Dispatch: Entry creates a switch on
*stateto jump to correct resume point - Stack Persistence: Values are spilled to persistent slots before yield
- Resume Restoration: Stack values are restored from slots after resume
Coroutine Implementation
Coroutines extend generators with the awaitable protocol:
struct JITCoroutineObject {
PyObject_HEAD
int32_t state; // Suspension state
PyObject** locals; // Persistent locals
Py_ssize_t num_locals;
GeneratorStepFunc step_func;
PyObject* name;
PyObject* qualname;
PyObject* awaiting; // Currently awaited object
};
Await Handling
The SEND opcode handles await delegation:
GET_AWAITABLE: Get iterator from awaited objectSEND None: Start iterationSEND value: Forward sent values to sub-iteratorEND_SEND: Handle completion, extract return value
Control Flow Graph (CFG) Implementation
JustJIT implements proper SSA (Static Single Assignment) form using CFG analysis and PHI nodes for complex control flow patterns.
Why CFG is Needed
Python bytecode simulates a value stack. When control flow merges (e.g., after if/else), different paths may have pushed different values. LLVM requires each value to have a single definition point, so we use PHI nodes to merge values from different paths.
CFG Data Structures
struct BasicBlockInfo {
int start_offset; // Block start
int end_offset; // Block end
std::vector<int> predecessors; // Incoming edges
std::vector<int> successors; // Outgoing edges
int stack_depth_at_entry; // Expected stack depth
bool is_exception_handler; // Handler block flag
bool needs_phi_nodes; // Multiple predecessors
llvm::BasicBlock* llvm_block; // LLVM representation
};
PHI Node Generation
When compiling pattern matching (match/case):
- Block Analysis: Identify all case blocks and their targets
- Stack Tracking: Track stack state at each merge point
- PHI Creation: At merge blocks, create PHI nodes for each stack slot
- Value Wiring: Connect incoming values from each predecessor
Exception Handling
JustJIT parses Python 3.11+ exception tables to handle try/except blocks correctly.
Exception Table Format
# Exception table entry structure
{
"start": 10, # Protected range start (byte offset)
"end": 50, # Protected range end
"target": 100, # Handler offset (PUSH_EXC_INFO)
"depth": 2, # Stack depth to unwind to
"lasti": False # Whether to push last instruction
}
Error Checking Pattern
Every Python C API call that can fail is wrapped with error checking:
// After each potentially-failing call
auto check_error_and_branch = [&](llvm::Value* result, ...) {
// Check if result is NULL
auto is_null = builder->CreateIsNull(result);
// If NULL, unwind stack and jump to handler
builder->CreateCondBr(is_null, error_block, continue_block);
};
Current Limitations
Not Yet Implemented
| Feature | Status | Notes |
|---|---|---|
| Async Generators | ❌ | async def with yield |
async for |
❌ | Needs GET_AITER/GET_ANEXT |
async with |
⚠️ | Basic support, not fully tested |
| Generator Comprehensions in Generators | ❌ | Complex stack handling across yields |
Known Issues
-
Generator + List Comprehension: When a generator contains a list comprehension, the stack handling across
YIELD_VALUEis incomplete. The list builds correctly, but stack spilling/restoration needs refinement. -
Deeply Nested Async: Chains of
awaitexpressions with complex nesting may have edge cases.
Fallback Behavior
When JustJIT encounters unsupported opcodes or patterns, it automatically falls back to Python execution with a warning:
RuntimeWarning: Generator 'my_gen' uses opcodes not yet supported by JIT.
Using Python implementation (no performance impact for generators).
Building from Source
Prerequisites
| Requirement | Version |
|---|---|
| Python | 3.10+ |
| CMake | 3.20+ |
| LLVM | 18.1.8+ |
| C++ Compiler | C++17 support |
| nanobind | 2.0.0+ |
Windows Build
# Set LLVM path
$env:LLVM_DIR = "C:\path\to\llvm\build\lib\cmake\llvm"
# Build and install
pip install -e . --no-build-isolation
Linux Build
# Set LLVM path
export LLVM_DIR=/path/to/llvm/build/lib/cmake/llvm
# Build and install
pip install -e . --no-build-isolation
macOS Build
# With Homebrew LLVM
export LLVM_DIR=$(brew --prefix llvm)/lib/cmake/llvm
# Build and install
pip install -e . --no-build-isolation
CMake Configuration
cmake -B build \
-DLLVM_DIR=/path/to/llvm/lib/cmake/llvm \
-DCMAKE_BUILD_TYPE=Release \
-DPython_EXECUTABLE=$(which python)
API Reference
@jit Decorator
@jit(
opt_level=3, # LLVM optimization level (0-3)
vectorize=True, # Enable loop vectorization
inline=True, # Enable function inlining
parallel=False, # Enable parallelization (experimental)
lazy=False, # Delay compilation until first call
mode='auto' # 'auto', 'object', or 'int'
)
def my_function(...):
...
dump_ir() Function
from justjit import dump_ir
@dump_ir
def my_func(x):
return x + 1
# Prints the generated LLVM IR
my_func(5)
Direct API Access
from justjit import JIT
jit = JIT()
jit.set_opt_level(3)
success = jit.compile_function(
instructions, constants, names,
globals_dict, builtins_dict,
closure_cells, exception_table,
"my_func", param_count=2, total_locals=5, nlocals=5
)
if success:
callable = jit.get_callable("my_func", param_count=2)
result = callable(arg1, arg2)
Contributing
Contributions are welcome! Please ensure:
- Code Style: Follow existing patterns in the codebase
- Testing: Test with various Python code patterns
- Documentation: Update docs for new features
- Compatibility: Maintain Python 3.10+ and cross-platform support
Development Workflow
# Clone the repository
git clone https://github.com/magi8101/justjit.git
cd justjit
# Create development environment
python -m venv venv
source venv/bin/activate # or .\venv\Scripts\Activate.ps1 on Windows
# Install in development mode
pip install -e . --no-build-isolation
# Run tests
python -c "from justjit import jit; @jit\ndef f(x): return x*2\nprint(f(21))"
License
MIT License - see LICENSE for details.
Acknowledgments
Built with:
- LLVM Project - Compiler infrastructure
- Python - The language we're optimizing
- nanobind - C++/Python bindings
Links
- Repository: github.com/magi8101/justjit
- Issues: github.com/magi8101/justjit/issues
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file justjit-0.1.5.tar.gz.
File metadata
- Download URL: justjit-0.1.5.tar.gz
- Upload date:
- Size: 1.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bc415a8902a0ce621cf047a2c0e7bbce3099a9b2ef7d4e81f11fe68be276b6a
|
|
| MD5 |
fbe69f405d2d86ff66a96b4ebf6f5d5f
|
|
| BLAKE2b-256 |
cd792e77db46004bd9467381815adc9f940102b4ecb9f9b873580dc35822e0f2
|
File details
Details for the file justjit-0.1.5-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: justjit-0.1.5-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 27.8 MB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d90521bf26d84647415dfc940d8f7ed5b4b390a4d7061f9041ddd38d36e0436
|
|
| MD5 |
e58159bc31a9bfa76265b1b832a24c39
|
|
| BLAKE2b-256 |
dd391cff7b28ecb0d943673d42a810a1c3186b97c63d4aff39a1f97f879f410f
|
File details
Details for the file justjit-0.1.5-cp313-cp313-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: justjit-0.1.5-cp313-cp313-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 72.6 MB
- Tags: CPython 3.13, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6dbd8cd988b7ad3107b766dd79f88afca76a959e8d4ed5c7cdce11adffcb66b5
|
|
| MD5 |
0a730c4e0a82acd7adca9c6ddea87079
|
|
| BLAKE2b-256 |
2dfdece4a5e677354f69a825a9b1edc1b761b2e424d988c000c2edda42961331
|
File details
Details for the file justjit-0.1.5-cp313-cp313-macosx_12_0_arm64.whl.
File metadata
- Download URL: justjit-0.1.5-cp313-cp313-macosx_12_0_arm64.whl
- Upload date:
- Size: 71.9 MB
- Tags: CPython 3.13, macOS 12.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0a268c80edb39e692c65030759e837db0d801a2a30c0d89b5384591dfce1e3a
|
|
| MD5 |
1785838f39f2f806b7cf78106d5bd4d0
|
|
| BLAKE2b-256 |
9419386d23ff0157364b988c7f5d6e8cac2cd8103cee2bdcc1d7226b7268a997
|