Constructing a generator in a hot loop takes a bus error on retired
from-space memory under the #7154 rooting instruments. The faulting frame is
js_generator_attach_prototype, i.e. generator construction — not stepping,
not the result object.
Found while working #7564. Not caused by it — #7564 touches no generator
code (git diff --stat covers only array/iter_object.rs, buffer/iter.rs,
collection_iter_object.rs, iterator_helpers.rs, string/iter_object.rs,
string/mod.rs, iter_result.rs, gc/mod.rs). It surfaced only after
#7564, because before that the process died earlier at
iterator_helpers::make_iter_result and never reached this site.
Repro
function churn(i: number): string {
return "pad-" + i + "-" + (i * 7919) + "-" + (i % 13);
}
function* small(n: number): Generator<number, void, undefined> {
for (let k = 0; k < n; k++) yield k + n;
}
let acc = 0;
for (let i = 0; i < 4000; i++) {
const g = small(4); // ← construction is what faults
let gs = g.next();
while (!gs.done) { acc += gs.value as number; gs = g.next(); }
churn(i);
}
console.log(acc);
Compile with PERRY_GC_MOVING_LOOP_POLLS=1 (loop polls are off by default
since #7161, so a default build never collects inside the loop), then:
PERRY_GC_ZEAL=1 PERRY_GC_PROTECT_FROMSPACE=1 \
PERRY_GC_PROTECT_FROMSPACE_DEPTH=800 PERRY_GC_DIAG=1 ./repro
Fault
[gc-fromspace-protect] mode=ProtectPages retired_set=#0 blocks=17 sets_held=1/800
bytes_protected=17825792
[gc-fromspace-protect] FAULT: signal 10 at 0x2cc0000fea0
This address is RETIRED FROM-SPACE. The evacuating minor moved or
freed the object here and the holder kept the pre-collection address.
block=0x2cbfff10000 +1048224 retired_bytes=1048512 retired_by_minor=#1
last-known object: user_ptr=0x2cc0000fea8 obj_type=2 size=72
2 js_generator_attach_prototype + 248
3 js_generator_attach_prototype + 248
4 perry_fn_..._small + 668
5 main + 7840
Exit 138 (128 + SIGBUS). The instrument is confirmed live —
mode=ProtectPages retired_set=#N printed and the run performed real copying
minors.
obj_type=2 is GC_TYPE_OBJECT. The doubled frame 2/3 suggests the fault is
inside a call js_generator_attach_prototype makes, with the return address
landing back in the same function.
Why this class matters
Per CLAUDE.md, a stale-from-space read is invisible at collection time — there
is nothing for the collector to find — and surfaces cycles later as
TypeError: value is not a function or a silently wrong answer. Without the
instruments this program produces a wrong result and exits 0. That is the
expensive failure mode, and it is why the fault is worth fixing even though
the default build does not reproduce it.
Where to look
js_generator_attach_prototype — whatever receiver or prototype pointer it
holds across an allocating call (a prototype lookup, a shape install, an
object allocation) needs to come back out of a RuntimeHandleScope handle via
RuntimeHandle::across_{mut,const,nanbox} rather than being bound before the
call. See docs/src/internals/gc-rooting-invariant.md.
Constructing a generator in a hot loop takes a bus error on retired
from-space memory under the #7154 rooting instruments. The faulting frame is
js_generator_attach_prototype, i.e. generator construction — not stepping,not the result object.
Found while working #7564. Not caused by it — #7564 touches no generator
code (
git diff --statcovers onlyarray/iter_object.rs,buffer/iter.rs,collection_iter_object.rs,iterator_helpers.rs,string/iter_object.rs,string/mod.rs,iter_result.rs,gc/mod.rs). It surfaced only after#7564, because before that the process died earlier at
iterator_helpers::make_iter_resultand never reached this site.Repro
Compile with
PERRY_GC_MOVING_LOOP_POLLS=1(loop polls are off by defaultsince #7161, so a default build never collects inside the loop), then:
Fault
Exit 138 (128 + SIGBUS). The instrument is confirmed live —
mode=ProtectPages retired_set=#Nprinted and the run performed real copyingminors.
obj_type=2isGC_TYPE_OBJECT. The doubled frame 2/3 suggests the fault isinside a call
js_generator_attach_prototypemakes, with the return addresslanding back in the same function.
Why this class matters
Per CLAUDE.md, a stale-from-space read is invisible at collection time — there
is nothing for the collector to find — and surfaces cycles later as
TypeError: value is not a functionor a silently wrong answer. Without theinstruments this program produces a wrong result and exits 0. That is the
expensive failure mode, and it is why the fault is worth fixing even though
the default build does not reproduce it.
Where to look
js_generator_attach_prototype— whatever receiver or prototype pointer itholds across an allocating call (a prototype lookup, a shape install, an
object allocation) needs to come back out of a
RuntimeHandleScopehandle viaRuntimeHandle::across_{mut,const,nanbox}rather than being bound before thecall. See
docs/src/internals/gc-rooting-invariant.md.