implementing
software machines
in
Ruby
Eleanor McHugh
@feyeleanor
http://github.com/feyeleanor
Rough
Cut
timely
stateful
conversational
discrete
despatch loops
fetch instruction
decode
execute
case...when
strings
inline values
class VM
def initialize(*program)
@program = program
end
def interpret
@s = []
@pc = 0
loop do
case read_program.downcase
when "push"
@s.push(read_program)
when "add"
@s.push(@s.pop + @s.pop)
when "print"
puts @s.pop
when "exit"
return
end
end
end
private def read_program
r = @program[@pc]
@pc += 1
r
end
end
vm = VM.new(
"push", 13,
"push", 28,
"add",
"print",
"exit",
)
vm.interpret
case...when
strings
inline values
class VM
def initialize(*program)
@program = program
end
def interpret
begin
@s = []
@pc = 0
loop do
case read_program.downcase
when "push"
@s.push(read_program)
when "add"
@s.push(@s.pop + @s.pop)
when "print"
puts @s.pop
end
end
rescue NoMethodError
end
end
private def read_program
r = @program[@pc]
@pc += 1
r
end
end
vm = VM.new(
"push", 13,
"push", 28,
"add",
"print",
)
vm.interpret
case...when
symbols
inline values
class VM
def initialize(*program)
@program = program
end
def interpret
begin
@s = []
@pc = 0
loop do
case read_program
when nil
raise NoMethodError
when :push
@s.push(read_program)
when :add
@s.push(@s.pop + @s.pop)
when :print
puts @s.pop
end
end
rescue NoMethodError
end
end
private def read_program
r = @program[@pc]
@pc += 1
r
end
end
vm = VM.new(
:push, 13,
:push, 28,
:add,
:print,
)
vm.interpret
case...when
symbols
fi
rst-class values
class VM
def initialize(*program)
@program = program
end
def interpret
begin
@s = []
@pc = 0
loop do
case (v = read_program)
when nil
raise NoMethodError
when :add
@s.push(@s.pop + @s.pop)
when :print
puts@s.pop
else
@s.push v
end
end
rescue NoMethodError
end
end
private def read_program
r = @program[@pc]
@pc += 1
r
end
end
vm = VM.new(
13,
28,
:add,
:print,
)
vm.interpret
method call
token.call
class VM
def initialize *program
@program = program
end
def interpret
catch :program_complete do
@s = []
@pc = 0
loop do
method(read_program).call
end
end
end
def push
@s.push(read_program)
end
def add_and_print
puts(@s.pop + @s.pop)
end
def exit
throw :program_complete
end
private def read_program
r = @program[@pc]
exit unless r
@pc += 1
r
end
end
VM.new(
:push, 13,
:push, 28,
:add_and_print
).interpret
method call
token.call
class VM
def initialize *program
@program = program
end
def interpret
catch :program_complete do
@s = []
@pc = 0
loop do
self.public_send read_program
end
end
end
def push
@s.push(read_program)
end
def add_and_print
puts(@s.pop + @s.pop)
end
def exit
throw :program_complete
end
private def read_program
r = @program[@pc]
exit unless r
@pc += 1
r
end
end
VM.new(
:push, 13,
:push, 28,
:add_and_print
).interpret
method call
compile tokens
token.call
class VM
def initialize *program
@program = begin
compile program
rescue SecurityError
[]
end
end
def interpret
catch :program_complete do
@s = []
@pc = 0
loop do
read_program.call
end
end
end
# insert opcode methods here
def invalid
raise "!!! I'M NOT A VALID OP_CODE !!!"
end
def exit
throw :program_complete
end
private
def read_program
r = @program[@pc]
exit unless r
@pc += 1
r
end
def compile program
program.collect do |v|
raise SecurityError if v == :invalid
begin
self.respond_to?(v) ?
self.method(v) : v
rescue TypeError
v
end
end
end
end
method call
compile tokens
token.call
puts "dangerous program"
VM.new(
:push, 13,
:push, 28,
:invalid,
:add_and_print
).interpret
puts "safe program"
VM.new(
:push, 13,
:push, 28,
:add_and_print
).interpret
method call
compilation
sandboxing
class VM
BLACKLIST = [:load, :compile, :interpret, :invalid] + Object.methods
def initialize *program
load(program)
end
def load program
@program = compile(program)
self
end
def compile program
program.collect do |v|
case
when v.is_a?(Method)
raise "method injection of #{v.inspect} is not supported" if BLACKLIST.include?(v.name)
raise "unknown method #{v.inspect}" unless methods.include?(v.name)
v = v.unbind
v.bind(self)
when methods.include?(v)
raise "direct execution of #{v} is forbidden" if BLACKLIST.include?(v)
self.method(v)
else
v
end
end
end
def invalid; end
method call
compilation
sandboxing
def interpret
catch :program_complete do
@s = []
@pc = 0
loop do
read_program.call
end
end
end
def push
@s.push(read_program)
end
def add_and_print
puts(@s.pop + @s.pop)
end
def read_program
r = @program[@pc]
exit unless r
@pc += 1
r
end
def exit
throw :program_complete
end
end
begin
VM.new(:invalid)
rescue Exception => e
puts "program compilation failed: #{e}"
end
vm = VM.new
p = vm.compile([
:push, 13,
:push, 28,
:add_and_print,
:push, 10,
:push, -1,
vm.method(:add_and_print)
])
begin
VM.new(*p)
rescue Exception => e
puts "program compilation failed: #{e}"
end
vm.load(p).interpret
VM.new(*p).interpret
indirect threading
self.public_send
class VM
def initialize *program
@program = program
end
def interpret
catch :program_complete do
@s = []
@pc = 0
read_program
end
end
def push
@s.push next_operand
end
def add_and_print
puts(@s.pop + @s.pop)
end
def exit
throw :program_complete
end
def at_pc = @program[@pc]
private def read_program
r = at_pc
exit unless r
increment_pc
public_send r
read_program
end
def increment_pc n = 1
@pc += n
end
def next_operand
r = at_pc
increment_pc
r
end
end
VM.new(
:push, 13,
:push, 28,
:add_and_print
).interpret
registers
operands
local caching
vm harness
dispatch
program
program counter
class VM
BL = [:load, :compile, :interpret] + Object.methods
def initialize *program
load(program)
end
def compile program
program.collect do |v|
case
when v.is_a?(Method)
if BL.include?(v.name)
raise "forbidden method: #{v.name}"
end
unless methods.include?(v.name)
raise "unknown method: #{v.name}"
end
v = v.unbind
v.bind(self)
when methods.include?(v)
if BL.include?(v)
raise "forbidden method: #{v}"
end
self.method(v)
else
v
end
end
end
def load program
@program = compile(program)
self
end
def interpret
catch :program_complete do
@pc = 0
read_program
end
end
def at_pc = @program[@pc]
private def read_program
r = at_pc
exit unless r
increment_pc
r.call
read_program
end
def increment_pc n = 1
@pc += n
end
def next_operand
r = at_pc
increment_pc
r
end
end
stack machine
zero operands
class Adder < VM
def interpret
@s = []
super
end
def print_state
puts "#{@pc}: @s => #{@s}"
end
def push
@s.push next_operand
end
def add
@s.push(@s.pop + @s.pop)
end
def exit
throw :program_complete
end
def jump_if_not_zero
@s[-1] == 0 ? increment_pc :
@pc = at_pc
end
end
Adder.new(
:push, 13,
:push, -1,
:add,
:print_state,
:jump_if_not_zero, 2
).interpret
accumulator
machine
single register
single operand
class Adder < VM
def interpret
@s = []
@a = 0
super
end
def print_state
puts "#{@pc}: @a = #{@a},
@s => #{@s}"
end
def push_value
@s.push next_operand
end
def push
@s.push @a
end
def add
@a += @s.pop
end
def jump_if_not_zero
@a == 0 ? increment_pc : @pc = at_pc
end
def exit
throw :program_complete
end
end
Adder.new(
:print_state,
:push_value, 13,
:print_state,
:add,
:print_state,
:push_value, -1,
:print_state,
:add,
:print_state,
:jump_if_not_zero, 6
).interpret
register machine
multi-register
multi-operand
class Adder < VM
def interpret
@r = Array.new 2, 0
super
end
def load_value
@r[next_operand] = next_operand
end
def add
@r[next_operand] += @r[next_operand]
end
def jump_if_not_zero
r = at_pc
increment_pc
@r[next_operand] == 0 ?
increment_pc :
@pc = at_pc
end
def exit
throw :program_complete
end
def print_state
puts "#{@pc}: @r => #{@r}"
end
end
Adder.new(
:load_value, 0, 13,
:load_value, 1, -1,
:print_state,
:add, 0, 1,
:print_state,
:jump_if_not_zero, 0, 6,
:print_state
).interpret
register machine
multi-register
multi-operand
class Adder < VM
def interpret
@r = { a: 0, b: 0 }
super
end
def load_value
@r[next_operand] = next_operand
end
def add
@r[next_operand] += @r[next_operand]
end
def jump_if_not_zero
@r[next_operand] == 0 ?
increment_pc :
@pc = at_pc
end
def exit
throw :program_complete
end
def print_state
puts "#{@pc}: @r => #{@r}"
end
end
Adder.new(
:load_value, :a, 13,
:load_value, :b, -1,
:print_state,
:add, :a, :b,
:print_state,
:jump_if_not_zero, :a, 6,
:print_state
).interpret
vector machine
matrix machine
hypercube
graph processor
class Adder < VM
VECTOR_LENGTH = 8
def interpret
@r = { a: 0 }
@rvx = { av: new_vector, bv: new_vector }
super
end
def print_state
puts "#{@pc}: @r => #{@r}, @rvx => #{@rvx}"
end
def load_vector
r = @rvx[next_operand]
VECTOR_LENGTH.times do |i|
break if (v = next_operand) == :vector_end
r[i] = v
end
end
def add
s = @rvx[next_operand]
d = @rvx[next_operand]
VECTOR_LENGTH.times do |i|
d[i] += s[i]
end
end
def sum
@r[next_operand] = @rvx[next_operand].sum
end
def exit
throw :program_complete
end
def jump_if_not_zero
@r[next_operand] == 0 ?
increment_pc :
@pc = at_pc
end
def new_vector
Array.new(VECTOR_LENGTH, 0)
end
end
Adder.new(
:load_vector, :av, 12, :vector_end,
:print_state,
:load_vector, :bv, -1, -2, -3, :vector_end,
:print_state,
:add, :bv, :av,
:print_state,
:sum, :a, :av,
:jump_if_not_zero, :a, 5,
:print_state
).interpret

