# Corpus for the GC x representation-selection stress matrix
# (scripts/gc_repsel_matrix.sh).
#
# ***A NEW TEST FILE MUST BE REGISTERED HERE OR IT WILL NOT RUN.***
# `test-files/test_gap_{gc,repsel,specabi}_*.ts` is compiled and executed by
# NOTHING unless its basename appears below. An unregistered witness is not a
# failing test, it is no test at all: the PR that adds it goes green having run
# it zero times. That happened four times -- #7192, #7216, #7252 and
# #7270/#7271 -- before it was gated.
#
# Enforcement, cheapest first:
#   * scripts/check_test_registration.py -- pure text, ~0.2s, runs in `lint`
#     (a REQUIRED context) on every PR. This is the one that will stop you.
#   * scripts/gc_repsel_matrix.sh -- auto-detects unregistered
#     `test_gap_repsel_*` / `test_gap_specabi_*`, but only behind a full
#     compiler build.
#   * gc-moving-witnesses.yml -- same, for `test_gap_gc_*`, same cost.
# Deleting a file without deleting its line here also FAILS (a rotted entry).
#
# That is the enforcement point behind docs/representation-selection-rfc.md
# 5.6: a new representation must be exercised against every GC arm, not just
# verified once by hand in its own PR. Files outside those three prefixes
# (typed-array param reads, int-valued typed-array locals) are not
# auto-detected at all, so register them explicitly.
#
# One basename per line, without the .ts extension. `#` starts a comment.

# --- Phase 1: canonical unboxed i32 locals (#6903) --------------------------
test_gap_repsel_canonical_i32
test_gap_int_valued_ta_locals

# --- Phase 1 widening: constant-bounded loop induction variables (#7110) ----
# A counter that is neither index-used nor `strictly_i32_bounded` now takes
# canonical i32 storage when the loop guard pins its whole interval inside i32.
# #7123 also admits a loop accumulator when trip count times step magnitude
# fits i32. The file's refusal probes (`overshoot`, `runtimeBound`, and
# `overflowingAccumulator`) print wrapped values under an unsound widening, so
# this fails loudly rather than merely changing an optimization report.
test_gap_repsel_loop_bounded_i32

# --- Phase 2: specialized calling convention / spec-ABI raw params (#6905) --
test_gap_specabi_polymorphic_coexist
test_gap_specabi_reassign
test_gap_specabi_recursion_escape
test_gap_specabi_view_detach
test_gap_specabi_ordinary_param_guards
test_gap_ta_param_numeric_read
test_gap_typedarray_param_read

# --- Phase 3a: canonical string locals, tagged-at-rest Str (#6909) ----------
test_gap_repsel_canonical_str_locals

# --- Phase 3b: Ptr<Shape> proven object locals (#6911) ----------------------
test_gap_repsel_ptr_shape_locals
test_gap_repsel_ptr_shape_barriers

# --- Phase 3b / #7034 §4: return-shape facts --------------------------------
# Both halves of the return escape: producer-side locals whose only escape is
# `return o`, and caller-side bindings seeded by a return-shape-carrying call.
# `survivesGc` deliberately holds a call-seeded local live across 60 000
# escaping allocations, so the evacuating arms have something to move under it
# — the caller's bound slot is the only rewritable root for that object, which
# is exactly the claim this phase's GC contract makes.
test_gap_repsel_return_shape

# --- Phase 3b / #7170 R1: the same mechanism inside Perry's own CJS IIFE -----
# `cjs_wrap` puts every CommonJS module body in `(function () { ... })()`, which
# turns every module-level `function` declaration into a closure -- so #7107's
# producer walk (`hir.functions`) and its caller-side seed (`Expr::FuncRef`)
# both missed it, and 91.6% of dependency-JS allocation sites were unreachable
# (#7170 §6). The GC claim is the same one and needs its own cells: the
# call-seeded local now lives in a CLOSURE region, whose shadow-slot binding is
# emitted by `codegen/closure.rs`, not `codegen/function.rs`.
test_gap_repsel_cjs_iife_return_shape

# --- Phase 3b / #7034 §3: array-element shape facts -------------------------
# `rows.push(row)` no longer disqualifies `row`, and `const r = rows[i]` under
# an `i < rows.length` loop is rule-1 provenance. Every element local is an
# ordinary shadow-bound NaN-boxed slot re-derived per access, so `churn`
# deliberately allocates 200 objects BETWEEN two field reads of the same
# element local: the evacuating arms are the ones that can catch a raw pointer
# cached across that safepoint.
test_gap_repsel_ptr_shape_elements

# --- Phase 4a / 4a.3: Ptr<NumArray> numeric arrays (#6915, #6916) -----------
test_gap_repsel_p4a_holes_axis
test_gap_repsel_p4a_inline_tiers
test_gap_repsel_p4a_logical_numeric
test_gap_repsel_p4a3_numarray_barriers
test_gap_repsel_p4a3_numarray_growth
test_gap_repsel_p4a3_ptr_numarray

# --- Phase 4b: class-field store note/addref elision (#6919) ----------------
test_gap_repsel_p4b_field_store_elision

# --- Phase 5a: Ptr<Shape> proven `this` in methods (#6925) ------------------
# Registered here after the fact: #6925 added the file without registering it,
# which is exactly the omission this manifest exists to catch — the matrix
# script exits 3 on an unregistered `test_gap_repsel_*` file, so `gc-stress`
# was failing on main for every PR until this line landed.
test_gap_repsel_proven_this_frozen

# --- Phase 5a routing: class-id dispatch tower -> proven `this` (#7142) ------
# The tower case proves `class_id`, which `delete` preserves while compacting
# the packed slots, so the route carries an INLINE keys-token check. Registered
# because the routed path has its own GC contract: the receiver is re-derived
# from the NaN-boxed argument at the call site and the clone shadow-binds its
# own receiver slot (#6925/#6990 -- `GC_TYPE_OBJECT` is movable, the `TaPtr`
# no-bind shortcut does not transfer). `churn()` allocates past the matrix's
# `--pressure 8` heap limit with `rows` live across it, so the evacuating arms
# have a receiver to move rather than reporting the file inert.
test_gap_repsel_pshape_tower_delete

