Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 80 additions & 92 deletions .github/workflows/pyre-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,19 @@ jobs:
# current. The `llbc2-` prefix retires the keys whose payload predates it —
# the key is the wide digest, which no edit to the extraction driver moves,
# so without a new namespace the shapeless entries would restore forever.
#
# Each entry also takes a `restore-keys` prefix, so a miss falls back to
# this crate's most recent entry instead of restoring nothing. The exact
# key is the WIDE digest — `Compute LLBC fingerprints` runs before any
# `.readfiles` exists, so `source=` is the whole cargo closure there and
# any edit anywhere misses every crate. The fallback is what lets the
# narrow digest decide instead: `extract-llbc.py` recomputes each crate's
# stamp against the current tree, using the restored `.readfiles` to
# narrow `source=`, and compares it to the restored stamp as exact text.
# A crate whose own closure did not move skips; every other crate
# re-extracts. So the key only chooses what is OFFERED — a stale
# restore costs a download and is then re-extracted, it cannot survive
# into the artefact.
- name: Cache LLBC (majit-rlib)
id: llbc-cache-rlib
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
Expand All @@ -188,6 +201,8 @@ jobs:
build/llbc/majit-rlib.ullbc.readfiles
build/llbc/majit-rlib.*.layouts.ullbc
key: llbc2-majit-rlib-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-${{ steps.llbc-fingerprint.outputs.majit_rlib }}
restore-keys: |
llbc2-majit-rlib-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-
Comment on lines +204 to +205

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid restoring LLBC entries that the stamp must reject

On every primary-key miss caused by a closure edit, this fallback cannot produce the advertised per-crate skip: Compute LLBC fingerprints calculates source= without a .readfiles sidecar, so it falls back to the same conservative input set hashed into the cached stamp's closure= field. Therefore a changed primary digest also means the restored entry's closure= differs from the current stamp, and extract()'s byte-for-byte stamp comparison rejects it and re-extracts the crate. The new fallback consequently downloads a stale LLBC on each miss without avoiding extraction; the key or freshness gate needs to be aligned so a fallback can actually pass.

AGENTS.md reference: AGENTS.md:L67-L72

Useful? React with 👍 / 👎.

- name: Cache LLBC (pyre-object)
id: llbc-cache-object
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
Expand All @@ -198,6 +213,8 @@ jobs:
build/llbc/pyre-object.ullbc.readfiles
build/llbc/pyre-object.*.layouts.ullbc
key: llbc2-pyre-object-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-${{ steps.llbc-fingerprint.outputs.pyre_object }}
restore-keys: |
llbc2-pyre-object-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-
- name: Cache LLBC (pyre-interpreter)
id: llbc-cache-interpreter
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
Expand All @@ -208,6 +225,8 @@ jobs:
build/llbc/pyre-interpreter.ullbc.readfiles
build/llbc/pyre-interpreter.*.layouts.ullbc
key: llbc2-pyre-interpreter-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-${{ steps.llbc-fingerprint.outputs.pyre_interpreter }}
restore-keys: |
llbc2-pyre-interpreter-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-
- name: Cache LLBC (pyre-jit)
id: llbc-cache-jit
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
Expand All @@ -217,6 +236,8 @@ jobs:
build/llbc/pyre-jit.ullbc.fingerprint
build/llbc/pyre-jit.ullbc.readfiles
key: llbc2-pyre-jit-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-${{ steps.llbc-fingerprint.outputs.pyre_jit }}
restore-keys: |
llbc2-pyre-jit-${{ runner.os }}-${{ runner.arch }}-${{ env.CHARON_VERSION }}-
- name: Extract LLBC
# Runs only when some crate's cache missed. extract-llbc.py self-skips
# the crates whose .ullbc + matching .fingerprint stamp were restored
Expand Down Expand Up @@ -261,6 +282,10 @@ jobs:
runs-on: ubuntu-24.04
needs: prepare-charon-llbc-linux
if: ${{ !cancelled() && needs.prepare-charon-llbc-linux.result == 'success' }}
# Two cargo passes plus the release build and run of the CPython suite.
# The cap carries over from the suite's former job; the passes above it
# measured ~34 min together, so it bounds a hang without bounding the work.
timeout-minutes: 90
env:
# See prepare-charon-llbc: downstream jobs download the prepared Charon
# artifact into this workspace path.
Expand All @@ -272,6 +297,15 @@ jobs:
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Set up CPython
# Only the Linux copy of this job runs the CPython suite, which needs an
# interpreter to drive its runner. The other two copies share these steps
# through the anchor, so the condition keeps the install off them.
id: cpython
if: runner.os == 'Linux'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.14"
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-bin: false
Expand Down Expand Up @@ -379,6 +413,31 @@ jobs:
-p tlr -p tl -p tla -p tiny2 -p tiny3 -p tinyframe \
-p braininterp -p dualtape -p tlc -p calc -p i64env -p spcount

