From 25cd57559ee58c19c6801626bd1e0f03d0006c21 Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Sun, 15 May 2022 14:03:01 -0400 Subject: [PATCH] Implement "boosted" Int-code computer * Support for 64bit integer values and non-continguous memory regions * Add support for relative mode parameters * New opcode to adjust relative mode base value * Legacy 32bit integer API is preserved via simulate() --- icc/Cargo.lock | 2 +- icc/Cargo.toml | 2 +- icc/src/lib.rs | 460 ++++++++++++++++++++++++++++++++----------------- 3 files changed, 302 insertions(+), 162 deletions(-) diff --git a/icc/Cargo.lock b/icc/Cargo.lock index d95ca2c..c7a86b4 100644 --- a/icc/Cargo.lock +++ b/icc/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "icc" -version = "1.0.0" +version = "1.1.0" diff --git a/icc/Cargo.toml b/icc/Cargo.toml index 3efbd7a..e72f5dc 100644 --- a/icc/Cargo.toml +++ b/icc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "icc" -version = "1.0.0" +version = "1.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/icc/src/lib.rs b/icc/src/lib.rs index aa0a9df..cf22d49 100644 --- a/icc/src/lib.rs +++ b/icc/src/lib.rs @@ -1,7 +1,9 @@ use std::collections::VecDeque; +use std::collections::HashMap; #[derive(Debug)] #[derive(PartialEq)] +#[derive(Copy, Clone)] pub enum Status { Running, Finished, @@ -13,174 +15,269 @@ pub struct Result { pub instruction: usize, } -pub fn simulate(v: &mut Vec, input: &mut VecDeque, output: &mut Vec, start: usize) -> Result { - let mut pos = start; - loop { - let mut code = v[pos]; +pub struct Computer { + memory: HashMap, + position: usize, + relative_base: i64, + input: VecDeque, + output: Vec, +} + +#[derive(Debug)] +#[derive(Copy, Clone)] +enum Operation { + Add, + Mul, + Input, + Output, + JumpIfTrue, + JumpIfFalse, + LessThan, + Equals, + AdjustRelativeBase, + End, // 99 +} + +#[derive(Debug)] +#[derive(Copy, Clone)] +enum ParameterMode { + Position, // 0 + Immediate, // 1 + Relative, // 2 +} + +impl ParameterMode { + pub fn from_int(i: i64) -> ParameterMode { + if i == 0 { + ParameterMode::Position + } + else if i == 1 { + ParameterMode::Immediate + } + else if i == 2 { + ParameterMode::Relative + } + else { + panic!("Unknown parameter mode integer value: {}", i); + } + } +} + +impl Computer { + pub fn initialize(program: Vec, input: Vec) -> Self { + let mut computer = Computer { + memory: HashMap::new(), + position: 0, + relative_base: 0, + input: VecDeque::from(input), + output: Vec::new(), + }; + for i in 0..program.len() { + computer.mem_put(i, program[i]); + } + return computer; + } + + fn mem_put(&mut self, address: usize, value: i64) { + self.memory.insert(address, value); + } + + fn mem_get(&mut self, address: usize) -> i64 { + let value = self.memory.get(&address); + if value.is_none() { + self.mem_put(address, 0); + return 0; + } + return *value.unwrap(); + } + + pub fn run(&mut self) -> Status { + loop { + let (op, p1_mode, p2_mode, p3_mode) = Self::decode_instruction( + self.mem_get(self.position) + ); + match op { + Operation::Add => { + let d1 = self.fetch(self.position + 1, p1_mode); + let d2 = self.fetch(self.position + 2, p2_mode); + // The original implementation didn't check p3_mode for the Add + // operation. + let dest = self.mem_get(self.position + 3) as usize; + self.mem_put(dest, d1 + d2); + self.position += 4; + }, + Operation::Mul => { + let d1 = self.fetch(self.position + 1, p1_mode); + let d2 = self.fetch(self.position + 2, p2_mode); + // The original implementation didn't check p3_mode for the Mul + // operation + let dest = self.mem_get(self.position + 3) as usize; + self.mem_put(dest, d1 * d2); + self.position += 4; + }, + Operation::Input => { + if self.input.len() == 0 { + return Status::WaitingForInput; + } + let dest = self.mem_get(self.position + 1) as usize; + let input = self.input.pop_front().expect("Input queue empty... weird"); + self.mem_put(dest, input); + println!("Input: {}", input); + self.position += 2; + }, + Operation::Output => { + let value = self.fetch(self.position + 1, p1_mode); + println!("Output: {}", value); + self.output.push(value); + self.position += 2; + }, + Operation::JumpIfTrue => { + let d1 = self.fetch(self.position + 1, p1_mode); + let d2 = self.fetch(self.position + 2, p2_mode); + if d1 != 0 { + self.position = d2 as usize; + } + else { + self.position += 3; + } + }, + Operation::JumpIfFalse => { + let d1 = self.fetch(self.position + 1, p1_mode); + let d2 = self.fetch(self.position + 2, p2_mode); + if d1 == 0 { + self.position = d2 as usize; + } + else { + self.position += 3; + } + }, + Operation::LessThan => { + let d1 = self.fetch(self.position + 1, p1_mode); + let d2 = self.fetch(self.position + 2, p2_mode); + // The original implementation didn't check p3_mode + let dest = self.mem_get(self.position + 3) as usize; + self.mem_put(dest, if d1 < d2 { 1 } else { 0 }); + self.position += 4; + }, + Operation::Equals => { + let d1 = self.fetch(self.position + 1, p1_mode); + let d2 = self.fetch(self.position + 2, p2_mode); + // The original implementation didn't check p3_mode + let dest = self.mem_get(self.position + 3) as usize; + self.mem_put(dest, if d1 == d2 { 1 } else { 0 }); + self.position += 4; + }, + Operation::AdjustRelativeBase => { + self.relative_base += self.mem_get(self.position + 1); + self.position += 2; + }, + Operation::End => { + break; + }, + other => { + panic!("'{:?}' not implemented", other); + }, + }; + } + return Status::Finished; + } + + fn fetch(&mut self, pos: usize, mode: ParameterMode) -> i64 { + let addr = self.absolute_address(pos, mode); + return self.mem_get(addr); + } + + fn absolute_address(&mut self, pos: usize, mode: ParameterMode) -> usize { + match mode { + ParameterMode::Position => { + self.mem_get( + pos + ) as usize + }, + ParameterMode::Immediate => { + pos + }, + ParameterMode::Relative => { + let p = self.mem_get(pos) as i64 + self.relative_base; + if p < 0 { + panic!("Attempting to resolve illegale address: {}", p); + } + return p as usize; + } + } + } + + fn decode_instruction(code: i64) -> (Operation, ParameterMode, ParameterMode, ParameterMode) { // Opcodes are ABCDE, where the op is DE // the modes for params 1, 2, and 3 as C, B, A // respectively. - let op = code % 100; - //println!("pos {}, code {}, op {}", pos, code, op); - let param_3_mode = code / 10000; - if param_3_mode == 1 { - code -= 10000; - } - let param_2_mode = code / 1000; - if param_2_mode == 1 { - code -= 1000; - } - let param_1_mode = code / 100; - if param_1_mode == 1 { - code -= 100; - } - if op == 1 { - // Add d1 + d2 -> dest - let d1: i32 = if param_1_mode == 1 { - v[pos+1] - } else { - v[v[pos+1] as usize] - }; - let d2: i32 = if param_2_mode == 1 { - v[pos+2] - } else { - v[v[pos+2] as usize] - }; - let dest = v[pos+3] as usize; - v[dest] = d1 + d2; - pos += 4; - continue; - } - else if op == 2 { - // Mult d1 * d2 -> dest - let d1: i32 = if param_1_mode == 1 { - v[pos+1] - } else { - v[v[pos+1] as usize] - }; - let d2: i32 = if param_2_mode == 1 { - v[pos+2] - } - else { - v[v[pos+2] as usize] - }; - let dest = v[pos+3] as usize; - v[dest] = d1 * d2; - pos += 4; - continue; - } - else if op == 3 { - // Input - let dest = v[pos+1] as usize; - v[dest] = if input.len() == 0 { - return Result { - status: Status::WaitingForInput, - instruction: pos - }; - } else { - input.pop_front().expect("Input stack empty... weird") - }; - println!("Input: {}", v[dest]); - pos += 2; - } - else if op == 4 { - // Output - let d = if param_1_mode == 1 { - v[pos+1] - } - else { - v[v[pos+1] as usize] - }; - println!("Output: {}", d); - output.push(d); - pos += 2; - } - else if op == 5 { - // Jump if true - let d1 = if param_1_mode == 1 { - v[pos+1] as usize - } - else { - v[v[pos+1] as usize] as usize - }; - let d2 = if param_2_mode == 1 { - v[pos+2] as usize - } - else { - v[v[pos+2] as usize] as usize - }; - if d1 != 0 { - pos = d2; - } - else { - pos += 3; - } - } - else if op == 6 { - // Jump if false - let d1 = if param_1_mode == 1 { - v[pos+1] as usize - } - else { - v[v[pos+1] as usize] as usize - }; - let d2 = if param_2_mode == 1 { - v[pos+2] as usize - } - else { - v[v[pos+2] as usize] as usize - }; - if d1 == 0 { - pos = d2; - } - else { - pos += 3; - } - } - else if op == 7 { - // Less than - let d1: i32 = if param_1_mode == 1 { - v[pos+1] - } else { - v[v[pos+1] as usize] - }; - let d2: i32 = if param_2_mode == 1 { - v[pos+2] - } - else { - v[v[pos+2] as usize] - }; - let dest = v[pos+3] as usize; - v[dest] = if d1 < d2 { 1 } else { 0 }; - pos += 4; - } - else if op == 8 { - // equals - let d1: i32 = if param_1_mode == 1 { - v[pos+1] - } else { - v[v[pos+1] as usize] - }; - let d2: i32 = if param_2_mode == 1 { - v[pos+2] - } - else { - v[v[pos+2] as usize] - }; - let dest = v[pos+3] as usize; - v[dest] = if d1 == d2 { 1 } else { 0 }; - pos += 4; - } - else if op == 99 { - break; + let mut c = code; + let op_int = code % 100; + let op = if op_int == 1 { + Operation::Add + } else if op_int == 2 { + Operation::Mul + } else if op_int == 3 { + Operation::Input + } else if op_int == 4 { + Operation::Output + } else if op_int == 5 { + Operation::JumpIfTrue + } else if op_int == 6 { + Operation::JumpIfFalse + } else if op_int == 7 { + Operation::LessThan + } else if op_int == 8 { + Operation::Equals + } else if op_int == 9 { + Operation::AdjustRelativeBase + }else if op_int == 99 { + Operation::End + } else { + panic!("Unknown opcode: {}", op_int); + }; + + let param_3_mode_int = c / 10000; + c -= param_3_mode_int * 10000; + let param_2_mode_int = c / 1000; + c -= param_2_mode_int * 1000; + let param_1_mode_int = c / 100; + return ( + op, + ParameterMode::from_int(param_1_mode_int), + ParameterMode::from_int(param_2_mode_int), + ParameterMode::from_int(param_3_mode_int), + ); + } +} + +pub fn simulate(v: &mut Vec, input: &mut VecDeque, output: &mut Vec, start: usize) -> Result { + let mut program: Vec = Vec::new(); + for i in 0..v.len() { + program.push(i64::from(v[i])); + } + let mut ip: VecDeque = VecDeque::new(); + for i in 0..input.len() { + ip.push_front(i64::from(input[i])); + } + let mut computer = Computer::initialize(program, vec![]); + computer.input = ip; + computer.position = start; + let r = computer.run(); + for i in 0..computer.output.len() { + if computer.output[i] <= i64::from(i32::MAX) && computer.output[i] >= i64::from(i32::MIN) { + output.push(computer.output[i] as i32); } else { - panic!("Unknown opcode: {}", code); + panic!("Output value '{}' out of range for legacy i32 int-code computer", computer.output[i]); } } + for i in 0..v.len() { + v[i] = computer.mem_get(i) as i32; + } return Result { - status: Status::Finished, - instruction: 0 + status: r, + instruction: computer.position, }; } @@ -345,4 +442,47 @@ mod tests { assert_eq!(result.status, Status::WaitingForInput); assert_eq!(result.instruction, 6); } + + #[test] + fn relative_base_adjust() { + let mut c = Computer::initialize(vec![109, 1, 99], vec![]); + c.run(); + assert_eq!(c.relative_base, 1); + } + + #[test] + #[should_panic] + fn negative_memory_access() { + let mut c = Computer::initialize(vec![109, -5000, 204, 0, 99], vec![]); + c.run(); + } + + #[test] + fn relative_example_1() { + // The program produces an output of itself + let program = vec![109,1,204,-1,1001,100,1,100,1008,100,16,101,1006,101,0,99]; + let mut c = Computer::initialize(program.clone(), vec![]); + c.run(); + for i in 0..program.len() { + assert_eq!(program[i], c.output[i]); + } + } + + #[test] + fn relative_example_2() { + // Outputs a 16 digit numbers + let program = vec![1102,34915192,34915192,7,4,7,99,0]; + let mut c = Computer::initialize(program, vec![]); + c.run(); + assert_eq!(c.output[0].to_string().len(), 16); + } + + #[test] + fn relative_example_3() { + // Outputs 1125899906842624 + let program = vec![104,1125899906842624,99]; + let mut c = Computer::initialize(program, vec![]); + c.run(); + assert_eq!(c.output[0], 1125899906842624); + } }