Skip to content

GC: module-global array populated in a closure loses all elements under PERRY_GC_FORCE_EVACUATE (no weak refs) #5467

Description

@proggeramlug

Summary

Under the full-evacuation stress mode PERRY_GC_FORCE_EVACUATE=1, a strong, module-level array that is populated from inside a function (closure/IIFE) loses all its elements after allocation churn + gc(). No weak references are involved. Distinct from #5459 (which was a closure array-push write-back codegen bug, fixed by #5461 and correct under default GC) — this reproduces only under forced evacuation.

Repro

declare function gc(): void;
function churn(n: number): void {
  let j: any[] = [];
  for (let i = 0; i < n; i++) { j.push({ i, p: "z".repeat(40) + i }); if (j.length > 128) j = []; }
}
const strong: any[] = [];
(function setup() { for (let n = 0; n < 40; n++) strong.push({ id: n }); })();  // populate from a fn
for (let c = 0; c < 12; c++) { churn(80000); gc(); }
let alive = 0;
for (let n = 0; n < 40; n++) if (strong[n] && strong[n].id === n) alive++;
console.log("strong-array alive:", alive, "/ 40");
  • Default GC: 40 / 40
  • PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1: 0 / 40 (5/5 runs). VERIFY does not panic, so no dangling-forwarded-ref is detected — the elements are simply collected despite the live strong reference from the module-global array. Downstream, accessing the freed elements can SIGSEGV (heap-layout dependent).

Notes

Impact

PERRY_GC_FORCE_EVACUATE is a debug/bisection mode (gc-stress CI, tag-gated), so no production impact — but it currently makes that stress mode unsound for an ordinary code shape, which limits its usefulness as a correctness gate.

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