Skip to content

Instantly share code, notes, and snippets.

@markolson
Last active December 9, 2019 05:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markolson/9b9f8b0ae2f6002c05cdb5462779679e to your computer and use it in GitHub Desktop.
Save markolson/9b9f8b0ae2f6002c05cdb5462779679e to your computer and use it in GitHub Desktop.
Advent VM
$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