// The opcode decoder.
//
// One match arm per official opcode, plus the unofficial ones that shipped games
// and the conformance ROMs execute. Cycle counts live here rather than in the
// operations, because a 6502's cost is a property of the addressing mode:
// `LDA $1234` and `LDA ($12),Y` reach the same operation with different prices,
// and page-crossing penalties only apply to the modes that can cross at all.
//
// The single-opcode arms follow the data sheet line by line, which makes a wrong
// count easy to see against the entry it came from.

///|
/// A read-modify-write operation applied to a memory location.
priv enum RmwOp {
  Inc
  Dec
  Asl
  Lsr
  Rol
  Ror
}

///|
/// A two-step operation that modifies memory and then combines it into a
/// register, the shape of six unofficial instructions.
priv enum CombinedOp {
  SlO
  Rla
  Sre
  Rra
  Dcp
  Isc
}

///|
/// Executes one already-fetched opcode and returns what it did.
fn Cpu::execute(
  self : Cpu,
  bus : &Bus,
  opcode : UInt32,
  at : UInt32,
) -> StepResult raise NesError {
  // Cleared first so that "no memory touched" is the default state.
  self.last_access = None
  let cycles = match opcode {
    0x01 => {
      let addr = self.indexed_indirect(bus)
      self.ora(self.read_operand(bus, addr))
      6L
    }
    0x05 => {
      let addr = self.zeropage(bus)
      self.ora(self.read_operand(bus, addr))
      3L
    }
    0x09 => {
      self.ora(self.immediate(bus))
      2L
    }
    0x0D => {
      let addr = self.absolute(bus)
      self.ora(self.read_operand(bus, addr))
      4L
    }
    0x11 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.ora(self.read_operand(bus, addr))
      5L + page
    }
    0x15 => {
      let addr = self.zeropage_x(bus)
      self.ora(self.read_operand(bus, addr))
      4L
    }
    0x19 => {
      let (addr, page) = self.absolute_y_page(bus)
      self.ora(self.read_operand(bus, addr))
      4L + page
    }
    0x1D => {
      let (addr, page) = self.absolute_x_page(bus)
      self.ora(self.read_operand(bus, addr))
      4L + page
    }
    0x21 => {
      let addr = self.indexed_indirect(bus)
      self.and_(self.read_operand(bus, addr))
      6L
    }
    0x25 => {
      let addr = self.zeropage(bus)
      self.and_(self.read_operand(bus, addr))
      3L
    }
    0x29 => {
      self.and_(self.immediate(bus))
      2L
    }
    0x2D => {
      let addr = self.absolute(bus)
      self.and_(self.read_operand(bus, addr))
      4L
    }
    0x31 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.and_(self.read_operand(bus, addr))
      5L + page
    }
    0x35 => {
      let addr = self.zeropage_x(bus)
      self.and_(self.read_operand(bus, addr))
      4L
    }
    0x39 => {
      let (addr, page) = self.absolute_y_page(bus)
      self.and_(self.read_operand(bus, addr))
      4L + page
    }
    0x3D => {
      let (addr, page) = self.absolute_x_page(bus)
      self.and_(self.read_operand(bus, addr))
      4L + page
    }
    0x41 => {
      let addr = self.indexed_indirect(bus)
      self.eor(self.read_operand(bus, addr))
      6L
    }
    0x45 => {
      let addr = self.zeropage(bus)
      self.eor(self.read_operand(bus, addr))
      3L
    }
    0x49 => {
      self.eor(self.immediate(bus))
      2L
    }
    0x4D => {
      let addr = self.absolute(bus)
      self.eor(self.read_operand(bus, addr))
      4L
    }
    0x51 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.eor(self.read_operand(bus, addr))
      5L + page
    }
    0x55 => {
      let addr = self.zeropage_x(bus)
      self.eor(self.read_operand(bus, addr))
      4L
    }
    0x59 => {
      let (addr, page) = self.absolute_y_page(bus)
      self.eor(self.read_operand(bus, addr))
      4L + page
    }
    0x5D => {
      let (addr, page) = self.absolute_x_page(bus)
      self.eor(self.read_operand(bus, addr))
      4L + page
    }
    0x61 => {
      let addr = self.indexed_indirect(bus)
      self.adc(self.read_operand(bus, addr))
      6L
    }
    0x65 => {
      let addr = self.zeropage(bus)
      self.adc(self.read_operand(bus, addr))
      3L
    }
    0x69 => {
      self.adc(self.immediate(bus))
      2L
    }
    0x6D => {
      let addr = self.absolute(bus)
      self.adc(self.read_operand(bus, addr))
      4L
    }
    0x71 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.adc(self.read_operand(bus, addr))
      5L + page
    }
    0x75 => {
      let addr = self.zeropage_x(bus)
      self.adc(self.read_operand(bus, addr))
      4L
    }
    0x79 => {
      let (addr, page) = self.absolute_y_page(bus)
      self.adc(self.read_operand(bus, addr))
      4L + page
    }
    0x7D => {
      let (addr, page) = self.absolute_x_page(bus)
      self.adc(self.read_operand(bus, addr))
      4L + page
    }
    0x81 => {
      let addr = self.indexed_indirect(bus)
      self.store(bus, addr, self.a)
      6L
    }
    0x84 => {
      let addr = self.zeropage(bus)
      self.store(bus, addr, self.y)
      3L
    }
    0x85 => {
      let addr = self.zeropage(bus)
      self.store(bus, addr, self.a)
      3L
    }
    0x86 => {
      let addr = self.zeropage(bus)
      self.store(bus, addr, self.x)
      3L
    }
    0x8C => {
      let addr = self.absolute(bus)
      self.store(bus, addr, self.y)
      4L
    }
    0x8D => {
      let addr = self.absolute(bus)
      self.store(bus, addr, self.a)
      4L
    }
    0x8E => {
      let addr = self.absolute(bus)
      self.store(bus, addr, self.x)
      4L
    }
    0x91 => {
      // Every store through an indexed mode pays the full six cycles: unlike a
      // read, a store cannot skip the cycle that would have fixed up the address
      // after a page crossing, so the penalty is unconditional.
      let addr = self.indirect_indexed(bus)
      self.store(bus, addr, self.a)
      6L
    }
    0x94 => {
      let addr = self.zeropage_x(bus)
      self.store(bus, addr, self.y)
      4L
    }
    0x95 => {
      let addr = self.zeropage_x(bus)
      self.store(bus, addr, self.a)
      4L
    }
    0x96 => {
      let addr = self.zeropage_y(bus)
      self.store(bus, addr, self.x)
      4L
    }
    0x99 => {
      let addr = self.absolute_y(bus)
      self.store(bus, addr, self.a)
      5L
    }
    0x9D => {
      let addr = self.absolute_x(bus)
      self.store(bus, addr, self.a)
      5L
    }
    0xA0 => {
      self.ldy(self.immediate(bus))
      2L
    }
    0xA1 => {
      let addr = self.indexed_indirect(bus)
      self.lda(self.read_operand(bus, addr))
      6L
    }
    0xA2 => {
      self.ldx(self.immediate(bus))
      2L
    }
    0xA4 => {
      let addr = self.zeropage(bus)
      self.ldy(self.read_operand(bus, addr))
      3L
    }
    0xA5 => {
      let addr = self.zeropage(bus)
      self.lda(self.read_operand(bus, addr))
      3L
    }
    0xA6 => {
      let addr = self.zeropage(bus)
      self.ldx(self.read_operand(bus, addr))
      3L
    }
    0xA9 => {
      self.lda(self.immediate(bus))
      2L
    }
    0xAC => {
      let addr = self.absolute(bus)
      self.ldy(self.read_operand(bus, addr))
      4L
    }
    0xAD => {
      let addr = self.absolute(bus)
      self.lda(self.read_operand(bus, addr))
      4L
    }
    0xAE => {
      let addr = self.absolute(bus)
      self.ldx(self.read_operand(bus, addr))
      4L
    }
    0xB1 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.lda(self.read_operand(bus, addr))
      5L + page
    }
    0xB4 => {
      let addr = self.zeropage_x(bus)
      self.ldy(self.read_operand(bus, addr))
      4L
    }
    0xB5 => {
      let addr = self.zeropage_x(bus)
      self.lda(self.read_operand(bus, addr))
      4L
    }
    0xB6 => {
      let addr = self.zeropage_y(bus)
      self.ldx(self.read_operand(bus, addr))
      4L
    }
    0xB9 => {
      let (addr, page) = self.absolute_y_page(bus)
      self.lda(self.read_operand(bus, addr))
      4L + page
    }
    0xBC => {
      let (addr, page) = self.absolute_x_page(bus)
      self.ldy(self.read_operand(bus, addr))
      4L + page
    }
    0xBD => {
      let (addr, page) = self.absolute_x_page(bus)
      self.lda(self.read_operand(bus, addr))
      4L + page
    }
    0xBE => {
      let (addr, page) = self.absolute_y_page(bus)
      self.ldx(self.read_operand(bus, addr))
      4L + page
    }
    0xC0 => {
      self.compare(self.y, self.immediate(bus))
      2L
    }
    0xC1 => {
      let addr = self.indexed_indirect(bus)
      self.compare(self.a, self.read_operand(bus, addr))
      6L
    }
    0xC4 => {
      let addr = self.zeropage(bus)
      self.compare(self.y, self.read_operand(bus, addr))
      3L
    }
    0xC5 => {
      let addr = self.zeropage(bus)
      self.compare(self.a, self.read_operand(bus, addr))
      3L
    }
    0xC9 => {
      self.compare(self.a, self.immediate(bus))
      2L
    }
    0xCC => {
      let addr = self.absolute(bus)
      self.compare(self.y, self.read_operand(bus, addr))
      4L
    }
    0xCD => {
      let addr = self.absolute(bus)
      self.compare(self.a, self.read_operand(bus, addr))
      4L
    }
    0xD1 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.compare(self.a, self.read_operand(bus, addr))
      5L + page
    }
    0xD5 => {
      let addr = self.zeropage_x(bus)
      self.compare(self.a, self.read_operand(bus, addr))
      4L
    }
    0xD9 => {
      let (addr, page) = self.absolute_y_page(bus)
      self.compare(self.a, self.read_operand(bus, addr))
      4L + page
    }
    0xDD => {
      let (addr, page) = self.absolute_x_page(bus)
      self.compare(self.a, self.read_operand(bus, addr))
      4L + page
    }
    0xE0 => {
      self.compare(self.x, self.immediate(bus))
      2L
    }
    0xE1 => {
      let addr = self.indexed_indirect(bus)
      self.sbc(self.read_operand(bus, addr))
      6L
    }
    0xE4 => {
      let addr = self.zeropage(bus)
      self.compare(self.x, self.read_operand(bus, addr))
      3L
    }
    0xE5 => {
      let addr = self.zeropage(bus)
      self.sbc(self.read_operand(bus, addr))
      3L
    }
    0xE9 => {
      self.sbc(self.immediate(bus))
      2L
    }
    0xEB => {
      self.sbc(self.immediate(bus))
      2L
    }
    0xEC => {
      let addr = self.absolute(bus)
      self.compare(self.x, self.read_operand(bus, addr))
      4L
    }
    0xED => {
      let addr = self.absolute(bus)
      self.sbc(self.read_operand(bus, addr))
      4L
    }
    0xF1 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.sbc(self.read_operand(bus, addr))
      5L + page
    }
    0xF5 => {
      let addr = self.zeropage_x(bus)
      self.sbc(self.read_operand(bus, addr))
      4L
    }
    0xF9 => {
      let (addr, page) = self.absolute_y_page(bus)
      self.sbc(self.read_operand(bus, addr))
      4L + page
    }
    0xFD => {
      let (addr, page) = self.absolute_x_page(bus)
      self.sbc(self.read_operand(bus, addr))
      4L + page
    }
    // ---- BIT ----
    0x24 => {
      let addr = self.zeropage(bus)
      self.bit(self.read_operand(bus, addr))
      3L
    }
    0x2C => {
      let addr = self.absolute(bus)
      self.bit(self.read_operand(bus, addr))
      4L
    }
    // ---- Transfers ----
    0xAA => {
      self.x = self.a
      self.set_zn(self.x)
      2L
    }
    0xA8 => {
      self.y = self.a
      self.set_zn(self.y)
      2L
    }
    0x8A => {
      self.a = self.x
      self.set_zn(self.a)
      2L
    }
    0x98 => {
      self.a = self.y
      self.set_zn(self.a)
      2L
    }
    0xBA => {
      self.x = self.sp
      self.set_zn(self.x)
      2L
    }
    0x9A => {
      self.sp = self.x
      2L
    }
    // ---- Stack ----
    0x48 => {
      self.push(bus, self.a)
      3L
    }
    0x68 => {
      self.a = self.pop(bus)
      self.set_zn(self.a)
      4L
    }
    0x08 => {
      self.push(bus, self.p | FLAG_BREAK | FLAG_UNUSED)
      3L
    }
    0x28 => {
      self.p = (self.pop(bus) | FLAG_UNUSED) & (FLAG_BREAK ^ 0xFFFF_FFFFU)
      4L
    }
    // ---- Register increments ----
    0xE8 => {
      self.x = self.inc(self.x)
      2L
    }
    0xC8 => {
      self.y = self.inc(self.y)
      2L
    }
    0xCA => {
      self.x = self.dec(self.x)
      2L
    }
    0x88 => {
      self.y = self.dec(self.y)
      2L
    }
    // ---- Shifts on the accumulator ----
    0x0A => {
      self.a = self.asl(self.a).0
      2L
    }
    0x4A => {
      self.a = self.lsr(self.a).0
      2L
    }
    0x2A => {
      self.a = self.rol(self.a).0
      2L
    }
    0x6A => {
      self.a = self.ror(self.a).0
      2L
    }
    // ---- Read-modify-write ----
    0xE6 => {
      let addr = self.zeropage(bus)
      self.rmw(bus, addr, Inc, 5L)
    }
    0xF6 => {
      let addr = self.zeropage_x(bus)
      self.rmw(bus, addr, Inc, 6L)
    }
    0xEE => {
      let addr = self.absolute(bus)
      self.rmw(bus, addr, Inc, 6L)
    }
    0xFE => {
      let addr = self.absolute_x(bus)
      self.rmw(bus, addr, Inc, 7L)
    }
    0xC6 => {
      let addr = self.zeropage(bus)
      self.rmw(bus, addr, Dec, 5L)
    }
    0xD6 => {
      let addr = self.zeropage_x(bus)
      self.rmw(bus, addr, Dec, 6L)
    }
    0xCE => {
      let addr = self.absolute(bus)
      self.rmw(bus, addr, Dec, 6L)
    }
    0xDE => {
      let addr = self.absolute_x(bus)
      self.rmw(bus, addr, Dec, 7L)
    }
    0x06 => {
      let addr = self.zeropage(bus)
      self.rmw(bus, addr, Asl, 5L)
    }
    0x16 => {
      let addr = self.zeropage_x(bus)
      self.rmw(bus, addr, Asl, 6L)
    }
    0x0E => {
      let addr = self.absolute(bus)
      self.rmw(bus, addr, Asl, 6L)
    }
    0x1E => {
      let addr = self.absolute_x(bus)
      self.rmw(bus, addr, Asl, 7L)
    }
    0x46 => {
      let addr = self.zeropage(bus)
      self.rmw(bus, addr, Lsr, 5L)
    }
    0x56 => {
      let addr = self.zeropage_x(bus)
      self.rmw(bus, addr, Lsr, 6L)
    }
    0x4E => {
      let addr = self.absolute(bus)
      self.rmw(bus, addr, Lsr, 6L)
    }
    0x5E => {
      let addr = self.absolute_x(bus)
      self.rmw(bus, addr, Lsr, 7L)
    }
    0x26 => {
      let addr = self.zeropage(bus)
      self.rmw(bus, addr, Rol, 5L)
    }
    0x36 => {
      let addr = self.zeropage_x(bus)
      self.rmw(bus, addr, Rol, 6L)
    }
    0x2E => {
      let addr = self.absolute(bus)
      self.rmw(bus, addr, Rol, 6L)
    }
    0x3E => {
      let addr = self.absolute_x(bus)
      self.rmw(bus, addr, Rol, 7L)
    }
    0x66 => {
      let addr = self.zeropage(bus)
      self.rmw(bus, addr, Ror, 5L)
    }
    0x76 => {
      let addr = self.zeropage_x(bus)
      self.rmw(bus, addr, Ror, 6L)
    }
    0x6E => {
      let addr = self.absolute(bus)
      self.rmw(bus, addr, Ror, 6L)
    }
    0x7E => {
      let addr = self.absolute_x(bus)
      self.rmw(bus, addr, Ror, 7L)
    }
    // ---- Control flow ----
    0x4C => {
      self.pc = self.fetch16(bus)
      3L
    }
    0x6C => self.jmp_indirect(bus)
    0x20 => {
      // JSR pushes its own last byte, so RTS's increment lands on the next
      // instruction.
      let target = self.fetch16(bus)
      let ret = (self.pc - 1) & 0xFFFF
      self.push(bus, (ret >> 8) & 0xFF)
      self.push(bus, ret & 0xFF)
      self.pc = target
      6L
    }
    0x60 => {
      let lo = self.pop(bus)
      let hi = self.pop(bus)
      self.pc = (((hi << 8) | lo) + 1) & 0xFFFF
      6L
    }
    0x40 => {
      // RTI pulls the status first and the program counter second, and unlike
      // RTS it does not increment: BRK already pushed the address of the byte
      // after its own operand byte, which is the address to resume at.
      let status = self.pop(bus)
      self.p = (status | FLAG_UNUSED) & (FLAG_BREAK ^ 0xFFFF_FFFFU)
      let lo = self.pop(bus)
      let hi = self.pop(bus)
      self.pc = (hi << 8) | lo
      6L
    }
    0x00 => self.brk(bus)
    0x10 => self.branch(bus, !self.flag(Negative))
    0x30 => self.branch(bus, self.flag(Negative))
    0x50 => self.branch(bus, !self.flag(Overflow))
    0x70 => self.branch(bus, self.flag(Overflow))
    0x90 => self.branch(bus, !self.flag(Carry))
    0xB0 => self.branch(bus, self.flag(Carry))
    0xD0 => self.branch(bus, !self.flag(Zero))
    0xF0 => self.branch(bus, self.flag(Zero))
    // ---- Flag operations ----
    0x18 => {
      self.set_flag(Carry, false)
      2L
    }
    0x38 => {
      self.set_flag(Carry, true)
      2L
    }
    0x58 => {
      self.set_flag(Interrupt, false)
      2L
    }
    0x78 => {
      self.set_flag(Interrupt, true)
      2L
    }
    0xB8 => {
      self.set_flag(Overflow, false)
      2L
    }
    0xD8 => {
      self.set_flag(Decimal, false)
      2L
    }
    0xF8 => {
      self.set_flag(Decimal, true)
      2L
    }
    0xEA => 2L
    // ---- Unofficial ----
    0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xFA => 2L
    0x80 | 0x82 | 0x89 | 0xC2 | 0xE2 => {
      ignore(self.fetch(bus))
      2L
    }
    0x04 | 0x44 | 0x64 => {
      ignore(self.zeropage(bus))
      3L
    }
    0x14 | 0x34 | 0x54 | 0x74 | 0xD4 | 0xF4 => {
      ignore(self.zeropage_x(bus))
      4L
    }
    0x0C => {
      ignore(self.absolute(bus))
      4L
    }
    0x1C | 0x3C | 0x5C | 0x7C | 0xDC | 0xFC => {
      // The multi-byte NOP reads its operand address and therefore pays the same
      // page-crossing penalty a real indexed read would.
      let (addr, page) = self.absolute_x_page(bus)
      ignore(self.read_operand(bus, addr))
      4L + page
    }
    0xA7 => self.lax(bus, self.zeropage(bus), 3L)
    0xB7 => self.lax(bus, self.zeropage_y(bus), 4L)
    0xAF => self.lax(bus, self.absolute(bus), 4L)
    0xBF => {
      let (addr, page) = self.absolute_y_page(bus)
      self.lax(bus, addr, 4L) + page
    }
    0xA3 => self.lax(bus, self.indexed_indirect(bus), 6L)
    0xB3 => {
      let (addr, page) = self.indirect_indexed_page(bus)
      self.lax(bus, addr, 5L) + page
    }
    0x87 => {
      let addr = self.zeropage(bus)
      self.store(bus, addr, self.a & self.x)
      3L
    }
    0x97 => {
      let addr = self.zeropage_y(bus)
      self.store(bus, addr, self.a & self.x)
      4L
    }
    0x8F => {
      let addr = self.absolute(bus)
      self.store(bus, addr, self.a & self.x)
      4L
    }
    0x83 => {
      let addr = self.indexed_indirect(bus)
      self.store(bus, addr, self.a & self.x)
      6L
    }
    0x07 => self.combined(bus, self.zeropage(bus), SlO, 5L)
    0x17 => self.combined(bus, self.zeropage_x(bus), SlO, 6L)
    0x0F => self.combined(bus, self.absolute(bus), SlO, 6L)
    0x1F => self.combined(bus, self.absolute_x(bus), SlO, 7L)
    0x1B => self.combined(bus, self.absolute_y(bus), SlO, 7L)
    0x03 => self.combined(bus, self.indexed_indirect(bus), SlO, 8L)
    0x13 => self.combined(bus, self.indirect_indexed(bus), SlO, 8L)
    0x27 => self.combined(bus, self.zeropage(bus), Rla, 5L)
    0x37 => self.combined(bus, self.zeropage_x(bus), Rla, 6L)
    0x2F => self.combined(bus, self.absolute(bus), Rla, 6L)
    0x3F => self.combined(bus, self.absolute_x(bus), Rla, 7L)
    0x3B => self.combined(bus, self.absolute_y(bus), Rla, 7L)
    0x23 => self.combined(bus, self.indexed_indirect(bus), Rla, 8L)
    0x33 => self.combined(bus, self.indirect_indexed(bus), Rla, 8L)
    0x47 => self.combined(bus, self.zeropage(bus), Sre, 5L)
    0x57 => self.combined(bus, self.zeropage_x(bus), Sre, 6L)
    0x4F => self.combined(bus, self.absolute(bus), Sre, 6L)
    0x5F => self.combined(bus, self.absolute_x(bus), Sre, 7L)
    0x5B => self.combined(bus, self.absolute_y(bus), Sre, 7L)
    0x43 => self.combined(bus, self.indexed_indirect(bus), Sre, 8L)
    0x53 => self.combined(bus, self.indirect_indexed(bus), Sre, 8L)
    0x67 => self.combined(bus, self.zeropage(bus), Rra, 5L)
    0x77 => self.combined(bus, self.zeropage_x(bus), Rra, 6L)
    0x6F => self.combined(bus, self.absolute(bus), Rra, 6L)
    0x7F => self.combined(bus, self.absolute_x(bus), Rra, 7L)
    0x7B => self.combined(bus, self.absolute_y(bus), Rra, 7L)
    0x63 => self.combined(bus, self.indexed_indirect(bus), Rra, 8L)
    0x73 => self.combined(bus, self.indirect_indexed(bus), Rra, 8L)
    0xC7 => self.combined(bus, self.zeropage(bus), Dcp, 5L)
    0xD7 => self.combined(bus, self.zeropage_x(bus), Dcp, 6L)
    0xCF => self.combined(bus, self.absolute(bus), Dcp, 6L)
    0xDF => self.combined(bus, self.absolute_x(bus), Dcp, 7L)
    0xDB => self.combined(bus, self.absolute_y(bus), Dcp, 7L)
    0xC3 => self.combined(bus, self.indexed_indirect(bus), Dcp, 8L)
    0xD3 => self.combined(bus, self.indirect_indexed(bus), Dcp, 8L)
    0xE7 => self.combined(bus, self.zeropage(bus), Isc, 5L)
    0xF7 => self.combined(bus, self.zeropage_x(bus), Isc, 6L)
    0xEF => self.combined(bus, self.absolute(bus), Isc, 6L)
    0xFF => self.combined(bus, self.absolute_x(bus), Isc, 7L)
    0xFB => self.combined(bus, self.absolute_y(bus), Isc, 7L)
    0xE3 => self.combined(bus, self.indexed_indirect(bus), Isc, 8L)
    0xF3 => self.combined(bus, self.indirect_indexed(bus), Isc, 8L)
    0x0B | 0x2B => {
      self.a = self.a & self.fetch(bus)
      self.set_zn(self.a)
      self.set_flag(Carry, (self.a & 0x80) != 0)
      2L
    }
    0x4B => {
      self.a = (self.a >> 1) & self.fetch(bus)
      self.set_zn(self.a)
      2L
    }
    0x6B => {
      let v = self.a & self.fetch(bus)
      let carry = if self.flag(Carry) { 1U } else { 0U }
      self.a = ((v >> 1) | (carry << 7)) & 0xFF
      self.set_zn(self.a)
      self.set_flag(Carry, (self.a & 0x40) != 0)
      self.set_flag(Overflow, ((self.a >> 6) & 1) != ((self.a >> 5) & 1))
      2L
    }
    0xCB => {
      let v = self.a & self.fetch(bus)
      let result = (v - self.x) & 0xFF
      self.set_flag(Carry, v >= self.x)
      self.x = result
      self.set_zn(result)
      2L
    }
    0x8B => {
      let v = self.a & self.x & self.fetch(bus)
      let addr = self.zeropage(bus)
      self.store(bus, addr, v)
      2L
    }
    0x9F | 0x9B => {
      let (addr, _) = self.absolute_y_page(bus)
      let v = self.a & self.x & ((addr >> 8) & 0xFF)
      self.store(bus, addr, v)
      5L
    }
    0x93 => {
      let (addr, _) = self.indirect_indexed_page(bus)
      let v = self.a & self.x & ((addr >> 8) & 0xFF)
      self.store(bus, addr, v)
      6L
    }
    0x9C => {
      let (addr, _) = self.absolute_x_page(bus)
      let v = self.y & (((addr >> 8) + 1) & 0xFF)
      self.store(bus, addr, v)
      5L
    }
    0x9E => {
      let (addr, _) = self.absolute_y_page(bus)
      let v = self.x & (((addr >> 8) + 1) & 0xFF)
      self.store(bus, addr, v)
      5L
    }
    0xBB => {
      let (addr, page) = self.absolute_y_page(bus)
      let v = self.read_operand(bus, addr) & self.x & 0xFF
      self.sp = v
      self.a = v
      self.x = v
      self.set_zn(v)
      4L + page
    }
    _ => raise UnknownOpcode(opcode~, at~)
  }
  { cycles, opcode, at, memory: self.last_access, }
}

