From 71bcec157413d596edddeb4ee29e02f84fb0d462 Mon Sep 17 00:00:00 2001 From: Jared Burce Date: Wed, 10 Mar 2021 07:09:59 -0800 Subject: [PATCH] emu: wrap cpu regs in Cell. r16 addr mode. r16 + modrm16 allowed cpu regs to alias. Wrap them in Cell to allow this. We also create a Reg wrapper when passing regs so that we can pass as &mut while simultaneously having other references to the same reg. --- src/emu/dos.rs | 6 +- src/emu/i8088.rs | 172 ++++++++++++++++++++++++++--------------------- src/emu/pc.rs | 13 ++-- 3 files changed, 104 insertions(+), 87 deletions(-) diff --git a/src/emu/dos.rs b/src/emu/dos.rs index 3cbcdfb..70c6763 100644 --- a/src/emu/dos.rs +++ b/src/emu/dos.rs @@ -2,16 +2,16 @@ use emu::pc::Bus; use emu::i8088::{i8088, read_hi, read_lo, segoff_to_addr}; pub fn interrupt(cpu: &mut i8088, bus: &mut Bus) { - let svc = read_hi(cpu.a); + let svc = read_hi(&cpu.a); match svc { 0x09 => print_string(cpu, bus), - 0x4C => exit(read_lo(cpu.a)), + 0x4C => exit(read_lo(&cpu.a)), _ => unimplemented!("dos service: AH={:02X}h\ncpu: {:#X?}", svc, cpu) } } fn print_string(cpu: &i8088, bus: &Bus) { - let addr = segoff_to_addr(cpu.ds, cpu.d); + let addr = segoff_to_addr(cpu.ds.get(), cpu.d.get()); for byte in bus.ram[addr..].iter().take_while(|byte| **byte as char != '$') { print!("{}", *byte as char); } diff --git a/src/emu/i8088.rs b/src/emu/i8088.rs index 68622a8..b859455 100644 --- a/src/emu/i8088.rs +++ b/src/emu/i8088.rs @@ -1,3 +1,4 @@ +use std::cell::Cell; use std::fmt::{Debug, Formatter}; use super::byteorder::{ByteOrder, LittleEndian}; @@ -5,28 +6,28 @@ use super::byteorder::{ByteOrder, LittleEndian}; use emu::pc::Bus; #[allow(non_camel_case_types)] -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct i8088 { // Data Registers - pub a: u16, - pub b: u16, - pub c: u16, - pub d: u16, + pub a: Cell, + pub b: Cell, + pub c: Cell, + pub d: Cell, // Index Registers - pub si: u16, // Source Index - pub di: u16, // Dest Index - pub bp: u16, // Base Pointer - pub sp: u16, // Stack Pointer + pub si: Cell, // Source Index + pub di: Cell, // Dest Index + pub bp: Cell, // Base Pointer + pub sp: Cell, // Stack Pointer // Segment Registers - pub cs: u16, // Code Segment - pub ds: u16, // Data Segment - pub es: u16, // Extra Segment - pub ss: u16, // Stack Segment + pub cs: Cell, // Code Segment + pub ds: Cell, // Data Segment + pub es: Cell, // Extra Segment + pub ss: Cell, // Stack Segment // Pointer Register - pub ip: u16, + pub ip: Cell, // Status Register pub flags: Flags, @@ -52,22 +53,22 @@ pub struct Flags { mod ops { use emu::byteorder::{ByteOrder, LittleEndian}; use emu::dos; - use emu::i8088::{Bus, LValue, RValue, i8088, segoff_to_addr}; + use emu::i8088::{Bus, LValue, Reg, RValue, i8088, segoff_to_addr}; pub fn show(cpu: &mut i8088) { println!("{:#X?}", cpu); } pub fn peek(cpu: &mut i8088, bus: &mut Bus, addr: &u16) { - let addr = segoff_to_addr(cpu.ds, *addr); + let addr = segoff_to_addr(cpu.ds.get(), *addr); let val = bus.ram[addr]; println!("PEEK: @{:#X} = {:#X} ({})", addr, val, val); } - pub fn call(ip: &mut u16, ss: u16, sp: &mut u16, mem: &mut [u8], addr: i16) { - let target = ip.wrapping_add(addr as u16); - push16(ss, sp, mem, *ip); - *ip = target; + pub fn call(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &mut [u8], addr: i16) { + let target = ip.read().wrapping_add(addr as u16); + push16(ss, sp, mem, ip.read()); + ip.write(target); } pub fn int(cpu: &mut i8088, bus: &mut Bus, num: &u8) { @@ -81,20 +82,20 @@ mod ops { dst.write(src.read()); } - pub fn pop16(ss: u16, sp: &mut u16, mem: &[u8]) -> u16 { - let val = LittleEndian::read_u16(&mem[segoff_to_addr(ss, *sp)..]); - *sp += 2; + pub fn pop16(ss: u16, sp: &mut Reg, mem: &[u8]) -> u16 { + let val = LittleEndian::read_u16(&mem[segoff_to_addr(ss.read(), sp.read())..]); + sp.write(sp.read() + 2); val } - pub fn push16(ss: u16, sp: &mut u16, mem: &mut [u8], val: u16) { + pub fn push16(ss: u16, sp: &mut Reg, mem: &mut [u8], val: u16) { // XXX: Not checking for stack faults or anything - *sp -= 2; - LittleEndian::write_u16(&mut mem[segoff_to_addr(ss, *sp)..], val); + sp.write(sp.read() - 2); + LittleEndian::write_u16(&mut mem[segoff_to_addr(ss.read(), sp.read())..], val); } - pub fn ret(ip: &mut u16, ss: u16, sp: &mut u16, mem: &[u8]) { - *ip = pop16(ss, sp, mem); + pub fn ret(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &[u8]) { + ip.write(pop16(ss, sp, mem)); } } @@ -124,8 +125,24 @@ impl RValue for u16 { } } +pub struct Reg<'a> { + reg: &'a Cell +} + +impl<'a> LValue for Reg<'a> { + fn write(&mut self, val: u16) { + self.reg.set(val); + } +} + +impl<'a> RValue for Reg<'a> { + fn read(&self) -> u16 { + self.reg.get() + } +} + struct RegHi<'a> { - reg: &'a mut u16 + reg: &'a Cell } impl<'a> LValue for RegHi<'a> { @@ -136,12 +153,12 @@ impl<'a> LValue for RegHi<'a> { impl<'a> RValue for RegHi<'a> { fn read(&self) -> u8 { - read_hi(*self.reg) + read_hi(self.reg) } } struct RegLo<'a> { - reg: &'a mut u16 + reg: &'a Cell } impl<'a> LValue for RegLo<'a> { @@ -152,7 +169,7 @@ impl<'a> LValue for RegLo<'a> { impl<'a> RValue for RegLo<'a> { fn read(&self) -> u8 { - read_lo(*self.reg) + read_lo(self.reg) } } @@ -196,14 +213,14 @@ macro_rules! step { } }; (@d8 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { { - let d8 = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); + let d8 = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, &d8) } }; (@d16 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { { let mut buf = [0; 2]; - buf[0] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); - buf[1] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); + buf[0] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); + buf[1] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, &LittleEndian::read_u16(&buf)) } }; @@ -220,57 +237,56 @@ macro_rules! step { } } }; - (@r16 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { { + (@r16 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { // TODO: Should these also be passed into the macro like the modrm specs? - let reg = match $modrm >> 3 & 0x7 { - 0 => &mut $cpu.a, - 1 => &mut $cpu.c, - 2 => &mut $cpu.d, - 3 => &mut $cpu.b, - 4 => &mut $cpu.sp, - 5 => &mut $cpu.bp, - 6 => &mut $cpu.si, - 7 => &mut $cpu.di, + match $modrm >> 3 & 0x7 { + 0 => step!(@arg $cookie, &mut Reg { reg: &$cpu.a } ), + 1 => step!(@arg $cookie, &mut Reg { reg: &$cpu.c } ), + 2 => step!(@arg $cookie, &mut Reg { reg: &$cpu.d } ), + 3 => step!(@arg $cookie, &mut Reg { reg: &$cpu.b } ), + 4 => step!(@arg $cookie, &mut Reg { reg: &$cpu.sp } ), + 5 => step!(@arg $cookie, &mut Reg { reg: &$cpu.bp } ), + 6 => step!(@arg $cookie, &mut Reg { reg: &$cpu.si } ), + 7 => step!(@arg $cookie, &mut Reg { reg: &$cpu.di } ), _ => unreachable!() }; - step!(@arg $cookie, reg) - } }; + }; (@r8 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { // TODO: Should these also be passed into the macro like the modrm specs? match $modrm >> 3 & 0x7 { - 0 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.a } ), - 1 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.c } ), - 2 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.d } ) , - 3 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.b } ), - 4 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.a } ), - 5 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.c } ), - 6 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.d } ), - 7 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.b } ), + 0 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.a } ), + 1 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.c } ), + 2 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.d } ), + 3 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.b } ), + 4 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.a } ), + 5 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.c } ), + 6 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.d } ), + 7 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.b } ), _ => unreachable!() }; }; (@reg=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { - step!(@arg $cookie, &mut $cpu.$reg); + step!(@arg $cookie, &mut Reg { reg: &$cpu.$reg }); }; (@reghi=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { - step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.$reg }); + step!(@arg $cookie, &mut RegHi { reg: &$cpu.$reg }); }; (@reglo=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { - step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.$reg }); + step!(@arg $cookie, &mut RegLo { reg: &$cpu.$reg }); }; (@regval=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { - step!(@arg $cookie, $cpu.$reg); + step!(@arg $cookie, $cpu.$reg.get()); }; (@rel16 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { { let mut buf = [0; 2]; - buf[0] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); - buf[1] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); + buf[0] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); + buf[1] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, LittleEndian::read_i16(&buf)) } }; @@ -303,17 +319,17 @@ macro_rules! step { modrm8: $modrm8:tt ) => { { - let opcode = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); + let opcode = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); let modrm: u8; // Type ascription unnecessary but gives better err messages when missing $ext match(opcode) { $( $( $code => { $( let $ext = (); /* No-op just to trigger expansion. */ - modrm = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); )? + modrm = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); )? step!(@code (), $cpu, $bus, (modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) } )? $( $code => { - modrm = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); + modrm = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@group $cpu, $bus, (modrm, $modrm16, $modrm8), $code, $subcodes) } )? @@ -329,30 +345,30 @@ pub const fn segoff_to_addr(segment: u16, offset: u16) -> usize { segaddr + offset as usize } -pub fn read_hi(val: u16) -> u8 { +pub fn read_hi(val: &Cell) -> u8 { let mut buf = [0; 2]; - LittleEndian::write_u16(&mut buf, val); + LittleEndian::write_u16(&mut buf, val.get()); buf[1] as u8 } -pub fn read_lo(val: u16) -> u8 { +pub fn read_lo(val: &Cell) -> u8 { let mut buf = [0; 2]; - LittleEndian::write_u16(&mut buf, val); + LittleEndian::write_u16(&mut buf, val.get()); buf[0] as u8 } -pub fn write_hi(reg: &mut u16, val: u8) { +pub fn write_hi(reg: &Cell, val: u8) { let mut buf = [0; 2]; - LittleEndian::write_u16(&mut buf, *reg); + LittleEndian::write_u16(&mut buf, reg.get()); buf[1] = val; - *reg = LittleEndian::read_u16(&buf); + reg.set(LittleEndian::read_u16(&buf)) } -pub fn write_lo(reg: &mut u16, val: u8) { +pub fn write_lo(reg: &Cell, val: u8) { let mut buf = [0; 2]; - LittleEndian::write_u16(&mut buf, *reg); + LittleEndian::write_u16(&mut buf, reg.get()); buf[1] = val; - *reg = LittleEndian::read_u16(&buf); + reg.set(LittleEndian::read_u16(&buf)); } impl i8088 { @@ -362,7 +378,7 @@ impl i8088 { opcodes: { 0x60 => show[cpu] / 0, // Fake opcode for debugging 0x61 => peek[cpu, bus, d16] / 0, // Fake opcode for debugging - //0x8B _ => mov[r16, modrm16] / "2/12+", + 0x8B _ => mov[r16, modrm16] / "2/12+", 0x8C: { 0x08 => mov[modrm16, reg=cs] / "2/13+", }, 0x8E: { 0x18 => mov[reg=ds, modrm16] / "2/12+", }, 0xB4 => mov[reghi=a, d8] / 4, @@ -394,9 +410,9 @@ impl i8088 { } } - fn next_ip<'a>(cs: u16, ip: &mut u16, bus: &Bus) -> u8 { - let eip = segoff_to_addr(cs, *ip); - *ip += 1; + fn next_ip<'a>(cs: u16, ip: &mut Cell, bus: &Bus) -> u8 { + let eip = segoff_to_addr(cs, ip.get()); + ip.set(ip.get() + 1); // We'll assume cpu is always executing in RAM. Also assume the // IP doesn't reach the end of the segment (My guess is that it diff --git a/src/emu/pc.rs b/src/emu/pc.rs index b797797..f526326 100644 --- a/src/emu/pc.rs +++ b/src/emu/pc.rs @@ -1,3 +1,4 @@ +use std::cell::Cell; use emu::i8088::{i8088, segoff_to_addr}; use std::fmt::{Debug, Formatter}; @@ -32,12 +33,12 @@ impl PC { "No memory for loading COM file"); let mut pc = PC { - cpu: { i8088 { cs: LOAD_SEGMENT, // COMs always uses Tiny memory model - ds: LOAD_SEGMENT, // COMs always uses Tiny memory model - es: LOAD_SEGMENT, // COMs always uses Tiny memory model - ss: LOAD_SEGMENT, // COMs always uses Tiny memory model - sp: 0xFFFE, - ip: LOAD_OFFSET, + cpu: { i8088 { cs: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model + ds: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model + es: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model + ss: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model + sp: Cell::new(0xFFFE), + ip: Cell::new(LOAD_OFFSET), ..i8088::default() } }, bus: Bus { ram: [0; RAM_SIZE] } };