# #9200: the tombstone-delete x evacuating-GC interaction, minimized out of
# the tower. A tombstone publish onto a PROMOTED receiver must arm the
# old-carrier gate at the stamp (`stamp_object_shape_id_with_carrier_note`),
# or the receiver's nursery-young owned keys array has no root a minor can
# see: the record is walked metadata-only, the keys array is swept while
# live, and `prune_dead_shape_keys` leaves the receiver shapeless —
# `Object.keys()` empty, fixed-slot reads undefined, exit 0. Live only with
# `PERRY_OBJECT_TOMBSTONES=1` while #9212's opt-in mitigation stands; it is
# the witness that must stay green when the default flips back on.
test_gap_repsel_pshape_tombstone_oldgen_delete

# --- Typed-array constructor source rooting (#6981) --------------------------
# Not a representation file, so the UNREGISTERED gate does not auto-detect it:
# registered explicitly per the header rule above. Gates the runtime-side half
# of #6981 -- a typed-array constructor SOURCE is only a C-ABI argument, and the
# classification/materialization inside the helper allocates. Verified to FAIL
# on the `evac_minor` arm before the fix (`literal: 0 undefined undefined`,
# 9 675 objects copied) and pass after. It is green on the PR arm set, because
# `cons_scan_off` alone does not reach the evacuating minor -- it starts gating
# per-PR when `evac_minor` joins PR_ARMS.
test_gap_gc_ta_ctor_source_rooting

# --- String-literal operand re-derivation (#7114) ----------------------------
# Not a representation file either, so registered explicitly per the header
# rule. `"acc:" + run(n)` loads the literal's `__perry_init_strings_*` handle
# BEFORE the call and masks the cached register AFTER it. The handle global is a
# registered root that evacuation REWRITES, so the register keeps the pre-move
# address and the concat silently produced an empty string -- exit code 0, no
# diagnostic. Verified on main (ff85fd483) to FAIL at DEFAULT GC settings, with
# no arm env at all, so it bites on `default` and `shipped_default` as well as
# on the %E% arms.
#
# LIVE BY CONSTRUCTION, AND ONLY ONCE PER RUN. Every literal in a module is
# materialized together by `__perry_init_strings_*` before user code runs, so
# the first evacuating cycle relocates all of them at once and afterwards they
# are old-gen and a nursery scavenge cannot move them again. The probe is
# therefore the FIRST statement of the file -- keep it first. Measured on main,
# `--release`, default settings: N=100 000 passes (7 cycles, 590 472 scavenged
# -- it collected and it moved, just not before the concat), N=150 000 is the
# first failing size, and the file ships at N=400 000 for margin.
test_gap_gc_string_literal_operand_rooting

# --- Scalar-replaced object/array locals (#6968) -----------------------------
# Not a representation of its own: escape analysis DELETES the object and keeps
# one entry-block alloca per field/element. Those allocas belong to no HIR
# local, so the shadow-slot assignment pass could not see them and a heap value
# living in one was invisible to a precise-roots collection. Live by
# construction (it churns across every read), so the `cons_scan_off` and
# `evac_minor` arms both bite.
test_gap_repsel_scalar_replaced_locals

# --- Module-init / program-entry canonical selection (#7109) -----------------
# Not a new representation: the SAME canonical i32/u32/Str reps, selected in the
# program-entry body, which `codegen/entry.rs` excluded wholesale until #7109.
# Registered because the GC contract differs by POSITION, not by rep — a
# top-level canonical `Str` keeps its shadow-slot binding in the one frame that
# also runs the `globalThis` population and every dependency's `__init`, and a
# top-level canonical i32 has no binding at all. Both need exercising against
# the evacuating arms in that frame, not only inside a callee.
test_gap_repsel_module_init_canonical

# --- Raw pointer publishes into traced payload slots (#7154) -----------------
# Not a representation file, so registered explicitly per the header rule.
# Three runtime helpers published a pointer into a GC-traced slot with a raw
# `*slot = bits`, skipping `layout_note_slot` (so the holder stayed
# `POINTER_FREE` and `heap_payload_slot_selection` skipped its WHOLE payload)
# and the write barrier (so an old->young edge never reached the remembered
# set). Every subject is read AFTER the churn that drives collection, so a
# non-moving collection cannot satisfy it. Verified to FAIL on the pre-fix
# stores (SIGBUS, exit 138) and pass after.
#
# ***THIS FILE IS LIVE ONLY ON THE MOVING ARMS, AND THAT IS THE POINT.***
# Measured 33 copying minors under `PERRY_GC_MOVING_LOOP_POLLS=1` /
# `evac_minor` / `force_evac`, and ZERO under the shipped default -- because
# #7161 flipped the evacuating minor off by default pending #7154. So its
# `default` / `verify_evac` / `cons_scan_off*` cells are UNVER, correctly: the
# bug it guards cannot manifest without a moving collector, and a PASS there
# would be the false confidence this harness exists to remove.
#
# The consequence to keep in view: `loop_polls` (and the other `requires=move`
# arms) are now the ONLY place this file bites end to end. If #7161 is ever
# reverted the cells flip to PASS on their own; if instead the moving-loop knob
# is deleted, this file goes dark and must be re-tuned or retired with it. The
# collector-side coverage does NOT depend on that knob: the three Rust tests in
# `gc/tests/copying/pointer_publish_7154.rs` call `gc_collect_minor` directly,
# which takes the copying path regardless, and each asserts `copied_objects > 0`.
test_gap_gc_pointer_publish

# --- The GC-live member ------------------------------------------------------
# Every file above performs ZERO collections (measured, #6950), which makes the
# GC arms inert against them. This one holds each representation's local live
# across escaping allocation churn heavy enough to reach the collector, so the
# "collect" arms actually bite. Keep it registered and keep it collecting.
test_gap_repsel_gc_stress


