Split out of #7394, which fixed the other half. The fast x29-chain walker was
decoding lsl #12 frame adjustments wrong; that is fixed and the default
configuration is correct. The platform-unwinder path is independently wrong,
by exactly one frame size.
Measured
test_gap_gc_call_argument_rooting, aarch64 macOS, RS4GC default backend, at
the commit that fixes the fast walker:
PERRY_STACKMAP_WALKER |
PERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0 |
fast (default) |
bad 0 ✅ |
unwind |
bad 1 ❌ |
verify |
panics — slot sets differ |
verify prints both sets:
fast : [0x16f625130, 0x16f625138, 0x16f625210]
unwind: [0x16f623950, 0x16f625060, 0x16f625068]
Pairing by frame rather than by sort order:
| frame |
fast |
unwind |
unwind − fast |
| A (2 slots) |
…5130, …5138 |
…5060, …5068 |
−0xD0 |
| B (1 slot) |
…5210 |
…3950 |
−0x18C0 |
0x18C0 is exactly the total frame of
perry_fn_test_gap_gc_call_argument_rooting_ts__run
(0x80 pre-index + 0x1000 + 0x840). The unwinder lands one whole frame
below the correct body SP, and 0xD0 is consistent with the same relationship on
the smaller frame.
Where
crates/perry-runtime/src/gc/roots/stack_maps.rs, the Itanium unwind::walk_frame
SP-relative base:
let cfa = _Unwind_GetCFA(context);
cfa.checked_sub(CFA_RETURN_ADDRESS_BYTES) // 0 on aarch64
.and_then(|v| v.checked_sub(record.stack_size as usize))
The delta being exactly record.stack_size on both frames points at a double
subtraction — i.e. _Unwind_GetCFA here already yields the value the formula is
trying to derive. That is a hypothesis, not a measurement; it needs confirming
before the formula is changed, and any change has to be checked on x86-64/Linux,
where CFA_RETURN_ADDRESS_BYTES = 8 and the current formula is what the Linux
gate runs against.
Why it is not merely a bisection knob
WalkerMode::Fast falls back to this path whenever chain_walkable is false
or fp_chain::visit returns None on an anomaly:
WalkerMode::Fast => {
if index.chain_walkable {
if let Some(stats) = fp_chain::visit(index, visit) { return stats; }
}
let mut stats = unwind::visit(index, visit); // <- here
So a frame the fast walker declines is handed to a walker that computes the
wrong base — and evacuation writes through the slots it is given.
It also means PERRY_STACKMAP_WALKER=verify cannot be promoted to a CI gate
until this is fixed: verify performs its real visitation through the unwinder,
so it currently both trips on the difference and does the wrong visitation.
That gate is the only check that can catch a fast walk silently skipping frames
— exactly the class of bug #7394 turned out to be — so it is worth having.
Split out of #7394, which fixed the other half. The fast x29-chain walker was
decoding
lsl #12frame adjustments wrong; that is fixed and the defaultconfiguration is correct. The platform-unwinder path is independently wrong,
by exactly one frame size.
Measured
test_gap_gc_call_argument_rooting, aarch64 macOS, RS4GC default backend, atthe commit that fixes the fast walker:
PERRY_STACKMAP_WALKERPERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0fast(default)bad 0✅unwindbad 1❌verifyverifyprints both sets:Pairing by frame rather than by sort order:
…5130,…5138…5060,…5068…5210…39500x18C0 is exactly the total frame of
perry_fn_test_gap_gc_call_argument_rooting_ts__run(
0x80pre-index +0x1000+0x840). The unwinder lands one whole framebelow the correct body SP, and 0xD0 is consistent with the same relationship on
the smaller frame.
Where
crates/perry-runtime/src/gc/roots/stack_maps.rs, the Itaniumunwind::walk_frameSP-relative base:
The delta being exactly
record.stack_sizeon both frames points at a doublesubtraction — i.e.
_Unwind_GetCFAhere already yields the value the formula istrying to derive. That is a hypothesis, not a measurement; it needs confirming
before the formula is changed, and any change has to be checked on x86-64/Linux,
where
CFA_RETURN_ADDRESS_BYTES = 8and the current formula is what the Linuxgate runs against.
Why it is not merely a bisection knob
WalkerMode::Fastfalls back to this path wheneverchain_walkableis falseor
fp_chain::visitreturnsNoneon an anomaly:So a frame the fast walker declines is handed to a walker that computes the
wrong base — and evacuation writes through the slots it is given.
It also means
PERRY_STACKMAP_WALKER=verifycannot be promoted to a CI gateuntil this is fixed:
verifyperforms its real visitation through the unwinder,so it currently both trips on the difference and does the wrong visitation.
That gate is the only check that can catch a fast walk silently skipping frames
— exactly the class of bug #7394 turned out to be — so it is worth having.