PythoC
PythoC is a Python DSL compiler that compiles statically-typed Python to LLVM IR, providing C-equivalent runtime capabilities with Python syntax and compile-time metaprogramming.
Install / Use
/learn @1flei/PythoCREADME
PythoC: Write C in Python
PythoC is a Python DSL compiler that compiles statically-typed Python to LLVM IR, providing C-equivalent runtime capabilities with Python syntax and compile-time metaprogramming.
Design Philosophy
Core principle: C-level runtime + Python-powered compile-time
- C-compatible runtime: Compiled code maps directly to native machine code with C-level control and performance
- Full access to low-level operations (pointers, manual memory, inline assembly)
- C calling conventions for seamless interoperability
- No runtime overhead beyond what C would have
- Compile-time = Python: Full Python power for metaprogramming, generics, and code generation
- Zero-cost abstractions: Python-level abstractions compile away completely
- Explicit typing: All types must be annotated (like C, unlike Python)
- Explicit control flow: No implicit control flow (no exceptions, no RAII, no destructors)
- Structs are plain data (no methods, no constructors)
- Manual resource management like C
- Optional safety features: Linear types and refinement types provide memory safety guarantees without introducing hidden control flow
- Prevent memory leaks, use-after-free, null pointer dereference, array out-of-bounds
- Completely optional - use only when needed
- No extra runtime overhead
- Convenient Python-C interoperability:
- Python can call PythoC compiled functions at runtime (via ctypes/cffi)
- PythoC can invoke Python code at compile-time for metaprogramming
Quick Start
Installation
pip install pythoc
Hello World
from pythoc import compile, i32
@compile
def add(x: i32, y: i32) -> i32:
return x + y
# Can compile to native code
@compile
def main() -> i32:
return add(10, 20)
# Or call the compiled dynamic library from Python directly
result = main()
Run Tests
# Run all tests
python test/run_all_tests.py
# Run specific test suites
python test/run_integration_tests.py
python test/run_examples.py
Example: Binary Tree Benchmark
This example demonstrates PythoC's direct mapping to C - compare with test/example/base_binary_tree_test.c:
from __future__ import annotations
from pythoc import i32, ptr, compile, nullptr, seq, sizeof
from pythoc.libc.stdlib import malloc, free
from pythoc.libc.stdio import printf
# C: typedef struct tn { struct tn* left; struct tn* right; } treeNode;
@compile
class TreeNode:
left: ptr[TreeNode]
right: ptr[TreeNode]
# C: treeNode* NewTreeNode(treeNode* left, treeNode* right)
@compile
def NewTreeNode(left: ptr[TreeNode], right: ptr[TreeNode]) -> ptr[TreeNode]:
new: ptr[TreeNode] = ptr[TreeNode](malloc(sizeof(TreeNode)))
new.left = left
new.right = right
return new
# C: long ItemCheck(treeNode* tree)
@compile
def ItemCheck(tree: ptr[TreeNode]) -> i32:
if tree.left == nullptr:
return 1
else:
return 1 + ItemCheck(tree.left) + ItemCheck(tree.right)
# C: treeNode* BottomUpTree(unsigned depth)
@compile
def BottomUpTree(depth: i32) -> ptr[TreeNode]:
if depth > 0:
return NewTreeNode(BottomUpTree(depth - 1), BottomUpTree(depth - 1))
else:
return NewTreeNode(nullptr, nullptr)
# C: void DeleteTree(treeNode* tree)
@compile
def DeleteTree(tree: ptr[TreeNode]):
if tree.left != nullptr:
DeleteTree(tree.left)
DeleteTree(tree.right)
free(tree)
C Parity: Full C Capabilities
Supported Features
PythoC provides complete C runtime capabilities:
Primitive types:
- Integers:
i8,i16,i32,i64,u8,u16,u32,u64 - Floats:
f16,f32,f64,bf16,f128 - Boolean:
bool
Composite types:
- Pointers:
ptr[T] - Arrays:
array[T, N]orarray[T, N, M, ...]for multi-dimensional - Structs:
@compile class,@struct class,struct[x: i32, y: i32](named) orstruct[i32, i32](unnamed) - Unions:
@union classorunion[T1, T2, ...] - Enums:
@enum class - Function pointers:
func[[arg_types], return_type]
Control flow:
if/else,while,forloopsbreak,continue,return- Pattern matching:
match/case(enhanced switch) - Scoped
gotoand labels for low-level control (similar to labeled continue/break, limited but safer)
Operations:
- Arithmetic:
+,-,*,/,%,// - Comparison:
==,!=,<,>,<=,>= - Logical:
and,or,not - Bitwise:
&,|,^,~,<<,>> - Pointer arithmetic and dereferencing
C Standard Library:
from pythoc.libc.stdio import printf, scanf
from pythoc.libc.stdlib import malloc, free, atoi
from pythoc.libc.string import memcpy, strlen
from pythoc.libc.math import sin, cos, pow
Not Yet Supported
Features deliberately excluded or pending:
- Fall-through
switch: Usematch/casewith explicit branches - Global variable initialization: Workaround with init functions and effect system
- Variable-length arrays (VLA): Use fixed-size arrays or malloc
- Flexible array members: Use separate size tracking
PythoC Language Core
Beyond C parity, PythoC adds modern type system features (all optional, minimal support):
Algebraic Data Types (ADT) and Pattern Matching
Provide Rust-like enums with payload for type-safe tagged unions:
from pythoc import enum, compile, i32
@enum
class Result:
Ok: i32
Err: i32
@compile
def handle_result(r: Result) -> i32:
match r:
case (Result.Ok, value):
return value
case (Result.Err, code):
return -code
Linear Types (Optional)
Prevent use-after-free and resource leaks without RAII or destructors:
from pythoc import compile, linear, consume, void, ptr, i8, i32, struct
from pythoc.libc.stdlib import malloc, free
# Allocator returns resource + linear proof
@compile
def lmalloc(size: i32) -> struct[ptr[i8], linear]:
return malloc(size), linear()
# Only the paired deallocator can consume the proof
@compile
def lfree(ptr: ptr[i8], prf: linear) -> void:
free(ptr)
consume(prf) # Proof consumed - resource released
@compile
def safe_usage() -> void:
mem, prf = lmalloc(100)
# ... use mem ...
lfree(mem, prf) # Must call lfree to consume prf
# Compile error if prf not consumed!
Motivation: Proof-carrying code pattern. The linear proof proves resource was allocated and must be deallocated. Compiler enforces pairing of alloc/free at compile-time.
Refinement Types (Optional)
Check once, use safely everywhere without runtime overhead:
from pythoc import compile, i32, bool, refined, refine, array, ptr, nullptr
# Non-null pointer - check once, use safely everywhere
@compile
def is_nonnull(p: ptr[i32]) -> bool:
return p != nullptr
NonNull = refined[is_nonnull]
@compile
def process_data(p: ptr[i32]) -> i32:
for ptr_checked in refine(p, is_nonnull):
# Type system knows ptr_checked is non-null
return access_ptr(ptr_checked)
else:
return -1 # Handle null case
@compile
def access_ptr(p: refined[is_nonnull]) -> i32:
return p[0]
# Array bounds - check once, access many arrays safely
@compile
def is_valid_index(idx: i32) -> bool:
return 0 <= idx < 10
@compile
def process_arrays(i: i32, arr1: array[i32, 10], arr2: array[i32, 10]) -> i32:
for idx in refine(i, is_valid_index):
# Type system remembers idx is valid
a = arr1[idx] # Safe: no bounds check
b = arr2[idx] # Safe: no bounds check
c = arr1[idx] # Safe: reuse safely
return a + b + c
else:
return -1
Motivation: Check once, encode in type system, use safely everywhere. Typical examples: non-null pointers, valid array indices, non-zero divisors. The type system remembers the property, eliminating redundant runtime checks. Zero overhead for subsequent uses.
Effect System
Compile-time dependency injection with zero runtime overhead. Manage the global state and can be overridden at compile-time.
# rng_lib.py
from pythoc import compile, effect, u64, void
from types import SimpleNamespace
@compile
def rng_next() -> u64:
return u64(42)
@compile
def rng_seed(s: u64) -> void:
pass
RNG = SimpleNamespace(next=rng_next, seed=rng_seed)
effect.default(rng=RNG)
@compile
def use_rng() -> u64:
return effect.rng.next()
# Another file can override the default RNG implementation
from pythoc import *
from types import SimpleNamespace
@compile
def mock_rng_next() -> u64:
return u64(43)
@compile
def mock_rng_seed(s: u64) -> void:
pass
MockRNG = SimpleNamespace(next=mock_rng_next, seed=mock_rng_seed)
with effect(rng=MockRNG, suffix="mock"):
from rng_lib import use_rng
print(use_rng()) # 43
Defer
Explicit scope-exit actions, lowered to control flow.
from pythoc import compile, defer, linear, consume, void
@compile
def consumer(t: linear) -> void:
consume(t)
@compile
def ok_defer(n: i32) -> void:
for i in seq(n):
t = linear()
defer(consumer, t)
yield i
# consumer(t) executes at scope exit and consumes t no what user-code continues or breaks
Cimport
Import C modules directly into PythoC.
cimport is a pure PythoC implementation to parse and import C modules.
It is under a heavy re-design and development currently.
Python as Preprocessor
Use Python's full power at compile-time for metaprogramming:
Generic Types via Python Functions
from pythoc import compile, struct, i32, f64
# Python function generates specialized types and functions
def make_point(T):
@struct(suffix=T) # suffix creates unique type name
class Point:
x: T
y: T
@compile(suffix=T) # suffix creates unique function name
def add_points(p1: Point, p2: Point) -> Point:
result: Point = Point()
result.x = p1.x + p2.x
result.y = p1.y +