# --- The three residual root-store holes (#7200, #7201, #7202) --------------
# Not representation files, so registered explicitly per the header rule.
#
# ***ALL THREE ARE LIVE ONLY ON THE `requires=move` ARMS, AND THAT IS THE
# POINT.*** Each is clean under the shipped default, because #7161 flipped the
# evacuating minor off pending #7154 — so their `default` / `verify_evac` /
# `cons_scan_off*` cells are UNVER, correctly. `loop_polls` is where they bite
# end to end today; when #7161 is reverted the rest flip to PASS on their own.
#
# Measured on `origin/main` (3a983ca6a), compiled AND run with
# `PERRY_GC_MOVING_LOOP_POLLS=1`:
#   spread_accessor_rooting     exit=139 (SIGSEGV) 3/3
#   static_block_this_rooting   `bad 4` 3/3 (deterministic)
#   inline_ctor_this_rooting    green — see its own header; it is a STATIC
#                               gate, pinned by the codegen test and by
#                               `gc_root_dominance_check.py --unrooted-allocas`
#
# #7217 UPDATE: `spread_accessor_rooting` and `static_block_this_rooting` are now
# ALSO green on the ALLOCATION-POINT route, which they were not when #7207
# landed. That route's failure was never in the code #7207 fixed: it was minor #0
# landing inside the lazy `globalThis` bootstrap, which threads raw
# `*mut ObjectHeader` installer locals across its own ~1.15 MB of allocations.
# The bootstrap now runs in a no-move window. Measured on `origin/main`
# (8b024958f) vs this branch, same profile, same host, idle, 10 runs each, at
# `PERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0 PERRY_CONSERVATIVE_STACK_SCAN=off`
# with NO compile-time GC env:
#   spread_accessor_rooting     exit=139 10/10  ->  `plain 0 hot 0 tail 0` 10/10
#   static_block_this_rooting   `bad 1`  10/10  ->  `bad 0`               10/10
#   inline_ctor_this_rooting    green    10/10  ->  green                 10/10
test_gap_gc_spread_accessor_rooting
test_gap_gc_static_block_this_rooting
test_gap_gc_inline_ctor_this_rooting

# --- The catch parameter and the closure `this` capture (#7209, #7208) -------
# Two more sites from the #7202 bare-alloca enumeration, both reserved-or-
# missing shadow slots rather than late root stores.
#
# Measured on `origin/main` (1679e22b4), compiled AND run with
# `PERRY_GC_MOVING_LOOP_POLLS=1`:
#   catch_param_rooting          `message 8 field 8` of 400, 3/3 deterministic
#   closure_this_capture_rooting exit=139 (SIGSEGV) 3/3; `bad 4` on the
#                                evacuating arm
#
# `catch_param_rooting` is the one file in this corpus that can go wrong under a
# NON-moving precise-roots collection as well: `js_clear_exception()` drops the
# runtime's own reference before the catch body runs, so with the slot unbound
# the exception is reachable from nothing the collector scans and can be SWEPT,
# not merely relocated. Keep it exercised on `cons_scan_off`, not only on the
# `requires=move` arms.
test_gap_gc_catch_param_rooting
test_gap_gc_closure_this_capture_rooting

# --- Stale REGISTERS: a receiver and a computed base (#7206) -----------------
# A different failure from every entry above. The sites above are root stores
# that are late, mis-indexed or missing; these two values are never rooted at
# all. Spec order evaluates the object first and the sibling operand second, so
# `recv.m(f())` and `o[f()]` leave the base in a bare SSA register while `f()`
# runs. It SURVIVES the minor -- the capture cell or module global it was read
# from is a root -- and therefore MOVES, the collector rewrites that location,
# and the register keeps naming from-space. A bare register is not a root, so
# no runtime GC probe can see it.
#
# Measured on `origin/main` (91170973c), compiled AND run with
# `PERRY_GC_MOVING_LOOP_POLLS=1`, oracle node 26.5.1 (`bad 0` for both):
#   method_receiver_rooting    `TypeError: value is not a function`, and
#                              exit=139 (SIGSEGV) under PERRY_GC_SCHEDULE_SEED=1 PERRY_GC_SCHEDULE_RATE=1
#                              + PERRY_GC_PROTECT_FROMSPACE=1
#   index_get_receiver_rooting `TypeError: Cannot read properties of undefined
#                              (reading 'v')`
# Both are `bad 0` with #7206 applied, and `bad 0` on the shipped default on
# BOTH sides -- the default cannot express the bug, which is why these belong on
# the `requires=move` arms and prove nothing on `default`.
test_gap_gc_method_receiver_rooting
test_gap_gc_index_get_receiver_rooting

# --- Stale REGISTERS on the generic dynamic call (#7214) ---------------------
# The same class as #7206 above, on the hottest call path the compiler emits.
# `js_closure_callN` held THREE classes of GC value in bare registers, each
# across a DIFFERENT window:
#
#   receiver | the callee read off it + every argument
#   callee   | every argument
#   argument | the arguments after it + the rebind unbox
#
# The rebind unbox is a collection point that exists only on the member-shaped
# path: `js_closure_unbox_callee_checked_rebind` calls
# `clone_closure_rebind_this`, which allocates a replacement closure when the
# callee captures `this`. It sits BELOW the last argument and ABOVE the
# dispatch, so the arguments are re-read after it. The receiverless path takes
# `js_closure_unbox_callee_checked` -- a tag check and a mask that allocates
# nothing -- so `f(x, y)` on inert operands emits byte-identical IR.
#
# Measured with #7206 applied but NOT #7214, i.e. these are exactly the sites
# #7206 left open. Compiled AND run with `PERRY_GC_MOVING_LOOP_POLLS=1`,
# oracle node 26.5.1 (`bad 0` for all three):
#   closure_call_callee_rooting    `TypeError: value is not a function`
#   closure_call_this_rooting      `TypeError: value is not a function`
#   closure_call_argument_rooting  `TypeError: value is not a function`
# All three are `bad 0` with #7214 applied, including under
# PERRY_GC_SCHEDULE_SEED=1 PERRY_GC_SCHEDULE_RATE=1,
# and clean on the shipped default on BOTH sides -- so they certify nothing on
# the `default` arm and belong with the `requires=move` rows.
test_gap_gc_closure_call_callee_rooting
test_gap_gc_closure_call_this_rooting
test_gap_gc_closure_call_argument_rooting

# --- A THIRD witness that was registered nowhere (#7252) --------------------
# `test_gap_gc_call_argument_rooting` shipped with #7252 (cross-module direct
# call argument rooting, authored by @jdalton, restacked at merge). Same defect
# as #7192 and #7216 above: the PR added the witness and did not add the line.
# It was dark from merge until the `test_gap_gc_*` enforcement added for those
# two caught it on the first `GC Moving Witnesses` run on main -- which is the
# enforcement working exactly as designed, on the third occurrence of the same
# mistake. Registering the file is the whole fix.
test_gap_gc_call_argument_rooting

# #8036: a NextRequest-shaped class crosses a generated-route-style re-export
# into an imported sync/async handler and stays live through allocation,
# dynamic import, promise/timer continuations, and body reads.
test_gap_gc_next_request_import

