diff --git a/.github/workflows/pyre-ci.yml b/.github/workflows/pyre-ci.yml index fdce7711133..fa91cf7ff64 100644 --- a/.github/workflows/pyre-ci.yml +++ b/.github/workflows/pyre-ci.yml @@ -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 @@ -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 }}- - name: Cache LLBC (pyre-object) id: llbc-cache-object uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -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 @@ -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 @@ -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 @@ -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. @@ -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 @@ -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 @@ -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 }} @@ -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' }} @@ -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 diff --git a/pyre/check.py b/pyre/check.py index 8a27f65cda4..77f21307423 100644 --- a/pyre/check.py +++ b/pyre/check.py @@ -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. """ diff --git a/pyre/cpython_tests/README.md b/pyre/cpython_tests/README.md index ee532e75310..3ad533ff933 100644 --- a/pyre/cpython_tests/README.md +++ b/pyre/cpython_tests/README.md @@ -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`