Skip to content

perf(gc): pre-register typed shape layouts #975

perf(gc): pre-register typed shape layouts

perf(gc): pre-register typed shape layouts #975

Workflow file for this run

# ---------------------------------------------------------------------------
# ext-link — per-PR LINK check for the `perry-ext-*` crates (#7656)
#
# A `perry-runtime` change broke the link of five `perry-ext-*` crates and no
# per-PR gate could have caught it (#7650, fixed in #7655); it surfaced at the
# next tag, days later.
#
# Why `cargo-test`'s scope cannot see this: `ci_test_scope.py` selects a
# reverse-dependency closure, and `_is_fanout_leaf` deliberately keeps
# `perry-ext-*` / `perry-stdlib` OUT of the fan-out. That is correct on its own
# terms — their unit tests are self-contained pure-Rust logic, and re-running
# ~40 crates on every foundational change is the cost the scoping exists to
# avoid. What it misses is that for these crates the coupling is the LINK, not
# the test: they pull in a feature-stripped runtime through `perry-ffi`'s
# `runtime-link` built with `-Wl,-dead_strip`, so a new reference edge inside
# `perry-runtime` can keep alive a chain the stripper had been removing. In
# #7650 that edge was one added call (`pin_object` -> `arena::classify_heap_space`)
# in code that had previously done a raw flag write, and the symptom was
# `Undefined symbols for architecture arm64`.
#
# So this job BUILDS and does not RUN: `cargo test --no-run` links the test
# binaries and stops. Running them would add time and check nothing this does
# not already.
#
# `--release` deliberately: `-dead_strip` is what makes the failure, and it is
# a release-profile behaviour. A dev-profile build links a different set and
# would be green through exactly the regression this exists to catch.
# ---------------------------------------------------------------------------
name: ext-link
on:
pull_request:
# PR arm is OPT-IN via the `run-extended-tests` label (see the header of
# test.yml and docs/src/testing/ci-tiers.md): an unlabelled PR still gets
# a run, but every job in it is skipped, which costs no runner slot. The
# main-line arm (schedule / tags) is unchanged. `labeled` re-fires the run
# when the label lands.
types: [opened, synchronize, reopened, labeled]
# Main-line arm: nightly + release tags. It used to have none -- the PR arm
# was the only execution and it was red on every PR (#8155) at 90-130 min a
# run. Staggered off the six-hourly GC gates' minutes.
schedule:
- cron: "52 4 * * *"
push:
tags: ["v*"]
workflow_dispatch:
concurrency:
# PR runs supersede each other; a manual dispatch is never cancelled.
group: ext-link-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
ext-link:
# PR arm is opt-in (label `run-extended-tests`); see the `on:` block.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-extended-tests')
runs-on: ubuntu-latest
# Measured on an arm64 dev Mac. COLD release target, the five crates from
# #7650 only: 7:24, 7 test binaries. All 38 ext crates with perry-runtime
# already built: 4:10, 216 test binaries. The shared `perry-runtime` release
# build dominates, so covering every ext crate costs LESS than building the
# runtime once — which is why the scope is all of them rather than the five
# that happened to fail in #7650.
#
# The bound is a backstop, not a budget: it has to cover a cold sccache
# building perry-runtime in release from scratch on a shared runner, which
# is far slower than the numbers above, while still cutting a true hang.
timeout-minutes: 120
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "false"
SCCACHE_DIR: ${{ github.workspace }}/.sccache
SCCACHE_CACHE_SIZE: "8G"
CARGO_INCREMENTAL: "0"
steps:
# This job compiles PR-controlled build scripts; don't leave the workflow
# token in .git/config for them to read.
- uses: actions/checkout@v7
with:
persist-credentials: false
# Cheap gate: no toolchain, no cargo, no cache restore. Every step below
# is skipped when the diff cannot change what the archives link.
- name: Compute ext-link scope
id: scope
env:
GH_TOKEN: ${{ github.token }}
run: |
python3 scripts/ci_ext_link_scope.py --self-test
if [ "${{ github.event_name }}" = "pull_request" ]; then
changed_files="$(gh pr view "${{ github.event.pull_request.number }}" \
--json files --jq '.files[].path')"
else
# Manual dispatch: check everything.
changed_files="crates/perry-runtime/src/lib.rs"
fi
pkgs="$(printf '%s\n' "$changed_files" | python3 scripts/ci_ext_link_scope.py)"
if [ -z "$pkgs" ]; then
echo "Diff cannot change what the ext archives link — nothing to do."
echo "pkgs=" >> "$GITHUB_OUTPUT"
else
echo "Linking $(printf '%s\n' "$pkgs" | wc -l) perry-ext-* crates:"
printf '%s\n' "$pkgs"
{
echo 'pkgs<<PERRY_EOF'
printf '%s\n' "$pkgs"
echo 'PERRY_EOF'
} >> "$GITHUB_OUTPUT"
fi
- name: Install Rust toolchain
if: steps.scope.outputs.pkgs != ''
uses: dtolnay/rust-toolchain@stable
- if: steps.scope.outputs.pkgs != ''
uses: ./.github/actions/setup-llvm22
- name: Install sccache
if: steps.scope.outputs.pkgs != ''
uses: mozilla-actions/sccache-action@v0.0.11
- name: Cache sccache objects
if: steps.scope.outputs.pkgs != ''
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
if: steps.scope.outputs.pkgs != ''
with:
shared-key: "${{ runner.os }}-perry"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Link the ext crates
if: steps.scope.outputs.pkgs != ''
env:
# lld has repeatedly SIGBUS'd large links on the shared runner.
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C linker-features=-lld"
CARGO_PROFILE_TEST_DEBUG: "0"
CARGO_BUILD_JOBS: "1"
PKGS: ${{ steps.scope.outputs.pkgs }}
run: |
# Explicit rather than relying on the runner's default `bash -e`: a
# failing `cargo test --no-run` IS the regression this job exists to
# catch, so its exit status must abort the step under any shell.
set -euo pipefail
# `--message-format=json` so the count below comes from cargo rather
# than being inferred from exit status. The counting lives in the
# script (`--count-linked`) so this file carries no inline Python —
# a heredoc at column 0 silently breaks the YAML block scalar.
#
# First check each package independently. This catches undeclared
# feature dependencies (for example Argon2 relying on another package
# to enable rand_core/getrandom) without paying for a full release
# link per package.
default_args=()
fetch_args=()
ws_args=()
while read -r pkg; do
[ -n "$pkg" ] || continue
echo "Checking ${pkg}'s independent feature graph..."
cargo check --release -p "$pkg"
case "$pkg" in
perry-ext-fetch)
fetch_args+=(-p "$pkg")
;;
perry-ext-fastify|perry-ext-ws)
ws_args+=(-p "$pkg")
;;
*)
default_args+=(-p "$pkg")
;;
esac
done <<< "$PKGS"
# Release-link packages in three feature-compatible groups. A single
# all-package command lets perry-ext-fetch's
# `external-fetch-symbols` and the WS providers' corresponding mode
# leak into unrelated test binaries, which then expect provider
# symbols they correctly do not link. Three groups preserve the real
# feature boundary while compiling the expensive runtime only a few
# times, keeping the gate inside its measured 120-minute backstop.
: > /tmp/ext-link.json
run_link_group() {
local group="$1"
shift
[ "$#" -gt 0 ] || return 0
local group_json="/tmp/ext-link-${group}.json"
echo "Linking ${group} feature group..."
set +e
cargo test --release --no-run --message-format=json "$@" > "$group_json"
local cargo_status=$?
set -e
cat "$group_json" >> /tmp/ext-link.json
if [ "$cargo_status" -ne 0 ]; then
jq -r 'select(.reason == "compiler-message") | .message.rendered // empty' \
"$group_json" >&2
exit "$cargo_status"
fi
}
run_link_group default "${default_args[@]}"
run_link_group fetch "${fetch_args[@]}"
run_link_group ws "${ws_args[@]}"
linked="$(python3 scripts/ci_ext_link_scope.py --count-linked /tmp/ext-link.json)"
echo "linked test binaries: $linked"
if [ "$linked" -eq 0 ]; then
echo "::error::ext-link linked ZERO test binaries. The gate ran but had no subject — either the scope selected no packages or cargo built nothing. Fix the scope rather than trusting this green."
exit 1
fi