Toolchain & C ABI¶
The post-py CLI¶
post-py check FILE... structural subset checking
post-py build FILE [options] compile to native artifacts
(postpyc is an equivalent alias for the same command, matching the
distribution name.)
Build options:
| Option | Effect |
|---|---|
--output PATH |
artifact path (default: next to the source) |
--ext-module |
build an importable CPython extension registering NumPy ufuncs |
--module-name NAME |
artifact / importable name (defaults to the file stem, or the package directory for __init__.py) |
--emit-header |
write the C ABI header next to the output |
--emit-manifest |
write the JSON export manifest next to the output |
--prefix PREFIX |
install the libpp<name> package-manager layout |
--search-path DIR |
additional POST module source root (repeatable) |
--cc, --cflags, --keep-c |
toolchain control |
The same pipeline is available as a Python API:
postpyc.build.build_file / build_source.
What a build does¶
- Check — the structural checker rejects non-subset constructs
(
PP0xx). - Compile the program — the entry file and every POST module it imports, dependencies first, one translation unit each (spec §9.1). Imports from the standard library or site-packages are CPython-boundary imports and are never compiled implicitly.
- Emit C99, compile, link — one object file per translation unit,
linked into a single shared library (or extension module). Private
(
_-prefixed) functions get internal linkage; public names must be unique across the program (PP501).
The stable C ABI (spec §9.1.1)¶
Every artifact defines pp_<name> wrapper symbols for its export set —
the entry unit's public functions, its POST imports under their local
names, and module-level aliases like gammaln = lgamma. Kernel symbol
names underneath are implementation detail (libm-colliding names such as
j0 are mangled to __pp_j0); pp_j0 is the contract.
The header (--emit-header) is self-contained C99: the
__pp_array view struct (spec §9.2), one declaration per export with
provenance and docstring comments, and the NumPy ufunc loop symbols
(spec §8.5).
The manifest (--emit-manifest) is versioned JSON:
{
"post_abi": 1,
"artifact": "ppspecial",
"exports": [
{
"name": "gammaln",
"c_symbol": "pp_gammaln",
"kernel_symbol": "__pp_lgamma",
"module": "_gamma",
"kind": "alias",
"alias_of": "lgamma",
"params": [{"name": "x", "dtype": "Float64", "is_array": false,
"is_core_dim": false}],
"return_dtype": "Float64",
"ufunc": {"loop_symbol": "lgamma_ufunc_loop", "signature": "()->()"},
"doc": "..."
}
]
}
Packaging recipes and foreign-language bindings should consume the manifest rather than guessing symbol names.
Array ABI¶
Arrays cross the C boundary as views with explicit metadata (spec §9.2):
typedef struct __pp_array {
void *data;
int64_t ndim;
int64_t const *shape;
int64_t const *strides; /* byte strides, NumPy-compatible */
int64_t offset_bytes;
} __pp_array;
Vectorized functions additionally export <name>_ufunc_loop with the
NumPy generalized-ufunc calling convention — outer broadcast steps
followed by per-argument core-dimension strides — so loops registered
via PyUFunc_FromFuncAndData handle non-contiguous inputs correctly.