fold last of the virtual into register

This commit is contained in:
Torsten Ruger
2015-10-22 18:16:29 +03:00
parent f658ecf425
commit dcbd3c7091
67 changed files with 161 additions and 227 deletions

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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

View File

@ -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
View 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"

View 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
View 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
View 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

View 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

View 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

View File

@ -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.