Skip to content

gc-matrix: --pressure disables the very path #7019 added — the 'default' arm runs ZERO copying minors on all 22 corpus rows #7024

Description

@proggeramlug

Found while re-measuring the matrix after #7019 landed. This is a gate-methodology defect: the matrix's own pressure knob switches off the collector path #7019 added, so the arms that are supposed to describe the shipped configuration measure the pre-#7019 non-moving path instead.

Measurement

scripts/gc_repsel_matrix.sh --arms all --pressure 8, origin/main @ 4340bffc9, 22 corpus rows. Separating the two counters the harness currently sums together (see the sibling issue on that):

arm files with a cycle files with [gc-copy-minor] ran Σ copied_objects
default 13/22 0/22 0
verify_evac 13/22 0/22 0
gen_gc_off 13/22 0/22 0
gen_off_verify 13/22 0/22 0
cons_scan_off 13/22 13/22 21 767 487
evac_minor 21/22 21/22 3 628 777
shipped_default (no env at all) 2/22 2/22 40 087 037

The default arm — "as-shipped GC configuration under allocation pressure" — runs zero copying minors on every corpus file. Its only movement evidence is moved_objects= from the older C4b mark-sweep evacuation policy, which is a different collector.

Mechanism

gc_check_trigger's defer arm (the one that hands the collection to js_gc_loop_safepointgc_safepoint_moving_minor, i.e. all of #7019) is guarded by

if gc_moving_loop_polls_enabled()
    && crate::arena::arena_total_bytes() < gc_moving_defer_hard_cap_dyn_bytes()

and gc_moving_defer_hard_cap_dyn_bytes() is budget_scaled(GC_MOVING_DEFER_HARD_CAP_BYTES, 1, 4, 2 MB)the same 1/4-of-budget-with-a-2 MB-floor formula as gc_trigger_absolute_ceiling_bytes(), which sets the arena trigger. Under an explicit PERRY_GC_HEAP_LIMIT both collapse to the same value, so arena_total_bytes() < hard_cap is false at precisely the moment the trigger becomes due. The defer never happens; control falls through to the direct minor with ManualGcScanGuard::force_full_scan(), and the copying minor is reported ineligible:

[gc-copy-minor] eligible=false fallback=conservative_stack

PERRY_CONSERVATIVE_STACK_SCAN=off rescues it (that is why cons_scan_off shows 13/22 copy-minors) because an explicit env value beats the guard — but that is not the shipped configuration either.

There is no heap-limit setting that fixes it

test_gap_repsel_canonical_i32 / test_gap_repsel_ptr_shape_locals, sweeping the knob:

PERRY_GC_HEAP_LIMIT cycles [gc-copy-minor] ran
2 1 / 2 0
8 (the matrix's --pressure 8) 1 / 2 0
16, 32, 64, 128 0 0

Below the knee the hard cap blocks the defer; above it no trigger is ever due. The band is empty.

Why it matters

#7019 shipped moving minor-GC on by default. The matrix is the gate that is supposed to tell us whether the representations are safe under it. Today the arm named default proves the opposite of what its name suggests: it certifies the non-moving path. The only arms exercising #7019's own path are the ones that override the stack-scan mode, plus shipped_default, which is a requires=none control and reaches a trigger on just 2 of 22 rows.

Suggested direction (not prescriptive): decouple the moving-defer hard cap from the trigger ceiling — it is a safety valve for a mega-expression that reached no poll, so it should be a multiple of the trigger, not equal to it. Independently, the matrix probably wants a pressure arm that lowers the trigger without lowering the hard cap.

Environment: release build, macOS arm64, node 26.5.0 pinned.

Related: #7019, #6950, #6942, #7016.

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