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.
This commit is contained in:
2021-03-10 07:09:59 -08:00
parent 49789c74f9
commit 71bcec1574
3 changed files with 104 additions and 87 deletions

View File

@@ -2,16 +2,16 @@ use emu::pc::Bus;
use emu::i8088::{i8088, read_hi, read_lo, segoff_to_addr}; use emu::i8088::{i8088, read_hi, read_lo, segoff_to_addr};
pub fn interrupt(cpu: &mut i8088, bus: &mut Bus) { pub fn interrupt(cpu: &mut i8088, bus: &mut Bus) {
let svc = read_hi(cpu.a); let svc = read_hi(&cpu.a);
match svc { match svc {
0x09 => print_string(cpu, bus), 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) _ => unimplemented!("dos service: AH={:02X}h\ncpu: {:#X?}", svc, cpu)
} }
} }
fn print_string(cpu: &i8088, bus: &Bus) { 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 != '$') { for byte in bus.ram[addr..].iter().take_while(|byte| **byte as char != '$') {
print!("{}", *byte as char); print!("{}", *byte as char);
} }

View File

@@ -1,3 +1,4 @@
use std::cell::Cell;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use super::byteorder::{ByteOrder, LittleEndian}; use super::byteorder::{ByteOrder, LittleEndian};
@@ -5,28 +6,28 @@ use super::byteorder::{ByteOrder, LittleEndian};
use emu::pc::Bus; use emu::pc::Bus;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct i8088 { pub struct i8088 {
// Data Registers // Data Registers
pub a: u16, pub a: Cell<u16>,
pub b: u16, pub b: Cell<u16>,
pub c: u16, pub c: Cell<u16>,
pub d: u16, pub d: Cell<u16>,
// Index Registers // Index Registers
pub si: u16, // Source Index pub si: Cell<u16>, // Source Index
pub di: u16, // Dest Index pub di: Cell<u16>, // Dest Index
pub bp: u16, // Base Pointer pub bp: Cell<u16>, // Base Pointer
pub sp: u16, // Stack Pointer pub sp: Cell<u16>, // Stack Pointer
// Segment Registers // Segment Registers
pub cs: u16, // Code Segment pub cs: Cell<u16>, // Code Segment
pub ds: u16, // Data Segment pub ds: Cell<u16>, // Data Segment
pub es: u16, // Extra Segment pub es: Cell<u16>, // Extra Segment
pub ss: u16, // Stack Segment pub ss: Cell<u16>, // Stack Segment
// Pointer Register // Pointer Register
pub ip: u16, pub ip: Cell<u16>,
// Status Register // Status Register
pub flags: Flags, pub flags: Flags,
@@ -52,22 +53,22 @@ pub struct Flags {
mod ops { mod ops {
use emu::byteorder::{ByteOrder, LittleEndian}; use emu::byteorder::{ByteOrder, LittleEndian};
use emu::dos; 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) { pub fn show(cpu: &mut i8088) {
println!("{:#X?}", cpu); println!("{:#X?}", cpu);
} }
pub fn peek(cpu: &mut i8088, bus: &mut Bus, addr: &u16) { 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]; let val = bus.ram[addr];
println!("PEEK: @{:#X} = {:#X} ({})", addr, val, val); println!("PEEK: @{:#X} = {:#X} ({})", addr, val, val);
} }
pub fn call(ip: &mut u16, ss: u16, sp: &mut u16, mem: &mut [u8], addr: i16) { pub fn call(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &mut [u8], addr: i16) {
let target = ip.wrapping_add(addr as u16); let target = ip.read().wrapping_add(addr as u16);
push16(ss, sp, mem, *ip); push16(ss, sp, mem, ip.read());
*ip = target; ip.write(target);
} }
pub fn int(cpu: &mut i8088, bus: &mut Bus, num: &u8) { pub fn int(cpu: &mut i8088, bus: &mut Bus, num: &u8) {
@@ -81,20 +82,20 @@ mod ops {
dst.write(src.read()); dst.write(src.read());
} }
pub fn pop16(ss: u16, sp: &mut u16, mem: &[u8]) -> u16 { pub fn pop16(ss: u16, sp: &mut Reg, mem: &[u8]) -> u16 {
let val = LittleEndian::read_u16(&mem[segoff_to_addr(ss, *sp)..]); let val = LittleEndian::read_u16(&mem[segoff_to_addr(ss.read(), sp.read())..]);
*sp += 2; sp.write(sp.read() + 2);
val 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 // XXX: Not checking for stack faults or anything
*sp -= 2; sp.write(sp.read() - 2);
LittleEndian::write_u16(&mut mem[segoff_to_addr(ss, *sp)..], val); 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]) { pub fn ret(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &[u8]) {
*ip = pop16(ss, sp, mem); ip.write(pop16(ss, sp, mem));
} }
} }
@@ -124,8 +125,24 @@ impl RValue<u16> for u16 {
} }
} }
pub struct Reg<'a> {
reg: &'a Cell<u16>
}
impl<'a> LValue<u16> for Reg<'a> {
fn write(&mut self, val: u16) {
self.reg.set(val);
}
}
impl<'a> RValue<u16> for Reg<'a> {
fn read(&self) -> u16 {
self.reg.get()
}
}
struct RegHi<'a> { struct RegHi<'a> {
reg: &'a mut u16 reg: &'a Cell<u16>
} }
impl<'a> LValue<u8> for RegHi<'a> { impl<'a> LValue<u8> for RegHi<'a> {
@@ -136,12 +153,12 @@ impl<'a> LValue<u8> for RegHi<'a> {
impl<'a> RValue<u8> for RegHi<'a> { impl<'a> RValue<u8> for RegHi<'a> {
fn read(&self) -> u8 { fn read(&self) -> u8 {
read_hi(*self.reg) read_hi(self.reg)
} }
} }
struct RegLo<'a> { struct RegLo<'a> {
reg: &'a mut u16 reg: &'a Cell<u16>
} }
impl<'a> LValue<u8> for RegLo<'a> { impl<'a> LValue<u8> for RegLo<'a> {
@@ -152,7 +169,7 @@ impl<'a> LValue<u8> for RegLo<'a> {
impl<'a> RValue<u8> for RegLo<'a> { impl<'a> RValue<u8> for RegLo<'a> {
fn read(&self) -> u8 { 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) => { { (@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) step!(@arg $cookie, &d8)
} }; } };
(@d16 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { { (@d16 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { {
let mut buf = [0; 2]; let mut buf = [0; 2];
buf[0] = 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, &mut $cpu.ip, $bus); buf[1] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus);
step!(@arg $cookie, &LittleEndian::read_u16(&buf)) 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? // TODO: Should these also be passed into the macro like the modrm specs?
let reg = match $modrm >> 3 & 0x7 { match $modrm >> 3 & 0x7 {
0 => &mut $cpu.a, 0 => step!(@arg $cookie, &mut Reg { reg: &$cpu.a } ),
1 => &mut $cpu.c, 1 => step!(@arg $cookie, &mut Reg { reg: &$cpu.c } ),
2 => &mut $cpu.d, 2 => step!(@arg $cookie, &mut Reg { reg: &$cpu.d } ),
3 => &mut $cpu.b, 3 => step!(@arg $cookie, &mut Reg { reg: &$cpu.b } ),
4 => &mut $cpu.sp, 4 => step!(@arg $cookie, &mut Reg { reg: &$cpu.sp } ),
5 => &mut $cpu.bp, 5 => step!(@arg $cookie, &mut Reg { reg: &$cpu.bp } ),
6 => &mut $cpu.si, 6 => step!(@arg $cookie, &mut Reg { reg: &$cpu.si } ),
7 => &mut $cpu.di, 7 => step!(@arg $cookie, &mut Reg { reg: &$cpu.di } ),
_ => unreachable!() _ => unreachable!()
}; };
step!(@arg $cookie, reg) };
} };
(@r8 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { (@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? // TODO: Should these also be passed into the macro like the modrm specs?
match $modrm >> 3 & 0x7 { match $modrm >> 3 & 0x7 {
0 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.a } ), 0 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.a } ),
1 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.c } ), 1 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.c } ),
2 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.d } ) , 2 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.d } ),
3 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.b } ), 3 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.b } ),
4 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.a } ), 4 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.a } ),
5 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.c } ), 5 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.c } ),
6 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.d } ), 6 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.d } ),
7 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.b } ), 7 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.b } ),
_ => unreachable!() _ => unreachable!()
}; };
}; };
(@reg=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { (@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) => { (@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) => { (@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) => { (@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) => { { (@rel16 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { {
let mut buf = [0; 2]; let mut buf = [0; 2];
buf[0] = 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, &mut $cpu.ip, $bus); buf[1] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus);
step!(@arg $cookie, LittleEndian::read_i16(&buf)) step!(@arg $cookie, LittleEndian::read_i16(&buf))
} }; } };
@@ -303,17 +319,17 @@ macro_rules! step {
modrm8: $modrm8:tt 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 let modrm: u8; // Type ascription unnecessary but gives better err messages when missing $ext
match(opcode) { match(opcode) {
$( $( $code => { $( $( $code => {
$( let $ext = (); /* No-op just to trigger expansion. */ $( 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)?)*)) step!(@code (), $cpu, $bus, (modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*))
} }
)? )?
$( $code => { $( $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) 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 segaddr + offset as usize
} }
pub fn read_hi(val: u16) -> u8 { pub fn read_hi(val: &Cell<u16>) -> u8 {
let mut buf = [0; 2]; let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, val); LittleEndian::write_u16(&mut buf, val.get());
buf[1] as u8 buf[1] as u8
} }
pub fn read_lo(val: u16) -> u8 { pub fn read_lo(val: &Cell<u16>) -> u8 {
let mut buf = [0; 2]; let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, val); LittleEndian::write_u16(&mut buf, val.get());
buf[0] as u8 buf[0] as u8
} }
pub fn write_hi(reg: &mut u16, val: u8) { pub fn write_hi(reg: &Cell<u16>, val: u8) {
let mut buf = [0; 2]; let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, *reg); LittleEndian::write_u16(&mut buf, reg.get());
buf[1] = val; 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<u16>, val: u8) {
let mut buf = [0; 2]; let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, *reg); LittleEndian::write_u16(&mut buf, reg.get());
buf[1] = val; buf[1] = val;
*reg = LittleEndian::read_u16(&buf); reg.set(LittleEndian::read_u16(&buf));
} }
impl i8088 { impl i8088 {
@@ -362,7 +378,7 @@ impl i8088 {
opcodes: { opcodes: {
0x60 => show[cpu] / 0, // Fake opcode for debugging 0x60 => show[cpu] / 0, // Fake opcode for debugging
0x61 => peek[cpu, bus, d16] / 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+", }, 0x8C: { 0x08 => mov[modrm16, reg=cs] / "2/13+", },
0x8E: { 0x18 => mov[reg=ds, modrm16] / "2/12+", }, 0x8E: { 0x18 => mov[reg=ds, modrm16] / "2/12+", },
0xB4 => mov[reghi=a, d8] / 4, 0xB4 => mov[reghi=a, d8] / 4,
@@ -394,9 +410,9 @@ impl i8088 {
} }
} }
fn next_ip<'a>(cs: u16, ip: &mut u16, bus: &Bus) -> u8 { fn next_ip<'a>(cs: u16, ip: &mut Cell<u16>, bus: &Bus) -> u8 {
let eip = segoff_to_addr(cs, *ip); let eip = segoff_to_addr(cs, ip.get());
*ip += 1; ip.set(ip.get() + 1);
// We'll assume cpu is always executing in RAM. Also assume the // 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 // IP doesn't reach the end of the segment (My guess is that it

View File

@@ -1,3 +1,4 @@
use std::cell::Cell;
use emu::i8088::{i8088, segoff_to_addr}; use emu::i8088::{i8088, segoff_to_addr};
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
@@ -32,12 +33,12 @@ impl PC {
"No memory for loading COM file"); "No memory for loading COM file");
let mut pc = PC { let mut pc = PC {
cpu: { i8088 { cs: LOAD_SEGMENT, // COMs always uses Tiny memory model cpu: { i8088 { cs: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
ds: LOAD_SEGMENT, // COMs always uses Tiny memory model ds: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
es: LOAD_SEGMENT, // COMs always uses Tiny memory model es: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
ss: LOAD_SEGMENT, // COMs always uses Tiny memory model ss: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
sp: 0xFFFE, sp: Cell::new(0xFFFE),
ip: LOAD_OFFSET, ip: Cell::new(LOAD_OFFSET),
..i8088::default() } }, ..i8088::default() } },
bus: Bus { ram: [0; RAM_SIZE] } bus: Bus { ram: [0; RAM_SIZE] }
}; };