fix(gc): route RS4GC through the in-process LLVM backend - #7339
fix(gc): route RS4GC through the in-process LLVM backend#7339proggeramlug wants to merge 4 commits into
Conversation
PERRY_RS4GC=1 died with `unterminated attribute group` on any stock toolchain: the pass ran under an external LLVM 22 `opt`, and Apple clang 21 then could not parse the IR it produced. That made RS4GC reachable only with PERRY_LLVM_CLANG pointed at a version-matched LLVM 22 -- and RS4GC is the only backend that can root an `invoke`, which since #7302 is every call inside a `try`. Run the pass in-process instead, where LLVM 22 is already pinned and no IR ever crosses a toolchain boundary. Two gaps had to be closed: - inprocess.rs ignored `-S`, so the statepoint backends' request for assembly silently produced an object. #7314's compact-map rewriter works on assembly text, so it needs the real thing. - the returned assembly was written straight to a `.o` with nothing assembling it (`ld: unknown file type`). Mirror the external path: write it to plan.asm_path, run compact_and_assemble, return the object. The assembler is resolved via find_clang() because plan.clang is the literal `(in-process)` placeholder here.
…cts for assembly requests
Every existing RS4GC step pins PERRY_LLVM_OPT and PERRY_LLVM_CLANG to one brew install, which is the requirement the in-process route removes. An arm that keeps the pinning cannot observe that. This step is the only one that unsets both, and it runs probe 09 -- try-carrying, so every call in it is an invoke, which the explicit bridge refuses (#7330). It asserts four things, each of which has been a way a GC gate went green while measuring nothing: the map section exists, the compact rewrite ran, a copying minor actually copied, and RS4GC (not a per-function bail to the bridge) did the lowering.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe in-process LLVM backend now performs RS4GC rewriting, supports assembly emission, and integrates compact GC-map assembly. CI validates try/catch roots, evacuation, and RS4GC lowering on ARM64. ChangesIn-process LLVM RS4GC pipeline
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant gc_native_roots as gc-native-roots workflow
participant inprocess_rs as inprocess.rs
participant linker_rs as linker.rs
participant clang
gc_native_roots->>inprocess_rs: build with llvm-inprocess
inprocess_rs->>inprocess_rs: run RS4GC statepoint rewriting
inprocess_rs-->>linker_rs: emit assembly
linker_rs->>linker_rs: compact GC maps
linker_rs->>clang: assemble compacted assembly
clang-->>linker_rs: return object
linker_rs-->>gc_native_roots: run evacuation and backend checks
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Full gap suite, two arms, 479/479 — zero new regressions, zero refusalsRan every Zero refusals is the number that matters here, more than zero regressions. For context: the earlier soak that returned "13 regressions, do not flip" was Scope, stated plainlyThis makes RS4GC-in-process viable as a default on aarch64. It does not make
The plain in-process path ( |
Closes #7327.
The problem
PERRY_RS4GC=1failed on any stock toolchain with:RS4GC ran as an external
optsubprocess and handed its output toclang. On aMac that pairing is Homebrew's LLVM 22
optfeeding Apple's clang 21, and thenewer
optemits attributes the olderclangcannot parse. So RS4GC wasreachable only with
PERRY_LLVM_CLANGpointed at a version-matched LLVM 22 —which the existing CI arm does, and which no user does.
That mattered more than a knob normally would, because RS4GC is the only
backend that can root an
invoke, and since #7302 every call inside atryisan invoke. The explicit bridge refuses them outright (#7330). 128 of 479 gap
tests (26%) contain
try {}. So there was no working statepoint path for aquarter of the suite on a default toolchain.
The fix
Run the pass in-process, where #7301 already pins LLVM 22 and no IR crosses a
toolchain boundary. The pass itself was already known to schedule there —
rs4gc_schedules_in_processhas been asserting it. Two gaps had to be closed toget from "schedules" to "produces a linkable object":
inprocess.rsignored-S. It fell into the catch-all that discards-c, so the statepoint backends asked for assembly and were handed anobject. Native-frame GC roots via LLVM statepoints, opt-in (#7173, #7174) #7314's compact-map rewriter rewrites
.llvm_stackmapsin assemblytext — that is where LLVM prints function addresses as symbol names — so it
needs the real thing.
Nothing assembled the result. The returned assembly went straight into a
.oand the link died withld: unknown file type. This now mirrors theexternal path: write to
plan.asm_path, runcompact_and_assemble, returnthe object. The assembler is resolved via
find_clang()becauseplan.clangis the literal
(in-process)placeholder on this path — and using the systemclang for it is sound, since the skew was an IR parse failure and by this
point the IR is gone.
Result
All 9 gc-ratchet probes compile under
PERRY_RS4GC=1 PERRY_LLVM_INPROCESS=1with no
PERRY_LLVM_*pinning, including probe 09, which the bridge cannotcompile at all:
__perry_gcmap9/9 byte-identical to the shadow-stack control, every one under
PERRY_CONSERVATIVE_STACK_SCAN=offso the native map is doing the rooting.--statepoint-reportreportsbackend rs4gc: 9 function(s)on probe 09 — noper-function bail to the bridge.
Gating
Existing RS4GC steps all pin
PERRY_LLVM_OPT+PERRY_LLVM_CLANG, so none ofthem can observe that the pinning is no longer needed. The new step is the only
arm that unsets both. It asserts four things, each of which is a way a GC gate
in this repo has previously gone green while measuring nothing:
__perry_gcmapexists (the map was emitted)__llvm_stackmapsis gone (the compact rewrite ran)gc_evacuation_liveness_assert.py(gc: the native-roots forced-evacuation arm is vacuous — nothing moves, and 5 of 8 probes match zero stack-map records #7336/ci(gc): make the forced-evacuation arm actually evacuate, and assert it (#7336) #7338 — the arm that justified statepoints was reporting
copied_objects = 0on all 8 probes)--only-backend rs4gc(the subject was RS4GC, not a silent bridge fallback)Job timeout goes 90 → 120 because the step builds a second time with the
llvm-inprocessfeature, which cargo cannot share with the build above it.No silent-fallback hole
The concern worth stating, since it is the #7332 shape: if the in-process
compile failed and fell back to external clang after
maybe_rs4gc_preprocesshad already skipped the external
opt, the result would be a binary with nostatepoints at all — correct-looking until a collection freed something live.
Checked: both the failure branch and the missing-feature stub
bail!, so thereis no path from "asked for in-process" to "served the text path".
Tests
dash_s_requests_assembly_and_dash_c_does_not— the parse gap directly.Verified it fails when
"-S" => emit_asm = trueis reverted to"-S" => {}.assembly_emission_is_text_not_an_object— asserts the twoFileTypes do notreturn identical bytes, so a future regression cannot silently re-swallow
-S.Scope
This does not change any default.
llvm-inprocessremains a non-default cargofeature, so
PERRY_RS4GC=1on a stock release build still takes the externalpath and still fails loudly there. Making RS4GC a default is a separate decision
that depends on #7301's feature becoming default, and on #7333 (the x86-64
walker) for non-aarch64 hosts.
Summary by CodeRabbit
New Features
invokeroots and compact GC-map generation are supported without external LLVM tool incompatibilities.Bug Fixes
Tests