Restructure into a library (#7)

This commit is contained in:
Patrick Stevens
2023-04-12 20:33:42 +01:00
committed by GitHub
parent eee94c399d
commit 0c6a3abc08
18 changed files with 2044 additions and 1925 deletions

29
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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'

View File

@@ -23,7 +23,7 @@
crate2nix,
...
}: let
name = "performance_aware_programming";
name = "wrapper";
in
utils.lib.eachDefaultSystem
(

12
sim_8086/Cargo.toml Normal file
View 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"

View 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,
}

View File

@@ -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))
}),

View 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
View 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
}
}
}

View 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
View 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;

View 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
View 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)
}
}

View File

@@ -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"),
}
}
}

View File

@@ -0,0 +1,4 @@
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
pub enum TriviaInstruction<InstructionOffset> {
Label(InstructionOffset),
}

View 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)
}
}

File diff suppressed because it is too large Load Diff

10
wrapper/Cargo.toml Normal file
View 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
View 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."
)
}