Skip to content

pycc-next v0.7.0

Latest

Choose a tag to compare

@github-actions github-actions released this 17 Oct 19:29
212be96

Real-world Python: frameworks, Windows. All five v0.7 goals are met. Windows x86_64 is a
first-class platform: CI runs the test suite, the corpus gate and the real-package wheel
checks on windows-2025, and the release ships a smoke-tested
pycc-v0.7.0-x86_64-windows.zip (first shipped, ahead of schedule, in 0.6.0).
--non-strict compiles classes inheriting library classes and sends only the smallest
enclosing function or method of a construct it cannot compile to CPython (also in 0.6.0).
pydantic 2.13.5 builds --ext --non-strict with 809 of its 1249 functions native and
5742 of its tests passing (5762 against its sources) -- but the compiled tree is
slower than CPython: import 0.40x, model definition 0.70x, model_json_schema()
0.70x, round-trips 0.91x of CPython's speed (the profile is in docs/ROADMAP.md, v0.7
accept 4; making it faster is v0.8). The real-world corpus goal carried over from v0.6 is
met: strict 190/200, --non-strict 200/200 with 0 of 1191 top-level statements in
CPython. Prebuilt archives: Linux x86_64, Linux aarch64, macOS arm64, Windows x86_64.

Benchmarks (python3 bench/run.py, best of 7, shared 4-core Xeon 2.1 GHz, Linux x86_64;
process start included except for the numpy kernels):

program CPython pycc-next Rust pycc vs CPython pycc vs Rust
collections_bfs 0.843s 0.131s 0.054s 6.5x faster 2.43x slower
fib 0.243s 0.013s 0.008s 18.5x faster 1.73x slower
mandelbrot 1.485s 0.042s 0.040s 35.2x faster 1.04x slower
nbody 2.299s 0.068s 0.027s 33.6x faster 2.55x slower
prefix_sum_np (kernel) 2.746s 0.030s 0.014s 92.3x faster 2.15x slower
sieve 1.084s 0.058s 0.022s 18.7x faster 2.66x slower
spectral_norm 0.815s 0.017s 0.009s 47.0x faster 1.87x slower
stencil2d (kernel) 3.694s 0.007s 0.003s 532.5x faster 2.25x slower

Against 0.6.0's table pycc-next's own times are about the same except sieve (0.058s
against 0.048s); the ratios also moved with CPython's times on a shared machine, so treat
differences under ~20% as noise. Windows is not benchmarked.

Added

  • Strict mode folds an isinstance(x, C) test (C a builtin class or a tuple of them,
    combined with not/and/or) whose subject's static type decides it, keeping only
    the branch that runs; the subject is still evaluated (#5). An unannotated parameter
    rebound to a value of another type gets a variable of its own, as a local does, and a
    parameter an isinstance guard normalizes (if not isinstance(B[0], list): B = [[b] for b in B]) gets one per path (#6). A nested helper whose calls give it conflicting
    argument types is compiled once per call site (#7). Together they compile corpus
    problem 188, and --non-strict runs no top-level statement of the corpus in CPython
    (from 1 of 1191).
  • range objects kept in a variable or a list and only iterated (r = list(map(range, xs)), r[k] = range(a, b), for i in r[0]) compile, as their
    (start, stop, step) triples; x is None / x is not None of a value whose type is
    never None (a parameter defaulting to None that every call passes) folds like an
    isinstance test. Corpus problem 168 compiles: strict 190/200 (v0.7 accept 5).
  • bench/pydantic_check.py --time times pydantic 2.13.5 compiled against its sources
    (bench/pydantic_workload.py: import, model definition, model_json_schema(),
    validation/serialization round-trips; fresh interleaved processes, best and median).
    The one-off result (v0.7 accept 4): the compiled tree is slower -- import 0.40x,
    definition 0.70x, schema 0.70x, round-trips 0.91x of CPython's speed -- with 5742
    tests passing and 20 failing; the profile is recorded in docs/ROADMAP.md.

Changed

  • A CPython exception caught in compiled code is mapped to its class by identity first
    (the common case: CPython raised the builtin class itself), so catching a KeyError
    from a dict takes 312 instead of 496 ns (CPython: 202).

Fixed

  • Replacing an element of a list of int/float/bool tuples (rows[i] = (a, b)) leaked
    the boxed tuple it overwrote.
  • The constraint solver typed [[c] * b for i in range(a)] over a parameter c as
    list[int] (a one-element display of a not-yet-typed value gave no type, and the
    comprehension's was guessed before the operator settled), so a helper building one
    was refused with T0022.
  • list(range(10 ** 30)) (and tuple()/sorted() of a range longer than a C
    ssize_t) iterated forever; it now raises CPython's OverflowError: Python int too large to convert to C ssize_t (#3).
  • A missing key of a dict[..., str], an out-of-range index of a list[str] and
    pop() of an empty one leaked the placeholder str returned beside the exception; it
    is now the shared immortal empty str (#4).

Known gaps

  • Compiled library code is slower than CPython: pydantic 2.13.5 under --ext --non-strict imports at 0.40x, defines models and builds JSON schemas at 0.70x and
    round-trips at 0.91x of CPython's speed (fallback source compiled with compile() at
    every import, ~25-40 ns per call across the boundary, object-protocol bodies without
    CPython's specializing interpreter). 440 of its 1249 functions still run in CPython
    (top reasons: the ... literal, function-local import, nested def, lambda,
    hash() of an object); 20 of its tests fail against the compiled tree (sys._getframe
    walks from compiled frames, monkeypatched module globals compiled code does not see).
    Target of v0.8.
  • Real-world corpus, strict: 10 of 200 refused -- mixed-type containers, an int | None
    used as an int without a None test, a variable rebound to another type on some
    paths, iterator objects as values.
  • Windows x86_64: a native program's stdout is \n-terminated UTF-8 whatever the console
    code page, --static-libpython is refused, and the compiler uses the system allocator.
  • Open rows of docs/DEVIATIONS.md, among them: an int beyond 63 bits as a slice bound
    and int ** negative int; container element and key types strict mode refuses
    (T0034-T0039); unsupported constructs refused in strict mode (C0001) or run in
    CPython under --non-strict; sys._getframe counts only Python frames; a published
    native class is not subclassable from Python outside its package; async with runs in
    CPython; an uncaught exception in compiled code prints only the last traceback line;
    raise X from None keeps __suppress_context__ false; reference cycles are never freed
    and __del__ is never called; a big int held by a tuple is never released; enum
    members at the --ext boundary (C0003). See docs/DEVIATIONS.md for every known
    difference from CPython 3.13.