fix(codegen): a declared numeric type is not a proof that the value is a number #379
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 Ptr<Shape> OFF-arm witness | |
| # #6984: PERRY_PTR_SHAPE_LOCALS is the documented A/B kill switch for repsel | |
| # Phase 3b/5a (`Ptr<Shape>` proven object locals + the proven-`this` method | |
| # clones it feeds, #6911/#6925). Its whole point is that flipping it OFF | |
| # reverts to the boxed lowering and reproduces byte-identical output — that is | |
| # the entire premise the `rep_ptr_shape_off` matrix arm and every other | |
| # representation's own OFF arm exist to verify (CLAUDE.md's GC knob | |
| # kill-policy: "every GC env knob either has a required CI arm exercising its | |
| # OFF state, or it is deleted after one release of soak"). | |
| # | |
| # That premise was FALSE on `main` for #6976/#6984: with the switch off, an | |
| # interface-typed local with `new` provenance and a scalar-replaceable | |
| # constructor (`const o: Shaped = new Impl(...)`) crashed with `TypeError: | |
| # Cannot read properties of undefined`. No GC was involved (`cycles=0 | |
| # moved=0`) — this is a plain codegen defect in a completely different, | |
| # supposedly-orthogonal optimization (scalar replacement of `new` locals, | |
| # `collectors/escape_news.rs`), which the switch is not even supposed to | |
| # touch. It went unnoticed because the arm that would have caught it — | |
| # `rep_ptr_shape_off`, part of `scripts/gc_repsel_matrix.sh`'s `--arms all` — | |
| # is NOT in `gc-stress`'s PR-gating subset (`PR_ARMS` in that script), so no | |
| # per-PR run ever compiled with the switch off. `--arms all` only runs on | |
| # push-to-main/schedule/workflow_dispatch, so a regression here would sit | |
| # undetected for up to a release cycle, exactly the class of hole #7194 (the | |
| # `gc_gate_wiring_check.py` precedent) was written to close for the moving | |
| # collector. | |
| # | |
| # WHAT THIS DOES | |
| # | |
| # One arm, the whole representation-selection corpus: | |
| # | |
| # scripts/gc_repsel_matrix.sh --arms rep_ptr_shape_off --filter test_gap_repsel_ | |
| # | |
| # `rep_ptr_shape_off` compiles with `PERRY_PTR_SHAPE_LOCALS=0` and runs under | |
| # the evacuating base + forced evacuation (`%E% PERRY_GC_FORCE_EVACUATE=1`), | |
| # so it doubles as the OFF arm's GC-under-evacuation witness — not just this | |
| # one crash's regression test. The matrix's own exit status already covers | |
| # both halves of CLAUDE.md's "four ways a gate can be unable to fail" #4: a | |
| # FAIL cell fails the run, and an all-UNVER table (the arm's `requires=move` | |
| # never actually relocating anything) fails it too | |
| # (`scripts/gc_matrix_liveness_check.py`, invoked by the matrix script | |
| # itself) — so this job needs no separate liveness re-check the way | |
| # `gc-moving-witnesses.yml` does for its non-repsel-prefixed corpus. | |
| # | |
| # THIS JOB IS DESIGNED TO BE ABLE TO FAIL, checked against all four ways | |
| # (CLAUDE.md): | |
| # | |
| # 1. no `continue-on-error`, no `|| true`, no pipe between the matrix and | |
| # the shell's exit status; | |
| # 2. NOT in branch protection's required contexts yet, deliberately: a new | |
| # gate has never been green, and promoting it immediately would block | |
| # every open PR (CLAUDE.md's own corollary). Promote once it has a green | |
| # run on `main` — that is a maintainer action, not something this PR | |
| # does. | |
| # 3. `concurrency` cancels pull-request runs only; `main` runs are keyed on | |
| # the commit SHA so they queue instead of being cancelled by the next | |
| # merge (#7205's fix, replicated from gc-moving-witnesses.yml). | |
| # 4. the matrix's OWN exit status already asserts liveness (see above), so | |
| # a green run here means the OFF arm actually compiled, actually ran, | |
| # and actually matched the pinned Node oracle — not merely that nothing | |
| # threw. | |
| # | |
| # WHAT THIS DELIBERATELY DOES NOT COVER | |
| # | |
| # * The other representations' own OFF arms (`rep_i32_off`, `rep_str_off`, | |
| # `rep_ptr_numarray_off`, `rep_spec_abi_off`, `rep_int_valued_off`, | |
| # `rep_str_static_off`) are still only exercised by `--arms all` on | |
| # push/schedule. This job is scoped to the one knob #6984 was filed | |
| # against; extending it to the others is a separate decision with its own | |
| # cost/coverage tradeoff, not bundled in here. | |
| # * `--arms all`'s other cells continue to run on their existing schedule | |
| # (gc-stress in test.yml); this job does not replace that run, it adds a | |
| # per-PR arm the existing PR-gating subset omits. | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| # One group per main COMMIT, cancelling PR runs only — see | |
| # gc-moving-witnesses.yml's comment for the #7205 rationale this mirrors. | |
| group: gc-ptr-shape-off-witness-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| # scripts/gc_repsel_matrix.sh deliberately does NOT set | |
| # PERRY_NO_AUTO_OPTIMIZE (see its own header): the auto-optimizer decides | |
| # whether the linked runtime carries the `diagnostics` feature, which | |
| # changes the GC trace format the harness parses for its liveness evidence. | |
| # Setting it here from outside could silently turn every cell UNVER. | |
| jobs: | |
| gc-ptr-shape-off-witness: | |
| # ubuntu-latest, matching gc-stress: the matrix already runs there, and | |
| # the cargo cache is shared with that job (same `shared-key`) so this | |
| # build is usually warm. | |
| runs-on: ubuntu-latest | |
| 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 Ptr<Shape> lowering | |
| 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 witness." | |
| 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 what a scalar-replaced receiver's | |
| # method-call lowering resolves to, not just the Ptr<Shape> | |
| # collectors themselves — #6984's defect lived in | |
| # lower_call/scalar_method.rs, three files away from ptr_shape.rs. | |
| if grep -qE '^(crates/|scripts/gc_repsel_matrix\.sh$|test-files/test_gap_repsel_|test-parity/gc_repsel_|Cargo\.(toml|lock)$|\.node-version$|\.github/workflows/gc-ptr-shape-off-witness\.yml$)' changed.txt; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| echo "Change touches representation-selection-relevant paths; running the witness." | |
| else | |
| echo "run=false" >> "$GITHUB_OUTPUT" | |
| echo "No representation-selection-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: | |
| # Same key as gc-stress, which builds the identical package set, so | |
| # this job usually starts from that job's 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: Setup Node oracle | |
| if: steps.relevance.outputs.run == 'true' | |
| uses: actions/setup-node@v7 | |
| with: | |
| # Single source of truth: .node-version. The matrix refuses to run | |
| # when the running node disagrees with the pin, so a version drift | |
| # here would abort loudly rather than silently drop a test. | |
| node-version-file: .node-version | |
| - name: Build perry and the runtime archives | |
| if: steps.relevance.outputs.run == 'true' | |
| env: | |
| # Match cargo-test / gc-stress's linker workaround (lld SIGBUS on | |
| # the 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.a and | |
| # libperry_stdlib.a come from the -static wrapper crates. Building | |
| # without them links a stale archive and makes the whole run | |
| # vacuous (CLAUDE.md, "Verifying a runtime change"). Package set | |
| # matches gc-stress exactly so the cache is shared. | |
| 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. | |
| # scripts/gc_repsel_matrix.sh's own exit status already folds in the | |
| # liveness gate (scripts/gc_matrix_liveness_check.py) alongside the | |
| # byte-exactness check, so a green run here means the OFF arm actually | |
| # compiled, actually ran under evacuation, and actually matched the | |
| # pinned Node oracle for every file in the representation-selection | |
| # corpus — not merely that nothing threw (CLAUDE.md's "four ways a | |
| # gate can be unable to fail", #4). | |
| - name: Run the PERRY_PTR_SHAPE_LOCALS=0 witness across the repsel corpus | |
| if: steps.relevance.outputs.run == 'true' | |
| run: | | |
| ./scripts/gc_repsel_matrix.sh \ | |
| --no-build \ | |
| --arms rep_ptr_shape_off \ | |
| --filter test_gap_repsel_ \ | |
| --json gc-ptr-shape-off-witness.json | |
| - name: Upload the witness report | |
| if: always() && steps.relevance.outputs.run == 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: gc-ptr-shape-off-witness-${{ github.sha }} | |
| path: gc-ptr-shape-off-witness.json | |
| if-no-files-found: ignore | |
| retention-days: 30 |