emu: SHL/SHR/SAR opcodes (BARELY TESTED!), const Op Directive

Carry Flag as yet untested and likely to contain bugs
This commit is contained in:
2021-03-28 02:08:23 -07:00
parent 874fc6adbc
commit a183a5ad38
4 changed files with 152 additions and 8 deletions

View File

@@ -70,6 +70,7 @@ impl Flags {
FlagOp::Eager => { self.res & 1 << OF_BIT != 0 }, FlagOp::Eager => { self.res & 1 << OF_BIT != 0 },
FlagOp::DEC => { self.res == self.sign_bit_mask - 1 }, FlagOp::DEC => { self.res == self.sign_bit_mask - 1 },
FlagOp::INC => { self.res == self.sign_bit_mask }, FlagOp::INC => { self.res == self.sign_bit_mask },
FlagOp::SHIFT => { 0 != self.sign_bit_mask & (self.dst ^ self.res) },
FlagOp::SUB => { 0 != self.sign_bit_mask & // In the (maybe) sign bit... FlagOp::SUB => { 0 != self.sign_bit_mask & // In the (maybe) sign bit...
(self.src ^ self.dst) & // ...operands have different signs... (self.src ^ self.dst) & // ...operands have different signs...
(self.dst ^ self.res) /* ...and result sign-bit changed */ }, (self.dst ^ self.res) /* ...and result sign-bit changed */ },
@@ -93,6 +94,7 @@ pub enum FlagOp {
Eager, // precomputed into result, for e.g. POPF? (Anything else?) Eager, // precomputed into result, for e.g. POPF? (Anything else?)
DEC, DEC,
INC, INC,
SHIFT,
SUB, SUB,
} }

View File

