From 6be786e075cd5f78641ea2c17f3ee8dfa5a0f9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 3 Aug 2026 21:03:56 +0200 Subject: [PATCH] ci: remove the temporary x86-64 statepoint diagnostic, and correct what it found The diagnostic job was merged with #7331 before it had served its purpose; it has now, so it comes out. Its answer contradicts the narrative still at the top of this file: the compact-map rewriter parses x86-64 stack maps fine. Every root is Indirect [RSP + off] (DWARF 7), round-tripping through the explicit- register tag, and no clang version or -march setting reproduced a parse failure. The x86-64 defect is at collection time, which #7324 refuses for. Replace the wrong explanation rather than leave two contradictory ones in the same header. --- .github/workflows/gc-native-roots.yml | 88 +++++--------------- changelog.d/7331-elf-stack-map-word-width.md | 28 +++++-- 2 files changed, 44 insertions(+), 72 deletions(-) diff --git a/.github/workflows/gc-native-roots.yml b/.github/workflows/gc-native-roots.yml index f84fe31ed0..4bfbaa162c 100644 --- a/.github/workflows/gc-native-roots.yml +++ b/.github/workflows/gc-native-roots.yml @@ -21,21 +21,29 @@ # ── Why the matrix runs on macos-14 (aarch64) and not ubuntu-latest ───────── # # It used to say ubuntu-latest, and it had never once gone green there. The -# compact-map rewriter refuses the FIRST probe on x86-64 Linux: +# first explanation written here — that the compact-map rewriter cannot parse an +# x86-64 stack map, with `gc_map.rs`'s aarch64 register names as the suspect — +# was WRONG, and is recorded as wrong because it survived into an issue (#7321) +# and a job name before anyone measured it. # -# 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. +# What is actually true, measured both by cross-compiling a probe to +# x86_64-unknown-linux-gnu and decoding the emitted map (#7324) and by five +# clang versions x twelve `-march` settings x all nine probes from two hosts +# (#7331): **x86-64 stack maps parse fine.** Every root is +# `Indirect [RSP + off]`, DWARF register 7, which round-trips through the +# compact format's explicit-register tag exactly. # -# That is the fail-closed path working exactly as designed — but it means the -# native-root mechanism does not compile on x86-64 today, so a matrix pointed -# there gates nothing while looking like it gates everything. gc_map.rs encodes -# its base registers in aarch64 terms throughout (`DWARF_REG_{FP,SP}_AARCH64`, -# "x19 on aarch64"), which is consistent with that, though this workflow does -# not prove the cause. The gap is asserted, not hidden: `statepoints-refuse-x86` -# below fails the day x86-64 starts working, so nobody has to remember to come -# back and widen the matrix. Tracked as #7321. +# The defect is one layer down, at collection time. `chain_walkable` admits only +# aarch64's DWARF 29/31, so on x86-64 every frame falls back to the platform +# unwinder, which resolves the base with `_Unwind_GetGR(ctx, 7)` — and that does +# not reliably return the stack pointer (`_Unwind_GetCFA` is the supported way). +# Wild addresses, then a segfault when the collector writes through them. The +# compiler now refuses that target outright (#7324) rather than emitting a +# binary that crashes under collection, so an x86-64 run of this matrix would +# test nothing but the refusal — which is what `statepoints-refuse-x86` is for. +# +# The same walk is unsound on aarch64 **Linux** too, where it is merely the +# non-default path: #7333. # # ── RUSTFLAGS ─────────────────────────────────────────────────────────────── # @@ -383,60 +391,6 @@ jobs: # REFUSAL (never a silently rootless binary), and goes red the day x86-64 # starts working, which is the prompt to widen the aarch64 matrix above (#7321). # Deliberately cheap: one probe, no runtime, no oracle. - # TEMPORARY (#7321 investigation): dump the stack-map block the x86-64 - # rewriter refuses, so the cause is read off a log instead of guessed. Removed - # once the fix lands. - x86-diagnose: - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - shared-key: gc-native-roots-x86 - - name: Build compiler and static runtime (perry-dev profile) - run: | - export RUSTFLAGS="-C force-frame-pointers=yes -C force-unwind-tables=yes" - cargo build --profile perry-dev -p perry -p perry-runtime-static -p perry-stdlib-static - - name: Toolchain - run: | - uname -m - lscpu | head -20 || true - which clang || true - clang --version || true - ls /usr/lib/ | grep -i llvm || true - - name: Compile and dump the block - run: | - set -uo pipefail - export PERRY_RUNTIME_DIR="$PWD/target/perry-dev" - export PERRY_NO_AUTO_OPTIMIZE=1 - for probe in benchmarks/gc_ratchet/probes/*.ts; do - name=$(basename "$probe" .ts) - set +e - PERRY_STATEPOINTS=1 ./target/perry-dev/perry "$probe" -o "/tmp/x86-$name" > "/tmp/x86-$name.log" 2>&1 - rc=$? - set -e - if [ "$rc" -eq 0 ]; then - echo "== $name: COMPILED" - readelf -S "/tmp/x86-$name" | grep -E "perry_gcmap|llvm_stackmaps" || echo " (no gc map section!)" - continue - fi - echo "== $name: FAILED" - grep -E "reason:|target:|assembly left at:|cannot yet express" "/tmp/x86-$name.log" | head -5 - asm=$(grep -o '/tmp/[^ ]*\.o\.s' "/tmp/x86-$name.log" | head -1) - [ -n "$asm" ] && [ -f "$asm" ] || continue - start=$(grep -n 'llvm_stackmaps' "$asm" | head -1 | cut -d: -f1) - [ -n "$start" ] || continue - echo " stackmaps at line $start of $(wc -l < "$asm")" - echo " --- first 40 lines of the block:" - sed -n "${start},$((start+40))p" "$asm" - echo " --- directive census from the block onward:" - tail -n "+${start}" "$asm" | awk '{print $1}' | sort | uniq -c | sort -rn | head -25 - echo " --- last 15 lines of the file:" - tail -15 "$asm" - done - statepoints-refuse-x86: runs-on: ubuntu-latest timeout-minutes: 45 diff --git a/changelog.d/7331-elf-stack-map-word-width.md b/changelog.d/7331-elf-stack-map-word-width.md index e04805e44c..0867f36dab 100644 --- a/changelog.d/7331-elf-stack-map-word-width.md +++ b/changelog.d/7331-elf-stack-map-word-width.md @@ -42,8 +42,26 @@ with Mach-O's leading underscore unconditionally under `extern "C"` declarations were different symbols and `perry-runtime` could not link at all. -This does **not** yet close #7321. The defect is an ELF defect but specifically -an AArch64-ELF one; x86 ELF spells these fields `.byte`/`.short`/`.long`/`.quad`, -which the old table already handled, and the x86-64 refusal could not be -reproduced under Apple clang 21, Homebrew clang 19/20/22 or Ubuntu clang 18, -across twelve `-march` settings, from either host, over all nine probes. +Measured on `aarch64-unknown-linux-gnu`, which could not compile a single module +under `PERRY_STATEPOINTS=1` before this and now runs the probe matrix **8/8** +against the pinned Node oracle under +`PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1`, with `.perry_gcmap` +present and `.llvm_stackmaps` absent asserted per probe. (`09_try_catch_roots` +is excluded because the explicit bridge refuses invokes since #7330, on every +target.) Census over those eight: **478 statepoints, 0 plain stack maps, 0 +parser fallbacks**, 648 relocations, 605 non-safepoint calls skipped, max 3 live +roots at one safepoint. + +This does **not** close #7321, and the suspicion recorded there — that +`gc_map.rs`'s aarch64 register naming was the cause — is now measured to be +wrong. x86-64 stack maps parse: every root is `Indirect [RSP + off]`, DWARF +register 7, which round-trips through the compact format's explicit-register tag +exactly, and no configuration reproduced a parse failure (Apple clang 21, +Homebrew clang 19/20/22, Ubuntu clang 16/17/18, twelve `-march` settings, both +hosts, all nine probes). The x86-64 defect is one layer down, at collection time, +and #7324 refuses that target for it. + +Filed while measuring this: **#7333** — `PERRY_STACKMAP_WALKER=unwind` segfaults +on Linux/ELF for 3 of the 8 probes, on a build whose default walker runs all 8 +clean. It matters beyond a bisection control, because on x86-64 the fp-chain +walker is not compiled in and the unwinder *is* the walker.