Restructure into a library (#7)
This commit is contained in:
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -222,15 +222,6 @@ version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "performance_aware_programming"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"const_panic",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
@@ -263,6 +254,14 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sim_8086"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"const_panic",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -271,9 +270,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.13"
|
||||
version = "2.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
|
||||
checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -423,3 +422,11 @@ name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "wrapper"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"sim_8086",
|
||||
]
|
||||
|
18
Cargo.toml
18
Cargo.toml
@@ -1,16 +1,10 @@
|
||||
[package]
|
||||
name = "performance_aware_programming"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.2.1", features = [ "derive" ] }
|
||||
const_panic = "0.2.7"
|
||||
nom = "7.1.3"
|
||||
[workspace]
|
||||
members = [
|
||||
"sim_8086",
|
||||
"wrapper"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
panic = 'abort'
|
||||
panic = 'abort'
|
@@ -23,7 +23,7 @@
|
||||
crate2nix,
|
||||
...
|
||||
}: let
|
||||
name = "performance_aware_programming";
|
||||
name = "wrapper";
|
||||
in
|
||||
utils.lib.eachDefaultSystem
|
||||
(
|
||||
|
12
sim_8086/Cargo.toml
Normal file
12
sim_8086/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "sim_8086"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
const_panic = "0.2.7"
|
||||
nom = "7.1.3"
|
171
sim_8086/src/arithmetic_instruction.rs
Normal file
171
sim_8086/src/arithmetic_instruction.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use const_panic::concat_panic;
|
||||
|
||||
use crate::{effective_address::EffectiveAddress, register::Register};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum ArithmeticInstructionSelect {
|
||||
RegisterToRegister(RegRegArithmetic),
|
||||
RegisterToMemory(RegMemArithmetic),
|
||||
MemoryToRegister(MemRegArithmetic),
|
||||
ImmediateToRegisterByte(Register, u8, bool),
|
||||
ImmediateToRegisterWord(Register, u16, bool),
|
||||
/// The bool here is "is this actually a u16"
|
||||
ImmediateToRegisterOrMemoryByte(EffectiveAddress, u8, bool),
|
||||
ImmediateToRegisterOrMemoryWord(EffectiveAddress, u16),
|
||||
ImmediateToAccByte(u8),
|
||||
ImmediateToAccWord(u16),
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct ArithmeticInstruction {
|
||||
pub op: ArithmeticOperation,
|
||||
pub instruction: ArithmeticInstructionSelect,
|
||||
}
|
||||
|
||||
impl ArithmeticInstruction {
|
||||
/// d is expected to be either 0 or 1.
|
||||
fn to_byte(s: ArithmeticOperation, d: u8, is_wide: bool) -> u8 {
|
||||
// Implicit opcode of 0b000 at the start.
|
||||
(s as u8) * 8 + d * 2 + if is_wide { 1 } else { 0 }
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::<u8>::with_capacity(2);
|
||||
match &self.instruction {
|
||||
ArithmeticInstructionSelect::RegisterToRegister(data) => {
|
||||
let (source, is_wide_s) = data.source.to_id();
|
||||
let (dest, is_wide_d) = data.dest.to_id();
|
||||
if is_wide_s != is_wide_d {
|
||||
panic!("Somehow tried to do arithmetic between mismatched sizes")
|
||||
}
|
||||
let d = 0;
|
||||
result.push(Self::to_byte(self.op, d, is_wide_s));
|
||||
|
||||
let mode = 0b11000000u8;
|
||||
result.push(mode + source * 8 + dest);
|
||||
}
|
||||
ArithmeticInstructionSelect::RegisterToMemory(instruction) => {
|
||||
let (source, is_wide) = instruction.source.to_id();
|
||||
let d = 0; // REG = source
|
||||
result.push(Self::to_byte(self.op, d, is_wide));
|
||||
instruction.dest.push(source, &mut result);
|
||||
}
|
||||
ArithmeticInstructionSelect::MemoryToRegister(instruction) => {
|
||||
let (dest, is_wide) = instruction.dest.to_id();
|
||||
let d = 1; // REG = dest
|
||||
result.push(Self::to_byte(self.op, d, is_wide));
|
||||
instruction.source.push(dest, &mut result);
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryByte(dest, data, signed) => {
|
||||
let sign_bit = if *signed { 1 } else { 0 };
|
||||
let w = sign_bit;
|
||||
result.push(0b10000000u8 + 2 * sign_bit + w);
|
||||
dest.push(self.op as u8, &mut result);
|
||||
result.push(*data);
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryWord(dest, data) => {
|
||||
let sign_bit = 0u8;
|
||||
let w = 1u8;
|
||||
result.push(0b10000000u8 + 2 * sign_bit + w);
|
||||
dest.push(self.op as u8, &mut result);
|
||||
result.push((data % 256) as u8);
|
||||
result.push((data / 256) as u8)
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterByte(reg, data, signed) => {
|
||||
let sign_bit = if *signed { 1 } else { 0 };
|
||||
let (rm, is_wide) = Register::to_id(reg);
|
||||
result.push(0b10000000u8 + 2 * sign_bit + if is_wide { 1 } else { 0 });
|
||||
result.push(0b11000000 + (self.op as u8) * 8 + rm);
|
||||
result.push(*data);
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterWord(reg, data, signed) => {
|
||||
let sign_bit = if *signed { 1 } else { 0 };
|
||||
let (rm, is_wide) = Register::to_id(reg);
|
||||
result.push(0b10000000u8 + 2 * sign_bit + if is_wide { 1 } else { 0 });
|
||||
result.push(0b11000000 + (self.op as u8) * 8 + rm);
|
||||
result.push((data % 256) as u8);
|
||||
if !*signed {
|
||||
result.push((data / 256) as u8);
|
||||
}
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToAccByte(data) => {
|
||||
let instruction = 0b00000100 + (self.op as u8) * 8;
|
||||
let w = 0u8;
|
||||
result.push(instruction + w);
|
||||
result.push(*data);
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToAccWord(data) => {
|
||||
let instruction = 0b00000100 + (self.op as u8) * 8;
|
||||
let w = 1u8;
|
||||
result.push(instruction + w);
|
||||
result.push((data % 256) as u8);
|
||||
result.push((data / 256) as u8);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
|
||||
pub enum ArithmeticOperation {
|
||||
Add = 0,
|
||||
Or = 1,
|
||||
AddWithCarry = 2,
|
||||
SubWithBorrow = 3,
|
||||
And = 4,
|
||||
Sub = 5,
|
||||
Xor = 6,
|
||||
Cmp = 7,
|
||||
}
|
||||
|
||||
impl Display for ArithmeticOperation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
ArithmeticOperation::Add => "add",
|
||||
ArithmeticOperation::Or => "or",
|
||||
ArithmeticOperation::AddWithCarry => "adc",
|
||||
ArithmeticOperation::SubWithBorrow => "sbb",
|
||||
ArithmeticOperation::And => "and",
|
||||
ArithmeticOperation::Sub => "sub",
|
||||
ArithmeticOperation::Xor => "xor",
|
||||
ArithmeticOperation::Cmp => "cmp",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ArithmeticOperation {
|
||||
pub const fn of_byte(x: u8) -> ArithmeticOperation {
|
||||
match x {
|
||||
0 => ArithmeticOperation::Add,
|
||||
1 => ArithmeticOperation::Or,
|
||||
2 => ArithmeticOperation::AddWithCarry,
|
||||
3 => ArithmeticOperation::SubWithBorrow,
|
||||
4 => ArithmeticOperation::And,
|
||||
5 => ArithmeticOperation::Sub,
|
||||
6 => ArithmeticOperation::Xor,
|
||||
7 => ArithmeticOperation::Cmp,
|
||||
_ => concat_panic!("Unrecognised arithmetic op: {}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct RegRegArithmetic {
|
||||
pub source: Register,
|
||||
pub dest: Register,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct RegMemArithmetic {
|
||||
pub source: Register,
|
||||
pub dest: EffectiveAddress,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct MemRegArithmetic {
|
||||
pub dest: Register,
|
||||
pub source: EffectiveAddress,
|
||||
}
|
@@ -11,11 +11,23 @@ use nom::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
register::{ByteRegisterSubset, GeneralRegister, Register, RegisterSubset, SpecialRegister},
|
||||
AccumulatorToMemory, ArithmeticInstruction, ArithmeticInstructionSelect, ArithmeticOperation,
|
||||
Base, EffectiveAddress, ImmediateToRegister, ImmediateToRegisterOrMemory, Instruction, Jump,
|
||||
MemRegMove, MemoryToAccumulator, Program, RegMemMove, RegRegMove, SourceDest,
|
||||
TriviaInstruction, WithOffset,
|
||||
arithmetic_instruction::{
|
||||
ArithmeticInstruction, ArithmeticInstructionSelect, ArithmeticOperation, MemRegArithmetic,
|
||||
RegMemArithmetic, RegRegArithmetic,
|
||||
},
|
||||
effective_address::{EffectiveAddress, WithOffset},
|
||||
instruction::Instruction,
|
||||
jump_instruction::Jump,
|
||||
move_instruction::{
|
||||
AccumulatorToMemory, ImmediateToRegister, ImmediateToRegisterOrMemory, MemRegMove,
|
||||
MemoryToAccumulator, MoveInstruction, RegMemMove, RegRegMove,
|
||||
},
|
||||
program::Program,
|
||||
register::{
|
||||
Base, ByteRegisterSubset, GeneralRegister, Register, RegisterSubset, SourceDest,
|
||||
SpecialRegister,
|
||||
},
|
||||
trivia_instruction::TriviaInstruction,
|
||||
};
|
||||
|
||||
fn comment(input: &str) -> IResult<&str, &str> {
|
||||
@@ -504,7 +516,7 @@ fn arithmetic_select(input: &str) -> IResult<&str, ArithmeticInstructionSelect>
|
||||
tuple((terminated(register, argument_sep), register)),
|
||||
|(dest, source)| {
|
||||
Ok::<_, ()>(ArithmeticInstructionSelect::RegisterToRegister(
|
||||
crate::RegRegArithmetic { source, dest },
|
||||
RegRegArithmetic { source, dest },
|
||||
))
|
||||
},
|
||||
),
|
||||
@@ -512,7 +524,7 @@ fn arithmetic_select(input: &str) -> IResult<&str, ArithmeticInstructionSelect>
|
||||
tuple((terminated(register, argument_sep), effective_address)),
|
||||
|(dest, source)| {
|
||||
Ok::<_, ()>(ArithmeticInstructionSelect::MemoryToRegister(
|
||||
crate::MemRegArithmetic { source, dest },
|
||||
MemRegArithmetic { source, dest },
|
||||
))
|
||||
},
|
||||
),
|
||||
@@ -520,7 +532,7 @@ fn arithmetic_select(input: &str) -> IResult<&str, ArithmeticInstructionSelect>
|
||||
tuple((terminated(effective_address, argument_sep), register)),
|
||||
|(dest, source)| {
|
||||
Ok::<_, ()>(ArithmeticInstructionSelect::RegisterToMemory(
|
||||
crate::RegMemArithmetic { source, dest },
|
||||
RegMemArithmetic { source, dest },
|
||||
))
|
||||
},
|
||||
),
|
||||
@@ -693,31 +705,37 @@ fn jump(input: &str) -> IResult<&str, (Jump, &str)> {
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn instruction(input: &str) -> IResult<&str, Instruction<&str>> {
|
||||
fn move_instruction(input: &str) -> IResult<&str, MoveInstruction> {
|
||||
alt((
|
||||
// This must come before MemRegMove.
|
||||
map_res(memory_to_accumulator_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::MemoryToAccumulator(v))
|
||||
Ok::<_, ()>(MoveInstruction::MemoryToAccumulator(v))
|
||||
}),
|
||||
// This must come before RegMemMove.
|
||||
map_res(accumulator_to_memory_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::AccumulatorToMemory(v))
|
||||
Ok::<_, ()>(MoveInstruction::AccumulatorToMemory(v))
|
||||
}),
|
||||
map_res(reg_reg_move_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::RegRegMove(v))
|
||||
Ok::<_, ()>(MoveInstruction::RegRegMove(v))
|
||||
}),
|
||||
map_res(reg_mem_move_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::RegMemMove(v))
|
||||
Ok::<_, ()>(MoveInstruction::RegMemMove(v))
|
||||
}),
|
||||
map_res(mem_reg_move_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::MemRegMove(v))
|
||||
Ok::<_, ()>(MoveInstruction::MemRegMove(v))
|
||||
}),
|
||||
map_res(immediate_to_register_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::ImmediateToRegister(v))
|
||||
Ok::<_, ()>(MoveInstruction::ImmediateToRegister(v))
|
||||
}),
|
||||
map_res(immediate_to_memory_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::ImmediateToRegisterOrMemory(v))
|
||||
Ok::<_, ()>(MoveInstruction::ImmediateToRegisterOrMemory(v))
|
||||
}),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn instruction(input: &str) -> IResult<&str, Instruction<&str>> {
|
||||
alt((
|
||||
map_res(move_instruction, |v| Ok::<_, ()>(Instruction::Move(v))),
|
||||
map_res(arithmetic_instruction, |v| {
|
||||
Ok::<_, ()>(Instruction::Arithmetic(v))
|
||||
}),
|
255
sim_8086/src/effective_address.rs
Normal file
255
sim_8086/src/effective_address.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::register::{Base, SourceDest};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum WithOffset<T> {
|
||||
Basic(T),
|
||||
WithU8(T, u8),
|
||||
WithU16(T, u16),
|
||||
}
|
||||
|
||||
impl<T> Display for WithOffset<T>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WithOffset::Basic(t) => f.write_fmt(format_args!("{}", t)),
|
||||
WithOffset::WithU8(t, offset) => f.write_fmt(format_args!("[{} + {}]", t, offset)),
|
||||
WithOffset::WithU16(t, offset) => f.write_fmt(format_args!("[{} + {}]", t, offset)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum EffectiveAddress {
|
||||
Sum(WithOffset<(Base, SourceDest)>),
|
||||
SpecifiedIn(WithOffset<SourceDest>),
|
||||
Bx(WithOffset<()>),
|
||||
Direct(u16),
|
||||
BasePointer(u8),
|
||||
BasePointerWide(u16),
|
||||
}
|
||||
|
||||
impl Display for EffectiveAddress {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EffectiveAddress::Sum(w) => match w {
|
||||
WithOffset::Basic((base, source_dest)) => {
|
||||
f.write_fmt(format_args!("[{} + {}i]", base, source_dest))
|
||||
}
|
||||
WithOffset::WithU8((base, source_dest), offset) => {
|
||||
f.write_fmt(format_args!("[{} + {}i + {}]", base, source_dest, offset))
|
||||
}
|
||||
WithOffset::WithU16((base, source_dest), offset) => {
|
||||
f.write_fmt(format_args!("[{} + {}i + {}]", base, source_dest, offset))
|
||||
}
|
||||
},
|
||||
EffectiveAddress::SpecifiedIn(WithOffset::Basic(source_dest)) => {
|
||||
f.write_fmt(format_args!("[{}i]", source_dest))
|
||||
}
|
||||
EffectiveAddress::SpecifiedIn(WithOffset::WithU8(source_dest, offset)) => {
|
||||
f.write_fmt(format_args!("[{}i + {}]", source_dest, offset))
|
||||
}
|
||||
EffectiveAddress::SpecifiedIn(WithOffset::WithU16(source_dest, offset)) => {
|
||||
f.write_fmt(format_args!("[{}i + {}]", source_dest, offset))
|
||||
}
|
||||
EffectiveAddress::Bx(offset) => match offset {
|
||||
WithOffset::Basic(()) => f.write_str("bx"),
|
||||
WithOffset::WithU8((), offset) => f.write_fmt(format_args!("[bx + {}]", offset)),
|
||||
WithOffset::WithU16((), offset) => f.write_fmt(format_args!("[bx + {}]", offset)),
|
||||
},
|
||||
EffectiveAddress::Direct(location) => f.write_fmt(format_args!("[{}]", location)),
|
||||
EffectiveAddress::BasePointer(offset) => f.write_fmt(format_args!("[bp + {}]", offset)),
|
||||
EffectiveAddress::BasePointerWide(offset) => {
|
||||
f.write_fmt(format_args!("[bp + {}]", offset))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectiveAddress {
|
||||
pub(crate) fn of_mode_rm<I>(mode: u8, rm: u8, bytes: &mut I) -> EffectiveAddress
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
let source_dest = if rm % 2 == 0 {
|
||||
SourceDest::Source
|
||||
} else {
|
||||
SourceDest::Dest
|
||||
};
|
||||
let base = if (rm / 2) % 2 == 0 {
|
||||
Base::Bx
|
||||
} else {
|
||||
Base::Bp
|
||||
};
|
||||
let displacement_low = if rm == 6 || mode > 0 {
|
||||
bytes.next().expect("required an 8-bit displacement")
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let displacement_high = if (rm == 6 && mode == 0) || mode == 2 {
|
||||
let high = bytes.next().expect("required a 16-bit displacement");
|
||||
(high as u16) * 256 + (displacement_low as u16)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if rm < 4 {
|
||||
match mode {
|
||||
0 => EffectiveAddress::Sum(WithOffset::Basic((base, source_dest))),
|
||||
1 => {
|
||||
EffectiveAddress::Sum(WithOffset::WithU8((base, source_dest), displacement_low))
|
||||
}
|
||||
2 => EffectiveAddress::Sum(WithOffset::WithU16(
|
||||
(base, source_dest),
|
||||
displacement_high,
|
||||
)),
|
||||
_ => panic!("Got bad mode: {}", mode),
|
||||
}
|
||||
} else if rm < 6 {
|
||||
match mode {
|
||||
0 => EffectiveAddress::SpecifiedIn(WithOffset::Basic(source_dest)),
|
||||
1 => {
|
||||
EffectiveAddress::SpecifiedIn(WithOffset::WithU8(source_dest, displacement_low))
|
||||
}
|
||||
2 => EffectiveAddress::SpecifiedIn(WithOffset::WithU16(
|
||||
source_dest,
|
||||
displacement_high,
|
||||
)),
|
||||
_ => panic!("Got bad mode: {}", mode),
|
||||
}
|
||||
} else if rm == 6 {
|
||||
match mode {
|
||||
0 => EffectiveAddress::Direct(displacement_high),
|
||||
1 => EffectiveAddress::BasePointer(displacement_low),
|
||||
2 => EffectiveAddress::BasePointerWide(displacement_high),
|
||||
_ => panic!("Got bad mode: {}", mode),
|
||||
}
|
||||
} else {
|
||||
assert_eq!(rm, 7);
|
||||
match mode {
|
||||
0 => EffectiveAddress::Bx(WithOffset::Basic(())),
|
||||
1 => EffectiveAddress::Bx(WithOffset::WithU8((), displacement_low)),
|
||||
2 => EffectiveAddress::Bx(WithOffset::WithU16((), displacement_high)),
|
||||
_ => panic!("Got bad mode: {}", mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&self, reg: u8, result: &mut Vec<u8>) {
|
||||
match self {
|
||||
EffectiveAddress::Sum(WithOffset::Basic((base, source_dest))) => {
|
||||
let mode = 0u8;
|
||||
let rm = match base {
|
||||
Base::Bx => 0u8,
|
||||
Base::Bp => 2,
|
||||
} + match source_dest {
|
||||
SourceDest::Source => 0,
|
||||
SourceDest::Dest => 1,
|
||||
};
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
}
|
||||
EffectiveAddress::Sum(WithOffset::WithU8((base, source_dest), offset)) => {
|
||||
result.reserve_exact(1);
|
||||
let mode = 1u8;
|
||||
let rm = match base {
|
||||
Base::Bx => 0u8,
|
||||
Base::Bp => 2,
|
||||
} + match source_dest {
|
||||
SourceDest::Source => 0,
|
||||
SourceDest::Dest => 1,
|
||||
};
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push(*offset);
|
||||
}
|
||||
EffectiveAddress::Sum(WithOffset::WithU16((base, source_dest), offset)) => {
|
||||
result.reserve_exact(2);
|
||||
let mode = 2u8;
|
||||
let rm = match base {
|
||||
Base::Bx => 0u8,
|
||||
Base::Bp => 2,
|
||||
} + match source_dest {
|
||||
SourceDest::Source => 0,
|
||||
SourceDest::Dest => 1,
|
||||
};
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push((offset % 256) as u8);
|
||||
result.push((offset / 256) as u8);
|
||||
}
|
||||
EffectiveAddress::SpecifiedIn(WithOffset::Basic(source_dest)) => {
|
||||
let mode = 0u8;
|
||||
let rm = match source_dest {
|
||||
SourceDest::Source => 4u8,
|
||||
SourceDest::Dest => 5,
|
||||
};
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
}
|
||||
EffectiveAddress::SpecifiedIn(WithOffset::WithU8(source_dest, offset)) => {
|
||||
result.reserve_exact(1);
|
||||
let mode = 1u8;
|
||||
let rm = match source_dest {
|
||||
SourceDest::Source => 4u8,
|
||||
SourceDest::Dest => 5,
|
||||
};
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push(*offset);
|
||||
}
|
||||
EffectiveAddress::SpecifiedIn(WithOffset::WithU16(source_dest, offset)) => {
|
||||
result.reserve_exact(2);
|
||||
let mode = 2u8;
|
||||
let rm = match source_dest {
|
||||
SourceDest::Source => 4u8,
|
||||
SourceDest::Dest => 5,
|
||||
};
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push((offset % 256) as u8);
|
||||
result.push((offset / 256) as u8);
|
||||
}
|
||||
EffectiveAddress::Bx(WithOffset::Basic(())) => {
|
||||
let mode = 0u8;
|
||||
let rm = 7u8;
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
}
|
||||
EffectiveAddress::Bx(WithOffset::WithU8((), offset)) => {
|
||||
result.reserve_exact(1);
|
||||
let mode = 1u8;
|
||||
let rm = 7u8;
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push(*offset);
|
||||
}
|
||||
EffectiveAddress::Bx(WithOffset::WithU16((), offset)) => {
|
||||
result.reserve_exact(2);
|
||||
let mode = 2u8;
|
||||
let rm = 7u8;
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push((offset % 256) as u8);
|
||||
result.push((offset / 256) as u8);
|
||||
}
|
||||
EffectiveAddress::Direct(address) => {
|
||||
result.reserve_exact(2);
|
||||
let mode = 0u8;
|
||||
let rm = 6u8;
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push((address % 256) as u8);
|
||||
result.push((address / 256) as u8);
|
||||
}
|
||||
EffectiveAddress::BasePointer(offset) => {
|
||||
result.reserve_exact(1);
|
||||
let mode = 1u8;
|
||||
let rm = 6u8;
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push(*offset);
|
||||
}
|
||||
EffectiveAddress::BasePointerWide(offset) => {
|
||||
result.reserve_exact(2);
|
||||
let mode = 2u8;
|
||||
let rm = 6u8;
|
||||
result.push(mode * 64 + reg * 8 + rm);
|
||||
result.push((offset % 256) as u8);
|
||||
result.push((offset / 256) as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
427
sim_8086/src/instruction.rs
Normal file
427
sim_8086/src/instruction.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
arithmetic_instruction::{
|
||||
ArithmeticInstruction, ArithmeticInstructionSelect, ArithmeticOperation, MemRegArithmetic,
|
||||
RegMemArithmetic, RegRegArithmetic,
|
||||
},
|
||||
effective_address::EffectiveAddress,
|
||||
jump_instruction::Jump,
|
||||
move_instruction::{
|
||||
AccumulatorToMemory, ImmediateToRegister, ImmediateToRegisterOrMemory, MemRegMove,
|
||||
MemoryToAccumulator, MoveInstruction, RegMemMove, RegRegMove,
|
||||
},
|
||||
register::Register,
|
||||
trivia_instruction::TriviaInstruction,
|
||||
};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum Instruction<InstructionOffset> {
|
||||
Move(MoveInstruction),
|
||||
/// Perform arithmetic
|
||||
Arithmetic(ArithmeticInstruction),
|
||||
Jump(Jump, InstructionOffset),
|
||||
/// An irrelevant instruction.
|
||||
Trivia(TriviaInstruction<InstructionOffset>),
|
||||
}
|
||||
|
||||
impl<InstructionOffset> Display for Instruction<InstructionOffset>
|
||||
where
|
||||
InstructionOffset: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Instruction::Move(instruction) => f.write_fmt(format_args!("{}", instruction)),
|
||||
Instruction::Arithmetic(op) => {
|
||||
f.write_fmt(format_args!("{} ", op.op))?;
|
||||
match &op.instruction {
|
||||
ArithmeticInstructionSelect::RegisterToRegister(inst) => {
|
||||
f.write_fmt(format_args!("{}, {}", inst.dest, inst.source))
|
||||
}
|
||||
ArithmeticInstructionSelect::RegisterToMemory(inst) => {
|
||||
f.write_fmt(format_args!("{}, {}", inst.dest, inst.source))
|
||||
}
|
||||
ArithmeticInstructionSelect::MemoryToRegister(inst) => {
|
||||
f.write_fmt(format_args!("{}, {}", inst.dest, inst.source))
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryByte(addr, data, _) => {
|
||||
f.write_fmt(format_args!("{}, {}", addr, data))
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryWord(addr, data) => {
|
||||
f.write_fmt(format_args!("{}, {}", addr, data))
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterByte(addr, data, signed) => {
|
||||
if *signed {
|
||||
f.write_fmt(format_args!("{}, {} ; signed byte", addr, *data))
|
||||
} else {
|
||||
f.write_fmt(format_args!("{}, {}", addr, data))
|
||||
}
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterWord(addr, data, signed) => {
|
||||
if *signed {
|
||||
f.write_fmt(format_args!("{}, {} ; signed word", addr, *data))
|
||||
} else {
|
||||
f.write_fmt(format_args!("{}, {}", addr, data))
|
||||
}
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToAccByte(data) => {
|
||||
f.write_fmt(format_args!("al, {}", data))
|
||||
}
|
||||
ArithmeticInstructionSelect::ImmediateToAccWord(data) => {
|
||||
f.write_fmt(format_args!("ax, {}", data))
|
||||
}
|
||||
}
|
||||
}
|
||||
Instruction::Jump(instruction, offset) => {
|
||||
f.write_fmt(format_args!("{} ; {}", instruction, offset))
|
||||
}
|
||||
Instruction::Trivia(trivia) => match trivia {
|
||||
TriviaInstruction::Label(l) => f.write_fmt(format_args!("{}:", l)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Instruction<&'a str> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Instruction::Move(mov) => mov.to_bytes(),
|
||||
Instruction::Arithmetic(instruction) => instruction.to_bytes(),
|
||||
Instruction::Jump(instruction, _) => {
|
||||
vec![
|
||||
match instruction {
|
||||
Jump::Je => 0b01110100,
|
||||
Jump::Jl => 0b01111100,
|
||||
Jump::Jle => 0b01111110,
|
||||
Jump::Jb => 0b01110010,
|
||||
Jump::Jbe => 0b01110110,
|
||||
Jump::Jp => 0b01111010,
|
||||
Jump::Jo => 0b01110000,
|
||||
Jump::Js => 0b01111000,
|
||||
Jump::Jne => 0b01110101,
|
||||
Jump::Jnl => 0b01111101,
|
||||
Jump::Jnle => 0b01111111,
|
||||
Jump::Jnb => 0b01110011,
|
||||
Jump::Jnbe => 0b01110111,
|
||||
Jump::Jnp => 0b01111011,
|
||||
Jump::Jno => 0b01110001,
|
||||
Jump::Jns => 0b01111001,
|
||||
Jump::Loop => 0b11100010,
|
||||
Jump::Loopz => 0b11100001,
|
||||
Jump::Loopnz => 0b11100000,
|
||||
Jump::Jcxz => 0b11100011,
|
||||
},
|
||||
// Placeholder destination which will be filled in later
|
||||
0,
|
||||
]
|
||||
}
|
||||
|
||||
Instruction::Trivia(_) => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction<i8> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Instruction::Move(mov) => mov.to_bytes(),
|
||||
Instruction::Arithmetic(instruction) => instruction.to_bytes(),
|
||||
Instruction::Jump(instruction, offset) => {
|
||||
let mut result = Vec::<u8>::with_capacity(2);
|
||||
|
||||
result.push(match instruction {
|
||||
Jump::Je => 0b01110100,
|
||||
Jump::Jl => 0b11111100,
|
||||
Jump::Jle => 0b01111110,
|
||||
Jump::Jb => 0b01110010,
|
||||
Jump::Jbe => 0b01110110,
|
||||
Jump::Jp => 0b01111010,
|
||||
Jump::Jo => 0b01110000,
|
||||
Jump::Js => 0b01111000,
|
||||
Jump::Jne => 0b01110101,
|
||||
Jump::Jnl => 0b01111101,
|
||||
Jump::Jnle => 0b01111111,
|
||||
Jump::Jnb => 0b01110011,
|
||||
Jump::Jnbe => 0b01110111,
|
||||
Jump::Jnp => 0b01111011,
|
||||
Jump::Jno => 0b01110001,
|
||||
Jump::Jns => 0b01111001,
|
||||
Jump::Loop => 0b11100010,
|
||||
Jump::Loopz => 0b11100001,
|
||||
Jump::Loopnz => 0b11100000,
|
||||
Jump::Jcxz => 0b11100011,
|
||||
});
|
||||
|
||||
result.push(if *offset >= 0 {
|
||||
*offset as u8
|
||||
} else {
|
||||
255 - (-*offset) as u8 + 1
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
Instruction::Trivia(_) => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume<I>(bytes: &mut I) -> Option<Instruction<i8>>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
if let Some(b) = bytes.next() {
|
||||
if (b & 0b11111100u8) == 0b10001000u8 {
|
||||
let d = (b / 2) % 2;
|
||||
let is_wide = b % 2 == 1;
|
||||
if let Some(mod_reg_rm) = bytes.next() {
|
||||
let mode = (mod_reg_rm & 0b11000000) / 64;
|
||||
let reg = (mod_reg_rm & 0b00111000) / 8;
|
||||
let rm = mod_reg_rm & 0b00000111;
|
||||
let reg = Register::of_id(reg, is_wide);
|
||||
|
||||
if mode == 3 {
|
||||
let rm = Register::of_id(rm, is_wide);
|
||||
|
||||
let instruction = if d == 0 {
|
||||
RegRegMove {
|
||||
source: reg,
|
||||
dest: rm,
|
||||
}
|
||||
} else {
|
||||
RegRegMove {
|
||||
source: rm,
|
||||
dest: reg,
|
||||
}
|
||||
};
|
||||
Some(Instruction::Move(MoveInstruction::RegRegMove(instruction)))
|
||||
} else {
|
||||
let mem_location = EffectiveAddress::of_mode_rm(mode, rm, bytes);
|
||||
if d == 0 {
|
||||
Some(Instruction::Move(MoveInstruction::RegMemMove(RegMemMove {
|
||||
source: reg,
|
||||
dest: mem_location,
|
||||
})))
|
||||
} else {
|
||||
Some(Instruction::Move(MoveInstruction::MemRegMove(MemRegMove {
|
||||
dest: reg,
|
||||
source: mem_location,
|
||||
})))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("mov required a second byte")
|
||||
}
|
||||
} else if (b & 0b11110000u8) == 0b10110000u8 {
|
||||
// Immediate to register
|
||||
let w = (b / 8) % 2;
|
||||
|
||||
if w == 1 {
|
||||
let reg = Register::of_id(b % 8, true);
|
||||
let next_low = bytes.next().unwrap() as u16;
|
||||
let next_high = bytes.next().unwrap() as u16;
|
||||
Some(Instruction::Move(MoveInstruction::ImmediateToRegister(
|
||||
ImmediateToRegister::Wide(reg, next_low + 256 * next_high),
|
||||
)))
|
||||
} else {
|
||||
let reg = Register::of_id(b % 8, false);
|
||||
let next_low = bytes.next().unwrap();
|
||||
Some(Instruction::Move(MoveInstruction::ImmediateToRegister(
|
||||
ImmediateToRegister::Byte(reg, next_low),
|
||||
)))
|
||||
}
|
||||
} else if (b & 0b11111110) == 0b10100000 {
|
||||
// Memory to accumulator
|
||||
let w = b % 2;
|
||||
let addr_low = bytes.next().unwrap() as u16;
|
||||
let addr_high = bytes.next().unwrap() as u16 * 256;
|
||||
Some(Instruction::Move(MoveInstruction::MemoryToAccumulator(
|
||||
MemoryToAccumulator {
|
||||
address: addr_high + addr_low,
|
||||
is_wide: w == 1,
|
||||
},
|
||||
)))
|
||||
} else if (b & 0b11111110) == 0b10100010 {
|
||||
// Accumulator to memory
|
||||
let w = b % 2;
|
||||
let addr_low = bytes.next().unwrap() as u16;
|
||||
let addr_high = bytes.next().unwrap() as u16 * 256;
|
||||
Some(Instruction::Move(MoveInstruction::AccumulatorToMemory(
|
||||
AccumulatorToMemory {
|
||||
address: addr_high + addr_low,
|
||||
is_wide: w == 1,
|
||||
},
|
||||
)))
|
||||
} else if (b & 0b11111110) == 0b11000110 {
|
||||
// Immediate to register/memory
|
||||
let w = b % 2;
|
||||
let mod_reg_rm = bytes.next().unwrap();
|
||||
let mode = (mod_reg_rm & 0b11000000) / 64;
|
||||
let reg = (mod_reg_rm & 0b00111000) / 8;
|
||||
let rm = mod_reg_rm & 0b00000111;
|
||||
assert_eq!(reg, 0);
|
||||
let dest = EffectiveAddress::of_mode_rm(mode, rm, bytes);
|
||||
|
||||
let data_low = bytes.next().unwrap();
|
||||
if w == 1 {
|
||||
let data_high = bytes.next().unwrap() as u16 * 256;
|
||||
Some(Instruction::Move(
|
||||
MoveInstruction::ImmediateToRegisterOrMemory(
|
||||
ImmediateToRegisterOrMemory::Word(dest, data_high + data_low as u16),
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Some(Instruction::Move(
|
||||
MoveInstruction::ImmediateToRegisterOrMemory(
|
||||
ImmediateToRegisterOrMemory::Byte(dest, data_low),
|
||||
),
|
||||
))
|
||||
}
|
||||
} else if (b & 0b11000100) == 0b00000000u8 {
|
||||
// Arithmetic instruction, reg/memory with register to either
|
||||
let op = ArithmeticOperation::of_byte((b & 0b00111000u8) / 8);
|
||||
let is_wide = b % 2 == 1;
|
||||
let d = (b / 2) % 2;
|
||||
let mod_reg_rm = bytes.next().unwrap();
|
||||
let mode = (mod_reg_rm & 0b11000000) / 64;
|
||||
let reg = Register::of_id((mod_reg_rm & 0b00111000) / 8, is_wide);
|
||||
let rm = mod_reg_rm & 0b00000111;
|
||||
if mode == 3 {
|
||||
let rm = Register::of_id(rm, is_wide);
|
||||
|
||||
let (source, dest) = if d == 0 { (reg, rm) } else { (rm, reg) };
|
||||
Some(Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op,
|
||||
instruction: ArithmeticInstructionSelect::RegisterToRegister(
|
||||
RegRegArithmetic { source, dest },
|
||||
),
|
||||
}))
|
||||
} else {
|
||||
let mem_location = EffectiveAddress::of_mode_rm(mode, rm, bytes);
|
||||
if d == 0 {
|
||||
Some(Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op,
|
||||
instruction: ArithmeticInstructionSelect::RegisterToMemory(
|
||||
RegMemArithmetic {
|
||||
source: reg,
|
||||
dest: mem_location,
|
||||
},
|
||||
),
|
||||
}))
|
||||
} else {
|
||||
Some(Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op,
|
||||
instruction: ArithmeticInstructionSelect::MemoryToRegister(
|
||||
MemRegArithmetic {
|
||||
source: mem_location,
|
||||
dest: reg,
|
||||
},
|
||||
),
|
||||
}))
|
||||
}
|
||||
}
|
||||
} else if b & 0b11111100 == 0b10000000 {
|
||||
// Immediate to register/memory
|
||||
let w = b % 2;
|
||||
let signed = (b / 2) % 2 == 1;
|
||||
let mod_reg_rm = bytes.next().unwrap();
|
||||
let mode = (mod_reg_rm & 0b11000000) / 64;
|
||||
let op = ArithmeticOperation::of_byte((mod_reg_rm & 0b00111000) / 8);
|
||||
let rm = mod_reg_rm & 0b00000111;
|
||||
if mode == 3 {
|
||||
let data_low = bytes.next().unwrap();
|
||||
let dest = Register::of_id(rm, w == 1);
|
||||
Some(Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op,
|
||||
instruction: if w == 0 || signed {
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterByte(
|
||||
dest, data_low, signed,
|
||||
)
|
||||
} else {
|
||||
let data = (bytes.next().unwrap() as u16) * 256 + data_low as u16;
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterWord(dest, data, signed)
|
||||
},
|
||||
}))
|
||||
} else {
|
||||
let dest = EffectiveAddress::of_mode_rm(mode, rm, bytes);
|
||||
let data_low = bytes.next().unwrap();
|
||||
Some(Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op,
|
||||
instruction: if w == 0 || signed {
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryByte(
|
||||
dest, data_low, signed,
|
||||
)
|
||||
} else {
|
||||
let data = (bytes.next().unwrap() as u16) * 256 + data_low as u16;
|
||||
ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryWord(dest, data)
|
||||
},
|
||||
}))
|
||||
}
|
||||
} else if b & 0b11000110u8 == 0b00000100 {
|
||||
// Immediate to accumulator
|
||||
let w = b % 2;
|
||||
let data = bytes.next().unwrap();
|
||||
let op = ArithmeticOperation::of_byte((b & 0b00111000) / 8);
|
||||
Some(Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op,
|
||||
instruction: if w == 0 {
|
||||
ArithmeticInstructionSelect::ImmediateToAccByte(data)
|
||||
} else {
|
||||
let data = 256 * (bytes.next().unwrap() as u16) + data as u16;
|
||||
ArithmeticInstructionSelect::ImmediateToAccWord(data)
|
||||
},
|
||||
}))
|
||||
} else if b & 0b11111100 == 0b11100000 {
|
||||
// Loop
|
||||
let next = bytes.next().unwrap();
|
||||
let instruction = match b % 4 {
|
||||
0 => Jump::Loopnz,
|
||||
1 => Jump::Loopz,
|
||||
2 => Jump::Loop,
|
||||
3 => Jump::Jcxz,
|
||||
b => panic!("maths fail, {} is not a remainder mod 4", b),
|
||||
};
|
||||
Some(Instruction::Jump(
|
||||
instruction,
|
||||
if next >= 128 {
|
||||
(255 - next) as i8 - 1
|
||||
} else {
|
||||
next as i8
|
||||
},
|
||||
))
|
||||
} else if b & 0b11110000 == 0b01110000 {
|
||||
// Jump
|
||||
let next = bytes.next().unwrap();
|
||||
let instruction = match b % 16 {
|
||||
0 => Jump::Jo,
|
||||
1 => Jump::Jno,
|
||||
2 => Jump::Jb,
|
||||
3 => Jump::Jnb,
|
||||
4 => Jump::Je,
|
||||
5 => Jump::Jne,
|
||||
6 => Jump::Jbe,
|
||||
7 => Jump::Jnbe,
|
||||
8 => Jump::Js,
|
||||
9 => Jump::Jns,
|
||||
10 => Jump::Jp,
|
||||
11 => Jump::Jnp,
|
||||
12 => Jump::Jl,
|
||||
13 => Jump::Jnl,
|
||||
14 => Jump::Jle,
|
||||
15 => Jump::Jnle,
|
||||
b => panic!("maths fail, {} is not a remainder mod 16", b),
|
||||
};
|
||||
Some(Instruction::Jump(
|
||||
instruction,
|
||||
if next >= 128 {
|
||||
(255 - next) as i8 - 1
|
||||
} else {
|
||||
next as i8
|
||||
},
|
||||
))
|
||||
} else {
|
||||
panic!("Unrecognised instruction byte: {}", b)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
52
sim_8086/src/jump_instruction.rs
Normal file
52
sim_8086/src/jump_instruction.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum Jump {
|
||||
Je,
|
||||
Jl,
|
||||
Jle,
|
||||
Jb,
|
||||
Jbe,
|
||||
Jp,
|
||||
Jo,
|
||||
Js,
|
||||
Jne,
|
||||
Jnl,
|
||||
Jnle,
|
||||
Jnb,
|
||||
Jnbe,
|
||||
Jnp,
|
||||
Jno,
|
||||
Jns,
|
||||
Loop,
|
||||
Loopz,
|
||||
Loopnz,
|
||||
Jcxz,
|
||||
}
|
||||
|
||||
impl Display for Jump {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Jump::Je => "je",
|
||||
Jump::Jl => "jl",
|
||||
Jump::Jle => "jle",
|
||||
Jump::Jb => "jb",
|
||||
Jump::Jbe => "jbe",
|
||||
Jump::Jp => "jp",
|
||||
Jump::Jo => "jo",
|
||||
Jump::Js => "js",
|
||||
Jump::Jne => "jne",
|
||||
Jump::Jnl => "jnl",
|
||||
Jump::Jnle => "jnle",
|
||||
Jump::Jnb => "jnb",
|
||||
Jump::Jnbe => "jnbe",
|
||||
Jump::Jnp => "jnp",
|
||||
Jump::Jno => "jno",
|
||||
Jump::Jns => "jns",
|
||||
Jump::Loop => "loop",
|
||||
Jump::Loopz => "loopz",
|
||||
Jump::Loopnz => "loopnz",
|
||||
Jump::Jcxz => "jcxz",
|
||||
})
|
||||
}
|
||||
}
|
9
sim_8086/src/lib.rs
Normal file
9
sim_8086/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod arithmetic_instruction;
|
||||
pub mod assembly;
|
||||
pub mod effective_address;
|
||||
pub mod instruction;
|
||||
pub mod jump_instruction;
|
||||
pub mod move_instruction;
|
||||
pub mod program;
|
||||
pub mod register;
|
||||
pub mod trivia_instruction;
|
335
sim_8086/src/move_instruction.rs
Normal file
335
sim_8086/src/move_instruction.rs
Normal file
@@ -0,0 +1,335 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
effective_address::EffectiveAddress,
|
||||
register::{ByteRegisterSubset, Register, RegisterSubset},
|
||||
};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct RegRegMove {
|
||||
pub source: Register,
|
||||
pub dest: Register,
|
||||
}
|
||||
|
||||
impl RegRegMove {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::with_capacity(2);
|
||||
let instruction1 = 0b10001000u8;
|
||||
let mut is_wide = 1u8;
|
||||
// We always pick the 0 direction, so REG indicates the source.
|
||||
let d: u8 = 0;
|
||||
// Register-to-register move
|
||||
let mode = 0b11000000u8;
|
||||
match (&self.dest, &self.source) {
|
||||
(
|
||||
Register::General(dest, RegisterSubset::Subset(dest_subset)),
|
||||
Register::General(source, RegisterSubset::Subset(source_subset)),
|
||||
) => {
|
||||
is_wide = 0;
|
||||
result.push(instruction1 + 2 * d + is_wide);
|
||||
|
||||
let dest_offset: u8 = 4 * match dest_subset {
|
||||
ByteRegisterSubset::Low => 0,
|
||||
ByteRegisterSubset::High => 1,
|
||||
};
|
||||
let rm: u8 = dest_offset + dest.to_id();
|
||||
|
||||
let source_offset: u8 = 4 * match source_subset {
|
||||
ByteRegisterSubset::Low => 0,
|
||||
ByteRegisterSubset::High => 1,
|
||||
};
|
||||
let reg: u8 = source_offset + source.to_id();
|
||||
result.push(mode + reg * 8 + rm);
|
||||
}
|
||||
(
|
||||
Register::General(dest, RegisterSubset::All),
|
||||
Register::General(source, RegisterSubset::All),
|
||||
) => {
|
||||
result.push(instruction1 + 2 * d + is_wide);
|
||||
let reg = source.to_id();
|
||||
let rm = dest.to_id();
|
||||
result.push(mode + reg * 8 + rm);
|
||||
}
|
||||
(Register::General(dest, RegisterSubset::All), Register::Special(source)) => {
|
||||
result.push(instruction1 + 2 * d + is_wide);
|
||||
let reg = source.to_id();
|
||||
let rm = dest.to_id();
|
||||
result.push(mode + reg * 8 + rm);
|
||||
}
|
||||
(Register::Special(dest), Register::General(source, RegisterSubset::All)) => {
|
||||
result.push(instruction1 + 2 * d + is_wide);
|
||||
let reg = source.to_id();
|
||||
let rm = dest.to_id();
|
||||
result.push(mode + reg * 8 + rm);
|
||||
}
|
||||
(Register::Special(dest), Register::Special(source)) => {
|
||||
result.push(instruction1 + 2 * d + is_wide);
|
||||
let reg = source.to_id();
|
||||
let rm = dest.to_id();
|
||||
result.push(mode + reg * 8 + rm);
|
||||
}
|
||||
(
|
||||
Register::General(_, RegisterSubset::Subset(_)),
|
||||
Register::General(_, RegisterSubset::All),
|
||||
) => {
|
||||
panic!("tried to move wide into narrow")
|
||||
}
|
||||
(Register::General(_, RegisterSubset::Subset(_)), Register::Special(_)) => {
|
||||
panic!("tried to move wide into narrow")
|
||||
}
|
||||
(Register::Special(_), Register::General(_, RegisterSubset::Subset(_))) => {
|
||||
panic!("tried to move narrow into wide")
|
||||
}
|
||||
(
|
||||
Register::General(_, RegisterSubset::All),
|
||||
Register::General(_, RegisterSubset::Subset(_)),
|
||||
) => {
|
||||
panic!("tried to move narrow into wide")
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct RegMemMove {
|
||||
pub source: Register,
|
||||
pub dest: EffectiveAddress,
|
||||
}
|
||||
|
||||
impl RegMemMove {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::<u8>::with_capacity(2);
|
||||
|
||||
let instruction1 = 0b10001000u8;
|
||||
// Source is the register.
|
||||
let d = 0;
|
||||
let (source_reg, is_wide) = self.source.to_id();
|
||||
result.push(instruction1 + 2 * d + if is_wide { 1 } else { 0 });
|
||||
|
||||
self.dest.push(source_reg, &mut result);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct MemRegMove {
|
||||
pub source: EffectiveAddress,
|
||||
pub dest: Register,
|
||||
}
|
||||
|
||||
impl MemRegMove {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::with_capacity(2);
|
||||
|
||||
let instruction1 = 0b10001000u8;
|
||||
// Source is the effective address, so REG is the dest.
|
||||
let d: u8 = 1;
|
||||
let (dest_reg, is_wide) = self.dest.to_id();
|
||||
result.push(instruction1 + 2 * d + if is_wide { 1 } else { 0 });
|
||||
|
||||
self.source.push(dest_reg, &mut result);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum ImmediateToRegister {
|
||||
Byte(Register, u8),
|
||||
Wide(Register, u16),
|
||||
}
|
||||
|
||||
impl ImmediateToRegister {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::<u8>::with_capacity(2);
|
||||
let instruction = 0b10110000u8;
|
||||
match self {
|
||||
ImmediateToRegister::Byte(register, data) => {
|
||||
let (reg, is_wide) = register.to_id();
|
||||
if is_wide {
|
||||
panic!("Tried to store a byte into a word register")
|
||||
}
|
||||
result.push(instruction + reg);
|
||||
result.push(*data);
|
||||
}
|
||||
ImmediateToRegister::Wide(register, data) => {
|
||||
result.reserve_exact(1);
|
||||
let (reg, is_wide) = register.to_id();
|
||||
if !is_wide {
|
||||
panic!("Tried to store a word into a byte register")
|
||||
}
|
||||
result.push(instruction + 8 + reg);
|
||||
result.push((data % 256) as u8);
|
||||
result.push((data / 256) as u8);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ImmediateToRegister {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ImmediateToRegister::Byte(dest, value) => {
|
||||
f.write_fmt(format_args!("{}, {}", dest, value))
|
||||
}
|
||||
ImmediateToRegister::Wide(dest, value) => {
|
||||
f.write_fmt(format_args!("{}, {}", dest, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum ImmediateToRegisterOrMemory {
|
||||
Byte(EffectiveAddress, u8),
|
||||
Word(EffectiveAddress, u16),
|
||||
}
|
||||
|
||||
impl ImmediateToRegisterOrMemory {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::<u8>::with_capacity(3);
|
||||
let opcode = 0b11000110u8;
|
||||
|
||||
match self {
|
||||
ImmediateToRegisterOrMemory::Byte(address, data) => {
|
||||
result.push(opcode);
|
||||
address.push(0, &mut result);
|
||||
result.push(*data);
|
||||
}
|
||||
ImmediateToRegisterOrMemory::Word(address, data) => {
|
||||
result.push(opcode + 1);
|
||||
address.push(0, &mut result);
|
||||
result.push((data % 256) as u8);
|
||||
result.push((data / 256) as u8);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct MemoryToAccumulator {
|
||||
pub address: u16,
|
||||
pub is_wide: bool,
|
||||
}
|
||||
|
||||
impl MemoryToAccumulator {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::<u8>::with_capacity(3);
|
||||
result.push(0b10100000u8 + if self.is_wide { 1 } else { 0 });
|
||||
result.push((self.address % 256) as u8);
|
||||
result.push((self.address / 256) as u8);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub struct AccumulatorToMemory {
|
||||
pub address: u16,
|
||||
pub is_wide: bool,
|
||||
}
|
||||
|
||||
impl AccumulatorToMemory {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::<u8>::with_capacity(3);
|
||||
result.push(0b10100010u8 + if self.is_wide { 1 } else { 0 });
|
||||
result.push((self.address % 256) as u8);
|
||||
result.push((self.address / 256) as u8);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum MoveInstruction {
|
||||
/// Move a value from one register to another
|
||||
RegRegMove(RegRegMove),
|
||||
/// Store a value from a register into memory
|
||||
RegMemMove(RegMemMove),
|
||||
/// Load a value from memory into a register
|
||||
MemRegMove(MemRegMove),
|
||||
/// Load a literal value into a register
|
||||
ImmediateToRegister(ImmediateToRegister),
|
||||
/// Load a literal value into a register or into memory
|
||||
ImmediateToRegisterOrMemory(ImmediateToRegisterOrMemory),
|
||||
/// Load a value from memory into the accumulator
|
||||
MemoryToAccumulator(MemoryToAccumulator),
|
||||
/// Store a value into memory from the accumulator
|
||||
AccumulatorToMemory(AccumulatorToMemory),
|
||||
}
|
||||
|
||||
impl Display for MoveInstruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MoveInstruction::RegRegMove(mov) => {
|
||||
f.write_fmt(format_args!("mov {}, {}", mov.dest, mov.source))
|
||||
}
|
||||
MoveInstruction::RegMemMove(mov) => {
|
||||
f.write_fmt(format_args!("mov {}, {}", mov.dest, mov.source))
|
||||
}
|
||||
MoveInstruction::MemRegMove(mov) => {
|
||||
f.write_fmt(format_args!("mov {}, {}", mov.dest, mov.source))
|
||||
}
|
||||
MoveInstruction::ImmediateToRegister(instruction) => {
|
||||
f.write_fmt(format_args!("mov {}", instruction))
|
||||
}
|
||||
MoveInstruction::ImmediateToRegisterOrMemory(ImmediateToRegisterOrMemory::Byte(
|
||||
address,
|
||||
value,
|
||||
)) => f.write_fmt(format_args!("mov {}, {}", address, value)),
|
||||
MoveInstruction::ImmediateToRegisterOrMemory(ImmediateToRegisterOrMemory::Word(
|
||||
address,
|
||||
value,
|
||||
)) => f.write_fmt(format_args!("mov {}, {}", address, value)),
|
||||
MoveInstruction::MemoryToAccumulator(instruction) => f.write_fmt(format_args!(
|
||||
"mov a{}, [{}]",
|
||||
if instruction.is_wide { 'x' } else { 'l' },
|
||||
instruction.address
|
||||
)),
|
||||
MoveInstruction::AccumulatorToMemory(instruction) => f.write_fmt(format_args!(
|
||||
"mov [{}], a{}",
|
||||
instruction.address,
|
||||
if instruction.is_wide { 'x' } else { 'l' }
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveInstruction {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
MoveInstruction::RegMemMove(mov) => mov.to_bytes(),
|
||||
MoveInstruction::MemRegMove(mov) => mov.to_bytes(),
|
||||
MoveInstruction::ImmediateToRegister(mov) => mov.to_bytes(),
|
||||
MoveInstruction::ImmediateToRegisterOrMemory(mov) => mov.to_bytes(),
|
||||
MoveInstruction::MemoryToAccumulator(mov) => mov.to_bytes(),
|
||||
MoveInstruction::AccumulatorToMemory(mov) => mov.to_bytes(),
|
||||
MoveInstruction::RegRegMove(mov) => mov.to_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_move_instruction {
|
||||
use crate::{
|
||||
effective_address::EffectiveAddress,
|
||||
instruction::Instruction,
|
||||
move_instruction::{MemRegMove, MoveInstruction},
|
||||
register::{GeneralRegister, Register, RegisterSubset},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn mem_reg_move_to_bytes() {
|
||||
let i = MoveInstruction::MemRegMove(MemRegMove {
|
||||
source: EffectiveAddress::BasePointer(0),
|
||||
dest: Register::General(GeneralRegister::D, RegisterSubset::All),
|
||||
});
|
||||
assert_eq!(Instruction::<i8>::Move(i).to_bytes(), vec![139, 86, 0]);
|
||||
}
|
||||
}
|
121
sim_8086/src/program.rs
Normal file
121
sim_8086/src/program.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use std::{collections::HashMap, fmt::Display, marker::PhantomData};
|
||||
|
||||
use crate::{instruction::Instruction, trivia_instruction::TriviaInstruction};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Program<T, InstructionOffset>
|
||||
where
|
||||
T: AsRef<[Instruction<InstructionOffset>]>,
|
||||
{
|
||||
pub bits: u8,
|
||||
pub instructions: T,
|
||||
pub offset: PhantomData<InstructionOffset>,
|
||||
}
|
||||
|
||||
impl<T, InstructionOffset> Display for Program<T, InstructionOffset>
|
||||
where
|
||||
T: AsRef<[Instruction<InstructionOffset>]>,
|
||||
InstructionOffset: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("bits {}\n", self.bits))?;
|
||||
for i in self.instructions.as_ref().iter() {
|
||||
f.write_fmt(format_args!("{}\n", i))?;
|
||||
}
|
||||
std::fmt::Result::Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Program<T, &'a str>
|
||||
where
|
||||
T: AsRef<[Instruction<&'a str>]>,
|
||||
{
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut result = Vec::new();
|
||||
if self.bits != 16 {
|
||||
panic!("Only 16-bits supported");
|
||||
}
|
||||
let mut labels = HashMap::new();
|
||||
for (counter, instruction) in self.instructions.as_ref().iter().enumerate() {
|
||||
if let Instruction::Trivia(TriviaInstruction::Label(s)) = instruction {
|
||||
if let Some(s) = labels.insert(*s, counter) {
|
||||
panic!("same label twice: {}", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut instruction_boundaries = vec![0; self.instructions.as_ref().len()];
|
||||
|
||||
for (counter, instruction) in self.instructions.as_ref().iter().enumerate() {
|
||||
let new_bytes = instruction.to_bytes();
|
||||
result.extend(new_bytes);
|
||||
instruction_boundaries[counter] = result.len();
|
||||
}
|
||||
|
||||
for (counter, instruction) in self.instructions.as_ref().iter().enumerate() {
|
||||
if let Instruction::Jump(_, offset) = instruction {
|
||||
let desired_target_instruction_number = match labels.get(offset) {
|
||||
Some(s) => *s,
|
||||
None => panic!("Tried to jump to label, but was not present: '{}'", offset),
|
||||
};
|
||||
let required_jump =
|
||||
(instruction_boundaries[desired_target_instruction_number] as i64
|
||||
- instruction_boundaries[counter] as i64) as i8;
|
||||
let required_jump = if required_jump < 0 {
|
||||
255 - (-required_jump as u8) + 1
|
||||
} else {
|
||||
required_jump as u8
|
||||
};
|
||||
result[instruction_boundaries[counter] - 1] = required_jump;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Program<T, i8>
|
||||
where
|
||||
T: AsRef<[Instruction<i8>]>,
|
||||
{
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
if self.bits != 16 {
|
||||
panic!("Only 16-bits supported");
|
||||
}
|
||||
self.instructions
|
||||
.as_ref()
|
||||
.iter()
|
||||
.flat_map(Instruction::<i8>::to_bytes)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Program<Vec<Instruction<i8>>, i8> {
|
||||
pub fn of_bytes<I>(mut bytes: I) -> Program<Vec<Instruction<i8>>, i8>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
|
||||
while let Some(i) = Instruction::consume(&mut bytes) {
|
||||
// println!("{}", i);
|
||||
output.push(i);
|
||||
}
|
||||
|
||||
Program {
|
||||
bits: 16,
|
||||
instructions: output,
|
||||
offset: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, U> PartialEq<Program<U, &'a str>> for Program<T, i8>
|
||||
where
|
||||
T: AsRef<[Instruction<i8>]>,
|
||||
U: AsRef<[Instruction<&'a str>]>,
|
||||
{
|
||||
fn eq(&self, other: &Program<U, &'a str>) -> bool {
|
||||
Program::<T, i8>::to_bytes(self) == Program::<U, &'a str>::to_bytes(other)
|
||||
}
|
||||
}
|
@@ -160,3 +160,33 @@ impl Register {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum SourceDest {
|
||||
Source,
|
||||
Dest,
|
||||
}
|
||||
|
||||
impl Display for SourceDest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SourceDest::Source => f.write_char('s'),
|
||||
SourceDest::Dest => f.write_char('d'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum Base {
|
||||
Bx,
|
||||
Bp,
|
||||
}
|
||||
|
||||
impl Display for Base {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Base::Bx => f.write_str("bx"),
|
||||
Base::Bp => f.write_str("bp"),
|
||||
}
|
||||
}
|
||||
}
|
4
sim_8086/src/trivia_instruction.rs
Normal file
4
sim_8086/src/trivia_instruction.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub enum TriviaInstruction<InstructionOffset> {
|
||||
Label(InstructionOffset),
|
||||
}
|
463
sim_8086/tests/test_program.rs
Normal file
463
sim_8086/tests/test_program.rs
Normal file
@@ -0,0 +1,463 @@
|
||||
#[cfg(test)]
|
||||
mod test_program {
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use sim_8086::{
|
||||
arithmetic_instruction::{
|
||||
ArithmeticInstruction, ArithmeticInstructionSelect, ArithmeticOperation,
|
||||
},
|
||||
assembly,
|
||||
instruction::Instruction,
|
||||
move_instruction::{ImmediateToRegister, MoveInstruction},
|
||||
program::Program,
|
||||
register::{GeneralRegister, Register, RegisterSubset},
|
||||
};
|
||||
|
||||
fn instruction_equal_ignoring_labels<A, B>(i1: &Instruction<A>, i2: &Instruction<B>) -> bool {
|
||||
match (i1, i2) {
|
||||
(Instruction::Move(i1), Instruction::Move(i2)) => i1 == i2,
|
||||
(Instruction::Move(_), _) => false,
|
||||
(Instruction::Arithmetic(i1), Instruction::Arithmetic(i2)) => i1 == i2,
|
||||
(Instruction::Arithmetic(_), _) => false,
|
||||
(Instruction::Jump(i1, _), Instruction::Jump(i2, _)) => i1 == i2,
|
||||
(Instruction::Jump(_, _), _) => false,
|
||||
(Instruction::Trivia(_), Instruction::Trivia(_)) => true,
|
||||
(Instruction::Trivia(_), _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_programs_with_different_instruction_sequences_are_not_equal() {
|
||||
let program1: Program<_, u8> = Program {
|
||||
bits: 64,
|
||||
instructions: vec![],
|
||||
offset: PhantomData,
|
||||
};
|
||||
let program2 = Program {
|
||||
bits: 64,
|
||||
instructions: vec![Instruction::Move(MoveInstruction::ImmediateToRegister(
|
||||
ImmediateToRegister::Byte(
|
||||
Register::General(GeneralRegister::D, RegisterSubset::All),
|
||||
1,
|
||||
),
|
||||
))],
|
||||
offset: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
assert_ne!(program1, program2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_programs_with_identical_instruction_sequences_are_equal() {
|
||||
let program1: Program<_, u8> = Program {
|
||||
bits: 64,
|
||||
instructions: vec![Instruction::Move(MoveInstruction::ImmediateToRegister(
|
||||
ImmediateToRegister::Byte(
|
||||
Register::General(GeneralRegister::D, RegisterSubset::All),
|
||||
1,
|
||||
),
|
||||
))],
|
||||
offset: PhantomData,
|
||||
};
|
||||
let program2 = Program {
|
||||
bits: 64,
|
||||
instructions: vec![Instruction::Move(MoveInstruction::ImmediateToRegister(
|
||||
ImmediateToRegister::Byte(
|
||||
Register::General(GeneralRegister::D, RegisterSubset::All),
|
||||
1,
|
||||
),
|
||||
))],
|
||||
offset: PhantomData,
|
||||
};
|
||||
|
||||
assert_eq!(program1, program2);
|
||||
}
|
||||
|
||||
fn test_parser_lax<T>(
|
||||
input_asm: &str,
|
||||
input_bytecode: T,
|
||||
permit_equivalences: HashMap<Instruction<&str>, Instruction<&str>>,
|
||||
) where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
let (remaining, parsed) = assembly::program(input_asm).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(parsed.bits, 16);
|
||||
|
||||
let adjusted_program: Program<Vec<Instruction<_>>, _> = Program {
|
||||
bits: parsed.bits,
|
||||
instructions: parsed
|
||||
.instructions
|
||||
.into_iter()
|
||||
.map(|i| match permit_equivalences.get(&i) {
|
||||
Some(v) => v.clone(),
|
||||
None => i.clone(),
|
||||
})
|
||||
.collect(),
|
||||
offset: PhantomData,
|
||||
};
|
||||
|
||||
for (i, (actual, expected)) in adjusted_program
|
||||
.to_bytes()
|
||||
.iter()
|
||||
.zip(input_bytecode.as_ref().iter())
|
||||
.enumerate()
|
||||
{
|
||||
if actual != expected {
|
||||
panic!(
|
||||
"Failed assertion: expected {} (from Casey), got {}, at position {}\n{:?}",
|
||||
expected,
|
||||
actual,
|
||||
i,
|
||||
adjusted_program.to_bytes()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_parser<T>(input_asm: &str, input_bytecode: T)
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
test_parser_lax(input_asm, input_bytecode, HashMap::new())
|
||||
}
|
||||
|
||||
fn test_disassembler_lax<T>(
|
||||
input_asm: &str,
|
||||
input_bytecode: T,
|
||||
permit_equivalences: HashSet<(Vec<u8>, Vec<u8>)>,
|
||||
) where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
let disassembled = Program::of_bytes(input_bytecode.as_ref().iter().cloned());
|
||||
|
||||
let (remaining, pre_compiled) = assembly::program(&input_asm).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
|
||||
let disassembled = disassembled.instructions.iter().filter(|i| match i {
|
||||
Instruction::Trivia(_) => false,
|
||||
_ => true,
|
||||
});
|
||||
let mut compiled = pre_compiled.instructions.iter().filter(|i| match i {
|
||||
Instruction::Trivia(_) => false,
|
||||
_ => true,
|
||||
});
|
||||
|
||||
let mut is_different = false;
|
||||
|
||||
for dis in disassembled {
|
||||
if let Some(compiled) = compiled.next() {
|
||||
if !instruction_equal_ignoring_labels(dis, compiled) {
|
||||
let compiled_bytes = compiled.to_bytes();
|
||||
let dis_bytes = dis.to_bytes();
|
||||
if !permit_equivalences.contains(&(compiled_bytes.clone(), dis_bytes.clone()))
|
||||
&& !permit_equivalences
|
||||
.contains(&(dis_bytes.clone(), compiled_bytes.clone()))
|
||||
{
|
||||
println!(
|
||||
"Different instruction. From disassembly: {dis} ({:?}). From our compilation: {compiled} ({:?}).",
|
||||
compiled_bytes,
|
||||
dis_bytes
|
||||
);
|
||||
is_different = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"Extra instruction from disassembly: {dis} ({:?})",
|
||||
dis.to_bytes()
|
||||
);
|
||||
is_different = true;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(compiled) = compiled.next() {
|
||||
println!(
|
||||
"Extra instruction from compilation: {compiled} ({:?})",
|
||||
compiled.to_bytes()
|
||||
);
|
||||
is_different = true;
|
||||
}
|
||||
|
||||
if is_different {
|
||||
panic!("Disassembling input bytecode produced a different program from compiling the input asm.")
|
||||
}
|
||||
}
|
||||
|
||||
fn test_disassembler<T>(input_asm: &str, input_bytecode: T)
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
test_disassembler_lax(input_asm, input_bytecode, HashSet::new())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_register_mov_parser() {
|
||||
let input_asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0037_single_register_mov.asm"
|
||||
);
|
||||
let input_bytecode = include_bytes!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0037_single_register_mov"
|
||||
);
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_register_mov_disassembler() {
|
||||
let bytecode = include_bytes!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0037_single_register_mov"
|
||||
);
|
||||
let asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0037_single_register_mov.asm"
|
||||
);
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_register_many_mov_parser() {
|
||||
let input_asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0038_many_register_mov.asm"
|
||||
);
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0038_many_register_mov");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_register_many_mov_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0038_many_register_mov");
|
||||
let asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0038_many_register_mov.asm"
|
||||
);
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_more_mov_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0039_more_movs.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0039_more_movs");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_more_mov_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0039_more_movs");
|
||||
let asm = include_str!("../../computer_enhance/perfaware/part1/listing_0039_more_movs.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_challenge_movs_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0040_challenge_movs.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0040_challenge_movs");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_challenge_movs_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0040_challenge_movs");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0040_challenge_movs.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_sub_cmp_jnz_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0041_add_sub_cmp_jnz.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0041_add_sub_cmp_jnz");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_sub_cmp_jnz_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0041_add_sub_cmp_jnz");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0041_add_sub_cmp_jnz.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_immediate_movs_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0043_immediate_movs.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0043_immediate_movs");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_immediate_movs_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0043_immediate_movs");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0043_immediate_movs.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_movs_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0044_register_movs.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0044_register_movs");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_movs_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0044_register_movs");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0044_register_movs.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
/*
|
||||
We have not yet implemented the segment registers, so this test can't pass.
|
||||
#[test]
|
||||
fn test_challenge_register_movs_parser() {
|
||||
let input_asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs.asm"
|
||||
);
|
||||
let input_bytecode = include_bytes!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs"
|
||||
);
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_register_movs_disassembler() {
|
||||
let bytecode = include_bytes!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs"
|
||||
);
|
||||
let asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs.asm"
|
||||
);
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_add_sub_cmp_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0046_add_sub_cmp.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0046_add_sub_cmp");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_sub_cmp_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0046_add_sub_cmp");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0046_add_sub_cmp.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_flags_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0047_challenge_flags.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0047_challenge_flags");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_flags_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0047_challenge_flags");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0047_challenge_flags.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ip_register_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0048_ip_register.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0048_ip_register");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ip_register_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0048_ip_register");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0048_ip_register.asm");
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conditional_jumps_parser() {
|
||||
let input_asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0049_conditional_jumps.asm"
|
||||
);
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0049_conditional_jumps");
|
||||
test_parser(input_asm, input_bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conditional_jumps_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0049_conditional_jumps");
|
||||
let asm = include_str!(
|
||||
"../../computer_enhance/perfaware/part1/listing_0049_conditional_jumps.asm"
|
||||
);
|
||||
test_disassembler(asm, bytecode)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_jumps_parser() {
|
||||
let input_asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0050_challenge_jumps.asm");
|
||||
let input_bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0050_challenge_jumps");
|
||||
let mut swaps = HashMap::new();
|
||||
swaps.insert(
|
||||
Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op: ArithmeticOperation::Add,
|
||||
instruction: ArithmeticInstructionSelect::ImmediateToAccWord(1),
|
||||
}),
|
||||
Instruction::Arithmetic(ArithmeticInstruction {
|
||||
op: ArithmeticOperation::Add,
|
||||
instruction: ArithmeticInstructionSelect::ImmediateToRegisterWord(
|
||||
Register::General(GeneralRegister::A, RegisterSubset::All),
|
||||
1,
|
||||
true,
|
||||
),
|
||||
}),
|
||||
);
|
||||
test_parser_lax(input_asm, input_bytecode, swaps)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_jumps_disassembler() {
|
||||
let bytecode =
|
||||
include_bytes!("../../computer_enhance/perfaware/part1/listing_0050_challenge_jumps");
|
||||
let asm =
|
||||
include_str!("../../computer_enhance/perfaware/part1/listing_0050_challenge_jumps.asm");
|
||||
let mut allowed = HashSet::new();
|
||||
// We implemented `add ax, 1` using "immediate to accumulator";
|
||||
// in this example, Casey implemented it using "immediate to register".
|
||||
allowed.insert((vec![5, 1, 0], vec![131, 192, 1]));
|
||||
test_disassembler_lax(asm, bytecode, allowed)
|
||||
}
|
||||
}
|
1885
src/main.rs
1885
src/main.rs
File diff suppressed because it is too large
Load Diff
10
wrapper/Cargo.toml
Normal file
10
wrapper/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "wrapper"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.2.1", features = [ "derive" ] }
|
||||
sim_8086 = { path = "../sim_8086" }
|
96
wrapper/src/main.rs
Normal file
96
wrapper/src/main.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use clap::Parser;
|
||||
use sim_8086::instruction::Instruction;
|
||||
use sim_8086::program::Program;
|
||||
|
||||
fn load_machine_code<P>(path: P) -> Vec<u8>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
fs::read(path).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
#[arg(value_name = "COMPILED_PATH")]
|
||||
compiled_path: std::path::PathBuf,
|
||||
#[arg(value_name = "ASM_PATH")]
|
||||
asm_path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
fn program_equal_ignoring_labels<A, B>(
|
||||
p1: &Program<Vec<Instruction<A>>, A>,
|
||||
p2: &Program<Vec<Instruction<B>>, B>,
|
||||
) -> bool
|
||||
where
|
||||
A: PartialEq,
|
||||
{
|
||||
if p1.bits != p2.bits {
|
||||
return false;
|
||||
}
|
||||
|
||||
let without_trivia_1 = p1
|
||||
.instructions
|
||||
.iter()
|
||||
.filter(|i| !matches!(i, Instruction::Trivia(_)));
|
||||
let mut without_trivia_2 = p1
|
||||
.instructions
|
||||
.iter()
|
||||
.filter(|i| !matches!(i, Instruction::Trivia(_)));
|
||||
|
||||
for i1 in without_trivia_1 {
|
||||
if let Some(i2) = without_trivia_2.next() {
|
||||
if i1 != i2 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if without_trivia_2.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
let expected_bytecode = load_machine_code(args.compiled_path);
|
||||
let asm = fs::read_to_string(args.asm_path).unwrap();
|
||||
let (remaining, compiled) = sim_8086::assembly::program(&asm).unwrap();
|
||||
|
||||
if !remaining.is_empty() {
|
||||
println!(
|
||||
"Failed to parse, as there was remaining code:\n{}",
|
||||
remaining
|
||||
);
|
||||
std::process::exit(2)
|
||||
}
|
||||
|
||||
let actual_bytecode = compiled.to_bytes();
|
||||
if expected_bytecode != actual_bytecode {
|
||||
println!(
|
||||
"Expected: {:?}\nActual: {:?}",
|
||||
expected_bytecode, actual_bytecode
|
||||
);
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
let disassembled = Program::of_bytes(expected_bytecode.iter().cloned());
|
||||
|
||||
if disassembled != compiled {
|
||||
println!("Disassembled and compiled versions do not produce the same bytes. From disassembly:\n{}\nFrom assembling the input asm:\n{}", disassembled, compiled);
|
||||
std::process::exit(3)
|
||||
}
|
||||
|
||||
if !program_equal_ignoring_labels(&disassembled, &compiled) {
|
||||
println!("Program failed to disassemble back to the compiled version. Compiled:\n{}\nDisassembled again:\n{}", compiled, disassembled);
|
||||
std::process::exit(4)
|
||||
}
|
||||
|
||||
println!(
|
||||
"Our assembly was equal to the reference, and it disassembled back to the same structure."
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user