# --- #7270's two witnesses, registered here rather than dark -----------------
# `rest_argument_rooting` and `same_module_call_argument_rooting` shipped with
# #7270. Registering them at merge rather than after: they are the FOURTH
# instance of the add-a-witness-forget-the-line defect (#7192, #7216, #7252
# above), and #7252's was caught only by the `test_gap_gc_*` enforcement firing
# on the first main-line `GC Moving Witnesses` run after #7253 gave that matrix
# a trigger at all. Four occurrences is a process problem, not four mistakes.
test_gap_gc_rest_argument_rooting
test_gap_gc_same_module_call_argument_rooting

# --- Two witnesses that were registered nowhere (#7192, #7216) ---------------
# Both files exist in test-files/, both say in their own headers that they are
# LIVE BY CONSTRUCTION and bite only on the moving arms, and neither was in this
# manifest -- so neither ran anywhere, not in `gc-stress --arms pr` and not in
# `--arms all`. `new_instance_rooting` shipped with #7192 and
# `assign_string_source_rooting` with #7216; both PRs registered their siblings
# and missed these.
#
# The UNREGISTERED gate in scripts/gc_repsel_matrix.sh cannot catch this: it
# auto-detects only `test_gap_repsel_*` / `test_gap_specabi_*`, and this file's
# header rule for every other prefix ("register them explicitly") was enforced
# by nothing. It is enforced for `test_gap_gc_*` now --
# .github/workflows/gc-moving-witnesses.yml requires every
# test-files/test_gap_gc_*.ts on disk to appear as a cell, which is how these
# two were found.
#
# Measured on this commit (7d1dc9ca2), `--arms loop_polls --filter test_gap_gc_`:
# both PASS with copy-minor > 0, i.e. they relocate and they are byte-exact.
# Also measured on the `gc-stress` PR arm set: PASS/UNVER only, no new red.
#
# #7217 UPDATE for `assign_string_source_rooting`: partly improved by #7217's
# no-move bootstrap window (`bad char 3 count 3` -> `bad char 1 count 1`, 10/10
# each) but NOT green. The residual iteration is a stale `js_eq` left operand in
# the test's own `got !== ALPHA[i % 26]` assertion, not anything in
# `js_object_assign_one` — tracked as #7248 with the emitted IR; the triage
# entries are retargeted there.
test_gap_gc_new_instance_rooting
test_gap_gc_assign_string_source_rooting

# --- UNROOTED CACHES: a different failure class entirely (#7211) -------------
# Every entry above is a stale REGISTER or a late/mis-indexed/missing root
# STORE. In all of them the value is reachable from something the collector
# scans and the defect is that one location did not get rewritten. These two are
# neither: a thread-local cache and a swapped-out root cell hold a GC pointer
# that NOTHING registers, so the FIRST collection kills it and the holder names
# abandoned memory for the rest of the process.
#
# The distinction decides which instrument can find them:
#
#   stale register (#7154 class) | goes bad only when a collection lands in a
#                                | narrow window -- timing-dependent, needs a
#                                | workload plus repetition; the static IR pass
#                                | CAN see the shape
#   unrooted cache (#7211)       | goes bad at collection #0 and stays bad --
#                                | reproduces 10/10; INVISIBLE to the static
#                                | pass, which reads emitted LLVM IR and cannot
#                                | see a runtime-side table at all
#
# So a CI arm that runs real workloads under PERRY_GC_MOVING_LOOP_POLLS=1 is the
# ONLY thing that catches this class. Registering them here is what puts them in
# front of it -- .github/workflows/gc-moving-witnesses.yml runs every registered
# `test_gap_gc_*` on the `loop_polls` arm and refuses to pass a cell that did
# not relocate.
#
# Measured on this branch, release, `--arms loop_polls --filter test_gap_gc_`:
# both PASS with the copying minor live (the arm reported copy-minor 16/16 across
# the whole gc-witness set). Measured on the #7227 branch, which is #7226 plus
# the regexp-receiver fix -- a different subsystem, and not something that could
# make a typeof cache or an implicit-this restore pass.
test_gap_gc_typeof_string_cache_rooting
test_gap_gc_closure_call_prev_this_rooting
# --- The regexp receiver across ToString (#7154 residual) --------------------
# `re.test(s)` / `re.exec(s)` unboxed the receiver to a raw `RegExpHeader*` and
# only THEN emitted `js_jsvalue_to_string_coerce`, which allocates and can
# dispatch a user `toString` -- arbitrary JS with its own back-edge polls. #7226
# measured this residual and named it rather than claiming closure; this file is
# it, and it is the last thing between `sfw-registry --help` and 30/30 under a
# genuine polls build.
#
# It is also the witness for the CHECKER half of the same bug. `ALLOC_RE` spelled
# the allocator `regexp_alloc\w*` while the real call is `js_regexp_new`, so the
# static pass had no recognised heap-value source for the register and reported
# nothing at all. That hole is gated now (`--audit-alloc-re`, which requires
# every alternative to match a real exported symbol), but a static gate cannot
# prove the FIX -- only running the program under a moving collector does, and
# only the `requires=move` arms do that.
#
# Measured on this branch, release, `--arms loop_polls --filter test_gap_gc_`:
# PASS on `loop_polls` 8/8 (`bad 0`, matching the oracle), and exit=139 on all
# TEN allocation-point arms, also deterministic. See gc_repsel_triage.txt: the
# fix in this PR is verified on the safepoint route and is NOT claimed on the
# allocation-point route.
#
# #7217 UPDATE: that class turned out to be TWO unrelated sites, not one. #7217
# itself (the lazy `globalThis` bootstrap) is fixed and this file is UNCHANGED by
# it — exit=139 10/10 both before and after. Its own site is `js_regexp_new`
# holding `&str` borrows into a movable `StringHeader` across its whole body,
# tracked as #7247; the triage entries are retargeted there.
test_gap_gc_regexp_receiver_rooting
# --- #7210: argument staging buffers filled interleaved with lowering -------
# `setTimeout(cb, 0, {…}, churn())` kept the CALLBACK closure in a bare SSA
# register across every argument's lowering, and each trailing argument in a
# bare `alloca_entry_array` the precise root walk never visits, while the next
# one was lowered. Measured at base (`7d1dc9ca2`), compiled and run under
# `PERRY_GC_MOVING_LOOP_POLLS=1 PERRY_GC_HEAP_LIMIT=8`: 5/5
# `TypeError: The "callback" argument must be of type function. Received an
# instance of Object` — deterministic, and identical on the `evac_minor` arm.
# Clean 5/5 on the shipped default (no polls, no pressure) both before and
# after, so this is a `requires=move`-only witness like the #7207 trio.
test_gap_gc_staging_args_rooting

