Skip to content

Effects and purity

The compiler infers an effect set for each function. This page lists the effects, explains @ppy.pure, and describes the three paths a program can run on.

The effect vocabulary

All consumers share one vocabulary: purity, native and GPU eligibility, code motion, inlining, parallelization, fusion, the async lowering, and plugin contracts.

alloc  read_object  write_object  read_memory  write_memory
read_global  write_global  io  network  random  time
thread  process  sync  atomic  may_raise
python_callback  python_dynamic  gpu_launch  device_memory  external_unknown

read_memory and write_memory are native memory a pointer or a buffer reaches. read_object and write_object are Python objects.

The IR carries each function's effects, and passes read them. For example, an unused call to a function that only allocates and reads is dead code.

@ppy.pure

@ppy.pure asserts the set contains none of the forbidden effects. Everything is forbidden except allocation, reads, and raising.

The checker proves it interprocedurally:

  • a pure function calling something with unknown effects is E1602
  • a callee that mutates the caller's argument is charged to the caller

Purity and lowering

Purity and lowering are two different questions about one write. Purity asks whether anything could observe it. Lowering asks whether CPython has to perform it.

A function that fills memory it allocated and passes it on lowers, but is not @ppy.pure, because the callee saw the object before the function returned.

The three execution paths

Command What runs
python f.ppy plain CPython. import ppy installs a sys.meta_path finder so .py files can import .ppy modules: natively when the compiler is installed and the module checks clean, as Python source otherwise.
ppy f.ppy the optimized Python backend: AST-level optimization (folding, inlining, LICM, loop transforms) executed by CPython.
ppy run f.ppy eligible functions compile through LLVM; everything else runs the Python body.

Any observable difference between the three is a compiler bug. The test suite and examples/run_all.py compare all three on every example.

ppy build produces the third path ahead of time. Its launcher runs through ppy_runtime with machine code from the library built next to it. ppy build --standalone goes further: a native executable with no CPython inside, for programs whose reachable graph is entirely native.

Guards and fallback

A guard that fails is a fallback, never a different answer: native code that cannot keep a promise runs the Python body.

One exception stands above that rule. A check --sanitize inserted does not fall back but raises SanitizerFailure (CLI).

Examples: Effects and contracts, Errors.