diff --git a/.github/workflows/gc-native-roots.yml b/.github/workflows/gc-native-roots.yml index a0c8bf679b..e187683f7a 100644 --- a/.github/workflows/gc-native-roots.yml +++ b/.github/workflows/gc-native-roots.yml @@ -535,15 +535,26 @@ jobs: # Same answer as the shadow stack, and a collection that actually # moved something. Without the movement assert this passes with the # conservative scan doing all the rooting (#7336, #7338). + # + # PERRY_GC_DIAG=1 is what makes the evacuation assert able to see + # anything: `[gc-copy-minor] ran copied_objects=...` is printed only + # under that variable (`gc/copying.rs`). Without it the trace holds + # nothing but the probe's own `#gcmetric` lines, the assert reads + # 0 copying minors / 0 objects copied off an empty file, and the step + # fails no matter how the collector behaved — which is how this arm + # read on its first-ever execution (three of four arms in this matrix + # were permanently queued until #7393). Diagnostics go to stderr only, + # so the control diff below is unaffected. ./target/perry-dev/perry "$probe" -o /tmp/inproc-09-control PERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0 PERRY_CONSERVATIVE_STACK_SCAN=off \ /tmp/inproc-09-control > /tmp/inproc-09.control.out 2>/dev/null - PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1 \ + PERRY_GC_DIAG=1 PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1 \ PERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0 PERRY_CONSERVATIVE_STACK_SCAN=off \ /tmp/inproc-09 > /tmp/inproc-09.out 2> /tmp/inproc-09.err diff /tmp/inproc-09.control.out /tmp/inproc-09.out \ || { echo "::error::in-process RS4GC diverged from the shadow-stack control"; exit 1; } - python3 scripts/gc_evacuation_liveness_assert.py /tmp/inproc-09.err + python3 scripts/gc_evacuation_liveness_assert.py /tmp/inproc-09.err \ + --probe "09_try_catch_roots (in-process)" # And it must be RS4GC doing the lowering, not a per-function bail to # the bridge -- which would make this arm green while testing the diff --git a/changelog.d/7414-macos-rs4gc-inprocess-gc-diag.md b/changelog.d/7414-macos-rs4gc-inprocess-gc-diag.md new file mode 100644 index 0000000000..3290451409 --- /dev/null +++ b/changelog.d/7414-macos-rs4gc-inprocess-gc-diag.md @@ -0,0 +1,12 @@ +**Fixed** the macOS `native-roots-rs4gc` arm's in-process step could never pass. + +It asserts that a copying minor moved objects by counting +`[gc-copy-minor] ran copied_objects=` lines, but both prints are gated on +`PERRY_GC_DIAG` and the step never set it. The trace held only the probe's own +`#gcmetric` lines, so the assert read 0/0 off an effectively empty file and +failed regardless of collector behaviour — the inverse of a gate that cannot +fail. It shipped with the step in #7339 and had never run, because three of four +arms in this matrix were permanently queued until #7393. + +The liveness assert now distinguishes "no collector diagnostics at all" from +"the collector moved nothing", which the old message conflated. diff --git a/scripts/gc_evacuation_liveness_assert.py b/scripts/gc_evacuation_liveness_assert.py index 5501bf25ef..0525534027 100755 --- a/scripts/gc_evacuation_liveness_assert.py +++ b/scripts/gc_evacuation_liveness_assert.py @@ -26,6 +26,13 @@ COPIED = re.compile(r"copied_objects=(\d+)") ELIGIBLE = re.compile(r"\[gc-copy-minor\] eligible=(\w+)(?: fallback=(\S+))?") MANUAL = re.compile(r"\[gc-scan-fallback\] site=manual_collect") +# Any collector diagnostic at all. Every one of them is printed behind +# `PERRY_GC_DIAG`, so a trace with none of them was produced by a run that did +# not set it — which is indistinguishable, by counts alone, from a collector +# that moved nothing. Told apart below, because guessing wrong costs a build: +# the `gc-native-roots` in-process arm read as "evacuated NOTHING" on its +# first-ever execution purely for want of the variable. +ANY_DIAG = re.compile(r"^\[gc-[a-z-]+\]", re.MULTILINE) def main() -> int: @@ -44,6 +51,14 @@ def main() -> int: print(f"{args.probe}: evacuation live — {ran} copying minor(s), {copied} objects copied") return 0 + if not ANY_DIAG.search(text): + print(f"::error::{args.probe}: {args.trace} carries no collector diagnostics at all, " + "so this assert measured nothing about the collector. Every line it reads is " + "printed behind PERRY_GC_DIAG; re-run the binary with PERRY_GC_DIAG=1 set " + "alongside PERRY_GC_FORCE_EVACUATE=1. (Diagnostics go to stderr, so this does " + "not disturb an stdout oracle diff.)") + return 1 + print(f"::error::{args.probe}: the forced-evacuation arm evacuated NOTHING " f"({ran} copying minors, {copied} objects copied). The arm is vacuous: " f"it proves the program ran, not that a moving collector did (#7336).")