fix(codegen): a declared numeric type is not a proof that the value is a number #235
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: TLS Budget | |
| # Keeps Darwin's `_tlv_get_addr` cost from creeping back into the runtime | |
| # a fourth time (#7469). | |
| # | |
| # WHY THIS EXISTS | |
| # | |
| # On Darwin every `thread_local!` access is an out-of-line call to | |
| # `_tlv_get_addr` in libdyld. `crates/perry-runtime/src/tls_hot.rs` has removed | |
| # that cost three times and it has come back three times: | |
| # | |
| # after #7565 churn_alloc 0% | |
| # later churn_alloc 8-9% | |
| # later interp 11% | |
| # v0.5.1434 asyncpipe 20.5% <- the largest single symbol in the | |
| # worst-performing realistic program | |
| # | |
| # The mechanism never decayed. Nothing measured it. This job is that | |
| # measurement. | |
| # | |
| # 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` between the gate script and the | |
| # shell's exit status. `scripts/tls_budget_gate.sh` runs under | |
| # `set -euo pipefail` and ends in a bare `exit "$rc"`. | |
| # 2. NOT wired into branch protection's required contexts by the change that | |
| # adds it -- 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 after the first observed green run on `main`, and per the | |
| # corollary it is not optional follow-through: `gc-root-dominance` sat red | |
| # on `main` for weeks because the second step was never taken. | |
| # 3. `concurrency` below cancels `pull_request` runs only; `push` runs are | |
| # keyed on the commit SHA so they queue instead of cancelling each other | |
| # (#7205). | |
| # 4. THE SUBJECT MUST BE THE UNCOVERED ONE, and that is the whole design. | |
| # Profiling `churn_alloc` -- the benchmark every previous fix was tuned | |
| # against -- would pass forever while the real cost grew, because churn's | |
| # thread-locals are exactly the sixteen the named-field cache covers by | |
| # construction. So the subjects are `benchmarks/tls-budget/asyncpipe.ts` | |
| # (Map/Set registries, buffer brands, descriptor state, async, template | |
| # literals) and `interp.ts` (inline-cache misses, field lookup, arguments | |
| # objects), and `scripts/tls_budget_check.py` refuses a pass unless the | |
| # run proves it was live: `PERRY_TLS_HOT_STATS=1` reporting | |
| # `direct_tsd=1` (else `hot()` is itself calling `_tlv_get_addr` and the | |
| # mechanism is inert) and `claimed` above a floor no allocation | |
| # microbenchmark clears. Its `--self-test` drives all seven rejections | |
| # and runs on every PR, compiler-free, in the first job below. | |
| # | |
| # The one-time sabotage proof -- one hot declaration reverted to a raw | |
| # `thread_local!`, both budgets going red, restoring the macro restoring the | |
| # pass -- is recorded in the PR that introduced this file, matching how | |
| # gc-root-dominance and gc-parse-churn-gate treat theirs. | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: tls-budget-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| MACOSX_DEPLOYMENT_TARGET: "13.0" | |
| jobs: | |
| self-test-checkers: | |
| # Compiler-free and unconditional: the verdict logic must always be able to | |
| # say no, and the thread-local policy ratchet is cheap enough to run on | |
| # every PR regardless of what it touched. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Self-test the budget verdict logic | |
| run: python3 scripts/tls_budget_check.py --self-test | |
| - name: Self-test the thread-local policy checker | |
| run: python3 scripts/check_thread_locals.py --self-test | |
| - name: Enforce the thread-local policy ratchet | |
| run: python3 scripts/check_thread_locals.py | |
| tls-budget: | |
| # macos-14 is arm64. `_tlv_get_addr` is a Mach-O TLS artefact and the | |
| # direct thread-specific-data path in tls_hot.rs is Apple-aarch64 only, so | |
| # this measurement does not exist on any other platform -- the gate script | |
| # says so and exits 0 rather than pretending to measure. | |
| runs-on: macos-14 | |
| 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 thread-local access cost | |
| 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: any runtime change can add a thread-local to a | |
| # hot path, and the whole point of this gate is that such a change | |
| # does not announce itself. The filter exists only to spare docs-only | |
| # PRs a compiler build. | |
| if grep -qE '^(crates/|benchmarks/tls-budget/|scripts/tls_budget_(gate\.sh|check\.py)$|scripts/check_thread_locals\.py$|Cargo\.(toml|lock)$|\.github/workflows/tls-budget\.yml$)' changed.txt; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| echo "Change touches a path that can move thread-local access cost." | |
| 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-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Build perry and the runtime archives | |
| if: steps.relevance.outputs.run == 'true' | |
| run: | | |
| set -euo pipefail | |
| # perry-runtime and 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 measurement | |
| # vacuous (CLAUDE.md, "Verifying a runtime change"). | |
| cargo build --release -p perry -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 | |
| - name: Measure the `_tlv_get_addr` budget | |
| if: steps.relevance.outputs.run == 'true' | |
| env: | |
| PERRY_RUNTIME_DIR: ${{ github.workspace }}/target/release | |
| PERRY_NO_AUTO_OPTIMIZE: "1" | |
| run: scripts/tls_budget_gate.sh target/release/perry "${{ runner.temp }}/tls-budget" | |
| - name: Attach the profiles | |
| if: always() && steps.relevance.outputs.run == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tls-budget-profiles | |
| path: ${{ runner.temp }}/tls-budget/*.sample | |
| if-no-files-found: warn |