Skip to content

#7341: the aarch64 stack-map fast walker reads the wrong stack for frames >= 4 KiB (lsl #12 prologue adjustments) #7394

Description

@proggeramlug

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

  1. 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.
  2. Land it behind the existing quarantine so the catch list is the acceptance test.
  3. 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).

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