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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions