fix(codegen): guard declared string self-append operator (#7841) #488
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: GC Parse-Churn Layout Gate | |
| # Promotes the "tape=0 + from-space-scan parse-then-churn" check to CI (#7647). | |
| # | |
| # WHY THIS EXISTS | |
| # | |
| # #7643 measured that `PERRY_JSON_TAPE=0` + `PERRY_GC_FROMSPACE_SCAN=1` over a | |
| # parse-then-churn workload is a known-good end-to-end detector for the whole | |
| # layout-state family (#7630 / #7633 / #7635 / #7644): with the JSON | |
| # materialiser's finalize sabotaged to always claim `POINTER_FREE`, it reports | |
| # `dangling=8000 owners=4000` and the binary SIGBUSes; clean, `dangling=0` and | |
| # exit 0. #7643/#7644 shipped workload-free unit tests for two invariants that | |
| # cannot be defeated by GC timing or a lazy path -- the right primary guard -- | |
| # but neither can catch a NEW materialiser path that forgets to finalize at | |
| # all, since a hand-built unit-test object never exercises a real call site. | |
| # That is what this job is for. Nothing ran the end-to-end check in CI before | |
| # this file; #7647 is that promotion. | |
| # | |
| # THIS JOB IS DESIGNED TO BE ABLE TO FAIL, checked against CLAUDE.md's "four | |
| # ways a gate can be unable to fail": | |
| # | |
| # 1. no `continue-on-error`, no `|| true`, no pipe between the gate script | |
| # and the shell's exit status. `scripts/gc_parse_churn_layout_gate.sh` | |
| # itself runs under `set -euo pipefail` and its final line is a bare | |
| # `exit "$CHECK_RC"`. | |
| # 2. NOT wired into branch protection's required contexts by this change, | |
| # deliberately: a new gate has never been green, so promoting it | |
| # immediately would block every open PR (CLAUDE.md's corollary). That is | |
| # a maintainer action, to be taken after this job's first observed green | |
| # run on `main` -- and per the corollary, taking it is not optional | |
| # follow-through: `gc-root-dominance` sat red on `main` for weeks after | |
| # the same deferral because the second step was never done. | |
| # 3. `concurrency` below cancels `pull_request` runs only; `push` (main) | |
| # runs are keyed on the commit SHA so they queue instead of cancelling | |
| # each other (the `gc-ratchet` regression this guards against is #7205). | |
| # 4. the subject must be LIVE, not merely quiet. | |
| # `scripts/gc_parse_churn_layout_check.py` -- the actual verdict logic, | |
| # not this workflow -- rejects a run whose from-space scan never even | |
| # ran, one where zero copying minors relocated anything (the moving | |
| # collector is opt-in: `PERRY_GC_MOVING_LOOP_POLLS=1`, both at compile | |
| # time and run time, is the ONLY configuration that exercises it end to | |
| # end per #7161's stopgap), and one whose from-space census stayed too | |
| # small to have eagerly materialised the record cohort (the #7635 | |
| # vacuity: a lazily-parsed cohort leaves only a handful of live objects, | |
| # nowhere near the fixture's record count, so a clean scan over it would | |
| # mean nothing). `scripts/gc_parse_churn_layout_check.py --self-test` | |
| # proves the checker itself can say no on all of the above, in both | |
| # directions, and runs on every PR regardless of relevance (it needs no | |
| # compiler). The end-to-end sabotage run -- the JSON materialiser's | |
| # finalize forced to always claim `POINTER_FREE`, #7635's exact | |
| # mutation -- is documented in this gate's introducing PR rather than | |
| # re-run on every CI invocation, matching how `gc-root-dominance` and | |
| # `gc-moving-witnesses` treat their own one-time proof. | |
| # | |
| # WHAT IT DOES | |
| # | |
| # One script, no arms to choose between: | |
| # | |
| # scripts/gc_parse_churn_layout_gate.sh <perry-binary> | |
| # | |
| # which compiles `scripts/fixtures/gc_parse_churn_layout_state.ts` with | |
| # `PERRY_GC_MOVING_LOOP_POLLS=1` (a compile-time gate as well as a runtime | |
| # one -- see that script's own comments), runs it under | |
| # `PERRY_JSON_TAPE=0 PERRY_GC_FROMSPACE_SCAN_ABORT=1 PERRY_GC_DIAG=1`, and | |
| # hands the captured stdout/stderr/exit-code to the Python checker above. | |
| # | |
| # WHAT IT DELIBERATELY DOES NOT COVER | |
| # | |
| # This is a single, deliberately narrow probe (4,000 JSON records, 2 | |
| # pointer-bearing string fields each, matching #7643's own reproduction | |
| # shape) -- not a sweep over every layout-state shape the codebase can | |
| # produce. It is the end-to-end complement to #7643/#7644's unit tests, not a | |
| # replacement for them, and not a substitute for `gc-moving-witnesses` (the | |
| # #7154 stale-root reproducer family) or `gc-root-dominance` (the static | |
| # root-store-dominance check), which cover different hazard classes. | |
| on: | |
| pull_request: | |
| # POST-MERGE ARM: staggered six-hourly sweep of `main`, NOT one run per merge. | |
| # `push: branches: [main]` starved this gate and nine others for two days | |
| # (#7856): ~29 jobs enqueued per merge at 58 merges/day against a ~9-job | |
| # concurrency ceiling. The pull-request arm is unchanged -- every PR is still | |
| # measured. ***DO NOT RESTORE `push: branches: [main]`.*** | |
| # Rationale, measurement and cost: docs/src/testing/ci-gate-scheduling.md | |
| schedule: | |
| - cron: "57 */6 * * *" | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| # One group per push COMMIT so `main` runs queue instead of cancelling each | |
| # other (a `cancel-in-progress: true` scoped to the whole group would still | |
| # cancel a pending `main` run the moment a new one enters the SAME group -- | |
| # #7205, measured on gc-ratchet with three consecutive `main` runs | |
| # cancelled and zero executed). PR runs are cancelled on superseding pushes. | |
| group: gc-parse-churn-gate-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| self-test-checker: | |
| # Cheap, compiler-free, and runs on every PR regardless of relevance: the | |
| # checker logic must always be able to say no. See CLAUDE.md's GC knob | |
| # kill-policy and the four-ways-a-gate-cannot-fail rule this whole job | |
| # exists to satisfy point 4 of. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Self-test the verdict logic | |
| run: python3 scripts/gc_parse_churn_layout_check.py --self-test | |
| gc-parse-churn-gate: | |
| runs-on: ubuntu-latest | |
| # Matches gc-moving-witnesses/gc-stress: the witness run itself is | |
| # minutes, but a cold cargo cache makes the compiler build the whole cost | |
| # of this job. | |
| timeout-minutes: 90 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Decide whether this change can affect the layout-state / parse-churn path | |
| id: relevance | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| REPOSITORY: ${{ github.repository }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$EVENT_NAME" != "pull_request" ]]; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| echo "Not a pull request; running the gate." | |
| exit 0 | |
| fi | |
| gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename' > changed.txt | |
| # Deliberately broad, same rationale as gc-moving-witnesses.yml: | |
| # anything under crates/ can move the collector or change how JSON | |
| # parses, and the filter exists only to spare docs-only PRs a | |
| # compiler build. | |
| if grep -qE '^(crates/|scripts/gc_parse_churn_layout_(gate\.sh|check\.py)$|scripts/fixtures/gc_parse_churn_layout_state\.ts$|Cargo\.(toml|lock)$|\.github/workflows/gc-parse-churn-gate\.yml$)' changed.txt; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| echo "Change touches a collector- or JSON-parse-relevant path; running the gate." | |
| else | |
| echo "run=false" >> "$GITHUB_OUTPUT" | |
| echo "No relevant paths changed." | |
| fi | |
| - name: Install Rust toolchain | |
| if: steps.relevance.outputs.run == 'true' | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-llvm22 | |
| if: steps.relevance.outputs.run == 'true' | |
| - uses: Swatinem/rust-cache@v2 | |
| if: steps.relevance.outputs.run == 'true' | |
| with: | |
| # Shared with gc-stress/gc-moving-witnesses (same package set), so | |
| # this job usually starts from an already-warm cache. | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Install clang | |
| if: steps.relevance.outputs.run == 'true' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y clang | |
| - name: Build perry and the runtime archives | |
| if: steps.relevance.outputs.run == 'true' | |
| env: | |
| # Matches cargo-test/gc-stress/gc-moving-witnesses: works around an | |
| # lld SIGBUS on this shared runner during large links. | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C linker-features=-lld" | |
| run: | | |
| set -euo pipefail | |
| # perry-runtime/perry-stdlib are rlib-only; libperry_{runtime,stdlib}.a | |
| # come from the *-static wrapper crates. Building without them links | |
| # a stale archive and makes this whole gate vacuous in a way nothing | |
| # downstream can detect (CLAUDE.md's "Verifying a runtime change"). | |
| cargo build --release \ | |
| -p perry -p perry-runtime -p perry-stdlib \ | |
| -p perry-runtime-static -p perry-stdlib-static | |
| for artifact in perry libperry_runtime.a libperry_stdlib.a; do | |
| test -s "target/release/$artifact" \ | |
| || { echo "::error::target/release/$artifact was not produced"; exit 1; } | |
| done | |
| # GATING. No pipe, no `|| true`: this step's exit status is the gate. | |
| - name: Run the parse-then-churn layout-state gate | |
| if: steps.relevance.outputs.run == 'true' | |
| run: scripts/gc_parse_churn_layout_gate.sh target/release/perry |