# The CPython suite rides this job's Linux copy rather than a job of its
# own: it needs the same checkout, toolchain, Cargo cache and prepared
# Charon/LLBC set, and those cost a job's worth of setup to assemble a
# second time. `!cancelled()` keeps the suite reporting even when a cargo
# pass above it fails — as separate jobs neither could hide the other, and
# a plain step order would.
- name: Build pyre-dynasm
if: ${{ !cancelled() && runner.os == 'Linux' }}
shell: bash
run: cargo build --release -p pyrex --bin pyre-dynasm --no-default-features --features dynasm
- name: Run CPython suite (gate regressions, JIT on)
# This is the only place CI runs the suite — `pyre/check.py` keeps the
# stage behind `--cpython-suite`, which no job passes, so its wall time is
# paid once. The runner gates against the shared baseline plus its own
# `baseline.linux-x86_64.json` overlay; `PLATFORM_GATED` only handles
# modules CPython skips wholesale on this host.
#
# The runner has 3 cores, so `--jobs 4` oversubscribes them and stretches
# the wall time of whichever module is running when the extra job lands.
# `--timeout` is per module: `test.test_asyncio` alone takes ~117s of CPU
# and ~2m47s of wall time, which does not fit the former 120s limit.
if: ${{ !cancelled() && runner.os == 'Linux' }}
shell: bash
run: ${{ steps.cpython.outputs.python-path }} pyre/cpython_tests/run.py --backend dynasm --baseline pyre/cpython_tests/baseline.json --jobs 3 --timeout 300

cargo-test-macos:
name: cargo test (macos-latest)
runs-on: macos-latest
Expand Down Expand Up @@ -497,18 +556,6 @@ jobs:
# native backends.
if: runner.os == 'Linux'
run: rustup target add wasm32-unknown-unknown
- name: Build pyre-wasm web binding (Linux only)
# check.py builds and exercises the wasm-host binding below. Build the
# browser binding in the same release target first so the two flavours
# share their compiled dependencies without spending two additional
# runner jobs on checkout, cache restore, and LLBC download. check.py's
# later wasm-host build intentionally replaces pyre_wasm.wasm before it
# snapshots and runs that module.
if: runner.os == 'Linux'
run: |
cargo build --release -p pyre-wasm \
--target wasm32-unknown-unknown \
--no-default-features --features web
- name: Run pyre/check.py
env:
PYRE_CHECK_PYTHON3: ${{ steps.cpython.outputs.python-path }}
Expand Down Expand Up @@ -639,87 +686,8 @@ jobs:
CHARON_VERSION: nightly-2026.05.29
steps: *pyre-check-steps

cpython-tests:
name: CPython suite (gate)
runs-on: ubuntu-24.04
# Artifacts are named for this Linux runner, so wait for the Linux prepare
# leg; waiting for macOS races the Linux upload. This job is the only place
# CI runs the suite — `pyre/check.py` keeps the stage behind
# `--cpython-suite`, which no job passes, so its wall time is paid once.
# Separately, this runner gates against the shared baseline plus its own
# `baseline.linux-x86_64.json` overlay; `PLATFORM_GATED` only handles
# modules CPython skips wholesale on this host.
needs: prepare-charon-llbc-linux
if: ${{ !cancelled() && needs.prepare-charon-llbc-linux.result == 'success' }}
timeout-minutes: 30
env:
# See prepare-charon-llbc: downstream jobs download the prepared Charon
# artifact into this workspace path.
PYRE_SHARED_BUILD: ${{ github.workspace }}/.pyre-build
CHARON_VERSION: nightly-2026.05.29
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Set up CPython
id: cpython
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.14"
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-bin: false
shared-key: ${{ runner.arch }}
# Restore-only on PRs (they pull main's cache via restore-keys); save
# only on main so per-PR target caches don't multiply across refs past
# GitHub's 10 GB cache budget and trigger LRU eviction.
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Download Charon artifact
# Run-scoped handoff from prepare-charon-llbc. Cross-run reuse still
# comes from the prepare job's cache; consumers avoid cache eviction
# races by downloading the prepared install tree directly.
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: charon-${{ runner.os }}-${{ runner.arch }}
path: .pyre-build/charon
- name: Restore Charon executable permissions (Unix)
if: runner.os != 'Windows'
shell: bash
run: chmod +x .pyre-build/charon/*/charon .pyre-build/charon/*/charon-driver
- name: Download LLBC artifact
# Run-scoped handoff from prepare-charon-llbc. Unlike actions/cache,
# an artifact is exempt from the repo-wide 10 GB cache budget and its
# LRU eviction, so a small ullbc set cannot vanish between prepare and
# a late-scheduled consumer (the macOS legs start well after the
# Linux/Windows legs — long enough for a cached entry to be evicted).
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: llbc-${{ runner.os }}-${{ runner.arch }}
path: build/llbc
- name: Verify prepared Charon/LLBC
shell: bash
run: |
test -d .pyre-build/charon
for crate in majit-rlib pyre-object pyre-interpreter pyre-jit; do
test -s "build/llbc/${crate}.ullbc"
# The sidecar carries the artefact's own file table, which is what
# `source=` hashes. Without it the fingerprint falls back to the whole
# cargo closure and every consumer reports LLBC STALE, so fail here
# where the cause is still visible.
test -s "build/llbc/${crate}.ullbc.readfiles"
done
- name: Build pyre-dynasm
run: cargo build --release -p pyrex --bin pyre-dynasm --no-default-features --features dynasm
- name: Run CPython suite (gate regressions, JIT on)
# The runner has 3 cores, so `--jobs 4` oversubscribes them and stretches
# the wall time of whichever module is running when the extra job lands.
# `--timeout` is per module: `test.test_asyncio` alone takes ~117s of CPU
# and ~2m47s of wall time, which does not fit the former 120s limit.
run: ${{ steps.cpython.outputs.python-path }} pyre/cpython_tests/run.py --backend dynasm --baseline pyre/cpython_tests/baseline.json --jobs 3 --timeout 300

