fold last of the virtual into register
This commit is contained in:
@ -238,5 +238,5 @@ module Register
|
||||
end
|
||||
end
|
||||
|
||||
Sof::Volotile.add(Register::Assembler , [:objects])
|
||||
Sof::Volotile.add(Assembler , [:objects])
|
||||
end
|
||||
|
90
lib/register/block.rb
Normal file
90
lib/register/block.rb
Normal file
@ -0,0 +1,90 @@
|
||||
module Register
|
||||
|
||||
# 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
|
||||
|
||||
def initialize(name , method )
|
||||
super()
|
||||
@method = method
|
||||
raise "Method is not Method, but #{method.class}" unless method == :__init__ or method.is_a?(Parfait::Method)
|
||||
@name = name.to_sym
|
||||
@branch = nil
|
||||
@codes = []
|
||||
end
|
||||
|
||||
attr_reader :name , :codes , :method , :position
|
||||
attr_accessor :branch
|
||||
|
||||
def add_code kode
|
||||
@codes << kode
|
||||
self
|
||||
end
|
||||
|
||||
# replace a code with an array of new codes. This is what happens in passes all the time
|
||||
def replace code , new_codes
|
||||
index = @codes.index code
|
||||
raise "Code not found #{code} in #{self}" unless index
|
||||
@codes.delete_at(index)
|
||||
if( new_codes.is_a? Array)
|
||||
new_codes.reverse.each {|c| @codes.insert(index , c)}
|
||||
else
|
||||
@codes.insert(index , new_codes)
|
||||
end
|
||||
end
|
||||
|
||||
# returns if this is a block that ends in a call (and thus needs local variable handling)
|
||||
def call_block?
|
||||
raise "called"
|
||||
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
|
||||
|
||||
# position is what another block uses to jump to. this is determined by the assembler
|
||||
# the assembler allso assembles and assumes a linear instruction sequence
|
||||
# Note: this will have to change for plocks and maybe anyway.
|
||||
def set_position at
|
||||
@position = at
|
||||
@codes.each do |code|
|
||||
begin
|
||||
code.set_position( at)
|
||||
rescue => e
|
||||
puts "BLOCK #{self.to_s[0..5000]}"
|
||||
raise e
|
||||
end
|
||||
raise code.inspect unless code.byte_length
|
||||
at += code.byte_length
|
||||
end
|
||||
end
|
||||
|
||||
def byte_length
|
||||
@codes.inject(0){|count , instruction| count += instruction.byte_length }
|
||||
end
|
||||
|
||||
# def reachable ret = []
|
||||
# add_next ret
|
||||
# add_branch ret
|
||||
# ret
|
||||
# end
|
||||
# # 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
|
166
lib/register/boot.rb
Normal file
166
lib/register/boot.rb
Normal file
@ -0,0 +1,166 @@
|
||||
module Register
|
||||
|
||||
# Booting is a complicated, so it is extracted into this file, even it has only one entry point
|
||||
|
||||
class Machine
|
||||
|
||||
# The general idea is that compiling is creating an object graph. Functionally
|
||||
# one tends to think of methods, and that is complicated enough, sure.
|
||||
# But for an object system the graph includes classes and all instance variables
|
||||
#
|
||||
# And so we have a chicken and egg problem. At the end of the boot function we want to have a
|
||||
# working Space object
|
||||
# But that has instance variables (List and Dictionary) and off course a class.
|
||||
# Or more precisely in salama, a Layout, that points to a class.
|
||||
# So we need a Layout, but that has Layout and Class too. hmmm
|
||||
#
|
||||
# The way out is to build empty shell objects and stuff the neccessary data into them
|
||||
# (not use the normal initialize way)
|
||||
# (PPS: The "real" solution is to read a sof graph and not do this by hand
|
||||
# That graph can be programatically built and written (with this to boot that process :-))
|
||||
|
||||
# There are some helpers below, but the roadmap is something like:
|
||||
# - create all the layouts, with thier layouts, but no classes
|
||||
# - create a space by "hand" , using allocate, not new
|
||||
# - create the class objects and assign them to the layouts
|
||||
def boot_parfait!
|
||||
boot_layouts
|
||||
boot_space
|
||||
boot_classes
|
||||
|
||||
@space.late_init
|
||||
|
||||
#puts Sof.write(@space)
|
||||
boot_functions!
|
||||
end
|
||||
|
||||
# layouts is where the snake bites its tail. Every chain end at a layout and then it
|
||||
# goes around (circular references). We create them from the list below and keep them
|
||||
# in an instance variable (that is a smell, because after booting it is not needed)
|
||||
def boot_layouts
|
||||
@layouts = {}
|
||||
layout_names.each do |name , ivars |
|
||||
@layouts[name] = layout_for( name , ivars)
|
||||
end
|
||||
layout_layout = @layouts[:Layout]
|
||||
@layouts.each do |name , layout |
|
||||
layout.set_layout(layout_layout)
|
||||
end
|
||||
end
|
||||
|
||||
# once we have the layouts we can create the space by creating the instance variables
|
||||
# by hand (can't call new yet as that uses the space)
|
||||
def boot_space
|
||||
space_dict = object_with_layout Parfait::Dictionary
|
||||
space_dict.keys = object_with_layout Parfait::List
|
||||
space_dict.values = object_with_layout Parfait::List
|
||||
|
||||
@space = object_with_layout Parfait::Space
|
||||
@space.classes = space_dict
|
||||
Parfait::Space.set_object_space @space
|
||||
end
|
||||
|
||||
# when running code instantiates a class, a layout is created automatically
|
||||
# but even to get our space up, we have already instantiated all layouts
|
||||
# so we have to continue and allocate classes and fill the data by hand
|
||||
# and off cource we can't use space.create_class , but still they need to go there
|
||||
def boot_classes
|
||||
classes = space.classes
|
||||
layout_names.each do |name , vars|
|
||||
cl = object_with_layout Parfait::Class
|
||||
cl.object_layout = @layouts[name]
|
||||
@layouts[name].object_class = cl
|
||||
cl.instance_methods = object_with_layout Parfait::List
|
||||
# puts "instance_methods is #{cl.instance_methods.class}"
|
||||
cl.name = name
|
||||
classes[name] = cl
|
||||
end
|
||||
object_class = classes[:Object]
|
||||
# superclasses other than default object
|
||||
supers = { :BinaryCode => :Word , :Layout => :List , :Class => :Module ,
|
||||
:Object => :Kernel , :Kernel => :Value, :Integer => :Value }
|
||||
layout_names.each do |classname , ivar|
|
||||
next if classname == :Value # has no superclass
|
||||
clazz = classes[classname]
|
||||
super_name = supers[classname]
|
||||
if super_name
|
||||
clazz.set_super_class classes[super_name]
|
||||
else
|
||||
clazz.set_super_class object_class
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# helper to create a Layout, name is the parfait name, ie :Layout
|
||||
def layout_for( name , ivars )
|
||||
l = Parfait::Layout.allocate.fake_init
|
||||
l.add_instance_variable :layout
|
||||
ivars.each {|n| l.add_instance_variable n }
|
||||
l
|
||||
end
|
||||
|
||||
# create an object with layout (ie allocate it and assign layout)
|
||||
# meaning the lauouts have to be booted, @layouts filled
|
||||
# here we pass the actual (ruby) class
|
||||
def object_with_layout(cl)
|
||||
o = cl.allocate.fake_init
|
||||
name = cl.name.split("::").last.to_sym
|
||||
o.set_layout @layouts[name]
|
||||
o
|
||||
end
|
||||
|
||||
# the function really just returns a constant (just avoiding the constant)
|
||||
# unfortuantely that constant condenses every detail about the system, class names
|
||||
# and all instance variable names. Really have to find a better way
|
||||
def layout_names
|
||||
{ :Word => [] ,
|
||||
:List => [] ,
|
||||
# Assumtion is that name is the last of message
|
||||
:Message => [:next_message , :receiver , :frame , :return_address , :return_value,
|
||||
:caller , :name ],
|
||||
:MetaClass => [],
|
||||
:Integer => [],
|
||||
:Object => [],
|
||||
:Kernel => [], #fix, kernel is a class, but should be a module
|
||||
:BinaryCode => [],
|
||||
:Space => [:classes , :first_message ],
|
||||
:Frame => [:next_frame ],
|
||||
:Layout => [:object_class] ,
|
||||
# TODO fix layouts for inherited classes. Currently only :Class and the
|
||||
# instances are copied (shame on you)
|
||||
:Class => [:object_layout , :name , :instance_methods , :super_class , :meta_class],
|
||||
:Dictionary => [:keys , :values ] ,
|
||||
:Method => [:name , :code ,:arguments , :for_class, :locals ] ,
|
||||
:Variable => [:type , :name , :value ] ,
|
||||
:Module => [:name , :instance_methods , :super_class , :meta_class ]
|
||||
}
|
||||
end
|
||||
|
||||
# classes have booted, now create a minimal set of functions
|
||||
# minimal means only that which can not be coded in ruby
|
||||
# Methods are grabbed from respective modules by sending the method name. This should return the
|
||||
# implementation of the method (ie a method object), not actually try to implement it
|
||||
# (as that's impossible in ruby)
|
||||
def boot_functions!
|
||||
# very fiddly chicken 'n egg problem. Functions need to be in the right order, and in fact we
|
||||
# have to define some dummies, just for the other to compile
|
||||
# TODO go through the virtual parfait layer and adjust function names to what they really are
|
||||
obj = @space.get_class_by_name(:Object)
|
||||
[:main , :_get_instance_variable , :_set_instance_variable].each do |f|
|
||||
obj.add_instance_method Builtin::Object.send(f , nil)
|
||||
end
|
||||
obj = @space.get_class_by_name(:Kernel)
|
||||
# create __init__ main first, __init__ calls it
|
||||
[:exit,:__send , :__init__ ].each do |f|
|
||||
obj.add_instance_method Builtin::Kernel.send(f , nil)
|
||||
end
|
||||
|
||||
@space.get_class_by_name(:Word).add_instance_method Builtin::Word.send(:putstring , nil)
|
||||
|
||||
obj = @space.get_class_by_name(:Integer)
|
||||
[:putint,:fibo , :plus].each do |f|
|
||||
obj.add_instance_method Builtin::Integer.send(f , nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -4,9 +4,9 @@ The Builtin module contains functions that can not be coded in ruby.
|
||||
It is the other side of the parfait coin, part of the runtime.
|
||||
|
||||
The functions are organized by their respective class and get loaded in boot_classes! ,
|
||||
right at the start. (see virtual/boot.rb)
|
||||
right at the start. (see register/boot.rb)
|
||||
|
||||
These functions return their code, ie a Parfait::Method with a Virtual::MethodSource object,
|
||||
These functions return their code, ie a Parfait::Method with a MethodSource object,
|
||||
which can then be called by ruby code as if it were a "normal" function.
|
||||
|
||||
A normal ruby function is one that is parsed and transformed to code. But not all functionality can
|
||||
|
@ -5,7 +5,7 @@ module Register
|
||||
module ClassMethods
|
||||
include AST::Sexp
|
||||
def plus c
|
||||
plus_function = Virtual::MethodSource.create_method(:Integer,:Integer,:plus , [:Integer] )
|
||||
plus_function = MethodSource.create_method(:Integer,:Integer,:plus , [:Integer] )
|
||||
plus_function.source.set_return_type :Integer
|
||||
plus_function.source.receiver = :Integer
|
||||
|
||||
@ -15,7 +15,7 @@ module Register
|
||||
|
||||
me = Register.tmp_reg :Integer
|
||||
plus_function.source.add_code Register.get_slot(plus_function , :message , :receiver , me )
|
||||
add = Register::OperatorInstruction.new( plus_function, :add , me , tmp )
|
||||
add = OperatorInstruction.new( plus_function, :add , me , tmp )
|
||||
plus_function.source.add_code add
|
||||
plus_function.source.add_code Register.set_slot(plus_function , me , :message , :return_value )
|
||||
return plus_function
|
||||
@ -28,14 +28,14 @@ module Register
|
||||
# As we write before we recurse (save a push) we write the number backwards
|
||||
# arguments: string address , integer
|
||||
# def utoa context
|
||||
# utoa_function = Virtual::MethodSource.create_method(:Integer ,:utoa , [ :Integer ] )
|
||||
# utoa_function = MethodSource.create_method(:Integer ,:utoa , [ :Integer ] )
|
||||
# function.source.return_type = :Integer
|
||||
# function.source.receiver = :Integer
|
||||
# return utoa_function
|
||||
# # str_addr = utoa_function.receiver
|
||||
# # number = utoa_function.args.first
|
||||
# # remainder = utoa_function.new_local
|
||||
# # Virtual::RegisterMachine.instance.div10( utoa_function , number , remainder )
|
||||
# # RegisterMachine.instance.div10( utoa_function , number , remainder )
|
||||
# # # make char out of digit (by using ascii encoding) 48 == "0"
|
||||
# # utoa_function.instance_eval do
|
||||
# # add( remainder , remainder , 48)
|
||||
@ -48,7 +48,7 @@ module Register
|
||||
# end
|
||||
|
||||
def putint context
|
||||
putint_function = Virtual::MethodSource.create_method(:Integer,:Integer,:putint , [] )
|
||||
putint_function = MethodSource.create_method(:Integer,:Integer,:putint , [] )
|
||||
putint_function.source.set_return_type :Integer
|
||||
putint_function.source.receiver = :Integer
|
||||
return putint_function
|
||||
@ -68,7 +68,7 @@ module Register
|
||||
# add( int , buffer , nil ) # string to write to
|
||||
# mov( moved_int , buffer.length )
|
||||
# end
|
||||
# Virtual::RegisterMachine.instance.write_stdout(putint_function)
|
||||
# RegisterMachine.instance.write_stdout(putint_function)
|
||||
# putint_function
|
||||
end
|
||||
|
||||
@ -77,7 +77,7 @@ module Register
|
||||
# a hand coded version of the fibonachi numbers
|
||||
# not my hand off course, found in the net http://www.peter-cockerell.net/aalp/html/ch-5.html
|
||||
def fibo context
|
||||
fibo_function = Virtual::MethodSource.create_method(:Integer,:Integer,:fibo , [] )
|
||||
fibo_function = MethodSource.create_method(:Integer,:Integer,:fibo , [] )
|
||||
fibo_function.source.set_return_type :Integer
|
||||
fibo_function.source.receiver = :Integer
|
||||
return fibo_function
|
||||
|
@ -6,7 +6,7 @@ module Register
|
||||
# it isn't really a function, ie it is jumped to (not called), exits and may not return
|
||||
# so it is responsible for initial setup
|
||||
def __init__ context
|
||||
function = Virtual::MethodSource.create_method(:Kernel,:Integer,:__init__ , [])
|
||||
function = MethodSource.create_method(:Kernel,:Integer,:__init__ , [])
|
||||
function.source.set_return_type :Integer
|
||||
# no method enter or return (automatically added), remove
|
||||
function.source.blocks.first.codes.pop # no Method enter
|
||||
@ -21,20 +21,20 @@ module Register
|
||||
# And store the space as the new self (so the call can move it back as self)
|
||||
function.source.add_code Register.set_slot( function, space_reg , :new_message , :receiver)
|
||||
# now we are set up to issue a call to the main
|
||||
Register.issue_call( function , Virtual.machine.space.get_main)
|
||||
Register.issue_call( function , Register.machine.space.get_main)
|
||||
emit_syscall( function , :exit )
|
||||
return function
|
||||
end
|
||||
def exit context
|
||||
function = Virtual::MethodSource.create_method(:Kernel,:Integer,:exit , [])
|
||||
function = MethodSource.create_method(:Kernel,:Integer,:exit , [])
|
||||
function.source.set_return_type :Integer
|
||||
return function
|
||||
ret = Virtual::RegisterMachine.instance.exit(function)
|
||||
ret = RegisterMachine.instance.exit(function)
|
||||
function.set_return ret
|
||||
function
|
||||
end
|
||||
def __send context
|
||||
function = Virtual::MethodSource.create_method(:Kernel,:Integer ,:__send , [] )
|
||||
function = MethodSource.create_method(:Kernel,:Integer ,:__send , [] )
|
||||
function.source.set_return_type :Integer
|
||||
return function
|
||||
end
|
||||
|
@ -6,7 +6,7 @@ module Register
|
||||
# main entry point, ie __init__ calls this
|
||||
# defined here as empty, to be redefined
|
||||
def main context
|
||||
function = Virtual::MethodSource.create_method(:Object, :Integer , :main , [])
|
||||
function = MethodSource.create_method(:Object, :Integer , :main , [])
|
||||
return function
|
||||
end
|
||||
|
||||
@ -18,13 +18,13 @@ module Register
|
||||
# The at_index is just "below" the api, something we need but don't want to expose,
|
||||
# so we can't code the above in ruby
|
||||
def _get_instance_variable context , name = :Integer
|
||||
get_function = Virtual::MethodSource.create_method(:Object,:Integer, :_get_instance_variable , [ ] )
|
||||
get_function = MethodSource.create_method(:Object,:Integer, :_get_instance_variable , [ ] )
|
||||
return get_function
|
||||
# me = get_function.receiver
|
||||
# var_name = get_function.args.first
|
||||
# return_to = get_function.return_type
|
||||
#
|
||||
# index_function = ::Virtual.machine.space.get_class_by_name(:Object).resolve_method(:index_of)
|
||||
# index_function = ::Register.machine.space.get_class_by_name(:Object).resolve_method(:index_of)
|
||||
# # get_function.push( [me] )
|
||||
# # index = get_function.call( index_function )
|
||||
#
|
||||
@ -39,7 +39,7 @@ module Register
|
||||
end
|
||||
|
||||
def _set_instance_variable(context , name = :Integer , value = :Integer )
|
||||
set_function = Virtual::MethodSource.create_method(:Object,:Integer,:_set_instance_variable ,[] )
|
||||
set_function = MethodSource.create_method(:Object,:Integer,:_set_instance_variable ,[] )
|
||||
return set_function
|
||||
# receiver set_function
|
||||
# me = set_function.receiver
|
||||
|
@ -3,7 +3,7 @@ module Register
|
||||
module Word
|
||||
module ClassMethods
|
||||
def putstring context
|
||||
function = Virtual::MethodSource.create_method(:Word,:Integer , :putstring , [] )
|
||||
function = MethodSource.create_method(:Word,:Integer , :putstring , [] )
|
||||
function.source.add_code Register.get_slot( function , :message , :receiver , :new_message )
|
||||
Kernel.emit_syscall( function , :putstring )
|
||||
function
|
||||
|
38
lib/register/collector.rb
Normal file
38
lib/register/collector.rb
Normal file
@ -0,0 +1,38 @@
|
||||
module Register
|
||||
|
||||
# collect anything that is in the space but and reachable from init
|
||||
module Collector
|
||||
def collect
|
||||
# init= Parfait::Space.object_space.get_class_by_name("Kernel").get_instance_method "__init__"
|
||||
self.objects.clear
|
||||
keep Parfait::Space.object_space , 0
|
||||
end
|
||||
|
||||
def keep object , depth
|
||||
return if object.nil?
|
||||
#puts "adding #{' ' * depth}:#{object.class}"
|
||||
#puts "ADD #{object.first.class}, #{object.last.class}" if object.is_a? Array
|
||||
return unless self.add_object object
|
||||
return unless object.respond_to? :has_layout?
|
||||
if( object.is_a? Parfait::Method)
|
||||
object.source.constants.each{|c|
|
||||
#puts "keeping constant #{c.class}:#{c.object_id}"
|
||||
keep(c , depth + 1)
|
||||
}
|
||||
end
|
||||
layout = object.get_layout
|
||||
keep(layout , depth + 1)
|
||||
layout.object_instance_names.each do |name|
|
||||
#puts "Keep #{name}"
|
||||
inst = object.get_instance_variable name
|
||||
keep(inst , depth + 1)
|
||||
end
|
||||
if object.is_a? Parfait::List
|
||||
object.each do |item|
|
||||
#puts "Keep item "
|
||||
keep(item , depth + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -24,8 +24,8 @@ module Register
|
||||
@index = index
|
||||
@register = register
|
||||
raise "not integer #{index}" unless index.is_a? Numeric
|
||||
raise "Not register #{register}" unless Register::RegisterValue.look_like_reg(register)
|
||||
raise "Not register #{array}" unless Register::RegisterValue.look_like_reg(array)
|
||||
raise "Not register #{register}" unless RegisterValue.look_like_reg(register)
|
||||
raise "Not register #{array}" unless RegisterValue.look_like_reg(array)
|
||||
end
|
||||
attr_accessor :array , :index , :register
|
||||
|
||||
|
@ -23,8 +23,8 @@ module Register
|
||||
@array = array
|
||||
@index = index
|
||||
raise "not integer #{index}" unless index.is_a? Numeric
|
||||
raise "Not register #{register}" unless Register::RegisterValue.look_like_reg(register)
|
||||
raise "Not register #{array}" unless Register::RegisterValue.look_like_reg(array)
|
||||
raise "Not register #{register}" unless RegisterValue.look_like_reg(register)
|
||||
raise "Not register #{array}" unless RegisterValue.look_like_reg(array)
|
||||
end
|
||||
attr_accessor :register , :array , :index
|
||||
def to_s
|
||||
|
166
lib/register/machine.rb
Normal file
166
lib/register/machine.rb
Normal file
@ -0,0 +1,166 @@
|
||||
require 'parslet/convenience'
|
||||
require_relative "collector"
|
||||
module Register
|
||||
# The Register Machine is a object based virtual machine in which ruby is implemented.
|
||||
#
|
||||
# It is minimal and realistic and low level
|
||||
# - minimal means that if one thing can be implemented by another, it is left out. This is quite
|
||||
# the opposite from ruby, which has several loops, many redundant if forms and the like.
|
||||
# - realistic means it is easy to implement on a 32 bit machine (arm) and possibly 64 bit.
|
||||
# Memory access,some registers of same size are the underlying hardware. (not ie byte machine)
|
||||
# - low level means it's basic instructions are realively easily implemented in a register machine.
|
||||
# Low level means low level in oo terms though, so basic instruction to implement oo
|
||||
# #
|
||||
# The ast is transformed to virtual-machine objects, some of which represent code, some data.
|
||||
#
|
||||
# The next step transforms to the register machine layer, which is quite close to what actually
|
||||
# executes. The step after transforms to Arm, which creates executables.
|
||||
#
|
||||
# More concretely, a virtual machine is a sort of oo turing machine, it has a current instruction,
|
||||
# executes the instructions, fetches the next one and so on.
|
||||
# Off course the instructions are not soo simple, but in oo terms quite so.
|
||||
#
|
||||
# The machine is virtual in the sense that it is completely modeled in software,
|
||||
# it's complete state explicitly available (not implicitly by walking stacks or something)
|
||||
|
||||
# The machine has a no register, but objects that represent it's state. There are four
|
||||
# - message : the currently executing message (See Parfait::Message)
|
||||
# - receiver : or self. This is actually an instance of Message, but "hoisted" out
|
||||
# - frame : A pssible frame for temporary data. Also part of the message and "hoisted" out
|
||||
# - next_message: A message object that the current activation wants to send.
|
||||
#
|
||||
# Messages form a linked list (not a stack) and the Space is responsible for storing
|
||||
# and handing out empty messages
|
||||
#
|
||||
# The "machine" is not part of the run-time (Parfait)
|
||||
|
||||
class Machine
|
||||
include Collector
|
||||
|
||||
def initialize
|
||||
@parser = Parser::Salama.new
|
||||
@passes = [ ]
|
||||
@objects = {}
|
||||
@booted = false
|
||||
end
|
||||
attr_reader :passes , :space , :class_mappings , :init , :objects , :booted
|
||||
|
||||
# run all passes before the pass given
|
||||
# also collect the block to run the passes on and
|
||||
# runs housekeeping Minimizer and Collector
|
||||
# Has to be called before run_after
|
||||
def run_before stop_at
|
||||
@blocks = [@init]
|
||||
@space.classes.values.each do |c|
|
||||
c.instance_methods.each do |f|
|
||||
nb = f.source.blocks
|
||||
@blocks += nb
|
||||
end
|
||||
end
|
||||
@passes.each do |pass_class|
|
||||
#puts "running #{pass_class}"
|
||||
run_blocks_for pass_class
|
||||
return if stop_at == pass_class
|
||||
end
|
||||
end
|
||||
|
||||
# run all passes after the pass given
|
||||
# run_before MUST be called first.
|
||||
# the two are meant as a poor mans breakoint
|
||||
def run_after start_at
|
||||
run = false
|
||||
@passes.each do |pass_class|
|
||||
if run
|
||||
#puts "running #{pass_class}"
|
||||
run_blocks_for pass_class
|
||||
else
|
||||
run = true if start_at == pass_class
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# as before, run all passes that are registered
|
||||
# (but now finer control with before/after versions)
|
||||
def run_passes
|
||||
return if @passes.empty?
|
||||
run_before @passes.first
|
||||
run_after @passes.first
|
||||
end
|
||||
|
||||
# Objects are data and get assembled after functions
|
||||
def add_object o
|
||||
return false if @objects[o.object_id]
|
||||
return if o.is_a? Fixnum
|
||||
raise "adding non parfait #{o.class}" unless o.is_a? Parfait::Object or o.is_a? Symbol
|
||||
@objects[o.object_id] = o
|
||||
true
|
||||
end
|
||||
|
||||
# Passes may be added to by anyone who wants
|
||||
# This is intentionally quite flexible, though one sometimes has to watch the order of them
|
||||
# most ordering is achieved by ordering the requires and using add_pass
|
||||
# but more precise control is possible with the _after and _before versions
|
||||
|
||||
def add_pass pass
|
||||
@passes << pass
|
||||
end
|
||||
def add_pass_after( pass , after)
|
||||
index = @passes.index(after)
|
||||
raise "No such pass (#{pass}) to add after: #{after}" unless index
|
||||
@passes.insert(index+1 , pass)
|
||||
end
|
||||
def add_pass_before( pass , after)
|
||||
index = @passes.index(after)
|
||||
raise "No such pass to add after: #{after}" unless index
|
||||
@passes.insert(index , pass)
|
||||
end
|
||||
|
||||
def boot
|
||||
boot_parfait!
|
||||
@init = Block.new("init", :__init__ )
|
||||
branch = Branch.new( "__init__" , self.space.get_init.source.blocks.first )
|
||||
@init.add_code branch
|
||||
@booted = true
|
||||
self
|
||||
end
|
||||
|
||||
def parse_and_compile bytes
|
||||
syntax = @parser.parse_with_debug(bytes)
|
||||
parts = Parser::Transform.new.apply(syntax)
|
||||
#puts parts.inspect
|
||||
Phisol::Compiler.compile( parts )
|
||||
end
|
||||
|
||||
private
|
||||
def run_blocks_for pass_class
|
||||
parts = pass_class.split("::")
|
||||
pass = Object.const_get(parts[0]).const_get parts[1]
|
||||
raise "no such pass-class as #{pass_class}" unless pass
|
||||
@blocks.each do |block|
|
||||
raise "nil block " unless block
|
||||
pass.new.run(block)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Module function to retrieve singleton
|
||||
def self.machine
|
||||
unless defined?(@machine)
|
||||
@machine = Machine.new
|
||||
end
|
||||
@machine
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Parfait::Method.class_eval do
|
||||
# for testing we need to reuse the main function (or do we?)
|
||||
# so remove the code that is there
|
||||
def clear_source
|
||||
self.source.send :initialize , self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
require_relative "boot"
|
170
lib/register/method_source.rb
Normal file
170
lib/register/method_source.rb
Normal file
@ -0,0 +1,170 @@
|
||||
require_relative "block"
|
||||
|
||||
module Register
|
||||
# the static info of a method (with its compiled code, argument names etc ) is part of the
|
||||
# runtime, ie found in Parfait::Method
|
||||
|
||||
# the source we create here is injected into the method and used only at compile-time
|
||||
|
||||
#
|
||||
# Methods are one step up from to VM::Blocks. Where Blocks can be jumped to, Methods can be called.
|
||||
|
||||
# Methods also have arguments and a return. These are typed by subclass instances of Value
|
||||
|
||||
# They also have local variables.
|
||||
|
||||
# Code-wise Methods are made up from a list of Blocks, in a similar way blocks are made up of
|
||||
# Instructions. 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 "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 MethodSource
|
||||
|
||||
# create method does two things
|
||||
# first it creates the parfait method, for the given class, with given argument names
|
||||
# second, it creates MethodSource and attaches it to the method
|
||||
#
|
||||
# compile code then works with the method, but adds code tot the info
|
||||
def self.create_method( class_name , return_type , method_name , args)
|
||||
raise "create_method #{class_name}.#{class_name.class}" unless class_name.is_a? Symbol
|
||||
raise "create_method #{method_name}.#{method_name.class}" unless method_name.is_a? Symbol
|
||||
clazz = Register.machine.space.get_class_by_name class_name
|
||||
raise "No such class #{class_name}" unless clazz
|
||||
arguments = []
|
||||
args.each_with_index do | arg , index |
|
||||
unless arg.is_a? Parfait::Variable
|
||||
raise "not type #{arg}:#{arg.class}" unless Register.machine.space.get_class_by_name arg
|
||||
arg = Parfait::Variable.new arg , "arg#{index}".to_sym
|
||||
end
|
||||
arguments << arg
|
||||
end
|
||||
method = clazz.create_instance_method( method_name , Register.new_list(arguments))
|
||||
method.source = MethodSource.new(method , return_type)
|
||||
method
|
||||
end
|
||||
# just passing the method object in for Instructions to make decisions (later)
|
||||
def initialize method , return_type
|
||||
init( method , return_type)
|
||||
end
|
||||
|
||||
def init method , return_type = nil
|
||||
# first block we have to create with .new , as new_block assumes a current
|
||||
enter = Block.new( "enter" , method )
|
||||
enter.add_code Register.save_return(self, :message , :return_address)
|
||||
set_return_type( return_type )
|
||||
@blocks = [enter]
|
||||
@current = enter
|
||||
ret = new_block("return")
|
||||
# move the current message to new_message
|
||||
ret.add_code RegisterTransfer.new(self, Register.message_reg , Register.new_message_reg )
|
||||
# and restore the message from saved value in new_message
|
||||
ret.add_code Register.get_slot(self,:new_message , :caller , :message )
|
||||
#load the return address into pc, affecting return. (other cpus have commands for this, but not arm)
|
||||
ret.add_code FunctionReturn.new( self , Register.new_message_reg , Register.resolve_index(:message , :return_address) )
|
||||
@constants = []
|
||||
end
|
||||
attr_reader :blocks , :constants , :return_type
|
||||
attr_accessor :current , :receiver
|
||||
|
||||
def set_return_type type
|
||||
return if type.nil?
|
||||
raise "not type #{type}" unless Register.machine.space.get_class_by_name type
|
||||
@return_type = type
|
||||
end
|
||||
# add an instruction after the current (insertion point)
|
||||
# the added instruction will become the new insertion point
|
||||
def add_code instruction
|
||||
unless instruction.is_a?(Instruction)
|
||||
raise instruction.to_s
|
||||
end
|
||||
@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 = [ RegisterValue.new(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 statement 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
|
||||
new_b = Block.new( new_name , @blocks.first.method )
|
||||
index = @blocks.index( @current )
|
||||
@blocks.insert( index + 1 , new_b ) # + one because we want the ne after the insert_at
|
||||
return new_b
|
||||
end
|
||||
|
||||
# sugar to create instructions easily.
|
||||
# any method will be passed on to the RegisterMachine and the result added to the insertion block
|
||||
# With this trick we can write what looks like assembler,
|
||||
# Example func.instance_eval
|
||||
# mov( r1 , r2 )
|
||||
# add( r1 , r2 , 4)
|
||||
# end
|
||||
# mov and add will be called on Machine and generate Instructions that are then added
|
||||
# to the current block
|
||||
# also symbols are supported and wrapped as register usages (for bare metal programming)
|
||||
# def method_missing(meth, *arguments, &block)
|
||||
# add_code ::Arm::ArmMachine.send(meth , *arguments)
|
||||
# end
|
||||
|
||||
def byte_length
|
||||
@blocks.inject(0) { |c , block| c += block.byte_length }
|
||||
end
|
||||
|
||||
# position of the function is the position of the entry block, is where we call
|
||||
def set_position at
|
||||
at += 8 #for the 2 header words
|
||||
@blocks.each do |block|
|
||||
block.set_position at
|
||||
at = at + block.byte_length
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
40
lib/register/minimizer.rb
Normal file
40
lib/register/minimizer.rb
Normal file
@ -0,0 +1,40 @@
|
||||
module Register
|
||||
|
||||
# Remove all functions that are not called
|
||||
# Not called is approximated by the fact that the method name doesn't show up
|
||||
# in any function reachable from main
|
||||
class Minimizer
|
||||
def run
|
||||
@gonners = []
|
||||
Parfait::Space.object_space.classes.values.each do |c|
|
||||
c.instance_methods.each do |f|
|
||||
@gonners << f
|
||||
end
|
||||
end
|
||||
keep Register.machine.space.get_init
|
||||
remove_remaining
|
||||
end
|
||||
|
||||
def keep function
|
||||
index = @gonners.index function
|
||||
unless index
|
||||
puts "function was already removed #{function.name}"
|
||||
return
|
||||
end
|
||||
#puts "stayer #{function.name}"
|
||||
@gonners.delete function
|
||||
function.source.blocks.each do |block|
|
||||
block.codes.each do |code|
|
||||
keep code.method if code.is_a? FunctionCall
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_remaining
|
||||
@gonners.each do |method|
|
||||
next if(method.name == :plus)
|
||||
method.for_class.remove_instance_method method.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/register/padding.rb
Normal file
21
lib/register/padding.rb
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
module Padding
|
||||
|
||||
# objects only come in lengths of multiple of 8 words
|
||||
# but there is a constant overhead of 2 words, one for type, one for layout
|
||||
# and as we would have to subtract 1 to make it work without overhead, we now have to add 7
|
||||
def padded len
|
||||
a = 32 * (1 + (len + 7)/32 )
|
||||
#puts "#{a} for #{len}"
|
||||
a
|
||||
end
|
||||
|
||||
def padded_words words
|
||||
padded(words*4) # 4 == word length, a constant waiting for a home
|
||||
end
|
||||
|
||||
def padding_for length
|
||||
pad = padded(length) - length - 8 # for header, type and layout
|
||||
pad
|
||||
end
|
||||
end
|
177
lib/register/parfait_adapter.rb
Normal file
177
lib/register/parfait_adapter.rb
Normal file
@ -0,0 +1,177 @@
|
||||
# Below we define functions (in different classes) that are not part of the run-time
|
||||
# They are used for the boot process, ie when this codes executes in the vm that builds salama
|
||||
|
||||
# To stay sane, we use the same classes that we use later, but "adapt" them to work in ruby
|
||||
# This affects mainly memory layout
|
||||
|
||||
module Register
|
||||
def self.new_list array
|
||||
list = Parfait::List.new
|
||||
list.set_length array.length
|
||||
index = 1
|
||||
while index <= array.length do
|
||||
list.set(index , array[index - 1])
|
||||
index = index + 1
|
||||
end
|
||||
list
|
||||
end
|
||||
end
|
||||
class Symbol
|
||||
include Positioned
|
||||
include Padding
|
||||
|
||||
def has_layout?
|
||||
true
|
||||
end
|
||||
def get_layout
|
||||
l = Register.machine.space.classes[:Word].object_layout
|
||||
#puts "LL #{l.class}"
|
||||
l
|
||||
end
|
||||
def word_length
|
||||
padded to_s.length
|
||||
end
|
||||
# not the prettiest addition to the game, but it wasn't me who decided symbols are frozen in 2.x
|
||||
def cache_positions
|
||||
unless defined?(@@symbol_positions)
|
||||
@@symbol_positions = {}
|
||||
end
|
||||
@@symbol_positions
|
||||
end
|
||||
def position
|
||||
pos = cache_positions[self]
|
||||
if pos == nil
|
||||
str = "position accessed but not set, "
|
||||
str += "Machine has object=#{Register.machine.objects.has_key?(self.object_id)} "
|
||||
raise str + " for Symbol:#{self}"
|
||||
end
|
||||
pos
|
||||
end
|
||||
def set_position pos
|
||||
# resetting of position used to be error, but since relink and dynamic instruction size it is ok.
|
||||
# in measures (of 32)
|
||||
old = cache_positions[self]
|
||||
if old != nil and ((old - pos).abs > 32)
|
||||
raise "position set again #{pos}!=#{old} for #{self}"
|
||||
end
|
||||
cache_positions[self] = pos
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
module Parfait
|
||||
# Objects memory functions. Object memory is 1 based
|
||||
# but we implement it with ruby array (0 based) and use 0 as type-word
|
||||
# These are the same functions that Builtin implements at run-time
|
||||
class Object
|
||||
include Padding
|
||||
include Positioned
|
||||
|
||||
def fake_init
|
||||
@memory = [0,nil]
|
||||
@position = nil
|
||||
self # for chaining
|
||||
end
|
||||
|
||||
# these internal functions are _really_ internal
|
||||
# they respresent the smallest code needed to build larger functionality
|
||||
# but should _never_ be used outside parfait. in fact that should be impossible
|
||||
def internal_object_get_typeword
|
||||
raise "failed init for #{self.class}" unless @memory
|
||||
@memory[0]
|
||||
end
|
||||
def internal_object_set_typeword w
|
||||
raise "failed init for #{self.class}" unless @memory
|
||||
@memory[0] = w
|
||||
end
|
||||
def internal_object_length
|
||||
raise "failed init for #{self.class}" unless @memory
|
||||
@memory.length - 1 # take of type-word
|
||||
end
|
||||
# 1 -based index
|
||||
def internal_object_get(index)
|
||||
@memory[index]
|
||||
end
|
||||
# 1 -based index
|
||||
def internal_object_set(index , value)
|
||||
raise "failed init for #{self.class}" unless @memory
|
||||
@memory[index] = value
|
||||
value
|
||||
end
|
||||
def internal_object_grow(length)
|
||||
old_length = internal_object_length()
|
||||
while( old_length < length )
|
||||
internal_object_set( old_length + 1, nil)
|
||||
old_length = old_length + 1
|
||||
end
|
||||
end
|
||||
def internal_object_shrink(length)
|
||||
old_length = internal_object_length()
|
||||
while( length < old_length )
|
||||
@memory.delete_at(old_length)
|
||||
old_length = old_length - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
class List
|
||||
def to_sof_node(writer , level , ref )
|
||||
Sof.array_to_sof_node(self , writer , level , ref )
|
||||
end
|
||||
def to_a
|
||||
array = []
|
||||
index = 1
|
||||
while( index <= self.get_length)
|
||||
array[index - 1] = get(index)
|
||||
index = index + 1
|
||||
end
|
||||
array
|
||||
end
|
||||
end
|
||||
class Dictionary
|
||||
def to_sof_node(writer , level , ref)
|
||||
Sof.hash_to_sof_node( self , writer , level , ref)
|
||||
end
|
||||
end
|
||||
|
||||
class Method
|
||||
attr_accessor :source
|
||||
end
|
||||
|
||||
class Word
|
||||
|
||||
def == other
|
||||
return false unless other.is_a?(String) or other.is_a?(Word)
|
||||
as_string = self.to_string
|
||||
unless other.is_a? String
|
||||
other = other.to_string
|
||||
end
|
||||
as_string == other
|
||||
end
|
||||
|
||||
def to_string
|
||||
string = ""
|
||||
index = 1
|
||||
while( index <= self.length)
|
||||
string[index - 1] = get_char(index).chr
|
||||
index = index + 1
|
||||
end
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
## sof related stuff
|
||||
class Object
|
||||
# parfait versions are deliberately called different, so we "relay"
|
||||
# have to put the "@" on the names for sof to take them off again
|
||||
def instance_variables
|
||||
get_instance_variables.to_a.collect{ |n| "@#{n}".to_sym }
|
||||
end
|
||||
# name comes in as a ruby @var name
|
||||
def instance_variable_get name
|
||||
var = get_instance_variable name.to_s[1 .. -1].to_sym
|
||||
#puts "getting #{name} #{var}"
|
||||
var
|
||||
end
|
||||
end
|
||||
|
||||
end
|
20
lib/register/positioned.rb
Normal file
20
lib/register/positioned.rb
Normal file
@ -0,0 +1,20 @@
|
||||
#require_relative "type"
|
||||
|
||||
module Positioned
|
||||
def position
|
||||
if @position.nil?
|
||||
str = "IN machine #{Register.machine.objects.has_key?(self.object_id)}, at #{self.object_id.to_s(16)}\n"
|
||||
raise str + "position not set for #{self.class} len #{word_length} for #{self.inspect[0...100]}"
|
||||
end
|
||||
@position
|
||||
end
|
||||
def set_position pos
|
||||
raise "Setting of nil not allowed" if pos.nil?
|
||||
# resetting of position used to be error, but since relink and dynamic instruction size it is ok.
|
||||
# in measures (of 32)
|
||||
if @position != nil and ((@position - pos).abs > 32)
|
||||
raise "position set again #{pos}!=#{@position} for #{self}"
|
||||
end
|
||||
@position = pos
|
||||
end
|
||||
end
|
@ -1,7 +0,0 @@
|
||||
require_relative "instruction"
|
||||
require_relative "register_value"
|
||||
require_relative "assembler"
|
||||
|
||||
# So the memory model of the machine allows for indexed access into an "object" .
|
||||
# A fixed number of objects exist (ie garbage collection is reclaming, not destroying and
|
||||
# recreating) although there may be a way to increase that number.
|
Reference in New Issue
Block a user