fix(cpu): PPCBUG-151 add reservation_width discriminator to stwcx./stdcx.
Track lwarx vs ldarx reservation width in PpcContext as a u8 (4 = word,
8 = doubleword, 0 = none). stwcx. requires width==4; stdcx. requires
width==8. Cross-width pairs (lwarx + stdcx., ldarx + stwcx.) now fail
deterministically with CR0.EQ=0 instead of spuriously succeeding.
The width is held per-thread; the cross-thread reservation table keeps
its existing slot encoding because each host thread consults its own
ctx.reservation_width before committing.
Affected:
PPCBUG-151 stwcx./stdcx. shared the same reservation slot without
width discriminator; cross-width commits silently succeeded
Tests: lwarx_then_stdcx_cross_width_fails,
ldarx_then_stwcx_cross_width_fails
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,6 +101,12 @@ pub struct PpcContext {
|
||||
pub reserved_line: u32,
|
||||
pub reserved_val: u64,
|
||||
pub has_reservation: bool,
|
||||
/// PPCBUG-151 — width of the active reservation: 4 = `lwarx` (word),
|
||||
/// 8 = `ldarx` (doubleword), 0 = no reservation. `stwcx.` requires
|
||||
/// width==4; `stdcx.` requires width==8. Cross-width pairs fail
|
||||
/// deterministically with CR0.EQ=0. Cleared alongside `has_reservation`
|
||||
/// on every `stwcx.`/`stdcx.` exit (success or failure).
|
||||
pub reservation_width: u8,
|
||||
/// M3.7 — generation stamp returned by [`crate::ReservationTable::reserve`]
|
||||
/// at the most recent `lwarx`/`ldarx`. Paired with `reserved_line`;
|
||||
/// `stwcx.`/`stdcx.` pass this back to `try_commit`. Meaningful only
|
||||
@@ -159,6 +165,7 @@ impl PpcContext {
|
||||
reserved_line: 0,
|
||||
reserved_val: 0,
|
||||
has_reservation: false,
|
||||
reservation_width: 0,
|
||||
reserved_generation: 0,
|
||||
reservation_table: None,
|
||||
hw_id: 0,
|
||||
|
||||
@@ -1124,6 +1124,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.reserved_line = ea & !RESERVATION_MASK;
|
||||
ctx.reserved_val = val as u64;
|
||||
ctx.has_reservation = true;
|
||||
ctx.reservation_width = 4; // PPCBUG-151: word reservation
|
||||
if let Some(t) = &ctx.reservation_table {
|
||||
if t.is_enabled() {
|
||||
ctx.reserved_generation = t.reserve(ea, ctx.hw_id);
|
||||
@@ -1140,17 +1141,21 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
.as_ref()
|
||||
.filter(|t| t.is_enabled())
|
||||
.cloned();
|
||||
// PPCBUG-151: stwcx. requires a word (lwarx) reservation;
|
||||
// a doubleword (ldarx) reservation must not commit here.
|
||||
let width_ok = ctx.reservation_width == 4;
|
||||
let success = if let Some(t) = &table_route {
|
||||
// Table-routed: success iff the slot still holds our
|
||||
// reservation AND the per-ctx flag agrees (the per-ctx
|
||||
// flag would be cleared by an intervening write or
|
||||
// context switch).
|
||||
ctx.has_reservation
|
||||
&& width_ok
|
||||
&& ctx.reserved_line == line
|
||||
&& t.try_commit(ea, ctx.reserved_generation, ctx.hw_id)
|
||||
} else {
|
||||
// Legacy per-ctx path (M2 default).
|
||||
ctx.has_reservation && ctx.reserved_line == line
|
||||
ctx.has_reservation && width_ok && ctx.reserved_line == line
|
||||
};
|
||||
if success {
|
||||
mem.write_u32(ea, ctx.gpr[instr.rs()] as u32);
|
||||
@@ -1176,6 +1181,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
}
|
||||
}
|
||||
ctx.has_reservation = false;
|
||||
ctx.reservation_width = 0; // PPCBUG-151: always clear on exit
|
||||
ctx.pc += 4;
|
||||
}
|
||||
|
||||
@@ -4282,6 +4288,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.reserved_line = ea & !RESERVATION_MASK;
|
||||
ctx.reserved_val = val;
|
||||
ctx.has_reservation = true;
|
||||
ctx.reservation_width = 8; // PPCBUG-151: doubleword reservation
|
||||
if let Some(t) = &ctx.reservation_table {
|
||||
if t.is_enabled() {
|
||||
ctx.reserved_generation = t.reserve(ea, ctx.hw_id);
|
||||
@@ -4297,12 +4304,16 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
.as_ref()
|
||||
.filter(|t| t.is_enabled())
|
||||
.cloned();
|
||||
// PPCBUG-151: stdcx. requires a doubleword (ldarx) reservation;
|
||||
// a word (lwarx) reservation must not commit here.
|
||||
let width_ok = ctx.reservation_width == 8;
|
||||
let success = if let Some(t) = &table_route {
|
||||
ctx.has_reservation
|
||||
&& width_ok
|
||||
&& ctx.reserved_line == line
|
||||
&& t.try_commit(ea, ctx.reserved_generation, ctx.hw_id)
|
||||
} else {
|
||||
ctx.has_reservation && ctx.reserved_line == line
|
||||
ctx.has_reservation && width_ok && ctx.reserved_line == line
|
||||
};
|
||||
if success {
|
||||
mem.write_u64(ea, ctx.gpr[instr.rs()]);
|
||||
@@ -4324,6 +4335,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
}
|
||||
}
|
||||
ctx.has_reservation = false;
|
||||
ctx.reservation_width = 0; // PPCBUG-151: always clear on exit
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::ldbrx => {
|
||||
@@ -6077,6 +6089,86 @@ mod tests {
|
||||
|
||||
/// Regression: `lvebx` must preserve the prior contents of the
|
||||
/// destination VR for lanes other than the loaded byte. Previously
|
||||
// ---------- PPCBUG-151: cross-width reservation pairs must fail ----------
|
||||
|
||||
/// PPCBUG-151: `lwarx` (width=4) followed by `stdcx.` (requires width=8)
|
||||
/// must fail with CR0.EQ=0. Memory must remain unchanged.
|
||||
#[test]
|
||||
fn lwarx_then_stdcx_cross_width_fails() {
|
||||
let table = std::sync::Arc::new(crate::ReservationTable::new());
|
||||
table.enable();
|
||||
|
||||
let mut ctx = PpcContext::new();
|
||||
ctx.reservation_table = Some(table.clone());
|
||||
ctx.hw_id = 0;
|
||||
let mut mem = TestMem::new();
|
||||
|
||||
// r4=0x1000 (target addr), r5=0 (index), r6=value to (attempt to) store.
|
||||
ctx.gpr[4] = 0x1000;
|
||||
ctx.gpr[5] = 0;
|
||||
ctx.gpr[6] = 0xDEAD_BEEF_CAFE_BABEu64;
|
||||
|
||||
// Instr 0: lwarx r3, r4, r5 (opcode 31, XO 20, Rc=0)
|
||||
let lwarx = (31u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (20 << 1);
|
||||
write_instr(&mut mem, 0, lwarx);
|
||||
// Instr 1: stdcx. r6, r4, r5 (opcode 31, XO 214, Rc=1)
|
||||
let stdcx = (31u32 << 26) | (6 << 21) | (4 << 16) | (5 << 11) | (214 << 1) | 1;
|
||||
write_instr(&mut mem, 4, stdcx);
|
||||
|
||||
// Execute lwarx — must set a word reservation (width=4).
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(ctx.has_reservation, "lwarx must set has_reservation");
|
||||
assert_eq!(ctx.reservation_width, 4, "lwarx must set reservation_width=4");
|
||||
|
||||
// Execute stdcx. — width mismatch (needs 8, got 4); must fail.
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(!ctx.cr[0].eq, "stdcx. must fail when reservation was set by lwarx (cross-width)");
|
||||
// Memory at 0x1000-0x1007 must be unchanged (still zero).
|
||||
assert_eq!(mem.read_u64(0x1000), 0, "stdcx. must not write on cross-width failure");
|
||||
// Width must be cleared on exit.
|
||||
assert_eq!(ctx.reservation_width, 0, "stdcx. must clear reservation_width on exit");
|
||||
}
|
||||
|
||||
/// PPCBUG-151: `ldarx` (width=8) followed by `stwcx.` (requires width=4)
|
||||
/// must fail with CR0.EQ=0. Memory must remain unchanged.
|
||||
#[test]
|
||||
fn ldarx_then_stwcx_cross_width_fails() {
|
||||
let table = std::sync::Arc::new(crate::ReservationTable::new());
|
||||
table.enable();
|
||||
|
||||
let mut ctx = PpcContext::new();
|
||||
ctx.reservation_table = Some(table.clone());
|
||||
ctx.hw_id = 0;
|
||||
let mut mem = TestMem::new();
|
||||
|
||||
// r4=0x1000 (target addr), r5=0 (index), r6=value to (attempt to) store.
|
||||
ctx.gpr[4] = 0x1000;
|
||||
ctx.gpr[5] = 0;
|
||||
ctx.gpr[6] = 0xCCCC_CCCCu64;
|
||||
|
||||
// Instr 0: ldarx r3, r4, r5 (opcode 31, XO 84, Rc=0)
|
||||
let ldarx = (31u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (84 << 1);
|
||||
write_instr(&mut mem, 0, ldarx);
|
||||
// Instr 1: stwcx. r6, r4, r5 (opcode 31, XO 150, Rc=1)
|
||||
let stwcx = (31u32 << 26) | (6 << 21) | (4 << 16) | (5 << 11) | (150 << 1) | 1;
|
||||
write_instr(&mut mem, 4, stwcx);
|
||||
|
||||
// Execute ldarx — must set a doubleword reservation (width=8).
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(ctx.has_reservation, "ldarx must set has_reservation");
|
||||
assert_eq!(ctx.reservation_width, 8, "ldarx must set reservation_width=8");
|
||||
|
||||
// Execute stwcx. — width mismatch (needs 4, got 8); must fail.
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(!ctx.cr[0].eq, "stwcx. must fail when reservation was set by ldarx (cross-width)");
|
||||
// Memory at 0x1000 must be unchanged (still zero).
|
||||
assert_eq!(mem.read_u32(0x1000), 0, "stwcx. must not write on cross-width failure");
|
||||
// Width must be cleared on exit.
|
||||
assert_eq!(ctx.reservation_width, 0, "stwcx. must clear reservation_width on exit");
|
||||
}
|
||||
|
||||
/// the handler started from a zeroed buffer.
|
||||
#[test]
|
||||
fn test_lvebx_preserves_other_lanes() {
|
||||
|
||||
Reference in New Issue
Block a user