///|
/// Reads an operand, recording it so that the trace can render `LDA $00 = 01`.
fn Cpu::read_operand(self : Cpu, bus : &Bus, address : UInt32) -> UInt32 {
  let value = bus.read(address & 0xFFFF) & 0xFF
  self.last_access = Some({ address, value, write: false, })
  value
}

///|
/// Applies a read-modify-write operation to a memory address.
///
/// The 6502 writes the unmodified value back before the modified one; nothing a
/// ROM can observe depends on that, so only the final value is written while the
/// passed cycle count carries the cost of both writes.
fn Cpu::rmw(
  self : Cpu,
  bus : &Bus,
  address : UInt32,
  op : RmwOp,
  cycles : Int64,
) -> Int64 {
  let current = bus.read(address & 0xFFFF) & 0xFF
  let result = match op {
    Inc => self.inc(current)
    Dec => self.dec(current)
    Asl => self.asl(current).0
    Lsr => self.lsr(current).0
    Rol => self.rol(current).0
    Ror => self.ror(current).0
  }
  bus.write(address & 0xFFFF, result)
  self.last_access = Some({ address, value: result, write: true, })
  cycles
}

///|
/// `LAX`: loads both the accumulator and X from one value.
fn Cpu::lax(self : Cpu, bus : &Bus, address : UInt32, cycles : Int64) -> Int64 {
  let value = self.read_operand(bus, address)
  self.a = value
  self.x = value
  self.set_zn(value)
  cycles
}

///|
/// The unofficial read-modify-write-then-combine instructions.
fn Cpu::combined(
  self : Cpu,
  bus : &Bus,
  address : UInt32,
  op : CombinedOp,
  cycles : Int64,
) -> Int64 {
  let current = bus.read(address & 0xFFFF) & 0xFF
  let modified = match op {
    SlO => self.asl(current).0
    Rla => self.rol(current).0
    Sre => self.lsr(current).0
    Rra => self.ror(current).0
    Dcp => self.dec(current)
    Isc => self.inc(current)
  }
  bus.write(address & 0xFFFF, modified)
  self.last_access = Some({ address, value: modified, write: true, })
  match op {
    SlO => {
      self.a = self.a | modified
      self.set_zn(self.a)
    }
    Rla => {
      self.a = self.a & modified
      self.set_zn(self.a)
    }
    Sre => {
      self.a = self.a ^ modified
      self.set_zn(self.a)
    }
    Rra => self.adc(modified)
    Dcp => self.compare(self.a, modified)
    Isc => self.sbc(modified)
  }
  cycles
}