You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Statepoint native roots do not compile on x86-64: the compact-map rewriter refuses every module #7321
PERRY_STATEPOINTS=1 cannot compile a single module on x86-64 Linux. The
compact-map rewriter refuses:
perry: this module emits an LLVM stack map that the compact-map rewriter could
not parse, so its GC roots would be invisible to the collector (the runtime
reads only the compact section). Refusing to emit a binary that would lose
roots silently.
Reproduced by gc-native-roots.yml itself: every run of that workflow since it
was pointed at ubuntu-latest has failed on the first probe
(01_nursery_churn), e.g. run 30823009708. The job has never been green there.
This is the fail-closed path working as designed — a refusal, not a rootless
binary — so there is no correctness exposure. What it means is scope: the
native-root mechanism is aarch64-only today, and #7314's evidence
(test-drizzle-pg, 23,301 statepoints) was gathered on aarch64.
Likely cause, not verified here: crates/perry-codegen/src/gc_map.rs describes
its base registers in aarch64 terms throughout — DWARF_REG_FP_AARCH64 / DWARF_REG_SP_AARCH64, and "LLVM also uses a callee-saved register (x19 on
aarch64) as a frame base pointer". The runtime decoder
(gc/roots/stack_maps.rs) maps its 2-bit base tag back onto the same two
aarch64 constants.
Blocks making statepoints the default root mechanism on any x86-64 target.
gc-native-roots.yml's statepoints-refuse-x86 job pins the refusal so it
stays a refusal, and fails the day x86-64 starts working — that is the prompt to
widen the aarch64 matrix and close this.
Both halves are now on main, and together they answer this issue properly rather than working around it.
#7331 — the parse. Root cause: .word is 4 bytes on AArch64 but 2 bytes on x86. LLVM picks each fixed-width field's spelling per target via MCAsmInfo::Data32bitsDirective, and GNU as defines .word as the target's natural machine word — 4 bytes on AArch64/ARM/PowerPC/MIPS/SPARC/RISC-V, 2 on x86 where it dates to 16-bit. The rewriter carried one fixed table (".long" | ".word" => 4) written against the Mach-O/AArch64 spelling it was developed on. Refusals now name the directive, the record and the reason, which is how this was found rather than guessed.
#7324 — the walker. Parsing was necessary but not sufficient. Measured on a cross-compiled x86-64 probe rather than inferred:
Every root is Indirect [RSP + off] — DWARF register 7 — and the runtime's chain_walkable admits only aarch64's FP/SP (29 and 31). So every frame fell back to the unwinder, which cannot reliably resolve the stack pointer, and the collector segfaulted on the evacuation-heaviest probe. The compiler now refuses off-aarch64 instead, naming the target. Also fixed: the ELF map was forcing a DT_TEXTREL in a PIE.
So the honest status is: statepoints are aarch64-only, and now say so. That is a smaller claim than "x86-64 works", and it is the true one. Making x86-64 work needs the runtime walker to resolve Indirect [RSP + off] — a separate piece of work, now clearly scoped rather than hidden behind an opaque parse failure.
Worth noting for anyone picking that up: the ELF question this gate originally existed to answer came back resolved — SHF_GNU_RETAIN kept .perry_gcmap through --gc-sections, and .llvm_stackmaps was gone. Both section assertions passed.
Activity
gc: name the reason a stack map could not be compacted (#7321)
gc: record why the aarch64 base-tag naming is not #7321's cause
gc: name the reason a stack map could not be compacted (#7321)
gc: record why the aarch64 base-tag naming is not #7321's cause
gc: decode ELF stack maps, and make the compact-map refusal say why (#…
proggeramlug commentedon Aug 3, 2026
Both halves are now on
main, and together they answer this issue properly rather than working around it.#7331 — the parse. Root cause:
.wordis 4 bytes on AArch64 but 2 bytes on x86. LLVM picks each fixed-width field's spelling per target viaMCAsmInfo::Data32bitsDirective, and GNUasdefines.wordas the target's natural machine word — 4 bytes on AArch64/ARM/PowerPC/MIPS/SPARC/RISC-V, 2 on x86 where it dates to 16-bit. The rewriter carried one fixed table (".long" | ".word" => 4) written against the Mach-O/AArch64 spelling it was developed on. Refusals now name the directive, the record and the reason, which is how this was found rather than guessed.#7324 — the walker. Parsing was necessary but not sufficient. Measured on a cross-compiled x86-64 probe rather than inferred:
Every root is
Indirect [RSP + off]— DWARF register 7 — and the runtime'schain_walkableadmits only aarch64's FP/SP (29 and 31). So every frame fell back to the unwinder, which cannot reliably resolve the stack pointer, and the collector segfaulted on the evacuation-heaviest probe. The compiler now refuses off-aarch64 instead, naming the target. Also fixed: the ELF map was forcing aDT_TEXTRELin a PIE.So the honest status is: statepoints are aarch64-only, and now say so. That is a smaller claim than "x86-64 works", and it is the true one. Making x86-64 work needs the runtime walker to resolve
Indirect [RSP + off]— a separate piece of work, now clearly scoped rather than hidden behind an opaque parse failure.Worth noting for anyone picking that up: the ELF question this gate originally existed to answer came back resolved —
SHF_GNU_RETAINkept.perry_gcmapthrough--gc-sections, and.llvm_stackmapswas gone. Both section assertions passed.ci(gc): add the aarch64-Linux arm, the one uncovered corner of the map (
docs(plan): both statepoint adoption gates are closed; record the pla…
gc: admit two provably-leaf helpers, and measure that it buys nothing (…
fix(codegen): statepoint report counted zero safepoints since #7348 (#…
docs(plan): restore the corrected size figure and close the adoption …