use super::cpu::CpuCore;
use super::op_cache::{AddrOp, BinaryOp, BitOp, is_pre_68020};
use super::types::{CpuType, Size};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FastEa {
DataReg(u8),
AddrReg(u8),
AnInd(u8),
AnPostInc(u8),
AnPreDec(u8),
AnDisp(u8),
AnIndex(u8),
AbsW,
AbsL,
PcDisp,
PcIndex,
Imm,
}
impl FastEa {
#[inline]
fn decode(mode: u16, reg: u16) -> Option<FastEa> {
Some(match mode & 7 {
0 => FastEa::DataReg(reg as u8),
1 => FastEa::AddrReg(reg as u8),
2 => FastEa::AnInd(reg as u8),
3 => FastEa::AnPostInc(reg as u8),
4 => FastEa::AnPreDec(reg as u8),
5 => FastEa::AnDisp(reg as u8),
6 => FastEa::AnIndex(reg as u8),
7 => match reg & 7 {
0 => FastEa::AbsW,
1 => FastEa::AbsL,
2 => FastEa::PcDisp,
3 => FastEa::PcIndex,
4 => FastEa::Imm,
_ => return None,
},
_ => unreachable!(),
})
}
#[inline]
fn is_data_alterable(self) -> bool {
!matches!(
self,
FastEa::AddrReg(_) | FastEa::PcDisp | FastEa::PcIndex | FastEa::Imm
)
}
#[inline]
fn is_control(self) -> bool {
matches!(
self,
FastEa::AnInd(_)
| FastEa::AnDisp(_)
| FastEa::AnIndex(_)
| FastEa::AbsW
| FastEa::AbsL
| FastEa::PcDisp
| FastEa::PcIndex
)
}
#[inline]
fn is_memory(self) -> bool {
!matches!(self, FastEa::DataReg(_) | FastEa::AddrReg(_) | FastEa::Imm)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BitSource {
Reg(u8),
Imm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DecodedMemOp {
Move {
size: Size,
src: FastEa,
dst: FastEa,
},
AluToReg {
op: BinaryOp,
size: Size,
src: FastEa,
dst: u8,
},
AluToMem {
op: BinaryOp,
size: Size,
src: u8,
dst: FastEa,
},
AluAddr {
op: AddrOp,
size: Size,
src: FastEa,
dst: u8,
},
AluImm {
op: BinaryOp,
size: Size,
dst: FastEa,
},
AddqSubq {
data: u32,
size: Size,
ea: FastEa,
is_sub: bool,
},
Tst {
size: Size,
ea: FastEa,
},
Clr {
size: Size,
ea: FastEa,
},
Neg {
size: Size,
ea: FastEa,
},
Not {
size: Size,
ea: FastEa,
},
BitMem {
op: BitOp,
bit: BitSource,
ea: FastEa,
},
CmpM {
size: Size,
src: u8,
dst: u8,
},
Lea {
reg: u8,
ea: FastEa,
},
Pea {
ea: FastEa,
},
Jmp {
ea: FastEa,
},
Jsr {
ea: FastEa,
},
Rts,
Bsr {
displacement: i32,
length: u32,
},
BranchWord {
condition: u8,
},
Dbcc {
condition: u8,
reg: u8,
},
}
impl DecodedMemOp {
pub(crate) fn decode(cpu_type: CpuType, opcode: u16) -> Option<Self> {
let group = (opcode >> 12) & 0xF;
match group {
0x0 => decode_group_0(opcode),
0x1 => decode_move(opcode, Size::Byte),
0x2 => decode_move(opcode, Size::Long),
0x3 => decode_move(opcode, Size::Word),
0x4 => decode_group_4(cpu_type, opcode),
0x5 => decode_group_5(opcode),
0x6 => decode_group_6(opcode),
0x8 | 0x9 | 0xB | 0xC | 0xD => decode_alu(opcode),
_ => None,
}
}
}
#[inline]
fn decode_size_00(bits: u16) -> Option<Size> {
match bits {
0 => Some(Size::Byte),
1 => Some(Size::Word),
2 => Some(Size::Long),
_ => None,
}
}
fn decode_move(opcode: u16, size: Size) -> Option<DecodedMemOp> {
let src = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
let dst = FastEa::decode((opcode >> 6) & 7, (opcode >> 9) & 7)?;
if size == Size::Byte
&& (matches!(src, FastEa::AddrReg(_)) || matches!(dst, FastEa::AddrReg(_)))
{
return None;
}
if !dst.is_data_alterable() && !matches!(dst, FastEa::AddrReg(_)) {
return None;
}
Some(DecodedMemOp::Move { size, src, dst })
}
fn decode_group_0(opcode: u16) -> Option<DecodedMemOp> {
if (opcode & 0x0100) != 0 {
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !ea.is_memory() || !ea.is_data_alterable() {
return None;
}
let op = match (opcode >> 6) & 3 {
0 => BitOp::Test,
1 => BitOp::Change,
2 => BitOp::Clear,
3 => BitOp::Set,
_ => unreachable!(),
};
return Some(DecodedMemOp::BitMem {
op,
bit: BitSource::Reg(((opcode >> 9) & 7) as u8),
ea,
});
}
if (opcode & 0xFF00) == 0x0800 {
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !ea.is_memory() || !ea.is_data_alterable() {
return None;
}
let op = match (opcode >> 6) & 3 {
0 => BitOp::Test,
1 => BitOp::Change,
2 => BitOp::Clear,
3 => BitOp::Set,
_ => unreachable!(),
};
return Some(DecodedMemOp::BitMem {
op,
bit: BitSource::Imm,
ea,
});
}
let size = decode_size_00((opcode >> 6) & 3)?;
let op = match (opcode >> 9) & 7 {
0 => BinaryOp::Or,
1 => BinaryOp::And,
2 => BinaryOp::Sub,
3 => BinaryOp::Add,
5 => BinaryOp::Eor,
6 => BinaryOp::Cmp,
_ => return None,
};
let dst = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !dst.is_data_alterable() {
return None;
}
Some(DecodedMemOp::AluImm { op, size, dst })
}
fn decode_group_4(cpu_type: CpuType, opcode: u16) -> Option<DecodedMemOp> {
if opcode == 0x4E75 {
return Some(DecodedMemOp::Rts);
}
if (opcode & 0xF1C0) == 0x41C0 {
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !ea.is_control() {
return None;
}
return Some(DecodedMemOp::Lea {
reg: ((opcode >> 9) & 7) as u8,
ea,
});
}
if (opcode & 0xFFC0) == 0x4840 {
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !ea.is_control() {
return None;
}
return Some(DecodedMemOp::Pea { ea });
}
if (opcode & 0xFFC0) == 0x4E80 || (opcode & 0xFFC0) == 0x4EC0 {
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !ea.is_control() {
return None;
}
return Some(if (opcode & 0x0040) != 0 {
DecodedMemOp::Jmp { ea }
} else {
DecodedMemOp::Jsr { ea }
});
}
let size = decode_size_00((opcode >> 6) & 3)?;
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !ea.is_memory() || !ea.is_data_alterable() {
return None;
}
let _ = cpu_type;
match opcode & 0xFF00 {
0x4A00 => Some(DecodedMemOp::Tst { size, ea }),
0x4200 => Some(DecodedMemOp::Clr { size, ea }),
0x4400 => Some(DecodedMemOp::Neg { size, ea }),
0x4600 => Some(DecodedMemOp::Not { size, ea }),
_ => None,
}
}
fn decode_group_5(opcode: u16) -> Option<DecodedMemOp> {
let size_bits = (opcode >> 6) & 3;
if size_bits == 3 {
if ((opcode >> 3) & 7) == 1 {
return Some(DecodedMemOp::Dbcc {
condition: ((opcode >> 8) & 0xF) as u8,
reg: (opcode & 7) as u8,
});
}
return None;
}
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if !ea.is_memory() || !ea.is_data_alterable() {
return None;
}
let size = decode_size_00(size_bits)?;
let data = ((opcode >> 9) & 7) as u32;
Some(DecodedMemOp::AddqSubq {
data: if data == 0 { 8 } else { data },
size,
ea,
is_sub: (opcode & 0x100) != 0,
})
}
fn decode_group_6(opcode: u16) -> Option<DecodedMemOp> {
let condition = ((opcode >> 8) & 0xF) as u8;
let displacement = (opcode & 0xFF) as u8;
if condition == 1 {
return match displacement {
0 => Some(DecodedMemOp::Bsr {
displacement: 0, length: 4,
}),
0xFF => None,
d => Some(DecodedMemOp::Bsr {
displacement: d as i8 as i32,
length: 2,
}),
};
}
if displacement == 0 {
return Some(DecodedMemOp::BranchWord { condition });
}
None
}
fn decode_alu(opcode: u16) -> Option<DecodedMemOp> {
let group = (opcode >> 12) & 0xF;
let reg = ((opcode >> 9) & 7) as u8;
let op_mode = (opcode >> 6) & 7;
let ea = FastEa::decode((opcode >> 3) & 7, opcode & 7)?;
if op_mode == 3 || op_mode == 7 {
let op = match group {
0x9 => AddrOp::Suba,
0xB => AddrOp::Cmpa,
0xD => AddrOp::Adda,
_ => return None,
};
return Some(DecodedMemOp::AluAddr {
op,
size: if op_mode == 3 { Size::Word } else { Size::Long },
src: ea,
dst: reg,
});
}
let size = decode_size_00(op_mode & 3)?;
let to_ea = (op_mode & 4) != 0;
if !to_ea {
let op = match group {
0x8 => BinaryOp::Or,
0x9 => BinaryOp::Sub,
0xB => BinaryOp::Cmp,
0xC => BinaryOp::And,
0xD => BinaryOp::Add,
_ => return None,
};
if matches!(ea, FastEa::AddrReg(_))
&& (size == Size::Byte || matches!(op, BinaryOp::Or | BinaryOp::And))
{
return None;
}
return Some(DecodedMemOp::AluToReg {
op,
size,
src: ea,
dst: reg,
});
}
match group {
0xB => {
if let FastEa::AddrReg(src) = ea {
return Some(DecodedMemOp::CmpM {
size,
src,
dst: reg,
});
}
if matches!(ea, FastEa::DataReg(_)) {
return None;
}
if !ea.is_data_alterable() || !ea.is_memory() {
return None;
}
Some(DecodedMemOp::AluToMem {
op: BinaryOp::Eor,
size,
src: reg,
dst: ea,
})
}
0x8 | 0x9 | 0xC | 0xD => {
if !ea.is_memory() || !ea.is_data_alterable() {
return None;
}
let op = match group {
0x8 => BinaryOp::Or,
0x9 => BinaryOp::Sub,
0xC => BinaryOp::And,
0xD => BinaryOp::Add,
_ => unreachable!(),
};
Some(DecodedMemOp::AluToMem {
op,
size,
src: reg,
dst: ea,
})
}
_ => None,
}
}
#[derive(Clone, Copy)]
struct Win {
ptr: *mut u8,
base: u32,
len: u32,
}
impl Win {
#[inline]
fn from_cpu(cpu: &CpuCore) -> Option<Win> {
if cpu.fm_len == 0 {
return None;
}
Some(Win {
ptr: cpu.fm_ptr as *mut u8,
base: cpu.fm_base,
len: cpu.fm_len,
})
}
#[inline]
fn off(self, addr: u32, bytes: u32) -> Option<usize> {
let o = addr.wrapping_sub(self.base);
if o <= self.len - bytes {
Some(o as usize)
} else {
None
}
}
#[inline]
fn read(self, off: usize, size: Size) -> u32 {
unsafe {
let p = self.ptr.add(off);
match size {
Size::Byte => *p as u32,
Size::Word => u16::from_be_bytes([*p, *p.add(1)]) as u32,
Size::Long => u32::from_be_bytes([*p, *p.add(1), *p.add(2), *p.add(3)]),
}
}
}
#[inline]
fn write(self, off: usize, size: Size, value: u32) {
unsafe {
let p = self.ptr.add(off);
match size {
Size::Byte => *p = value as u8,
Size::Word => {
let b = (value as u16).to_be_bytes();
*p = b[0];
*p.add(1) = b[1];
}
Size::Long => {
let b = value.to_be_bytes();
*p = b[0];
*p.add(1) = b[1];
*p.add(2) = b[2];
*p.add(3) = b[3];
}
}
}
}
}
#[derive(Clone, Copy, Default)]
struct Pending {
slots: [(u8, u32); 2],
count: u8,
}
impl Pending {
#[inline(always)]
fn a(&self, cpu: &CpuCore, reg: u8) -> u32 {
for i in (0..self.count as usize).rev() {
if self.slots[i].0 == reg {
return self.slots[i].1;
}
}
cpu.dar[8 + reg as usize]
}
#[inline(always)]
fn push(&mut self, reg: u8, value: u32) {
self.slots[self.count as usize] = (reg, value);
self.count += 1;
}
#[inline(always)]
fn commit(&self, cpu: &mut CpuCore) {
for i in 0..self.count as usize {
cpu.dar[8 + self.slots[i].0 as usize] = self.slots[i].1;
}
}
}
struct Ctx {
next_ext: u32,
pending: Pending,
aligned_only: bool,
}
enum Loc {
Reg(usize),
Mem(usize),
Imm(u32),
}
#[inline]
fn fast_move(cpu: &mut CpuCore, win: Win, size: Size, src: FastEa, dst: FastEa) -> Option<bool> {
let mut src_adj: Option<(usize, u32)> = None;
let aligned_only = cpu.is_pre_68020;
let locate = |cpu: &CpuCore, raw: u32| -> Result<usize, ()> {
if aligned_only && size != Size::Byte && (raw & 1) != 0 {
return Err(());
}
win.off(cpu.address(raw), size.bytes()).ok_or(())
};
let value = match src {
FastEa::DataReg(r) => cpu.dar[r as usize] & size.mask(),
FastEa::AddrReg(r) => cpu.dar[8 + r as usize] & size.mask(),
FastEa::AnInd(r) => {
let Ok(off) = locate(cpu, cpu.dar[8 + r as usize]) else {
return Some(false);
};
win.read(off, size)
}
FastEa::AnPostInc(r) => {
let a = cpu.dar[8 + r as usize];
let Ok(off) = locate(cpu, a) else {
return Some(false);
};
src_adj = Some((8 + r as usize, a.wrapping_add(ea_step(size, r))));
win.read(off, size)
}
FastEa::AnPreDec(r) => {
let a = cpu.dar[8 + r as usize].wrapping_sub(ea_step(size, r));
let Ok(off) = locate(cpu, a) else {
return Some(false);
};
src_adj = Some((8 + r as usize, a));
win.read(off, size)
}
_ => return None,
};
let addr_base = |cpu: &CpuCore, r: u8| -> u32 {
match src_adj {
Some((idx, val)) if idx == 8 + r as usize => val,
_ => cpu.dar[8 + r as usize],
}
};
match dst {
FastEa::DataReg(r) => {
if let Some((idx, val)) = src_adj {
cpu.dar[idx] = val;
}
let mask = size.mask();
let r = r as usize;
cpu.dar[r] = (cpu.dar[r] & !mask) | value;
cpu.set_logic_flags(value, size);
}
FastEa::AddrReg(r) => {
if let Some((idx, val)) = src_adj {
cpu.dar[idx] = val;
}
cpu.dar[8 + r as usize] = if size == Size::Word {
value as u16 as i16 as i32 as u32
} else {
value
};
}
FastEa::AnInd(r) => {
let Ok(off) = locate(cpu, addr_base(cpu, r)) else {
return Some(false);
};
if let Some((idx, val)) = src_adj {
cpu.dar[idx] = val;
}
win.write(off, size, value);
cpu.set_logic_flags(value, size);
}
FastEa::AnPostInc(r) => {
let a = addr_base(cpu, r);
let Ok(off) = locate(cpu, a) else {
return Some(false);
};
if let Some((idx, val)) = src_adj {
cpu.dar[idx] = val;
}
cpu.dar[8 + r as usize] = a.wrapping_add(ea_step(size, r));
win.write(off, size, value);
cpu.set_logic_flags(value, size);
}
FastEa::AnPreDec(r) => {
let a = addr_base(cpu, r).wrapping_sub(ea_step(size, r));
let Ok(off) = locate(cpu, a) else {
return Some(false);
};
if let Some((idx, val)) = src_adj {
cpu.dar[idx] = val;
}
cpu.dar[8 + r as usize] = a;
win.write(off, size, value);
cpu.set_logic_flags(value, size);
}
_ => return None,
}
Some(true)
}
#[inline]
fn fast_an_disp(cpu: &mut CpuCore, win: Win, op: DecodedMemOp) -> Option<bool> {
let read_word = |cpu: &CpuCore, pc: u32| -> Result<u32, ()> {
let off = win.off(cpu.address(pc), 2).ok_or(())?;
Ok(win.read(off, Size::Word))
};
let locate = |cpu: &CpuCore, reg: u8, disp: u32, size: Size| -> Result<usize, ()> {
let raw = (cpu.dar[8 + reg as usize] as i32).wrapping_add(disp as u16 as i16 as i32) as u32;
if cpu.is_pre_68020 && size != Size::Byte && (raw & 1) != 0 {
return Err(());
}
win.off(cpu.address(raw), size.bytes()).ok_or(())
};
match op {
DecodedMemOp::Move {
size,
src: FastEa::AnDisp(src),
dst: FastEa::DataReg(dst),
} => {
let Ok(ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(off) = locate(cpu, src, ext, size) else {
return Some(false);
};
let value = win.read(off, size);
let mask = size.mask();
let dst = dst as usize;
cpu.dar[dst] = (cpu.dar[dst] & !mask) | value;
cpu.set_logic_flags(value, size);
cpu.pc = cpu.pc.wrapping_add(2);
Some(true)
}
DecodedMemOp::Move {
size,
src: FastEa::AnDisp(src),
dst: FastEa::AddrReg(dst),
} => {
let Ok(ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(off) = locate(cpu, src, ext, size) else {
return Some(false);
};
let value = win.read(off, size);
cpu.dar[8 + dst as usize] = if size == Size::Word {
value as u16 as i16 as i32 as u32
} else {
value
};
cpu.pc = cpu.pc.wrapping_add(2);
Some(true)
}
DecodedMemOp::Move {
size,
src: FastEa::DataReg(src),
dst: FastEa::AnDisp(dst),
}
| DecodedMemOp::Move {
size,
src: FastEa::AddrReg(src),
dst: FastEa::AnDisp(dst),
} => {
let Ok(ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(off) = locate(cpu, dst, ext, size) else {
return Some(false);
};
let src_index = if matches!(
op,
DecodedMemOp::Move {
src: FastEa::DataReg(_),
..
}
) {
src as usize
} else {
8 + src as usize
};
let value = cpu.dar[src_index] & size.mask();
win.write(off, size, value);
cpu.set_logic_flags(value, size);
cpu.pc = cpu.pc.wrapping_add(2);
Some(true)
}
DecodedMemOp::Move {
size,
src: FastEa::AnDisp(src),
dst: FastEa::AnPreDec(dst),
} => {
let Ok(ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(src_off) = locate(cpu, src, ext, size) else {
return Some(false);
};
let dst_addr = cpu.dar[8 + dst as usize].wrapping_sub(ea_step(size, dst));
if cpu.is_pre_68020 && size != Size::Byte && (dst_addr & 1) != 0 {
return Some(false);
}
let Some(dst_off) = win.off(cpu.address(dst_addr), size.bytes()) else {
return Some(false);
};
let value = win.read(src_off, size);
cpu.dar[8 + dst as usize] = dst_addr;
win.write(dst_off, size, value);
cpu.set_logic_flags(value, size);
cpu.pc = cpu.pc.wrapping_add(2);
Some(true)
}
DecodedMemOp::Move {
size,
src: FastEa::AnDisp(src),
dst: FastEa::AnDisp(dst),
} => {
let Ok(src_ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(dst_ext) = read_word(cpu, cpu.pc.wrapping_add(2)) else {
return Some(false);
};
let Ok(src_off) = locate(cpu, src, src_ext, size) else {
return Some(false);
};
let Ok(dst_off) = locate(cpu, dst, dst_ext, size) else {
return Some(false);
};
let value = win.read(src_off, size);
win.write(dst_off, size, value);
cpu.set_logic_flags(value, size);
cpu.pc = cpu.pc.wrapping_add(4);
Some(true)
}
DecodedMemOp::Tst {
size,
ea: FastEa::AnDisp(reg),
} => {
let Ok(ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(off) = locate(cpu, reg, ext, size) else {
return Some(false);
};
cpu.set_logic_flags(win.read(off, size), size);
cpu.pc = cpu.pc.wrapping_add(2);
Some(true)
}
DecodedMemOp::Clr {
size,
ea: FastEa::AnDisp(reg),
} => {
let Ok(ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(off) = locate(cpu, reg, ext, size) else {
return Some(false);
};
win.write(off, size, 0);
cpu.n_flag = 0;
cpu.not_z_flag = 0;
cpu.v_flag = 0;
cpu.c_flag = 0;
cpu.pc = cpu.pc.wrapping_add(2);
Some(true)
}
DecodedMemOp::AddqSubq {
data,
size,
ea: FastEa::AnDisp(reg),
is_sub,
} => {
let Ok(ext) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(off) = locate(cpu, reg, ext, size) else {
return Some(false);
};
let dst = win.read(off, size);
let result = if is_sub {
dst.wrapping_sub(data)
} else {
dst.wrapping_add(data)
};
win.write(off, size, result & size.mask());
if is_sub {
cpu.set_sub_flags(data, dst, result, size);
} else {
cpu.set_add_flags(data, dst, result, size);
}
cpu.pc = cpu.pc.wrapping_add(2);
Some(true)
}
DecodedMemOp::BitMem {
op,
bit: BitSource::Imm,
ea: FastEa::AnDisp(reg),
} => {
let Ok(bit) = read_word(cpu, cpu.pc) else {
return Some(false);
};
let Ok(ext) = read_word(cpu, cpu.pc.wrapping_add(2)) else {
return Some(false);
};
let Ok(off) = locate(cpu, reg, ext, Size::Byte) else {
return Some(false);
};
let bit = bit & 7;
let mask = 1 << bit;
let value = win.read(off, Size::Byte);
cpu.not_z_flag = u32::from(value & mask != 0);
match op {
BitOp::Test => {}
BitOp::Set => win.write(off, Size::Byte, value | mask),
BitOp::Clear => win.write(off, Size::Byte, value & !mask),
BitOp::Change => win.write(off, Size::Byte, value ^ mask),
}
cpu.pc = cpu.pc.wrapping_add(4);
Some(true)
}
_ => None,
}
}
#[inline]
fn fast_flow(cpu: &mut CpuCore, win: Win, op: DecodedMemOp) -> bool {
let read_ext = |cpu: &CpuCore| -> Option<u32> {
let off = win.off(cpu.address(cpu.pc), 2)?;
Some(win.read(off, Size::Word))
};
match op {
DecodedMemOp::Dbcc { condition, reg } => {
let Some(disp) = read_ext(cpu) else {
return false;
};
if !cpu.test_condition(condition) {
let reg = reg as usize;
let counter = (cpu.dar[reg] as u16).wrapping_sub(1);
cpu.dar[reg] = (cpu.dar[reg] & 0xFFFF_0000) | counter as u32;
if counter != 0xFFFF {
cpu.pc = (cpu.pc as i32).wrapping_add(disp as u16 as i16 as i32) as u32;
return true;
}
}
cpu.pc = cpu.pc.wrapping_add(2);
true
}
DecodedMemOp::Rts => {
let sp = cpu.dar[15];
if cpu.is_pre_68020 && (sp & 1) != 0 {
return false;
}
let Some(off) = win.off(cpu.address(sp), 4) else {
return false;
};
let ret = win.read(off, Size::Long);
cpu.dar[15] = sp.wrapping_add(4);
cpu.change_of_flow = true;
cpu.pc = ret;
true
}
DecodedMemOp::Bsr {
displacement,
length,
} => {
let base = cpu.pc;
let disp = if length == 4 {
let Some(v) = read_ext(cpu) else {
return false;
};
v as u16 as i16 as i32
} else {
displacement
};
let ret = base.wrapping_add(length - 2);
let sp = cpu.dar[15].wrapping_sub(4);
if cpu.is_pre_68020 && (sp & 1) != 0 {
return false;
}
let Some(off) = win.off(cpu.address(sp), 4) else {
return false;
};
win.write(off, Size::Long, ret);
cpu.dar[15] = sp;
cpu.change_of_flow = true;
cpu.pc = (base as i32).wrapping_add(disp) as u32;
true
}
DecodedMemOp::BranchWord { condition } => {
let base = cpu.pc;
let Some(disp) = read_ext(cpu) else {
return false;
};
if condition == 0 || cpu.test_condition(condition) {
cpu.change_of_flow = true;
cpu.pc = (base as i32).wrapping_add(disp as u16 as i16 as i32) as u32;
} else {
cpu.pc = base.wrapping_add(2);
}
true
}
_ => unreachable!("fast_flow only handles flow-control ops"),
}
}
fn ea_step(size: Size, reg: u8) -> u32 {
if size == Size::Byte && reg == 7 {
2
} else {
size.bytes()
}
}
#[inline]
fn read_ext_word(cpu: &CpuCore, win: Win, ctx: &mut Ctx) -> Option<u32> {
let addr = cpu.address(ctx.next_ext);
let off = win.off(addr, 2)?;
ctx.next_ext = ctx.next_ext.wrapping_add(2);
Some(win.read(off, Size::Word))
}
#[inline]
fn brief_index(cpu: &CpuCore, pending: &Pending, base: u32, ext: u32) -> Option<u32> {
let is_020_plus = !is_pre_68020(cpu.cpu_type);
if (ext & 0x0100) != 0 && is_020_plus {
return None;
}
let d8 = (ext & 0xFF) as u8 as i8 as i32;
let idx_reg = ((ext >> 12) & 0xF) as usize;
let idx_is_addr = (ext & 0x8000) != 0;
let idx_is_long = (ext & 0x0800) != 0;
let scale = if is_020_plus {
1i32 << ((ext >> 9) & 0x3)
} else {
1i32
};
let idx_val = if idx_is_addr {
pending.a(cpu, (idx_reg & 7) as u8)
} else {
cpu.dar[idx_reg & 7]
};
let idx_val = if idx_is_long {
idx_val as i32
} else {
(idx_val as i16) as i32
};
let idx_val = idx_val.wrapping_mul(scale);
Some((base as i32).wrapping_add(d8).wrapping_add(idx_val) as u32)
}
#[inline(always)]
fn resolve(cpu: &CpuCore, win: Win, ea: FastEa, size: Size, ctx: &mut Ctx) -> Option<Loc> {
let mem = |cpu: &CpuCore, ctx: &Ctx, raw: u32| -> Option<Loc> {
if ctx.aligned_only && size != Size::Byte && (raw & 1) != 0 {
return None;
}
let addr = cpu.address(raw);
Some(Loc::Mem(win.off(addr, size.bytes())?))
};
match ea {
FastEa::DataReg(r) => Some(Loc::Reg(r as usize)),
FastEa::AddrReg(r) => Some(Loc::Reg(8 + r as usize)),
FastEa::AnInd(r) => mem(cpu, ctx, ctx.pending.a(cpu, r)),
FastEa::AnPostInc(r) => {
let a = ctx.pending.a(cpu, r);
let loc = mem(cpu, ctx, a)?;
ctx.pending.push(r, a.wrapping_add(ea_step(size, r)));
Some(loc)
}
FastEa::AnPreDec(r) => {
let a = ctx.pending.a(cpu, r).wrapping_sub(ea_step(size, r));
let loc = mem(cpu, ctx, a)?;
ctx.pending.push(r, a);
Some(loc)
}
FastEa::AnDisp(r) => {
let d = read_ext_word(cpu, win, ctx)? as u16 as i16 as i32;
let a = (ctx.pending.a(cpu, r) as i32).wrapping_add(d) as u32;
mem(cpu, ctx, a)
}
FastEa::AnIndex(r) => {
let ext = read_ext_word(cpu, win, ctx)?;
let a = brief_index(cpu, &ctx.pending, ctx.pending.a(cpu, r), ext)?;
mem(cpu, ctx, a)
}
FastEa::AbsW => {
let a = read_ext_word(cpu, win, ctx)? as u16 as i16 as i32 as u32;
mem(cpu, ctx, a)
}
FastEa::AbsL => {
let hi = read_ext_word(cpu, win, ctx)?;
let lo = read_ext_word(cpu, win, ctx)?;
mem(cpu, ctx, (hi << 16) | lo)
}
FastEa::PcDisp => {
let base = ctx.next_ext;
let d = read_ext_word(cpu, win, ctx)? as u16 as i16 as i32;
mem(cpu, ctx, (base as i32).wrapping_add(d) as u32)
}
FastEa::PcIndex => {
let base = ctx.next_ext;
let ext = read_ext_word(cpu, win, ctx)?;
let a = brief_index(cpu, &ctx.pending, base, ext)?;
mem(cpu, ctx, a)
}
FastEa::Imm => {
let v = match size {
Size::Byte => read_ext_word(cpu, win, ctx)? & 0xFF,
Size::Word => read_ext_word(cpu, win, ctx)?,
Size::Long => {
let hi = read_ext_word(cpu, win, ctx)?;
let lo = read_ext_word(cpu, win, ctx)?;
(hi << 16) | lo
}
};
Some(Loc::Imm(v))
}
}
}
fn resolve_control_addr(cpu: &CpuCore, win: Win, ea: FastEa, ctx: &mut Ctx) -> Option<u32> {
match ea {
FastEa::AnInd(r) => Some(cpu.dar[8 + r as usize]),
FastEa::AnDisp(r) => {
let d = read_ext_word(cpu, win, ctx)? as u16 as i16 as i32;
Some((cpu.dar[8 + r as usize] as i32).wrapping_add(d) as u32)
}
FastEa::AnIndex(r) => {
let ext = read_ext_word(cpu, win, ctx)?;
brief_index(cpu, &ctx.pending, cpu.dar[8 + r as usize], ext)
}
FastEa::AbsW => Some(read_ext_word(cpu, win, ctx)? as u16 as i16 as i32 as u32),
FastEa::AbsL => {
let hi = read_ext_word(cpu, win, ctx)?;
let lo = read_ext_word(cpu, win, ctx)?;
Some((hi << 16) | lo)
}
FastEa::PcDisp => {
let base = ctx.next_ext;
let d = read_ext_word(cpu, win, ctx)? as u16 as i16 as i32;
Some((base as i32).wrapping_add(d) as u32)
}
FastEa::PcIndex => {
let base = ctx.next_ext;
let ext = read_ext_word(cpu, win, ctx)?;
brief_index(cpu, &ctx.pending, base, ext)
}
_ => None,
}
}
#[inline(always)]
fn load(cpu: &CpuCore, win: Win, loc: &Loc, size: Size) -> u32 {
match *loc {
Loc::Reg(i) => cpu.dar[i] & size.mask(),
Loc::Mem(off) => win.read(off, size),
Loc::Imm(v) => v & size.mask(),
}
}
#[inline(always)]
fn store(cpu: &mut CpuCore, win: Win, loc: &Loc, size: Size, value: u32) {
match *loc {
Loc::Reg(i) => {
let mask = size.mask();
cpu.dar[i] = (cpu.dar[i] & !mask) | (value & mask);
}
Loc::Mem(off) => win.write(off, size, value),
Loc::Imm(_) => unreachable!("immediate is never a destination"),
}
}
pub(crate) fn execute_mem_op(cpu: &mut CpuCore, op: DecodedMemOp) -> bool {
let Some(win) = Win::from_cpu(cpu) else {
return false;
};
if let DecodedMemOp::Move { size, src, dst } = op
&& let Some(handled) = fast_move(cpu, win, size, src, dst)
{
return handled;
}
if let Some(handled) = fast_an_disp(cpu, win, op) {
return handled;
}
match op {
DecodedMemOp::Dbcc { .. }
| DecodedMemOp::Bsr { .. }
| DecodedMemOp::Rts
| DecodedMemOp::BranchWord { .. } => return fast_flow(cpu, win, op),
_ => {}
}
let mut ctx = Ctx {
next_ext: cpu.pc,
pending: Pending::default(),
aligned_only: is_pre_68020(cpu.cpu_type),
};
macro_rules! finish_pc {
() => {
cpu.pc = ctx.next_ext;
};
}
match op {
DecodedMemOp::Move { size, src, dst } => {
let Some(src_loc) = resolve(cpu, win, src, size, &mut ctx) else {
return false;
};
let value = load(cpu, win, &src_loc, size);
if let FastEa::AddrReg(r) = dst {
let value = if size == Size::Word {
value as u16 as i16 as i32 as u32
} else {
value
};
ctx.pending.commit(cpu);
cpu.dar[8 + r as usize] = value;
finish_pc!();
return true;
}
let Some(dst_loc) = resolve(cpu, win, dst, size, &mut ctx) else {
return false;
};
ctx.pending.commit(cpu);
store(cpu, win, &dst_loc, size, value);
cpu.set_logic_flags(value, size);
finish_pc!();
true
}
DecodedMemOp::AluToReg { op, size, src, dst } => {
let Some(src_loc) = resolve(cpu, win, src, size, &mut ctx) else {
return false;
};
let src_val = load(cpu, win, &src_loc, size);
ctx.pending.commit(cpu);
let dst_val = cpu.dar[dst as usize] & size.mask();
apply_binary_to_reg(cpu, op, size, src_val, dst_val, dst as usize);
finish_pc!();
true
}
DecodedMemOp::AluToMem { op, size, src, dst } => {
let Some(dst_loc) = resolve(cpu, win, dst, size, &mut ctx) else {
return false;
};
let dst_val = load(cpu, win, &dst_loc, size);
let src_val = cpu.dar[src as usize] & size.mask();
let result = match op {
BinaryOp::Add => src_val.wrapping_add(dst_val),
BinaryOp::Sub => dst_val.wrapping_sub(src_val),
BinaryOp::And => src_val & dst_val,
BinaryOp::Or => src_val | dst_val,
BinaryOp::Eor => src_val ^ dst_val,
BinaryOp::Cmp => unreachable!("CMP has no to-memory form"),
};
ctx.pending.commit(cpu);
store(cpu, win, &dst_loc, size, result & size.mask());
match op {
BinaryOp::Add => cpu.set_add_flags(src_val, dst_val, result, size),
BinaryOp::Sub => cpu.set_sub_flags(src_val, dst_val, result, size),
_ => cpu.set_logic_flags(result, size),
}
finish_pc!();
true
}
DecodedMemOp::AluAddr { op, size, src, dst } => {
let Some(src_loc) = resolve(cpu, win, src, size, &mut ctx) else {
return false;
};
let raw = load(cpu, win, &src_loc, size);
let src_val = if size == Size::Word {
raw as u16 as i16 as i32 as u32
} else {
raw
};
ctx.pending.commit(cpu);
let dst_i = 8 + dst as usize;
let dst_val = cpu.dar[dst_i];
match op {
AddrOp::Adda => cpu.dar[dst_i] = dst_val.wrapping_add(src_val),
AddrOp::Suba => cpu.dar[dst_i] = dst_val.wrapping_sub(src_val),
AddrOp::Cmpa => {
let result = dst_val.wrapping_sub(src_val);
cpu.set_cmp_flags(src_val, dst_val, result, Size::Long);
}
}
finish_pc!();
true
}
DecodedMemOp::AluImm { op, size, dst } => {
let imm = {
let Some(Loc::Imm(v)) = resolve(cpu, win, FastEa::Imm, size, &mut ctx) else {
return false;
};
v
};
let Some(dst_loc) = resolve(cpu, win, dst, size, &mut ctx) else {
return false;
};
let dst_val = load(cpu, win, &dst_loc, size);
let result = match op {
BinaryOp::Add => imm.wrapping_add(dst_val),
BinaryOp::Sub | BinaryOp::Cmp => dst_val.wrapping_sub(imm),
BinaryOp::And => imm & dst_val,
BinaryOp::Or => imm | dst_val,
BinaryOp::Eor => imm ^ dst_val,
};
ctx.pending.commit(cpu);
if op != BinaryOp::Cmp {
store(cpu, win, &dst_loc, size, result & size.mask());
}
match op {
BinaryOp::Add => cpu.set_add_flags(imm, dst_val, result, size),
BinaryOp::Sub => cpu.set_sub_flags(imm, dst_val, result, size),
BinaryOp::Cmp => cpu.set_cmp_flags(imm, dst_val, result, size),
_ => cpu.set_logic_flags(result, size),
}
finish_pc!();
true
}
DecodedMemOp::AddqSubq {
data,
size,
ea,
is_sub,
} => {
let Some(loc) = resolve(cpu, win, ea, size, &mut ctx) else {
return false;
};
let dst_val = load(cpu, win, &loc, size);
let result = if is_sub {
dst_val.wrapping_sub(data)
} else {
dst_val.wrapping_add(data)
};
ctx.pending.commit(cpu);
store(cpu, win, &loc, size, result & size.mask());
if is_sub {
cpu.set_sub_flags(data, dst_val, result, size);
} else {
cpu.set_add_flags(data, dst_val, result, size);
}
finish_pc!();
true
}
DecodedMemOp::Tst { size, ea } => {
let Some(loc) = resolve(cpu, win, ea, size, &mut ctx) else {
return false;
};
let value = load(cpu, win, &loc, size);
ctx.pending.commit(cpu);
cpu.set_logic_flags(value, size);
finish_pc!();
true
}
DecodedMemOp::Clr { size, ea } => {
let Some(loc) = resolve(cpu, win, ea, size, &mut ctx) else {
return false;
};
ctx.pending.commit(cpu);
store(cpu, win, &loc, size, 0);
cpu.n_flag = 0;
cpu.not_z_flag = 0;
cpu.v_flag = 0;
cpu.c_flag = 0;
finish_pc!();
true
}
DecodedMemOp::Neg { size, ea } => {
let Some(loc) = resolve(cpu, win, ea, size, &mut ctx) else {
return false;
};
let src = load(cpu, win, &loc, size);
let result = 0u32.wrapping_sub(src);
ctx.pending.commit(cpu);
store(cpu, win, &loc, size, result & size.mask());
cpu.set_sub_flags(src, 0, result, size);
finish_pc!();
true
}
DecodedMemOp::Not { size, ea } => {
let Some(loc) = resolve(cpu, win, ea, size, &mut ctx) else {
return false;
};
let src = load(cpu, win, &loc, size);
let result = !src & size.mask();
ctx.pending.commit(cpu);
store(cpu, win, &loc, size, result);
cpu.set_logic_flags(result, size);
finish_pc!();
true
}
DecodedMemOp::BitMem { op, bit, ea } => {
let bit_num = match bit {
BitSource::Reg(r) => cpu.dar[r as usize],
BitSource::Imm => {
let Some(v) = read_ext_word(cpu, win, &mut ctx) else {
return false;
};
v
}
} & 7;
let Some(loc) = resolve(cpu, win, ea, Size::Byte, &mut ctx) else {
return false;
};
let value = load(cpu, win, &loc, Size::Byte);
ctx.pending.commit(cpu);
cpu.not_z_flag = if value & (1 << bit_num) != 0 { 1 } else { 0 };
match op {
BitOp::Test => {}
BitOp::Set => store(cpu, win, &loc, Size::Byte, value | (1 << bit_num)),
BitOp::Clear => store(cpu, win, &loc, Size::Byte, value & !(1 << bit_num)),
BitOp::Change => store(cpu, win, &loc, Size::Byte, value ^ (1 << bit_num)),
}
finish_pc!();
true
}
DecodedMemOp::CmpM { size, src, dst } => {
let Some(src_loc) = resolve(cpu, win, FastEa::AnPostInc(src), size, &mut ctx) else {
return false;
};
let src_val = load(cpu, win, &src_loc, size);
let Some(dst_loc) = resolve(cpu, win, FastEa::AnPostInc(dst), size, &mut ctx) else {
return false;
};
let dst_val = load(cpu, win, &dst_loc, size);
ctx.pending.commit(cpu);
let result = dst_val.wrapping_sub(src_val);
cpu.set_cmp_flags(src_val, dst_val, result, size);
finish_pc!();
true
}
DecodedMemOp::Lea { reg, ea } => {
let Some(addr) = resolve_control_addr(cpu, win, ea, &mut ctx) else {
return false;
};
cpu.dar[8 + reg as usize] = addr;
finish_pc!();
true
}
DecodedMemOp::Pea { ea } => {
let Some(addr) = resolve_control_addr(cpu, win, ea, &mut ctx) else {
return false;
};
let sp = cpu.dar[15].wrapping_sub(4);
if ctx.aligned_only && (sp & 1) != 0 {
return false;
}
let Some(off) = win.off(cpu.address(sp), 4) else {
return false;
};
win.write(off, Size::Long, addr);
cpu.dar[15] = sp;
finish_pc!();
true
}
DecodedMemOp::Jmp { ea } => {
let Some(addr) = resolve_control_addr(cpu, win, ea, &mut ctx) else {
return false;
};
cpu.change_of_flow = true;
cpu.pc = addr;
true
}
DecodedMemOp::Jsr { ea } => {
let Some(addr) = resolve_control_addr(cpu, win, ea, &mut ctx) else {
return false;
};
let ret = ctx.next_ext;
let sp = cpu.dar[15].wrapping_sub(4);
if ctx.aligned_only && (sp & 1) != 0 {
return false;
}
let Some(off) = win.off(cpu.address(sp), 4) else {
return false;
};
win.write(off, Size::Long, ret);
cpu.dar[15] = sp;
cpu.change_of_flow = true;
cpu.pc = addr;
true
}
DecodedMemOp::Rts => {
let sp = cpu.dar[15];
if ctx.aligned_only && (sp & 1) != 0 {
return false;
}
let Some(off) = win.off(cpu.address(sp), 4) else {
return false;
};
let ret = win.read(off, Size::Long);
cpu.dar[15] = sp.wrapping_add(4);
cpu.change_of_flow = true;
cpu.pc = ret;
true
}
DecodedMemOp::Bsr {
displacement,
length,
} => {
let base = cpu.pc;
let disp = if length == 4 {
let Some(v) = read_ext_word(cpu, win, &mut ctx) else {
return false;
};
v as u16 as i16 as i32
} else {
displacement
};
let ret = ctx.next_ext;
let sp = cpu.dar[15].wrapping_sub(4);
if ctx.aligned_only && (sp & 1) != 0 {
return false;
}
let Some(off) = win.off(cpu.address(sp), 4) else {
return false;
};
win.write(off, Size::Long, ret);
cpu.dar[15] = sp;
cpu.change_of_flow = true;
cpu.pc = (base as i32).wrapping_add(disp) as u32;
true
}
DecodedMemOp::BranchWord { condition } => {
let base = cpu.pc;
let Some(disp) = read_ext_word(cpu, win, &mut ctx) else {
return false;
};
if condition == 0 || cpu.test_condition(condition) {
cpu.change_of_flow = true;
cpu.pc = (base as i32).wrapping_add(disp as u16 as i16 as i32) as u32;
} else {
cpu.pc = ctx.next_ext;
}
true
}
DecodedMemOp::Dbcc { condition, reg } => {
let Some(disp) = read_ext_word(cpu, win, &mut ctx) else {
return false;
};
if !cpu.test_condition(condition) {
let reg = reg as usize;
let counter = (cpu.dar[reg] as u16).wrapping_sub(1);
cpu.dar[reg] = (cpu.dar[reg] & 0xFFFF_0000) | counter as u32;
if counter != 0xFFFF {
cpu.pc = (cpu.pc as i32).wrapping_add(disp as u16 as i16 as i32) as u32;
return true;
}
}
cpu.pc = ctx.next_ext;
true
}
}
}
#[inline(always)]
fn apply_binary_to_reg(
cpu: &mut CpuCore,
op: BinaryOp,
size: Size,
src: u32,
dst: u32,
reg: usize,
) {
let mask = size.mask();
match op {
BinaryOp::Add => {
let result = src.wrapping_add(dst);
cpu.dar[reg] = (cpu.dar[reg] & !mask) | (result & mask);
cpu.set_add_flags(src, dst, result, size);
}
BinaryOp::Sub => {
let result = dst.wrapping_sub(src);
cpu.dar[reg] = (cpu.dar[reg] & !mask) | (result & mask);
cpu.set_sub_flags(src, dst, result, size);
}
BinaryOp::Cmp => {
let result = dst.wrapping_sub(src);
cpu.set_cmp_flags(src, dst, result, size);
}
BinaryOp::And => {
let result = src & dst;
cpu.dar[reg] = (cpu.dar[reg] & !mask) | (result & mask);
cpu.set_logic_flags(result, size);
}
BinaryOp::Or => {
let result = src | dst;
cpu.dar[reg] = (cpu.dar[reg] & !mask) | (result & mask);
cpu.set_logic_flags(result, size);
}
BinaryOp::Eor => {
let result = src ^ dst;
cpu.dar[reg] = (cpu.dar[reg] & !mask) | (result & mask);
cpu.set_logic_flags(result, size);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cpu_with_window(mem: &mut [u8]) -> CpuCore {
let mut cpu = CpuCore::new();
cpu.set_cpu_type(CpuType::M68040);
cpu.fm_ptr = mem.as_mut_ptr() as usize;
cpu.fm_base = 0;
cpu.fm_len = mem.len() as u32;
cpu.pc = 0x100;
cpu.set_a(5, 0x400);
cpu
}
#[test]
fn fast_an_disp_tst_and_clr_advance_extension_cursor() {
let mut mem = vec![0u8; 0x1000];
mem[0x100..0x102].copy_from_slice(&0x0012u16.to_be_bytes());
mem[0x412] = 0x80;
let mut cpu = cpu_with_window(&mut mem);
assert!(execute_mem_op(
&mut cpu,
DecodedMemOp::Tst {
size: Size::Byte,
ea: FastEa::AnDisp(5),
}
));
assert_eq!(cpu.pc, 0x102);
assert_ne!(cpu.n_flag, 0);
assert_ne!(cpu.not_z_flag, 0);
cpu.pc = 0x100;
assert!(execute_mem_op(
&mut cpu,
DecodedMemOp::Clr {
size: Size::Byte,
ea: FastEa::AnDisp(5),
}
));
assert_eq!(mem[0x412], 0);
assert_eq!(cpu.pc, 0x102);
assert_eq!(cpu.not_z_flag, 0);
}
#[test]
fn fast_an_disp_immediate_bit_uses_both_extension_words() {
let mut mem = vec![0u8; 0x1000];
mem[0x100..0x102].copy_from_slice(&3u16.to_be_bytes());
mem[0x102..0x104].copy_from_slice(&0x0012u16.to_be_bytes());
mem[0x412] = 0b0000_1000;
let mut cpu = cpu_with_window(&mut mem);
assert!(execute_mem_op(
&mut cpu,
DecodedMemOp::BitMem {
op: BitOp::Test,
bit: BitSource::Imm,
ea: FastEa::AnDisp(5),
}
));
assert_eq!(cpu.pc, 0x104);
assert_eq!(cpu.not_z_flag, 1);
assert_eq!(mem[0x412], 0b0000_1000);
}
#[test]
fn fast_an_disp_move_checks_both_operands_before_commit() {
let mut mem = vec![0u8; 0x1000];
mem[0x100..0x102].copy_from_slice(&0x0012u16.to_be_bytes());
mem[0x412..0x416].copy_from_slice(&0xDEAD_BEEFu32.to_be_bytes());
let mut cpu = cpu_with_window(&mut mem);
cpu.set_a(7, 0x800);
assert!(execute_mem_op(
&mut cpu,
DecodedMemOp::Move {
size: Size::Long,
src: FastEa::AnDisp(5),
dst: FastEa::AnPreDec(7),
}
));
assert_eq!(cpu.a(7), 0x7FC);
assert_eq!(&mem[0x7FC..0x800], &0xDEAD_BEEFu32.to_be_bytes());
assert_eq!(cpu.pc, 0x102);
cpu.pc = 0x100;
cpu.set_a(7, 2);
let old_flags = (cpu.n_flag, cpu.not_z_flag, cpu.v_flag, cpu.c_flag);
assert!(!execute_mem_op(
&mut cpu,
DecodedMemOp::Move {
size: Size::Long,
src: FastEa::AnDisp(5),
dst: FastEa::AnPreDec(7),
}
));
assert_eq!(cpu.a(7), 2);
assert_eq!(cpu.pc, 0x100);
assert_eq!(
(cpu.n_flag, cpu.not_z_flag, cpu.v_flag, cpu.c_flag),
old_flags
);
}
}