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:
Torsten Ruger
2017-01-19 09:02:29 +02:00
parent da5823a1a0
commit aa79e41d1c
127 changed files with 348 additions and 346 deletions

48
lib/risc/README.md Normal file
View 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
View 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
View 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

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

View 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

View 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

View 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

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

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