# --- #7210 runtime half: setInterval trailing args were in no root ----------
# `scan_timer_roots_mut` walked `args` for CALLBACK_TIMERS and for both
# MOCK_TIMERS lists, and NOT for INTERVAL_TIMERS; the incremental twin
# `scan_interval_timers_step` had the same hole, and cycle-based collections run
# only the step scanner. A partially-correct scanner is worse than an absent one
# — it reads as covered. Measured at base: `BAD interval.a` from tick 2 onward.
test_gap_gc_interval_args_rooting

# --- #7231: unrooted runtime-side caches (nothing static can find these) ----
# `js_process_env_impl` builds `process.env` once with `js_object_alloc` (the
# NURSERY) and stores it in a thread-local `Cell<f64>` that no scanner visited.
# That cell is the whole reference graph — `process.env` is a getter CALL, not
# a field of `process` — so the first minor swept or evacuated the object and
# every later `process.env.X = v` wrote through a dangling pointer.
#
# The observable is ENUMERATION (`Object.keys(process.env)`, spread, `for…in`),
# because a direct `process.env.KEY` read lowers to `js_getenv` and asks the OS.
#
# Measured at base (`c9cd73ba5`), compiled and run under
# `PERRY_GC_MOVING_LOOP_POLLS=1` with `PERRY_GC_INCREMENTAL=0
# PERRY_CONSERVATIVE_STACK_SCAN=off PERRY_GC_HEAP_LIMIT=8`: **SIGBUS 10/10**,
# `[gc-fromspace-protect] obj_type=2 size=416 … RETIRED FROM-SPACE`. Clean 5/5
# on the shipped default both before and after, so this is a `requires=move`
# witness. 10/10 green after, byte-exact vs node 26.5.1.
#
# The determinism is the diagnostic signature (#7226): an unrooted CACHE goes
# bad at collection #0 and stays bad, where an unrooted REGISTER needs a
# collection to land in a narrow window.
test_gap_gc_process_env_cache_rooting

# --- #7236: a Symbol-typed local had no shadow slot at all ------------------
# Not a representation file, so registered explicitly per the header rule.
# `collectors/pointer_locals.rs` classified `Type::Symbol` as a NON-pointer, so
# `const s = Symbol()` got no shadow-stack slot and lived in a plain `alloca` —
# the last violation `gc_root_dominance_check.py --unrooted-allocas
# --moving-only` reported on the #7235 corpus, and the one thing standing
# between `gc-root-dominance` and promotion to a required context (#7198).
#
# ★ THE FAILURE IS A PREMATURE FREE, NOT A STALE ADDRESS, and that dictates the
# shape of the file. `alloc_symbol` is `gc_malloc(_, GC_TYPE_STRING)` and
# `gc_malloc` is the SYSTEM allocator with a GcHeader in front, not an arena
# allocation — so the copying minor cannot relocate a symbol, it can only
# `dealloc` it. Under #7235's taxonomy the local is RECLAIMABLE and not
# MOVABLE. `SYMBOL_POINTERS` does not save it:
# `scan_symbol_pointer_metadata_roots_mut` uses `visit_metadata_usize_slot`,
# which rewrites without marking, exactly as `alloc_symbol`'s own comment says
# ("kept alive ... or NOT AT ALL").
#
# So the pressure is SYMBOLS, not objects. `copied_minor_malloc_sweep_due`
# gates the malloc sweep on a `MallocCount` trigger or the malloc-object
# threshold; object churn reaches the ARENA trigger instead, and with it the
# defect reproduces intermittently — measured at 1 failure in 80 probes on one
# object-churn shape, and 0 on four of five repeats of another. Symbol churn
# makes the malloc-count trigger the one that fires AND makes the freed block
# the exact size class the next `Symbol()` reuses.
#
# ★ THE PROBE SYMBOL DELIBERATELY HAS NO DESCRIPTION, so this file gates ONE
# defect. `GC_TYPE_STRING` is `pointer_free: true` / `Leaf`, so a symbol's
# `description` StringHeader is never traced or rewritten and can be reaped out
# from under a perfectly rooted symbol — a SEPARATE runtime-side defect (#7246)
# that #7236 does not touch. With `Symbol("k")` this file stayed red 2/20 on the
# `PERRY_GC_INCREMENTAL=0 PERRY_CONSERVATIVE_STACK_SCAN=off` arms AFTER the
# fix; with `Symbol()` there is no such pointer and what remains is exactly the
# lifetime of the symbol object. If those two arms ever go red here again,
# suspect the description defect before suspecting the classification.
#
# Measured with `--arms all --pressure 8`, oracle node 26.5.1 (`B 0`):
#   f8f1e7188 (base)  21/21 cells FAIL, 0/21 byte-exact (5 of them exit=1)
#   with the fix      0 FAIL, 21/21 byte-exact, PASS=17 UNVER=4
# It does NOT need a `requires=move` arm: the conservative stack scan is what
# was hiding it, and a malloc-count-driven collection stops it hiding, so it is
# red on the SHIPPED DEFAULT at base too.
test_gap_gc_symbol_local_rooting

