Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
932fde7
Unify shape facts, ConstFn calls and numeric receiver regions
proggeramlug Oct 2, 2026
581b53f
fix(runtime): reuse ConstFn key-add transitions after ordered stores
proggeramlug Oct 2, 2026
344b9fa
docs: record ordered ConstFn transition reuse
proggeramlug Oct 2, 2026
bf1b622
Verify SPECIAL ConstFn field body invariants
Oct 2, 2026
76558cc
Audit verifier store and census snapshot pins
proggeramlug Oct 2, 2026
52f2e6c
Document SPECIAL verifier and re-audit census window
proggeramlug Oct 2, 2026
8688829
Honor fixture GC witness environments in loop-poll matrix arm
proggeramlug Oct 2, 2026
239f74c
Exercise short GC witnesses with seeded moving collections
proggeramlug Oct 2, 2026
75c7117
test(gc): retain large-Eden relocation coverage alongside policy ratchet
proggeramlug Oct 2, 2026
155db5b
fix(ci): link macOS stdlib provider frameworks
proggeramlug Oct 2, 2026
f21531c
changelog: record macOS provider framework link repair
proggeramlug Oct 2, 2026
9260ac4
fix(ci): export provider feature installer bootstrap symbols
proggeramlug Oct 2, 2026
b632a2f
fix(codegen): keep ConstFn packed keys out of the heap
proggeramlug Oct 2, 2026
2bf6352
fix(ci): follow the shared shape mint and ratchet removed sites
proggeramlug Oct 2, 2026
5c00500
fix(runtime): scope ConstFn handles and isolate worker test observations
proggeramlug Oct 2, 2026
07ee04e
fix(codegen): account for shape proof use in guarded regions
proggeramlug Oct 2, 2026
8404f77
fix(codegen): take the static shape guard in single-receiver body reg…
Oct 2, 2026
0dad3a5
Merge main (9f39f738d2, #11770) into perf-shape-record-lookup
Oct 2, 2026
262f464
Merge main (a223b139a3) into perf-shape-record-lookup
Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 22 additions & 37 deletions .github/workflows/gc-native-roots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -419,25 +419,10 @@ jobs:
for probe in benchmarks/gc_ratchet/probes/*.ts; do
total=$((total+1))
name=$(basename "$probe" .ts)
if [ "$RUNNER_OS" = "Windows" ] && [ "$name" = "09_try_catch_roots" ]; then
# #7354 measured negative, pinned as a REFUSAL: windows-msvc
# `try` lowers to WinEH funclet pads, which crash LLVM's
# rewrite-statepoints-for-gc outright (access violation on opt
# 22.1.3, reproducible from an eight-line module). Perry refuses
# the module before the pass runs; this arm pins that it STAYS a
# refusal — never a crash, never a silently rootless binary. It
# goes red the day the pass learns funclet EH, which is the
# prompt to fold 09 into this matrix.
if PERRY_RS4GC=1 ./target/perry-dev/perry "$probe" \
-o "/tmp/rs4gc-$name" > "/tmp/rs4gc-$name.compile.log" 2>&1; then
echo "::error::$name compiled under RS4GC on Windows — the funclet refusal is gone: either rewrite-statepoints-for-gc learned funclet EH (fold 09 into the matrix) or the refusal was lost"
exit 1
fi
grep -q "funclet" "/tmp/rs4gc-$name.compile.log" \
|| { echo "::error::$name failed for a reason other than the funclet refusal:"; cat "/tmp/rs4gc-$name.compile.log"; exit 1; }
pass=$((pass+1))
continue
fi
# #10385 replaced Windows funclets with Perry's landing-pad
# personality. Probe 09 must now execute with precise roots on
# Windows too. Actual funclet IR remains refused by linker.rs's
# rs4gc_funclet_refusal and its unit test (#7354).
node --expose-gc --experimental-strip-types "$probe" > "/tmp/rs4gc-$name.oracle"
PERRY_RS4GC=1 ./target/perry-dev/perry "$probe" -o "/tmp/rs4gc-$name"
# perry appends the platform default extension to an -o with none.
Expand All @@ -461,11 +446,17 @@ jobs:
readelf -S "$out" | grep -q "\.llvm_stackmaps" \
&& { echo "::error::$name still carries .llvm_stackmaps — the compact rewrite did not run"; exit 1; }
fi
PERRY_GC_DIAG=1 \
PERRY_RS4GC=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 \
"$out" > "/tmp/rs4gc-$name.out" 2> "/tmp/rs4gc-$name.err"
diff "/tmp/rs4gc-$name.oracle" "/tmp/rs4gc-$name.out" \
|| { echo "::error::$name diverged from the pinned oracle under RS4GC"; exit 1; }
if [ "$name" = "09_try_catch_roots" ]; then
py=python3; command -v python3 >/dev/null 2>&1 || py=python
"$py" scripts/gc_evacuation_liveness_assert.py "/tmp/rs4gc-$name.err" \
--probe "$name ($RUNNER_OS RS4GC)"
fi
errs="$errs /tmp/rs4gc-$name.err"
pass=$((pass+1))
done
Expand All @@ -485,14 +476,9 @@ jobs:
# windows-latest exposes the toolcache python as `python`, not python3.
py=python3; command -v python3 >/dev/null 2>&1 || py=python

# The PORTABLE assertion, on every arm. `11_collect_at_depth` is
# deliberate: it contains no `try`, so it compiles under RS4GC
# everywhere. `09_try_catch_roots` does NOT — RS4GC cannot rewrite
# WinEH funclet pads, so `linker.rs`'s `rs4gc_funclet_refusal` rejects
# it on windows-msvc, and the probe loop above only tolerates that
# because it greps the compile log for "funclet". A report assertion
# pinned to a probe that cannot compile on one arm is a gate that
# fails for a reason unrelated to its subject.
# The recursive-depth assertion, on every arm. This probe carries
# live roots across a deep stack; the separate try probe below
# covers roots across normal and unwinding exception edges.
#
# --only-backend proves the lowering ran on every function; the two
# --require-positive checks prove it PRODUCED something. Those counts
Expand All @@ -510,19 +496,18 @@ jobs:
--require-positive records \
--require-positive roots

# The try-specific arm, everywhere RS4GC can compile a `try`. This is
# The try-specific arm on every target, including Windows since
# #10385 replaced funclets with Perry's landing-pad personality. This is
# the coverage the probe above cannot give: 128 of 479 gap tests
# contain `try {}`, and RS4GC being the only backend that handles them
# is the reason the bridge could be deleted (#7339, #7348).
if [ "$RUNNER_OS" != "Windows" ]; then
PERRY_RS4GC=1 ./target/perry-dev/perry \
benchmarks/gc_ratchet/probes/09_try_catch_roots.ts \
-o /tmp/rs4gc-try-probe --statepoint-report=json 2> /tmp/rs4gc-try.json
"$py" scripts/statepoint_report_assert.py /tmp/rs4gc-try.json \
--only-backend rs4gc \
--require-positive records \
--require-positive roots
fi
PERRY_RS4GC=1 ./target/perry-dev/perry \
benchmarks/gc_ratchet/probes/09_try_catch_roots.ts \
-o /tmp/rs4gc-try-probe --statepoint-report=json 2> /tmp/rs4gc-try.json
"$py" scripts/statepoint_report_assert.py /tmp/rs4gc-try.json \
--only-backend rs4gc \
--require-positive records \
--require-positive roots

# Walker liveness, on EVERY arm. A walker that visits zero frames
# still lets most probes print the right answer, because other root
Expand Down
16 changes: 15 additions & 1 deletion .github/workflows/gc-ratchet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ jobs:
# deliberately broad; it exists only to spare docs-only PRs a build.
# If the listing is empty or the API failed, `set -e` already aborted
# — the job does not silently fall through to "not relevant".
if grep -qE '^(crates/|benchmarks/gc_ratchet/|Cargo\.(toml|lock)$|\.github/workflows/gc-ratchet\.yml$|tests/test_gc_ratchet\.py$)' changed.txt; then
if grep -qE '^(crates/|benchmarks/gc_ratchet/|Cargo\.(toml|lock)$|\.github/workflows/gc-ratchet\.yml$|tests/(test_gc_ratchet|test_large_eden_gc_ratchet)\.py$)' changed.txt; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "Change touches collector-relevant paths; measuring."
else
Expand Down Expand Up @@ -227,6 +227,7 @@ jobs:
|| { echo "::error::expected Node $expected, found $actual"; exit 1; }

- name: Measure
id: measurement
if: steps.relevance.outputs.run == 'true'
env:
PERRY_RUNTIME_DIR: ${{ github.workspace }}/target/release
Expand Down Expand Up @@ -261,13 +262,26 @@ jobs:
--current .bench-results/gc-ratchet-current.json \
--profile shared_ci

# Run even when the ordinary fingerprint is red, retaining both verdicts.
- name: Check large-Eden relocation separately
if: always() && steps.measurement.outcome == 'success'
env:
PERRY_RUNTIME_DIR: ${{ github.workspace }}/target/release
PERRY_NO_AUTO_OPTIMIZE: "1"
run: |
python3 benchmarks/gc_ratchet/check_large_eden_relocation.py \
--perry target/release/perry \
--node "$(command -v node)" \
--output .bench-results/gc-ratchet-relocation.json

- name: Upload measurement
if: always() && steps.relevance.outputs.run == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: gc-ratchet-${{ github.sha }}
path: |
.bench-results/gc-ratchet-current.json
.bench-results/gc-ratchet-relocation.json
benchmarks/gc_ratchet/baseline/gc-ratchet-v1.json
retention-days: 90

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,7 @@ jobs:
if: ${{ !cancelled() }}
run: |
./scripts/gc_repsel_matrix.sh --self-test-liveness-parser
./scripts/gc_repsel_matrix.sh --self-test-fixture-env
python3 scripts/gc_repsel_matrix_merge.py --self-test
python3 scripts/gc_matrix_liveness_check.py --self-test
python3 scripts/gc_matrix_liveness_check.py --check-registry
Expand Down
44 changes: 38 additions & 6 deletions benchmarks/gc_ratchet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,40 @@ probes**, and 21.8 MB on `12_large_live_set`, whose tenured-proportional cap
term (`gc/tenuring.rs`, `max(influx x scale, tenured/2)`) already raises its
Eden a little. That last row is worth noticing — it is the shipped path by which
a large Eden is reached without any knob, and it tops out around 22 MB on the
biggest workload the suite has. The guard that keeps this honest is `check`'s existing
liveness rule (`minor_cycles > 0` and `copied_objects + promoted_objects > 0`):
a future change that stops reaching the copying minor at this cadence cannot be
pinned, it fails.
biggest workload the suite had at that measurement. These are historical cadence
measurements, not invariants of the current adaptive nursery policy.

### Relocation coverage alongside the normal policy measurement

Promotion can now retain whole nursery blocks in place. Consequently,
`minor_cycles > 0` and `copied_objects + promoted_objects > 0` prove minor
activity but do not prove a reference crossed physical relocation. Probe 13's
normal configuration still measures that shipping policy, with all original
counter bands and baseline rows intact.

`check_large_eden_relocation.py` additionally compiles the unchanged probe and
runs it twice with its declared 64 MB nursery setting plus
`PERRY_GC_PROMOTE_IN_PLACE=0` and `PERRY_GC_DIAG=1`. Each run must exit normally,
match the exact pinned Node oracle, and report positive `minor_cycles`,
`copied_objects`, and
`copied_bytes`. The JSON retains both complete traces and verdicts. The workflow
runs this check even if the ordinary counter comparison fails, and uploads both
artifacts. Its result never accepts a changed normal-policy counter.

The 64 MB setting is a base for the adaptive ladder, not a promise of a fixed
collection cadence. At #11645's child, the separate arm measured eight minors,
335,661 copied objects / 19,017,288 copied bytes and 109,455,704 freed bytes;
the original parent had three minors. This restores evacuation of the original
survivor graph, fresh note edges and strings at the large nursery setting; it
does not reproduce the parent's exact timing. Its timing and memory costs are
not shipping-policy measurements.

```bash
PERRY_RUNTIME_DIR=target/release PERRY_NO_AUTO_OPTIMIZE=1 \
python3 benchmarks/gc_ratchet/check_large_eden_relocation.py \
--perry target/release/perry --node "$(command -v node)" \
--output .bench-results/gc-ratchet-relocation.json
```

## Why wall time is excluded from the shared-CI gate

Expand Down Expand Up @@ -447,8 +477,10 @@ minors — was being reported as passing.
**Why the second probe is a sum (#7558).** It used to be `copied_objects`
alone. Both counters come from the same `[gc-copy-minor] ran` line: they are the
evacuating minor's own accounting of *where* it put each survivor — survivor
space, or straight to old-gen. Either one alone names a destination; only the
sum answers "did the copying minor move anything". #7558 produced the
space, or straight to old-gen. At #7558 both destinations required relocation, so the sum then
answered "did the copying minor move anything". In-place promotion subsequently
made the sum a minor-activity check only; the separate probe-13 check above
requires actual copying. #7558 produced the
distinction for real: with the conservative scan gone, the adaptive-tenuring
seed (`gc/tenuring.rs`, which deliberately refuses input from a conservatively
scanned cycle) started receiving data on `gc()`-driven workloads,
Expand Down
73 changes: 73 additions & 0 deletions benchmarks/gc_ratchet/check_large_eden_relocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Exercise probe 13's relocation path separately from its policy fingerprint."""

from __future__ import annotations

import argparse
import hashlib
import json
from pathlib import Path
import tempfile

if __package__:
from . import gc_ratchet as ratchet
else:
import gc_ratchet as ratchet


SOURCE = Path(__file__).resolve().parent / "probes/13_large_eden_survivors.ts"


def check_relocation(binary: Path, node: Path, source: Path = SOURCE) -> dict:
"""Keep both raw runs, including failures; promotion alone is not movement."""
run_env = ratchet.probe_run_env(source)
if run_env.get("PERRY_GC_SCAVENGE_NURSERY_MB") != "64":
raise ratchet.RatchetError("probe 13 must retain its 64 MB nursery setting")
run_env.update(PERRY_GC_PROMOTE_IN_PLACE="0", PERRY_GC_DIAG="1")
oracle_version = "v" + (ratchet.REPO_ROOT / ".node-version").read_text().strip().lstrip("v")
result = {
"probe": source.stem,
"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
"binary_sha256": hashlib.sha256(binary.read_bytes()).hexdigest(),
"run_env": run_env,
"runs": [],
"failures": [],
}
for index in range(2):
run = ratchet.run_once([str(binary)], extra_env=run_env)
counters = ratchet.parse_gc_diag(run["stderr"])
correctness = ratchet._check_against_node(node, source, run["stdout"])
result["runs"].append({**run, "counters": counters, "correctness": correctness})
if run["returncode"] != 0:
result["failures"].append(f"run {index + 1}: exited {run['returncode']}")
if correctness.get("status") != "pass":
result["failures"].append(f"run {index + 1}: Node parity was not verified")
if correctness.get("oracle_version") != oracle_version:
result["failures"].append(f"run {index + 1}: expected Node {oracle_version}")
# These are positive on actual evacuation into survivor space, whereas
# promoted_objects also counts survivors whose blocks stayed in place.
for metric in ("minor_cycles", "copied_objects", "copied_bytes"):
if counters[metric] <= 0:
result["failures"].append(f"run {index + 1}: {metric} must be positive")
result["status"] = "fail" if result["failures"] else "pass"
return result


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--perry", required=True, type=Path)
parser.add_argument("--node", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args(argv)
with tempfile.TemporaryDirectory(prefix="gc-ratchet-relocation-") as tmp:
binary = ratchet.compile_probe(args.perry.resolve(), SOURCE, Path(tmp))
result = check_relocation(binary, args.node.resolve())
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
print(f"probe 13 relocation: {result['status']}")
for failure in result["failures"]:
print(failure)
return int(result["status"] != "pass")


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading