use crate::vm::instruction::{Condition, Instruction};
use std::io::{Read, Result as IOResult, Write};
pub mod instruction;
pub const KBSR: usize = 0xFE00;
pub const KBDR: usize = 0xFE02;
pub const DSR: usize = 0xFE04;
pub const DDR: usize = 0xFE06;
pub const MCR: usize = 0xFFFE;
const DEFAULT_MEM: [u16; 1 << 16] = {
let mut mem = [0; 1 << 16];
mem[DSR] = 0b1000_0000_0000_0000;
mem[MCR] = 0b1000_0000_0000_0000;
mem
};
const OPERATING_SYSTEM: &'static [u8] = include_bytes!("../../static/lc3os.obj");
struct CRLFtoLF<'a, T> {
reader: &'a mut T,
}
impl<'a, T> Read for CRLFtoLF<'a, T>
where
T: Read,
{
fn read(&mut self, buf: &mut [u8]) -> IOResult<usize> {
let size = self.reader.read(buf);
let newbuf = std::str::from_utf8(buf).unwrap().replace("\x0D", "");
buf[0..newbuf.len()].copy_from_slice(newbuf.as_bytes());
size.and(Ok(newbuf.len()))
}
}
#[derive(Clone)]
pub struct VM {
pub register: [i16; 8],
pub pc: u16,
pub ir: u16,
pub supervisor: bool,
pub priority: u8,
pub condition: Condition,
pub mem: [u16; 1 << 16],
}
impl VM {
pub fn new() -> VM {
let mut vm = VM {
pc: 0x3000,
..Default::default()
};
vm.load_u8(OPERATING_SYSTEM);
vm
}
pub fn load_u8(&mut self, program: &[u8]) {
let mut chunks = program.chunks(2);
let header = chunks.next().unwrap();
let orig = (((header[0] as u16) << 8) + header[1] as u16).into();
self.load_u16(
orig,
chunks
.map(|x| ((x[0] as u16) << 8) + x[1] as u16)
.collect::<Vec<_>>()
.as_ref(),
);
}
pub fn load_file(&mut self, path: &std::path::Path) -> std::io::Result<()> {
let program = std::fs::read(path)?;
self.load_u8(&program);
Ok(())
}
pub fn load_u16(&mut self, entry: usize, program: &[u16]) {
self.mem[entry..(entry + program.len())].copy_from_slice(program);
self.pc = entry as u16;
}
pub fn psr(&self) -> u16 {
((if self.supervisor { 0 } else { 1 << 15 })
+ ((self.priority as u16 & 0b111) << 8)
+ (if self.condition.n { 1 << 2 } else { 0 })
+ (if self.condition.z { 1 << 1 } else { 0 })
+ (if self.condition.p { 1 } else { 0 })) as u16
}
fn fetch(&mut self) {
self.ir = self.mem[self.pc as usize];
self.pc += 1;
}
fn update_condition(&mut self, reg: usize) {
self.condition.n = self.register[reg] < 0;
self.condition.z = self.register[reg] == 0;
self.condition.p = self.register[reg] > 0;
}
fn process_instruction<F, G>(&mut self, mut preld: Option<F>, mut postst: Option<G>)
where
F: FnMut(&mut Self, usize) -> (),
G: FnMut(&mut Self, usize) -> (),
{
macro_rules! reg {
($num:expr) => {
self.register[$num as usize]
};
}
macro_rules! zero_if_eq {
($addr:expr, $data:expr, $status:expr) => {
if $addr == $data {
self.mem[$status] = 0;
}
};
}
let instruction = Instruction::from_u16(self.ir);
#[cfg(feature = "instruction-trace")]
eprintln!("[DEBUG] {:?}", instruction);
match instruction {
Instruction::ADD { dst, src1, src2 } => {
reg!(dst) = reg!(src1).wrapping_add(reg!(src2));
self.update_condition(dst as usize);
}
Instruction::ADDi { dst, src, immd } => {
reg!(dst) = reg!(src).wrapping_add(immd);
self.update_condition(dst as usize);
}
Instruction::AND { dst, src1, src2 } => {
reg!(dst) = reg!(src1) & reg!(src2);
self.update_condition(dst as usize);
}
Instruction::ANDi { dst, src, immd } => {
reg!(dst) = reg!(src) & immd;
self.update_condition(dst as usize);
}
Instruction::BR { cond, offset } => {
if cond.satisfies(&self.condition) {
self.pc = self.pc.wrapping_add(offset as u16);
}
}
Instruction::JMP { base } => {
self.pc = reg!(base) as u16;
}
Instruction::JSR { offset } => {
reg!(7) = self.pc as i16;
self.pc = self.pc.wrapping_add(offset as u16);
}
Instruction::JSRR { base } => {
reg!(7) = self.pc as i16;
self.pc = reg!(base) as u16;
}
Instruction::LD { dst, offset } => {
let addr = (self.pc.wrapping_add(offset as u16)) as usize;
if let Some(ref mut func) = preld {
func(self, addr);
}
reg!(dst) = self.mem[addr] as i16;
self.update_condition(dst as usize);
zero_if_eq!(addr, KBDR, KBSR);
}
Instruction::LDI { dst, offset } => {
let addr = self.mem[(self.pc.wrapping_add(offset as u16)) as usize] as usize;
if let Some(ref mut func) = preld {
func(self, addr);
}
reg!(dst) = self.mem[addr] as i16;
self.update_condition(dst as usize);
zero_if_eq!(addr, KBDR, KBSR);
}
Instruction::LDR { dst, base, offset } => {
let addr = (reg!(base) as u16).wrapping_add(offset as u16) as usize;
if let Some(ref mut func) = preld {
func(self, addr);
}
reg!(dst) = self.mem[addr] as i16;
self.update_condition(dst as usize);
zero_if_eq!(addr, KBDR, KBSR);
}
Instruction::LEA { dst, offset } => {
reg!(dst) = (self.pc as i16).wrapping_add(offset);
self.update_condition(dst as usize);
}
Instruction::NOT { dst, src } => {
reg!(dst) = !reg!(src);
self.update_condition(dst as usize);
}
Instruction::RTI => unimplemented!(),
Instruction::ST { src, offset } => {
let addr = (self.pc.wrapping_add(offset as u16)) as usize;
self.mem[addr] = reg!(src) as u16;
zero_if_eq!(addr, DDR, DSR);
if let Some(ref mut func) = postst {
func(self, addr);
}
}
Instruction::STI { src, offset } => {
let addr = self.mem[(self.pc.wrapping_add(offset as u16)) as usize] as usize;
self.mem[addr] = reg!(src) as u16;
zero_if_eq!(addr, DDR, DSR);
if let Some(ref mut func) = postst {
func(self, addr);
}
}
Instruction::STR { src, base, offset } => {
let addr = (reg!(base) as u16).wrapping_add(offset as u16) as usize;
self.mem[addr] = reg!(src) as u16;
zero_if_eq!(addr, DDR, DSR);
if let Some(ref mut func) = postst {
func(self, addr);
}
}
Instruction::RESERVED => unimplemented!(),
Instruction::TRAP { vect } => {
reg!(7) = self.pc as i16;
self.pc = self.mem[(vect as u16) as usize];
}
}
}
pub fn step(&mut self) {
self.fetch();
self.process_instruction(None::<fn(&mut _, _)>, None::<fn(&mut _, _)>);
}
pub fn step_n(&mut self, n: usize) {
for _ in 0..n {
self.step();
}
}
pub fn step_with_hook<F, G>(&mut self, pre_load: F, post_store: G)
where
F: FnMut(&mut Self, usize),
G: FnMut(&mut Self, usize),
{
self.fetch();
self.process_instruction(Some(pre_load), Some(post_store));
}
pub fn run(&mut self) -> usize {
let mut steps = 0;
while self.mem[MCR] >> 15 > 0 {
self.step();
steps += 1;
}
steps
}
pub fn run_n(&mut self, n: usize) -> usize {
let mut steps = 0;
while self.mem[MCR] >> 15 > 0 && steps < n {
self.step();
steps += 1;
}
steps
}
pub fn run_with_io<'a, 'b, R, W>(&'a mut self, input: &'b mut R, output: &'b mut W) -> usize
where
R: Read + 'a,
W: Write + 'a,
{
self.run_n_with_io(0, input, output)
}
pub fn run_n_with_io<'a, 'b, R, W>(
&mut self,
n: usize,
input: &'b mut R,
output: &'b mut W,
) -> usize
where
R: Read + 'a,
W: Write + 'a,
{
let mut steps = 0;
#[cfg(all(target_os = "windows", not(feature = "disable-crlf-compat-windows")))]
let input = &mut CRLFtoLF { reader: input };
let mut in_stream = input.bytes();
while self.mem[MCR] >> 15 > 0 && (n == 0 || steps < n) {
#[cfg(feature = "register-trace")]
let pc = self.pc;
self.step_with_hook(
|this, addr| {
if addr == KBSR {
if let Some(result) = in_stream.next() {
this.mem[KBSR] |= 0b1000_0000_0000_0000;
this.mem[KBDR] = result.unwrap() as u16;
}
}
},
|this, addr| {
if addr == DDR {
output.write_all(&[this.mem[DDR] as u8]).unwrap();
this.mem[DSR] |= 0b1000_0000_0000_0000;
}
},
);
#[cfg(feature = "register-trace")]
eprintln!(
"[DEBUG] PC=0x{:04X}, IR=0x{:04X}, POST_REG={:04X?}",
pc, self.ir, self.register
);
steps += 1;
}
steps
}
}
impl Default for VM {
fn default() -> VM {
VM {
register: [0; 8],
pc: 0x0000,
ir: 0x0000,
supervisor: false,
priority: 0,
condition: Default::default(),
mem: DEFAULT_MEM,
}
}
}
#[test]
fn test_update_condition() {
let mut vm = VM::default();
vm.register[0] = 0xFF33u16 as i16;
vm.update_condition(0);
assert!(vm.condition.n);
vm.register[2] = 0x0F33i16;
vm.update_condition(2);
assert!(vm.condition.p);
vm.register[1] = 0x0000;
vm.update_condition(1);
assert!(vm.condition.z);
}
#[test]
fn test_step() {
let mut vm = VM::default();
vm.load_u16(0x3000, &[0x1260]);
vm.step();
assert!(vm.condition.z);
}
#[test]
fn test_step_n() {
let mut vm = VM::default();
vm.load_u16(0x3000, &[0x5020, 0x102E]);
vm.step_n(2);
assert_eq!(vm.register[0], 14);
}
#[test]
fn test_run() {
use std::io::{empty, sink};
let mut vm = VM::new();
vm.load_u16(
0x3000,
&[
0x5020, 0x5260, 0x1023, 0x0403, 0x1262, 0x103F, 0x0FFC, 0xB201, 0xF025, 0x3100,
],
);
vm.run_with_io(&mut empty(), &mut sink());
assert_eq!(vm.mem[0x3100], 6);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load() {
VM::new();
VM::default().load_u8(OPERATING_SYSTEM);
VM::default().load_u8(&[0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF]);
VM::default().load_u16(0x3000, &[0, 0, 0, 0]);
}
#[test]
#[should_panic]
fn test_load_panic() {
VM::default().load_u8(OPERATING_SYSTEM[1..].as_ref());
VM::default().load_u8(&[0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF]);
VM::default().load_u8(&[0xFF, 0xFF, 0xFF, 0xFE, 0xFF]);
VM::default().load_u16(0xFFFF, &[0, 0, 0, 0]);
}
}