maikor-vm-core 0.1.15

VM for playing Maikor games
Documentation
use crate::internals::flags::{has_overflowed_byte, has_overflowed_word};
use crate::VM;
use maikor_platform::registers::flags::CARRY;

impl VM {
    pub fn mathc_reg_num_byte(&mut self, method: fn(u8, u8, bool) -> (u8, bool)) -> usize {
        let dst = self.read_arg_register();
        let src = self.read_arg_byte();
        let (offset, offset_cost) = self.pre_process(&dst, 1);
        let (dst_value, read_cost) = self.read_byte_reg(&dst, offset);
        let (result, carried) = method(dst_value, src, self.check_flag(CARRY));
        let write_cost = self.write_byte_reg(&dst, offset, result);
        self.set_math_flags_byte(result, carried, has_overflowed_byte(dst_value, result));
        self.post_process(&dst, 1) + read_cost + write_cost + offset_cost
    }

    pub fn mathc_reg_reg_byte(&mut self, method: fn(u8, u8, bool) -> (u8, bool)) -> usize {
        let dst = self.read_arg_register();
        let src = self.read_arg_register();
        let (dst_offset, offset_cost1) = self.pre_process(&dst, 1);
        let (src_offset, offset_cost2) = self.pre_process(&src, 1);
        let (dst_value, read_cost1) = self.read_byte_reg(&dst, dst_offset);
        let (src_value, read_cost2) = self.read_byte_reg(&src, src_offset);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_byte_reg(&dst, dst_offset, result);
        self.set_math_flags_byte(result, carried, has_overflowed_byte(dst_value, result));
        self.post_process(&src, 1)
            + self.post_process(&dst, 1)
            + write_cost
            + read_cost1
            + read_cost2
            + offset_cost1
            + offset_cost2
    }

    pub fn mathc_addr_num_byte(&mut self, method: fn(u8, u8, bool) -> (u8, bool)) -> usize {
        let dst = self.read_arg_word();
        let src = self.read_arg_byte();
        let (dst_value, read_cost) = self.read_byte_mem(dst);
        let (result, carried) = method(dst_value, src, self.check_flag(CARRY));
        let write_cost = self.write_byte_mem(dst, result);
        self.set_math_flags_byte(result, carried, has_overflowed_byte(dst_value, result));
        read_cost + write_cost
    }

    pub fn mathc_addr_reg_byte(&mut self, method: fn(u8, u8, bool) -> (u8, bool)) -> usize {
        let dst = self.read_arg_word();
        let src = self.read_arg_register();
        let (offset, offset_cost) = self.pre_process(&src, 1);
        let (dst_value, read_cost1) = self.read_byte_mem(dst);
        let (src_value, read_cost2) = self.read_byte_reg(&src, offset);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_byte_mem(dst, result);
        self.set_math_flags_byte(result, carried, has_overflowed_byte(dst_value, result));
        self.post_process(&src, 1) + read_cost1 + read_cost2 + offset_cost + write_cost
    }

    pub fn mathc_addr_addr_byte(&mut self, method: fn(u8, u8, bool) -> (u8, bool)) -> usize {
        let dst = self.read_arg_word();
        let src = self.read_arg_word();
        let (dst_value, read_cost1) = self.read_byte_mem(dst);
        let (src_value, read_cost2) = self.read_byte_mem(src);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_byte_mem(dst, result);
        self.set_math_flags_byte(result, carried, has_overflowed_byte(dst_value, result));
        read_cost1 + read_cost2 + write_cost
    }

    pub fn mathc_reg_addr_byte(&mut self, method: fn(u8, u8, bool) -> (u8, bool)) -> usize {
        let dst = self.read_arg_register();
        let src = self.read_arg_word();
        let (offset, offset_cost) = self.pre_process(&dst, 1);
        let (dst_value, read_cost1) = self.read_byte_reg(&dst, offset);
        let (src_value, read_cost2) = self.read_byte_mem(src);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_byte_reg(&dst, offset, result);
        self.set_math_flags_byte(result, carried, has_overflowed_byte(dst_value, result));
        self.post_process(&dst, 1) + write_cost + read_cost1 + read_cost2 + offset_cost
    }

