Skip to main content

A compiled Python dialect that eliminates CPython overhead without sacrificing Python syntax

Project description

TurboPython

A compiled Python dialect that eliminates core sources of CPython overhead — without sacrificing Python's syntax or readability.

TurboPython compiles .tpy files to native C via GCC, producing either a shared library (.so) callable from Python via ctypes, or a standalone executable. New features are strictly opt-in: unannotated code stays valid Python.

This is work in progress. The syntax can still change as we find more things that one could improve.

The Name

The name has two references. Turbo comes from Turbo Pascal — Borland's legendary 1980s compiler that made Pascal fast enough to write real software on a home computer, in part by making compilation itself instant. The parallel is intentional: TurboPython aims to make Python fast enough for systems-level work without leaving the language behind. Python is there because it stays Python — same syntax, same stdlib, same feel.

The short name is tython, which is also the name of the drop-in runner. And also the name of a planet (#lightsaber)


How It Works

TurboPython addresses a number independent axes of CPython overhead. Each has its own opt-in syntax. See docs/turbopython_syntax.md for the full language reference.

1. Strict Static Typing

Type annotations are enforced at compile time, not ignored like Python hints. The compiler emits unboxed native arithmetic — no object headers, no dynamic dispatch.

@native
def distance(x: float, y: float) -> float:
    return (x**2 + y**2) ** 0.5
# Emits: double distance(double x, double y) { return sqrt(x*x + y*y); }

Unannotated functions fall back to normal CPython — typing is opt-in.

2. Value Types — struct

A new struct keyword creates stack-allocated, contiguous-memory types with no heap allocation, no refcount, no GC overhead.

struct Vec3:
    x: float
    y: float
    z: float

points: array[Vec3, 1000]  # 24,000 bytes, one contiguous block
                           # vs. Python list: 1000 pointers + 1000 heap objects

Memory comparison: Vec3 = 24 bytes. Equivalent Python object ≈ 200+ bytes.

3. Ownership and Borrowing

Rust-inspired ownership eliminates refcount overhead on the hot path. Three modes, all opt-in:

def consume(data: owned list[int]) -> int:   # caller transfers ownership — source invalidated
    return sum(data)

def analyze(data: ref list[int]) -> float:   # immutable borrow — zero-cost, no refcount
    return sum(data) / len(data)

def normalize(data: mut ref list[float]):    # exclusive mutable borrow — no data races
    total = sum(data)
    for i in range(len(data)):
        data[i] /= total

The compiler enforces: multiple ref borrows are fine; mut ref is exclusive — any overlap is a compile error.

4. Native Compilation Directives

Explicit control over what compiles to native code:

@native   # AOT compile — all types resolved at compile time
def fib(n: int) -> int: ...

@inline   # static inline in C — zero-overhead small helpers
def clamp(val: float, lo: float, hi: float) -> float:
    return min(max(val, lo), hi)

@jit      # JIT compile on first call, specialize on observed types
def flexible_sum(items): ...

const MAX_ITER: int = 256   # compile-time constant, embedded in binary

5. True Parallelism — No GIL

parallel for partitions loop iterations across all cores via OpenMP. The ownership checker statically guarantees no data races — no locks needed.

parallel for y in range(height):
    for x in range(width):
        pixels[y * width + x] = mandelbrot(cx, cy, 256)
# Emits: #pragma omp parallel for

spawn launches concurrent tasks with typed channels as the only communication mechanism:

spawn filter_stage(data, chan_filtered)
spawn transform_stage(chan_filtered, chan_results)

6. Extended Integer Types

int128 maps to GCC's __int128, giving a range of ±1.7 × 10³⁸ — suitable for large combinatorics and cryptographic primitives without external dependencies.

@native
def fib(n: int) -> int128:
    a: int128 = 0
    b: int128 = 1
    for i in range(n):
        tmp: int128 = a + b
        a = b
        b = tmp
    return a

Summary

Concept Standard Python TurboPython
Type enforcement Advisory hints Compile-time enforced
Memory layout class (heap, dict-backed) struct (stack, packed, no GC)
Integer range Arbitrary precision (slow) int (64-bit) or int128 (128-bit)
Ownership Refcounted, implicit sharing owned, ref, mut ref
Compilation Interpreted bytecode @native, @jit, @inline
Constants Convention (UPPER_CASE) const (compile-time evaluated)
Parallelism threading (GIL-bound) parallel for, spawn, Channel
Typed arrays list (boxed, pointer array) array[T, N] (contiguous, unboxed)

Installation

Requirements: Python 3.10+, GCC with OpenMP support.

git clone https://github.com/ribalba/TurboPython.git
cd TurboPython
python -m turbopython.cli --help

No pip install needed — just run from the repo root.


Quick Start

1. Write a .tpy file

# benchmarks/hello.tpy
@native
def fib(n: int) -> int:
    a: int = 0
    b: int = 1
    for i in range(n):
        tmp: int = a + b
        a = b
        b = tmp
    return a

@native
def main() -> int:
    return fib(40)

2. Compile to an executable

python -m turbopython.cli compile benchmarks/hello.tpy --exe

Output:

✓ Compilation successful
  C source:   benchmarks/hello.c
  Executable: benchmarks/hello

Run it like any native binary:

./benchmarks/hello
time ./benchmarks/hello

3. Or compile to a shared library and call from Python

python -m turbopython.cli compile benchmarks/hello.tpy
import ctypes

lib = ctypes.CDLL("./benchmarks/hello.so")
lib.fib.argtypes = [ctypes.c_int64]
lib.fib.restype  = ctypes.c_int64

print(lib.fib(40))  # 102334155 — computed in native code

4. Or use the tython runner

./tython benchmarks/hello.tpy        # compiles + runs main()
./tython myscript.py                 # runs .py with import hook active

Benchmarks

python benchmarks/bench_mandelbrot.py
python benchmarks/bench_hello.py

Expected output (numbers vary by machine):

Mandelbrot (400×300, 256 iterations):

Pure Python  : 0.263s   (checksum: 3303274)
Compiling mandelbrot.tpy... done
TurboPython  : 0.007s   (checksum: 3303274)

Speedup      : 37.1x faster

Fibonacci (fib_sum(150) × 5000 reps, int128):

Pure Python  : 1.842s   (result: ...)
Compiling hello.tpy... done
TurboPython  : 0.031s   (result: ...)

Speedup      : 59.4x faster

Language Features

@native — AOT compiled functions

All types must be fully resolved at compile time. Emitted as a C symbol with unboxed arithmetic.

@native
def mandelbrot(cx: float, cy: float, max_iter: int) -> int:
    zx: float = 0.0
    zy: float = 0.0
    for i in range(max_iter):
        if zx * zx + zy * zy > 4.0:
            return i
        tx: float = zx * zx - zy * zy + cx
        zy = 2.0 * zx * zy + cy
        zx = tx
    return max_iter

@inline — inlined at call sites

Emitted as static inline in C. Best for small math helpers.

@inline
def vec3_dot(a: Vec3, b: Vec3) -> float:
    return a.x * b.x + a.y * b.y + a.z * b.z

struct — value types with no heap allocation

Stack-allocated, copied on assignment, no GC overhead. A Vec3 is exactly 24 bytes — vs 200+ bytes for an equivalent Python object.

struct Vec3:
    x: float
    y: float
    z: float

@native
def vec3_length(v: Vec3) -> float:
    return (v.x * v.x + v.y * v.y + v.z * v.z) ** 0.5

parallel for — multi-core loops via OpenMP

Emits #pragma omp parallel for. The ownership checker enforces that loop bodies do not share mutable state.

parallel for y in range(height):
    for x in range(width):
        cx: float = (x - width / 2.0) / (width / 4.0)
        cy: float = (y - height / 2.0) / (height / 4.0)
        total = total + mandelbrot(cx, cy, max_iter)

Ownership annotations

Rust-inspired, opt-in. Eliminates refcount overhead on the hot path.

def consume(data: owned list[int]) -> int:   # caller's variable is invalidated
    return sum(data)

def analyze(data: ref list[int]) -> float:   # immutable borrow, zero-cost
    return sum(data) / len(data)

def normalize(data: mut ref list[float]):    # exclusive mutable borrow
    total = sum(data)
    for i in range(len(data)):
        data[i] /= total

const — compile-time constants

const MAX_ITER: int = 1000
const PI: float = 3.14159265358979

CLI Reference

# Compile to a .so shared library
python -m turbopython.cli compile examples/mandelbrot.tpy

# Compile with verbose output (shows generated C)
python -m turbopython.cli compile examples/mandelbrot.tpy --verbose

# Compile to a specific output directory
python -m turbopython.cli compile examples/mandelbrot.tpy -o build/

# Produce a standalone executable (requires @native def main() -> int)
python -m turbopython.cli compile benchmarks/hello.tpy --exe

# Specify a custom entry-point function name
python -m turbopython.cli compile benchmarks/hello.tpy --exe --entry run

# Inspect all compilation stages without producing output
python -m turbopython.cli inspect examples/vectors.tpy

inspect prints: original source, preprocessed Python, struct layouts, function signatures with inferred C types, and the full type environment. Useful for debugging codegen.

When --exe is passed:

  1. Validates that the entry-point function (default: main) exists
  2. Renames it to __tp_main in the generated C to avoid clashing with C's main
  3. Appends a int main(int argc, char** argv) wrapper
  4. Compiles without -shared -fPIC, producing a native executable

tython — Drop-in Runner

tython is an executable at the repo root that acts as a Python-aware interpreter for both .py and .tpy files, with the import hook pre-installed.

# Compile and run a .tpy file — calls main() and uses its return as exit code
./tython examples/vectors.tpy
./tython benchmarks/hello.tpy

# Run a .py script — .tpy files on sys.path are importable by name
./tython myscript.py

# Inline command
./tython -c "import vectors; print(vectors.compute_total_distance(100))"

# Interactive REPL with import hook active
./tython

Inside a .py script run via tython, any .tpy file on sys.path imports transparently:

# myscript.py — no special setup needed when run via tython
import vectors
print(vectors.compute_total_distance(1000))

Import Hook

The import hook can also be used in any regular Python script without the tython runner:

from turbopython.importer import install
install()

import vectors   # finds vectors.tpy on sys.path, compiles to vectors.so
print(vectors.compute_total_distance(1000))

install() inserts a sys.meta_path finder that:

  1. Searches sys.path for <module>.tpy when an import cannot find a .py/.pyc
  2. Compiles the .tpy with the full TurboPython pipeline
  3. Wraps the resulting .so in a module object with argtypes/restype set automatically from the compiled type signatures
  4. Returns the module — the caller uses it as any normal Python module

The .so is written next to the .tpy file and reused on subsequent runs.


Examples

File Demonstrates
examples/mandelbrot.tpy @native, typed arithmetic, struct
examples/vectors.tpy struct value types, @inline, @native, main
examples/nbody.tpy parallel for, struct arrays, main
benchmarks/hello.tpy int128, fibonacci, main
benchmarks/hello.py Pure Python equivalent of hello.tpy
benchmarks/bench_hello.py Fibonacci benchmark vs pure Python
benchmarks/bench_mandelbrot.py Mandelbrot benchmark vs pure Python

Project Layout

TurboPython/
├── README.md
├── tython                     # Drop-in runner / interpreter
├── turbopython/
│   ├── __init__.py
│   ├── cli.py                 # Command-line interface
│   ├── compiler.py            # Pipeline driver
│   ├── preprocessor.py        # Stage 1: syntax → valid Python + metadata
│   ├── type_checker.py        # Stage 2: type resolution and validation
│   ├── ownership.py           # Stage 3: move/borrow checking
│   ├── codegen.py             # Stage 4: C code generation + GCC invocation
│   ├── importer.py            # sys.meta_path hook for transparent .tpy imports
│   └── test_compiler.py       # Test suite
├── examples/
│   ├── mandelbrot.tpy         # Mandelbrot fractal
│   ├── vectors.tpy            # 3D vector math
│   └── nbody.tpy              # N-body gravitational simulation
├── benchmarks/
│   ├── hello.tpy              # int128 fibonacci (compile to .so or executable)
│   ├── hello.py               # Pure Python equivalent
│   ├── bench_hello.py         # Side-by-side benchmark
│   └── bench_mandelbrot.py    # Mandelbrot benchmark
└── docs/
    ├── ARCHITECTURE.md        # Detailed pipeline design
    └── turbopython_syntax.md  # Full language reference

Type System

TurboPython C type Range
int int64_t ±9.2 × 10¹⁸
int128 __int128 ±1.7 × 10³⁸
float double 64-bit IEEE 754
bool int 0 / 1
str const char* read-only C string
array[float, N] double* contiguous heap/stack
struct Foo Foo (typedef'd struct) stack-allocated value type

What Stays Standard Python

  • Indentation-based blocks
  • def, class, for, if, while, with, return, yield
  • List/dict/set comprehensions
  • Standard library imports
  • Unannotated functions run as normal CPython

The philosophy: opt in to performance where it matters, keep everything else as dynamic and expressive as Python.


Further Reading

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

turbopython-0.1.0.tar.gz (40.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

turbopython-0.1.0-py3-none-any.whl (37.6 kB view details)

Uploaded Python 3

File details

Details for the file turbopython-0.1.0.tar.gz.

File metadata

  • Download URL: turbopython-0.1.0.tar.gz
  • Upload date:
  • Size: 40.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for turbopython-0.1.0.tar.gz
Algorithm Hash digest
SHA256 91bc1781132a8caec8e88947fe10be7232c943ed3e90f91d7776a102f4fbfe00
MD5 4927361c935aad7fb6873f96cd6ff219
BLAKE2b-256 f05db19532e48a1018b1dd2a3e37a763a1091b6a887993674f82621862232c7b

See more details on using hashes here.

File details

Details for the file turbopython-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: turbopython-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 37.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for turbopython-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 474d833cae5a86a161a97681c6f4912bcce95a3cc46ed0a314a32d71b8758740
MD5 d2147b46c466c15a12459558375d1362
BLAKE2b-256 103943ed6572710fed27be09145f8f56c868f6a26926ad33dd3b397658947403

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page