Triaging the catches left after #7373–#7376, #7380, #7381, #7383, #7385, #7391, they are not independent bugs. Every one has the same signature.
Evidence
| test |
fault |
what it dereferences |
gc_call_argument_rooting |
js_string_concat_chain + 552 |
byte_len at [x1, #4] — no allocating call precedes it in the callee |
backoff_options |
js_closure_call0 + 40 |
[x0, #0xc], cmp 0x4f53 — CLOSURE_MAGIC, first deref |
os_3004plus |
js_closure_call3 + 48 |
same shape |
gc_process_env_cache_rooting |
js_array_push_f64 + 68 |
early |
gc_same_module_call_argument_rooting |
js_string_concat_box + 212 |
early |
In each case the fault is at or near the callee's first dereference of an incoming pointer, after the plausibility checks pass — a retired from-space address still looks like a heap pointer. Disassembling js_string_concat_chain up to the fault shows no allocating call at all before it, only bzero and a TLS registration.
The value is already dead when the callee is entered. Nothing inside these functions can fix that.
Why js_closure_call0 is the decisive case
It takes no arguments — only the closure. So this cannot be argument-list staleness. The callee itself is held in a bare SSA register across something that allocates, and then called. Whatever the shared defect is, it covers receivers as well as arguments.
Not root_reload's job
root_reload.rs re-reads shadow slots below collection points, which fixes values that are in a slot. These are bare SSA values straight out of lower_expr — there is no slot to re-read. It cannot see them by construction.
What this means for planning
test_gap_gc_call_argument_rooting's own header documents the shape and names #7154's perry_fn_<src>__<name> path — "an argument list is evaluated left to right and each finished value sits in a bare SSA register while the later ones are lowered". That module has since been reorganised, and expr/static_method.rs alone has ~10 for a in args loops lowering arguments with no root protection.
So this is one structural fix in argument/receiver lowering, not N per-callee patches — and it should close several catches at once. Attempting it per-site is what I'd advise against: two such attempts today (Object.assign source operand, and the replace search value earlier) landed textbook-perfect roots in the IR and closed zero catches, because the root was downstream of where the value died.
Suggested sequencing
- Decide where the protection belongs — per-call-site temp roots for pointer-bearing operands when any later operand can allocate, vs. a pass over the emitted CFG like
root_reload but for SSA operands rather than slot loads.
- Land it behind the existing quarantine so the catch list is the acceptance test.
- Re-run the sweep; expect several of the table above to close together, and treat any that don't as genuinely separate.
Related: #7341, #7154, #7294 (engine plan, layer 1 — docs/src/internals/rfc-rooting-by-construction.md argues exactly this shape is better prevented than reviewed).
Triaging the catches left after #7373–#7376, #7380, #7381, #7383, #7385, #7391, they are not independent bugs. Every one has the same signature.
Evidence
gc_call_argument_rootingjs_string_concat_chain + 552byte_lenat[x1, #4]— no allocating call precedes it in the calleebackoff_optionsjs_closure_call0 + 40[x0, #0xc], cmp0x4f53— CLOSURE_MAGIC, first derefos_3004plusjs_closure_call3 + 48gc_process_env_cache_rootingjs_array_push_f64 + 68gc_same_module_call_argument_rootingjs_string_concat_box + 212In each case the fault is at or near the callee's first dereference of an incoming pointer, after the plausibility checks pass — a retired from-space address still looks like a heap pointer. Disassembling
js_string_concat_chainup to the fault shows no allocating call at all before it, onlybzeroand a TLS registration.The value is already dead when the callee is entered. Nothing inside these functions can fix that.
Why
js_closure_call0is the decisive caseIt takes no arguments — only the closure. So this cannot be argument-list staleness. The callee itself is held in a bare SSA register across something that allocates, and then called. Whatever the shared defect is, it covers receivers as well as arguments.
Not
root_reload's jobroot_reload.rsre-reads shadow slots below collection points, which fixes values that are in a slot. These are bare SSA values straight out oflower_expr— there is no slot to re-read. It cannot see them by construction.What this means for planning
test_gap_gc_call_argument_rooting's own header documents the shape and names #7154'sperry_fn_<src>__<name>path — "an argument list is evaluated left to right and each finished value sits in a bare SSA register while the later ones are lowered". That module has since been reorganised, andexpr/static_method.rsalone has ~10for a in argsloops lowering arguments with no root protection.So this is one structural fix in argument/receiver lowering, not N per-callee patches — and it should close several catches at once. Attempting it per-site is what I'd advise against: two such attempts today (
Object.assignsource operand, and thereplacesearch value earlier) landed textbook-perfect roots in the IR and closed zero catches, because the root was downstream of where the value died.Suggested sequencing
root_reloadbut for SSA operands rather than slot loads.Related: #7341, #7154, #7294 (engine plan, layer 1 —
docs/src/internals/rfc-rooting-by-construction.mdargues exactly this shape is better prevented than reviewed).