Skip to content

A multi-module project

Two modules analyzed as one call graph and built as one program. src/app.ppy calls geometry.perimeter from src/geometry.ppy. ppy check src types both together, and ppy build links both modules' IR into one object.

Run it

ppy check src
ppy run   src/app.ppy

What it prints

ppy check src

(prints nothing; exits 0)

ppy run src/app.ppy

4.0
5.0

One graph

@ppy.pure
def perimeter(points: list[tuple[float, float]]) -> float:
    total: float = 0.0
    for index in range(len(points)):
        a = points[index]
        b = points[(index + 1) % len(points)]
        total += distance(a[0], a[1], b[0], b[1])
    return total

A function's parameter types may come from call sites in another file. distance takes four f64, and perimeter calls it from a loop over tuples. Both lower, and after linking the call is a direct native call. The whole-program pass:

  • inlines a small callee across modules,
  • makes functions Python never binds private,
  • drops what nothing reaches.
ppy emit linked-ir src/app.ppy     # the whole program, modules linked and optimized as one

Cache keys and layout

A change to geometry.ppy invalidates app.ppy's cache entry, and nothing else, because the dependency digest is part of its key.

  • pyproject.toml at the root names src as the source root.
  • app.ppy calls ppy.install() before importing its sibling, so the same file runs under plain python too.
  • ppy convert src/ and ppy check src/ take the directory and work across it.

Where the code comes from

src/app.ppy and src/geometry.ppy are hand-written; there is no .py source and no conversion step.

Read on: Interop · The IR: linking and the program · Configuration

26_project/src/app.ppy

import ppy

ppy.install()

import geometry


def main() -> None:
    square: list[tuple[float, float]] = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
    print(round(geometry.perimeter(square), 6))
    print(round(geometry.distance(0.0, 0.0, 3.0, 4.0), 6))


if __name__ == "__main__":
    main()

26_project/src/geometry.ppy

import ppy
from ppy import f64


@ppy.pure
@ppy.opt(3)
def distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64:
    dx: f64 = x2 - x1
    dy: f64 = y2 - y1
    return (dx * dx + dy * dy) ** 0.5


@ppy.pure
def perimeter(points: list[tuple[float, float]]) -> float:
    total: float = 0.0
    for index in range(len(points)):
        a = points[index]
        b = points[(index + 1) % len(points)]
        total += distance(a[0], a[1], b[0], b[1])
    return total

Source: examples/26_project.