Skip to content

fix(hir): route node-core module spread calls through the variadic dispatch #1084

fix(hir): route node-core module spread calls through the variadic dispatch

fix(hir): route node-core module spread calls through the variadic dispatch #1084

Workflow file for this run

# 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
# 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:
push:
branches: [main]
concurrency:
group: llvm-inprocess-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}
# One group per main COMMIT, cancelling PR runs only. `cancel-in-progress:
# false` alone does not protect a `main` run: GitHub allows at most one
# PENDING run per group and cancels the previously pending one when a new run
# enters, regardless of that setting. That is #7205 — measured here as EIGHT
# consecutive `main` runs cancelled, zero executions. Keying push runs on the
# SHA gives every merged commit a group of its own.
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:
- 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 "dialect::tests::corpus_exception_handling ... 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
# #7302: exception handling. try/catch lowers to invoke/landingpad
# with a personality on the define, so a reader that cannot build
# those forms silently loses every try-containing module to the
# textual path — which is exactly how this arm went red once the
# EH migration landed. Assert the EH program takes the native path
# AND behaves identically.
EH=test-files/test_gap_7302_invoke_eh_paths.ts
"$BIN" "$EH" -o /tmp/eh_text
/tmp/eh_text > /tmp/eh_text.out
PERRY_LLVM_INPROCESS=native "$BIN" "$EH" -o /tmp/eh_native 2> /tmp/eh_native.err
grep -q "in-process LLVM backend active" /tmp/eh_native.err
/tmp/eh_native > /tmp/eh_native.out
cmp /tmp/eh_text.out /tmp/eh_native.out
PERRY_LLVM_INPROCESS=diff "$BIN" "$EH" -o /tmp/eh_diff 2> /tmp/eh_diff.err
grep -q "ir-diff. OK" /tmp/eh_diff.err