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()
This commit is contained in:
parent
065d2d8192
commit
25cd57559e
|
@ -4,4 +4,4 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "icc"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
|
|
|
@ -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
|
||||
|
|
450
icc/src/lib.rs
450
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<i32>, input: &mut VecDeque<i32>, output: &mut Vec<i32>, start: usize) -> Result {
|
||||
let mut pos = start;
|
||||
pub struct Computer {
|
||||
memory: HashMap<usize, i64>,
|
||||
position: usize,
|
||||
relative_base: i64,
|
||||
input: VecDeque<i64>,
|
||||
output: Vec<i64>,
|
||||
}
|
||||
|
||||
#[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<i64>, input: Vec<i64>) -> 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 mut code = v[pos];
|
||||
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]
|
||||
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 {
|
||||
v[v[pos+1] as usize]
|
||||
panic!("Unknown opcode: {}", op_int);
|
||||
};
|
||||
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;
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
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]
|
||||
}
|
||||
|
||||
pub fn simulate(v: &mut Vec<i32>, input: &mut VecDeque<i32>, output: &mut Vec<i32>, start: usize) -> Result {
|
||||
let mut program: Vec<i64> = Vec::new();
|
||||
for i in 0..v.len() {
|
||||
program.push(i64::from(v[i]));
|
||||
}
|
||||
let mut ip: VecDeque<i64> = 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 {
|
||||
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;
|
||||
panic!("Output value '{}' out of range for legacy i32 int-code computer", computer.output[i]);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
else {
|
||||
panic!("Unknown opcode: {}", code);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
Reference in New Issue