docs(cpu): PPCBUG-108 document legacy reservation path's strict-lockstep requirement
Adds doc comments above lwarx/ldarx/stwcx./stdcx. clarifying that the
legacy per-ctx reservation path is only correct in strict lockstep
(single host thread); under --parallel the M3 scheduler must enable
the cross-thread ReservationTable before spawning a second host thread.
A debug_assert fires in the legacy stwcx./stdcx. branch if a
non-primary HW slot (hw_id != 0) takes that path — surfacing
ReservationTable-disabled misconfiguration early in debug builds.
Note: the primary slot (hw_id==0) racing other parallel slots is
not caught by the assert; that case requires the table to be enabled.
Affected:
PPCBUG-108 legacy per-ctx reservation path cannot invalidate
cross-thread; informational — no behavioral change
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1116,6 +1116,15 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
// per-`PpcContext` fields. Both paths leave the per-ctx fields
|
// per-`PpcContext` fields. Both paths leave the per-ctx fields
|
||||||
// in a coherent state so a flag flip mid-run doesn't corrupt
|
// in a coherent state so a flag flip mid-run doesn't corrupt
|
||||||
// outstanding reservations.
|
// outstanding reservations.
|
||||||
|
//
|
||||||
|
// PPCBUG-108: lwarx + stwcx. atomicity is provided by `ReservationTable`
|
||||||
|
// in the M3 multi-HW-thread runtime. The legacy per-ctx fallback (when
|
||||||
|
// `reservation_table` is None or the table is disabled) cannot observe
|
||||||
|
// stores from other host threads — a store by thread B cannot clear
|
||||||
|
// `ctx_A.has_reservation`. This path is only correct in strict lockstep
|
||||||
|
// (single-host-thread) mode. The M3 scheduler MUST enable the table
|
||||||
|
// before spawning a second host thread. See stwcx./stdcx. for the
|
||||||
|
// debug_assert that fires if a non-primary slot takes this path.
|
||||||
PpcOpcode::lwarx => {
|
PpcOpcode::lwarx => {
|
||||||
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
||||||
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
||||||
@@ -1132,6 +1141,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
}
|
}
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
|
// PPCBUG-108: see lwarx comment above. stwcx. legacy path cannot observe
|
||||||
|
// cross-thread reservation invalidations; only safe in lockstep mode.
|
||||||
PpcOpcode::stwcx => {
|
PpcOpcode::stwcx => {
|
||||||
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
||||||
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
||||||
@@ -1154,7 +1165,19 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
&& ctx.reserved_line == line
|
&& ctx.reserved_line == line
|
||||||
&& t.try_commit(ea, ctx.reserved_generation, ctx.hw_id)
|
&& t.try_commit(ea, ctx.reserved_generation, ctx.hw_id)
|
||||||
} else {
|
} else {
|
||||||
// Legacy per-ctx path (M2 default).
|
// Legacy per-ctx path (M2 default / lockstep).
|
||||||
|
// PPCBUG-108: fires on non-primary HW slots under misconfig —
|
||||||
|
// if the table is disabled while workers are active, slots
|
||||||
|
// 1..N will trip this assert, surfacing the misconfiguration
|
||||||
|
// early in debug builds. Note: hw_id==0 (primary slot) taking
|
||||||
|
// this path while other slots run in parallel would NOT be
|
||||||
|
// caught; that case requires the table to be enabled instead.
|
||||||
|
debug_assert!(
|
||||||
|
ctx.hw_id == 0,
|
||||||
|
"PPCBUG-108: legacy per-ctx stwcx. on non-primary HW slot \
|
||||||
|
(hw_id={}) — ReservationTable must be enabled under --parallel",
|
||||||
|
ctx.hw_id
|
||||||
|
);
|
||||||
ctx.has_reservation && width_ok && ctx.reserved_line == line
|
ctx.has_reservation && width_ok && ctx.reserved_line == line
|
||||||
};
|
};
|
||||||
if success {
|
if success {
|
||||||
@@ -4281,6 +4304,11 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
// §4k — Scalar reservation / byte-reverse (doubleword)
|
// §4k — Scalar reservation / byte-reverse (doubleword)
|
||||||
// ═════════════════════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════════════════════
|
||||||
// M3.7 — same table-vs-legacy split as lwarx/stwcx.
|
// M3.7 — same table-vs-legacy split as lwarx/stwcx.
|
||||||
|
// PPCBUG-108: ldarx + stdcx. have the same cross-thread atomicity
|
||||||
|
// limitation as lwarx/stwcx. in the legacy per-ctx fallback path.
|
||||||
|
// See the lwarx block comment for the full explanation. The M3
|
||||||
|
// scheduler must enable `ReservationTable` before spawning a second
|
||||||
|
// host thread. stdcx. carries the debug_assert (see below).
|
||||||
PpcOpcode::ldarx => {
|
PpcOpcode::ldarx => {
|
||||||
let ea = ea_indexed(ctx, instr);
|
let ea = ea_indexed(ctx, instr);
|
||||||
let val = mem.read_u64(ea);
|
let val = mem.read_u64(ea);
|
||||||
@@ -4296,6 +4324,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
}
|
}
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
|
// PPCBUG-108: see ldarx comment above. stdcx. legacy path cannot observe
|
||||||
|
// cross-thread reservation invalidations; only safe in lockstep mode.
|
||||||
PpcOpcode::stdcx => {
|
PpcOpcode::stdcx => {
|
||||||
let ea = ea_indexed(ctx, instr);
|
let ea = ea_indexed(ctx, instr);
|
||||||
let line = ea & !RESERVATION_MASK;
|
let line = ea & !RESERVATION_MASK;
|
||||||
@@ -4313,6 +4343,15 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
&& ctx.reserved_line == line
|
&& ctx.reserved_line == line
|
||||||
&& t.try_commit(ea, ctx.reserved_generation, ctx.hw_id)
|
&& t.try_commit(ea, ctx.reserved_generation, ctx.hw_id)
|
||||||
} else {
|
} else {
|
||||||
|
// Legacy per-ctx path (M2 default / lockstep).
|
||||||
|
// PPCBUG-108: same sentinel as stwcx. — fires on non-primary
|
||||||
|
// HW slots if the table is disabled under --parallel.
|
||||||
|
debug_assert!(
|
||||||
|
ctx.hw_id == 0,
|
||||||
|
"PPCBUG-108: legacy per-ctx stdcx. on non-primary HW slot \
|
||||||
|
(hw_id={}) — ReservationTable must be enabled under --parallel",
|
||||||
|
ctx.hw_id
|
||||||
|
);
|
||||||
ctx.has_reservation && width_ok && ctx.reserved_line == line
|
ctx.has_reservation && width_ok && ctx.reserved_line == line
|
||||||
};
|
};
|
||||||
if success {
|
if success {
|
||||||
|
|||||||
Reference in New Issue
Block a user