Skip to content

gc: a relocating minor with PRECISE roots breaks 14 of 20 representation-corpus files (5 crashes, 9 mismatches) — the conservative stack scan is load-bearing for correctness #6981

Description

@proggeramlug

Found by #6977, which made the evacuating minor reachable for the first time. #6950's premise held: nothing had ever moved, so nothing had ever tested this.

Result

scripts/gc_repsel_matrix.sh --arms all --pressure 8, release build, macOS arm64, pinned Node 26.5.0, with the evacuating base from #6977:

arm liveness across the corpus:
  evac_minor          requires=move     collected 19/20   moved-objects 19/20

Evacuation is now genuinely live on 19 of 20 corpus files — and 14 of 20 break.

The discriminator is precise roots, not relocation

Both columns collect and both relocate. The only difference is whether the conservative native-stack scan is running.

corpus test PERRY_GC_INCREMENTAL=0 (conservative scan ON) + PERRY_CONSERVATIVE_STACK_SCAN=off (precise roots)
repsel_canonical_i32 OK (1 122 copied) SIGABRT (4 579 copied)
int_valued_ta_locals OK (12) OK (12)
specabi_polymorphic_coexist OK (1 099) mismatch (9 647)
specabi_reassign OK (1 101) mismatch (9 671)
specabi_recursion_escape OK (4) OK (4)
specabi_view_detach OK (6) OK (6)
ta_param_numeric_read OK (15) SIGSEGV (5 948)
typedarray_param_read OK (15) SIGABRT (5 965)
repsel_canonical_str_locals OK (0) OK (63)
repsel_ptr_shape_locals OK (6) SIGABRT (4 640)
repsel_ptr_shape_barriers OK (5 923) SIGSEGV (4 616)
repsel_p4a_holes_axis OK (5 888) OK (4 602)
repsel_p4a_inline_tiers OK (5 631) mismatch (10 621)
repsel_p4a_logical_numeric OK (5 633) mismatch (10 636)
repsel_p4a3_numarray_barriers OK (18) mismatch (15 297)
repsel_p4a3_numarray_growth OK (0) OK (0)
repsel_p4a3_ptr_numarray OK (5 637) mismatch (10 639)
repsel_p4b_field_store_elision OK (0) mismatch (9 698)
repsel_gc_stress SIGSEGV — separate defect, see below mismatch (1 573 353)
repsel_proven_this_frozen OK (5 935) SIGSEGV (4 614)

(N copied) is [gc-copy-minor] ran copied_objects=N summed over the run — the collector's own evidence that survivors were relocated.

Read that table carefully: relocation with a conservative scan is fine. 19/20 pass while copying thousands of objects. What breaks is relocation with precise roots — i.e. exactly the values that only the conservative scan was keeping alive or keeping correct, which is the class #6951/#6972 opened and #6968/#6969/#6970/#6971 enumerate. This is the same conclusion #6972's commit message reached ("the scan was doing load-bearing correctness work, not acting as a safety net"), now measured across the corpus instead of on one shape.

Minimal reproducer

test-files/test_gap_specabi_reassign.ts, 20 lines, no loop:

function first(a: any) { return a[0]; }

let P = new Int32Array(4);
P[0] = 42;
console.log("before:", first(P));

P = new Int32Array([7, 8]);
console.log("after:", first(P));
$ PERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0 ./test_gap_specabi_reassign
before: 42
after: 7          <- correct, 1 101 objects copied

$ PERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0 PERRY_CONSERVATIVE_STACK_SCAN=off \
      PERRY_GC_TRACE=1 PERRY_GC_DIAG=1 ./test_gap_specabi_reassign
before: 42
after: undefined  <- 9 671 objects copied
third: 99 1

before: and third: are correct; only the middle call is wrong, and only in the run where a collection lands inside it. The value reaching first is a typed array passed under the specialized ABI — a raw unboxed pointer, which is neither a shadow-stack root nor a slot an evacuating cycle rewrites. The pre-collection register is stale afterwards, and a[0] reads through it.

Note this is the SAME defect shape as #6951 (an SSA register holding the only reference across a collection point), one layer further in: #6972 rooted the argument accumulator; this is the argument value itself when the specialized ABI passes it raw. #6969 (ctor args), #6970 (native-method args) and #6971 (string-method receiver + args) are neighbouring cases; this one is an ordinary user-function call.

Why this matters more than the count

PERRY_CONSERVATIVE_STACK_SCAN resolves Auto -> SkipDisabled in shipped builds. The only reason precise roots are not already the shipped behaviour on the automatic paths is that gc_check_trigger forces ManualGcScanGuard::force_full_scan() on both arms. That forced scan is currently load-bearing for correctness on 14 of 20 representation-corpus files. Any change that removes it — which is the direction the moving-GC work is going — turns this table into production behaviour.

Reproducing

cargo build --release -p perry -p perry-runtime -p perry-stdlib \
                      -p perry-runtime-static -p perry-stdlib-static
scripts/gc_repsel_matrix.sh --arms evac_minor --pressure 8 --no-build

(needs #6977 for the evac_minor arm; before it, every one of these cells reported UNVERIFIED because nothing moved.)

Related: #6950, #6977, #6951, #6968, #6969, #6970, #6971, #6942, #6946.

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