Skip to content

Commit 8667709

Browse files
committed
2016 day 23
1 parent 265005d commit 8667709

File tree

4 files changed

+255
-97
lines changed

4 files changed

+255
-97
lines changed

crates/year2016/src/assembunny.rs

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

crates/year2016/src/day12.rs

Lines changed: 6 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::assembunny::{Interpreter, InterpreterConfig};
12
use utils::prelude::*;
23

34
/// Interpreting assembly, again.
@@ -12,120 +13,28 @@ use utils::prelude::*;
1213
/// cycles ~5,000 times for part 1 and ~100,000 times for part 2, to around ~200 cycles each.
1314
#[derive(Clone, Debug)]
1415
pub struct Day12 {
15-
instructions: Vec<Instruction>,
16-
}
17-
18-
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
19-
enum Register {
20-
A,
21-
B,
22-
C,
23-
D,
24-
}
25-
26-
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
27-
enum Value {
28-
Register(Register),
29-
Number(i32),
30-
}
31-
32-
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
33-
enum Instruction {
34-
Copy(Value, Register),
35-
Increment(Register),
36-
Decrement(Register),
37-
JumpIfNotZero(Value, i32),
16+
interpreter: Interpreter<Self>,
3817
}
3918

4019
impl Day12 {
4120
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
42-
let register = parser::one_of((
43-
b'a'.map(|_| Register::A),
44-
b'b'.map(|_| Register::B),
45-
b'c'.map(|_| Register::C),
46-
b'd'.map(|_| Register::D),
47-
));
48-
let value = register
49-
.map(Value::Register)
50-
.or(parser::i32().map(Value::Number));
51-
5221
Ok(Self {
53-
instructions: parser::one_of((
54-
register.with_prefix("inc ").map(Instruction::Increment),
55-
register.with_prefix("dec ").map(Instruction::Decrement),
56-
value
57-
.with_prefix("cpy ")
58-
.then(register.with_prefix(" "))
59-
.map(|(v, r)| Instruction::Copy(v, r)),
60-
value
61-
.with_prefix("jnz ")
62-
.then(parser::i32().with_prefix(" "))
63-
.map(|(v, o)| Instruction::JumpIfNotZero(v, o)),
64-
))
65-
.parse_lines(input)?,
22+
interpreter: Interpreter::new(input)?,
6623
})
6724
}
6825

6926
#[must_use]
7027
pub fn part1(&self) -> i32 {
71-
self.execute([0; 4])
28+
self.interpreter.execute([0; 4])
7229
}
7330

7431
#[must_use]
7532
pub fn part2(&self) -> i32 {
76-
self.execute([0, 0, 1, 0])
77-
}
78-
79-
fn execute(&self, mut reg: [i32; 4]) -> i32 {
80-
let mut pc = 0;
81-
82-
while pc < self.instructions.len() {
83-
// Recognize the following pattern of instructions which can be simplified to addition
84-
// inc $r1
85-
// dec $r2
86-
// jnz $r2 -2
87-
if let [Instruction::Increment(r1), Instruction::Decrement(r2), Instruction::JumpIfNotZero(Value::Register(r3), -2), ..] =
88-
self.instructions[pc..]
89-
{
90-
if r2 == r3 {
91-
reg[r1 as usize] += reg[r2 as usize];
92-
reg[r2 as usize] = 0;
93-
pc += 3;
94-
continue;
95-
}
96-
}
97-
98-
match self.instructions[pc] {
99-
Instruction::Copy(v, dst) => reg[dst as usize] = v.get(&reg),
100-
Instruction::Increment(dst) => reg[dst as usize] += 1,
101-
Instruction::Decrement(dst) => reg[dst as usize] -= 1,
102-
Instruction::JumpIfNotZero(v, offset) => {
103-
if v.get(&reg) != 0 {
104-
let Some(new_pc) = pc.checked_add_signed(offset as isize) else {
105-
break;
106-
};
107-
pc = new_pc;
108-
continue;
109-
}
110-
}
111-
}
112-
113-
pc += 1;
114-
}
115-
116-
reg[0]
33+
self.interpreter.execute([0, 0, 1, 0])
11734
}
11835
}
11936

120-
impl Value {
121-
#[inline]
122-
fn get(self, registers: &[i32; 4]) -> i32 {
123-
match self {
124-
Value::Register(r) => registers[r as usize],
125-
Value::Number(n) => n,
126-
}
127-
}
128-
}
37+
impl InterpreterConfig for Day12 {}
12938

13039
examples!(Day12 -> (i32, i32) [
13140
{

crates/year2016/src/day23.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::assembunny::{Interpreter, InterpreterConfig};
2+
use utils::prelude::*;
3+
4+
/// Interpreting assembunny assembly, again.
5+
///
6+
/// The key optimization is that
7+
/// ```text
8+
/// cpy $r1 $r2
9+
/// inc $r3
10+
/// dec $r2
11+
/// jnz $r2 -2
12+
/// dec $r4
13+
/// jnz $r4 -5
14+
/// ```
15+
/// can be replaced with `$r3 = $r1 * r3` followed by `$r2 = 0` and `$r4 = 0`. This reduces the
16+
/// number of simulated cycles for part 2 ~2,000,000 times when using the previously implemented
17+
/// addition optimization, and ~6,000,000 times compared to a naive implementation.
18+
///
19+
/// See also [2016 day 12](crate::Day12).
20+
#[derive(Clone, Debug)]
21+
pub struct Day23 {
22+
interpreter: Interpreter<Self>,
23+
}
24+
25+
impl Day23 {
26+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
27+
Ok(Self {
28+
interpreter: Interpreter::new(input)?,
29+
})
30+
}
31+
32+
#[must_use]
33+
pub fn part1(&self) -> i32 {
34+
self.interpreter.execute([7, 0, 0, 0])
35+
}
36+
37+
#[must_use]
38+
pub fn part2(&self) -> i32 {
39+
self.interpreter.execute([12, 0, 0, 0])
40+
}
41+
}
42+
43+
impl InterpreterConfig for Day23 {
44+
const SUPPORTS_TOGGLE: bool = true;
45+
}
46+
47+
examples!(Day23 -> (i32, i32) [
48+
{
49+
input: "cpy 2 a\n\
50+
tgl a\n\
51+
tgl a\n\
52+
tgl a\n\
53+
cpy 1 a\n\
54+
dec a\n\
55+
dec a",
56+
part1: 3,
57+
},
58+
]);

crates/year2016/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![doc = include_str!("../README.md")]
22
#![cfg_attr(not(feature = "unsafe"), forbid(unsafe_code))]
33

4+
mod assembunny;
5+
46
utils::year!(2016 => year2016, ${
57
1 => day01::Day01,
68
2 => day02::Day02<'_>,
@@ -24,4 +26,5 @@ utils::year!(2016 => year2016, ${
2426
20 => day20::Day20,
2527
21 => day21::Day21,
2628
22 => day22::Day22,
29+
23 => day23::Day23,
2730
});

0 commit comments

Comments
 (0)