Skip to content

instance_private_key_hidden faults under PERRY_GC_SCAVENGE_NURSERY_MB=64 (residual from #7472) #7481

Closed

Description

@proggeramlug

Residual from #7472 (now closed — its headline stringify_object_inner crash was fixed by #7473). At a non-default nursery cap, a different site still faults:

cd <repo>
cargo build --release -p perry -p perry-runtime-static -p perry-stdlib-static
PERRY_RUNTIME_DIR=$PWD/target/release PERRY_NO_AUTO_OPTIMIZE=1 \
  ./target/release/perry benchmarks/json_polyglot/bench_field_access.ts --debug-symbols -o /tmp/fa
PERRY_GC_SCAVENGE_NURSERY_MB=64 /tmp/fa
EXC_BAD_ACCESS (code=1, address=0x33038393663)
frame #0: perry_runtime::object::field_get_set::enumeration::instance_private_key_hidden + 60

Observed on main @ 351742d (2026-08-06), deterministic at cap=64, absent at the default 16 MB and at 1/4/32 MB post-#7473. The fault address is a garbage pointer (not a NaN-box immediate this time), in the enumeration path's private-key check — likely the same family as #7472's mechanism (a value surviving a large, infrequent copying minor without its slot being rewritten), but at a different consumer.

Not release-blocking (non-default knob), but it is a live copying-minor correctness signal at exactly the cadence #7472 showed the ratchet probes never exercise — a large-Eden probe arm would catch both. Recording it so it does not vanish inside the closed ticket.

Activity

  1. proggeramlug commented on Aug 8, 2026

    @proggeramlug
    ContributorAuthor

    The cadence this issue named is now a pinned, gated ratchet probe (#7666) — and the fault itself no longer reproduces

    Two separate findings; the second does not close the first.

    1. The fault does not reproduce at 7bde3de24 (v0.5.1376)

    The reproducer exactly as written here — benchmarks/json_polyglot/bench_field_access.ts, release build, PERRY_RUNTIME_DIR pinned, PERRY_NO_AUTO_OPTIMIZE=1, PERRY_GC_SCAVENGE_NURSERY_MB=64 — exits 0 on 5/5 runs with an identical checksum (2552985550) on the pinned quiet host. No EXC_BAD_ACCESS, no instance_private_key_hidden fault.

    And it is not vacuous: at cap 64 that workload still runs 6 copying minors (11 at the default cap), with freed_bytes 38 MB and 34 MB on the large ones. The copying minor was reached; it just no longer produces a bad answer. Which of the ~20 rooting fixes landed between 351742d30 and 7bde3de24 closed it was not bisected.

    2. The coverage gap you named is closed, and the shape of it was worse than "no probe"

    a large-Eden probe arm would catch both

    #7666 adds 13_large_eden_survivors, pinned at PERRY_GC_SCAVENGE_NURSERY_MB=64 through a new per-probe // gc-ratchet-env: declaration that check compares like a metric. It holds a survivor cohort across the minors, takes an old-to-young pointer into that cohort every 256th iteration, and reads all of it back into five separately-diffed output lines, so a missed reference rewrite must fault or print a wrong number.

    The finding worth carrying out of this issue: PERRY_GC_SCAVENGE_NURSERY_MB=64 is not, on its own, a larger-Eden knob. On a workload with a small retained set it runs zero copying minors, because gc/policy.rs's arena_growth_full_escalation_due escalates every minor to a full mark-sweep once arena in-use clears its 32 MB floor and exceeds twice the post-full baseline — which a 64 MB Eden over a ~1 MB live set does every time. The first draft of the probe did exactly that (0 copying minors, 4 full sweeps) and would have been pinned on a collector it never reached.

    Sizing the retained set is what makes the cadence reachable. Measured on the pinned host:

    KEEP copying minors at 64 MB at the shipped default
    8,192 0 15
    131,072 1 14
    262,144 (shipped) 4 12

    At the shipped size the four minors free 37, 36, 68 and 68 MB — 49.7 MB per minor — and the first copies 532,482 objects (32 MB) in one cycle, against 14.6–16.6 MB per minor on eleven of the twelve default-cap probes (21.8 MB on 12_large_live_set, whose tenured-proportional cap term is the shipped path to a larger Eden and tops out around there).

    So your reproducer's cap-64 sensitivity was real for that workload because it carries a live set; a probe copied from a smaller workload would have been silently vacuous. That is why the arm is gated rather than merely present, and why check's liveness rule (minor_cycles > 0 and copied + promoted > 0) is what refuses to pin a shape that stops reaching the copying minor.

    Suggested disposition

    Close as covered. The specific fault is gone and the cadence it exposed is now pinned, gated, and shown able to fail (deleting the arm's directive, and PERRY_GC_SCAVENGE=0, both turn check red — details in #7666).

  2. proggeramlug commented on Aug 9, 2026

    @proggeramlug
    ContributorAuthor

    Closed by #7666 (v0.5.1379) — the fault no longer reproduces, and the lesson is now a pinned probe.

    Re-checked under PERRY_GC_SCAVENGE_NURSERY_MB=64 on the pinned mini: 5/5 exit 0. And the check is not vacuous — the same configuration runs 6 copying minors at cap 64, so the collector this issue is about is demonstrably reached.

    More usefully, the lesson is now gated. 13_large_eden_survivors pins the copying-minor cadence the matrix had no coverage of, carrying a per-probe // gc-ratchet-env: PERRY_GC_SCAVENGE_NURSERY_MB=64 declaration that check compares like a metric — an arm whose environment can drift silently is an arm measuring something else.

    The finding that shaped the probe is worth recording here, because it nearly produced a probe that measured nothing: a large Eden on a small retained set runs zero copying minors — arena_growth_full_escalation_due escalates every minor to a full sweep above its 32 MB floor. KEEP 8,192 → 0 minors, 131,072 → 1, 262,144 → 4.

    Shown able to fail three ways: control OK; env directive deleted → FAILED; PERRY_GC_SCAVENGE=0 → FAILED (minor_cycles 4→0). The first of those matters most — with the directive gone, retention/RSS/wall all read as improvements (−80%, −37%, −27%) because those bands are one-sided, so a probe that gets faster and smaller by not doing its job is indistinguishable from a win.

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