# --- #7280: RUNTIME-DISPATCHED `new` — the instance in a Rust frame ----------
# The sibling of `test_gap_gc_new_instance_rooting` one layer down. That file
# covers the CODEGEN route: `new C(n)` with a statically-known `C`, where the
# caller holds the instance itself. This one covers the four routes where
# codegen cannot resolve the callee and hands the whole construction to
# perry-runtime's `js_new_function_construct` family:
#
#   1. `new obj.ctor(n)`, `ctor` a declared class     -> construct_registered_class_ref
#   2. `new obj.ctor(n)`, `ctor` a plain function     -> the closure tail
#   3. `new obj.ctor(n)`, `ctor` a class EXPRESSION   -> the class-object arm
#   4. `Reflect.construct(fn, [n], otherFn)`          -> ..._with_new_target
#
# Each allocated the instance, called back into user JS to run the constructor
# body, and returned the pointer it had captured BEFORE that call. A runtime
# frame is not visited by the precise root walk and the conservative stack scan
# resolves to SkipDisabled in shipped builds, so the instance moved and the
# helper returned the pre-move address — a stale receiver produced OUTSIDE the
# emitted IR, which is why `scripts/gc_root_dominance_check.py` is structurally
# blind to it (it reads LLVM IR; none of this is in LLVM IR).
#
# Measured on this branch, release, `PERRY_GC_MOVING_LOOP_POLLS=1` at compile:
#   before the fix, POLLS=1 + RATE=1     200/200 iterations wrong on EVERY route
#   after  the fix, POLLS=1 + RATE=1     10/10 runs byte-exact with the oracle
#   control, + PERRY_GEN_GC=0            clean on both sides
# and under the loop_polls arm env
# (`HEAP_LIMIT=8 INCREMENTAL=0 CONSERVATIVE_STACK_SCAN=off POLLS=1 FORCE_EVACUATE=1`)
# red before / clean after, which is the arm `gc-moving-witnesses` runs.
#
# ONE FILE, FOUR ROUTES, FOUR SEPARATE console.log LINES. The matrix byte-diffs
# stdout, so a regression on any single route names itself instead of collapsing
# into one `bad N`.
test_gap_gc_dynamic_construct_receiver_rooting

# --- #9081: INLINED CONSTRUCTOR BODIES — spliced locals need the SPLICING ----
# frame's slots. The super() parent-body inline, the `new`-site own/inherited
# ctor inlines, and let_stmt's scalar-ctor variants all lower a ctor body into
# the enclosing function, whose shadow-slot map was computed from that
# function's own body alone — the spliced body's pointer locals (and bound
# ctor params) sat in plain entry allocas the collector never rewrites. This
# is how three.js `new WebGLRenderTarget()` lost RenderTarget's `texture`
# local across `this.textures = []` under a small nursery. The test's
# parity-env quarantine turns the stale deref into a SIGSEGV on a regression;
# the stamp read-back catches it by value when the pages were recycled.
test_gap_gc_inlined_ctor_body_locals_rooting

# --- #7280: OPTIONAL PARAMETERS — the residual that held #7161's stopgap -----
# THE file for #7154's surviving residual. zod `util.ts`'s
# `clone(inst, def?, params?: { parent: boolean })` gave `params` no shadow slot
# even though HIR types it `Object(...)` and `type_is_pointer_bearing` says
# true, so it crossed `new inst._zod.constr(...)` in callee-saved `d8` and
# `params?.parent` dereferenced retired from-space.
#
# The cause is the OPTIONAL MARKER, not the object type. The pointer-locals
# refinement fixpoint proves a local non-pointer from its WRITES, and a
# PARAMETER's write list is a strict SUBSET of its definitions — the incoming
# argument is not a write. The optional-parameter desugaring then donates the
# one write that completes the false proof, for free, on every optional
# parameter in the program:
#
#     if (p === undefined) { p = undefined; }
#
# Two functions because the fixpoint draws TWO conclusions from that subset and
# both are unsound for a parameter:
#   `annotated` — the declared-Object shape (zod's). Needs the `all_non_pointer`
#                 exclusion.
#   `inferred`  — `p?: any` plus a local aliased from it. Needs the
#                 `precise_inference` exclusion as well: measured RED 200/200
#                 with only the first exclusion applied.
#
# Measured on this branch, release, `PERRY_GC_MOVING_LOOP_POLLS=1` at compile:
#   before, POLLS=1 + RATE=1            200/200 iterations wrong, both shapes
#   after,  POLLS=1 + RATE=1            byte-exact with the oracle
#   after,  loop_polls arm env          byte-exact
#   control, + PERRY_GEN_GC=0           clean on both sides
# and on the real workload, `sfw-registry --help` under
# `PROTECT_FROMSPACE=1 DEPTH=800 POLLS=1` (no schedule): 10/10 FAULT before,
# 40/40 clean after.
test_gap_gc_optional_param_receiver_rooting

# --- #7475: the array-iterator drain (`[...obj.arr]` / Array.from) ----------
#
# The FIRST witness in this corpus that fails on the SHIPPED DEFAULT, not only
# on a `requires=move` arm. It is registered here anyway, because the moving
# arms are what make its failure a fault instead of a wrong answer.
#
# `js_iterator_to_array` held five live values in bare Rust locals across a
# `.next()` call that allocates the `{ value, done }` object: the iterator, the
# accumulator array, the `next` closure, and the two key strings — and one
# level down `make_iter_result` held the element value and its own freshly
# allocated result object across four more allocations. A copying minor moved
# them and rewrote only the slots it can see. The iterator's pre-move copy then
# handed `dispatch_array_iterator_method` a from-space backing array, which is
# where `js_array_length` faulted.
#
# Found through the auto-optimize link (#7475): `object_deep_clone` threw
# `TypeError: next is not a function` there while printing the right checksum
# under `PERRY_NO_AUTO_OPTIMIZE=1`. The feature-stripped runtime did not have a
# different bug — `PERRY_GC_PROTECT_FROMSPACE=1` faults on BOTH links, at the
# same minor #0 — it allocated in the order that made the stale read visible.
#
# Measured on this branch:
#   before, default link + default env     TypeError: next is not a function
#   before, auto-optimize link             TypeError: next is not a function
#   before, PROTECT_FROMSPACE=1 DEPTH=200  FAULT at js_array_length inside
#                                          dispatch_array_iterator_method, both links
#   after,  both links                     byte-exact with the oracle
#   after,  PROTECT_FROMSPACE=1 DEPTH=200  that fault is GONE; a DIFFERENT one
#                                          remains (#7498)
#
# THE INSTRUMENT IS NOT CLEAN AFTER THE FIX, and saying otherwise would be the
# overclaim this repo keeps paying for. `[...o.meta.tags]` reaches the drain
# through `array_from_spread_value`, whose `[Symbol.iterator]` prototype walk
# has its OWN stale from-space deref — `array_prototype_property_value` →
# `default_object_prototype_property_value` → `js_object_get_field_by_name`, on
# a 56-byte GC_TYPE_STRING at minor #3. That is #7498, unfixed here: it does not
# corrupt this file's result, so the file gates the drain and nothing else. When
# #7498 lands, a protected run of this file should go silent — if it does not,
# there is a third site.
#
# #7498 HAS LANDED AND THIS FILE DID NOT GO SILENT — so, per the sentence
# above, THERE IS A THIRD SITE, and it is filed as #7528. #7498's
# two faults are gone from this file's protected run; what remains is
# `js_native_call_method` reading its ROOTED receiver into a local ~330 lines
# and a dozen allocating probes before `is_closure_ptr` dereferences it
# (`array_from_spread_value` → `js_closure_call0` → `js_native_call_method +
# 5604`). Measured on the #7498 branch, macOS/arm64: this file prints
# `badLen 1` instead of `badLen 0` (5/5), and `PERRY_GEN_GC=0`,
# `PERRY_GC_SCAVENGE=0` and `PERRY_WRITE_BARRIERS=0` each make it correct while
# `PERRY_GEN_GC_EVACUATE=0` does not — the copying minor, not policy
# evacuation. Patching the one faulting line was tried and REVERTED: the fault
# simply moved 800 bytes further into the same function, which is the signal
# that the whole receiver needs re-reading, not one arm of it.
test_gap_gc_iterator_drain_rooting