    pub fn mathc_reg_num_word(&mut self, method: fn(u16, u16, bool) -> (u16, bool)) -> usize {
        let dst = self.read_arg_register();
        let src = self.read_arg_word();
        let (offset, offset_cost) = self.pre_process(&dst, 2);
        let (dst_value, read_cost) = self.read_word_reg(&dst, offset);
        let (result, carried) = method(dst_value, src, self.check_flag(CARRY));
        let write_cost = self.write_word_reg(&dst, offset, result);
        self.set_math_flags_word(result, carried, has_overflowed_word(dst_value, result));
        self.post_process(&dst, 2) + read_cost + write_cost + offset_cost
    }

    pub fn mathc_reg_reg_word(&mut self, method: fn(u16, u16, bool) -> (u16, bool)) -> usize {
        let dst = self.read_arg_register();
        let src = self.read_arg_register();
        let (dst_offset, offset_cost1) = self.pre_process(&dst, 2);
        let (src_offset, offset_cost2) = self.pre_process(&src, 2);
        let (dst_value, read_cost1) = self.read_word_reg(&dst, dst_offset);
        let (src_value, read_cost2) = self.read_word_reg(&src, src_offset);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_word_reg(&dst, dst_offset, result);
        self.set_math_flags_word(result, carried, has_overflowed_word(dst_value, result));
        self.post_process(&src, 2)
            + self.post_process(&dst, 2)
            + write_cost
            + read_cost1
            + read_cost2
            + offset_cost1
            + offset_cost2
    }

    pub fn mathc_addr_num_word(&mut self, method: fn(u16, u16, bool) -> (u16, bool)) -> usize {
        let dst = self.read_arg_word();
        let src = self.read_arg_word();
        let (dst_value, read_cost) = self.read_word_mem(dst);
        let (result, carried) = method(dst_value, src, self.check_flag(CARRY));
        let write_cost = self.write_word_mem(dst, result);
        self.set_math_flags_word(result, carried, has_overflowed_word(dst_value, result));
        read_cost + write_cost
    }

    pub fn mathc_addr_reg_word(&mut self, method: fn(u16, u16, bool) -> (u16, bool)) -> usize {
        let dst = self.read_arg_word();
        let src = self.read_arg_register();
        let (offset, offset_cost) = self.pre_process(&src, 2);
        let (dst_value, read_cost1) = self.read_word_mem(dst);
        let (src_value, read_cost2) = self.read_word_reg(&src, offset);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_word_mem(dst, result);
        self.set_math_flags_word(result, carried, has_overflowed_word(dst_value, result));
        self.post_process(&src, 2) + read_cost1 + read_cost2 + offset_cost + write_cost
    }

    pub fn mathc_addr_addr_word(&mut self, method: fn(u16, u16, bool) -> (u16, bool)) -> usize {
        let dst = self.read_arg_word();
        let src = self.read_arg_word();
        let (dst_value, read_cost1) = self.read_word_mem(dst);
        let (src_value, read_cost2) = self.read_word_mem(src);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_word_mem(dst, result);
        self.set_math_flags_word(result, carried, has_overflowed_word(dst_value, result));
        read_cost1 + read_cost2 + write_cost
    }

    pub fn mathc_reg_addr_word(&mut self, method: fn(u16, u16, bool) -> (u16, bool)) -> usize {
        let dst = self.read_arg_register();
        let src = self.read_arg_word();
        let (offset, offset_cost) = self.pre_process(&dst, 2);
        let (dst_value, read_cost1) = self.read_word_reg(&dst, offset);
        let (src_value, read_cost2) = self.read_word_mem(src);
        let (result, carried) = method(dst_value, src_value, self.check_flag(CARRY));
        let write_cost = self.write_word_reg(&dst, offset, result);
        self.set_math_flags_word(result, carried, has_overflowed_word(dst_value, result));
        self.post_process(&dst, 2) + write_cost + read_cost1 + read_cost2 + offset_cost
    }
}

pub fn u8_carrying_add(lhs: u8, rhs: u8, carry: bool) -> (u8, bool) {
    let (a, b) = lhs.overflowing_add(rhs);
    let (c, d) = a.overflowing_add(carry as u8);
    (c, b || d)
}

