ADR-0000 — Build postc on previous work¶
- Status: Accepted
- Date: 2026-07
- Builds on: previous work — postpyc; our own earlier compilers (aopyc, p2w, Prescrypt-NG); and the wider AOT-Python lineage
Context¶
postc implements the emerging POST Python standard, and it starts from work that already exists rather than from a blank page.
postpyc — the POST Python reference compiler, created by Travis E. Oliphant and contributors — already has the piece that matters most: a backend-agnostic typed SSA IR feeding two working code generators (C and QBE), with the QBE output verified byte-identical to C. That IR is the hard part, and it is the crown jewel this work is built on.
Behind it sits a longer lineage of ahead-of-time Python compilation that the standard sets out to unify, and from which POST Python takes its conventions: Numba (whose @vectorize / @guvectorize ufunc model POST Python adopts wholesale), Cython, Pythran, Codon, mypyc, and taichi-lang.
Our own earlier compilers feed in as directly:
- aopyc — an end-to-end AOT compiler for "Aopy", a small static language with Python syntax, emitting several targets (C, then gcc; x86 assembly) behind a
--list-backends/--all-backendsCLI. It is the direct ancestor of postc's runtime-selectable backend registry: the idea that one frontend, many code generators, chosen at run time is the right shape was proven there first. - p2w — a Python→WebAssembly compiler covering a broad slice of the language on WASM GC (with JS interop and browser demos). Its WAT-emission and assembly approach informs postc's WASM backend, though postc's numeric, unboxed value model diverges from p2w's boxed one.
- Prescrypt-NG — a Python→JavaScript transpiler over a well-defined subset, prioritizing correctness over completeness and held down by a 2400-test suite. That subset-and-oracle discipline is exactly how postc is built and verified.
The open question was structure. postpyc's build driver selected a backend with a hardcoded if/else; packaging was conda-first; and the test, benchmark, and documentation layout was shaped around a single package. Carrying four backends — C, QBE, and the planned llvmlite and WASM — needs a different shape.
Decision¶
Build a new sibling package, postc ("POST Compiler"), on top of that previous work — a continuation of postpyc, kept installable and comparable alongside it:
- Keep the proven core unchanged. The typed SSA IR, the frontend, the type checker, and both backends are lifted from postpyc essentially as-is.
- Rebuild the structure. A runtime-selectable backend registry over the IR, uv-first packaging, a test pyramid with a differential oracle, a benchmark harness, and this documentation.
- postpyc lives on as the POST Python reference implementation and postc's benchmark baseline (a development dependency), so postc's progress is measured against it over time.
This is explicitly a continuation of postpyc's work, with full credit to its authors. postc is where the multi-backend structure evolves; postpyc remains the reference.
Consequences¶
- Adding a backend costs exactly one IR→text lowering — validated by porting both backends under the registry with zero IR changes, and the goal of the llvmlite and WASM phases.
- A differential oracle (semantic vs CPython, plus byte-for-byte parity vs the C backend) can hold every backend to the same semantics, precisely because they all consume identical IR. This has already paid off — it caught a real integer
%semantics bug the parity check alone could not. - postc and postpyc diverge over time; the benchmark session history makes that divergence visible and holds postc's numbers to its baseline.
Alternatives considered¶
- Evolve postpyc in place. Rejected: it couples the reference implementation's stability to multi-backend churn, and conflates the baseline with the experiment.
- A monorepo or a branch. Rejected: postpyc must stay independently installable as postc's benchmark dependency; a separate sibling repository keeps that boundary clean.
Roadmap¶
Phase 0 bootstrap · 1 core port + backend registry · 2 test pyramid + differential oracle · 3 benchmarking vs postpyc · 4 documentation · 5 llvmlite backend · 6 WASM backend. Phases 0–5 are complete (the LLVM backend matches cc -O2 and is held to the same byte-identical oracle); Phase 6 has landed as a scalar MVP (a WAT emitter run under wasmtime, byte-identical to C on the scalar subset). The detailed planning notes live in the postpython repository (notes/02-plan.md).