Skip to content

Commit e30dd2c

Browse files
committed
2018 day 21
1 parent df299e3 commit e30dd2c

File tree

5 files changed

+355
-100
lines changed

5 files changed

+355
-100
lines changed

crates/year2018/src/day16.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use utils::prelude::*;
22

33
/// Inferring opcode operations from examples.
4+
///
5+
/// See also [day 19](crate::Day19) and [day 21](crate::Day21).
46
#[derive(Clone, Debug)]
57
pub struct Day16 {
68
samples: Vec<(u32, u16)>,

crates/year2018/src/day19.rs

Lines changed: 14 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,19 @@
1+
use crate::elfcode::{HookControlFlow, Instruction, Interpreter};
12
use utils::number::sum_of_divisors;
23
use utils::prelude::*;
34

45
/// Interpreting assembly to calculate the sum of divisors.
56
///
6-
/// See also [day 16](crate::Day16), which uses the same instruction set.
7+
/// See also [day 16](crate::Day16) and [day 21](crate::Day21).
78
#[derive(Clone, Debug)]
89
pub struct Day19 {
9-
instruction_pointer: Register,
10-
instructions: Vec<Instruction>,
11-
}
12-
13-
// Avoids bounds checks when indexing the register array
14-
utils::enumerable_enum! {
15-
#[repr(u32)]
16-
#[derive(Copy, Clone, Debug, PartialEq)]
17-
enum Register {
18-
A,
19-
B,
20-
C,
21-
D,
22-
E,
23-
F,
24-
}
25-
}
26-
27-
#[derive(Copy, Clone, Debug)]
28-
enum Instruction {
29-
Addr(Register, Register, Register),
30-
Addi(Register, u32, Register),
31-
Mulr(Register, Register, Register),
32-
Muli(Register, u32, Register),
33-
Banr(Register, Register, Register),
34-
Bani(Register, u32, Register),
35-
Borr(Register, Register, Register),
36-
Bori(Register, u32, Register),
37-
Setr(Register, Register),
38-
Seti(u32, Register),
39-
Gtir(u32, Register, Register),
40-
Gtri(Register, u32, Register),
41-
Gtrr(Register, Register, Register),
42-
Eqir(u32, Register, Register),
43-
Eqri(Register, u32, Register),
44-
Eqrr(Register, Register, Register),
10+
interpreter: Interpreter,
4511
}
4612

4713
impl Day19 {
4814
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
49-
let register =
50-
parser::byte_range(b'0'..=b'5').map(|b| Register::from_discriminant((b - b'0') as u32));
51-
let rrr_instructions = parser::literal_map!(
52-
"addr " => Instruction::Addr as fn(_, _, _) -> _,
53-
"mulr " => Instruction::Mulr,
54-
"banr " => Instruction::Banr,
55-
"borr " => Instruction::Borr,
56-
"gtrr " => Instruction::Gtrr,
57-
"eqrr " => Instruction::Eqrr,
58-
);
59-
let rir_instructions = parser::literal_map!(
60-
"addi " => Instruction::Addi as fn(_, _, _) -> _,
61-
"muli " => Instruction::Muli,
62-
"bani " => Instruction::Bani,
63-
"bori " => Instruction::Bori,
64-
"gtri " => Instruction::Gtri,
65-
"eqri " => Instruction::Eqri,
66-
);
67-
let instruction = parser::parse_tree!(
68-
(i @ rrr_instructions, a @ register, b' ', b @ register, b' ', c @ register) => i(a, b, c),
69-
(i @ rir_instructions, a @ register, b' ', b @ parser::u32(), b' ', c @ register) => i(a, b, c),
70-
("setr ", a @ register, b' ', parser::u32(), b' ', c @ register) => Instruction::Setr(a, c),
71-
("seti ", a @ parser::u32(), b' ', parser::u32(), b' ', c @ register) => Instruction::Seti(a, c),
72-
("gtir ", a @ parser::u32(), b' ', b @ register, b' ', c @ register) => Instruction::Gtir(a, b, c),
73-
("eqir ", a @ parser::u32(), b' ', b @ register, b' ', c @ register) => Instruction::Eqir(a, b, c),
74-
);
75-
76-
let (instruction_pointer, instructions) = register
77-
.with_prefix("#ip ")
78-
.with_suffix(parser::eol())
79-
.then(instruction.repeat(parser::eol(), 1))
80-
.parse_complete(input)?;
81-
8215
Ok(Self {
83-
instruction_pointer,
84-
instructions,
16+
interpreter: Interpreter::new(input)?,
8517
})
8618
}
8719

@@ -97,9 +29,10 @@ impl Day19 {
9729

9830
fn run(&self, reg0: u32) -> u32 {
9931
let mut reg = [reg0, 0, 0, 0, 0, 0];
100-
while let addr = reg[self.instruction_pointer] as usize
101-
&& addr < self.instructions.len()
102-
{
32+
33+
self.interpreter.run(&mut reg, |instructions, instruction_pointer, reg| {
34+
let addr = reg[instruction_pointer] as usize;
35+
10336
// Recognize the naive sum of divisors loop and replace it with a native implementation.
10437
// loop0: seti #1 $div
10538
// loop1: seti #1 $mul
@@ -112,7 +45,7 @@ impl Day19 {
11245
// gtrr $mul $tgt $tmp
11346
// addr $ip $tmp $ip
11447
// seti #loop1 $ip
115-
// addi $div $1 $div
48+
// addi $div #1 $div
11649
// gtrr $div $tgt $tmp
11750
// addr $tmp $ip $ip
11851
// seti #loop0 $ip
@@ -134,14 +67,14 @@ impl Day19 {
13467
Instruction::Addr(tmp8, ip8, ip9),
13568
Instruction::Seti(loop0, ip10),
13669
..,
137-
] = self.instructions[addr..]
70+
] = instructions[addr..]
13871
&& div == div2 && div == div3 && div == div4 && div == div5 && div == div6
13972
&& mul == mul2 && mul == mul3 && mul == mul4 && mul == mul5
14073
&& tmp == tmp2 && tmp == tmp3 && tmp == tmp4 && tmp == tmp5 && tmp == tmp6 && tmp == tmp7 && tmp == tmp8
14174
&& tgt == tgt2 && tgt == tgt3
14275
&& ip == ip2 && ip == ip3 && ip == ip4 && ip == ip5 && ip == ip6 && ip == ip7 && ip == ip8 && ip == ip9 && ip == ip10
14376
&& sum == sum2
144-
&& ip == self.instruction_pointer
77+
&& ip == instruction_pointer
14578
&& loop0 as usize == addr
14679
&& loop1 as usize == addr + 1
14780
{
@@ -151,30 +84,11 @@ impl Day19 {
15184
reg[mul] = reg[tgt] + 1;
15285
reg[tmp] = 1;
15386
reg[ip] += 15;
154-
continue;
87+
return HookControlFlow::Next
15588
};
15689

157-
match self.instructions[addr] {
158-
Instruction::Addr(a, b, c) => reg[c] = reg[a] + reg[b],
159-
Instruction::Addi(a, b, c) => reg[c] = reg[a] + b,
160-
Instruction::Mulr(a, b, c) => reg[c] = reg[a] * reg[b],
161-
Instruction::Muli(a, b, c) => reg[c] = reg[a] * b,
162-
Instruction::Banr(a, b, c) => reg[c] = reg[a] & reg[b],
163-
Instruction::Bani(a, b, c) => reg[c] = reg[a] & b,
164-
Instruction::Borr(a, b, c) => reg[c] = reg[a] | reg[b],
165-
Instruction::Bori(a, b, c) => reg[c] = reg[a] | b,
166-
Instruction::Setr(a, c) => reg[c] = reg[a],
167-
Instruction::Seti(a, c) => reg[c] = a,
168-
Instruction::Gtir(a, b, c) => reg[c] = u32::from(a > reg[b]),
169-
Instruction::Gtri(a, b, c) => reg[c] = u32::from(reg[a] > b),
170-
Instruction::Gtrr(a, b, c) => reg[c] = u32::from(reg[a] > reg[b]),
171-
Instruction::Eqir(a, b, c) => reg[c] = u32::from(a == reg[b]),
172-
Instruction::Eqri(a, b, c) => reg[c] = u32::from(reg[a] == b),
173-
Instruction::Eqrr(a, b, c) => reg[c] = u32::from(reg[a] == reg[b]),
174-
}
175-
176-
reg[self.instruction_pointer] += 1;
177-
}
90+
HookControlFlow::Execute
91+
});
17892

17993
reg[0]
18094
}

crates/year2018/src/day21.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use crate::elfcode::{HookControlFlow, Instruction, Interpreter, Register};
2+
use std::collections::HashSet;
3+
use utils::prelude::*;
4+
5+
/// Interpreting assembly to find the longest running input.
6+
///
7+
/// See also [day 16](crate::Day16) and [day 19](crate::Day19).
8+
#[derive(Clone, Debug)]
9+
pub struct Day21 {
10+
interpreter: Interpreter,
11+
}
12+
13+
impl Day21 {
14+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
15+
let interpreter = Interpreter::new(input)?;
16+
17+
let instruction_pointer = interpreter.instruction_pointer();
18+
if instruction_pointer == Register::A {
19+
return Err(InputError::new(input, 0, "expected #ip to be non-zero"));
20+
}
21+
22+
let instructions = interpreter.instructions();
23+
if instructions
24+
.iter()
25+
.flat_map(|i| i.registers())
26+
.filter(|&r| r == Register::A)
27+
.count()
28+
!= 1
29+
{
30+
return Err(InputError::new(
31+
input,
32+
0,
33+
"expected exactly one use of register 0",
34+
));
35+
}
36+
37+
if let [
38+
..,
39+
Instruction::Eqrr(_, Register::A, tmp),
40+
Instruction::Addr(tmp2, ip, ip2),
41+
Instruction::Seti(_, ip3),
42+
] = *instructions
43+
&& tmp == tmp2
44+
&& ip == ip2
45+
&& ip == ip3
46+
&& ip == instruction_pointer
47+
&& tmp != instruction_pointer
48+
{
49+
// Register 0 is used in exactly one place, in the 3rd last instruction, and the outer
50+
// loop terminates when it matches a computed value.
51+
Ok(Self { interpreter })
52+
} else {
53+
Err(InputError::new(
54+
input,
55+
input.len() - 1,
56+
"expected register 0 matching to control termination of the outer loop",
57+
))
58+
}
59+
}
60+
61+
#[must_use]
62+
pub fn part1(&self) -> u32 {
63+
self.next(&mut [0; 6])
64+
}
65+
66+
#[must_use]
67+
pub fn part2(&self) -> u32 {
68+
// Using a HashSet is faster than using Brent's algorithm as it minimizes the number of next
69+
// calls, which are slow.
70+
let mut reg = [0; 6];
71+
let mut seen = HashSet::with_capacity(20000);
72+
let mut last_first_seen = 0;
73+
loop {
74+
let target = self.next(&mut reg);
75+
if seen.insert(target) {
76+
last_first_seen = target;
77+
} else {
78+
break;
79+
}
80+
}
81+
last_first_seen
82+
}
83+
84+
fn next(&self, reg: &mut [u32; 6]) -> u32 {
85+
let mut target = None;
86+
87+
self.interpreter
88+
.run(reg, |instructions, instruction_pointer, reg| {
89+
let addr = reg[instruction_pointer] as usize;
90+
91+
if let Instruction::Eqrr(tgt, Register::A, tmp) = instructions[addr] {
92+
target = Some(reg[tgt]);
93+
94+
// Simulate r0 not equaling the target, so the program always continues when
95+
// the interpreter is next resumed.
96+
reg[tmp] = 0;
97+
reg[instruction_pointer] += 1;
98+
99+
return HookControlFlow::Halt;
100+
}
101+
102+
// Recognize the division by 256 and replace it with a native computation.
103+
// start: seti #0 $quo
104+
// addi $quo #1 $tmp
105+
// muli $tmp #256 $tmp
106+
// gtrr $tmp $num $tmp
107+
// addr $tmp $ip $ip
108+
// addi $ip #1 $ip
109+
// seti #end $ip
110+
// addi $quo #1 $quo
111+
// end: seti #start $ip
112+
#[rustfmt::skip]
113+
if let [
114+
Instruction::Seti(0, quo),
115+
Instruction::Addi(quo2, 1, tmp),
116+
Instruction::Muli(tmp2, 256, tmp3),
117+
Instruction::Gtrr(tmp4, num, tmp5),
118+
Instruction::Addr(tmp6, ip, ip2),
119+
Instruction::Addi(ip3, 1, ip4),
120+
Instruction::Seti(end, ip5),
121+
Instruction::Addi(quo3, 1, quo4),
122+
Instruction::Seti(start, ip6),
123+
..,
124+
] = instructions[addr..]
125+
&& quo == quo2 && quo == quo3 && quo == quo4
126+
&& tmp == tmp2 && tmp == tmp3 && tmp == tmp4 && tmp == tmp5 && tmp == tmp6
127+
&& ip == ip2 && ip == ip3 && ip == ip4 && ip == ip5 && ip == ip6
128+
&& start as usize == addr
129+
&& end as usize == addr + 8
130+
{
131+
reg[quo] = reg[num] >> 8;
132+
reg[tmp] = 1;
133+
reg[ip] += 9;
134+
return HookControlFlow::Next;
135+
};
136+
137+
HookControlFlow::Execute
138+
});
139+
140+
target.expect("no solution found")
141+
}
142+
}
143+
144+
examples!(Day21 -> (u32, u32) [
145+
// Custom example
146+
{
147+
// Equivalent to
148+
//
149+
// r2 = 0;
150+
// do {
151+
// r2++;
152+
// if (r2 == 100) r2 = 1;
153+
// } while (r2 != r0);
154+
//
155+
// Produces sequence 1, 2, 3, ..., 98, 99, 1, 2, 3, ..., 98, 99, 1, ...
156+
input: "#ip 4\n\
157+
seti 0 0 2\n\
158+
addi 2 1 2\n\
159+
eqri 2 100 1\n\
160+
addr 1 4 4\n\
161+
addi 4 1 4\n\
162+
seti 1 0 2\n\
163+
eqrr 2 0 1\n\
164+
addr 1 4 4\n\
165+
seti 0 0 4",
166+
part1: 1,
167+
part2: 99,
168+
},
169+
]);

0 commit comments

Comments
 (0)