0

I am writing a gameboy emulator and I have this function:

pub fn execute_classic_instruction(&mut self, opcode: u8) -> u64 {
    match opcode {
        0x31 => self.load_word_immediate_into_reg(&mut self.registers.sp),
        _ => unimplemented!(
            "Unimplemented opcode: 0x{:X} at PC=0x{:X}",
            opcode,
            self.registers.pc.value() - 1
        ),
    }
}

When opcode 0x31 is next it executes this:

pub fn load_word_immediate_into_reg<F>(&mut self, register: &mut Register) -> u64 {
    let value = self.mmu.read_word(self.registers.pc.value());
    self.registers.pc.add(2);

    register.set_value(value);

    12
}

The problem of course is the double borrow that happens due to '&mut self.registers.sp'. Is there any way around that without using unsafe, or maybe another way to do it without writing a separate function for each opcode that changes different registers?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Daniel
  • 85
  • 5

1 Answers1

1

You can take an identifying value instead of a reference.

enum RegisterId {
    Sp,
    Pc,
    ...
}

pub fn execute_classic_instruction(&mut self, opcode: u8) -> u64 {
    match opcode {
        0x31 => self.load_word_immediate_into_reg(RegisterId::Sp),
        _ => unimplemented!(
            "Unimplemented opcode: 0x{:X} at PC=0x{:X}",
            opcode,
            self.registers.pc.value() - 1
        ),
    }
}

pub fn load_word_immediate_into_reg(&mut self, register: RegisterId) -> u64 {
    let value = self.mmu.read_word(self.registers.pc.value());
    self.registers.pc.add(2);

    // Implement Index<RegisterId> so you don't need to match every time
    self.registers[RegisterId].set_value(value);

    12
}

Or you can make this an unassociated function.

pub fn execute_classic_instruction(&mut self, opcode: u8) -> u64 {
    match opcode {
        0x31 => load_word_immediate_into_reg(&mut self.mmu, &mut self.registers.pc, &mut self.registers.sp),
        _ => unimplemented!(
            "Unimplemented opcode: 0x{:X} at PC=0x{:X}",
            opcode,
            self.registers.pc.value() - 1
        ),
    }
}

pub fn load_word_immediate_into_reg(mmu: &mut Mmu, pc: &mut Pc, reg: &mut Reg) -> u64 {
    let value = mmu.read_word(pc.value());
    pc.add(2);

    reg.set_value(value);

    12
}

The second one may be slightly more easily optimized since pc and reg are guaranteed to be separate variables. But these should be inlined enough for it not to matter. The first one is necessary if you want to pass pc as the register.

drewtato
  • 6,783
  • 1
  • 12
  • 17