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
118 changes: 118 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,124 @@ jobs:
name: compiler-output-regression
path: target/compiler-output-regression/

# ---------------------------------------------------------------------------
# Representation-selection promotion census (#7106)
#
# Counts, per workload and PER REPRESENTATION, how many values got each
# unboxed representation, and compares each count against a ratcheted floor
# (benchmarks/repsel_census/baseline.json).
#
# Why this job exists: #7034 discovered by hand-instrumenting the compiler
# that `Ptr<Shape>` promotes NOTHING on `batch.ts` — the object-heavy
# workload it exists for — and that only 3 of 17 suite benchmarks promote a
# single shape local each. Nothing in CI could have told anyone that.
#
# Why it can fail (CLAUDE.md, "Four ways a gate can be unable to fail"):
# floors alone would not be enough, because the honest floor for
# `Ptr<Shape>` on real code is zero today and a zero floor can never go red.
# The corpus therefore includes hand-written liveness fixtures whose
# minimums live in `scripts/compiler_output_harness/repsel_census.py`, NOT in
# the regenerable baseline, plus a corpus-wide assertion that no census key
# reads zero everywhere. Verified by sabotage in both directions: each of
# PERRY_PTR_SHAPE_LOCALS / PERRY_PTR_NUMARRAY_LOCALS /
# PERRY_CANONICAL_I32_LOCALS / PERRY_CANONICAL_STR_LOCALS /
# PERRY_INT_VALUED_LOCALS set to 0 turns this job red; the default build is
# green.
#
# DELIBERATELY NOT a required status check on its first landing — a gate
# that has never been green would block every open PR. Promoting it is a
# follow-up, and CLAUDE.md failure mode (2) is what happens if that
# follow-up is skipped.
# ---------------------------------------------------------------------------
repsel-census:
runs-on: ubuntu-latest
timeout-minutes: 45
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "false"
SCCACHE_DIR: ${{ github.workspace }}/.sccache
SCCACHE_CACHE_SIZE: "12G"
CARGO_INCREMENTAL: "0"
# The census only needs codegen (`--no-link`), never a linked binary, so
# it cannot be fooled by a stale libperry_runtime.a. Auto-optimize is off
# because it rebuilds runtime libs the census never links.
PERRY_NO_AUTO_OPTIMIZE: "1"
steps:
- uses: actions/checkout@v7

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.10

- name: Cache sccache objects
uses: actions/cache@v6
with:
path: ${{ github.workspace }}/.sccache
key: sccache-${{ runner.os }}-perry-${{ github.job }}-${{ github.run_id }}
restore-keys: |
sccache-${{ runner.os }}-perry-

- uses: Swatinem/rust-cache@v2
with:
shared-key: "${{ runner.os }}-perry"
save-if: ${{ github.ref == 'refs/heads/main' }}

# Verdict logic first, cheaply: if the census can't tell a regression
# from a pass, the compile-based run below is not worth the minutes.
- name: Census verdict self-test
run: |
python3 scripts/compiler_output_regression.py census-self-test
python3 -m unittest tests.test_repsel_census

- name: Build compiler
run: cargo build -p perry

- name: Promotion census
run: |
python3 scripts/compiler_output_regression.py census \
--perry target/debug/perry \
--keep-reports target/repsel-census/reports \
--gate

# Prove the gate's subject was live: with `Ptr<Shape>` selection
# disabled the census MUST go red. A census that reports zero and exits
# green is worth nothing, and this step is what stops that from being
# possible. `!` because a green run here is the failure.
- name: Sabotage check (the gate must be able to fail)
run: |
set +e
out="$(python3 scripts/compiler_output_regression.py census \
--perry target/debug/perry \
--env PERRY_PTR_SHAPE_LOCALS=0 \
--gate 2>&1)"
status=$?
set -e
printf '%s\n' "$out"
# Exit 1 is the gate's verdict; exit 2 is a harness error (a compile
# that fell over, a schema mismatch). Only the former proves the
# census observed the sabotage, so insist on the code AND the reason.
if [ "$status" -ne 1 ]; then
echo "::error::Sabotage run exited $status, expected 1 (a gate verdict)."
echo "::error::Exit 0 means the census cannot see Ptr<Shape> promotion at all."
exit 1
fi
if ! printf '%s' "$out" | grep -q "fixture_ptr_shape: ptr-shape promoted 0"; then
echo "::error::Sabotage run failed for some reason OTHER than the"
echo "::error::ptr-shape fixture losing its promotion. The gate is red,"
echo "::error::but not for the reason that proves its subject was live."
exit 1
fi
echo "Census correctly went red with PERRY_PTR_SHAPE_LOCALS=0."

- name: Upload census reports
if: always()
uses: actions/upload-artifact@v7
with:
name: repsel-census
path: target/repsel-census/