pub fn u16_carrying_add(lhs: u16, rhs: u16, carry: bool) -> (u16, bool) {
    let (a, b) = lhs.overflowing_add(rhs);
    let (c, d) = a.overflowing_add(carry as u16);
    (c, b || d)
}

pub fn u8_carrying_sub(lhs: u8, rhs: u8, carry: bool) -> (u8, bool) {
    let (a, b) = lhs.overflowing_sub(rhs);
    let (c, d) = a.overflowing_sub(carry as u8);
    (c, b || d)
}

pub fn u16_carrying_sub(lhs: u16, rhs: u16, carry: bool) -> (u16, bool) {
    let (a, b) = lhs.overflowing_sub(rhs);
    let (c, d) = a.overflowing_sub(carry as u16);
    (c, b || d)
}

#[allow(clippy::type_complexity)]
#[cfg(test)]
mod test {
    use super::*;
    use maikor_platform::mem::address::RESERVED;
    use maikor_platform::registers::id;

    #[test]
    fn test_costs() {
        check_math_methods_b(u8_carrying_add);
        check_math_methods_b(u8_carrying_sub);
        check_math_methods_w(u16_carrying_add);
        check_math_methods_w(u16_carrying_sub);
    }

    pub fn check_math_methods_b(math_method: fn(u8, u8, bool) -> (u8, bool)) {
        check_byte_math_cycles(&[id::AL as u8, 10], 2, math_method, VM::mathc_reg_num_byte);
        check_byte_math_cycles(
            &[id::AL as u8, id::AH as u8],
            3,
            math_method,
            VM::mathc_reg_reg_byte,
        );
        check_byte_math_cycles(
            &[id::CH as u8, 0, 100],
            3,
            math_method,
            VM::mathc_reg_addr_byte,
        );
        check_byte_math_cycles(&[1, 1, 60], 2, math_method, VM::mathc_addr_num_byte);
        check_byte_math_cycles(
            &[9, 9, id::AL as u8],
            3,
            math_method,
            VM::mathc_addr_reg_byte,
        );
        check_byte_math_cycles(&[0, 0, 1, 1], 3, math_method, VM::mathc_addr_addr_byte);
    }

    pub fn check_math_methods_w(math_method: fn(u16, u16, bool) -> (u16, bool)) {
        check_word_math_cycles(&[id::AX as u8, 10], 4, math_method, VM::mathc_reg_num_word);
        check_word_math_cycles(
            &[id::AL as u8, id::AH as u8],
            6,
            math_method,
            VM::mathc_reg_reg_word,
        );
        check_word_math_cycles(
            &[id::CX as u8, 0, 100],
            6,
            math_method,
            VM::mathc_reg_addr_word,
        );
        check_word_math_cycles(&[1, 1, 60], 4, math_method, VM::mathc_addr_num_word);
        check_word_math_cycles(
            &[9, 9, id::AX as u8],
            6,
            math_method,
            VM::mathc_addr_reg_word,
        );
        check_word_math_cycles(&[0, 0, 1, 1], 6, math_method, VM::mathc_addr_addr_word);
    }

    pub fn check_byte_math_cycles(
        bytes: &[u8],
        expected_cycles: usize,
        math_method: fn(u8, u8, bool) -> (u8, bool),
        op_method: fn(&mut VM, fn(u8, u8, bool) -> (u8, bool)) -> usize,
    ) {
        let mut vm = VM::new_test();
        vm.arg_ptr = RESERVED;
        for (i, byte) in bytes.iter().enumerate() {
            vm.memory[RESERVED as usize + i] = *byte;
        }
        assert_eq!(op_method(&mut vm, math_method), expected_cycles)
    }

    pub fn check_word_math_cycles(
        bytes: &[u8],
        expected_cycles: usize,
        math_method: fn(u16, u16, bool) -> (u16, bool),
        op_method: fn(&mut VM, fn(u16, u16, bool) -> (u16, bool)) -> usize,
    ) {
        let mut vm = VM::new_test();
        vm.arg_ptr = RESERVED;
        for (i, byte) in bytes.iter().enumerate() {
            vm.memory[RESERVED as usize + i] = *byte;
        }
        assert_eq!(op_method(&mut vm, math_method), expected_cycles)
    }
}