# --- #7497: the globalThis builtin-value lookup -----------------------------
#
# `js_get_global_this_builtin_value` read the `globalThis` object out of its
# root into a raw `*const ObjectHeader` and only THEN allocated the lookup key.
# The root is correct and IS rewritten by evacuation; the raw copy already read
# out of it is not. That lookup interns nothing, so every call mints a fresh
# string, and any of those allocations can be the copying minor that moves
# `globalThis` — after which `js_object_get_field_by_name` dereferences retired
# from-space.
#
# `Promise.all` is what finds it: each element runs `Promise.resolve`, and
# `js_promise_resolve_spec` asks `is_default_promise_constructor` for
# `globalThis.Promise` through that function. One 50 000-element combinator
# call therefore performs 50 000 of these lookups, so one straddles the
# collection. That is why the app-pattern kernel `promise_all_chains`
# (1000 x 50) needed its full scale while a single wide call needs 0.2 s.
#
# Measured on this branch, release, PERRY_NO_AUTO_OPTIMIZE=1 and default env:
#   before  Uncaught (in promise) TypeError: value is not a function
#   before, PROTECT_FROMSPACE=1 DEPTH=200
#           FAULT in js_object_get_field_by_name, reached from
#           js_get_global_this_builtin_value <- js_promise_resolve_spec <-
#           spec_combinators::call_with_this <- run_combinator <-
#           js_promise_all_iterable, on a GC_TYPE_OBJECT at minor #0
#   after   byte-exact with the oracle, and that fault is gone
#
# The failure is NOT arm-conditional — it fails on the shipped default — but it
# is registered here because the moving arms are what turn it into a fault with
# an address instead of a wrong answer.
test_gap_gc_global_builtin_lookup_rooting
# --- #7498: the OTHER `[...obj.arr]` lowering (inlined → the symbol walk) ----
#
# The spread has two lowerings. Out of line it calls `js_iterator_to_array`
# (above). Inlined, it routes through `array_from_spread_value`, which resolves
# `[Symbol.iterator]` through the whole prototype-walk tower first — and three
# frames on that walk held a GC value the collector cannot see:
#
#   * `req_handle_symbol_fallback` read the receiver into a `usize`, interned a
#     `"_req"` key (an allocation), then read a field off the PRE-move address.
#     It runs on every heap-object symbol read whose own-symbol lookup missed,
#     so the window is unconditional — 5/5, not intermittent.
#   * `array_prototype_property_value` and the array/object arms of
#     `get_field_by_name_object_tail` took the property name as a `&str` / a
#     `&[u8]` BORROWED OUT OF THE KEY'S `StringHeader`. No root fixes that: a
#     borrow is not a slot the collector can rewrite. They copy the bytes off
#     the heap before their first allocation now.
#   * `array_from_spread_value` carried the spread receiver through a dozen
#     probes and the entire walk, then used it to rebind `this` for the
#     factory.
#
# LATENT BY CONSTRUCTION, like `10_store_receiver_across_alloc.ts`: evacuation
# copies rather than zeroes, so the stale read returns the correct old bytes and
# this file printed the RIGHT checksum before the fix, on both links. Only
# unmapping retired from-space makes it a signal. Measured on this branch:
#
#   before, default link + default env      byte-exact with the oracle
#   before, auto-optimize link              byte-exact with the oracle
#   before, PROTECT_FROMSPACE=1 DEPTH=200   FAULT 5/5 on BOTH links, at
#                                           array_from_spread_value →
#                                           js_object_get_symbol_property →
#                                           js_object_get_field_by_name_f64 →
#                                           js_object_get_field_by_name, on a
#                                           40-byte GC_TYPE_ARRAY (the pre-move
#                                           copy's keys_array) at minor #1
#   after,  both links                      byte-exact with the oracle
#   after,  PROTECT_FROMSPACE=1 DEPTH=200   CLEAN 5/5 on BOTH links, with the
#                                           instrument proved live: four
#                                           `retired_set=#N` page-sets and
#                                           `[gc-copy-minor] ran
#                                           copied_objects=11794` on the first
#                                           minor alone
#
# The auto-optimize arm was A/B'd with the runtime archive rebuilt from source
# on both sides (`perry-auto-*`, asserted on the `[link] invoking:` line), so
# the two rows are the same link with different runtime source, not two links.
#
# KEEP `clone` SMALL. Its size is the whole point: grow it and the spread goes
# back out of line onto `js_iterator_to_array`, i.e. onto #7475's path, and this
# file stops covering the walk.
test_gap_gc_spread_symbol_iterator_rooting
test_gap_repsel_element_shape_loop_clone

# --- #7682: an allocation-point collection must not MOVE anything -----------
#
# NOT a rooting witness like every entry above it, and the difference is the
# point. The others plant a value that codegen failed to root and let a moving
# collection expose it. Here codegen's root discipline is COMPLETE for the
# program — `scripts/gc_root_dominance_check.py` reports 0 violations over 380
# root stores — and the collector still relocated a value out from under a
# register, because it ran an *evacuating* minor at an arbitrary allocation
# point (`gc_check_trigger`'s nursery-churn arm) rather than at a declared
# safepoint. `PERRY_GC_SCAVENGE` gated off the `force_full_scan()` that keeps
# that collection non-moving. See `changelog.d/` for the full route.
#
# NOT LATENT: it prints a wrong NUMBER, not a wrong-but-plausible one. A
# tree-walking interpreter's variable lookup misses bindings that are present,
# and naive `fib` — a count of leaves returning 1 — comes back short by exactly
# the miss count. Measured under the gap harness's own PERRY_NO_AUTO_OPTIMIZE=1:
#
#   before (a853135aa runtime)   1708662 / 1708662 / 1708662   oracle 1708840
#   after                        1708840 3/3, and 1708840 with auto-optimize
#
# On the `loop_polls` arm this file is a moving-collector witness like the rest
# — back-edge polls put the copying minor at the safepoints, which is where it
# is supposed to be.
test_gap_gc_alloc_point_no_move

