From 2975059fd553a5d20d7de1aec895f5d12edc2295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 3 Aug 2026 12:38:29 +0200 Subject: [PATCH 1/3] ci: exercise the in-process LLVM backend (kill-policy arm for #7301) Every shipped mode needs a CI arm or deletion; this is the arm for PERRY_LLVM_INPROCESS. macOS runner, LLVM 22 pinned with a loud drift check, feature build, the 528-test suite with the corpus/RS4GC gates asserted to have RUN (not skipped), and a native-mode smoke that asserts liveness, behavior parity, and both object-byte diff verdicts (single module + forced 3-unit split). Non-required until first green, per the new-gate corollary; concurrency never cancels main runs (gate-trap 3). Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo --- .github/workflows/llvm-inprocess.yml | 86 ++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/llvm-inprocess.yml diff --git a/.github/workflows/llvm-inprocess.yml b/.github/workflows/llvm-inprocess.yml new file mode 100644 index 0000000000..11cb932bbe --- /dev/null +++ b/.github/workflows/llvm-inprocess.yml @@ -0,0 +1,86 @@ +# CI arm for the in-process LLVM backend (#7241, merged in #7301). +# +# The GC knob kill-policy (CLAUDE.md) demands every shipped mode be exercised +# by CI or deleted; this job is the exercise for `PERRY_LLVM_INPROCESS`. +# Each step asserts its subject was LIVE (liveness line, diff verdict) rather +# than merely that nothing threw — see "Four ways a gate can be unable to +# fail". NON-REQUIRED until it has run green once; promote afterwards +# (a new gate has never been green, so promoting first blocks every PR). +name: llvm-inprocess + +on: + pull_request: + paths: + - "crates/perry-codegen/**" + - "crates/perry/src/commands/compile/**" + - "experiments/llvm-inprocess-spike/**" + - ".github/workflows/llvm-inprocess.yml" + push: + branches: [main] + paths: + - "crates/perry-codegen/**" + - "crates/perry/src/commands/compile/**" + - "experiments/llvm-inprocess-spike/**" + - ".github/workflows/llvm-inprocess.yml" + +concurrency: + group: llvm-inprocess-${{ github.ref }} + # Gate-trap 3: cancel superseded PR runs, but NEVER cancel main runs — a + # busy merge day would otherwise starve the gate to zero executions. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + native-backend: + runs-on: macos-15 + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + + - name: Install LLVM 22 (pinned major — fail loudly on drift) + run: | + set -euo pipefail + brew install llvm@22 2>/dev/null || brew install llvm + PREFIX="$(brew --prefix llvm@22 2>/dev/null || brew --prefix llvm)" + # llvm-sys 221 requires major 22. If the runner's formula moves on, + # this must go red, not quietly build something else. + "$PREFIX/bin/llvm-config" --version | grep -q '^22\.' + echo "LLVM_SYS_221_PREFIX=$PREFIX" >> "$GITHUB_ENV" + + - name: Build with the llvm-inprocess feature + run: | + cargo build --profile perry-dev -p perry -p perry-runtime-static \ + -p perry-stdlib-static --features perry/llvm-inprocess + + - name: Unit gates (528 incl. corpus construction + RS4GC pin) + run: | + set -euo pipefail + out=$(cargo test --profile perry-dev -p perry-codegen \ + --features llvm-inprocess --lib 2>&1) || { echo "$out"; exit 1; } + # The corpus gates must have RUN, not skipped: a checkout missing + # the tracked .ll corpora would otherwise green vacuously. + echo "$out" | grep -q "dialect::tests::corpus_spike ... ok" + echo "$out" | grep -q "dialect::tests::corpus_batch_kernel ... ok" + echo "$out" | grep -q "inprocess::tests::rs4gc_schedules_in_process ... ok" + + - name: Native-mode smoke — liveness, behavior parity, object-byte verdicts + run: | + set -euo pipefail + export PERRY_RUNTIME_DIR="$PWD/target/perry-dev" + export PERRY_NO_AUTO_OPTIMIZE=1 + BIN=target/perry-dev/perry + SRC=experiments/llvm-inprocess-spike/spike.ts + + "$BIN" "$SRC" -o /tmp/spike_text + /tmp/spike_text > /tmp/text.out + + PERRY_LLVM_INPROCESS=native "$BIN" "$SRC" -o /tmp/spike_native 2> /tmp/native.err + grep -q "in-process LLVM backend active" /tmp/native.err + /tmp/spike_native > /tmp/native.out + cmp /tmp/text.out /tmp/native.out + + PERRY_LLVM_INPROCESS=diff "$BIN" "$SRC" -o /tmp/spike_diff 2> /tmp/diff.err + grep -q "ir-diff. OK" /tmp/diff.err + + PERRY_LLVM_INPROCESS=diff PERRY_CODEGEN_UNITS=3 "$BIN" \ + benchmarks/app-patterns/kernels/batch.ts -o /tmp/batch_diff 2> /tmp/diffu.err + grep -q "ir-diff. OK.*3 units" /tmp/diffu.err From 3419cd7edbc1b80324cafa0142f0b0f665bc8ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 3 Aug 2026 12:38:58 +0200 Subject: [PATCH 2/3] ci: changelog fragment for #7304 Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo --- changelog.d/7304-llvm-inprocess-ci-arm.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog.d/7304-llvm-inprocess-ci-arm.md diff --git a/changelog.d/7304-llvm-inprocess-ci-arm.md b/changelog.d/7304-llvm-inprocess-ci-arm.md new file mode 100644 index 0000000000..617f513692 --- /dev/null +++ b/changelog.d/7304-llvm-inprocess-ci-arm.md @@ -0,0 +1,7 @@ +CI arm for the in-process LLVM backend (#7301 follow-up, kill-policy): a +path-filtered macOS job builds the `llvm-inprocess` feature against a +loudly-pinned LLVM 22, runs the perry-codegen suite asserting the corpus +and RS4GC gates actually ran, and smokes `PERRY_LLVM_INPROCESS=native` +end-to-end — liveness line, behavior parity with the text arm, and +object-byte `=diff` verdicts for a single module and a forced 3-unit +split. Lands non-required; promote after first green. From 2b80b88cb82a8fcea428251466d2c5caf72f5acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 3 Aug 2026 12:43:48 +0200 Subject: [PATCH 3/3] =?UTF-8?q?ci:=20job-level=20relevance=20filter=20?= =?UTF-8?q?=E2=80=94=20promotion-safe=20required=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trigger-level paths on a required check never create a check run for out-of-scope PRs, wedging the required context at 'waiting' forever (CodeRabbit on #7304 — the valid half). Filtering moves to a cheap ubuntu job querying the PR files API; a skipped native-backend job still reports a check run, which branch protection accepts. batch.ts joins the relevant set (the smoke consumes it). Stays non-required until first green per the new-gate corollary — the sequencing half of the suggestion is declined, with this comment as the reason. Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo --- .github/workflows/llvm-inprocess.yml | 45 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/llvm-inprocess.yml b/.github/workflows/llvm-inprocess.yml index 11cb932bbe..4e76b87185 100644 --- a/.github/workflows/llvm-inprocess.yml +++ b/.github/workflows/llvm-inprocess.yml @@ -8,20 +8,16 @@ # (a new gate has never been green, so promoting first blocks every PR). name: llvm-inprocess +# No trigger-level `paths:` — deliberately. A required check whose workflow +# is path-filtered at the trigger never CREATES a check run for PRs outside +# those paths, so the required context sits "waiting" forever and blocks the +# merge (the promotion trap CodeRabbit flagged on #7304). Filtering lives in +# the `changes` job instead: a job skipped by `if:` still reports a check +# run (conclusion: skipped), which branch protection accepts. on: pull_request: - paths: - - "crates/perry-codegen/**" - - "crates/perry/src/commands/compile/**" - - "experiments/llvm-inprocess-spike/**" - - ".github/workflows/llvm-inprocess.yml" push: branches: [main] - paths: - - "crates/perry-codegen/**" - - "crates/perry/src/commands/compile/**" - - "experiments/llvm-inprocess-spike/**" - - ".github/workflows/llvm-inprocess.yml" concurrency: group: llvm-inprocess-${{ github.ref }} @@ -30,7 +26,36 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + # Cheap relevance filter via the PR files API (no checkout, no third-party + # action). Pushes to main always run — main executions are the gate's + # anchor and the promotion prerequisite. + changes: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + pull-requests: read + outputs: + relevant: ${{ steps.filter.outputs.relevant }} + steps: + - id: filter + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "${{ github.event_name }}" = "push" ]; then + echo "relevant=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + files=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --paginate --jq '.[].filename') + if echo "$files" | grep -qE '^(crates/perry-codegen/|crates/perry/src/commands/compile/|experiments/llvm-inprocess-spike/|benchmarks/app-patterns/kernels/batch\.ts$|\.github/workflows/llvm-inprocess\.yml$)'; then + echo "relevant=true" >> "$GITHUB_OUTPUT" + else + echo "relevant=false" >> "$GITHUB_OUTPUT" + fi + native-backend: + needs: changes + if: needs.changes.outputs.relevant == 'true' runs-on: macos-15 timeout-minutes: 90 steps: