rename register to risc
seems to fit the layer much better as we really have a very reduced instruction set
This commit is contained in:
48
lib/risc/README.md
Normal file
48
lib/risc/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
Risc Machine
|
||||
================
|
||||
|
||||
The RiscMachine, is an abstract machine with registers. Think of it as an arm machine with
|
||||
normal instruction names. It is not however an abstraction of existing hardware, but only
|
||||
of that subset that we need.
|
||||
|
||||
Our primary objective is to compile typed code to this level, so the register machine has:
|
||||
- object access instructions
|
||||
- object load
|
||||
- object oriented call semantics
|
||||
- extended (and extensible) branching
|
||||
- normal integer operators (but no sub word instructions)
|
||||
|
||||
All data is in objects.
|
||||
|
||||
The register machine is aware of Parfait objects, and specifically uses Message and Frame to
|
||||
express call semantics.
|
||||
|
||||
Calls and syscalls
|
||||
------------------
|
||||
|
||||
The RiscMachine only uses 1 fixed register, the currently worked on Message.
|
||||
|
||||
There is no stack, rather messages form a linked list, and preparing to call, the data is pre-filled
|
||||
into the next message. Calling then means moving the new message to the current one and jumping
|
||||
to the address of the method. Returning is the somewhat reverse process.
|
||||
|
||||
Syscalls are implemented by *one* Syscall instruction. The Risc machine does not specify/limit
|
||||
the meaning or number of syscalls. This is implemented by the level below, eg the arm/interpreter.
|
||||
|
||||
Interpreter
|
||||
===========
|
||||
|
||||
There is an interpreter that can interpret compiled register machine programs.
|
||||
This is very handy for debugging (an nothing else).
|
||||
|
||||
Even more handy is the graphical interface for the interpreter, which is in it's own repository:
|
||||
salama-debugger.
|
||||
|
||||
Arm / Elf
|
||||
=========
|
||||
|
||||
There is also a (very strightforward) transformation to arm instructions.
|
||||
Together with the also quite minimal elf module, arm binaries can be produced.
|
||||
|
||||
These binaries have no external dependencies and in fact can not even call c at the moment
|
||||
(only syscalls :-)).
|
279
lib/risc/assembler.rb
Normal file
279
lib/risc/assembler.rb
Normal file
@ -0,0 +1,279 @@
|
||||
module Risc
|
||||
class LinkException < Exception
|
||||
end
|
||||
# Assemble the object machine into a binary.
|
||||
# Assemble first to get positions, then write
|
||||
|
||||
# The assemble function determines the length of an object and then actually
|
||||
# writes the bytes they are pretty much dependant. In an earlier version they were
|
||||
# functions on the objects, but now it has gone to a visitor pattern.
|
||||
|
||||
class Assembler
|
||||
include Logging
|
||||
log_level :info
|
||||
|
||||
MARKER = 0xA51AF00D
|
||||
|
||||
def initialize( machine , objects)
|
||||
@machine = machine
|
||||
@objects = objects
|
||||
@load_at = 0x8054 # this is linux/arm
|
||||
end
|
||||
|
||||
def assemble
|
||||
at = 0
|
||||
#need the initial jump at 0 and then functions
|
||||
@machine.init.set_position( 0)
|
||||
at = @machine.init.byte_length
|
||||
at = assemble_objects( at )
|
||||
# and then everything code
|
||||
asseble_code_from( at )
|
||||
end
|
||||
|
||||
def asseble_code_from( at )
|
||||
@objects.each do |id , objekt|
|
||||
next unless objekt.is_a? Parfait::TypedMethod
|
||||
log.debug "CODE1 #{objekt.name}"
|
||||
binary = objekt.binary
|
||||
Positioned.set_position(binary,at)
|
||||
objekt.instructions.set_position( at + 12) # BinaryCode header
|
||||
len = objekt.instructions.total_byte_length
|
||||
log.debug "CODE2 #{objekt.name} at #{Positioned.position(binary)} len: #{len}"
|
||||
binary.set_length(len , 0)
|
||||
at += binary.padded_length
|
||||
end
|
||||
at
|
||||
end
|
||||
|
||||
def assemble_objects( at)
|
||||
at += 8 # thats the padding
|
||||
# want to have the objects first in the executable
|
||||
@objects.each do | id , objekt|
|
||||
next if objekt.is_a? Risc::Label # will get assembled as method.instructions
|
||||
next if objekt.is_a? Parfait::BinaryCode
|
||||
Positioned.set_position(objekt,at)
|
||||
at += objekt.padded_length
|
||||
end
|
||||
at
|
||||
end
|
||||
|
||||
def write_as_string
|
||||
# must be same order as assemble
|
||||
begin
|
||||
return try_write
|
||||
rescue LinkException
|
||||
# knowing that we fix the problem, we hope to get away with retry.
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
# private method to implement write_as_string. May throw link Exception in which
|
||||
# case we try again. Once.
|
||||
def try_write
|
||||
assemble
|
||||
try_write_debug
|
||||
try_write_create_binary
|
||||
try_write_objects
|
||||
try_write_method
|
||||
log.debug "Assembled #{stream_position} bytes"
|
||||
return @stream.string
|
||||
end
|
||||
|
||||
# debugging loop accesses all positions to force an error if it's not set
|
||||
def try_write_debug
|
||||
all = @objects.values.sort{|a,b| Positioned.position(a) <=> Positioned.position(b)}
|
||||
all.each do |objekt|
|
||||
next if objekt.is_a?(Risc::Label)
|
||||
log.debug "Linked #{objekt.class}(#{objekt.object_id}) at #{Positioned.position(objekt)} / #{objekt.padded_length}"
|
||||
Positioned.position(objekt)
|
||||
end
|
||||
end
|
||||
|
||||
def try_write_create_binary
|
||||
# first we need to create the binary code for the methods
|
||||
@objects.each do |id , objekt|
|
||||
next unless objekt.is_a? Parfait::TypedMethod
|
||||
assemble_binary_method(objekt)
|
||||
end
|
||||
@stream = StringIO.new
|
||||
@machine.init.assemble( @stream )
|
||||
8.times do
|
||||
@stream.write_unsigned_int_8(0)
|
||||
end
|
||||
end
|
||||
|
||||
def try_write_objects
|
||||
# then the objects , not code yet
|
||||
@objects.each do | id, objekt|
|
||||
next if objekt.is_a? Parfait::BinaryCode
|
||||
next if objekt.is_a? Risc::Label # ignore
|
||||
write_any( objekt )
|
||||
end
|
||||
end
|
||||
|
||||
def try_write_method
|
||||
# then write the methods to file
|
||||
@objects.each do |id, objekt|
|
||||
next unless objekt.is_a? Parfait::BinaryCode
|
||||
write_any( objekt )
|
||||
end
|
||||
end
|
||||
|
||||
# assemble the MethodSource into a stringio
|
||||
# and then plonk that binary data into the method.code array
|
||||
def assemble_binary_method method
|
||||
stream = StringIO.new
|
||||
#puts "Method #{method.source.instructions.to_ac}"
|
||||
begin
|
||||
#puts "assemble #{method.source.instructions}"
|
||||
method.instructions.assemble_all( stream )
|
||||
rescue => e
|
||||
log.debug "Assembly error #{method.name}\n#{Sof.write(method.instructions).to_s[0...2000]}"
|
||||
raise e
|
||||
end
|
||||
write_binary_method_to_stream( method, stream)
|
||||
end
|
||||
|
||||
def write_binary_method_to_stream(method, stream)
|
||||
write_binary_method_checks(method,stream)
|
||||
index = 1
|
||||
stream.each_byte do |b|
|
||||
method.binary.set_char(index , b )
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
def write_binary_method_checks(method,stream)
|
||||
stream.rewind
|
||||
length = stream.length
|
||||
binary = method.binary
|
||||
total_byte_length = method.instructions.total_byte_length
|
||||
log.debug "Assembled code #{method.name} with length #{length}"
|
||||
raise "length error #{binary.char_length} != #{total_byte_length}" if binary.char_length != total_byte_length
|
||||
raise "length error #{length} != #{total_byte_length}" if total_byte_length != length
|
||||
end
|
||||
|
||||
def write_any obj
|
||||
write_any_log( obj , "Write")
|
||||
if @stream.length != Positioned.position(obj)
|
||||
raise "Write #{obj.class} #{obj.object_id} at #{stream_position} not #{Positioned.position(obj)}"
|
||||
end
|
||||
write_any_out(obj)
|
||||
write_any_log( obj , "Wrote")
|
||||
Positioned.position(obj)
|
||||
end
|
||||
|
||||
def write_any_log( obj , at)
|
||||
log.debug "#{at} #{obj.class}(#{obj.object_id}) at stream #{stream_position} pos:#{Positioned.position(obj)} , len:#{obj.padded_length}"
|
||||
end
|
||||
|
||||
def write_any_out(obj)
|
||||
if obj.is_a?(Parfait::Word) or obj.is_a?(Symbol)
|
||||
write_String obj
|
||||
else
|
||||
write_object obj
|
||||
end
|
||||
end
|
||||
# write type of the instance, and the variables that are passed
|
||||
# variables ar values, ie int or refs. For refs the object needs to save the object first
|
||||
def write_object( object )
|
||||
write_object_check(object)
|
||||
obj_written = write_object_variables(object)
|
||||
log.debug "instances=#{object.get_instance_variables.inspect} mem_len=#{object.padded_length}"
|
||||
indexed_written = write_object_indexed(object)
|
||||
log.debug "type #{obj_written} , total #{obj_written + indexed_written} (array #{indexed_written})"
|
||||
log.debug "Len = #{object.get_length} , inst = #{object.get_type.instance_length}" if object.is_a? Parfait::Type
|
||||
pad_after( obj_written + indexed_written )
|
||||
Positioned.position(object)
|
||||
end
|
||||
|
||||
def write_object_check(object)
|
||||
log.debug "Write object #{object.class} #{object.inspect}"
|
||||
unless @objects.has_key? object.object_id
|
||||
raise "Object(#{object.object_id}) not linked #{object.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def write_object_indexed(object)
|
||||
written = 0
|
||||
if( object.is_a? Parfait::List)
|
||||
object.each do |inst|
|
||||
write_ref_for(inst)
|
||||
written += 4
|
||||
end
|
||||
end
|
||||
written
|
||||
end
|
||||
|
||||
def write_object_variables(object)
|
||||
@stream.write_signed_int_32( MARKER )
|
||||
written = 0 # compensate for the "secrect" marker
|
||||
object.get_instance_variables.each do |var|
|
||||
inst = object.get_instance_variable(var)
|
||||
#puts "Nil for #{object.class}.#{var}" unless inst
|
||||
write_ref_for(inst)
|
||||
written += 4
|
||||
end
|
||||
written
|
||||
end
|
||||
|
||||
def write_BinaryCode code
|
||||
write_String code
|
||||
end
|
||||
|
||||
def write_String( string )
|
||||
if string.is_a? Parfait::Word
|
||||
str = string.to_string
|
||||
raise "length mismatch #{str.length} != #{string.char_length}" if str.length != string.char_length
|
||||
end
|
||||
str = string.to_s if string.is_a? Symbol
|
||||
log.debug "#{string.class} is #{string} at #{Positioned.position(string)} length #{string.length}"
|
||||
write_checked_string(string , str)
|
||||
end
|
||||
|
||||
def write_checked_string(string, str)
|
||||
@stream.write_signed_int_32( MARKER )
|
||||
write_ref_for( string.get_type ) #ref
|
||||
@stream.write_signed_int_32( str.length ) #int
|
||||
@stream.write str
|
||||
pad_after(str.length + 8 ) # type , length *4 == 12
|
||||
log.debug "String (#{string.length}) stream #{@stream.length}"
|
||||
end
|
||||
|
||||
def write_Symbol(sym)
|
||||
return write_String(sym)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# write means we write the resulting address straight into the assembler stream
|
||||
# object means the object of which we write the address
|
||||
def write_ref_for object
|
||||
case object
|
||||
when nil
|
||||
@stream.write_signed_int_32(0)
|
||||
when Fixnum
|
||||
@stream.write_signed_int_32(object)
|
||||
else
|
||||
@stream.write_signed_int_32(Positioned.position(object) + @load_at)
|
||||
end
|
||||
end
|
||||
|
||||
# pad_after is always in bytes and pads (writes 0's) up to the next 8 word boundary
|
||||
def pad_after length
|
||||
before = stream_position
|
||||
pad = Padding.padding_for(length) - 4 # four is for the MARKER we write
|
||||
pad.times do
|
||||
@stream.write_unsigned_int_8(0)
|
||||
end
|
||||
after = stream_position
|
||||
log.debug "padded #{length} with #{pad} stream #{before}/#{after}"
|
||||
end
|
||||
|
||||
# return the stream length as hex
|
||||
def stream_position
|
||||
@stream.length
|
||||
end
|
||||
end
|
||||
|
||||
Sof::Volotile.add(Assembler , [:objects])
|
||||
end
|
180
lib/risc/boot.rb
Normal file
180
lib/risc/boot.rb
Normal file
@ -0,0 +1,180 @@
|
||||
module Risc
|
||||
|
||||
# Booting is complicated, so it is extracted into this file, even it has only one entry point
|
||||
|
||||
# a ruby object as a placeholder for the parfait Space during boot
|
||||
class BootSpace
|
||||
attr_reader :classes
|
||||
def initialize
|
||||
@classes = {}
|
||||
end
|
||||
|
||||
def get_class_by_name(name)
|
||||
cl = @classes[name]
|
||||
raise "No class for #{name}" unless cl
|
||||
cl
|
||||
end
|
||||
end
|
||||
|
||||
# another ruby object to shadow the parfait, just during booting.
|
||||
# all it needs is the type, which we make the Parfait type
|
||||
class BootClass
|
||||
attr_reader :instance_type
|
||||
|
||||
def initialize( type)
|
||||
@instance_type = type
|
||||
end
|
||||
end
|
||||
|
||||
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 Type, that points to a class.
|
||||
# So we need a Type, but that has Type 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 Type instances, with their basic types, but no classes
|
||||
# - create a BootSpace that has BootClasses , used only during booting
|
||||
# - create the Class objects and assign them to the types
|
||||
# - flesh out the types , create the real space
|
||||
# - and finally load the methods
|
||||
def boot_parfait!
|
||||
Parfait.set_object_space( nil )
|
||||
types = boot_types
|
||||
boot_boot_space( types )
|
||||
classes = boot_classes( types )
|
||||
fix_types( types , classes )
|
||||
|
||||
space = Parfait::Space.new( classes )
|
||||
Parfait.set_object_space( space )
|
||||
|
||||
#puts Sof.write(space)
|
||||
boot_functions( space )
|
||||
end
|
||||
|
||||
# types is where the snake bites its tail. Every chain ends at a type and then it
|
||||
# goes around (circular references). We create them from the list below, just as empty
|
||||
# shells, that we pass back, for the BootSpace to be created
|
||||
def boot_types
|
||||
types = {}
|
||||
type_names.each do |name , ivars |
|
||||
types[name] = Parfait::Type.allocate
|
||||
end
|
||||
type_type = types[:Type]
|
||||
types.each do |name , type |
|
||||
type.set_type(type_type)
|
||||
end
|
||||
types
|
||||
end
|
||||
|
||||
# The BootSpace is an object that holds fake classes, that hold _real_ types
|
||||
# Once we plug it in we can use .new
|
||||
# then we need to create the parfait classes and fix the types before creating a Space
|
||||
def boot_boot_space(types)
|
||||
boot_space = BootSpace.new
|
||||
types.each do |name , type|
|
||||
clazz = BootClass.new(type)
|
||||
boot_space.classes[name] = clazz
|
||||
end
|
||||
Parfait.set_object_space( boot_space )
|
||||
end
|
||||
|
||||
# when running code instantiates a class, a type is created automatically
|
||||
# but even to get our space up, we have already instantiated all types
|
||||
# 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(types)
|
||||
classes = Parfait::Dictionary.new
|
||||
type_names.each do |name , vars|
|
||||
super_c = super_class_names[name] || :Object
|
||||
classes[name] = Parfait::Class.new(name , super_c , types[name] )
|
||||
end
|
||||
classes
|
||||
end
|
||||
|
||||
# Types are hollow shells before this, so we need to set the object_class
|
||||
# and initialize the list variables (which we now can with .new)
|
||||
def fix_types(types , classes)
|
||||
type_names.each do |name , ivars |
|
||||
type = types[name]
|
||||
clazz = classes[name]
|
||||
type.set_object_class( clazz )
|
||||
type.init_lists({:type => :Type }.merge(ivars))
|
||||
end
|
||||
end
|
||||
|
||||
# superclasses other than default object
|
||||
def super_class_names
|
||||
{ :Object => :Kernel , :Kernel => :Value , :Integer => :Value , :BinaryCode => :Word }
|
||||
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 type_names
|
||||
{ :Word => {:char_length => :Integer} ,
|
||||
:List => {:indexed_length => :Integer} ,
|
||||
:Message => { :next_message => :Message, :receiver => :Object, :locals => :NamedList ,
|
||||
:return_address => :Integer, :return_value => :Integer,
|
||||
:caller => :Message , :name => :Word , :arguments => :NamedList },
|
||||
:Integer => {},
|
||||
:Object => {},
|
||||
:Kernel => {}, #fix, kernel is a class, but should be a module
|
||||
:BinaryCode => {:char_length => :Integer} ,
|
||||
:Space => {:classes => :Dictionary , :types => :Dictionary , :first_message => :Message},
|
||||
:NamedList => {},
|
||||
:Type => {:names => :List , :types => :List ,
|
||||
:object_class => :Class, :methods => :List } ,
|
||||
:Class => {:instance_methods => :List, :instance_type => :Type, :name => :Word,
|
||||
:super_class_name => :Word , :instance_names => :List },
|
||||
:Dictionary => {:keys => :List , :values => :List } ,
|
||||
:TypedMethod => {:name => :Word, :source => :Object, :instructions => :Object, :binary => :Object,
|
||||
:arguments => :Type , :for_type => :Type, :locals => :Type } ,
|
||||
}
|
||||
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( space )
|
||||
# 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 others to compile
|
||||
# TODO go through the virtual parfait layer and adjust function names to what they really are
|
||||
space_class = space.get_class
|
||||
space_class.instance_type.add_method Builtin::Space.send(:main, nil)
|
||||
|
||||
obj = space.get_class_by_name(:Object)
|
||||
[ :get_internal_word , :set_internal_word ].each do |f|
|
||||
obj.instance_type.add_method Builtin::Object.send(f , nil)
|
||||
end
|
||||
obj = space.get_class_by_name(:Kernel)
|
||||
# create __init__ main first, __init__ calls it
|
||||
[:exit , :__init__ ].each do |f|
|
||||
obj.instance_type.add_method Builtin::Kernel.send(f , nil)
|
||||
end
|
||||
|
||||
obj = space.get_class_by_name(:Word)
|
||||
[:putstring , :get_internal_byte , :set_internal_byte ].each do |f|
|
||||
obj.instance_type.add_method Builtin::Word.send(f , nil)
|
||||
end
|
||||
|
||||
obj = space.get_class_by_name(:Integer)
|
||||
[ :putint, :mod4, :div10].each do |f| #mod4 is just a forward declaration
|
||||
obj.instance_type.add_method Builtin::Integer.send(f , nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/risc/builtin/README.md
Normal file
18
lib/risc/builtin/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
### Builtin module
|
||||
|
||||
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 register/boot.rb)
|
||||
|
||||
These functions return their code, ie a Parfait::TypedMethod 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
|
||||
be written in ruby, one of those chicken and egg things.
|
||||
C uses Assembler in this situation, we use Builtin functions.
|
||||
|
||||
Slightly more here : http://ruby-x.org/2014/06/10/more-clarity.html (then still called Kernel)
|
||||
|
||||
The Builtin module is scattered into several files, but that is just so the file doesn't get too long.
|
27
lib/risc/builtin/compile_helper.rb
Normal file
27
lib/risc/builtin/compile_helper.rb
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
module Risc
|
||||
module Builtin
|
||||
module CompileHelper
|
||||
|
||||
def self_and_int_arg(compiler , source)
|
||||
me = compiler.process( Vm::Tree::KnownName.new( :self) )
|
||||
int_arg = load_int_arg_at(compiler , source , 1 )
|
||||
return me , int_arg
|
||||
end
|
||||
|
||||
def compiler_for( type , method_name , extra_args = {})
|
||||
args = {:index => :Integer}.merge( extra_args )
|
||||
Vm::MethodCompiler.create_method(type , method_name , args ).init_method
|
||||
end
|
||||
|
||||
# Load the value
|
||||
def load_int_arg_at(compiler, source , at)
|
||||
int_arg = compiler.use_reg :Integer
|
||||
compiler.add_slot_to_reg(source , :message , :arguments , int_arg )
|
||||
compiler.add_slot_to_reg(source , int_arg , at + 1, int_arg ) #1 for type
|
||||
return int_arg
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
75
lib/risc/builtin/integer.rb
Normal file
75
lib/risc/builtin/integer.rb
Normal file
@ -0,0 +1,75 @@
|
||||
#integer related kernel functions
|
||||
module Risc
|
||||
module Builtin
|
||||
module Integer
|
||||
module ClassMethods
|
||||
include AST::Sexp
|
||||
|
||||
def mod4 context
|
||||
compiler = Vm::MethodCompiler.create_method(:Integer,:mod4 ).init_method
|
||||
return compiler.method
|
||||
end
|
||||
def putint context
|
||||
compiler = Vm::MethodCompiler.create_method(:Integer,:putint ).init_method
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
|
||||
def div10 context
|
||||
s = "div_10"
|
||||
compiler = Vm::MethodCompiler.create_method(:Integer,:div10 ).init_method
|
||||
me = compiler.process( Vm::Tree::KnownName.new( :self) )
|
||||
tmp = compiler.process( Vm::Tree::KnownName.new( :self) )
|
||||
q = compiler.process( Vm::Tree::KnownName.new( :self) )
|
||||
const = compiler.process( Vm::Tree::IntegerExpression.new(1) )
|
||||
# int tmp = self >> 1
|
||||
compiler.add_code Risc.op( s , ">>" , tmp , const)
|
||||
# int q = self >> 2
|
||||
compiler.add_load_constant( s , 2 , const)
|
||||
compiler.add_code Risc.op( s , ">>" , q , const)
|
||||
# q = q + tmp
|
||||
compiler.add_code Risc.op( s , "+" , q , tmp )
|
||||
# tmp = q >> 4
|
||||
compiler.add_load_constant( s , 4 , const)
|
||||
compiler.add_transfer( s, q , tmp)
|
||||
compiler.add_code Risc.op( s , ">>" , tmp , const)
|
||||
# q = q + tmp
|
||||
compiler.add_code Risc.op( s , "+" , q , tmp )
|
||||
# tmp = q >> 8
|
||||
compiler.add_load_constant( s , 8 , const)
|
||||
compiler.add_transfer( s, q , tmp)
|
||||
compiler.add_code Risc.op( s , ">>" , tmp , const)
|
||||
# q = q + tmp
|
||||
compiler.add_code Risc.op( s , "+" , q , tmp )
|
||||
# tmp = q >> 16
|
||||
compiler.add_load_constant( s , 16 , const)
|
||||
compiler.add_transfer( s, q , tmp)
|
||||
compiler.add_code Risc.op( s , ">>" , tmp , const)
|
||||
# q = q + tmp
|
||||
compiler.add_code Risc.op( s , "+" , q , tmp )
|
||||
# q = q >> 3
|
||||
compiler.add_load_constant( s , 3 , const)
|
||||
compiler.add_code Risc.op( s , ">>" , q , const)
|
||||
# tmp = q * 10
|
||||
compiler.add_load_constant( s , 10 , const)
|
||||
compiler.add_transfer( s, q , tmp)
|
||||
compiler.add_code Risc.op( s , "*" , tmp , const)
|
||||
# tmp = self - tmp
|
||||
compiler.add_code Risc.op( s , "-" , me , tmp )
|
||||
compiler.add_transfer( s , me , tmp)
|
||||
# tmp = tmp + 6
|
||||
compiler.add_load_constant( s , 6 , const)
|
||||
compiler.add_code Risc.op( s , "+" , tmp , const )
|
||||
# tmp = tmp >> 4
|
||||
compiler.add_load_constant( s , 4 , const)
|
||||
compiler.add_code Risc.op( s , ">>" , tmp , const )
|
||||
# return q + tmp
|
||||
compiler.add_code Risc.op( s , "+" , q , tmp )
|
||||
compiler.add_reg_to_slot( s , q , :message , :return_value)
|
||||
return compiler.method
|
||||
end
|
||||
end
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
69
lib/risc/builtin/kernel.rb
Normal file
69
lib/risc/builtin/kernel.rb
Normal file
@ -0,0 +1,69 @@
|
||||
module Risc
|
||||
module Builtin
|
||||
module Kernel
|
||||
module ClassMethods
|
||||
# this is the really really first place the machine starts (apart from the jump here)
|
||||
# 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
|
||||
compiler = Vm::MethodCompiler.create_method(:Kernel,:__init__ )
|
||||
new_start = Risc.label("__init__ start" , "__init__" )
|
||||
compiler.method.set_instructions( new_start)
|
||||
compiler.set_current new_start
|
||||
|
||||
space = Parfait.object_space
|
||||
space_reg = compiler.use_reg(:Space) #Set up the Space as self upon init
|
||||
compiler.add_load_constant("__init__ load Space", space , space_reg)
|
||||
message_ind = Risc.resolve_to_index( :space , :first_message )
|
||||
compiler.add_slot_to_reg( "__init__ load 1st message" , space_reg , message_ind , :message)
|
||||
compiler.add_reg_to_slot( "__init__ store Space in message", space_reg , :message , :receiver)
|
||||
#fixme: should add arg type here, as done in call_site (which this sort of is)
|
||||
exit_label = Risc.label("_exit_label for __init__" , "#{compiler.type.object_class.name}.#{compiler.method.name}" )
|
||||
ret_tmp = compiler.use_reg(:Label)
|
||||
compiler.add_load_constant("__init__ load return", exit_label , ret_tmp)
|
||||
compiler.add_reg_to_slot("__init__ store return", ret_tmp , :message , :return_address)
|
||||
compiler.add_code Risc.function_call( "__init__ issue call" , Parfait.object_space.get_main )
|
||||
compiler.add_code exit_label
|
||||
emit_syscall( compiler , :exit )
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
def exit context
|
||||
compiler = Vm::MethodCompiler.create_method(:Kernel,:exit ).init_method
|
||||
emit_syscall( compiler , :exit )
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
def emit_syscall compiler , name
|
||||
save_message( compiler )
|
||||
compiler.add_code Syscall.new("emit_syscall(#{name})", name )
|
||||
restore_message(compiler)
|
||||
return unless (@clazz and @method)
|
||||
compiler.add_label( "#{@clazz.name}.#{@message.name}" , "return_syscall" )
|
||||
end
|
||||
|
||||
# save the current message, as the syscall destroys all context
|
||||
#
|
||||
# This relies on linux to save and restore all registers
|
||||
#
|
||||
def save_message(compiler)
|
||||
r8 = RiscValue.new( :r8 , :Message)
|
||||
compiler.add_transfer("save_message", Risc.message_reg , r8 )
|
||||
end
|
||||
|
||||
def restore_message(compiler)
|
||||
r8 = RiscValue.new( :r8 , :Message)
|
||||
return_tmp = Risc.tmp_reg :Integer
|
||||
source = "_restore_message"
|
||||
# get the sys return out of the way
|
||||
compiler.add_transfer(source, Risc.message_reg , return_tmp )
|
||||
# load the stored message into the base RiscMachine
|
||||
compiler.add_transfer(source, r8 , Risc.message_reg )
|
||||
# save the return value into the message
|
||||
compiler.add_reg_to_slot( source , return_tmp , :message , :return_value )
|
||||
end
|
||||
end
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
40
lib/risc/builtin/object.rb
Normal file
40
lib/risc/builtin/object.rb
Normal file
@ -0,0 +1,40 @@
|
||||
require_relative "compile_helper"
|
||||
|
||||
module Risc
|
||||
module Builtin
|
||||
class Object
|
||||
module ClassMethods
|
||||
include CompileHelper
|
||||
|
||||
# self[index] basically. Index is the first arg
|
||||
# return is stored in return_value
|
||||
# (this method returns a new method off course, like all builtin)
|
||||
def get_internal_word context
|
||||
compiler = compiler_for(:Object , :get_internal_word )
|
||||
source = "get_internal_word"
|
||||
me , index = self_and_int_arg(compiler,source)
|
||||
# reduce me to me[index]
|
||||
compiler.add_slot_to_reg( source , me , index , me)
|
||||
# and put it back into the return value
|
||||
compiler.add_reg_to_slot( source , me , :message , :return_value)
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
# self[index] = val basically. Index is the first arg , value the second
|
||||
# no return
|
||||
def set_internal_word context
|
||||
compiler = compiler_for(:Object , :set_internal_word , {:value => :Object} )
|
||||
source = "set_internal_word"
|
||||
me , index = self_and_int_arg(compiler,source)
|
||||
value = load_int_arg_at(compiler,source , 2)
|
||||
|
||||
# do the set
|
||||
compiler.add_reg_to_slot( source , value , me , index)
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
end
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
25
lib/risc/builtin/space.rb
Normal file
25
lib/risc/builtin/space.rb
Normal file
@ -0,0 +1,25 @@
|
||||
require "ast/sexp"
|
||||
|
||||
module Risc
|
||||
module Builtin
|
||||
class Space
|
||||
module ClassMethods
|
||||
include AST::Sexp
|
||||
|
||||
# main entry point, ie __init__ calls this
|
||||
# defined here as empty, to be redefined
|
||||
def main context
|
||||
compiler = Vm::MethodCompiler.create_method(:Space , :main ).init_method
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
end
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require_relative "integer"
|
||||
require_relative "object"
|
||||
require_relative "kernel"
|
||||
require_relative "word"
|
52
lib/risc/builtin/word.rb
Normal file
52
lib/risc/builtin/word.rb
Normal file
@ -0,0 +1,52 @@
|
||||
require_relative "compile_helper"
|
||||
|
||||
module Risc
|
||||
module Builtin
|
||||
module Word
|
||||
module ClassMethods
|
||||
include CompileHelper
|
||||
|
||||
def putstring context
|
||||
compiler = Vm::MethodCompiler.create_method(:Word , :putstring ).init_method
|
||||
compiler.add_slot_to_reg( "putstring" , :message , :receiver , :new_message )
|
||||
index = Parfait::Word.get_length_index
|
||||
reg = RiscValue.new(:r2 , :Integer)
|
||||
compiler.add_slot_to_reg( "putstring" , :new_message , index , reg )
|
||||
Kernel.emit_syscall( compiler , :putstring )
|
||||
compiler.method
|
||||
end
|
||||
|
||||
# self[index] basically. Index is the first arg > 0
|
||||
# return (and word sized int) is stored in return_value
|
||||
def get_internal_byte context
|
||||
compiler = compiler_for(:Word , :get_internal_byte)
|
||||
source = "get_internal_byte"
|
||||
me , index = self_and_int_arg(compiler,source)
|
||||
# reduce me to me[index]
|
||||
compiler.add_byte_to_reg( source , me , index , me)
|
||||
# and put it back into the return value
|
||||
compiler.add_reg_to_slot( source , me , :message , :return_value)
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
# self[index] = val basically. Index is the first arg ( >0),
|
||||
# value the second
|
||||
# no return
|
||||
def set_internal_byte context
|
||||
compiler = compiler_for(:Word, :set_internal_byte , {:value => :Integer} )
|
||||
args = compiler.method.arguments
|
||||
len = args.instance_length
|
||||
raise "Compiler arg number mismatch, method=#{args} " if len != 3
|
||||
source = "set_internal_byte"
|
||||
me , index = self_and_int_arg(compiler,source)
|
||||
value = load_int_arg_at(compiler , source , 2 )
|
||||
# do the set
|
||||
compiler.add_reg_to_byte( source , value , me , index)
|
||||
return compiler.method
|
||||
end
|
||||
|
||||
end
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
58
lib/risc/collector.rb
Normal file
58
lib/risc/collector.rb
Normal file
@ -0,0 +1,58 @@
|
||||
module Risc
|
||||
|
||||
# collect anything that is in the space but and reachable from init
|
||||
module Collector
|
||||
def self.collect_space
|
||||
@objects = {}
|
||||
keep Parfait.object_space , 0
|
||||
Risc.machine.constants.each {|obj| keep(obj,0)}
|
||||
@objects
|
||||
end
|
||||
|
||||
def self.keep( object , depth )
|
||||
return if object.nil?
|
||||
return unless add_object( object , depth )
|
||||
# probably should make labels or even instructions derive from Parfait::Object, but . .
|
||||
if object.is_a? Risc::Label
|
||||
object.each_label { |l| self.add_object(l ,depth)}
|
||||
end
|
||||
return unless object.respond_to? :has_type?
|
||||
type = object.get_type
|
||||
keep(type , depth + 1)
|
||||
return if object.is_a? Symbol
|
||||
type.names.each do |name|
|
||||
keep(name , depth + 1)
|
||||
inst = object.get_instance_variable name
|
||||
keep(inst , depth + 1)
|
||||
end
|
||||
if object.is_a? Parfait::List
|
||||
object.each do |item|
|
||||
keep(item , depth + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Objects are data and get assembled after functions
|
||||
def self.add_object( objekt , depth)
|
||||
return false if @objects[objekt.object_id]
|
||||
return true if objekt.is_a? Fixnum
|
||||
#puts message(objekt , depth)
|
||||
#puts "ADD #{objekt.inspect}, #{objekt.name}" if objekt.is_a? Parfait::TypedMethod
|
||||
unless objekt.is_a?( Parfait::Object) or objekt.is_a?( Symbol) or objekt.is_a?( Risc::Label)
|
||||
raise "adding non parfait #{objekt.class}"
|
||||
end
|
||||
#raise "Method #{objekt.name}" if objekt.is_a? Parfait::TypedMethod
|
||||
@objects[objekt.object_id] = objekt
|
||||
true
|
||||
end
|
||||
|
||||
def self.message(object , depth)
|
||||
msg = "adding #{depth}#{' ' * depth}:"
|
||||
if( object.respond_to?(:sof_reference_name))
|
||||
msg + object.sof_reference_name.to_s
|
||||
else
|
||||
msg + object.class.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
37
lib/risc/eventable.rb
Normal file
37
lib/risc/eventable.rb
Normal file
@ -0,0 +1,37 @@
|
||||
# A simple event registering/triggering module to mix into classes.
|
||||
# Events are stored in the `@events` ivar.
|
||||
module Eventable
|
||||
|
||||
# Risc a handler for the given event name.
|
||||
# The event name is the method name called on the handler object
|
||||
#
|
||||
# obj.on(:foo , some_object_that_implements foo( whateverargs)
|
||||
#
|
||||
# @param [String, Symbol] name event name
|
||||
# @param [Object] object handling the event, ie implement the function name
|
||||
# @return handler
|
||||
def register_event(name, handler)
|
||||
event_table[name] << handler
|
||||
handler
|
||||
end
|
||||
|
||||
def unregister_event(name, handler)
|
||||
event_table[name].delete handler
|
||||
end
|
||||
|
||||
def event_table
|
||||
return @event_table if @event_table
|
||||
@event_table = Hash.new { |hash, key| hash[key] = [] }
|
||||
end
|
||||
|
||||
# Trigger the given event name and passes all args to each handler
|
||||
# for this event.
|
||||
#
|
||||
# obj.trigger(:foo)
|
||||
# obj.trigger(:foo, 1, 2, 3)
|
||||
#
|
||||
# @param [String, Symbol] name event name to trigger
|
||||
def trigger(name, *args)
|
||||
event_table[name].each { |handler| handler.send( name.to_sym , *args) }
|
||||
end
|
||||
end
|
132
lib/risc/instruction.rb
Normal file
132
lib/risc/instruction.rb
Normal file
@ -0,0 +1,132 @@
|
||||
module Risc
|
||||
|
||||
# the register machine has at least 8 registers, named r0-r5 , :lr and :pc (for historical reasons)
|
||||
# we can load and store their contents and
|
||||
# access (get/set) memory at a constant offset from a register
|
||||
# while the vm works with objects, the register machine has registers,
|
||||
# but we keep the names for better understanding, r4/5 are temporary/scratch
|
||||
# there is no direct memory access, only through registers
|
||||
# constants can/must be loaded into registers before use
|
||||
|
||||
# Instructions form a graph.
|
||||
# Linear instructions form a linked list
|
||||
# Branches fan out, Labels collect
|
||||
# Labels are the only valid branch targets
|
||||
class Instruction
|
||||
|
||||
def initialize source , nekst = nil
|
||||
@source = source
|
||||
@next = nekst
|
||||
return unless source
|
||||
raise "Source must be string or ast node, not #{source.class}" unless source.is_a?(String) or source.is_a?(Vm::Code)
|
||||
end
|
||||
attr_reader :source
|
||||
|
||||
# set the next instruction (also aliased as <<)
|
||||
# throw an error if that is set, use insert for that use case
|
||||
# return the instruction, so chaining works as one wants (not backwards)
|
||||
def set_next nekst
|
||||
raise "Next already set #{@next}" if @next
|
||||
@next = nekst
|
||||
nekst
|
||||
end
|
||||
alias :<< :set_next
|
||||
|
||||
# during translation we replace one by one
|
||||
def replace_next nekst
|
||||
old = @next
|
||||
@next = nekst
|
||||
@next.append old.next if old
|
||||
end
|
||||
|
||||
# get the next instruction (without arg given )
|
||||
# when given an interger, advance along the line that many time and return.
|
||||
def next( amount = 1)
|
||||
(amount == 1) ? @next : @next.next(amount-1)
|
||||
end
|
||||
# set the give instruction as the next, while moving any existing
|
||||
# instruction along to the given ones's next.
|
||||
# ie insert into the linked list that the instructions form
|
||||
def insert instruction
|
||||
instruction.set_next @next
|
||||
@next = instruction
|
||||
end
|
||||
|
||||
# return last set instruction. ie follow the linked list until it stops
|
||||
def last
|
||||
code = self
|
||||
code = code.next while( code.next )
|
||||
return code
|
||||
end
|
||||
|
||||
# set next for the last (see last)
|
||||
# so append the given code to the linked list at the end
|
||||
def append code
|
||||
last.set_next code
|
||||
end
|
||||
|
||||
def length labels = []
|
||||
ret = 1
|
||||
ret += self.next.length( labels ) if self.next
|
||||
ret
|
||||
end
|
||||
|
||||
def to_arr labels = []
|
||||
ret = [self.class]
|
||||
ret += self.next.to_arr(labels) if self.next
|
||||
ret
|
||||
end
|
||||
|
||||
# derived classes must provide a byte_length
|
||||
def byte_length
|
||||
raise "Abstract called on #{self}"
|
||||
end
|
||||
|
||||
def assemble_all io , labels = []
|
||||
self.assemble(io)
|
||||
self.next.assemble_all(io, labels) if self.next
|
||||
end
|
||||
|
||||
def assemble io
|
||||
raise "Abstract called on #{self}"
|
||||
end
|
||||
|
||||
def total_byte_length labels = []
|
||||
ret = self.byte_length
|
||||
ret += self.next.total_byte_length(labels) if self.next
|
||||
#puts "#{self.class.name} return #{ret}"
|
||||
ret
|
||||
end
|
||||
|
||||
def set_position position , labels = []
|
||||
Positioned.set_position(self,position)
|
||||
position += byte_length
|
||||
if self.next
|
||||
self.next.set_position(position , labels)
|
||||
else
|
||||
position
|
||||
end
|
||||
end
|
||||
|
||||
def each_label labels =[] , &block
|
||||
self.next.each_label(labels , &block) if self.next
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
require_relative "instructions/setter"
|
||||
require_relative "instructions/getter"
|
||||
require_relative "instructions/reg_to_slot"
|
||||
require_relative "instructions/slot_to_reg"
|
||||
require_relative "instructions/reg_to_byte"
|
||||
require_relative "instructions/byte_to_reg"
|
||||
require_relative "instructions/load_constant"
|
||||
require_relative "instructions/syscall"
|
||||
require_relative "instructions/function_call"
|
||||
require_relative "instructions/function_return"
|
||||
require_relative "instructions/register_transfer"
|
||||
require_relative "instructions/label"
|
||||
require_relative "instructions/branch"
|
||||
require_relative "instructions/operator_instruction"
|
67
lib/risc/instructions/branch.rb
Normal file
67
lib/risc/instructions/branch.rb
Normal file
@ -0,0 +1,67 @@
|
||||
module Risc
|
||||
|
||||
|
||||
# a branch must branch to a block.
|
||||
class Branch < Instruction
|
||||
def initialize source , to
|
||||
super(source)
|
||||
@label = to
|
||||
end
|
||||
attr_reader :label
|
||||
|
||||
def to_s
|
||||
"#{self.class.name.split("::").last}: #{label ? label.name : ''}"
|
||||
end
|
||||
alias :inspect :to_s
|
||||
|
||||
def length labels = []
|
||||
ret = super(labels)
|
||||
ret += self.label.length(labels) if self.label
|
||||
ret
|
||||
end
|
||||
|
||||
def to_arr( labels = [] )
|
||||
ret = super(labels)
|
||||
ret += self.label.to_arr(labels) if self.label
|
||||
ret
|
||||
end
|
||||
|
||||
def total_byte_length labels = []
|
||||
ret = super(labels)
|
||||
ret += self.label.total_byte_length(labels) if self.label
|
||||
#puts "#{self.class.name} return #{ret}"
|
||||
ret
|
||||
end
|
||||
|
||||
# labels have the same position as their next
|
||||
def set_position position , labels = []
|
||||
set_position self.label.set_position( position , labels ) if self.label
|
||||
super(position,labels)
|
||||
end
|
||||
|
||||
def assemble_all io , labels = []
|
||||
self.assemble(io)
|
||||
self.label.assemble_all(io,labels) if self.label
|
||||
self.next.assemble_all(io, labels) if self.next
|
||||
end
|
||||
|
||||
def each_label labels =[] , &block
|
||||
super
|
||||
self.label.each_label(labels , &block) if self.label
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class IsZero < Branch
|
||||
end
|
||||
|
||||
class IsNotzero < Branch
|
||||
end
|
||||
|
||||
class IsMinus < Branch
|
||||
end
|
||||
|
||||
class IsPlus < Branch
|
||||
end
|
||||
|
||||
end
|
20
lib/risc/instructions/byte_to_reg.rb
Normal file
20
lib/risc/instructions/byte_to_reg.rb
Normal file
@ -0,0 +1,20 @@
|
||||
module Risc
|
||||
|
||||
# ByteToReg moves a single byte into a register from memory.
|
||||
|
||||
# indexes are 1 based (as for slots) , which means we sacrifice a byte of every word
|
||||
# for our sanity
|
||||
|
||||
class ByteToReg < Getter
|
||||
|
||||
end
|
||||
|
||||
# Produce a ByteToReg instruction.
|
||||
# from and to are translated (from symbol to register if neccessary)
|
||||
# but index is left as is.
|
||||
def self.byte_to_reg( source , array , index , to)
|
||||
array = resolve_to_register( array)
|
||||
to = resolve_to_register( to)
|
||||
ByteToReg.new( source , array , index , to)
|
||||
end
|
||||
end
|
33
lib/risc/instructions/function_call.rb
Normal file
33
lib/risc/instructions/function_call.rb
Normal file
@ -0,0 +1,33 @@
|
||||
module Risc
|
||||
# name says it all really
|
||||
# only arg is the method object we want to call
|
||||
# assembly takes care of the rest (ie getting the address)
|
||||
|
||||
class FunctionCall < Instruction
|
||||
def initialize( source , method )
|
||||
super(source)
|
||||
@method = method
|
||||
end
|
||||
attr_reader :method
|
||||
|
||||
def to_s
|
||||
"FunctionCall: #{method.name}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.function_call( source , method )
|
||||
Risc::FunctionCall.new( source , method )
|
||||
end
|
||||
|
||||
def self.issue_call( compiler , callee )
|
||||
return_label = Risc.label("_return_label #{callee.name}" , "#{compiler.type.object_class.name}.#{compiler.method.name}" )
|
||||
ret_tmp = compiler.use_reg(:Label)
|
||||
compiler.add_load_constant("#{callee.name} load ret", return_label , ret_tmp)
|
||||
compiler.add_reg_to_slot("#{callee.name} store ret", ret_tmp , :new_message , :return_address)
|
||||
compiler.add_transfer("#{callee.name} move new message", Risc.new_message_reg , Risc.message_reg )
|
||||
compiler.add_code Risc.function_call( "#{callee.name} call" , callee )
|
||||
compiler.add_code return_label
|
||||
compiler.add_transfer("#{callee.name} remove new message", Risc.message_reg , Risc.new_message_reg )
|
||||
compiler.add_slot_to_reg("#{callee.name} restore message" , :new_message , :caller , :message )
|
||||
end
|
||||
end
|
22
lib/risc/instructions/function_return.rb
Normal file
22
lib/risc/instructions/function_return.rb
Normal file
@ -0,0 +1,22 @@
|
||||
module Risc
|
||||
|
||||
# return from a function call
|
||||
# register and index specify where the return address is stored
|
||||
|
||||
class FunctionReturn < Instruction
|
||||
def initialize( source , register , index)
|
||||
super(source)
|
||||
@register = register
|
||||
@index = index
|
||||
end
|
||||
attr_reader :register , :index
|
||||
|
||||
def to_s
|
||||
"FunctionReturn: #{register} [#{index}]"
|
||||
end
|
||||
end
|
||||
|
||||
def self.function_return( source , register , index)
|
||||
FunctionReturn.new( source , register , index)
|
||||
end
|
||||
end
|
38
lib/risc/instructions/getter.rb
Normal file
38
lib/risc/instructions/getter.rb
Normal file
@ -0,0 +1,38 @@
|
||||
module Risc
|
||||
|
||||
# Getter is a base class for get instructions (SlotToReg and ByteToReg , possibly more coming)
|
||||
#
|
||||
# The instruction that is modelled is loading data from an array into a register
|
||||
#
|
||||
# Getter has a
|
||||
# - an array where the data comes from
|
||||
# - and (array) index
|
||||
# - Risc that the data is moved to
|
||||
|
||||
# Getter and Setter api follow the pattern from -> to
|
||||
|
||||
class Getter < Instruction
|
||||
|
||||
# If you had a c array and index offset
|
||||
# the instruction would do register = array[index]
|
||||
# The arguments are in the order that makes sense for the Instruction name
|
||||
# So SlotToReg means the slot (array and index) moves to the register (last argument)
|
||||
def initialize source , array , index , register
|
||||
super(source)
|
||||
@array = array
|
||||
@index = index
|
||||
@register = register
|
||||
raise "index 0 " if index == 0
|
||||
raise "Not integer or reg #{index}" unless index.is_a?(Numeric) or RiscValue.look_like_reg(index)
|
||||
raise "Not register #{register}" unless RiscValue.look_like_reg(register)
|
||||
raise "Not register #{array}" unless RiscValue.look_like_reg(array)
|
||||
end
|
||||
attr_accessor :array , :index , :register
|
||||
|
||||
def to_s
|
||||
"#{self.class.name.split("::").last}: #{array}[#{index}] -> #{register}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
85
lib/risc/instructions/label.rb
Normal file
85
lib/risc/instructions/label.rb
Normal file
@ -0,0 +1,85 @@
|
||||
module Risc
|
||||
|
||||
# A label is a placeholder for it's next Instruction
|
||||
# It's function is not to turn into code, but to be a valid brnch target
|
||||
#
|
||||
# So branches and Labels are pairs, fan out, fan in
|
||||
#
|
||||
#
|
||||
|
||||
class Label < Instruction
|
||||
def initialize( source , name , nekst = nil)
|
||||
super(source , nekst)
|
||||
@name = name
|
||||
end
|
||||
attr_reader :name
|
||||
|
||||
def to_s
|
||||
"Label: #{@name} (#{self.next.class.name.split("::").last})"
|
||||
end
|
||||
|
||||
def sof_reference_name
|
||||
@name
|
||||
end
|
||||
|
||||
# a method start has a label of the form Class.method , test for that
|
||||
def is_method
|
||||
@name.split(".").length == 2
|
||||
end
|
||||
|
||||
def to_arr labels = []
|
||||
return [] if labels.include?(self)
|
||||
labels << self
|
||||
super
|
||||
end
|
||||
|
||||
def length labels = []
|
||||
return 0 if labels.include?(self)
|
||||
labels << self
|
||||
ret = 1
|
||||
ret += self.next.length(labels) if self.next
|
||||
ret
|
||||
end
|
||||
|
||||
def assemble io
|
||||
end
|
||||
|
||||
def assemble_all io , labels = []
|
||||
return if labels.include?(self)
|
||||
labels << self
|
||||
self.next.assemble_all(io,labels)
|
||||
end
|
||||
|
||||
def total_byte_length labels = []
|
||||
return 0 if labels.include?(self)
|
||||
labels << self
|
||||
ret = self.next.total_byte_length(labels)
|
||||
#puts "#{self.class.name} return #{ret}"
|
||||
ret
|
||||
end
|
||||
|
||||
# labels have the same position as their next
|
||||
def set_position position , labels = []
|
||||
return position if labels.include?(self)
|
||||
labels << self
|
||||
super(position , labels)
|
||||
self.next.set_position(position,labels)
|
||||
end
|
||||
|
||||
# shame we need this, just for logging
|
||||
def byte_length
|
||||
0
|
||||
end
|
||||
|
||||
def each_label labels =[] , &block
|
||||
return if labels.include?(self)
|
||||
labels << self
|
||||
block.yield(self)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def self.label( source , name , nekst = nil)
|
||||
Label.new( source , name , nekst = nil)
|
||||
end
|
||||
end
|
36
lib/risc/instructions/load_constant.rb
Normal file
36
lib/risc/instructions/load_constant.rb
Normal file
@ -0,0 +1,36 @@
|
||||
module Risc
|
||||
# load a constant into a register
|
||||
#
|
||||
# first is the actual constant, either immediate register or object reference (from the space)
|
||||
# second argument is the register the constant is loaded into
|
||||
|
||||
class LoadConstant < Instruction
|
||||
def initialize source , constant , register
|
||||
super(source)
|
||||
@register = register
|
||||
@constant = constant
|
||||
end
|
||||
attr_accessor :register , :constant
|
||||
|
||||
def to_s
|
||||
"LoadConstant: #{register} <- #{constant_str}"
|
||||
end
|
||||
|
||||
private
|
||||
def constant_str
|
||||
case @constant
|
||||
when String , Symbol , Fixnum , Integer
|
||||
@constant.to_s
|
||||
else
|
||||
if( @constant.respond_to? :sof_reference_name )
|
||||
constant.sof_reference_name
|
||||
else
|
||||
constant.class.name.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
def self.load_constant( source , constant , register )
|
||||
LoadConstant.new source , constant , register
|
||||
end
|
||||
end
|
21
lib/risc/instructions/operator_instruction.rb
Normal file
21
lib/risc/instructions/operator_instruction.rb
Normal file
@ -0,0 +1,21 @@
|
||||
module Risc
|
||||
|
||||
class OperatorInstruction < Instruction
|
||||
def initialize source , operator , left , right
|
||||
super(source)
|
||||
@operator = operator
|
||||
@left = left
|
||||
@right = right
|
||||
end
|
||||
attr_reader :operator, :left , :right
|
||||
|
||||
def to_s
|
||||
"OperatorInstruction: #{left} #{operator} #{right}"
|
||||
end
|
||||
|
||||
end
|
||||
def self.op source , operator , left , right
|
||||
OperatorInstruction.new source , operator , left , right
|
||||
end
|
||||
|
||||
end
|
21
lib/risc/instructions/reg_to_byte.rb
Normal file
21
lib/risc/instructions/reg_to_byte.rb
Normal file
@ -0,0 +1,21 @@
|
||||
module Risc
|
||||
|
||||
# RegToByte moves a byte into memory from a register.
|
||||
|
||||
# indexes are 1 based !
|
||||
|
||||
class RegToByte < Setter
|
||||
|
||||
end
|
||||
|
||||
# Produce a RegToByte instruction.
|
||||
# from and to are translated (from symbol to register if neccessary)
|
||||
# but index is left as is.
|
||||
def self.reg_to_byte( source , from , to , index)
|
||||
from = resolve_to_register from
|
||||
index = resolve_to_index( to , index)
|
||||
to = resolve_to_register to
|
||||
RegToByte.new( source, from , to , index)
|
||||
end
|
||||
|
||||
end
|
26
lib/risc/instructions/reg_to_slot.rb
Normal file
26
lib/risc/instructions/reg_to_slot.rb
Normal file
@ -0,0 +1,26 @@
|
||||
module Risc
|
||||
|
||||
# RegToSlot moves data into memory from a register.
|
||||
# SlotToReg moves data into a register from memory.
|
||||
# Both use a base memory (a register)
|
||||
|
||||
# This is because that is what cpu's can do. In programming terms this would be accessing
|
||||
# an element in an array, in the case of RegToSlot setting the register in the array.
|
||||
|
||||
# btw: to move data between registers, use RiscTransfer
|
||||
|
||||
class RegToSlot < Setter
|
||||
|
||||
end
|
||||
|
||||
# Produce a RegToSlot instruction.
|
||||
# From and to are registers or symbols that can be transformed to a register by resolve_to_register
|
||||
# index resolves with resolve_to_index.
|
||||
def self.reg_to_slot source , from , to , index
|
||||
from = resolve_to_register from
|
||||
index = resolve_to_index( to , index)
|
||||
to = resolve_to_register to
|
||||
RegToSlot.new( source, from , to , index)
|
||||
end
|
||||
|
||||
end
|
35
lib/risc/instructions/register_transfer.rb
Normal file
35
lib/risc/instructions/register_transfer.rb
Normal file
@ -0,0 +1,35 @@
|
||||
module Risc
|
||||
|
||||
# transfer the constents of one register to another.
|
||||
# possibly called move in some cpus
|
||||
|
||||
# There are other instructions to move data from / to memory, namely SlotToReg and RegToSlot
|
||||
|
||||
# Get/Set Slot move data around in vm objects, but transfer moves the objects (in the machine)
|
||||
#
|
||||
# Also it is used for moving temorary data
|
||||
#
|
||||
|
||||
class RiscTransfer < Instruction
|
||||
# initialize with from and to registers.
|
||||
# First argument from
|
||||
# second argument to
|
||||
#
|
||||
# Note: this may be reversed from some assembler notations (also arm)
|
||||
def initialize source , from , to
|
||||
super(source)
|
||||
@from = from
|
||||
@to = to
|
||||
raise "Fix me #{from}" unless from.is_a? RiscValue
|
||||
raise "Fix me #{to}" unless to.is_a? RiscValue
|
||||
end
|
||||
attr_reader :from, :to
|
||||
|
||||
def to_s
|
||||
"RiscTransfer: #{from} -> #{to}"
|
||||
end
|
||||
end
|
||||
def self.transfer( source , from , to)
|
||||
RiscTransfer.new( source , from , to)
|
||||
end
|
||||
end
|
36
lib/risc/instructions/setter.rb
Normal file
36
lib/risc/instructions/setter.rb
Normal file
@ -0,0 +1,36 @@
|
||||
module Risc
|
||||
# Setter is a base class for set instructions (RegToSlot and RegToByte , possibly more coming)
|
||||
#
|
||||
# The instruction that is modelled is loading data from a register into an array
|
||||
#
|
||||
# Setter has a
|
||||
# - Risc that the data is comes from
|
||||
# - an array where the data goes
|
||||
# - and (array) index
|
||||
|
||||
# Getter and Setter api follow the pattern from -> to
|
||||
|
||||
class Setter < Instruction
|
||||
|
||||
# If you had a c array and index offset
|
||||
# the instruction would do array[index] = register
|
||||
# So Setter means the register (first argument) moves to the slot (array and index)
|
||||
def initialize source , register , array , index
|
||||
super(source)
|
||||
@register = register
|
||||
@array = array
|
||||
@index = index
|
||||
raise "index 0 " if index == 0
|
||||
raise "Not integer or reg #{index}" unless index.is_a?(Numeric) or RiscValue.look_like_reg(index)
|
||||
raise "Not register #{register}" unless RiscValue.look_like_reg(register)
|
||||
raise "Not register #{array}" unless RiscValue.look_like_reg(array)
|
||||
end
|
||||
attr_accessor :register , :array , :index
|
||||
|
||||
def to_s
|
||||
"#{self.class.name.split("::").last}: #{register} -> #{array}[#{index}]"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
25
lib/risc/instructions/slot_to_reg.rb
Normal file
25
lib/risc/instructions/slot_to_reg.rb
Normal file
@ -0,0 +1,25 @@
|
||||
module Risc
|
||||
|
||||
# SlotToReg moves data into a register from memory.
|
||||
# RegToSlot moves data into memory from a register.
|
||||
# Both use a base memory (a register)
|
||||
|
||||
# This is because that is what cpu's can do. In programming terms this would be accessing
|
||||
# an element in an array, in the case of SlotToReg setting the value in the array.
|
||||
|
||||
# btw: to move data between registers, use RiscTransfer
|
||||
|
||||
class SlotToReg < Getter
|
||||
|
||||
end
|
||||
|
||||
# Produce a SlotToReg instruction.
|
||||
# Array and to are registers or symbols that can be transformed to a register by resolve_to_register
|
||||
# index resolves with resolve_to_index.
|
||||
def self.slot_to_reg source , array , index , to
|
||||
index = resolve_to_index( array , index)
|
||||
array = resolve_to_register( array )
|
||||
to = resolve_to_register( to )
|
||||
SlotToReg.new( source , array , index , to)
|
||||
end
|
||||
end
|
23
lib/risc/instructions/syscall.rb
Normal file
23
lib/risc/instructions/syscall.rb
Normal file
@ -0,0 +1,23 @@
|
||||
module Risc
|
||||
|
||||
# name says it all really
|
||||
# only arg is the method syscall name
|
||||
# how the layer below executes these is really up to it
|
||||
|
||||
# Any function issuing a Syscall should also save the current message
|
||||
# and restore it after the syscall, saving the return value in Return_index
|
||||
|
||||
class Syscall < Instruction
|
||||
|
||||
def initialize source ,name
|
||||
super(source)
|
||||
@name = name
|
||||
end
|
||||
attr_reader :name
|
||||
|
||||
def to_s
|
||||
"Syscall: #{name}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
276
lib/risc/interpreter.rb
Normal file
276
lib/risc/interpreter.rb
Normal file
@ -0,0 +1,276 @@
|
||||
|
||||
require_relative "eventable"
|
||||
|
||||
module Risc
|
||||
|
||||
# An interpreter for the register level. As the register machine is a simple model,
|
||||
# interpreting it is not so terribly difficult.
|
||||
#
|
||||
# There is a certain amount of basic machinery to fetch and execute the next instruction
|
||||
# (as a cpu would), and then there is a method for each instruction. Eg an instruction SlotToReg
|
||||
# will be executed by method execute_SlotToReg
|
||||
#
|
||||
# The Interpreter (a bit like a cpu) has a state flag, a current instruction and registers
|
||||
# We collect the stdout (as a hack not to interpret the OS)
|
||||
#
|
||||
class Interpreter
|
||||
# fire events for changed pc and register contents
|
||||
include Eventable
|
||||
include Logging
|
||||
log_level :info
|
||||
|
||||
attr_reader :instruction # current instruction or pc
|
||||
attr_reader :clock # current instruction or pc
|
||||
|
||||
attr_reader :registers # the registers, 16 (a hash, sym -> contents)
|
||||
attr_reader :stdout # collect the output
|
||||
attr_reader :state # running etc
|
||||
attr_reader :flags # somewhat like the lags on a cpu, hash sym => bool (zero .. . )
|
||||
|
||||
#start in state :stopped and set registers to unknown
|
||||
def initialize
|
||||
@state = :stopped
|
||||
@stdout = ""
|
||||
@registers = {}
|
||||
@flags = { :zero => false , :plus => false ,
|
||||
:minus => false , :overflow => false }
|
||||
@clock = 0
|
||||
(0...12).each do |r|
|
||||
set_register "r#{r}".to_sym , "r#{r}:unknown"
|
||||
end
|
||||
end
|
||||
|
||||
def start instruction
|
||||
initialize
|
||||
set_state(:running)
|
||||
set_instruction instruction
|
||||
end
|
||||
|
||||
def set_state state
|
||||
old = @state
|
||||
return if state == old
|
||||
@state = state
|
||||
trigger(:state_changed , old , state )
|
||||
end
|
||||
|
||||
def set_instruction i
|
||||
return if @instruction == i
|
||||
old = @instruction
|
||||
@instruction = i
|
||||
trigger(:instruction_changed, old , i)
|
||||
set_state( :exited) unless i
|
||||
end
|
||||
|
||||
def get_register( reg )
|
||||
reg = reg.symbol if reg.is_a? Risc::RiscValue
|
||||
raise "Not a register #{reg}" unless Risc::RiscValue.look_like_reg(reg)
|
||||
@registers[reg]
|
||||
end
|
||||
|
||||
def set_register reg , val
|
||||
old = get_register( reg ) # also ensures format
|
||||
if val.is_a? Fixnum
|
||||
@flags[:zero] = (val == 0)
|
||||
@flags[:plus] = (val >= 0)
|
||||
@flags[:minus] = (val < 0)
|
||||
log.debug "Set_flags #{val} :#{@flags.inspect}"
|
||||
else
|
||||
@flags[:zero] = @flags[:plus] = true
|
||||
@flags[:minus] = false
|
||||
end
|
||||
return if old === val
|
||||
reg = reg.symbol if reg.is_a? Risc::RiscValue
|
||||
@registers[reg] = val
|
||||
trigger(:register_changed, reg , old , val)
|
||||
end
|
||||
|
||||
def tick
|
||||
return unless @instruction
|
||||
@clock += 1
|
||||
name = @instruction.class.name.split("::").last
|
||||
log.debug "#{@clock.to_s}: #{@instruction.to_s}"
|
||||
fetch = send "execute_#{name}"
|
||||
return unless fetch
|
||||
set_instruction @instruction.next
|
||||
end
|
||||
|
||||
# Label is a noop.
|
||||
def execute_Label
|
||||
true
|
||||
end
|
||||
# Instruction interpretation starts here
|
||||
def execute_Branch
|
||||
label = @instruction.label
|
||||
set_instruction label
|
||||
false
|
||||
end
|
||||
|
||||
def execute_IsZero
|
||||
@flags[:zero] ? execute_Branch : true
|
||||
end
|
||||
def execute_IsNotzero
|
||||
@flags[:zero] ? true : execute_Branch
|
||||
end
|
||||
def execute_IsPlus
|
||||
@flags[:plus] ? execute_Branch : true
|
||||
end
|
||||
def execute_IsMinus
|
||||
@flags[:minus] ? execute_Branch : true
|
||||
end
|
||||
|
||||
def execute_LoadConstant
|
||||
to = @instruction.register
|
||||
value = @instruction.constant
|
||||
#value = value.object_id unless value.is_a?(Fixnum)
|
||||
set_register( to , value )
|
||||
true
|
||||
end
|
||||
|
||||
def execute_SlotToReg
|
||||
object = get_register( @instruction.array )
|
||||
if( @instruction.index.is_a?(Numeric) )
|
||||
index = @instruction.index
|
||||
else
|
||||
index = get_register(@instruction.index)
|
||||
end
|
||||
if object.is_a?(Symbol)
|
||||
raise "Must convert symbol to word:#{object}" unless( index == 2 )
|
||||
value = object.to_s.length
|
||||
else
|
||||
value = object.get_internal_word( index )
|
||||
end
|
||||
#value = value.object_id unless value.is_a? Fixnum
|
||||
set_register( @instruction.register , value )
|
||||
true
|
||||
end
|
||||
|
||||
def execute_RegToSlot
|
||||
value = get_register( @instruction.register )
|
||||
object = get_register( @instruction.array )
|
||||
if( @instruction.index.is_a?(Numeric) )
|
||||
index = @instruction.index
|
||||
else
|
||||
index = get_register(@instruction.index)
|
||||
end
|
||||
object.set_internal_word( index , value )
|
||||
trigger(:object_changed, @instruction.array , index)
|
||||
true
|
||||
end
|
||||
|
||||
def execute_ByteToReg
|
||||
object = get_register( @instruction.array )
|
||||
if( @instruction.index.is_a?(Numeric) )
|
||||
index = @instruction.index
|
||||
else
|
||||
index = get_register(@instruction.index)
|
||||
end
|
||||
raise "Unsupported action, must convert symbol to word:#{object}" if object.is_a?(Symbol)
|
||||
value = object.get_char( index )
|
||||
#value = value.object_id unless value.is_a? Fixnum
|
||||
set_register( @instruction.register , value )
|
||||
true
|
||||
end
|
||||
|
||||
def execute_RegToByte
|
||||
value = get_register( @instruction.register )
|
||||
object = get_register( @instruction.array )
|
||||
if( @instruction.index.is_a?(Numeric) )
|
||||
index = @instruction.index
|
||||
else
|
||||
index = get_register(@instruction.index)
|
||||
end
|
||||
object.set_char( index , value )
|
||||
trigger(:object_changed, @instruction.array , index / 4 )
|
||||
true
|
||||
end
|
||||
|
||||
def execute_RiscTransfer
|
||||
value = get_register @instruction.from
|
||||
set_register @instruction.to , value
|
||||
true
|
||||
end
|
||||
|
||||
def execute_FunctionCall
|
||||
set_instruction @instruction.method.instructions
|
||||
false
|
||||
end
|
||||
|
||||
def execute_FunctionReturn
|
||||
object = get_register( @instruction.register )
|
||||
link = object.get_internal_word( @instruction.index )
|
||||
@instruction = link
|
||||
# we jump back to the call instruction. so it is as if the call never happened and we continue
|
||||
true
|
||||
end
|
||||
|
||||
def execute_Syscall
|
||||
name = @instruction.name
|
||||
ret_value = 0
|
||||
case name
|
||||
when :putstring
|
||||
ret_value = handle_putstring
|
||||
when :exit
|
||||
set_instruction(nil)
|
||||
return false
|
||||
else
|
||||
raise "un-implemented syscall #{name}"
|
||||
end
|
||||
set_register( :r0 , ret_value ) # syscalls return into r0 , usually some int
|
||||
true
|
||||
end
|
||||
|
||||
def handle_putstring
|
||||
str = get_register( :r1 ) # should test length, ie r2
|
||||
case str
|
||||
when Symbol
|
||||
@stdout += str.to_s
|
||||
return str.to_s.length
|
||||
when Parfait::Word
|
||||
@stdout += str.to_string
|
||||
return str.char_length
|
||||
else
|
||||
raise "NO string for putstring #{str.class}:#{str.object_id}" unless str.is_a?(Symbol)
|
||||
end
|
||||
end
|
||||
|
||||
def execute_OperatorInstruction
|
||||
left = get_register(@instruction.left) || 0
|
||||
rr = @instruction.right
|
||||
right = get_register(rr) || 0
|
||||
@flags[:overflow] = false
|
||||
result = handle_operator(left,right)
|
||||
if( result > 2**32 )
|
||||
@flags[:overflow] = true
|
||||
result = result % 2**32
|
||||
else
|
||||
result = result.to_i
|
||||
end
|
||||
log.debug "#{@instruction} == #{result}(#{result.class}) (#{left}|#{right})"
|
||||
right = set_register(@instruction.left , result)
|
||||
true
|
||||
end
|
||||
|
||||
def handle_operator(left, right)
|
||||
case @instruction.operator.to_s
|
||||
when "+"
|
||||
return left + right
|
||||
when "-"
|
||||
return left - right
|
||||
when ">>"
|
||||
return left / (2**right)
|
||||
when "<<"
|
||||
return left * (2**right)
|
||||
when "*"
|
||||
return left * right
|
||||
when "&"
|
||||
return left & right
|
||||
when "|"
|
||||
return left | right
|
||||
when "=="
|
||||
return (left == right) ? 1 : 0
|
||||
else
|
||||
raise "unimplemented '#{@instruction.operator}' #{@instruction}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
81
lib/risc/machine.rb
Normal file
81
lib/risc/machine.rb
Normal file
@ -0,0 +1,81 @@
|
||||
require_relative "collector"
|
||||
|
||||
module Risc
|
||||
# The Risc Machine is an abstraction of the register level. This is seperate from the
|
||||
# actual assembler level to allow for several cpu architectures.
|
||||
# The Instructions (see class Instruction) define what the machine can do (ie load/store/maths)
|
||||
|
||||
# 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.
|
||||
#
|
||||
|
||||
class Machine
|
||||
include Collector
|
||||
include Logging
|
||||
log_level :info
|
||||
|
||||
def initialize
|
||||
@booted = false
|
||||
@constants = []
|
||||
end
|
||||
attr_reader :constants , :init , :booted
|
||||
|
||||
# idea being that later method missing could catch translate_xxx and translate to target xxx
|
||||
# now we just instantiate ArmTranslater and pass instructions
|
||||
def translate_arm
|
||||
methods = Parfait.object_space.collect_methods
|
||||
translate_methods( methods )
|
||||
label = @init.next
|
||||
@init = Arm::Translator.new.translate( @init )
|
||||
@init.append label
|
||||
end
|
||||
|
||||
def translate_methods(methods)
|
||||
translator = Arm::Translator.new
|
||||
methods.each do |method|
|
||||
log.debug "Translate method #{method.name}"
|
||||
instruction = method.instructions
|
||||
while instruction.next
|
||||
nekst = instruction.next
|
||||
t = translator.translate(nekst) # returning nil means no replace
|
||||
if t
|
||||
nekst = t.last
|
||||
instruction.replace_next(t)
|
||||
end
|
||||
instruction = nekst
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def boot
|
||||
initialize
|
||||
boot_parfait!
|
||||
@init = Branch.new( "__initial_branch__" , Parfait.object_space.get_init.instructions )
|
||||
@booted = true
|
||||
self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Module function to retrieve singleton
|
||||
def self.machine
|
||||
unless defined?(@machine)
|
||||
@machine = Machine.new
|
||||
end
|
||||
@machine
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Parfait::TypedMethod.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"
|
22
lib/risc/padding.rb
Normal file
22
lib/risc/padding.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# Helper functions to pad memory.
|
||||
#
|
||||
# Meory is always in lines, chunks of 8 words / 32 bytes
|
||||
module Padding
|
||||
|
||||
# objects only come in lengths of multiple of 8 words / 32 bytes
|
||||
# and there is a "hidden" 1 word that is used for debug/check memory corruption
|
||||
def self.padded( len )
|
||||
a = 32 * (1 + (len + 3)/32 )
|
||||
#puts "#{a} for #{len}"
|
||||
a
|
||||
end
|
||||
|
||||
def self.padded_words( words )
|
||||
padded(words*4) # 4 == word length, a constant waiting for a home
|
||||
end
|
||||
|
||||
def self.padding_for( length )
|
||||
pad = padded(length) - length # for header, type
|
||||
pad
|
||||
end
|
||||
end
|
32
lib/risc/positioned.rb
Normal file
32
lib/risc/positioned.rb
Normal file
@ -0,0 +1,32 @@
|
||||
# Helper module that extract position attribute.
|
||||
module Positioned
|
||||
@positions = {}
|
||||
|
||||
def self.positions
|
||||
@positions
|
||||
end
|
||||
|
||||
def self.position(object)
|
||||
pos = self.positions[object]
|
||||
if pos == nil
|
||||
str = "position accessed but not set, "
|
||||
str += "#{object.object_id.to_s(16)}\n"
|
||||
raise str + "for #{object.class} byte_length #{object.byte_length if object.respond_to?(:byte_length)} for #{object.inspect[0...100]}"
|
||||
end
|
||||
pos
|
||||
end
|
||||
|
||||
def self.set_position( object , pos )
|
||||
raise "Position must be number not :#{pos}:" unless pos.is_a?(Numeric)
|
||||
# resetting of position used to be error, but since relink and dynamic instruction size it is ok.
|
||||
# in measures (of 32)
|
||||
#puts "Setting #{pos} for #{self.class}"
|
||||
old = Positioned.positions[object]
|
||||
if old != nil and ((old - pos).abs > 10000)
|
||||
raise "position set again #{pos}!=#{old} for #{object}"
|
||||
end
|
||||
self.positions[object] = pos
|
||||
end
|
||||
|
||||
|
||||
end
|
112
lib/risc/register_value.rb
Normal file
112
lib/risc/register_value.rb
Normal file
@ -0,0 +1,112 @@
|
||||
module Risc
|
||||
|
||||
# RiscValue is like a variable name, a storage location. The location is a register off course.
|
||||
|
||||
class RiscValue
|
||||
|
||||
attr_accessor :symbol , :type , :value
|
||||
|
||||
def initialize r , type , value = nil
|
||||
raise "wrong type for register init #{r}" unless r.is_a? Symbol
|
||||
raise "double r #{r}" if r.to_s[0,1] == "rr"
|
||||
raise "not reg #{r}" unless self.class.look_like_reg r
|
||||
@type = type
|
||||
@symbol = r
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_s
|
||||
s = "#{symbol}:#{type}"
|
||||
s += ":#{value}" if value
|
||||
s
|
||||
end
|
||||
|
||||
def reg_no
|
||||
@symbol.to_s[1 .. -1].to_i
|
||||
end
|
||||
|
||||
def self.look_like_reg is_it
|
||||
return true if is_it.is_a? RiscValue
|
||||
return false unless is_it.is_a? Symbol
|
||||
if( [:lr , :pc].include? is_it )
|
||||
return true
|
||||
end
|
||||
if( (is_it.to_s.length <= 3) and (is_it.to_s[0] == "r"))
|
||||
# could tighten this by checking that the rest is a number
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
def == other
|
||||
return false if other.nil?
|
||||
return false if other.class != RiscValue
|
||||
symbol == other.symbol
|
||||
end
|
||||
|
||||
#helper method to calculate with register symbols
|
||||
def next_reg_use type , value = nil
|
||||
int = @symbol[1,3].to_i
|
||||
raise "No more registers #{self}" if int > 12
|
||||
sym = "r#{int + 1}".to_sym
|
||||
RiscValue.new( sym , type, value)
|
||||
end
|
||||
|
||||
def sof_reference_name
|
||||
@symbol
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Here we define the mapping from virtual machine objects, to register machine registers
|
||||
#
|
||||
|
||||
# The register we use to store the current message object is :r0
|
||||
def self.message_reg
|
||||
RiscValue.new :r0 , :Message
|
||||
end
|
||||
|
||||
# The register we use to store the new message object is :r3
|
||||
# The new message is the one being built, to be sent
|
||||
def self.new_message_reg
|
||||
RiscValue.new :r1 , :Message
|
||||
end
|
||||
|
||||
# The first scratch register. There is a next_reg_use to get a next and next.
|
||||
# Current thinking is that scratch is schatch between instructions
|
||||
def self.tmp_reg( type , value = nil)
|
||||
RiscValue.new :r2 , type , value
|
||||
end
|
||||
|
||||
# The first arg is a class name (possibly lowercase) and the second an instance variable name.
|
||||
# By looking up the class and the type for that class, we can resolve the instance
|
||||
# variable name to an index.
|
||||
# The class can be mapped to a register, and so we get a memory address (reg+index)
|
||||
def self.resolve_to_index( clazz_name , instance_name )
|
||||
return instance_name unless instance_name.is_a? Symbol
|
||||
real_name = clazz_name.to_s.split('_').last.capitalize.to_sym
|
||||
clazz = Parfait.object_space.get_class_by_name(real_name)
|
||||
raise "Class name not given #{real_name}" unless clazz
|
||||
index = clazz.instance_type.variable_index( instance_name )
|
||||
raise "Instance name=#{instance_name} not found on #{real_name}:#{clazz.instance_type}" unless index.is_a?(Numeric)
|
||||
return index # the type word is at index 0, but type is a list and starts at 1 == type
|
||||
end
|
||||
|
||||
# if a symbol is given, it may be the message or the new_message.
|
||||
# These are mapped to register references.
|
||||
# The valid symbols (:message,:new_message) are the same that are returned
|
||||
# by the slots. All data (at any time) is in one of the instance variables of these two
|
||||
# objects. Risc defines module methods with the same names (and _reg)
|
||||
def self.resolve_to_register( reference )
|
||||
return reference if reference.is_a?(RiscValue)
|
||||
case reference
|
||||
when :message
|
||||
return message_reg
|
||||
when :new_message
|
||||
return new_message_reg
|
||||
else
|
||||
raise "not recognized register reference #{reference} #{reference.class}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Reference in New Issue
Block a user