# ---------------------------------------------------------------------------
# Native ABI evidence packet
#
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tests/test-*
!tests/test_perf_frontier_report.py
!tests/test_test262_focused_report.py
!tests/test_npm_sweep.py
!tests/test_repsel_census.py
!tests/test_perf_frontier_gate_smoke.sh
!tests/*/
bench_*
Expand Down
97 changes: 97 additions & 0 deletions benchmarks/repsel_census/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Representation-selection promotion census (#7106)

How many values actually get each of Perry's unboxed representations, per
workload, with a ratcheted floor so a drop turns a build red.

```bash
# Count promotions across the corpus and check the floors.
python3 scripts/compiler_output_regression.py census --gate

# Report only, no verdict.
python3 scripts/compiler_output_regression.py census

# Re-record the floors after an intentional change.
python3 scripts/compiler_output_regression.py census --update

# Verdict logic only (no compiler needed).
python3 scripts/compiler_output_regression.py census-self-test
```

## Why this exists

Perry's performance story rests on six unboxed representations. Until #7034,
nobody knew how many values any of them promoted on real code, because nothing
reported it — an agent had to hand-instrument the compiler to find out that
`Ptr<Shape>` promotes **nothing at all** on `benchmarks/app-patterns/kernels/batch.ts`,
the object/property-heavy program that representation exists for.
Independently confirmed at the time: `PERRY_PTR_SHAPE_LOCALS=0` and the default
produced a byte-identical binary.

The census makes that a standing, visible, gated number instead of a discovery.

## How it cannot quietly pass

Read CLAUDE.md, "★ Four ways a gate can be unable to fail". The fourth applies
here most directly: *the gate runs but its subject never did*. A census that
faithfully prints `Ptr<Shape>: 0` and exits green is worth nothing.

Three separate mechanisms, in increasing order of paranoia:

1. **Per-workload, per-representation floors** in `baseline.json`. A count
below its floor is a regression. Counts are recorded per representation,
never aggregated — the interesting signal is `Ptr<Shape>` being 0 *while*
canonical `Str` is nonzero, and one total hides exactly that.

2. **Liveness fixtures** in `fixtures/`. Floors alone are not enough: the
honest floor for `Ptr<Shape>` on real code is zero today, and a zero floor
can never fail. Each fixture is written to satisfy one representation's
proof obligations in full, and its minimum lives in `LIVENESS_FLOORS` in
`scripts/compiler_output_harness/repsel_census.py` — **in code, not in the
regenerable baseline**, so that re-running `--update` after a breakage
cannot write the fixture down to zero and leave a permanently-green gate.
`--update` refuses to do it.

3. **A corpus-wide instrument check**: a census key that reads zero in *every*
workload fails the run. A counter that is zero because nothing promoted and
one that is zero because nobody increments it look identical; this
distinguishes them. `Ptr<NumArray>` was in the second state until #7106 — it
had an `Analysis` variant, a `Ptr<NumArray>` target-rep string, and no
`select()` call site anywhere in the tree.

Sabotage-verified in both directions. Each of `PERRY_PTR_SHAPE_LOCALS=0`,
`PERRY_PTR_NUMARRAY_LOCALS=0`, `PERRY_CANONICAL_I32_LOCALS=0`,
`PERRY_CANONICAL_STR_LOCALS=0` and `PERRY_INT_VALUED_LOCALS=0` turns the census
red; the default build is green. CI re-runs the first of those on every job so
the property is checked, not just claimed once.

## Editing the fixtures

Don't tidy them. Every one is written against a specific collector's rules and
most of the obvious cleanups disqualify the very local under test — passing a
`Ptr<Shape>` local to a function is an escape (rule 2), wrapping a canonical-i32
local in a loop moves it to the parallel-shadow model, adding a bounds check to
`fixture_int_valued_ta` moves its locals to the ordinary integer-local path.
Each file says which edits would silently take it to zero.

If a fixture legitimately stops exercising its representation, change the
fixture *and* say so in the PR. Lowering `LIVENESS_FLOORS` instead is how this
gate would end up unable to fail.

## Coverage

Instrumented (counted by `--opt-report`, #6952):

| Census key | Representation | Source of truth |
| --- | --- | --- |
| `ptr-shape` | `Ptr<Shape>` | `collectors/ptr_shape.rs` |
| `ptr-numarray` | `Ptr<NumArray>` | `collectors/ptr_numarray.rs` |
| `canonical-i32` / `canonical-u32` / `canonical-str` | canonical slot reps | `expr/slot_rep.rs` |
| `int-valued-ta` | int-valued locals | `collectors/int_valued_ta_locals.rs` |
| `spec-abi-entry` | specialized ABI entries | `codegen/typed_abi.rs` |
| `spec-abi-taptr-slot` | `TaPtr` parameter slots | `collectors/spec_abi_sites.rs` |

**Not** instrumented, stated plainly rather than reported as a zero: the
masked-window / buffer-view `TaPtr` *region* machinery
(`stmt/masked_window_region.rs`). It is region-shaped rather than a per-value
promotion and has no `opt_report` analysis, so the census makes no claim about
it. `spec-abi-taptr-slot` covers `TaPtr` only in its parameter form.
Loading
Loading