year2016/
assembunny.rs

1//! Assembunny interpreter.
2//!
3//! See [`Day12`](crate::Day12), [`Day23`](crate::Day23) and [`Day25`](crate::Day25).
4
5use std::ops::ControlFlow;
6use utils::prelude::*;
7
8#[derive(Clone, Debug)]
9pub(crate) struct Interpreter<const TGL: bool, const OUT: bool> {
10    instructions: Vec<Instruction>,
11}
12
13#[derive(Copy, Clone, PartialEq, Eq, Debug)]
14enum Register {
15    A,
16    B,
17    C,
18    D,
19}
20
21#[derive(Copy, Clone, PartialEq, Eq, Debug)]
22enum Value {
23    Register(Register),
24    Number(i32),
25}
26
27#[derive(Copy, Clone, PartialEq, Eq, Debug)]
28enum Instruction {
29    Copy(Value, Register),
30    Increment(Register),
31    Decrement(Register),
32    JumpIfNotZero(Value, Value),
33    Toggle(Register),
34    Out(Register),
35    Invalid2(Value, Value),
36}
37
38impl<const TGL: bool, const OUT: bool> Interpreter<TGL, OUT> {
39    pub fn new(input: &str) -> Result<Self, InputError> {
40        let register = parser::literal_map!(
41            "a" => Register::A,
42            "b" => Register::B,
43            "c" => Register::C,
44            "d" => Register::D,
45        );
46        let value = register
47            .map(Value::Register)
48            .or(parser::i32().map(Value::Number));
49
50        Ok(Self {
51            instructions: parser::parse_tree!(
52                ("inc ", r @ register) => Instruction::Increment(r),
53                ("dec ", r @ register) => Instruction::Decrement(r),
54                ("cpy ", v @ value, " ", r @ register) => Instruction::Copy(v, r),
55                ("jnz ", v @ value, " ", o @ value) => Instruction::JumpIfNotZero(v, o),
56                ("tgl ", r @ register) =?> {
57                    if TGL {
58                        Ok(Instruction::Toggle(r))
59                    } else {
60                        Err("tgl instruction not supported")
61                    }
62                },
63                ("out ", r @ register) =?> {
64                    if OUT {
65                        Ok(Instruction::Out(r))
66                    } else {
67                        Err("out instruction not supported")
68                    }
69                },
70            )
71            .parse_lines(input)?,
72        })
73    }
74}
75
76impl<const TGL: bool> Interpreter<TGL, false> {
77    pub fn execute(&self, reg: [i32; 4]) -> i32 {
78        execute(&self.instructions, reg, |_, _| unreachable!())
79    }
80}
81
82impl<const TGL: bool> Interpreter<TGL, true> {
83    pub fn execute(
84        &self,
85        reg: [i32; 4],
86        out_fn: impl FnMut(i32, (usize, [i32; 4])) -> ControlFlow<()>,
87    ) -> i32 {
88        execute(&self.instructions, reg, out_fn)
89    }
90}
91
92#[inline]
93fn execute(
94    instructions: &[Instruction],
95    mut reg: [i32; 4],
96    mut out_fn: impl FnMut(i32, (usize, [i32; 4])) -> ControlFlow<()>,
97) -> i32 {
98    let mut pc = 0;
99
100    let mut instructions = instructions.to_vec();
101    while pc < instructions.len() {
102        #[rustfmt::skip] // Rustfmt wants each pattern to be on a single really long line
103        match instructions[pc..] {
104            // Recognize the following pattern of instructions which can be simplified to addition
105            //  inc $x
106            //  dec $y
107            //  jnz $y -2
108            // This is the key optimization for Day 12
109            [
110                Instruction::Increment(x),
111                Instruction::Decrement(y),
112                Instruction::JumpIfNotZero(Value::Register(y2), Value::Number(-2)),
113                ..
114            ] if y == y2 => {
115                reg[x as usize] += reg[y as usize];
116                reg[y as usize] = 0;
117                pc += 3;
118                continue;
119            }
120            // Recognize the following pattern of instructions which can be simplified to multiplication
121            //  cpy $w $x
122            //  inc $y
123            //  dec $x
124            //  jnz $x -2
125            //  dec $z
126            //  jnz $z -5
127            // This is the key optimisation for Day 23
128            [
129                Instruction::Copy(Value::Register(w), x),
130                Instruction::Increment(y),
131                Instruction::Decrement(x2),
132                Instruction::JumpIfNotZero(Value::Register(x3), Value::Number(-2)),
133                Instruction::Decrement(z),
134                Instruction::JumpIfNotZero(Value::Register(z2), Value::Number(-5)),
135                ..
136            ] if x == x2 && x == x3 && z == z2 => {
137                reg[y as usize] = reg[w as usize] * reg[z as usize];
138                reg[x as usize] = 0;
139                reg[z as usize] = 0;
140                pc += 6;
141                continue;
142            }
143            // Recognize the following pattern of instructions which can be simplified to division
144            //  cpy $N $x
145            //  jnz $y 2
146            //  jnz 1 6
147            //  dec $y
148            //  dec $x
149            //  jnz $x -4
150            //  inc $z
151            //  jnz 1 -7
152            // This is the key optimisation for Day 25
153            [
154                Instruction::Copy(Value::Number(n), x),
155                Instruction::JumpIfNotZero(Value::Register(y), Value::Number(2)),
156                Instruction::JumpIfNotZero(Value::Number(1), Value::Number(6)),
157                Instruction::Decrement(y2),
158                Instruction::Decrement(x2),
159                Instruction::JumpIfNotZero(Value::Register(x3), Value::Number(-4)),
160                Instruction::Increment(z),
161                Instruction::JumpIfNotZero(Value::Number(1), Value::Number(-7)),
162                ..
163            ] if x == x2 && x == x3 && y == y2 => {
164                reg[z as usize] += reg[y as usize] / n;
165                reg[x as usize] = n - (reg[y as usize] % n);
166                reg[y as usize] = 0;
167                pc += 8;
168                continue;
169            }
170            _ => {}
171        };
172
173        match instructions[pc] {
174            Instruction::Copy(v, dst) => reg[dst as usize] = v.get(&reg),
175            Instruction::Increment(dst) => reg[dst as usize] += 1,
176            Instruction::Decrement(dst) => reg[dst as usize] -= 1,
177            Instruction::JumpIfNotZero(v, offset) => {
178                if v.get(&reg) != 0 {
179                    let offset = offset.get(&reg);
180                    let Some(new_pc) = pc.checked_add_signed(offset as isize) else {
181                        break;
182                    };
183                    pc = new_pc;
184                    continue;
185                }
186            }
187            Instruction::Toggle(r) => 'toggle: {
188                let Some(index) = pc.checked_add_signed(reg[r as usize] as isize) else {
189                    break 'toggle;
190                };
191                if index >= instructions.len() {
192                    break 'toggle;
193                }
194
195                instructions[index] = match instructions[index] {
196                    Instruction::Increment(r) => Instruction::Decrement(r),
197                    Instruction::Decrement(r) => Instruction::Increment(r),
198                    Instruction::Toggle(r) => Instruction::Increment(r),
199                    Instruction::Out(r) => Instruction::Increment(r),
200                    Instruction::JumpIfNotZero(v, Value::Register(r)) => Instruction::Copy(v, r),
201                    Instruction::JumpIfNotZero(v, o @ Value::Number(_)) => {
202                        Instruction::Invalid2(v, o)
203                    }
204                    Instruction::Copy(v, r) => Instruction::JumpIfNotZero(v, Value::Register(r)),
205                    Instruction::Invalid2(v1, v2) => {
206                        // Effectively an invalid copy instruction
207                        Instruction::JumpIfNotZero(v1, v2)
208                    }
209                };
210            }
211            Instruction::Out(r) => {
212                if out_fn(reg[r as usize], (pc, reg)).is_break() {
213                    break;
214                }
215            }
216            Instruction::Invalid2(_, _) => {}
217        }
218
219        pc += 1;
220    }
221
222    reg[0]
223}
224
225impl Value {
226    #[inline]
227    fn get(self, registers: &[i32; 4]) -> i32 {
228        match self {
229            Value::Register(r) => registers[r as usize],
230            Value::Number(n) => n,
231        }
232    }
233}