# #7210 §2/§3 and #7640 section A (PR #7736). Three rooting fixtures whose
# hazards are all "a value staged in a register or a plain stack buffer while
# an allocating helper runs" — the shape the matrix's moving arms exist to
# catch, so they belong here rather than only in the dominance corpus.
#
#   class_field_receiver          the typed-`this` field store's receiver
#   index_set_bounded_globalthis  the bounded-index array store and
#                                 `globalThis[k] = v`'s key + singleton copy
#   namespace_and_computed        `emit_namespace_populator`'s `vals_buf`
#                                 staging, and `obj[strKey](args)` computed
#                                 dispatch across `unbox_str_handle`
test_gap_gc_class_field_receiver_rooting
test_gap_gc_index_set_bounded_globalthis_ta_rooting
test_gap_gc_namespace_and_computed_dispatch_rooting

# #7949 (PR #7962): Rust-side containers retained raw JS values across
# allocating callbacks and property operations. These two witnesses shipped
# with that fix but were never registered, leaving their moving-GC coverage
# dark and the registration lint red on main.
test_gap_gc_container_value_rooting
test_gap_gc_define_properties_key_rooting

# #7979: comparison operands are lowered left-to-right, so the first call
# result must remain rooted while an allocating second call runs. This is the
# #7963 defineProperty witness restored to its natural inline verdict shape;
# retired-from-space protection made the pre-fix js_eq stale read fault.
test_gap_gc_define_property_descriptor_rooting

# #7766 (PR #7778): the element-binding clone through a function boundary.
# Lying callers force the slow path while the honest arm's clone reads raw
# f64 slots off relocatable objects — live on the evacuating arms (9 copying
# minors / 15,843 objects moved under zeal at audit time).
test_gap_repsel_element_shape_param_binding

# #7770 (PR #7774, registered by #7778): the element-group numeric-field
# proof. Low-allocation read loop — its cell can run zero copying minors on
# a filtered arm (the arm-level liveness gate is the vacuity guard); its
# value here is byte-parity under every arm plus raw-f64 loads on proven
# fields whenever relocation does land.
test_gap_repsel_element_group_numeric

# #7949/#7962 and #7978: runtime-held JS values and the full
# Object.defineProperty receiver/key/descriptor chain must remain rooted while
# allocating helpers run. These witnesses are specifically intended for the
# moving-GC matrix, where a stale raw copy becomes observable.
test_gap_gc_container_value_rooting
test_gap_gc_define_properties_key_rooting
test_gap_gc_define_property_descriptor_rooting

# #8179 (this PR): `dispatch_uint8_buffer_method` — the shared uint8
# `%TypedArray%.prototype` dispatcher every Buffer-backed `Uint8Array` callback
# method funnels through — held the receiver address, the NaN-boxed receiver
# handed to callbacks, `map`'s freshly allocated result buffer,
# `sort`/`toSorted`'s permuted output and `reduce`'s accumulator in bare Rust
# locals across `js_closure_call{2,3,4}`, and rooted the callback closure on
# only ONE of its three entries. The CLOSURE is the live half: it is an
# ordinary nursery allocation that MOVES, and on two of those
# three entries the raw parameter was its only reference. Pre-fix this file
# throws `TypeError: value is not a function` on the shipped default and dies on
# the FIRST collection under `PERRY_GC_SCHEDULE_RATE=1 PERRY_GC_SCHEDULE_SEED=7
# PERRY_GC_PROTECT_FROMSPACE=1`. Callbacks here allocate inside a LOOP on
# purpose — a back-edge poll is the only safepoint reachable from user JS, so an
# allocation-free callback makes the whole file pass vacuously.
test_gap_gc_uint8_buffer_callback_rooting

# Census follow-up (gc_runtime_root_holders ffi/ext coverage): runtime-side
# side-table holders. Each parks a NaN-boxed value in a Rust static across a
# GC window; the fixture drives the owning subsystem (fetch caches, Request
# signal registry, ext-net once-flags, ext-http H2 pending-event queue) with
# allocating churn between park and use.
test_gap_gc_fetch_method_value_cache_rooting
test_gap_gc_request_signal_rooting
test_gap_gc_net_once_flags_rekey
test_gap_gc_http2_pending_event_callback_rooting

# Interpreter global-`this` rooting on the `Function("return this")()` /
# dyn_eval path (#6559). `dyn_function_from_strings` obtained the global from
# `js_get_global_this()` but did NOT root it before `env_new_root()`
# allocated a scope object. A copying minor at that allocation evacuates the
# global singleton — `THREAD_GLOBAL_THIS` is rewritten, the raw local is not.
# The stale pointer flows into the closure's capture slots 3 (global) and 4
# (intrinsics); the sloppy-mode `this = global` then returns undefined, and
# `Object.getPrototypeOf(undefined)` throws. Pre-fix `bad > 0` under seeded
# GC; post-fix `bad 0`. Clean under `PERRY_GEN_GC=0`.
test_gap_gc_interp_global_this_rooting

# #8439: `js_string_copy` borrowed its source's bytes across an allocation that
# can trigger a moving minor. Registered so gc-stress / gc-moving-witnesses
# actually execute it instead of leaving it dark on disk.
test_gap_gc_string_copy_source_rooting
# #8443: `String.prototype.repeat` held its receiver unrooted across a
# re-entrant count coercion. Same reason.
test_gap_gc_string_repeat_reentrant_count

# --- `?? null` local rooting ------------------------------------------------
# `const masks = opts?.masks ?? null` was declared `Null` by the `??` inference
# rule (an unknown left took the RIGHT operand's type), and the pointer-locals
# collector accepted that as proof the local holds no pointer: no shadow slot,
# a from-space array address in a plain `alloca double` across the loop poll.
# Pre-fix: SIGBUS after the first copying minor under
# PERRY_GC_PROTECT_FROMSPACE=1; post-fix: `600000 0.35 0.25 0.1 100000 0.35`.
test_gap_gc_coalesce_local_root
