Last active
December 9, 2019 05:37
-
-
Save markolson/9b9f8b0ae2f6002c05cdb5462779679e to your computer and use it in GitHub Desktop.
Advent VM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$TRACE ||= ENV['TRACE'] | |
$PROFILE ||= ENV['PROFILE'] | |
require 'ruby-prof' if $PROFILE | |
class IVM | |
attr_accessor :memory, :inputs, :ops, :halted | |
attr_accessor :name, :external_ref | |
attr_accessor :input_proc, :output_proc, :has_signal, :halt_proc | |
POSITION_MODE = 0 | |
IMMEDIATE_MODE = 1 | |
RELATIVE_MODE = 2 | |
OPCACHE = [] | |
OP_NAMES = [] | |
def initialize(code) | |
@name = :VM | |
@inputs = [] | |
@input_proc = nil | |
@output_proc = nil | |
@halt_proc = nil | |
@has_signal = false | |
@ops = 0 | |
@ip = 0 | |
@relative_base = 0 | |
@halted = false | |
@memory = code.split(',').map(&:to_i) | |
end | |
def run!(*ins) | |
s = Time.now.to_f | |
@inputs = ins.reverse unless ins.empty? | |
profile = RubyProf::Profile.new(:track_allocations => true).start if $PROFILE | |
while(!@halted) | |
start_ip = @ip.to_s.rjust(memory.length.to_s.length, "0") if $TRACE | |
instruction = consume | |
@opcode, @modes = OPCACHE[instruction] ||= [ | |
instruction % 100, | |
(instruction / 100).to_s.chars.reverse.map(&:to_i) | |
] | |
@mode_idx = 0 | |
print_debug "#{start_ip} #{OP_NAMES[@opcode]} " if $TRACE | |
swizzle(@opcode) | |
print_debug "\n" if $TRACE | |
end | |
if $PROFILE | |
result = profile.stop | |
printer = RubyProf::GraphHtmlPrinter.new(result) | |
printer.print(File.open('out.html', 'w')) | |
end | |
e = Time.now.to_f - s | |
@halt_proc.call if @halt_proc | |
puts "[#{name}]Run took: #{e} for #{@ops} OPS [#{@ops/e}/s]" | |
rescue => e | |
puts e.backtrace | |
puts e | |
puts memory.join(",") | |
end | |
def poke(addr, value) | |
memory[addr] = value | |
end | |
def peek(addr) | |
memory[addr] | |
end | |
def jump(to) | |
print_debug "\n --> @ip=#{to}" if $TRACE | |
@ip = to | |
end | |
def write(address, value) | |
if @modes[@mode_idx].to_i == RELATIVE_MODE | |
address = address + @relative_base | |
print_debug "\n --> [(#{address}+R#{@relative_base}=)#{address + @relative_base}]=#{value} " if $TRACE | |
else | |
print_debug "\n --> [#{address}]=#{value}" if $TRACE | |
end | |
memory[address] = value | |
end | |
def consume | |
@ip += 1 | |
memory[@ip-1] | |
end | |
def last_output | |
@last_out | |
end | |
def read | |
address = consume | |
case @modes[@mode_idx].to_i | |
when IMMEDIATE_MODE | |
value = address | |
print_debug "$#{address} " if $TRACE | |
when POSITION_MODE | |
value = memory[address] | |
print_debug "[#{address}]=#{value} " if $TRACE | |
when RELATIVE_MODE | |
value = memory[address + @relative_base] | |
print_debug "[(#{address}+R#{@relative_base}=)#{address + @relative_base}]=#{value} " if $TRACE | |
end | |
@mode_idx += 1 | |
value.to_i | |
end | |
OP_NAMES[1] = 'add' | |
def run_add(a=read, b=read, to=consume) | |
write(to, a + b) | |
end | |
OP_NAMES[2] = 'mult' | |
def run_mult(a=read, b=read, to=consume) | |
write(to, a * b) | |
end | |
OP_NAMES[3] = 'input' | |
def run_input(to=consume) | |
@input_proc.call if @input_proc | |
if x = inputs.pop | |
puts "[#{name}]IN > #{x}" | |
write(to, x) | |
else | |
print "[#{name}]IN > " | |
input = gets().strip.to_i | |
write(to, input) | |
end | |
end | |
OP_NAMES[4] = 'output' | |
def run_output | |
out = read | |
@last_out = out | |
@output_proc.call if @output_proc | |
puts "\n[#{name}]OUT< " + @last_out.to_s | |
end | |
OP_NAMES[5] = 'jnz' | |
def run_jnz(a=read, to=read) | |
jump(to) if not a.zero? | |
end | |
OP_NAMES[6] = 'jz' | |
def run_jz(a=read, to=read) | |
jump(to) if a.zero? | |
end | |
OP_NAMES[7] = 'lt' | |
def run_lt(a=read, b=read, to=consume) | |
o = a < b ? 1 : 0 | |
write(to, o) | |
end | |
OP_NAMES[8] = 'eq' | |
def run_eq(a=read, b=read, to=consume) | |
o = a == b ? 1 : 0 | |
write(to, o) | |
end | |
OP_NAMES[9] = 'rb' | |
def run_rb(a=read) | |
@relative_base += a | |
print_debug "\n[RB]+=#{a}=#{@relative_base}" if $TRACE | |
end | |
OP_NAMES[99] = 'halt' | |
def run_halt | |
@halted = true | |
end | |
x = "def swizzle(op)\n@ops+=1\ncase op\n" | |
OP_NAMES.each_with_index do |e,i| | |
next if e.nil? | |
x += "when #{i} then run_#{e}\n" | |
end | |
x += "end\nend" | |
class_eval(x) | |
end | |
def print_debug(msg); print msg; end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment