PPy¶
A compiler for the statically analyzable part of Python
It reads ordinary Python files and builds them into native code, C, CUDA, or StableHLO.
A .ppy file is valid Python. You can run it with python and no compiler
installed, and that run is the reference answer. The compiler reads the same
file, proves what it can about the types and effects, lowers it through a
canonical IR, and hands it to a backend.
import ppy
@ppy.pure
@ppy.opt(3)
def longest(limit: int) -> int:
best: int = 0
for start in range(1, limit):
n: int = start
steps: int = 0
while n != 1:
n = n // 2 if n % 2 == 0 else 3 * n + 1
steps += 1
best = max(best, steps)
return best
print(longest(ppy.input[int]()))
echo 300000 | python collatz.ppy # plain CPython 1068.8 ms
echo 300000 | ppy run collatz.ppy # LLVM, Python's integers 41.7 ms
echo 300000 | ppy build collatz.ppy -o dist && ./dist/collatz # native 30.8 ms
The same loop in C takes 41.9 ms with gcc -O3 and 32.8 ms with clang -O3.
These are ten fresh processes each on one machine. The
performance page has the full table, including
Numba, Codon, PyPy, Cython, mypyc, and Nuitka.
Install¶
uv add "ppy-lang[llvm]" # or: pip install "ppy-lang[llvm]"
uv run ppy doctor # reports the compiler, LLVM, the C toolchain, plugin runtimes
Then follow Getting started, which goes from this file to a native build in about ten minutes.
Highlights¶
- Three paths, one answer.
python f.ppyruns plain CPython,ppy f.ppyruns the optimized Python backend, andppy run f.ppyruns LLVM native code. All three must print the same output. A difference is a compiler bug, and every example is checked for it. - Falls back instead of guessing. Code outside the supported subset is reported with its location. A guard that fails at runtime, such as an overflowing multiply, returns to the Python body and finishes there.
- Ahead-of-time builds.
ppy buildproduces a launcher and a library that keep working after the compiler is uninstalled.ppy build --standaloneproduces a native executable with no CPython inside. - Seven backends off one IR. LLVM, C11, C++17, CUDA, HIP, NVVM/PTX, and StableHLO, across 19 IR dialects. Other packages can register their own backend.
- Collections without pointers.
Vec,Deque,Heap,LinkedList,HashMap,HashSet,TreeMap, andTreeSetcompile to native code and give the same answers as their Python reference classes. - Eight library plugins. NumPy, PyTorch, JAX/Flax, pydantic, FastAPI/Uvicorn, SciPy, pandas, and PyArrow.
- A cheap call boundary. A native call with two
intarguments costs 47 ns, against 28 ns for a plain Python call, with no Python frames on the native path. - Tested. 1264 test functions on Python 3.12, 3.13, and 3.14, with 74% statement coverage. 78 diagnostic codes, each documented in one place.
Documentation¶
| Getting started | Install, run a file three ways, check it, build it. |
| Guide | The language, one topic per page: the subset, directives, native memory, SIMD, threads, parallel loops, derivatives, coroutines, GPU kernels, XLA, generics, regular expressions. |
| Examples | 47 folders and 57 programs with their commands and output. 18 of them are measured against Numba, Cython, NumPy, numexpr, JAX, PyTorch, CuPy, Triton, Taichi, Mojo, Codon, Rust, C, pandas, polars, asyncio, and uvloop; the results are collected on the comparisons page. |
| Reference | The command line, diagnostics, configuration, performance, compatibility, and the Python API. |
| Internals | The pipeline, the IR and its dialects, the cache, the solver. |
Limitations¶
PPy accepts Python's syntax and works with its ecosystem, but it does not
support every dynamic behavior. exec and eval, monkey-patching, dynamic
namespace mutation, and unrestricted runtime reflection are restricted, or
isolated behind an explicit ppy.dynamic boundary. That is the trade for
analysis and native code you can rely on.
To bring an existing Python project over, use ppy migrate
(how a real project went).