-
Notifications
You must be signed in to change notification settings - Fork 2
/
19_go_with_the_flow.rb
131 lines (113 loc) · 3.28 KB
/
19_go_with_the_flow.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
require_relative 'lib/optimise'
instructions = STANDARD_INSTRUCTIONS.merge(
# isfactor candidate n dest: add candidate to dest if candidate is a factor of n.
#
# as a side effect:
# tmp1 is set to candidate * n
# tmp2 is set to bool(candidate * n == n)
# i is set to n + 1
# tmp3 is set to 1
isfactor: ->(candidate, n, dest, len, i, tmp1, tmp2, tmp3, regs, ipreg) {
regs[dest] += regs[candidate] if regs[n] % regs[candidate] == 0
# Side effects
regs[tmp1] = regs[candidate] * regs[n]
regs[tmp2] = regs[tmp1] == regs[n] ? 1 : 0
regs[i] = regs[n] + 1
regs[tmp3] = 1
regs[ipreg] += len - 1
},
# sumfactors adds the factors of n to dest.
#
# as a side effect:
# all side effects of candidate are applied (where candidate's i is sumfactor's j)
# i is set to n + 1
# tmp4 is set to 1
sumfactors: ->(n, dest, len, i, j, tmp1, tmp2, tmp3, tmp4, regs, ipreg) {
nval = regs[n]
(1..(nval ** 0.5)).each { |candidate|
q, r = nval.divmod(candidate)
next if r != 0
regs[dest] += candidate + (q == candidate ? 0 : q)
}
# Side effects
regs[tmp1] = regs[n] * regs[n]
regs[tmp2] = regs[tmp1] == regs[n] ? 1 : 0
regs[j] = regs[n] + 1
regs[tmp3] = 1
regs[i] = regs[n] + 1
regs[tmp4] = 1
regs[ipreg] += len - 1
},
).freeze
rules = [
{
pattern: [
[:seti, 1, :ANY, :i], # i = 1
%i(mulr candidate i tmp1), # tmp1 = candidate * i
%i(eqrr tmp1 n tmp2), # if tmp1 == n:
%i(addr tmp2 IPREG IPREG),
[:addi, :IPREG, 1, :IPREG],
%i(addr candidate dest dest), # dest += candidate
[:addi, :i, 1, :i], # i += 1
%i(gtrr i n tmp3), # if i <= n:
%i(addr IPREG tmp3 IPREG),
%i(seti STARTIP ANY IPREG), # goto START
],
unique: %i(n i candidate dest),
replace: %i(isfactor candidate n dest LEN i tmp1 tmp2 tmp3),
},
{
pattern: [
[:seti, 1, :ANY, :i],
%i(isfactor i n dest ANY j tmp1 tmp2 tmp3),
[:ANY],
[:ANY],
[:ANY],
[:ANY],
[:ANY],
[:ANY],
[:ANY],
[:ANY],
[:ANY],
[:addi, :i, 1, :i], # i += 1
%i(gtrr i n tmp4), # if i <= n:
%i(addr tmp4 IPREG IPREG),
%i(seti STARTIP ANY IPREG), # goto START
],
unique: %i(n i j dest),
replace: %i(sumfactors n dest LEN i j tmp1 tmp2 tmp3 tmp4),
},
]
def run(r0, program, jumps: nil)
regs = [r0] + ([0] * 5)
flow = Hash.new { |h, k| h[k] = Hash.new(0) }
cycles = 0
ip = 0
# Nothing in this instruction set should cause a register to be < 0
# so I'm omitting the regs[ipreg] >= 0 check.
# Revisit this if the instruction set changes!
while (inst = program[ip])
cycles += 1
ipreg = inst[:ipreg]
regs[ipreg] = ip
inst[:f][*inst[:args], regs, ipreg]
newip = regs[ipreg] + 1
flow[ip][newip] += 1
ip = newip
end
if jumps
puts ?= * 20 + " cycle #{cycles}"
jump_freq_report(flow, program)
puts ?= * 20
end
regs
end
verbose = ARGV.delete('-v')
jumps = ARGV.delete('-j')
slow = ARGV.delete('--slow')
input = ARGF.each_line.map(&:split)
program = parse_asm(input, instructions, verbose: verbose, rules: !slow && rules)
[0, 1].each { |r0|
regs = run(r0, program, jumps: jumps)
p verbose ? regs : regs[0]
}