adding the blocks to virtual machine and store instructions in array not list
This commit is contained in:
59
lib/virtual/block.rb
Normal file
59
lib/virtual/block.rb
Normal file
@ -0,0 +1,59 @@
|
||||
require_relative "object"
|
||||
|
||||
module Virtual
|
||||
|
||||
# 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
|
||||
|
||||
# Blocks form a graph, which is managed by the method
|
||||
|
||||
class Block < Virtual::Object
|
||||
|
||||
def initialize(name , method )
|
||||
super()
|
||||
@method = method
|
||||
@name = name.to_sym
|
||||
@branch = nil
|
||||
@codes = []
|
||||
end
|
||||
|
||||
attr_reader :name , :next , :codes , :method
|
||||
attr_accessor :branch
|
||||
|
||||
def reachable ret = []
|
||||
add_next ret
|
||||
add_branch ret
|
||||
ret
|
||||
end
|
||||
|
||||
def add_code kode
|
||||
@codes << kode
|
||||
self
|
||||
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
|
||||
|
||||
private
|
||||
# helper for determining reachable blocks
|
||||
def add_next ret
|
||||
return if @next.nil?
|
||||
return if ret.include? @next
|
||||
ret << @next
|
||||
@next.reachable ret
|
||||
end
|
||||
# helper for determining reachable blocks
|
||||
def add_branch ret
|
||||
return if @branch.nil?
|
||||
return if ret.include? @branch
|
||||
ret << @branch
|
||||
@branch.reachable ret
|
||||
end
|
||||
end
|
||||
end
|
@ -2,22 +2,16 @@ require_relative "object"
|
||||
|
||||
module Virtual
|
||||
|
||||
# Instruction is an abstract for all the code of the object-machine. Derived classe make up the actual functionality
|
||||
# of the machine.
|
||||
# Instruction is an abstract for all the code of the object-machine.
|
||||
# Derived classes make up the actual functionality of the machine.
|
||||
# All functions on the machine are captured as instances of instructions
|
||||
#
|
||||
# It is actully the point of the virtual machine layer to express oo functionality in the set of instructions, thus
|
||||
# defining a minimal set of instructions needed to implement oo.
|
||||
# It is actually the point of the virtual machine layer to express oo functionality in the set of instructions,
|
||||
# thus defining a minimal set of instructions needed to implement oo.
|
||||
|
||||
# This is partly because jumping over this layer and doing in straight in assember was too big a step
|
||||
class Instruction < Virtual::Object
|
||||
attr_accessor :next
|
||||
def attributes
|
||||
[:next]
|
||||
end
|
||||
def initialize nex = nil
|
||||
@next = nex
|
||||
end
|
||||
|
||||
# simple thought: don't recurse for labels, just check their names
|
||||
def == other
|
||||
return false unless other.class == self.class
|
||||
@ -25,7 +19,7 @@ module Virtual
|
||||
left = send(a)
|
||||
right = other.send(a)
|
||||
return false unless left.class == right.class
|
||||
if( left.is_a? Label)
|
||||
if( left.is_a? Block)
|
||||
return false unless left.name == right.name
|
||||
else
|
||||
return false unless left == right
|
||||
@ -33,23 +27,16 @@ module Virtual
|
||||
end
|
||||
return true
|
||||
end
|
||||
# insert the given instruction as the next after this
|
||||
# insert is not just set, but preserves the previous @next value as the next of the given instruction
|
||||
# so if you think of the instructions as a linked list, it inserts the give instruction _after_ this one
|
||||
def insert instruction
|
||||
instruction.next = @next
|
||||
@next = instruction
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
module Named
|
||||
def initialize name , nex = nil
|
||||
super(nex)
|
||||
def initialize name
|
||||
@name = name
|
||||
end
|
||||
attr_reader :name
|
||||
def attributes
|
||||
[:name ] + super
|
||||
[:name ]
|
||||
end
|
||||
end
|
||||
|
||||
@ -66,18 +53,12 @@ module Virtual
|
||||
class MethodReturn < Instruction
|
||||
end
|
||||
|
||||
#resolves to nothing, but allows forward definition
|
||||
class Label < Instruction
|
||||
include Named
|
||||
end
|
||||
|
||||
# the next instruction represents the true branch and the other is the .... other
|
||||
# the next instruction represents the "true" branch and the other is the .... other
|
||||
# could have been the false, but false is a keyword and is asymetric to next anyway
|
||||
# this is an abstract base class (though no measures are taken to prevent instantiation) and derived
|
||||
# class names indicate the actual test
|
||||
class Branch < Instruction
|
||||
def initialize name , nex = nil , other = nil
|
||||
super(nex)
|
||||
def initialize name , other = nil
|
||||
unless(name.to_s.split("_").last.to_i > 0)
|
||||
name = "#{name}_#{name.to_i(36) % 65536}".to_sym
|
||||
end
|
||||
@ -87,14 +68,7 @@ module Virtual
|
||||
attr_reader :name
|
||||
attr_accessor :other
|
||||
def attributes
|
||||
[:name , :next , :other]
|
||||
end
|
||||
# so code can be "poured in" in the same way as normal, we swap the braches around in after the true condition
|
||||
# and swap them back after
|
||||
def swap
|
||||
tmp = @other
|
||||
@other = @next
|
||||
@next = tmp
|
||||
[:name , :other]
|
||||
end
|
||||
end
|
||||
|
||||
@ -111,49 +85,45 @@ module Virtual
|
||||
end
|
||||
|
||||
class MessageSend < Instruction
|
||||
def initialize name , args = [] , nex = nil
|
||||
super(nex)
|
||||
def initialize name , args = []
|
||||
@name = name.to_sym
|
||||
@args = args
|
||||
end
|
||||
attr_reader :name , :args
|
||||
def attributes
|
||||
[:name , :args ] + super
|
||||
[:name , :args ]
|
||||
end
|
||||
end
|
||||
|
||||
class FrameSet < Instruction
|
||||
def initialize name , val , nex = nil
|
||||
super(nex)
|
||||
def initialize name , val
|
||||
@name = name.to_sym
|
||||
@value = val
|
||||
end
|
||||
attr_reader :name , :value
|
||||
def attributes
|
||||
[:name , :value] + super
|
||||
[:name , :value]
|
||||
end
|
||||
end
|
||||
|
||||
class MessageSet < Instruction
|
||||
def initialize name , val , nex = nil
|
||||
super(nex)
|
||||
def initialize name , val
|
||||
@name = name.to_sym
|
||||
@value = val
|
||||
end
|
||||
attr_reader :name , :value
|
||||
def attributes
|
||||
[:name , :value] + super
|
||||
[:name , :value]
|
||||
end
|
||||
end
|
||||
|
||||
class LoadSelf < Instruction
|
||||
def initialize val , nex = nil
|
||||
super(nex)
|
||||
def initialize val
|
||||
@value = val
|
||||
end
|
||||
attr_reader :value
|
||||
def attributes
|
||||
[:value] + super
|
||||
[:value]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -29,3 +29,5 @@ class Node < List
|
||||
@key == key ? @value = value : super(key,value)
|
||||
end
|
||||
end
|
||||
|
||||
# https://www.youtube.com/watch?v=HJ-719EGIts
|
@ -37,25 +37,25 @@ module Virtual
|
||||
#
|
||||
def compile_get method , name
|
||||
if method.has_arg(name)
|
||||
method.add MessageGet.new(name)
|
||||
method.add_code MessageGet.new(name)
|
||||
else
|
||||
method.add FrameGet.new(name)
|
||||
method.add_code FrameGet.new(name)
|
||||
end
|
||||
method.get_var(name)
|
||||
end
|
||||
|
||||
def compile_send method , name , me , with = []
|
||||
method.add Virtual::LoadSelf.new(me)
|
||||
method.add MessageSend.new(name , with )
|
||||
method.add_code Virtual::LoadSelf.new(me)
|
||||
method.add_code MessageSend.new(name , with )
|
||||
Return.new( method.return_type )
|
||||
end
|
||||
|
||||
def compile_set method , name , val
|
||||
method.set_var(name,val)
|
||||
if method.has_arg(name)
|
||||
method.add MessageSet.new(name , val )
|
||||
method.add_code MessageSet.new(name , val )
|
||||
else
|
||||
method.add FrameSet.new(name , val )
|
||||
method.add_code FrameSet.new(name , val )
|
||||
end
|
||||
method.get_var(name)
|
||||
end
|
||||
|
@ -1,4 +1,4 @@
|
||||
require_relative "object"
|
||||
require_relative "block"
|
||||
|
||||
module Virtual
|
||||
# static description of a method
|
||||
@ -9,6 +9,26 @@ module Virtual
|
||||
# known local variable names
|
||||
# temp variables (numbered)
|
||||
#
|
||||
# Methods are similar to Blocks. Where Blocks can be jumped to, Methods can be called.
|
||||
|
||||
# Methods also have arguments and a return. These are Value subclass instances, ie specify
|
||||
# type (by class type) and register by instance
|
||||
|
||||
# They also have local variables. Args take up the first n regs, then locals the rest. No
|
||||
# direct manipulating of registers (ie specifying the number) should be done.
|
||||
|
||||
# Code-wise Methods are made up from a list of Blocks, in a similar way blocks are made up of codes
|
||||
# The function starts with one block, and that has a start and end (return)
|
||||
|
||||
# Blocks can be linked in two ways:
|
||||
# -linear: flow continues from one to the next as they are sequential both logically and "physically"
|
||||
# use the block set_next for this.
|
||||
# This "the straight line", there must be a continuous sequence from body to return
|
||||
# Linear blocks may be created from an existing block with new_block
|
||||
# - branched: You create new blocks using function.new_block which gets added "after" return
|
||||
# These (eg if/while) blocks may themselves have linear blocks ,but the last of these
|
||||
# MUST have an uncoditional branch. And remember, all roads lead to return.
|
||||
|
||||
class MethodDefinition < Virtual::Object
|
||||
#return the main function (the top level) into which code is compiled
|
||||
def MethodDefinition.main
|
||||
@ -17,27 +37,81 @@ module Virtual
|
||||
def attributes
|
||||
[:name , :args , :receiver , :return_type , :start]
|
||||
end
|
||||
def initialize name , args , receiver = Virtual::SelfReference.new , return_type = Virtual::Mystery , start = MethodEnter.new(MethodReturn.new)
|
||||
def initialize name , args , receiver = Virtual::SelfReference.new , return_type = Virtual::Mystery , start = MethodEnter.new()
|
||||
@name = name.to_sym
|
||||
@args = args
|
||||
@locals = []
|
||||
@tmps = []
|
||||
@receiver = receiver
|
||||
@return_type = return_type
|
||||
@start = start
|
||||
@current = @start
|
||||
@blocks = []
|
||||
# first block we have to create with .new , as new_block assumes a current
|
||||
enter = Block.new( name , self ).add_code(start)
|
||||
@blocks << enter
|
||||
@current = enter
|
||||
new_block("return").add_code(MethodReturn.new)
|
||||
end
|
||||
attr_reader :name , :args , :receiver , :start
|
||||
attr_reader :name , :args , :receiver , :blocks
|
||||
attr_accessor :return_type , :current
|
||||
|
||||
# add an instruction after the current (insertion point)
|
||||
# the added instruction will become the new insertion point
|
||||
def add instruction
|
||||
def add_code instruction
|
||||
raise instruction.inspect unless instruction.is_a? Instruction
|
||||
@current.insert(instruction) #insert after current
|
||||
@current = instruction
|
||||
@current.add_code(instruction) #insert after current
|
||||
self
|
||||
end
|
||||
|
||||
# return a list of registers that are still in use after the given block
|
||||
# a call_site uses pushes and pops these to make them available for code after a call
|
||||
def locals_at l_block
|
||||
used =[]
|
||||
# call assigns the return register, but as it is in l_block, it is not asked.
|
||||
assigned = [ RegisterReference.new(Vm::RegisterMachine.instance.return_register) ]
|
||||
l_block.reachable.each do |b|
|
||||
b.uses.each {|u|
|
||||
(used << u) unless assigned.include?(u)
|
||||
}
|
||||
assigned += b.assigns
|
||||
end
|
||||
used.uniq
|
||||
end
|
||||
|
||||
# control structures need to see blocks as a graph, but they are stored as a list with implict branches
|
||||
# So when creating a new block (with new_block), it is only added to the list, but instructions
|
||||
# still go to the current one
|
||||
# With this function one can change the current block, to actually code it.
|
||||
# This juggling is (unfortunately) neccessary, as all compile functions just keep puring their code into the
|
||||
# method and don't care what other compiles (like if's) do.
|
||||
|
||||
# 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 current block
|
||||
@current = block
|
||||
self
|
||||
end
|
||||
|
||||
# create a new linear block after the current insertion block.
|
||||
# Linear means there is no brach needed from that one to the new one.
|
||||
# Usually the new one just serves as jump address for a control statement
|
||||
# In code generation , the new_block is written after this one, ie zero runtime cost
|
||||
# This does _not_ change the insertion point, that has do be done with insert_at(block)
|
||||
def new_block new_name
|
||||
block_name = "#{@current.name}_#{new_name}"
|
||||
new_b = Block.new( block_name , self )
|
||||
index = @blocks.index( @current )
|
||||
@blocks.insert( index + 1 , new_b ) # + one because we want the ne after the insert_at
|
||||
return new_b
|
||||
end
|
||||
|
||||
|
||||
|
||||
# determine whether this method has a variable by the given name
|
||||
# variables are locals and and arguments
|
||||
# used to determine if a send must be issued
|
||||
|
@ -5,12 +5,6 @@ module Virtual
|
||||
#
|
||||
# functions on these classes express their functionality as function objects
|
||||
class Object
|
||||
def initialize
|
||||
@layout = Layout.new( attributes )
|
||||
end
|
||||
def attributes
|
||||
[:layout]
|
||||
end
|
||||
def == other
|
||||
return false unless other.class == self.class
|
||||
attributes.each do |a|
|
||||
|
64
lib/virtual/plock.rb
Normal file
64
lib/virtual/plock.rb
Normal file
@ -0,0 +1,64 @@
|
||||
module Virtual
|
||||
# Plock (Proc-Block) is mostly a Block but also somewhat Proc-ish: A Block that carries data.
|
||||
#
|
||||
# Data in a Block is usefull in the same way data in objects is. Plocks being otherwise just code.
|
||||
#
|
||||
# But the concept is not quite straigtforwrd: If one thinks of a Plock embedded in a normal method,
|
||||
# the a data in the Plock would be static data. In OO terms this comes quite close to a Proc, if the data is the local
|
||||
# variables. Quite possibly they shall be used to implement procs, but that is not the direction now.
|
||||
#
|
||||
# For now we use Plocks behaind the scenes as it were. In the code that you never see, method invocation mainly.
|
||||
#
|
||||
# In terms of implementation the Plock is a Block with data (Not too much data, mainly a couple of references).
|
||||
# The block writes it's instructions as normal, but a jump is inserted as the last instruction. The jump is to the
|
||||
# next block, over the data that is inserted after the block code (and so before the next)
|
||||
#
|
||||
# It follows that Plocks should be linear blocks.
|
||||
class Plock < Block
|
||||
|
||||
def initialize(name , method , next_block )
|
||||
super
|
||||
@data = []
|
||||
@branch_code = RegisterMachine.instance.b next_block
|
||||
end
|
||||
|
||||
def set_next next_b
|
||||
super
|
||||
@branch_code = RegisterMachine.instance.b next_block
|
||||
end
|
||||
|
||||
# Data gets assembled after methods
|
||||
def add_data o
|
||||
return if @objects.include? o
|
||||
raise "must be derived from Code #{o.inspect}" unless o.is_a? Vm::Code
|
||||
@data << o # TODO check type , no basic values allowed (must be wrapped)
|
||||
end
|
||||
|
||||
# Code interface follows. Note position is inheitted as is from Code
|
||||
|
||||
# length of the Plock is the length of the block, plus the branch, plus data.
|
||||
def length
|
||||
len = @data.inject(super) {| sum , item | sum + item.length}
|
||||
len + @branch_code.length
|
||||
end
|
||||
|
||||
# again, super + branch plus data
|
||||
def link_at pos , context
|
||||
super(pos , context)
|
||||
@branch_code.link_at pos , context
|
||||
@data.each do |code|
|
||||
code.link_at(pos , context)
|
||||
pos += code.length
|
||||
end
|
||||
end
|
||||
|
||||
# again, super + branch plus data
|
||||
def assemble(io)
|
||||
super
|
||||
@branch_code.assemble(io)
|
||||
@data.each do |obj|
|
||||
obj.assemble io
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user