rubyx/lib/vm/block.rb

156 lines
5.1 KiB
Ruby
Raw Normal View History

2014-05-03 14:13:44 +02:00
require_relative "values"
module Vm
# Think flowcharts: blocks are the boxes. The smallest unit of linear code
# Blocks must end in control instructions (jump/call/return).
# And the only valid argument for a jump is a Block
2014-05-05 08:35:40 +02:00
# Blocks form a linked list
2014-05-03 14:13:44 +02:00
# There are four ways for a block to get data (to work on)
# - hard coded constants (embedded in code)
# - memory move
# - values passed in (from previous blocks. ie local variables)
# See Value description on how to create code/instructions
# Codes then get assembled into bytes (after linking)
2014-05-05 08:35:40 +02:00
2014-05-03 21:18:04 +02:00
class Block < Code
2014-05-03 14:13:44 +02:00
def initialize(name , function , next_block )
2014-05-03 14:13:44 +02:00
super()
@function = function
2014-05-03 14:13:44 +02:00
@name = name.to_sym
@next = next_block
@branch = nil
2014-05-03 17:51:47 +02:00
@codes = []
@insert_at = self
# keeping track of register usage, left (assigns) or right (uses)
@assigns = []
@uses = []
2014-05-03 14:13:44 +02:00
end
attr_reader :name , :next , :codes , :function , :assigns , :uses
attr_accessor :branch
def reachable
ret = []
add_next ret
add_branch ret
ret
end
2014-05-03 14:13:44 +02:00
2014-05-03 21:18:04 +02:00
def add_code(kode)
raise "alarm #{kode}" if kode.is_a? Word
raise "alarm #{kode.class} #{kode}" unless kode.is_a? Code
@insert_at.do_add kode
2014-05-05 08:35:40 +02:00
self
2014-05-03 21:18:04 +02:00
end
def do_add kode
kode.assigns.each { |a| (@assigns << a) unless @assigns.include?(a) }
kode.uses.each { |use| (@uses << use) unless (@assigns.include?(use) or @uses.include?(use)) }
#puts "IN ADD #{name}#{uses}"
@codes << kode
end
alias :<< :add_code
2014-05-03 21:18:04 +02:00
# create a new linear block after this block. Linear means there is no brach needed from this one
# to the new one. Usually the new one just serves as jump address for a control statement
# In code generation (assembly) , new new_block is written after this one, ie zero runtime cost
def new_block new_name
new_b = Block.new( new_name , @function , @insert_at.next )
@insert_at.set_next new_b
return new_b
2014-05-03 21:18:04 +02:00
end
def set_next next_b
@next = next_b
end
# when control structures create new blocks (with new_block) control continues at some new block the
# the control structure creates.
# Example: while, needs 2 extra blocks
# 1 condition code, must be its own blockas we jump back to it
# - the body, can actually be after the condition as we don't need to jump there
# 2 after while block. Condition jumps here
# After block 2, the function is linear again and the calling code does not need to know what happened
# But subsequent statements are still using the original block (self) to add code to
# So the while expression creates the extra blocks, adds them and the code and then "moves" the insertion point along
def insert_at block
@insert_at = block
self
end
# sugar to create instructions easily.
# any method will be passed on to the RegisterMachine and the result added to the block
2014-05-20 10:03:18 +02:00
# With this trick we can write what looks like assembler,
# Example b.instance_eval
# mov( r1 , r2 )
# add( r1 , r2 , 4)
# end
# mov and add will be called on Machine and generate Inststuction that are then added
# to the block
# also symbols are supported and wrapped as register usages (for bare metal programming)
def method_missing(meth, *args, &block)
2014-05-21 18:43:46 +02:00
add_code RegisterMachine.instance.send(meth , *args)
end
# returns if this is a block that ends in a call (and thus needs local variable handling)
def call_block?
return false unless codes.last.is_a?(CallInstruction)
return false unless codes.last.opcode == :call
codes.dup.reverse.find{ |c| c.is_a? StackInstruction }
end
# Code interface follows. Note position is inheitted as is from Code
2014-05-03 14:13:44 +02:00
# length of the block is the length of it's codes, plus any next block (ie no branch follower)
# Note, the next is in effect a linked list and as such may have many blocks behind it.
def length
cods = @codes.inject(0) {| sum , item | sum + item.length}
cods += @next.length if @next
cods
end
# to link we link the codes (instructions), plus any next in line block (non- branched)
def link_at pos , context
super(pos , context)
@codes.each do |code|
code.link_at(pos , context)
pos += code.length
end
if @next
@next.link_at pos , context
pos += @next.length
end
pos
end
# assemble the codes (instructions) and any next in line block
def assemble(io)
@codes.each do |obj|
obj.assemble io
end
@next.assemble(io) if @next
end
private
# helper for determining reachable blocks
def add_next ret
return if @next.nil?
return if ret.include? @next
ret << @next
ret + @next.reachable
end
# helper for determining reachable blocks
def add_branch ret
return if @branch.nil?
return if ret.include? @branch
ret << @branch
ret + @branch.reachable
end
end
2014-05-03 14:13:44 +02:00
end