Skip to content

feat(codegen): make the in-process LLVM backend the default, statically linked - #7353

Merged
proggeramlug merged 1 commit into
mainfrom
feat/llvm-inprocess-default
Aug 4, 2026
Merged

feat(codegen): make the in-process LLVM backend the default, statically linked#7353
proggeramlug merged 1 commit into
mainfrom
feat/llvm-inprocess-default

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Makes the in-process LLVM backend the default, statically linked. Perry ships self-contained: we own the LLVM assumption instead of pushing it onto the user, and there is no "install a compatible clang" step left to get wrong.

This is load-bearing, not a preference. The explicit statepoint bridge is gone (#7348), so RS4GC is the only native-root backend — and RS4GC cannot round-trip its IR through an external opt plus a different clang (#7339). Keeping this opt-in meant the only working statepoint path lived behind a flag nobody sets.

Two defaults, flipped together

Either alone is half a feature — built-in but never used, or requested but not built.

  • llvm-inprocess becomes a default cargo feature.
  • inprocess_requested() defaults ON — but only iff the backend is compiled in.

That second qualifier is not pedantry. Defaulting to true unconditionally routes every compile in a --no-default-features build into the not-built-in stub and fails the build outright. I hit exactly that and it is now covered both ways:

build PERRY_LLVM_INPROCESS unset =0 =1
default in-process clang in-process
--no-default-features clang clang loud "rebuild with the feature"

CI

New .github/actions/setup-llvm22 composite action, referenced from all 44 toolchain steps across 18 workflows. One definition rather than 44 inline recipes, because the three platforms need three different sources and only one is obvious:

  • Linux — apt.llvm.org. Ubuntu 24.04's own llvm-dev is 18, so the distro cannot supply this.
  • Windows — the official clang+llvm-*-pc-windows-msvc tarball. Not chocolatey: its llvm package is the clang toolchain — no llvm-config.exe, none of the static libraries llvm-sys links — and it has no 22.x pin at all. (Learned the hard way; the feasibility spike's Windows cell failed on exactly this.)
  • macOS — brew.

Every arm asserts the major version, because llvm-sys 221 needs 22 specifically and a runner image moving its formula must fail loudly rather than build something subtly different several steps later.

The insertion is mechanical and auditable: 44 insertions, zero deletions.

Size

98.9 MB, not the 185.9 MB this would have cost before #7350initialize_all() was linking ~18 backends nothing can reach.

A bug the flip surfaced

PERRY_LLVM_KEEP_IR promises the whole scratch dir including the .o. The clang path got that for free because the object is a file; the in-process path returns bytes and silently dropped it — degrading a debugging aid at exactly the moment someone is debugging. Caught by keep_ir_retains_the_whole_scratch_dir, fixed rather than relaxed.

Verification

On the 81-module zod dependency corpus, with no env set: compiles, and output is byte-identical to the clang path. PERRY_RS4GC=1 now compiles a try-carrying probe with no further flags. 605 codegen tests pass.

Known and accepted

Compile time is ~75% higher on that corpus (7.1s → 12.3s). Same parallelism (4.4× both) and same opt level, so it is LLVM 22's default<O3> via PassBuilder versus Apple clang 21's driver-tuned -O3 — a tuning gap, not a design flaw. Accepted deliberately; the reliability and the statepoint path are worth it, and it is tunable later.

PERRY_LLVM_INPROCESS=native (function bodies built through the C API, no per-function text) stays opt-in: byte-identical objects on all 81 zod modules, but CI covers only two small programs so far.

Summary by CodeRabbit

  • New Features

    • In-process LLVM compilation is now enabled by default for supported builds.
    • LLVM 22 is consistently provisioned across build, test, coverage, benchmark, security, and release workflows.
    • Environment settings can still select the traditional external compiler path when needed.
    • Debug output can retain generated object files alongside intermediate compilation artifacts.
  • Documentation

    • Updated build requirements and configuration guidance to reflect LLVM 22 and the new default compilation mode.

…ly linked

Perry now links LLVM 22 statically and ships self-contained. We own the
assumption rather than pushing it onto the user, and there is no "install
a compatible clang" step left to get wrong.

It is load-bearing, not a preference. The explicit statepoint bridge is
gone (#7348), so RS4GC is the only native-root backend, and RS4GC cannot
round-trip its IR through an external `opt` plus a different clang
(#7339). Keeping this opt-in meant the only working statepoint path was
behind a flag nobody sets.

Two defaults flip together, because either alone is half a feature:

  * `llvm-inprocess` becomes a default cargo feature.
  * `inprocess_requested()` defaults to ON -- but only iff the backend is
    actually compiled in. Defaulting to `true` unconditionally would route
    every compile in a `--no-default-features` build into the
    not-built-in stub and fail it outright. Verified both ways.

`PERRY_LLVM_INPROCESS=0` reverts to the clang subprocess for bisection,
and `--no-default-features` still builds the text path.

CI: a new `.github/actions/setup-llvm22` composite action, referenced from
all 44 toolchain steps across 18 workflows. One definition rather than 44
inline recipes, because the three platforms need three different sources
and only one is obvious -- Ubuntu 24.04's own llvm-dev is 18, and
chocolatey's `llvm` is the clang toolchain with no llvm-config.exe and
none of the static libs. Every arm asserts the major version.

Size: 98.9 MB, not the 185.9 MB this would have cost before #7350 --
`initialize_all()` was linking ~18 backends nothing can reach.

Also fixed, surfaced by the flip: PERRY_LLVM_KEEP_IR promises the whole
scratch dir including the .o. The clang path got that free because the
object is a file; in-process returns bytes and silently dropped it,
degrading a debugging aid exactly when someone is debugging.

Verified on the 81-module zod corpus with no env set: compiles, output
byte-identical to the clang path, and PERRY_RS4GC=1 now compiles a
try-carrying probe with no further flags. 605 codegen tests pass.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

LLVM 22 in-process compilation

Layer / File(s) Summary
In-process backend default and object retention
crates/perry-codegen/Cargo.toml, crates/perry-codegen/src/linker.rs, crates/perry/Cargo.toml
In-process LLVM compilation is now the default when enabled. Explicit opt-outs remain supported. Successful object output is retained when PERRY_LLVM_KEEP_IR is enabled.
Cross-platform LLVM 22 setup
.github/actions/setup-llvm22/action.yml
A composite action installs and validates LLVM 22 on macOS, Linux, and Windows, then exports LLVM_SYS_221_PREFIX.
CI workflow rollout
.github/workflows/*.yml
Repository workflows now invoke the local LLVM 22 setup action after Rust toolchain installation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Workflow
  participant SetupLLVM22
  participant Compiler
  participant LLVM22
  Workflow->>SetupLLVM22: install LLVM 22
  SetupLLVM22->>LLVM22: validate version and export prefix
  Workflow->>Compiler: build with LLVM 22
  Compiler->>LLVM22: compile through in-process LLVM
  LLVM22-->>Compiler: return object bytes
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: andrewtdiz, thehypnoo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the primary change: making the in-process LLVM backend the default with static linking.
Description check ✅ Passed The description thoroughly covers the change, rationale, CI updates, verification, performance impact, and known limitations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/llvm-inprocess-default

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/actions/setup-llvm22/action.yml:
- Around line 47-48: Update the LLVM setup steps around the apt.llvm.org
signing-key command to use a repository-scoped keyring rather than
/etc/apt/trusted.gpg.d, validate the downloaded key against the committed
expected fingerprint before apt-get update, and configure the apt source with
deb [signed-by=...] pointing to that keyring. Remove reliance on globally
trusted APT metadata while preserving LLVM package installation.

In @.github/workflows/gc-moving-witnesses.yml:
- Line 200: Apply the relevance condition to both LLVM setup steps: add the
existing steps.relevance.outputs.run == 'true' job condition to
.github/workflows/gc-moving-witnesses.yml at lines 200-200 and
.github/workflows/gc-ratchet.yml at lines 118-118, so each setup runs only for
relevant pull requests.

In @.github/workflows/gc-native-roots.yml:
- Line 127: Remove the later macOS override that assigns the LLVM prefix from
brew, and retain the validated prefix exported by setup-llvm22. Ensure the
in-process build uses LLVM_SYS_221_PREFIX consistently.

In @.github/workflows/test.yml:
- Line 118: Remove the setup-llvm22 action from the formatting-only lint job in
.github/workflows/test.yml:118-118, and from the cargo audit job at
.github/workflows/security-audit.yml:26-26 and cargo-deny job at
.github/workflows/security-audit.yml:159-159; leave LLVM provisioning unchanged
for jobs that compile Rust.
- Line 808: Condition the setup-llvm22 step in .github/workflows/test.yml at
lines 808-808 on steps.scope.outputs.suites being non-empty. In
.github/workflows/container-tests.yml at lines 172-172, move the apple/container
availability probe before the Rust and LLVM setup steps so unavailable jobs skip
provisioning; update both workflow sites accordingly.

In `@crates/perry-codegen/src/linker.rs`:
- Around line 601-609: Align the self-contained LLVM documentation with the
actual backend behavior: in crates/perry-codegen/src/linker.rs (lines 601-609),
document the keep/failure paths that write .ll files and the statepoint path
that still requires system clang, or remove that external assembler dependency;
update the feature requirements and setup guidance in
crates/perry-codegen/Cargo.toml (lines 12-20) accordingly; and qualify the
“ships self-contained” claim in crates/perry/Cargo.toml (lines 143-146) unless
the backend is made fully self-contained.
- Around line 721-739: Update the policy.keep branch in the Ok(bytes) arm to
propagate failures from fs::create_dir_all and fs::write instead of discarding
or merely logging them, so the function does not return Ok(bytes) when requested
object retention fails. Preserve the existing success logging and returned bytes
on successful retention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8492b0a3-d5ee-40e0-9245-2cf10e771d96

📥 Commits

Reviewing files that changed from the base of the PR and between 2b64e7b and e592c2b.

📒 Files selected for processing (22)
  • .github/actions/setup-llvm22/action.yml
  • .github/workflows/benchmark.yml
  • .github/workflows/cache-warm.yml
  • .github/workflows/container-tests.yml
  • .github/workflows/coverage.yml
  • .github/workflows/eh-transport.yml
  • .github/workflows/feature-matrix.yml
  • .github/workflows/gc-moving-witnesses.yml
  • .github/workflows/gc-native-roots.yml
  • .github/workflows/gc-ratchet.yml
  • .github/workflows/gc-root-dominance.yml
  • .github/workflows/node-compat-matrix.yml
  • .github/workflows/node-core-subset.yml
  • .github/workflows/node-suite-guard.yml
  • .github/workflows/npm-package-sweep.yml
  • .github/workflows/release-packages.yml
  • .github/workflows/security-audit.yml
  • .github/workflows/simctl-tests.yml
  • .github/workflows/test.yml
  • crates/perry-codegen/Cargo.toml
  • crates/perry-codegen/src/linker.rs
  • crates/perry/Cargo.toml

Comment on lines +47 to +48
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \
| sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc >/dev/null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== action file =="
cat -n .github/actions/setup-llvm22/action.yml | sed -n '1,90p'

echo
echo "== repository references to setup-llvm22 and apt.key trust path =="
rg -n "setup-llvm22|apt\.llvm\.org|trusted\.gpg\.d|keyring:|signed-by:|LLVM_SYS_221_PREFIX|apt-get update|apt-get install" .github crates/perry-codegen/Cargo.toml || true

echo
echo "== local gpg/apt behavior probe (no repos/keyring changes) =="
python3 - <<'PY'
from subprocess import run, PIPE
cmds = [
    ("gpg --version", ["gpg", "--version"]),
    ("apt apt-secure man snippet", ["apt", "show", "-A", "apt", "Security"]),
]
for label, cmd in cmds:
    print(f"\n-- {label} --")
    r = run(cmd, text=True, stdout=PIPE, stderr=PIPE, start_new_session=False)
    print(f"exit={r.returncode}")
    out = r.stdout.splitlines()[:20] + r.stderr.splitlines()[:10]
    print("\n".join(out))
PY

Repository: PerryTS/perry

Length of output: 11702


🌐 Web query:

Debian apt trusted.gpg.d global keyring signed-by apt-key apt-secure official documentation

💡 Result:

In modern Debian systems, the use of apt-key and the global /etc/apt/trusted.gpg and /etc/apt/trusted.gpg.d/ keyrings is deprecated and discouraged [1][2][3]. The recommended practice is to use the signed-by option in your APT repository configuration to explicitly associate a repository with its specific public GPG key [1][4][5]. This approach prevents a key trusted for one repository from being automatically trusted for all repositories on the system, significantly enhancing security [6][5]. Recommended Workflow 1. Obtain the GPG key and ensure it is in the correct format (binary.gpg or ASCII-armored.asc) [4][7][2]. 2. Place the key file in a dedicated, secure directory: - Use /etc/apt/keyrings/ for administrator-managed keys [1][7][5]. - Use /usr/share/keyrings/ for keys managed by packages (e.g., provided by a.deb archive) [4][8][5]. - Ensure the file has appropriate read permissions (e.g., chmod 644) [5]. 3. Configure your repository by adding the signed-by option to your source entry [1][6]: - For a legacy.list file: deb [signed-by=/etc/apt/keyrings/example.gpg] https://example.com/debian stable main - For modern.sources files (deb822 format), you can either reference the file or embed the ASCII-armored key directly within the file [1][4][7]. Key Differences and Deprecation - apt-key: This utility is deprecated and has been removed in recent Debian releases (e.g., Debian 13) [9][2]. It should no longer be used for managing repository keys [1][3]. - Global Keyrings (/etc/apt/trusted.gpg and /etc/apt/trusted.gpg.d/): These locations are deprecated [3]. While some systems may still support them for backward compatibility, they are considered insecure because they grant global trust to any key placed within them [3][6][5]. - Signed-By: This option binds a specific repository to a specific key, providing a granular and secure mechanism for verifying package authenticity [4][6][5]. For more information, consult the apt-secure(8) manual page on your system, which provides the authoritative details on repository security and key configuration [4][3].

Citations:


Security Misconfiguration (CWE-345)

Reachability: External

Pin and scope the apt signing key.

The APT signing key is fetched at runtime and stored under global trusted APT metadata, so a compromised endpoint can make future packages from this source appear verified. Store the key in a repository-scoped keyring, verify a committed fingerprint before apt-get update, and configure deb [signed-by=...] instead of relying on /etc/apt/trusted.gpg.d.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/setup-llvm22/action.yml around lines 47 - 48, Update the
LLVM setup steps around the apt.llvm.org signing-key command to use a
repository-scoped keyring rather than /etc/apt/trusted.gpg.d, validate the
downloaded key against the committed expected fingerprint before apt-get update,
and configure the apt source with deb [signed-by=...] pointing to that keyring.
Remove reliance on globally trusted APT metadata while preserving LLVM package
installation.

- name: Install Rust toolchain
if: steps.relevance.outputs.run == 'true'
uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/setup-llvm22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Apply the relevance condition to every LLVM setup step.

Both jobs gate their expensive work on steps.relevance.outputs.run, but the new LLVM setup step bypasses that gate. An irrelevant pull request can still perform external package installation and fail the CI job.

  • .github/workflows/gc-moving-witnesses.yml#L200-L200: add if: steps.relevance.outputs.run == 'true'.
  • .github/workflows/gc-ratchet.yml#L118-L118: add if: steps.relevance.outputs.run == 'true'.
Proposed fix
       - uses: ./.github/actions/setup-llvm22
+        if: steps.relevance.outputs.run == 'true'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: ./.github/actions/setup-llvm22
- uses: ./.github/actions/setup-llvm22
if: steps.relevance.outputs.run == 'true'
📍 Affects 2 files
  • .github/workflows/gc-moving-witnesses.yml#L200-L200 (this comment)
  • .github/workflows/gc-ratchet.yml#L118-L118
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/gc-moving-witnesses.yml at line 200, Apply the relevance
condition to both LLVM setup steps: add the existing steps.relevance.outputs.run
== 'true' job condition to .github/workflows/gc-moving-witnesses.yml at lines
200-200 and .github/workflows/gc-ratchet.yml at lines 118-118, so each setup
runs only for relevant pull requests.

with:
node-version-file: .node-version
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/setup-llvm22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve the validated LLVM 22 prefix.

Line 127 exports the prefix validated by setup-llvm22, but the later macOS build replaces it with brew --prefix llvm. That formula is unversioned and is not checked for major version. The in-process build can therefore use a different LLVM major. Remove the later override and use the action-provided LLVM_SYS_221_PREFIX.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/gc-native-roots.yml at line 127, Remove the later macOS
override that assigns the LLVM prefix from brew, and retain the validated prefix
exported by setup-llvm22. Ensure the in-process build uses LLVM_SYS_221_PREFIX
consistently.

uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: ./.github/actions/setup-llvm22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Remove LLVM provisioning from jobs that do not compile Rust.

The action adds an unnecessary privileged external dependency to non-compiling jobs. Remove it at each site:

  • .github/workflows/test.yml#L118-L118: remove setup from the formatting-only lint job.
  • .github/workflows/security-audit.yml#L26-L26: remove setup from cargo audit.
  • .github/workflows/security-audit.yml#L159-L159: remove setup from cargo-deny.
📍 Affects 2 files
  • .github/workflows/test.yml#L118-L118 (this comment)
  • .github/workflows/security-audit.yml#L26-L26
  • .github/workflows/security-audit.yml#L159-L159
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml at line 118, Remove the setup-llvm22 action from
the formatting-only lint job in .github/workflows/test.yml:118-118, and from the
cargo audit job at .github/workflows/security-audit.yml:26-26 and cargo-deny job
at .github/workflows/security-audit.yml:159-159; leave LLVM provisioning
unchanged for jobs that compile Rust.

- name: Install Rust toolchain
if: steps.scope.outputs.suites != ''
uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/setup-llvm22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not provision LLVM before conditional jobs confirm execution.

Prevent the setup action from running when the job will skip its compile path:

  • .github/workflows/test.yml#L808-L808: add if: steps.scope.outputs.suites != ''.
  • .github/workflows/container-tests.yml#L172-L172: move the apple/container availability probe before Rust and LLVM setup.
📍 Affects 2 files
  • .github/workflows/test.yml#L808-L808 (this comment)
  • .github/workflows/container-tests.yml#L172-L172
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml at line 808, Condition the setup-llvm22 step in
.github/workflows/test.yml at lines 808-808 on steps.scope.outputs.suites being
non-empty. In .github/workflows/container-tests.yml at lines 172-172, move the
apple/container availability probe before the Rust and LLVM setup steps so
unavailable jobs skip provisioning; update both workflow sites accordingly.

Comment on lines +601 to +609
/// Route `.ll -> .o` through the LLVM C API inside this process (no clang
/// subprocess, no `.ll` on disk).
///
/// **ON BY DEFAULT.** Perry links LLVM 22 statically and ships self-contained,
/// so there is no "find a compatible clang" step to get wrong. It is also
/// load-bearing rather than a preference: the explicit statepoint bridge is
/// gone (#7348), leaving RS4GC as the only native-root backend, and RS4GC
/// cannot round-trip its IR through an external `opt` + a different clang
/// (#7339).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Align the self-contained documentation with the implementation.

The documentation describes an unconditional LLVM-only path. The implementation writes .ll files for keep and failure modes, and it still requires system clang for statepoint assembly. A documented LLVM-only setup can therefore fail during compilation.

  • crates/perry-codegen/src/linker.rs#L601-L609: qualify the no-.ll and no-clang claims with the keep, failure, and statepoint exceptions, or remove the external assembler step.
  • crates/perry-codegen/Cargo.toml#L12-L20: update the feature requirements and setup instructions to match the actual exceptions.
  • crates/perry/Cargo.toml#L143-L146: qualify the “ships self-contained” statement or make the backend fully self-contained.
📍 Affects 3 files
  • crates/perry-codegen/src/linker.rs#L601-L609 (this comment)
  • crates/perry-codegen/Cargo.toml#L12-L20
  • crates/perry/Cargo.toml#L143-L146
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/linker.rs` around lines 601 - 609, Align the
self-contained LLVM documentation with the actual backend behavior: in
crates/perry-codegen/src/linker.rs (lines 601-609), document the keep/failure
paths that write .ll files and the statepoint path that still requires system
clang, or remove that external assembler dependency; update the feature
requirements and setup guidance in crates/perry-codegen/Cargo.toml (lines 12-20)
accordingly; and qualify the “ships self-contained” claim in
crates/perry/Cargo.toml (lines 143-146) unless the backend is made fully
self-contained.

Comment on lines +721 to +739
Ok(bytes) => {
// `PERRY_LLVM_KEEP_IR` promises the whole scratch dir, `.o`
// included. The clang path gets that for free because the object
// IS a file; in-process returns bytes and would silently drop it —
// degrading a debugging aid at exactly the moment someone is
// debugging. Now that this backend is the default, write it.
if policy.keep {
let _ = fs::create_dir_all(&paths.scratch_dir);
if let Err(e) = fs::write(&plan.obj_path, &bytes) {
eprintln!(
"[perry-codegen] could not keep {}: {e}",
plan.obj_path.display()
);
} else {
eprintln!("[perry-codegen] kept object: {}", plan.obj_path.display());
}
}
Ok(bytes)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not silently lose a requested object.

When policy.keep is true, create_dir_all failures are discarded and object-write failures are only logged. The function still returns Ok(bytes). PERRY_LLVM_KEEP_IR can therefore succeed without retaining the .o file that its comment promises. Propagate retention errors, or explicitly define retention as best effort and test that contract.

Strict retention variant
             if policy.keep {
-                let _ = fs::create_dir_all(&paths.scratch_dir);
-                if let Err(e) = fs::write(&plan.obj_path, &bytes) {
-                    eprintln!(
-                        "[perry-codegen] could not keep {}: {e}",
-                        plan.obj_path.display()
-                    );
-                } else {
-                    eprintln!("[perry-codegen] kept object: {}", plan.obj_path.display());
-                }
+                fs::create_dir_all(&paths.scratch_dir)
+                    .with_context(|| format!("Failed to create {}", paths.scratch_dir.display()))?;
+                fs::write(&plan.obj_path, &bytes)
+                    .with_context(|| format!("Failed to keep {}", plan.obj_path.display()))?;
+                eprintln!("[perry-codegen] kept object: {}", plan.obj_path.display());
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Ok(bytes) => {
// `PERRY_LLVM_KEEP_IR` promises the whole scratch dir, `.o`
// included. The clang path gets that for free because the object
// IS a file; in-process returns bytes and would silently drop it —
// degrading a debugging aid at exactly the moment someone is
// debugging. Now that this backend is the default, write it.
if policy.keep {
let _ = fs::create_dir_all(&paths.scratch_dir);
if let Err(e) = fs::write(&plan.obj_path, &bytes) {
eprintln!(
"[perry-codegen] could not keep {}: {e}",
plan.obj_path.display()
);
} else {
eprintln!("[perry-codegen] kept object: {}", plan.obj_path.display());
}
}
Ok(bytes)
}
Ok(bytes) => {
// `PERRY_LLVM_KEEP_IR` promises the whole scratch dir, `.o`
// included. The clang path gets that for free because the object
// IS a file; in-process returns bytes and would silently drop it —
// degrading a debugging aid at exactly the moment someone is
// debugging. Now that this backend is the default, write it.
if policy.keep {
fs::create_dir_all(&paths.scratch_dir)
.with_context(|| format!("Failed to create {}", paths.scratch_dir.display()))?;
fs::write(&plan.obj_path, &bytes)
.with_context(|| format!("Failed to keep {}", plan.obj_path.display()))?;
eprintln!("[perry-codegen] kept object: {}", plan.obj_path.display());
}
Ok(bytes)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/linker.rs` around lines 721 - 739, Update the
policy.keep branch in the Ok(bytes) arm to propagate failures from
fs::create_dir_all and fs::write instead of discarding or merely logging them,
so the function does not return Ok(bytes) when requested object retention fails.
Preserve the existing success logging and returned bytes on successful
retention.

@proggeramlug
proggeramlug merged commit 2494595 into main Aug 4, 2026
29 of 50 checks passed
@proggeramlug
proggeramlug deleted the feat/llvm-inprocess-default branch August 4, 2026 08:16
proggeramlug added a commit that referenced this pull request Aug 4, 2026
…7357)

Measured on main, last ~10 runs each:

    security-audit    9 cancelled, 0 success   <- REQUIRED context
    eh-transport      8 cancelled, 0 success
    llvm-inprocess    8 cancelled, 0 success
    gc-moving-witness 0 cancelled, 2 success   <- already fixed

Two of the three had a comment claiming they were already safe:
"cancel superseded PR runs, never main runs -- a busy merge day would
otherwise starve the gate to zero executions." A busy merge day starved
them anyway, by a mechanism the comment did not anticipate.

 is not sufficient. GitHub allows at most one
PENDING run per concurrency group and cancels the previously pending one
when a new run enters, regardless of that setting. With the group keyed
on , every main push shares one group, so a merge burst
cancels the intermediate runs. gc-moving-witnesses already carries both
the diagnosis and the fix (#7205); this applies it to the three that
still had the old shape. security-audit additionally had
, which cancels main runs outright.

Keying push runs on the SHA gives every merged commit its own group.

This is CLAUDE.md hazard 3, and it is worth noting how it was found: not
by reading the config -- two of these LOOKED correct -- but by asking
what each workflow's main runs actually concluded. llvm-inprocess is the
gate for the backend #7353 just made the default, and it had never
executed on main.

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant