Skip to content

Statepoint native roots do not compile on x86-64: the compact-map rewriter refuses every module #7321

Closed

Description

@proggeramlug

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.

Activity

  1. proggeramlug commented on Aug 3, 2026

    @proggeramlug
    ContributorAuthor

    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:

    x86-64 location kinds:       {Constant: 222, Indirect: 178}
    x86-64 base regs (8B slots): {7: 178}
    

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions