use std::collections::{BTreeSet, HashMap, HashSet};
use tracing::{debug, instrument};
#[macro_use]
mod macros;
mod dispatch;
mod display;
mod exceptions;
mod float;
mod host;
mod memory;
mod registers;
mod stack;
mod trap;
pub use display::ValueFormat;
pub use host::{Host, StdHost};
use registers::{RA_D, RA_I, RA_O, RA_ROUND_SHIFT, RA_U, RA_V, RA_W, RA_X, RA_Z, SAVE_SPECIALS};
pub use registers::{RA_MAX, SpecialReg};
use stack::PopFrame;
pub(crate) use stack::STACK_SEGMENT_START;
use trap::FileHandle;
pub use trap::TrapCode;
pub struct MMix {
general_regs: [u64; 256],
special_regs: [u64; 32],
memory: HashMap<u64, u8>,
pc: u64,
file_handles: HashMap<u8, FileHandle>,
exit_code: u64,
host: Box<dyn Host>,
debug_strings: Vec<Vec<u8>>,
journal_enabled: bool,
journal: HashSet<u64>,
loaded: BTreeSet<u64>,
}
impl Default for MMix {
fn default() -> Self {
Self::new()
}
}
impl MMix {
pub fn new() -> Self {
Self::with_host(StdHost)
}
pub fn with_host<H: Host + 'static>(host: H) -> Self {
Self::blank(Box::new(host))
}
fn blank(host: Box<dyn Host>) -> Self {
let mut mmix = Self {
general_regs: [0; 256],
special_regs: [0; 32],
memory: HashMap::new(),
pc: 0,
file_handles: HashMap::new(),
exit_code: 0,
host,
debug_strings: Vec::new(),
journal_enabled: false,
journal: HashSet::new(),
loaded: BTreeSet::new(),
};
mmix.initialize();
mmix
}
pub fn reset(&mut self) {
let host = std::mem::replace(&mut self.host, Box::new(StdHost));
let journal_enabled = self.journal_enabled;
*self = Self::blank(host);
self.journal_enabled = journal_enabled;
}
pub fn host_mut(&mut self) -> &mut dyn Host {
&mut *self.host
}
pub fn into_host(self) -> Box<dyn Host> {
self.host
}
fn initialize(&mut self) {
self.set_special(SpecialReg::RN, 2009);
self.set_special(SpecialReg::RG, 32);
self.set_special(SpecialReg::RL, 0);
self.set_special(SpecialReg::RO, STACK_SEGMENT_START);
self.set_special(SpecialReg::RS, STACK_SEGMENT_START);
self.set_special(SpecialReg::RK, 0xFFFFFFFFFFFFFFFF);
self.set_special(SpecialReg::RT, 0x8000000500000000);
self.set_special(SpecialReg::RTT, 0x8000000600000000);
self.set_special(SpecialReg::RV, 0x369C200400000000);
self.file_handles.insert(
0,
FileHandle {
file: None,
read: true,
write: false,
seek: false,
read_write: false,
},
);
for fd in [1u8, 2] {
self.file_handles.insert(
fd,
FileHandle {
file: None,
read: false,
write: true,
seek: false,
read_write: false,
},
);
}
}
pub fn set_debug_strings(&mut self, strings: Vec<Vec<u8>>) {
self.debug_strings = strings;
}
pub fn get_register(&self, reg: u8) -> u64 {
self.general_regs[reg as usize]
}
pub fn set_register(&mut self, reg: u8, value: u64) {
self.claim_local(reg);
self.general_regs[reg as usize] = value;
}
fn claim_local(&mut self, reg: u8) {
let rg = self.special_regs[SpecialReg::RG as usize];
if (reg as u64) >= rg {
return;
}
let rl = self.special_regs[SpecialReg::RL as usize];
if (reg as u64) < rl {
return;
}
let rl = rl as u8;
for marginal in rl..=reg {
self.general_regs[marginal as usize] = 0;
}
self.special_regs[SpecialReg::RL as usize] = (reg as u64) + 1;
}
fn writes_general_register_x(op_byte: u8) -> bool {
matches!(
op_byte,
0x01..=0x3F | 0x60..=0x99 | 0x9E..=0x9F | 0xBE..=0xEF | 0xF2..=0xF5 | 0xFA | 0xFE
)
}
pub fn get_special(&self, reg: SpecialReg) -> u64 {
self.special_regs[reg as usize]
}
pub fn set_special(&mut self, reg: SpecialReg, value: u64) {
self.special_regs[reg as usize] = value;
}
fn reject(&mut self, message: &str) -> bool {
self.host.diagnostic(message);
self.exit_code = 1;
false
}
pub fn get_pc(&self) -> u64 {
self.pc
}
pub fn set_pc(&mut self, pc: u64) {
self.pc = pc;
}
pub fn advance_pc(&mut self) {
self.pc = self.pc.wrapping_add(4);
}
pub fn get_exit_code(&self) -> u64 {
self.exit_code
}
#[instrument(skip(self))]
pub fn run(&mut self) -> usize {
self.run_bounded(usize::MAX).0
}
#[instrument(skip(self))]
pub fn run_bounded(&mut self, budget: usize) -> (usize, Stop) {
debug!("Starting MMIX execution");
let mut count = 0;
let stop = loop {
if count >= budget {
break Stop::BudgetExhausted;
}
if !self.execute_instruction() {
break Stop::Halted;
}
count += 1;
};
match stop {
Stop::Halted => self.host.diagnostic(&format!(
"Execution stopped at PC={:#018x} after {} instructions",
self.pc, count
)),
Stop::BudgetExhausted => self.host.diagnostic(&format!(
"Execution paused at PC={:#018x} after {} instructions (budget exhausted)",
self.pc, count
)),
Stop::Breakpoint(_) => unreachable!("run_bounded never returns Stop::Breakpoint"),
}
debug!(instruction_count = count, "Execution completed");
(count, stop)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Stop {
Halted,
BudgetExhausted,
Breakpoint(u64),
}
#[cfg(test)]
mod tests;