sandbox-build:
name: sandbox build + e2e (ubuntu-24.04)
name: sandbox e2e + wasm web build (ubuntu-24.04)
runs-on: ubuntu-24.04
needs: prepare-charon-llbc-linux
if: ${{ !cancelled() && needs.prepare-charon-llbc-linux.result == 'success' }}
Expand Down Expand Up @@ -768,6 +736,26 @@ jobs:
# where the cause is still visible.
test -s "build/llbc/${crate}.ullbc.readfiles"
done
- name: Add wasm32 target
run: rustup target add wasm32-unknown-unknown
- name: Build pyre-wasm web binding
# Compile check only: nothing here runs the browser binding, and the
# `pyre/check.py` job overwrites pyre_wasm.wasm with its own wasm-host
# build before it snapshots and runs that module. It runs here, and
# first, because this job already carries the checkout, toolchain, Cargo
# cache and prepared Charon/LLBC set the build needs, and finishes well
# inside the wall time of the check.py leg it used to sit in.
#
# It was placed ahead of check.py on the premise that the two wasm
# flavours would share compiled dependencies. Measured: the web build
# takes 6m and leaves the later wasm-host build 50s cheaper (2m32 ->
# 1m42), because check.py sets RUSTFLAGS the web build does not and half
# the graph recompiles under the difference. So the sharing is real and
# costs about seven times what it returns.
run: |
cargo build --release -p pyre-wasm \
--target wasm32-unknown-unknown \
--no-default-features --features web
- name: Build pyre --features sandbox
# A green sandbox build is the fails-closed proof: every mediated module
# names libc through host_seam::sys, so any direct syscall left outside
Expand Down
2 changes: 1 addition & 1 deletion pyre/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -3095,7 +3095,7 @@ def run_cpython_suite(self):

Off by default and reached only through `--cpython-suite`: the suite
costs more wall time than every other stage here put together, and the
`cpython-tests` CI job already runs it on its own schedule. Pass the
`cargo-test-linux` CI job already runs it on its own schedule. Pass the
flag locally when a change could move a verdict the synthetic corpus
does not cover.
"""
Expand Down
6 changes: 3 additions & 3 deletions pyre/cpython_tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ before any test, a denied resource, or a suite whose every case was skipped).

## CI

- `.github/workflows/pyre-ci.yml` job `cpython-tests` — gates PRs on the
baseline-`PASS` subset, dynasm with **JIT on** (`MAJIT_STRICT=1`), on
`ubuntu-24.04` (x86_64). The baseline is recorded on linux-x86_64 and the JIT
- `.github/workflows/pyre-ci.yml` job `cargo-test-linux`, in its trailing
`Run CPython suite` step — gates PRs on the baseline-`PASS` subset, dynasm
with **JIT on** (`MAJIT_STRICT=1`), on `ubuntu-24.04` (x86_64). The baseline is recorded on linux-x86_64 and the JIT
codegen is architecture-specific, so local baseline comparisons must use the
same host.
- `.github/workflows/pyre-cpython-nightly.yml` — non-gating nightly `--full`
Expand Down
Loading