Implementing Software Machines in Ruby [Rough Cut]

  • 1.
  • 4.
  • 5.
  • 6.
    case...when strings inline values class VM definitialize(*program) @program = program end def interpret @s = [] @pc = 0 loop do case read_program.downcase when "push" @s.push(read_program) when "add" @s.push(@s.pop + @s.pop) when "print" puts @s.pop when "exit" return end end end private def read_program r = @program[@pc] @pc += 1 r end end vm = VM.new( "push", 13, "push", 28, "add", "print", "exit", ) vm.interpret
  • 7.
    case...when strings inline values class VM definitialize(*program) @program = program end def interpret begin @s = [] @pc = 0 loop do case read_program.downcase when "push" @s.push(read_program) when "add" @s.push(@s.pop + @s.pop) when "print" puts @s.pop end end rescue NoMethodError end end private def read_program r = @program[@pc] @pc += 1 r end end vm = VM.new( "push", 13, "push", 28, "add", "print", ) vm.interpret
  • 8.
    case...when symbols inline values class VM definitialize(*program) @program = program end def interpret begin @s = [] @pc = 0 loop do case read_program when nil raise NoMethodError when :push @s.push(read_program) when :add @s.push(@s.pop + @s.pop) when :print puts @s.pop end end rescue NoMethodError end end private def read_program r = @program[@pc] @pc += 1 r end end vm = VM.new( :push, 13, :push, 28, :add, :print, ) vm.interpret
  • 9.
    case...when symbols fi rst-class values class VM definitialize(*program) @program = program end def interpret begin @s = [] @pc = 0 loop do case (v = read_program) when nil raise NoMethodError when :add @s.push(@s.pop + @s.pop) when :print puts@s.pop else @s.push v end end rescue NoMethodError end end private def read_program r = @program[@pc] @pc += 1 r end end vm = VM.new( 13, 28, :add, :print, ) vm.interpret
  • 10.
    method call token.call class VM definitialize *program @program = program end def interpret catch :program_complete do @s = [] @pc = 0 loop do method(read_program).call end end end def push @s.push(read_program) end def add_and_print puts(@s.pop + @s.pop) end def exit throw :program_complete end private def read_program r = @program[@pc] exit unless r @pc += 1 r end end VM.new( :push, 13, :push, 28, :add_and_print ).interpret
  • 11.
    method call token.call class VM definitialize *program @program = program end def interpret catch :program_complete do @s = [] @pc = 0 loop do self.public_send read_program end end end def push @s.push(read_program) end def add_and_print puts(@s.pop + @s.pop) end def exit throw :program_complete end private def read_program r = @program[@pc] exit unless r @pc += 1 r end end VM.new( :push, 13, :push, 28, :add_and_print ).interpret
  • 12.
    method call compile tokens token.call classVM def initialize *program @program = begin compile program rescue SecurityError [] end end def interpret catch :program_complete do @s = [] @pc = 0 loop do read_program.call end end end # insert opcode methods here def invalid raise "!!! I'M NOT A VALID OP_CODE !!!" end def exit throw :program_complete end private def read_program r = @program[@pc] exit unless r @pc += 1 r end def compile program program.collect do |v| raise SecurityError if v == :invalid begin self.respond_to?(v) ? self.method(v) : v rescue TypeError v end end end end
  • 13.
    method call compile tokens token.call puts"dangerous program" VM.new( :push, 13, :push, 28, :invalid, :add_and_print ).interpret puts "safe program" VM.new( :push, 13, :push, 28, :add_and_print ).interpret
  • 14.
    method call compilation sandboxing class VM BLACKLIST= [:load, :compile, :interpret, :invalid] + Object.methods def initialize *program load(program) end def load program @program = compile(program) self end def compile program program.collect do |v| case when v.is_a?(Method) raise "method injection of #{v.inspect} is not supported" if BLACKLIST.include?(v.name) raise "unknown method #{v.inspect}" unless methods.include?(v.name) v = v.unbind v.bind(self) when methods.include?(v) raise "direct execution of #{v} is forbidden" if BLACKLIST.include?(v) self.method(v) else v end end end def invalid; end
  • 15.
    method call compilation sandboxing def interpret catch:program_complete do @s = [] @pc = 0 loop do read_program.call end end end def push @s.push(read_program) end def add_and_print puts(@s.pop + @s.pop) end def read_program r = @program[@pc] exit unless r @pc += 1 r end def exit throw :program_complete end end begin VM.new(:invalid) rescue Exception => e puts "program compilation failed: #{e}" end vm = VM.new p = vm.compile([ :push, 13, :push, 28, :add_and_print, :push, 10, :push, -1, vm.method(:add_and_print) ]) begin VM.new(*p) rescue Exception => e puts "program compilation failed: #{e}" end vm.load(p).interpret VM.new(*p).interpret
  • 16.
    indirect threading self.public_send class VM definitialize *program @program = program end def interpret catch :program_complete do @s = [] @pc = 0 read_program end end def push @s.push next_operand end def add_and_print puts(@s.pop + @s.pop) end def exit throw :program_complete end def at_pc = @program[@pc] private def read_program r = at_pc exit unless r increment_pc public_send r read_program end def increment_pc n = 1 @pc += n end def next_operand r = at_pc increment_pc r end end VM.new( :push, 13, :push, 28, :add_and_print ).interpret
  • 17.
  • 18.
    vm harness dispatch program program counter classVM BL = [:load, :compile, :interpret] + Object.methods def initialize *program load(program) end def compile program program.collect do |v| case when v.is_a?(Method) if BL.include?(v.name) raise "forbidden method: #{v.name}" end unless methods.include?(v.name) raise "unknown method: #{v.name}" end v = v.unbind v.bind(self) when methods.include?(v) if BL.include?(v) raise "forbidden method: #{v}" end self.method(v) else v end end end def load program @program = compile(program) self end def interpret catch :program_complete do @pc = 0 read_program end end def at_pc = @program[@pc] private def read_program r = at_pc exit unless r increment_pc r.call read_program end def increment_pc n = 1 @pc += n end def next_operand r = at_pc increment_pc r end end
  • 19.
    stack machine zero operands classAdder < VM def interpret @s = [] super end def print_state puts "#{@pc}: @s => #{@s}" end def push @s.push next_operand end def add @s.push(@s.pop + @s.pop) end def exit throw :program_complete end def jump_if_not_zero @s[-1] == 0 ? increment_pc : @pc = at_pc end end Adder.new( :push, 13, :push, -1, :add, :print_state, :jump_if_not_zero, 2 ).interpret
  • 20.
    accumulator machine single register single operand classAdder < VM def interpret @s = [] @a = 0 super end def print_state puts "#{@pc}: @a = #{@a}, @s => #{@s}" end def push_value @s.push next_operand end def push @s.push @a end def add @a += @s.pop end def jump_if_not_zero @a == 0 ? increment_pc : @pc = at_pc end def exit throw :program_complete end end Adder.new( :print_state, :push_value, 13, :print_state, :add, :print_state, :push_value, -1, :print_state, :add, :print_state, :jump_if_not_zero, 6 ).interpret
  • 21.
    register machine multi-register multi-operand class Adder< VM def interpret @r = Array.new 2, 0 super end def load_value @r[next_operand] = next_operand end def add @r[next_operand] += @r[next_operand] end def jump_if_not_zero r = at_pc increment_pc @r[next_operand] == 0 ? increment_pc : @pc = at_pc end def exit throw :program_complete end def print_state puts "#{@pc}: @r => #{@r}" end end Adder.new( :load_value, 0, 13, :load_value, 1, -1, :print_state, :add, 0, 1, :print_state, :jump_if_not_zero, 0, 6, :print_state ).interpret
  • 22.
    register machine multi-register multi-operand class Adder< VM def interpret @r = { a: 0, b: 0 } super end def load_value @r[next_operand] = next_operand end def add @r[next_operand] += @r[next_operand] end def jump_if_not_zero @r[next_operand] == 0 ? increment_pc : @pc = at_pc end def exit throw :program_complete end def print_state puts "#{@pc}: @r => #{@r}" end end Adder.new( :load_value, :a, 13, :load_value, :b, -1, :print_state, :add, :a, :b, :print_state, :jump_if_not_zero, :a, 6, :print_state ).interpret
  • 23.
    vector machine matrix machine hypercube graphprocessor class Adder < VM VECTOR_LENGTH = 8 def interpret @r = { a: 0 } @rvx = { av: new_vector, bv: new_vector } super end def print_state puts "#{@pc}: @r => #{@r}, @rvx => #{@rvx}" end def load_vector r = @rvx[next_operand] VECTOR_LENGTH.times do |i| break if (v = next_operand) == :vector_end r[i] = v end end def add s = @rvx[next_operand] d = @rvx[next_operand] VECTOR_LENGTH.times do |i| d[i] += s[i] end end def sum @r[next_operand] = @rvx[next_operand].sum end def exit throw :program_complete end def jump_if_not_zero @r[next_operand] == 0 ? increment_pc : @pc = at_pc end def new_vector Array.new(VECTOR_LENGTH, 0) end end Adder.new( :load_vector, :av, 12, :vector_end, :print_state, :load_vector, :bv, -1, -2, -3, :vector_end, :print_state, :add, :bv, :av, :print_state, :sum, :a, :av, :jump_if_not_zero, :a, 5, :print_state ).interpret