lc3/
lib.rs

1use std::io::{empty, sink, Empty, Error as IOError, Sink};
2use std::path::Path;
3
4pub mod instruction;
5pub mod iostream_handler;
6
7#[cfg(all(target_os = "windows", not(feature = "disable-crlf-compat-windows")))]
8pub(crate) mod crlf_helper;
9
10pub use instruction::*;
11pub use iostream_handler::IOStreamHandler;
12use std::fmt::Debug;
13
14// These should be u16 in sense, but we define as usize for convenience.
15pub const KBSR: usize = 0xFE00;
16pub const KBDR: usize = 0xFE02;
17pub const DSR: usize = 0xFE04;
18pub const DDR: usize = 0xFE06;
19pub const MCR: usize = 0xFFFE;
20
21const DEFAULT_MEM: [u16; 1 << 16] = {
22    let mut mem = [0; 1 << 16];
23    mem[DSR] = 0b1000_0000_0000_0000;
24    mem[MCR] = 0b1000_0000_0000_0000;
25    mem
26};
27
28/*
29const OS_LOADED_MEM: [u16; 1 << 16] = {
30    // TODO
31    let mut mem = [0; 1 << 16];
32    mem[DSR] = 0b1000_0000_0000_0000;
33    mem[MCR] = 0b1000_0000_0000_0000;
34    mem
35};
36*/
37
38const OPERATING_SYSTEM: &[u8] = include_bytes!("../static/lc3os.obj");
39
40/// An instruction handler interface.
41pub trait InstructionHandler
42where
43    Self: Sized,
44{
45    /// Handler context.
46    type Context;
47    /// Err return type after processing instructions.
48    /// TODO: Convert `Err` into `std::ops::Try` once it is stabilized
49    type Err;
50
51    fn create_vm(initial_context: Self::Context) -> VM<Self>;
52
53    fn process_instruction(
54        vm_state: &mut VMState,
55        context: &mut Self::Context,
56        instruction: Instruction,
57    ) -> Result<(), Self::Err>;
58}
59
60/// Trivial InstructionHandler implementation, without I/O support
61pub struct TrivialHandler;
62
63impl InstructionHandler for TrivialHandler {
64    type Context = (Empty, Sink);
65    type Err = IOError;
66
67    fn create_vm(_: Self::Context) -> VM<Self> {
68        let mut vm = VM {
69            state: VMState::default(),
70            context: (empty(), sink()),
71        };
72        vm.load_u8(OPERATING_SYSTEM);
73        vm
74    }
75
76    fn process_instruction(
77        vm_state: &mut VMState,
78        context: &mut Self::Context,
79        instruction: Instruction,
80    ) -> Result<(), Self::Err> {
81        IOStreamHandler::process_instruction(vm_state, context, instruction)
82    }
83}
84
85/// A single LC-3 virtual machine.
86/// Each machines processes instructions in memory,
87/// handling appropriate I/O requests in device registers.
88///
89/// Note: memory-mapped I/O registers are not implemented as separated fields.
90/// The VM will treat device registers as normal words in the mem.
91///
92/// Note: Currently interrupts are not implemented.
93#[derive(Clone)]
94pub struct VM<H=TrivialHandler>
95where
96    H: InstructionHandler,
97{
98    pub state: VMState,
99    /// Instruction handler context
100    pub context: H::Context,
101}
102
103impl<H> VM<H>
104where
105    H: InstructionHandler,
106    H::Err: Debug,
107{
108    /// Returns a new, initialized LC-3 machine, with default operating system loaded.
109    pub fn new(initial_context: H::Context) -> VM<H> {
110        H::create_vm(initial_context)
111    }
112
113    /// Loads a program from raw u8 slice(generally, file contents).
114    /// # Panics
115    /// This function panics if `program` slice's length is not even or less than two(invalid header).
116    pub fn load_u8(&mut self, program: &[u8]) {
117        let mut chunks = program.chunks(2);
118        let header = chunks.next().unwrap();
119        let orig = (((u16::from(header[0])) << 8) + u16::from(header[1])).into();
120        self.load_u16(
121            orig,
122            chunks
123                .map(|x| ((u16::from(x[0])) << 8) + u16::from(x[1]))
124                .collect::<Vec<_>>()
125                .as_ref(),
126        );
127    }
128
129    /// Loads a program from given path of file.
130    /// # Panics
131    /// This function panics if file contents' length is not even or less than two(invalid header).
132    pub fn load_file<P: AsRef<Path>>(&mut self, path: P) -> std::io::Result<()> {
133        let program = std::fs::read(path)?;
134        self.load_u8(&program);
135        Ok(())
136    }
137
138    /// Loads a program from u16 slice from entry point given, and set pc to it.
139    /// All other load_* wrappers should convert given input program into u16 slice,
140    /// and pass it to this method at the last.
141    /// # Panics
142    /// This function panics if program size exceeds machine memory range.
143    /// (`entry + program.len() >= 65536`)
144    pub fn load_u16(&mut self, entry: usize, program: &[u16]) {
145        self.state.mem[entry..(entry + program.len())].copy_from_slice(program);
146        self.state.pc = entry as u16;
147    }
148
149    /// Executes next single instruction.
150    /// This function does not care about the clock enable bit.
151    pub fn step(&mut self) -> Result<(), H::Err> {
152        self.state.fetch();
153        let instruction = Instruction::from_u16(self.state.ir);
154        H::process_instruction(&mut self.state, &mut self.context, instruction)
155    }
156
157    /// Executes next n instructions.
158    /// This function does not care about the clock enable bit.
159    pub fn step_n(&mut self, n: usize) -> Result<(), H::Err> {
160        for _ in 0..n {
161            self.step()?;
162        }
163        Ok(())
164    }
165
166    /// Executes as much as instructions, while the clock enable bit is set to 1.
167    ///
168    /// Returns number of instructions executed if vm was executed without errors until termination,
169    /// or returns error immediately when error has occurred.
170    pub fn run(&mut self) -> Result<usize, H::Err> {
171        let mut steps = 0;
172        while self.state.mem[MCR] >> 15 > 0 {
173            self.step()?;
174            steps += 1;
175        }
176        Ok(steps)
177    }
178
179    /// Executes next n instructions at maximum, while
180    /// the clock enable bit is set to 1.
181    ///
182    /// Returns number of instructions executed if vm was executed without errors until termination,
183    /// or returns error immediately when error has occurred.
184    pub fn run_n(&mut self, n: usize) -> Result<usize, H::Err> {
185        let mut steps = 0;
186        while self.state.mem[MCR] >> 15 > 0 && steps < n {
187            #[cfg(feature = "register-trace")]
188            let pc = self.pc;
189
190            self.step()?;
191
192            #[cfg(feature = "register-trace")]
193            eprintln!(
194                "[DEBUG] PC=0x{:04X}, IR=0x{:04X}, POST_REG={:04X?}",
195                pc, self.state.ir, self.state.register
196            );
197
198            steps += 1;
199        }
200        Ok(steps)
201    }
202}
203
204impl Default for VM<TrivialHandler> {
205    fn default() -> Self {
206        Self {
207            state: Default::default(),
208            context: (empty(), sink()),
209        }
210    }
211}
212
213/// A VM state.
214#[derive(Clone)]
215pub struct VMState {
216    /// General purpose registers(R0~R7)
217    pub register: [i16; 8],
218    /// Program Counter
219    pub pc: u16,
220    /// Instruction Register
221    pub ir: u16,
222    /// Supervisor mode
223    pub supervisor: bool,
224    /// Priority Level(PL0~PL7)
225    pub priority: u8,
226    /// Condition codes
227    pub condition: Condition,
228    /// Computer memory
229    pub mem: [u16; 1 << 16],
230}
231
232impl VMState {
233    /// Calculates, and returns processor state register(PSR) value.
234    pub fn psr(&self) -> u16 {
235        ((if self.supervisor { 0 } else { 1 << 15 })
236            + ((u16::from(self.priority) & 0b111) << 8)
237            + (if self.condition.n { 1 << 2 } else { 0 })
238            + (if self.condition.z { 1 << 1 } else { 0 })
239            + (if self.condition.p { 1 } else { 0 })) as u16
240    }
241
242    /// Fetches an instruction from `self.mem[self.pc]`, into IR, and increments pc.
243    fn fetch(&mut self) {
244        self.ir = self.mem[self.pc as usize];
245        self.pc += 1;
246    }
247
248    /// Updates condition code with given register.
249    /// Note that each instruction handler should call this explicitly if needed.
250    fn update_condition(&mut self, reg: usize) {
251        self.condition.n = self.register[reg] < 0;
252        self.condition.z = self.register[reg] == 0;
253        self.condition.p = self.register[reg] > 0;
254    }
255}
256
257impl Default for VMState {
258    /// Returns empty, zero-filled virtual machine status with default memory filled.
259    fn default() -> Self {
260        VMState {
261            register: [0; 8],
262            pc: 0x0000,
263            ir: 0x0000,
264            supervisor: false,
265            priority: 0,
266            condition: Default::default(),
267            mem: DEFAULT_MEM,
268        }
269    }
270}
271
272#[test]
273fn test_update_condition() {
274    let mut vm = VM::default();
275
276    vm.state.register[0] = 0xFF33u16 as i16;
277    vm.state.update_condition(0);
278    assert!(vm.state.condition.n);
279
280    vm.state.register[2] = 0x0F33i16;
281    vm.state.update_condition(2);
282    assert!(vm.state.condition.p);
283
284    vm.state.register[1] = 0x0000;
285    vm.state.update_condition(1);
286    assert!(vm.state.condition.z);
287}
288
289#[test]
290fn test_step() {
291    let mut vm = VM::default();
292    // ADD R1, R1, #0
293    vm.load_u16(0x3000, &[0x1260]);
294    vm.step().unwrap();
295    assert!(vm.state.condition.z);
296}
297
298#[test]
299fn test_step_n() {
300    let mut vm = VM::default();
301    // AND R0, R0 #0
302    // ADD R0, R0, #14
303    vm.load_u16(0x3000, &[0x5020, 0x102E]);
304    vm.step_n(2).unwrap();
305    assert_eq!(vm.state.register[0], 14);
306}
307
308#[test]
309fn test_run() {
310    let mut vm = VM::default();
311    //      AND R0, R0, #0
312    //      AND R1, R1, #0
313    //      ADD R0, R0, #3
314    // LOOP BRz BYE
315    //      ADD R1, R1, #2
316    //      ADD R0, R0, #-1
317    //      BRnzp   LOOP
318    // BYE  STI  R1, SAVE
319    //      HALT
320    // SAVE .FILL   x3100
321    vm.load_u16(
322        0x3000,
323        &[
324            0x5020, 0x5260, 0x1023, 0x0403, 0x1262, 0x103F, 0x0FFC, 0xB201, 0xF025, 0x3100,
325        ],
326    );
327    vm.run().unwrap();
328    assert_eq!(vm.state.mem[0x3100], 6);
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334
335    #[test]
336    fn test_load() {
337        VM::default();
338        VM::default().load_u8(OPERATING_SYSTEM);
339        VM::default().load_u8(&[0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF]);
340        VM::default().load_u16(0x3000, &[0, 0, 0, 0]);
341    }
342
343    #[test]
344    #[should_panic]
345    fn test_load_panic() {
346        VM::default().load_u8(OPERATING_SYSTEM[1..].as_ref());
347        VM::default().load_u8(&[0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF]);
348        VM::default().load_u8(&[0xFF, 0xFF, 0xFF, 0xFE, 0xFF]);
349        VM::default().load_u16(0xFFFF, &[0, 0, 0, 0]);
350    }
351}