diff --git a/Cargo.toml b/Cargo.toml index a7297cf..b1362d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,16 @@ path = "src/cli.rs" name = "testcli" path = "src/testcli.rs" +[[bench]] +name = "executor_compare" +harness = false + +[features] +default = [] +consolidated = ["dep:pinned-init"] +cached_resolve = ["dep:pinned-init"] +instruction_mem = ["dep:pinned-init"] + [dependencies] anyhow = "1.0.95" once_cell = "1.20.2" @@ -26,13 +36,19 @@ strum = { version = "0.26.3", features = ["derive"] } strum_macros = "0.26.4" # To reduce error boilerplate thiserror = "2" +# For better error tracking +bare_err_tree = "0.7" +# Creating large struct parts directly on the heap +pinned-init = { version = "0.0.9", optional = true, default-features = false, features = ["std"] } [dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } # To reduce getter boilerplate derive-getters = "0.5" # More efficient map initialization once_map = "0.4" # Reducing test writing boilerplate via macro paste = "1" +stable_deref_trait = "1" # Random tempfile names uuid = { version = "1", features = ["v4"] } diff --git a/benches/executor_compare.rs b/benches/executor_compare.rs new file mode 100644 index 0000000..44acc7c --- /dev/null +++ b/benches/executor_compare.rs @@ -0,0 +1,123 @@ +use std::time::Duration; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use lc3sim_project::{ + executors::{core::CoreLC3, populate_from_bin, LC3}, + harnesses::{simple::FailIO, sync::step_continue}, +}; + +macro_rules! setup_lc3 { + ( $lc3:ident, $path:literal ) => { + || { + let mut lc3 = $lc3.clone(); + populate_from_bin(&mut lc3, include_bytes!("../penn_sim/lc3os.obj").as_slice()); + populate_from_bin(&mut lc3, include_bytes!($path).as_slice()); + lc3 + } + }; +} + +pub fn create_new(c: &mut Criterion) { + let mut c = c.benchmark_group("create_new"); + + macro_rules! bench_new { + ( $lc3:expr, $name: literal ) => { + c.bench_function($name, |b| { + b.iter($lc3); + }); + }; + } + + bench_new!(CoreLC3::new, "core"); + #[cfg(feature = "consolidated")] + bench_new!( + lc3sim_project::executors::consolidated::ConsolidatedLC3::boxed, + "consolidated" + ); + #[cfg(feature = "cached_resolve")] + bench_new!( + lc3sim_project::executors::cached_resolve::CachedResolveLC3::boxed, + "cached_resolve" + ); + #[cfg(feature = "instruction_mem")] + bench_new!( + lc3sim_project::executors::instruction_mem::InstMemLC3::boxed, + "instruction_mem" + ); +} + +pub fn load_os(c: &mut Criterion) { + let mut c = c.benchmark_group("load_os"); + + fn exec_setup(mut lc3: E) -> E { + populate_from_bin(&mut lc3, include_bytes!("../penn_sim/lc3os.obj").as_slice()); + black_box(lc3) + } + + macro_rules! bench_load { + ( $lc3:expr, $name: literal ) => { + c.bench_function($name, |b| { + b.iter_batched($lc3, exec_setup, criterion::BatchSize::SmallInput); + }); + }; + } + + bench_load!(CoreLC3::new, "core"); + #[cfg(feature = "consolidated")] + bench_load!( + lc3sim_project::executors::consolidated::ConsolidatedLC3::boxed, + "consolidated" + ); + #[cfg(feature = "cached_resolve")] + bench_load!( + lc3sim_project::executors::cached_resolve::CachedResolveLC3::boxed, + "cached_resolve" + ); + #[cfg(feature = "instruction_mem")] + bench_load!( + lc3sim_project::executors::instruction_mem::InstMemLC3::boxed, + "instruction_mem" + ); +} + +pub fn tiny_loop(c: &mut Criterion) { + let mut c = c.benchmark_group("tiny_loop"); + let c = c.measurement_time(Duration::from_secs(20)); + + fn exec_loop(mut lc3: E) { + step_continue(&mut FailIO, &mut lc3).unwrap(); + } + + macro_rules! bench_loop { + ( $lc3:expr, $name: literal ) => { + let lc3 = $lc3; + c.bench_function($name, |b| { + b.iter_batched( + setup_lc3!(lc3, "../test_data/custom/loop.obj"), + exec_loop, + criterion::BatchSize::SmallInput, + ); + }); + }; + } + + bench_loop!(CoreLC3::new(), "core"); + #[cfg(feature = "consolidated")] + bench_loop!( + lc3sim_project::executors::consolidated::ConsolidatedLC3::boxed(), + "consolidated" + ); + #[cfg(feature = "cached_resolve")] + bench_loop!( + lc3sim_project::executors::cached_resolve::CachedResolveLC3::boxed(), + "cached_resolve" + ); + #[cfg(feature = "instruction_mem")] + bench_loop!( + lc3sim_project::executors::instruction_mem::InstMemLC3::boxed(), + "instruction_mem" + ); +} + +criterion_group!(speed_compare, create_new, load_os, tiny_loop); +criterion_main!(speed_compare); diff --git a/penn_sim/lc3os.obj b/penn_sim/lc3os.obj new file mode 100644 index 0000000..23f8cef Binary files /dev/null and b/penn_sim/lc3os.obj differ diff --git a/src/executors/cached_resolve.rs b/src/executors/cached_resolve.rs new file mode 100644 index 0000000..867c8c8 --- /dev/null +++ b/src/executors/cached_resolve.rs @@ -0,0 +1,350 @@ +use std::{iter::FusedIterator, pin::Pin}; + +use pinned_init::{init, pin_data, pin_init, InPlaceInit}; + +use crate::{ + defs::{ + LC3MemAddr, LC3Word, RegAddr, ADDR_SPACE_SIZE, MACHINE_CONTROL_REGISTER, NUM_REGS, + OS_SUPER_STACK, STACK_REG, SUPERVISOR_SP_INIT, + }, + instruction::{Instruction, InstructionEnum}, +}; + +use super::{LC3MemLoc, StepFailure, LC3}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum Condition { + None, + Negative, + Zero, + Positive, +} + +/// Caches a resolution of LC3Word to an instruction +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum CachedResolve { + Raw(LC3Word), + Resolved(InstructionEnum), +} + +impl CachedResolve { + pub fn new(word: LC3Word) -> Self { + Self::Raw(word) + } + + #[inline] + #[cold] + pub fn raw_instr(&mut self, word: LC3Word) -> Option { + let instr = InstructionEnum::parse(word)?; + *self = Self::Resolved(instr); + Some(instr) + } + + pub fn instr(&mut self) -> Option { + match *self { + Self::Raw(word) => self.raw_instr(word), + Self::Resolved(instr) => Some(instr), + } + } + + pub fn word(&self) -> LC3Word { + match self { + Self::Raw(word) => *word, + Self::Resolved(instr) => (*instr).into(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[pin_data] +#[repr(transparent)] +struct CachedResolveStruct { + inner: CachedResolve, +} + +impl CachedResolveStruct { + pub fn new(word: LC3Word) -> Self { + Self { + inner: CachedResolve::new(word), + } + } + + pub fn instr(&mut self) -> Option { + self.inner.instr() + } + + pub fn word(&self) -> LC3Word { + self.inner.word() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[pin_data] +pub struct CachedResolveLC3 { + mem: [CachedResolveStruct; ADDR_SPACE_SIZE], + condition: Condition, + priority: u8, + privileged: bool, + regs: [LC3Word; NUM_REGS], + supervisor_sp: LC3Word, + pc: LC3MemAddr, + halted: bool, + mpr_disabled: bool, +} + +impl CachedResolveLC3 { + pub fn new() -> Self { + Self { + mem: [CachedResolveStruct::new(0); ADDR_SPACE_SIZE], + condition: Condition::None, + priority: 0, + privileged: true, + supervisor_sp: SUPERVISOR_SP_INIT, + regs: [0; NUM_REGS], + pc: OS_SUPER_STACK, + halted: false, + mpr_disabled: false, + } + } + + /// Constructs this as a box without allocating the full size on stack. + pub fn boxed() -> Box { + let this = Box::pin_init(pin_init!(Self { + // This is the field that might exceed stack capacity + mem <- pinned_init::init_array_from_fn(|_| init!(CachedResolveStruct { + inner: CachedResolve::Raw(0) + })), + condition: Condition::None, + priority: 0, + privileged: true, + supervisor_sp: SUPERVISOR_SP_INIT, + regs: [0; NUM_REGS], + pc: OS_SUPER_STACK, + halted: false, + mpr_disabled: false, + })); + Pin::into_inner(this.expect("Infalliable")) + } +} + +impl Default for CachedResolveLC3 { + fn default() -> Self { + Self::new() + } +} + +impl LC3 for CachedResolveLC3 { + fn pc(&self) -> LC3MemAddr { + self.pc + } + fn set_pc(&mut self, pc: LC3MemAddr) { + self.pc = pc + } + + fn reg(&self, addr: RegAddr) -> LC3Word { + let reg_addr = usize::from(addr); + + if self.privileged && reg_addr == STACK_REG.into() { + self.supervisor_sp + } else { + self.regs[reg_addr] + } + } + fn set_reg(&mut self, addr: RegAddr, value: LC3Word) { + let reg_addr = usize::from(addr); + + if self.privileged && reg_addr == STACK_REG.into() { + self.supervisor_sp = value + } else { + self.regs[reg_addr] = value + } + } + + fn mem(&self, addr: LC3MemAddr) -> LC3Word { + self.mem[addr as usize].word() + } + fn set_mem(&mut self, addr: LC3MemAddr, value: LC3Word) { + self.mem[addr as usize] = CachedResolveStruct::new(value); + if addr == MACHINE_CONTROL_REGISTER { + self.mpr_disabled = (value & (1 << 15)) == 0; + } + } + + fn priority(&self) -> u8 { + self.priority + } + fn set_priority(&mut self, priority: u8) { + if priority < 8 { + self.priority = priority + } + } + + fn privileged(&self) -> bool { + self.privileged + } + fn set_privileged(&mut self, priviledged: bool) { + self.privileged = priviledged + } + + fn positive_cond(&self) -> bool { + self.condition == Condition::Positive + } + fn zero_cond(&self) -> bool { + self.condition == Condition::Zero + } + fn negative_cond(&self) -> bool { + self.condition == Condition::Negative + } + + fn flag_positive(&mut self) { + self.condition = Condition::Positive; + } + fn flag_zero(&mut self) { + self.condition = Condition::Zero; + } + fn flag_negative(&mut self) { + self.condition = Condition::Negative; + } + + fn clear_flags(&mut self) { + self.condition = Condition::None; + } + + type FullIter<'a> = CachedLC3FullIter<'a>; + fn iter(&self) -> Self::FullIter<'_> { + CachedLC3FullIter::new(self) + } + + type SparseIter<'a> = CachedLC3SparseIter<'a>; + fn sparse_iter(&self) -> Self::SparseIter<'_> { + CachedLC3SparseIter::new(self.iter()) + } + + fn halt(&mut self) { + self.halted = true; + } + + fn unhalt(&mut self) { + self.halted = false; + } + + fn is_halted(&self) -> bool { + self.halted + } + + /// Executes the current instruction. + /// + /// Does not handle memory map updates. + fn step(&mut self) -> Result<(), StepFailure> { + if self.halted { + Err(StepFailure::Halted) + } else if self.mpr_disabled { + Err(StepFailure::ClockDisabled) + } else { + let inst = self.mem[self.pc() as usize] + .instr() + .ok_or(StepFailure::InvalidInstruction(self.mem(self.pc())))?; + + inst.execute(self)?; + + if !matches!(inst, InstructionEnum::IBranch(_)) + && !matches!(inst, InstructionEnum::IJump(_)) + { + self.pc += 1; + } + + Ok(()) + } + } + + fn populate>(&mut self, start: LC3MemAddr, words: I) { + let mem_iter_mut = self.mem[start.into()..].iter_mut(); + for (word, loc) in words.into_iter().zip(mem_iter_mut) { + *loc = CachedResolveStruct::new(word); + } + } +} + +/// Sparse iterator for [`CoreLC3`]. +/// +/// Skips all zero elements. +pub struct CachedLC3FullIter<'a> { + iter: std::slice::Iter<'a, CachedResolveStruct>, +} + +impl<'a> CachedLC3FullIter<'a> { + fn new(base: &'a CachedResolveLC3) -> Self { + Self { + iter: base.mem.iter(), + } + } +} + +impl Iterator for CachedLC3FullIter<'_> { + type Item = u16; + + fn next(&mut self) -> Option { + self.iter.next().map(|x| x.word()) + } +} + +impl DoubleEndedIterator for CachedLC3FullIter<'_> { + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|x| x.word()) + } +} + +impl ExactSizeIterator for CachedLC3FullIter<'_> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for CachedLC3FullIter<'_> {} + +/// Sparse iterator for [`CoreLC3`]. +/// +/// Skips all zero elements. +pub struct CachedLC3SparseIter<'a> { + iter: std::iter::Enumerate>, +} + +impl<'a> CachedLC3SparseIter<'a> { + fn new(iter: ::FullIter<'a>) -> Self { + Self { + iter: iter.enumerate(), + } + } +} + +impl Iterator for CachedLC3SparseIter<'_> { + type Item = LC3MemLoc; + + fn next(&mut self) -> Option { + loop { + let (loc, value) = self.iter.next()?; + if value != 0 { + return Some(LC3MemLoc { + loc: loc as u16, + value, + }); + } + } + } +} + +impl DoubleEndedIterator for CachedLC3SparseIter<'_> { + fn next_back(&mut self) -> Option { + loop { + let (loc, value) = self.iter.next_back()?; + if value != 0 { + return Some(LC3MemLoc { + loc: loc as u16, + value, + }); + } + } + } +} + +impl FusedIterator for CachedLC3SparseIter<'_> {} diff --git a/src/executors/consolidated.rs b/src/executors/consolidated.rs new file mode 100644 index 0000000..019bb8a --- /dev/null +++ b/src/executors/consolidated.rs @@ -0,0 +1,253 @@ +use std::{iter::FusedIterator, pin::Pin}; + +use pinned_init::{pin_data, pin_init, InPlaceInit}; + +use crate::{ + defs::{ + LC3MemAddr, LC3Word, RegAddr, ADDR_SPACE_SIZE, MACHINE_CONTROL_REGISTER, NUM_REGS, + OS_SUPER_STACK, STACK_REG, SUPERVISOR_SP_INIT, + }, + instruction::{Instruction, InstructionEnum}, +}; + +use super::{LC3MemLoc, StepFailure, LC3}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum Condition { + None, + Negative, + Zero, + Positive, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[pin_data] +pub struct ConsolidatedLC3 { + mem: [LC3Word; ADDR_SPACE_SIZE], + condition: Condition, + priority: u8, + privileged: bool, + regs: [LC3Word; NUM_REGS], + supervisor_sp: LC3Word, + pc: LC3MemAddr, + halted: bool, + mpr_disabled: bool, +} + +impl ConsolidatedLC3 { + pub fn new() -> Self { + Self { + mem: [0; ADDR_SPACE_SIZE], + condition: Condition::None, + priority: 0, + privileged: true, + supervisor_sp: SUPERVISOR_SP_INIT, + regs: [0; NUM_REGS], + pc: OS_SUPER_STACK, + halted: false, + mpr_disabled: false, + } + } + + /// Constructs this as a box without allocating the full size on stack. + pub fn boxed() -> Box { + let this = Box::pin_init(pin_init!(Self { + // This is the field that might exceed stack capacity + mem <- pinned_init::zeroed(), + condition: Condition::None, + priority: 0, + privileged: true, + supervisor_sp: SUPERVISOR_SP_INIT, + regs: [0; NUM_REGS], + pc: OS_SUPER_STACK, + halted: false, + mpr_disabled: false, + })); + Pin::into_inner(this.expect("Infalliable")) + } +} + +impl Default for ConsolidatedLC3 { + fn default() -> Self { + Self::new() + } +} + +impl LC3 for ConsolidatedLC3 { + fn pc(&self) -> LC3MemAddr { + self.pc + } + fn set_pc(&mut self, pc: LC3MemAddr) { + self.pc = pc + } + + fn reg(&self, addr: RegAddr) -> LC3Word { + let reg_addr = usize::from(addr); + + if self.privileged && reg_addr == STACK_REG.into() { + self.supervisor_sp + } else { + self.regs[reg_addr] + } + } + fn set_reg(&mut self, addr: RegAddr, value: LC3Word) { + let reg_addr = usize::from(addr); + + if self.privileged && reg_addr == STACK_REG.into() { + self.supervisor_sp = value + } else { + self.regs[reg_addr] = value + } + } + + fn mem(&self, addr: LC3MemAddr) -> LC3Word { + self.mem[addr as usize] + } + fn set_mem(&mut self, addr: LC3MemAddr, value: LC3Word) { + self.mem[addr as usize] = value; + if addr == MACHINE_CONTROL_REGISTER { + self.mpr_disabled = (value & (1 << 15)) == 0; + } + } + + fn priority(&self) -> u8 { + self.priority + } + fn set_priority(&mut self, priority: u8) { + if priority < 8 { + self.priority = priority + } + } + + fn privileged(&self) -> bool { + self.privileged + } + fn set_privileged(&mut self, priviledged: bool) { + self.privileged = priviledged + } + + fn positive_cond(&self) -> bool { + self.condition == Condition::Positive + } + fn zero_cond(&self) -> bool { + self.condition == Condition::Zero + } + fn negative_cond(&self) -> bool { + self.condition == Condition::Negative + } + + fn flag_positive(&mut self) { + self.condition = Condition::Positive; + } + fn flag_zero(&mut self) { + self.condition = Condition::Zero; + } + fn flag_negative(&mut self) { + self.condition = Condition::Negative; + } + + fn clear_flags(&mut self) { + self.condition = Condition::None; + } + + type FullIter<'a> = std::iter::Cloned>; + fn iter(&self) -> Self::FullIter<'_> { + self.mem.iter().cloned() + } + + type SparseIter<'a> = CoreLC3SparseIter<'a>; + fn sparse_iter(&self) -> Self::SparseIter<'_> { + CoreLC3SparseIter::new(self.iter()) + } + + fn halt(&mut self) { + self.halted = true; + } + + fn unhalt(&mut self) { + self.halted = false; + } + + fn is_halted(&self) -> bool { + self.halted + } + + /// Executes the current instruction. + /// + /// Does not handle memory map updates. + fn step(&mut self) -> Result<(), StepFailure> { + if self.halted { + Err(StepFailure::Halted) + } else if self.mpr_disabled { + Err(StepFailure::ClockDisabled) + } else { + let inst = self + .cur_inst() + .ok_or(StepFailure::InvalidInstruction(self.mem(self.pc())))?; + + inst.execute(self)?; + + if !matches!(inst, InstructionEnum::IBranch(_)) + && !matches!(inst, InstructionEnum::IJump(_)) + { + self.pc += 1; + } + + Ok(()) + } + } + + fn populate>(&mut self, start: LC3MemAddr, words: I) { + let mem_iter_mut = self.mem[start.into()..].iter_mut(); + for (word, loc) in words.into_iter().zip(mem_iter_mut) { + *loc = word; + } + } +} + +/// Sparse iterator for [`CoreLC3`]. +/// +/// Skips all zero elements. +pub struct CoreLC3SparseIter<'a> { + iter: std::iter::Enumerate<::FullIter<'a>>, +} + +impl<'a> CoreLC3SparseIter<'a> { + fn new(iter: ::FullIter<'a>) -> Self { + Self { + iter: iter.enumerate(), + } + } +} + +impl Iterator for CoreLC3SparseIter<'_> { + type Item = LC3MemLoc; + + fn next(&mut self) -> Option { + loop { + let (loc, value) = self.iter.next()?; + if value != 0 { + return Some(LC3MemLoc { + loc: loc as u16, + value, + }); + } + } + } +} + +impl DoubleEndedIterator for CoreLC3SparseIter<'_> { + fn next_back(&mut self) -> Option { + loop { + let (loc, value) = self.iter.next_back()?; + if value != 0 { + return Some(LC3MemLoc { + loc: loc as u16, + value, + }); + } + } + } +} + +impl FusedIterator for CoreLC3SparseIter<'_> {} diff --git a/src/executors/core.rs b/src/executors/core.rs index aff91b2..e4084bd 100644 --- a/src/executors/core.rs +++ b/src/executors/core.rs @@ -206,7 +206,7 @@ impl LC3 for CoreLC3 { /// Sparse iterator for [`CoreLC3`]. /// -/// Skips all nonzero elements. +/// Skips all zero elements. pub struct CoreLC3SparseIter<'a> { iter: std::iter::Enumerate<::FullIter<'a>>, } diff --git a/src/executors/instruction_mem.rs b/src/executors/instruction_mem.rs new file mode 100644 index 0000000..eaeac83 --- /dev/null +++ b/src/executors/instruction_mem.rs @@ -0,0 +1,284 @@ +use std::{iter::FusedIterator, pin::Pin}; + +use pinned_init::{init, pin_data, pin_init, InPlaceInit}; + +use crate::{ + defs::{ + LC3MemAddr, LC3Word, RegAddr, ADDR_SPACE_SIZE, MACHINE_CONTROL_REGISTER, NUM_REGS, + OS_SUPER_STACK, STACK_REG, SUPERVISOR_SP_INIT, + }, + instruction::{Instruction, InstructionEnum}, +}; + +use super::{LC3MemLoc, StepFailure, LC3}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum Condition { + None, + Negative, + Zero, + Positive, +} + +/// A maybe-known instruction line +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[pin_data] +#[repr(transparent)] +struct InstLine { + inner: Option, +} + +impl From for Option { + fn from(value: InstLine) -> Self { + value.inner + } +} + +impl From> for InstLine { + fn from(value: Option) -> Self { + Self { inner: value } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[pin_data] +pub struct InstMemLC3 { + mem: [LC3Word; ADDR_SPACE_SIZE], + insts: [InstLine; ADDR_SPACE_SIZE], + condition: Condition, + priority: u8, + privileged: bool, + regs: [LC3Word; NUM_REGS], + supervisor_sp: LC3Word, + pc: LC3MemAddr, + halted: bool, + mpr_disabled: bool, +} + +impl InstMemLC3 { + pub fn new() -> Self { + Self { + mem: [0; ADDR_SPACE_SIZE], + insts: [None.into(); ADDR_SPACE_SIZE], + condition: Condition::None, + priority: 0, + privileged: true, + supervisor_sp: SUPERVISOR_SP_INIT, + regs: [0; NUM_REGS], + pc: OS_SUPER_STACK, + halted: false, + mpr_disabled: false, + } + } + + /// Constructs this as a box without allocating the full size on stack. + pub fn boxed() -> Box { + let this = Box::pin_init(pin_init!(Self { + // This is the field that might exceed stack capacity + mem <- pinned_init::zeroed(), + insts <- pinned_init::init_array_from_fn(|_| init!(InstLine { + inner: None, + })), + condition: Condition::None, + priority: 0, + privileged: true, + supervisor_sp: SUPERVISOR_SP_INIT, + regs: [0; NUM_REGS], + pc: OS_SUPER_STACK, + halted: false, + mpr_disabled: false, + })); + Pin::into_inner(this.expect("Infalliable")) + } +} + +impl Default for InstMemLC3 { + fn default() -> Self { + Self::new() + } +} + +impl LC3 for InstMemLC3 { + fn pc(&self) -> LC3MemAddr { + self.pc + } + fn set_pc(&mut self, pc: LC3MemAddr) { + self.pc = pc + } + + fn reg(&self, addr: RegAddr) -> LC3Word { + let reg_addr = usize::from(addr); + + if self.privileged && reg_addr == STACK_REG.into() { + self.supervisor_sp + } else { + self.regs[reg_addr] + } + } + fn set_reg(&mut self, addr: RegAddr, value: LC3Word) { + let reg_addr = usize::from(addr); + + if self.privileged && reg_addr == STACK_REG.into() { + self.supervisor_sp = value + } else { + self.regs[reg_addr] = value + } + } + + fn mem(&self, addr: LC3MemAddr) -> LC3Word { + self.mem[addr as usize] + } + fn set_mem(&mut self, addr: LC3MemAddr, value: LC3Word) { + self.mem[addr as usize] = value; + self.insts[addr as usize] = None.into(); + if addr == MACHINE_CONTROL_REGISTER { + self.mpr_disabled = (value & (1 << 15)) == 0; + } + } + + fn priority(&self) -> u8 { + self.priority + } + fn set_priority(&mut self, priority: u8) { + if priority < 8 { + self.priority = priority + } + } + + fn privileged(&self) -> bool { + self.privileged + } + fn set_privileged(&mut self, priviledged: bool) { + self.privileged = priviledged + } + + fn positive_cond(&self) -> bool { + self.condition == Condition::Positive + } + fn zero_cond(&self) -> bool { + self.condition == Condition::Zero + } + fn negative_cond(&self) -> bool { + self.condition == Condition::Negative + } + + fn flag_positive(&mut self) { + self.condition = Condition::Positive; + } + fn flag_zero(&mut self) { + self.condition = Condition::Zero; + } + fn flag_negative(&mut self) { + self.condition = Condition::Negative; + } + + fn clear_flags(&mut self) { + self.condition = Condition::None; + } + + type FullIter<'a> = std::iter::Cloned>; + fn iter(&self) -> Self::FullIter<'_> { + self.mem.iter().cloned() + } + + type SparseIter<'a> = CoreLC3SparseIter<'a>; + fn sparse_iter(&self) -> Self::SparseIter<'_> { + CoreLC3SparseIter::new(self.iter()) + } + + fn halt(&mut self) { + self.halted = true; + } + + fn unhalt(&mut self) { + self.halted = false; + } + + fn is_halted(&self) -> bool { + self.halted + } + + /// Executes the current instruction. + /// + /// Does not handle memory map updates. + fn step(&mut self) -> Result<(), StepFailure> { + if self.halted { + Err(StepFailure::Halted) + } else if self.mpr_disabled { + Err(StepFailure::ClockDisabled) + } else { + let inst = self.insts[self.pc() as usize] + .inner + .or_else(|| { + let inst = self.cur_inst(); + self.insts[self.pc() as usize] = inst.into(); + inst + }) + .ok_or(StepFailure::InvalidInstruction(self.mem(self.pc())))?; + + inst.execute(self)?; + + if !matches!(inst, InstructionEnum::IBranch(_)) + && !matches!(inst, InstructionEnum::IJump(_)) + { + self.pc += 1; + } + + Ok(()) + } + } + + fn populate>(&mut self, start: LC3MemAddr, words: I) { + let mem_iter_mut = self.mem[start.into()..].iter_mut(); + for (word, loc) in words.into_iter().zip(mem_iter_mut) { + *loc = word; + } + } +} + +/// Sparse iterator for [`CoreLC3`]. +/// +/// Skips all zero elements. +pub struct CoreLC3SparseIter<'a> { + iter: std::iter::Enumerate<::FullIter<'a>>, +} + +impl<'a> CoreLC3SparseIter<'a> { + fn new(iter: ::FullIter<'a>) -> Self { + Self { + iter: iter.enumerate(), + } + } +} + +impl Iterator for CoreLC3SparseIter<'_> { + type Item = LC3MemLoc; + + fn next(&mut self) -> Option { + loop { + let (loc, value) = self.iter.next()?; + if value != 0 { + return Some(LC3MemLoc { + loc: loc as u16, + value, + }); + } + } + } +} + +impl DoubleEndedIterator for CoreLC3SparseIter<'_> { + fn next_back(&mut self) -> Option { + loop { + let (loc, value) = self.iter.next_back()?; + if value != 0 { + return Some(LC3MemLoc { + loc: loc as u16, + value, + }); + } + } + } +} + +impl FusedIterator for CoreLC3SparseIter<'_> {} diff --git a/src/executors/mod.rs b/src/executors/mod.rs index 683087d..7830f3d 100644 --- a/src/executors/mod.rs +++ b/src/executors/mod.rs @@ -10,6 +10,15 @@ use crate::{ pub mod core; +#[cfg(feature = "consolidated")] +pub mod consolidated; + +#[cfg(feature = "cached_resolve")] +pub mod cached_resolve; + +#[cfg(feature = "instruction_mem")] +pub mod instruction_mem; + /// LC3 Memory Address. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct LC3MemLoc { @@ -200,6 +209,172 @@ pub fn populate_from_bin(processor: &mut P, bin: R) { } } +impl LC3 for &mut T { + fn pc(&self) -> LC3MemAddr { + T::pc(self) + } + fn set_pc(&mut self, pc: LC3MemAddr) { + T::set_pc(self, pc); + } + fn reg(&self, addr: RegAddr) -> LC3Word { + T::reg(self, addr) + } + fn set_reg(&mut self, addr: RegAddr, value: LC3Word) { + T::set_reg(self, addr, value); + } + fn mem(&self, addr: LC3MemAddr) -> LC3Word { + T::mem(self, addr) + } + fn set_mem(&mut self, addr: LC3MemAddr, value: LC3Word) { + T::set_mem(self, addr, value); + } + fn priority(&self) -> u8 { + T::priority(self) + } + fn set_priority(&mut self, priority: u8) { + T::set_priority(self, priority); + } + fn privileged(&self) -> bool { + T::privileged(self) + } + fn set_privileged(&mut self, priviledged: bool) { + T::set_privileged(self, priviledged); + } + fn positive_cond(&self) -> bool { + T::positive_cond(self) + } + fn zero_cond(&self) -> bool { + T::zero_cond(self) + } + fn negative_cond(&self) -> bool { + T::negative_cond(self) + } + fn flag_positive(&mut self) { + T::flag_positive(self); + } + fn flag_zero(&mut self) { + T::flag_zero(self); + } + fn flag_negative(&mut self) { + T::flag_negative(self); + } + fn clear_flags(&mut self) { + T::clear_flags(self); + } + type FullIter<'a> + = T::FullIter<'a> + where + Self: 'a; + fn iter(&self) -> Self::FullIter<'_> { + T::iter(self) + } + type SparseIter<'a> + = T::SparseIter<'a> + where + Self: 'a; + fn sparse_iter(&self) -> Self::SparseIter<'_> { + T::sparse_iter(self) + } + fn halt(&mut self) { + T::halt(self); + } + fn unhalt(&mut self) { + T::unhalt(self); + } + fn is_halted(&self) -> bool { + T::is_halted(self) + } + fn step(&mut self) -> Result<(), StepFailure> { + T::step(self) + } + fn populate>(&mut self, start: LC3MemAddr, words: I) { + T::populate(self, start, words); + } +} + +impl LC3 for Box { + fn pc(&self) -> LC3MemAddr { + T::pc(self) + } + fn set_pc(&mut self, pc: LC3MemAddr) { + T::set_pc(self, pc); + } + fn reg(&self, addr: RegAddr) -> LC3Word { + T::reg(self, addr) + } + fn set_reg(&mut self, addr: RegAddr, value: LC3Word) { + T::set_reg(self, addr, value); + } + fn mem(&self, addr: LC3MemAddr) -> LC3Word { + T::mem(self, addr) + } + fn set_mem(&mut self, addr: LC3MemAddr, value: LC3Word) { + T::set_mem(self, addr, value); + } + fn priority(&self) -> u8 { + T::priority(self) + } + fn set_priority(&mut self, priority: u8) { + T::set_priority(self, priority); + } + fn privileged(&self) -> bool { + T::privileged(self) + } + fn set_privileged(&mut self, priviledged: bool) { + T::set_privileged(self, priviledged); + } + fn positive_cond(&self) -> bool { + T::positive_cond(self) + } + fn zero_cond(&self) -> bool { + T::zero_cond(self) + } + fn negative_cond(&self) -> bool { + T::negative_cond(self) + } + fn flag_positive(&mut self) { + T::flag_positive(self); + } + fn flag_zero(&mut self) { + T::flag_zero(self); + } + fn flag_negative(&mut self) { + T::flag_negative(self); + } + fn clear_flags(&mut self) { + T::clear_flags(self); + } + type FullIter<'a> + = T::FullIter<'a> + where + Self: 'a; + fn iter(&self) -> Self::FullIter<'_> { + T::iter(self) + } + type SparseIter<'a> + = T::SparseIter<'a> + where + Self: 'a; + fn sparse_iter(&self) -> Self::SparseIter<'_> { + T::sparse_iter(self) + } + fn halt(&mut self) { + T::halt(self); + } + fn unhalt(&mut self) { + T::unhalt(self); + } + fn is_halted(&self) -> bool { + T::is_halted(self) + } + fn step(&mut self) -> Result<(), StepFailure> { + T::step(self) + } + fn populate>(&mut self, start: LC3MemAddr, words: I) { + T::populate(self, start, words); + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/instruction/args.rs b/src/instruction/args.rs index 490ce29..aec2a71 100644 --- a/src/instruction/args.rs +++ b/src/instruction/args.rs @@ -1,51 +1,51 @@ use crate::defs::{LC3Word, RegAddr, SignedLC3Word}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InstrRegSignedImm { pub dest_reg: RegAddr, pub src_reg: RegAddr, pub imm: SignedLC3Word, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InstrRegImm { pub dest_reg: RegAddr, pub src_reg: RegAddr, pub imm: LC3Word, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InstrRegReg { pub dest_reg: RegAddr, pub src_reg_1: RegAddr, pub src_reg_2: RegAddr, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InstrRegOnly { pub dest_reg: RegAddr, pub src_reg: RegAddr, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InstrOffset6 { pub target_reg: RegAddr, pub base_reg: RegAddr, pub offset: SignedLC3Word, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InstrPCOffset9 { pub target_reg: RegAddr, pub pc_offset: SignedLC3Word, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InstrPCOffset11 { pub pc_offset: SignedLC3Word, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ConditionCodes { pub positive: bool, pub negative: bool, diff --git a/src/instruction/iadd.rs b/src/instruction/iadd.rs index 7219c38..103c0bb 100644 --- a/src/instruction/iadd.rs +++ b/src/instruction/iadd.rs @@ -8,7 +8,7 @@ use crate::{ util::{apply_offset, shift_to_signed, shift_to_unsigned}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IAdd { Reg(InstrRegReg), Imm(InstrRegSignedImm), diff --git a/src/instruction/iand.rs b/src/instruction/iand.rs index 0852e6e..9ec8cd3 100644 --- a/src/instruction/iand.rs +++ b/src/instruction/iand.rs @@ -7,7 +7,7 @@ use crate::{ }, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IAnd { Reg(InstrRegReg), Imm(InstrRegImm), diff --git a/src/instruction/ibranch.rs b/src/instruction/ibranch.rs index cf76e84..6fdc20c 100644 --- a/src/instruction/ibranch.rs +++ b/src/instruction/ibranch.rs @@ -7,7 +7,7 @@ use crate::{ util::{apply_offset, shift_to_signed, shift_to_unsigned}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct IBranch { //while br roughly follows the bit assignment of PCoffset9, //this is treated as a special case for ease of implementation diff --git a/src/instruction/ijump.rs b/src/instruction/ijump.rs index fa6057d..89f6f01 100644 --- a/src/instruction/ijump.rs +++ b/src/instruction/ijump.rs @@ -6,7 +6,7 @@ use crate::{ use super::InsufficientPerms; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IJump { Instr(RegAddr), PrivClear(RegAddr), // Clears privilege bit diff --git a/src/instruction/ijumpsr.rs b/src/instruction/ijumpsr.rs index 1305721..ee3a0d4 100644 --- a/src/instruction/ijumpsr.rs +++ b/src/instruction/ijumpsr.rs @@ -7,7 +7,7 @@ use crate::{ util::{apply_offset, shift_to_signed, shift_to_unsigned}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IJumpSubRoutine { Offset(InstrPCOffset11), //JSR Reg(RegAddr), //JSRR treated as an offset6 with an offset of 0 diff --git a/src/instruction/iload.rs b/src/instruction/iload.rs index 8d51153..f63aef8 100644 --- a/src/instruction/iload.rs +++ b/src/instruction/iload.rs @@ -8,7 +8,7 @@ use crate::{ util::{apply_offset, shift_to_signed, shift_to_unsigned}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ILoad { Std(InstrPCOffset9), //LD Indirect(InstrPCOffset9), //LDI diff --git a/src/instruction/inot.rs b/src/instruction/inot.rs index f70176a..195dad2 100644 --- a/src/instruction/inot.rs +++ b/src/instruction/inot.rs @@ -6,7 +6,7 @@ use crate::{ }, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct INot(pub InstrRegOnly); pub const NOT_OPCODE: u8 = 0b1001; diff --git a/src/instruction/istore.rs b/src/instruction/istore.rs index cf2abbc..3211d5c 100644 --- a/src/instruction/istore.rs +++ b/src/instruction/istore.rs @@ -8,7 +8,7 @@ use crate::{ util::{apply_offset, shift_to_signed, shift_to_unsigned}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IStore { Std(InstrPCOffset9), //ST Indirect(InstrPCOffset9), //STI diff --git a/src/instruction/mod.rs b/src/instruction/mod.rs index 512cfef..57d1b86 100644 --- a/src/instruction/mod.rs +++ b/src/instruction/mod.rs @@ -60,7 +60,7 @@ pub trait Instruction: Into { /// /// If this does not parse and execute, there is no valid LC-3 instruction /// that could parse and execute the given word. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InstructionEnum { IAdd(IAdd), IAnd(IAnd), diff --git a/src/instruction/trap.rs b/src/instruction/trap.rs index dd5208b..dbcdfa8 100644 --- a/src/instruction/trap.rs +++ b/src/instruction/trap.rs @@ -4,7 +4,7 @@ use crate::{ instruction::{get_bits, get_opcode, Instruction, InstructionErr}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Trap { Getc, // 0x20 Out, // 0x21 diff --git a/test_data/custom/loop.asm b/test_data/custom/loop.asm new file mode 100644 index 0000000..7c63653 --- /dev/null +++ b/test_data/custom/loop.asm @@ -0,0 +1,5 @@ + .ORIG x3000 + AND R0, R0, #0 +LOOP ADD R0, R0, #1 + BRnp LOOP + HALT diff --git a/test_data/custom/loop.obj b/test_data/custom/loop.obj new file mode 100644 index 0000000..3ccb4bc Binary files /dev/null and b/test_data/custom/loop.obj differ diff --git a/tests/common/penn_sim.rs b/tests/common/penn_sim.rs index 91e4b13..7565ab8 100644 --- a/tests/common/penn_sim.rs +++ b/tests/common/penn_sim.rs @@ -4,6 +4,7 @@ use std::{ fs::{self, create_dir_all, read_to_string, File}, hash::Hash, io::{BufRead, BufReader, Read, Write}, + ops::Deref, path::{Path, PathBuf}, process::Command, sync::LazyLock, @@ -15,8 +16,25 @@ use lc3sim_project::{ executors::{populate_from_bin, LC3}, }; use once_map::OnceMap; +use stable_deref_trait::StableDeref; use uuid::Uuid; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MemDump { + pub output_lines: String, + pub memory: [LC3Word; DEV_REG_ADDR as usize], +} + +impl Deref for MemDump { + type Target = Self; + fn deref(&self) -> &Self::Target { + self + } +} + +// Consists of two [`StableDeref`] types. +unsafe impl StableDeref for MemDump {} + /// Set of input and result from PennSim. /// /// Specializes comparisons on the assumption that PennSim assembly output @@ -125,10 +143,7 @@ impl CompileSet { } /// Get output lines and final memory after a pennsim run. - pub fn post_process_mem_dump>( - &self, - input: S, - ) -> (String, [LC3Word; DEV_REG_ADDR as usize]) { + pub fn post_process_mem_dump>(&self, input: S) -> MemDump { // Create the temporary directory let temp_dir = temp_dir().join(Uuid::new_v4().to_string()); create_dir_all(&temp_dir).unwrap(); @@ -201,7 +216,10 @@ impl CompileSet { // Clean up the temporary directory fs::remove_dir_all(&temp_dir).unwrap(); - (cmd_output, out) + MemDump { + output_lines: cmd_output, + memory: out, + } } } @@ -219,6 +237,28 @@ pub fn get_compiled>(path: P, data: &'static str) -> &'static Com }) } +/// Deliberately leaks values for a static lifetime +static OUTPUT: LazyLock> = LazyLock::new(OnceMap::new); + +/// Get this file after processing through PennSim. +/// +/// Only compiles a given file through PennSim once, and never drops it. +/// +/// See [`static_output`] to only provide a filename. +pub fn get_output, S: AsRef>( + path: P, + data: &'static str, + input: S, +) -> &'static MemDump { + OUTPUT.insert( + (path.as_ref().to_path_buf(), input.as_ref().to_string()), + |_| { + let compiled = get_compiled(path, data); + compiled.post_process_mem_dump(input) + }, + ) +} + #[macro_export] macro_rules! static_compiled { ( $x: expr ) => { @@ -226,6 +266,13 @@ macro_rules! static_compiled { }; } +#[macro_export] +macro_rules! static_output { + ( $x: expr, $input: expr ) => { + $crate::common::penn_sim::get_output($x, include_str!($x), $input) + }; +} + static OS: LazyLock<&'static CompileSet> = LazyLock::new(|| static_compiled!("../../penn_sim/lc3os.asm")); diff --git a/tests/core_unca.rs b/tests/core_unca.rs index 15485e4..02d2bb0 100644 --- a/tests/core_unca.rs +++ b/tests/core_unca.rs @@ -5,7 +5,13 @@ use paste::paste; mod exec { use super::*; - use common::penn_sim::load_os; + use common::penn_sim::{load_os, MemDump}; + #[cfg(feature = "cached_resolve")] + use lc3sim_project::executors::cached_resolve::CachedResolveLC3; + #[cfg(feature = "consolidated")] + use lc3sim_project::executors::consolidated::ConsolidatedLC3; + #[cfg(feature = "instruction_mem")] + use lc3sim_project::executors::instruction_mem::InstMemLC3; use lc3sim_project::{ defs::{LC3MemAddr, USER_SPACE}, executors::{core::CoreLC3, populate_from_bin, LC3}, @@ -16,18 +22,18 @@ mod exec { const EXEC_LIMIT: u64 = 100_000; macro_rules! cmp_test { - ( $name:ident, $path:literal ) => { + ( $name:ident, $path:literal, $executor:expr ) => { paste! { #[test] fn [<$name _exec>]() { - let mult_10 = static_compiled!($path); + let program_under_test = static_compiled!($path); - let mut lc3 = CoreLC3::new(); + let mut lc3 = $executor; load_os(&mut lc3); - populate_from_bin(&mut lc3, &**mult_10.obj()); + populate_from_bin(&mut lc3, &**program_under_test.obj()); // Confirm the memory loaded correctly - for (offset, word) in mult_10.obj_words().skip(1).enumerate() { + for (offset, word) in program_under_test.obj_words().skip(1).enumerate() { let pos = (offset as LC3MemAddr) + USER_SPACE; assert_eq!(lc3.mem(pos), word); } @@ -36,19 +42,33 @@ mod exec { assert!(lim_step_continue(&mut FailIO, &mut lc3, EXEC_LIMIT).unwrap()); // Confirm full memory match with penn-sim - let (output, mem_lines) = mult_10.post_process_mem_dump(""); - assert_eq!(output, ""); - for (lc3_mem, penn_mem) in lc3.iter().zip(mem_lines) { - assert_eq!(lc3_mem, penn_mem) + let MemDump { output_lines, memory } = static_output!($path, ""); + assert_eq!(output_lines, ""); + for (lc3_mem, penn_mem) in lc3.iter().zip(memory) { + assert_eq!(&lc3_mem, penn_mem) } } } }; } - cmp_test!(mult_10, "../test_data/unca/split_apart/mult_10.asm"); - cmp_test!(rev_string, "../test_data/unca/split_apart/rev_string.asm"); - cmp_test!(char_count, "../test_data/unca/split_apart/char_count.asm"); - cmp_test!(r1_pop, "../test_data/unca/split_apart/r1_pop.asm"); - cmp_test!(xor, "../test_data/unca/split_apart/xor.asm"); + macro_rules! cmp_test_all { + ( $name:ident, $path:literal ) => { + paste! { + cmp_test!( [<$name _core>], $path, CoreLC3::new() ); + #[cfg(feature = "consolidated")] + cmp_test!( [<$name _consolidated>], $path, ConsolidatedLC3::boxed() ); + #[cfg(feature = "cached_resolve")] + cmp_test!( [<$name _cached_resolve>], $path, CachedResolveLC3::boxed() ); + #[cfg(feature = "instruction_mem")] + cmp_test!( [<$name _instruction_mem>], $path, InstMemLC3::boxed() ); + } + }; + } + + cmp_test_all!(mult_10, "../test_data/unca/split_apart/mult_10.asm"); + cmp_test_all!(rev_string, "../test_data/unca/split_apart/rev_string.asm"); + cmp_test_all!(char_count, "../test_data/unca/split_apart/char_count.asm"); + cmp_test_all!(r1_pop, "../test_data/unca/split_apart/r1_pop.asm"); + cmp_test_all!(xor, "../test_data/unca/split_apart/xor.asm"); }