Skip to content

In-process LLVM backend: native construction via the C API, opt-in (#7241, engine-plan layer 0) - #7301

Merged
proggeramlug merged 26 commits into
mainfrom
exp/llvm-inprocess
Aug 3, 2026
Merged

In-process LLVM backend: native construction via the C API, opt-in (#7241, engine-plan layer 0)#7301
proggeramlug merged 26 commits into
mainfrom
exp/llvm-inprocess

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

In-process LLVM backend: native construction, opt-in (#7241, engine-plan layer 0)

With the llvm-inprocess cargo feature built in and PERRY_LLVM_INPROCESS=native, Perry constructs LLVM modules through the C API against pinned LLVM 22 — no module-scale IR text, no .ll on disk, no clang subprocess, no dependence on the user's toolchain. PERRY_SAVE_LL / --trace llvm / PERRY_LLVM_KEEP_IR print the constructed module on demand as the debug view. The default build is byte-for-byte unchanged and links no LLVM (proof below). Do not merge without maintainer sequencing — this is layer 0 of docs/engine-plan.md; layer 2 (statepoints, #7174) is blocked on it.

Full experiment record: docs/llvm-inprocess-experiment.md + issue #7241 (Phase 0 answers, corpus censuses, the two audits, every bug found and fixed along the way).

What's in the PR

  • Typed instruction storage (inst.rs): all 68 LlBlock semantic methods emit typed LlInst variants; renderers are byte-identical to the replaced format! strings (pinned by the 528-test suite). Raw remains for the 89 bespoke emit_raw sites + entry-splice strings; per-module (typed, raw) counts are the migration ratchet.
  • Finalized-item visitor (LlFunction::for_each_final_item): one implementation of finalized order + return-site rewrites, shared by to_ir (text) and the native builder — the two consumers cannot drift.
  • Dialect reader + typed dispatch (dialect.rs): typed instructions build directly (no text); Raw lines go through a bounded, loud-failing parser for exactly perry's dialect. Placeholder-RAUW for non-phi forward references; callsite-typed calls (opaque-ptr semantics); llvm.* intrinsics auto-declared at use (mirroring LLParser); !invariant.load honored on both paths.
  • Codegen-unit splits are native (compile_module_units_native): per-unit context+module (same peak-RSS bound as the per-unit clang model), internal definitions promoted exactly like render_fn_external, ld -r merge shared with the text path. The only text fallthrough left is emit_ir_only, which by definition wants text.
  • =diff harness: builds both backends in the same LLVM and byte-compares emitted objects (pre-opt prints can't compare — the C-API builder constant-folds at construction); PERRY_LLVM_DIFF_DIR dumps both arms. There is no "benign divergence" class: byte mismatch = bug.
  • Layer-2 readiness pinned by test: rs4gc_schedules_in_process proves rewrite-statepoints-for-gc runs as a module pass in-process at the pinned LLVM, emits the statepoint, and relocates the live addrspace(1) pointer. gc: RewriteStatepointsForGC over managed-pointer SSA — the measured-only road to native-root file-size parity #7174 can proceed on this pipeline.
  • Caches: PERRY_LLVM_INPROCESS participates in build- and object-cache keys; a featureless build fails loudly under the flag; first in-process compile prints a liveness line.

Evidence

  • Object-level parity: =diff byte-identical on spike.ts (21,193 B) and the batch.ts kernel (60,089 B single-module; 56,296 B as a forced 3-unit split).

  • Full gap suite under =native (macOS/arm64, one binary, flag-only A/B, per-compile liveness asserted): 459/466 byte-identical outputs (stdout+stderr+exit), 466/466 compiles proven live, 0 compile failures (cold caches, resumable-harness accounting: 0 resumed, 0 infra-excluded). The 7 residual DIFFs are pre-existing runtime noise, each verified to reproduce between two runs of the same arm: 5× perry-ext-http server.rs:911 SIGABRT + 1× tokio listener panic (stderr embeds the OS thread id), 1× console.time wall-clock jitter.

  • Default unchanged, by object comparison: featureless branch-head vs merge-base (9cb31f1b0) compilers (separate worktrees, separate target dirs, identical package sets, binaries hash-verified different) over a 12-file corpus incl. a forced 3-unit split and a multi-module compile: 10/12 emitted .ll byte-identical outright; the 2 others differ only in js_register_function_name pair order — a pre-existing run-to-run coin (flips between two runs of a single binary at merge-base; cross-arm runs match when the coin agrees). Side-finding relevant to the codegen: object emission is nondeterministic on Linux — the LLVM temp module name embeds pid + nanotime #7131 determinism work, filed in the issue.

  • 528 perry-codegen unit tests (incl. two full-corpus construction gates and the RS4GC pin) + 881 perry unit tests green; cargo fmt --all -- --check clean; the four lint-gate scripts report identically at the merge-base (no delta from this branch).

  • Linux verified on-branch (d3b3c4607): Fedora 43 / x86_64 / glibc / LLVM 22.1.8 — full gap A/B 459/466 SAME, 0 compile failures, 466/466 live, same 7 noise families each re-proven against a single arm; =diff byte-identical objects on ELF as on Mach-O. -fno-math-errno closed: Perry never emits named libm callees (only errno-free llvm.* intrinsics and js_math_* runtime helpers), and objects measure byte-identical with/without the flag under clang 22 and clang 19.

Not verified here (explicitly)

Costs

LLVM 22 dev libs required only when building the feature (LLVM_SYS_221_PREFIX); ~+171 MB on a static-linked perry-dev binary when enabled; zero cost by default. cl::opt passthrough state is process-global (documented; one env-derived value per process today).

Summary by CodeRabbit

  • New Features

    • Added an opt-in in-process LLVM 22 compilation backend.
    • Supports native compilation, comparison mode, per-unit linking, debug IR/object output, and transport-only operation.
    • Existing compilation behavior remains unchanged by default.
    • Clear errors are reported when requested features are unavailable or unsupported.
  • Documentation

    • Added setup, usage, validation, performance, and troubleshooting guidance.
  • Bug Fixes

    • Build and object caches now distinguish between compilation backend modes.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 36cc1971-d2e4-4d9a-9853-5a6c88b723be

📥 Commits

Reviewing files that changed from the base of the PR and between 51f0a25 and 2c6ff43.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • experiments/llvm-inprocess-spike/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • changelog.d/7241-inprocess-llvm-native-construction.md
  • crates/perry-codegen/Cargo.toml
  • crates/perry-codegen/src/block.rs
  • crates/perry-codegen/src/codegen/mod.rs
  • crates/perry-codegen/src/dialect.rs
  • crates/perry-codegen/src/function.rs
  • crates/perry-codegen/src/inprocess.rs
  • crates/perry-codegen/src/inst.rs
  • crates/perry-codegen/src/lib.rs
  • crates/perry-codegen/src/linker.rs
  • crates/perry-codegen/src/module.rs
  • crates/perry-codegen/src/native_emit.rs
  • crates/perry/Cargo.toml
  • crates/perry/src/commands/compile/build_cache.rs
  • crates/perry/src/commands/compile/object_cache.rs
  • crates/perry/src/commands/compile/object_cache/object_cache_tests.rs
  • docs/llvm-inprocess-experiment.md
  • experiments/llvm-inprocess-spike/.gitignore
  • experiments/llvm-inprocess-spike/Cargo.toml
  • experiments/llvm-inprocess-spike/batch_ab.sh
  • experiments/llvm-inprocess-spike/batch_kernel.ll
  • experiments/llvm-inprocess-spike/mk_llvm_sys_shim.sh
  • experiments/llvm-inprocess-spike/spike.ts
  • experiments/llvm-inprocess-spike/spike_text.ll
  • experiments/llvm-inprocess-spike/src/main.rs

📝 Walkthrough

Walkthrough

The PR adds an opt-in LLVM 22 in-process backend. It introduces typed LLVM instructions, native module construction, in-process object emission, native and differential modes, cache-key integration, and validation tooling.

Changes

In-process LLVM compilation

Layer / File(s) Summary
Typed LLVM instruction storage and streaming
crates/perry-codegen/src/inst.rs, crates/perry-codegen/src/block.rs, crates/perry-codegen/src/function.rs
LLVM instructions use typed representations. Rendering, size estimation, GC-unsafe call detection, and streamed function traversal use the new model.
LLVM dialect reader and native module construction
crates/perry-codegen/src/dialect.rs, crates/perry-codegen/src/module.rs, crates/perry-codegen/src/native_emit.rs
The backend parses LLVM text, resolves references, streams function items into LLVM modules, handles split units, and verifies constructed modules.
Feature-gated compilation and routing
crates/perry-codegen/Cargo.toml, crates/perry-codegen/src/inprocess.rs, crates/perry-codegen/src/linker.rs, crates/perry-codegen/src/codegen/mod.rs, crates/perry/Cargo.toml
The opt-in feature enables LLVM 22 dependencies. Native and differential modes share target selection, optimization, object emission, and failure handling.
Cache separation and validation support
crates/perry/src/commands/compile/*, docs/llvm-inprocess-experiment.md, experiments/llvm-inprocess-spike/*, changelog.d/7241-inprocess-llvm-native-construction.md
Backend selection affects cache keys. Documentation, generated LLVM modules, spike programs, comparison scripts, and setup tooling exercise the backend.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#6855 — Both PRs modify LlFunction::to_ir in crates/perry-codegen/src/function.rs.

Suggested labels: tooling, run-extended-tests

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exp/llvm-inprocess

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.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

@proggeramlug I will review pull request #7301.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Ralph Küpper added 26 commits August 3, 2026 11:56
Standalone (own workspace) inkwell 0.9 + LLVM 22 binary with three modes:
--version clang-compatible banner, 'demo' builder-API construction of the
brief's can-it-express-X list, and a clang-argv shim that compiles Perry's
.ll in-process (parse -> verify -> default<O3> -> TargetMachine emit) so
PERRY_LLVM_CLANG=<shim> A/Bs the backend through an unmodified perry.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
Demo proves NaN-box constant bit fidelity (f64::from_bits -> const_float
preserves 0x7FFC000000000001 payloads end to end), inline asm, module asm,
appending @llvm.used and gc attrs all expressible (LLVMSetGC via llvm-sys).
Shim arm produces objects BYTE-IDENTICAL to Homebrew clang 22 on the same
IR/flags; full perry compile via PERRY_LLVM_CLANG=<shim> yields output
byte-identical to the text path and to pinned Node 26.5.1.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
New perry-codegen 'llvm-inprocess' cargo feature (inkwell 0.9 / LLVM 22,
optional deps — the default build links no LLVM and is unchanged). Truthy
PERRY_LLVM_INPROCESS routes compile_ll_to_object through the C API: parse
from memory, verify, default<ON> pipeline, TargetMachine emission — no .ll
on disk, no clang subprocess. Decisions (opt level incl. #4880 fallback,
-mcpu, inlinehint) are interpreted from the same build_clang_compile_plan
argv so the backends cannot drift independently. Flag joins both cache
keys; a build without the feature fails loudly instead of falling back.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
Integrated flag validated one-binary: liveness line, byte-identical program
output, node-oracle match, distinct object-cache keys observed. 26-test
corpus slice through PERRY_LLVM_INPROCESS=1: 25/26 byte-identical, 26/26
proven live, the 1 diff is the pre-existing perry-ext-http panic family
(thread-id-only delta, reproduced arm-vs-itself). Featureless build fails
loudly under the flag and is untouched without it. inkwell 0.9 NUL-buffer
requirement fixed; fmt applied; perry-codegen 526 + perry 881 tests pass.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
PERRY_LLVM_INPROCESS gains 'native' (function bodies built through the
LLVM C API; only the module skeleton stays textual) and 'diff' (both arms
built in the same LLVM, normalized prints compared, text arm's object
returned). Skeleton = LlModule::skeleton_ir; plan argv comes from the same
build_clang_compile_plan decision code via native_plan_args (size input =
the render-free estimate). inprocess.rs split into parse_ir_text +
optimize_and_emit_module for reuse. dialect.rs is a loud-failing stub so
the new modes cannot silently no-op before the reader lands; unported
paths (unit-split, emit_ir_only) fall through to in-process transport.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
Bounded reader for perry's IR dialect (census-driven): typed operand
resolution with per-function %name maps, placeholder-RAUW for non-phi
cross-block forward references (the idispatch tower shape), deferred phi
incomings, callsite-derived call types (opaque-ptr semantics: a direct
call's fn type is the callsite's, not the declare's — measured on
js_native_call_value), pointer icmp, atomic/volatile loads, fneg, SIMD
vectors, pre-declaration of module-internal defines. Corpus gate: every
function of spike_text.ll + batch_kernel.ll (9k+ instructions) constructs
natively and passes the LLVM verifier.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
Diff mode's verdict is now emitted-object bytes (construction-time constant
folding makes pre-opt print comparison structurally impossible; bytes are
ground truth, pre-opt IR dumps remain the localization artifact). Reader
keeps textual block order under forward references (move_after), applies
returns_twice callsite groups, derives call types from callsites. Result:
spike.ts arms emit BYTE-IDENTICAL objects; batch.ts differs by 336 bytes of
tighter native-arm code (register-allocation divergence downstream of
pre-folded constants — all pre-opt diff classes are folding, none are
construction bugs).

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
Two fixes from the gap-corpus slice: parse the explicit 'external' linkage
keyword init_body defines carry, and parse the skeleton together with
synthesized declares for every define — skeleton globals reference defined
wrapper functions (extern-closure descriptors), and the declares double as
the module-scope forward-reference mechanism, replacing the separate
pre-declare loop. dynamic-import multi-module test now compiles natively
with output identical to the text arm.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
LlBlock stores Vec<LlInst> instead of Vec<String>; Raw carries the
pre-rendered line so zero bytes of IR change (gate: the full 528-test
suite, which pins exact rendered IR). Consumers move to render_into /
text_len / scan_str so typed variants need no per-line String. This is
the enabling step for migrating block.rs's ~40 semantic methods off
format! one opcode at a time, with =diff's object-byte verdict as the
per-step gate.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
…xt gone

LlFunction::for_each_final_line is the single finalized-order visitor
(blocks, entry splices, streamed return-site rewrites); to_ir is now that
visitor plus header/brace/volatile-pass, so byte-identity is by
construction (528-test gate). dialect::FnStream consumes the stream line
by line; native mode materializes no per-function text except for
has_try functions, whose setjmp volatile promotion needs whole-function
analysis. Remaining transient text is per-LINE formatting of Raw insts —
removed opcode-by-opcode as typed LlInst variants land.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
The C-API builder constant-folds 'select true, undef, undef' to the
uniqued constant undef: un-RAUW-able, verifier-clean, and silently
substituting undef for every non-phi forward reference. Caught by the
FULL gap sweep as a class/prototype family failing with '(number).set
is not a function' (sampled slices never hit the family); it also
explains the batch.ts 336-byte object delta previously misread as
benign regalloc divergence. Placeholder is now a load from a scratch
alloca (real instructions, never foldable, erased on resolution), and
a use/def type mismatch is a hard error. Both corpora now emit
BYTE-IDENTICAL objects from the two construction paths.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
…chains

The text path leans on LLParser auto-declaring @llvm.* intrinsics at
first use (perry's clamp lowering emits smax/smin with no textual
declare); the reader now mirrors that for intrinsics only — anything
else undeclared stays a loud error. Native-path error wrappers print
the full anyhow chain ({:#}) so root causes are not swallowed by the
outermost context. Fixes the one compile failure in the full sweep
(test_gap_6339_i32_arith_chain_copy), now byte-identical.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
The dialect corpus tests read spike_text.ll/batch_kernel.ll — gitignored
until now, so a fresh clone skipped them silently (a gate that cannot
fail). Track both (~670 KB). batch_ab.sh: gtimeout/timeout detection and
a portable free-disk probe replace the macOS-only spellings.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
On Mach-O the module name reaches nothing; on ELF it lands in the
object's symtab (#7131), so differently-named diff arms would fail
byte-identity on the identifier alone. Same name for both arms.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
…pus skip

Everything needed to run the Phase 2 sweep on Linux, found by running it.

batch_ab.sh could not start on Linux at all: `TIMEOUT=$(command -v "$TIMEOUT")`
dies under `set -u` (the intent was coreutils' macOS `gtimeout`), and WT was a
hardcoded /Users path. WT now derives from the script's own location, and the
free-space probe looks at the filesystem the work dir is actually on rather
than "/" — on a typical Linux box /home is a different, much larger volume.

Two guards earned by a real interruption: a concurrent cargo build pushed the
host into the OOM killer, which killed six compiles mid-link. Those landed in
results.txt as CFAIL_BOTH — indistinguishable, after the fact, from six real
pre-existing failures, and they cost a full restart of a ~80-minute run. So:
re-running now resumes (tests already recorded are skipped), and a compile that
dies on a signal is never scored — it waits for memory, retries once, and if it
dies again it is recorded as OOM_INFRA and reported on its own summary line, a
category no backend conclusion may be drawn from.

The corpus gate's missing-file branch printed "skipping" and returned green.
The corpora have been tracked in-tree since 4f001d3, so absence now means a
broken checkout, not a branch without artifacts — it is a hard error. That
branch was exactly the vacuous green the Linux run had to rule out by hand.

Also documents the Fedora LLVM 22 route (no distro package at any version):
the upstream release tarball plus a shim prefix, because the tarball's baked-in
`llvm-config --system-libs` names its Ubuntu build host's libzstd.a by absolute
path and llvm-sys panics on an entry that is neither -lfoo nor an existing file.
Every emit(format!) in the semantic surface becomes a typed push:
Bin/FNeg/FCmp/ICmp/Alloca/Load{5 flavors}/Store/Cast/Select/Call/
CallIndirect/AsmBarrier/Br/CondBr/Ret/RetVoid/Unreachable/Gep/Phi.
Renderers are byte-identical to the replaced format! strings (gate: the
full 528-test suite, which pins exact rendered IR). push_inst preserves
the terminator discipline and try-region store tracking (typed
note_store_ptr twin); contains_gc_unsafe_call answers structurally for
typed calls. text_len renders into a thread-local scratch so the
codegen-unit balancer's sums stay exact. emit/emit_raw remain the Raw
escape hatch (89 bespoke call sites).

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
FnStream::item consumes FinalItem::Inst directly: every typed variant
builds through the C API with no line formatting and no parsing; only
labels, entry splices, ret-rewrite lines and Raw payloads remain text.
for_each_final_line is now a rendering adapter over for_each_final_item
so the two consumers cannot drift. Parser and typed paths both honor
!invariant.load (the parser silently dropped it before). finish()
returns (typed, raw) counts — the ratchet. Gates: 528 unit tests;
=diff object bytes identical on both corpora through the typed path.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
PERRY_SAVE_LL (which --trace llvm sets, #7154) and PERRY_LLVM_KEEP_IR
now work under =native/=diff: they print the CONSTRUCTED module — what
LLVM actually verifies and optimizes, construction folds included —
instead of silently no-opping. Filenames mirror the text path's with a
.native infix. This closes the 'debug knobs dead under native' gap: the
in-process no-text pipeline and on-demand .ll dumps are now both flags,
independently switchable.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
LlModule::codegen_unit_parts extracts the #5391 partition (greedy
size-balanced buckets + per-unit skeleton) with render_codegen_units as
a thin text renderer over it (existing unit tests pin the bytes).
compile_module_units_native builds each unit as its own context+module —
same peak-RSS bound as the per-unit clang model — with internal/private
definitions promoted exactly like render_fn_external, then partial-links
via the extracted linker::merge_unit_objects. =diff gains a unit-split
harness (merged-object byte verdict). PERRY_CODEGEN_UNITS=3 on the batch
kernel: byte-identical merged objects. The only remaining text
fallthrough is emit_ir_only, which by definition wants text.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
rs4gc_schedules_in_process: RewriteStatepointsForGC runs as a module
pass through the in-process pipeline at the pinned LLVM (no opt
subprocess, no toolchain skew — the exact blocker #7108 measured), a
statepoint lands at the may-GC call, and the live addrspace(1) pointer
is gc.relocate'd across it. This is the mechanism engine-plan layer 2
(#7174) was waiting on; the test keeps it true.

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo
Second OS, second architecture, second object format, same conclusions.

Full 466-test gap A/B under =native on Fedora 43 / x86_64 / glibc / LLVM
22.1.8: 459 SAME, 7 DIFF, 0 compile failures on either arm, 466/466 compiles
carrying the liveness line. The 7 residuals are the three macOS noise families
(perry-ext-http SIGABRT, tokio listener panic, console.time jitter) and each
was proven by re-running a single arm's binary twice — all 7 differ against
themselves, so none is backend-attributable. =diff reports byte-identical
objects on both corpora, so the two construction paths agree on ELF as they do
on Mach-O.

-fno-math-errno is closed rather than deferred: it only governs whether LLVM
may treat a call to a NAMED libm function as errno-free, and Perry never emits
one — math lowers to llvm.* intrinsics (errno-free by definition) or to
js_math_* runtime helpers, whose libm behavior lives in libperry_runtime.a and
is not produced by this compile. Measured on a 431 KB real module and on a
probe with 10 intrinsic call sites, 6 frems and zero named libm callees:
byte-identical objects with and without the flag, under clang 22 and clang 19.
The in-process backend needs no equivalent knob on glibc.
The Linux bring-up needed one artifact that lived only on the box that did it:
a wrapper llvm-config. Upstream LLVM release tarballs are built on Ubuntu and
report the BUILD HOST's static system libs by absolute path
(/usr/lib/x86_64-linux-gnu/libzstd.a), and llvm-sys panics on any --system-libs
entry that is neither `-lfoo` nor an existing file -- so on a distro without an
LLVM 22 package the build dies before compiling anything. The write-up
described the fix in prose, which left the next person to re-derive it.

mk_llvm_sys_shim.sh generates the prefix instead. It generalizes past the one
symptom: any dangling absolute path in --system-libs becomes `-lname`, while
paths that DO exist are left alone, since llvm-sys handles a real absolute path
and silently converting a deliberate static link to a dynamic one is not a
change to make on the user's behalf.

Verified against the hand-written shim the Linux sweep actually built with:
identical output for --system-libs/--version/--libdir/--includedir/--prefix/
--libnames, and a full relink of the perry-codegen test binary through the
generated prefix resolves dylib=zstd and passes the corpus gates.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@proggeramlug
proggeramlug merged commit 3c8dddc into main Aug 3, 2026
26 of 39 checks passed
@proggeramlug
proggeramlug deleted the exp/llvm-inprocess branch August 3, 2026 10:22
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

proggeramlug added a commit that referenced this pull request Aug 3, 2026
…7304)

* 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

* ci: changelog fragment for #7304

Claude-Session: https://claude.ai/code/session_01GsQbePfACqFLd4LaGfQzLo

* ci: job-level relevance filter — promotion-safe required check

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

---------

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
proggeramlug pushed a commit that referenced this pull request Aug 3, 2026
Resolution: keep the typed-instruction infrastructure; the invoke +
inline continuation label ride the Raw escape hatch inside handler
scopes (plain calls stay typed). Try-region volatile tracking and
has_try die on both sides; native construction bails to the textual
path for personality-carrying modules until the line reader learns
invoke (follow-up on #7301); synth_define_header carries the
personality clause in lockstep with to_ir.
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