@@ -85,7 +85,7 @@ macro_rules! step {
step!(@code form=$form, $done, $cpu, $bus, $prefix, $modrm, $name, $cycles, $rest) step!(@code form=$form, $done, $cpu, $bus, $prefix, $modrm, $name, $cycles, $rest)
}; };
// Argument Decoders // === OP DIRECTIVES ===
(@addr $cookie:tt, $cpu:expr, $bus:expr, (@addr $cookie:tt, $cpu:expr, $bus:expr,
($segment:ident, $repeat:ident, $prefix_loop:lifetime), ($segment:ident, $repeat:ident, $prefix_loop:lifetime),
@@ -137,6 +137,10 @@ macro_rules! step {
) )
}; };
(@const=$val:literal $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => {
step!(@arg $cookie, $val)
};
(@cpu $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { (@cpu $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => {
step!(@arg $cookie, $cpu) step!(@arg $cookie, $cpu)
}; };
@@ -186,6 +190,14 @@ macro_rules! step {
step!(@arg $cookie, &mut $cpu.flags) step!(@arg $cookie, &mut $cpu.flags)
}; };
(@form=ternary_byte $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => {
step!(@form $cookie, form=(u8, _, _, _));
};
(@form=ternary_word $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => {
step!(@form $cookie, form=(u16, _, _, _));
};
(@form=unary_byte $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { (@form=unary_byte $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => {
step!(@form $cookie, form=(u8, _)); step!(@form $cookie, form=(u8, _));
}; };
@@ -512,17 +524,29 @@ impl i8088 {
0xBF => mov[reg=di, d16] / 4, 0xBF => mov[reg=di, d16] / 4,
0xC3 => pop[bus, regval=ss, reg=sp, reg=ip] / 20, // RET 0xC3 => pop[bus, regval=ss, reg=sp, reg=ip] / 20, // RET
0xCD => int[cpu, bus, d8] / 71, 0xCD => int[cpu, bus, d8] / 71,
0xD0: { 0x20 => shl[form=ternary_byte, flags, modrm8, const=1u8] / "2/23+", // SHL r/m8, 1
0x28 => shr[form=ternary_byte, flags, modrm8, const=1u8] / "2/23+", // SHR r/m8, 1
0x38 => sar[form=ternary_byte, flags, modrm8, const=1u8] / "2/23+" }, // SAR r/m8, 1
0xD1: { 0x20 => shl[form=ternary_word, flags, modrm16, const=1u16] / "2/23+", // SHL r/m16, 1
0x28 => shr[form=ternary_word, flags, modrm16, const=1u16] / "2/23+", // SHR r/m16, 1
0x38 => sar[form=ternary_word, flags, modrm16, const=1u16] / "2/23+" }, // SAR r/m16, 1
0xD2: { 0x20 => shl[form=ternary_byte, flags, modrm8, reglo=c] / "2/23+", // SHL r/m8, CL
0x28 => shr[form=ternary_byte, flags, modrm8, reglo=c] / "2/23+", // SHR r/m8, CL
0x38 => sar[form=ternary_byte, flags, modrm8, reglo=c] / "2/23+" }, // SAR r/m8, CL
0xD3: { 0x20 => shl[form=ternary_word, flags, modrm16, reglo=c] / "2/23+", // SHL r/m16, CL
0x28 => shr[form=ternary_word, flags, modrm16, reglo=c] / "2/23+", // SHR r/m16, CL
0x38 => sar[form=ternary_word, flags, modrm16, reglo=c] / "2/23+" }, // SAR r/m16, CL
0xE3 => jcxz[reg=ip, reg=c, rel8] / "18/6", // JCXZ rel8 0xE3 => jcxz[reg=ip, reg=c, rel8] / "18/6", // JCXZ rel8
0xE9 => jmp[reg=ip, rel16] / 15, // JMP rel16 0xE9 => jmp[reg=ip, rel16] / 15, // JMP rel16
0xEB => jmp[reg=ip, rel8] / 15, // JMP rel8 0xEB => jmp[reg=ip, rel8] / 15, // JMP rel8
0xE8 => call[reg=ip, bus, regval=ss, reg=sp, rel16] / 23, 0xE8 => call[reg=ip, bus, regval=ss, reg=sp, rel16] / 23,
0xF2 => nop[rep=NotEqual, prefix] / 0, // REPNE/REPNZ 0xF2 => nop[rep=NotEqual, prefix] / 0, // REPNE/REPNZ
0xF3 => nop[rep=Equal, prefix] / 0, // REP/REPE/REPZ 0xF3 => nop[rep=Equal, prefix] / 0, // REP/REPE/REPZ
0xFE: { 00 => inc[form=unary_byte, flags, modrm8] / "3/23+", // INC r/m8 0xFE: { 0x00 => inc[form=unary_byte, flags, modrm8] / "3/23+", // INC r/m8
08 => dec[form=unary_byte, flags, modrm8] / "3/23+", }, // DEC r/m8 0x08 => dec[form=unary_byte, flags, modrm8] / "3/23+", }, // DEC r/m8
0xFF: { 00 => inc[form=unary_word, flags, modrm16] / "3/23+", // INC r/m16 0xFF: { 0x00 => inc[form=unary_word, flags, modrm16] / "3/23+", // INC r/m16
08 => dec[form=unary_word, flags, modrm16] / "3/23+", // DEC r/m16 0x08 => dec[form=unary_word, flags, modrm16] / "3/23+", // DEC r/m16
30 => push_modrm[unsafe_bus_maybe, regval=ss, reg=sp, modrm16] / "15/24+" }, 0x30 => push_modrm[unsafe_bus_maybe, regval=ss, reg=sp, modrm16] / "15/24+" },
}, },
modrm: { modrm: {
0x00 => seg=ds, displace0=(b+si) / M 7, 0x00 => seg=ds, displace0=(b+si) / M 7,

View File

@@ -6,16 +6,26 @@ use emu::num_traits as nt;
use emu::util::{read_hi, read_lo, segoff_to_addr, write_hi, write_lo}; use emu::util::{read_hi, read_lo, segoff_to_addr, write_hi, write_lo};
use emu::pc::Bus; use emu::pc::Bus;
pub trait Operand: Copy + pub trait Operand: Copy + 'static +
std::convert::Into<u16> + std::convert::Into<u16> +
nt::Num + nt::Num +
nt::Bounded +
nt::ops::checked::CheckedShl<Output = Self> +
nt::ops::checked::CheckedShr<Output = Self> +
nt::ops::overflowing::OverflowingAdd + nt::ops::overflowing::OverflowingAdd +
nt::ops::overflowing::OverflowingSub + nt::ops::overflowing::OverflowingSub +
nt::ops::wrapping::WrappingAdd + nt::ops::wrapping::WrappingAdd +
nt::ops::wrapping::WrappingSub + nt::ops::wrapping::WrappingSub +
std::ops::BitAnd<Output = Self> + std::ops::BitAnd<Output = Self> +
std::ops::BitXor<Output = Self> { std::ops::BitXor<Output = Self>
{
type Signed: nt::ops::checked::CheckedShr<Output = Self::Signed>;
const WIDTH: OperandWidth; const WIDTH: OperandWidth;
const HI_BIT_MASK: Self;
fn hi_bit(self) -> bool;
fn as_signed(self) -> Self::Signed;
fn from_signed(src: Self::Signed) -> Self;
} }
pub enum OperandWidth { pub enum OperandWidth {
@@ -24,11 +34,39 @@ pub enum OperandWidth {
} }
impl Operand for u8 { impl Operand for u8 {
type Signed = i8;
const WIDTH: OperandWidth = OperandWidth::Byte; const WIDTH: OperandWidth = OperandWidth::Byte;
const HI_BIT_MASK: u8 = 0x80;
fn hi_bit(self) -> bool {
self >> 7 == 1
}
fn as_signed(self) -> Self::Signed {
self as Self::Signed
}
fn from_signed(src: Self::Signed) -> Self {
src as Self
}
} }
impl Operand for u16 { impl Operand for u16 {
type Signed = i16;
const WIDTH: OperandWidth = OperandWidth::Word; const WIDTH: OperandWidth = OperandWidth::Word;
const HI_BIT_MASK: u16 = 0x8000;
fn hi_bit(self) -> bool {
self >> 15 == 1
}
fn as_signed(self) -> Self::Signed {
self as Self::Signed
}
fn from_signed(src: Self::Signed) -> Self {
src as Self
}
} }
pub trait LValue<T>: RValue<T> { pub trait LValue<T>: RValue<T> {

View File

@@ -1,6 +1,9 @@
use std::convert::Into; use std::convert::Into;
use std::fmt::Debug; use std::fmt::Debug;
use emu::num_traits::AsPrimitive;
use emu::num_traits::CheckedShr;
use emu::dos; use emu::dos;
use emu::flags::{FlagOp, Flags}; use emu::flags::{FlagOp, Flags};
use emu::i8088::{RepPrefix, i8088}; use emu::i8088::{RepPrefix, i8088};
@@ -373,6 +376,37 @@ pub fn push_modrm<'a>(bus: Option<&mut Bus>, ss: u16, sp: Reg, src: impl Into<Dy
} }
} }
pub fn sar<T, U, LVal, RVal>(flags: &mut Flags, mut dst: LVal, src: RVal)
where T: Operand,
U: Operand + AsPrimitive<u32>,
LVal: LValue<T>,
RVal: RValue<U>
{
let dst_before = dst.read();
let src_before = src.read(); // may alias dst
if src_before == U::zero() { return }
// Before the 286, shifts could be up to 255 bits even though the
// operands themselves could only be 16 bits. The 286 and beyond
// masked the shamt down to 5 bits, but does so regardless of target
// operand size so the result is kinda weird. Since nobody seemed to
// care about this change back in the day, it's tempting for us not
// to care either, but we already need to do work to calculate CF
// (the last bit shifted off), and figuring out what that should be
// with a 5-bit shamt mask doesn't actually sound easier.
let sign_bit = dst_before.hi_bit();
let res = match dst_before.as_signed().checked_shr(src_before.as_()) {
Some(shifted) => T::from_signed(shifted),
None => if sign_bit { T::max_value() } else { T::zero() } // Out-of-Range yields all sign bits
};
dst.write(res);
flags.cf = match T::one().checked_shl(src_before.as_() - 1) {
Some(carrymask) => dst_before & carrymask != T::zero(),
None => sign_bit
};
flags.update(FlagOp::SHIFT, dst_before.into(), src_before.into(), res.into(), T::WIDTH);
}
pub fn scasb(flags: &mut Flags, pub fn scasb(flags: &mut Flags,
bus: &mut Bus, bus: &mut Bus,
rep: RepPrefix, rep: RepPrefix,
@@ -401,6 +435,52 @@ pub fn scasw(flags: &mut Flags,
}); });
} }
// See comments on sar()
pub fn shl<T, U, LVal, RVal>(flags: &mut Flags,
mut dst: LVal,
src: RVal)
where T: Operand,
U: Operand + AsPrimitive<u32>,
LVal: LValue<T>,
RVal: RValue<U>
{
let dst_before = dst.read();
let src_before = src.read(); // may alias dst
let res = match dst_before.checked_shl(src_before.as_()) {
Some(shifted) => shifted,
None => T::zero()
};
dst.write(res);
flags.cf = match T::HI_BIT_MASK.checked_shr(src_before.as_() - 1) {
Some(carrymask) => dst_before & carrymask != T::zero(),
None => false
};
flags.update(FlagOp::SHIFT, dst_before.into(), src_before.into(), res.into(), T::WIDTH);
}
// See comments on sar()
pub fn shr<T, U, LVal, RVal>(flags: &mut Flags,
mut dst: LVal,
src: RVal)
where T: Operand,
U: Operand + AsPrimitive<u32>,
LVal: LValue<T>,
RVal: RValue<U>
{
let dst_before = dst.read();
let src_before = src.read(); // may alias dst
let res = match dst_before.checked_shr(src_before.as_()) {
Some(shifted) => shifted,
None => T::zero()
};
dst.write(res);
flags.cf = match T::one().checked_shl(src_before.as_() - 1) {
Some(carrymask) => dst_before & carrymask != T::zero(),
None => false
};
flags.update(FlagOp::SHIFT, dst_before.into(), src_before.into(), res.into(), T::WIDTH);
}
pub fn stosb(flags: &Flags, pub fn stosb(flags: &Flags,
bus: &mut Bus, bus: &mut Bus,
rep: RepPrefix, rep: RepPrefix,