245
day09/src/vm.rs
Normal file
245
day09/src/vm.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
use crate::data::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HaltResult {
|
||||
Halt,
|
||||
WaitRead,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Program {
|
||||
program: Vec<isize>,
|
||||
pc: usize,
|
||||
rel_base: isize,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn new(program: Vec<isize>) -> Self {
|
||||
Program { program, pc: 0, rel_base: 0, }
|
||||
}
|
||||
|
||||
fn ensure_addr(&mut self, addr: usize) {
|
||||
if self.program.len() <= addr {
|
||||
let new_size = ((addr as f64) * 1.5) as usize;
|
||||
self.program.resize_with(new_size, Default::default);
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&mut self, param: Param) -> isize {
|
||||
match param {
|
||||
Param::Pos(p) => {
|
||||
self.ensure_addr(p);
|
||||
self.program[p]
|
||||
},
|
||||
Param::Imm(v) => v,
|
||||
Param::Rel(r) => {
|
||||
let addr = self.rel_base + r;
|
||||
assert!(addr >= 0, "invalid relative address");
|
||||
let addr = addr as usize;
|
||||
self.ensure_addr(addr);
|
||||
self.program[addr]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_addr(&mut self, param: Param) -> usize {
|
||||
match param {
|
||||
Param::Pos(p) => p,
|
||||
Param::Imm(_) => panic!("illegal addressing mode for target address"),
|
||||
Param::Rel(r) => {
|
||||
let addr = self.rel_base + r;
|
||||
assert!(addr >= 0, "invalid relative address");
|
||||
addr as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&mut self, addr: usize, what: isize) {
|
||||
self.ensure_addr(addr);
|
||||
self.program[addr] = what;
|
||||
}
|
||||
|
||||
pub fn run(&mut self, value_queue: &mut dyn IntQueue) -> HaltResult {
|
||||
loop {
|
||||
let op = Op::from(&self.program[self.pc..]);
|
||||
let mut next_pc = self.pc + op.len();
|
||||
match op {
|
||||
Op::Add { in1, in2, out } => {
|
||||
let v1 = self.get(in1);
|
||||
let v2 = self.get(in2);
|
||||
let out = self.get_addr(out);
|
||||
self.set(out, v1 + v2);
|
||||
}
|
||||
Op::Mul { in1, in2, out } => {
|
||||
let v1 = self.get(in1);
|
||||
let v2 = self.get(in2);
|
||||
let out = self.get_addr(out);
|
||||
self.set(out, v1 * v2);
|
||||
}
|
||||
Op::Read(pos) => {
|
||||
if let Some(value) = value_queue.next() {
|
||||
let addr = self.get_addr(pos);
|
||||
self.set(addr, value);
|
||||
} else {
|
||||
// PC is not incremented, so this should do the read again
|
||||
break HaltResult::WaitRead;
|
||||
}
|
||||
}
|
||||
Op::Write(pos) => {
|
||||
let value = self.get(pos);
|
||||
value_queue.feed(value);
|
||||
}
|
||||
Op::JumpTrue { test, jump } => {
|
||||
let value = self.get(test);
|
||||
if value != 0 {
|
||||
let addr = self.get(jump);
|
||||
assert!(addr >= 0, "invalid jump address: {}", addr);
|
||||
next_pc = addr as usize;
|
||||
}
|
||||
}
|
||||
Op::JumpFalse { test, jump } => {
|
||||
let value = self.get(test);
|
||||
if value == 0 {
|
||||
let addr = self.get(jump);
|
||||
assert!(addr >= 0, "invalid jump address: {}", addr);
|
||||
next_pc = addr as usize;
|
||||
}
|
||||
}
|
||||
Op::LessThan { in1, in2, out } => {
|
||||
let v1 = self.get(in1);
|
||||
let v2 = self.get(in2);
|
||||
let out = self.get_addr(out);
|
||||
self.set(out, (v1 < v2) as isize);
|
||||
}
|
||||
Op::Equals { in1, in2, out } => {
|
||||
let v1 = self.get(in1);
|
||||
let v2 = self.get(in2);
|
||||
let out = self.get_addr(out);
|
||||
self.set(out, (v1 == v2) as isize);
|
||||
}
|
||||
Op::SetRel(rel) => {
|
||||
self.rel_base += self.get(rel);
|
||||
}
|
||||
Op::Halt => {
|
||||
self.pc = next_pc;
|
||||
break HaltResult::Halt;
|
||||
}
|
||||
}
|
||||
self.pc = next_pc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Param {
|
||||
Pos(usize),
|
||||
Imm(isize),
|
||||
Rel(isize),
|
||||
}
|
||||
|
||||
fn param_mode(op: usize, param: u32) -> usize {
|
||||
let mode = op / 100;
|
||||
(mode / (10usize.pow(param))) % 10
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_mode() {
|
||||
assert_eq!(param_mode(1100, 0), 1);
|
||||
assert_eq!(param_mode(2100, 1), 2);
|
||||
assert_eq!(param_mode(2200, 0), 2);
|
||||
assert_eq!(param_mode(1000, 0), 0);
|
||||
assert_eq!(param_mode(1100, 0), 1);
|
||||
}
|
||||
|
||||
fn make_param(op: usize, param: u32, value: isize) -> Param {
|
||||
let mode = param_mode(op, param);
|
||||
match mode {
|
||||
0 => {
|
||||
assert!(
|
||||
value >= 0,
|
||||
"invalid position parameter for mode 0 op={} param={} value={}",
|
||||
op,
|
||||
param,
|
||||
value
|
||||
);
|
||||
Param::Pos(value as usize)
|
||||
}
|
||||
1 => Param::Imm(value),
|
||||
2 => Param::Rel(value),
|
||||
_ => panic!("invalid mode {} for op={} param={}", mode, op, param),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Op {
|
||||
Add { in1: Param, in2: Param, out: Param },
|
||||
Mul { in1: Param, in2: Param, out: Param },
|
||||
Read(Param),
|
||||
Write(Param),
|
||||
JumpTrue { test: Param, jump: Param },
|
||||
JumpFalse { test: Param, jump: Param },
|
||||
LessThan { in1: Param, in2: Param, out: Param },
|
||||
Equals { in1: Param, in2: Param, out: Param },
|
||||
SetRel(Param),
|
||||
Halt,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Op::Add { .. } | Op::Mul { .. } | Op::LessThan { .. } | Op::Equals { .. } => 4,
|
||||
Op::JumpTrue { .. } | Op::JumpFalse { .. } => 3,
|
||||
Op::Read(_) | Op::Write(_) | Op::SetRel(_) => 2,
|
||||
Op::Halt => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'_ [isize]> for Op {
|
||||
fn from(other: &'_ [isize]) -> Self {
|
||||
let op = other[0] as usize;
|
||||
match op % 100 {
|
||||
1 => {
|
||||
Op::Add {
|
||||
in1: make_param(op, 0, other[1]),
|
||||
in2: make_param(op, 1, other[2]),
|
||||
out: make_param(op, 2, other[3]),
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
Op::Mul {
|
||||
in1: make_param(op, 0, other[1]),
|
||||
in2: make_param(op, 1, other[2]),
|
||||
out: make_param(op, 2, other[3]),
|
||||
}
|
||||
}
|
||||
3 => Op::Read(make_param(op, 0, other[1])),
|
||||
4 => Op::Write(make_param(op, 0, other[1])),
|
||||
5 => Op::JumpTrue {
|
||||
test: make_param(op, 0, other[1]),
|
||||
jump: make_param(op, 1, other[2]),
|
||||
},
|
||||
6 => Op::JumpFalse {
|
||||
test: make_param(op, 0, other[1]),
|
||||
jump: make_param(op, 1, other[2]),
|
||||
},
|
||||
7 => {
|
||||
Op::LessThan {
|
||||
in1: make_param(op, 0, other[1]),
|
||||
in2: make_param(op, 1, other[2]),
|
||||
out: make_param(op, 2, other[3]),
|
||||
}
|
||||
}
|
||||
8 => {
|
||||
Op::Equals {
|
||||
in1: make_param(op, 0, other[1]),
|
||||
in2: make_param(op, 1, other[2]),
|
||||
out: make_param(op, 2, other[3]),
|
||||
}
|
||||
}
|
||||
9 => Op::SetRel(make_param(op, 0, other[1])),
|
||||
99 => Op::Halt,
|
||||
op => panic!("invalid op: {}", op),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user