homing in on line length 100
This commit is contained in:
parent
33d464f032
commit
e651b57d08
@ -88,7 +88,7 @@ inlining, but i have at least an idea.
|
||||
|
||||
### Sof
|
||||
|
||||
**S**alama **O**bject **F**ile format is a yaml ike format to look at code dumps and help testing.
|
||||
**S**alama **O**bject **F**ile format is a yaml like format to look at code dumps and help testing.
|
||||
The dumper is ok and does produce (as intended) considerably denser dumps than yaml
|
||||
|
||||
When a reader is done (not started) the idea is to use sof as pre-compiled, language independent
|
||||
|
@ -9,15 +9,15 @@ module Arm
|
||||
|
||||
# Also, shortcuts are created to easily instantiate Instruction objects.
|
||||
# Example: pop -> StackInstruction.new( {:opcode => :pop}.merge(options) )
|
||||
# Instructions work with options, so you can pass anything in, and the only thing the functions does
|
||||
# is save you typing the clazz.new. It passes the function name as the :opcode
|
||||
# Instructions work with options, so you can pass anything in, and the only thing the functions
|
||||
# does is save you typing the clazz.new. It passes the function name as the :opcode
|
||||
|
||||
class ArmMachine
|
||||
|
||||
# conditions specify all the possibilities for branches. Branches are b + condition
|
||||
# Example: beq means brach if equal.
|
||||
# :al means always, so bal is an unconditional branch (but b() also works)
|
||||
CONDITIONS = [ :al , :eq , :ne , :lt , :le, :ge, :gt , :cs , :mi , :hi , :cc , :pl, :ls , :vc , :vs ]
|
||||
CONDITIONS = [:al ,:eq ,:ne ,:lt ,:le ,:ge,:gt ,:cs ,:mi ,:hi ,:cc ,:pl,:ls ,:vc ,:vs]
|
||||
|
||||
# here we create the shortcuts for the "standard" instructions, see above
|
||||
# Derived machines may use own instructions and define functions for them if so desired
|
||||
@ -67,8 +67,8 @@ module Arm
|
||||
# an actual machine must create derived classes (from this base class)
|
||||
# These instruction classes must follow a naming pattern and take a hash in the contructor
|
||||
# Example, a mov() opcode instantiates a Register::MoveInstruction
|
||||
# for an Arm machine, a class Arm::MoveInstruction < Register::MoveInstruction exists, and it will
|
||||
# be used to define the mov on an arm machine.
|
||||
# for an Arm machine, a class Arm::MoveInstruction < Register::MoveInstruction exists, and it
|
||||
# will be used to define the mov on an arm machine.
|
||||
# This methods picks up that derived class and calls a define_instruction methods that can
|
||||
# be overriden in subclasses
|
||||
def self.define_instruction_one(inst , clazz , defaults = {} )
|
||||
|
@ -28,7 +28,8 @@ module Arm
|
||||
|
||||
#codition codes can be applied to many instructions and thus save branches
|
||||
# :al => always , :eq => equal and so on
|
||||
# eq mov if equal :moveq r1 r2 (also exists as function) will only execute if the last operation was 0
|
||||
# eq mov if equal :moveq r1 r2 (also exists as function) will only execute
|
||||
# if the last operation was 0
|
||||
COND_CODES = {
|
||||
:al => 0b1110, :eq => 0b0000,
|
||||
:ne => 0b0001, :cs => 0b0010,
|
||||
@ -39,7 +40,8 @@ module Arm
|
||||
:ge => 0b1010, :gt => 0b1100,
|
||||
:vs => 0b0110
|
||||
}
|
||||
#return the bit pattern for the @attributes[:condition_code] variable, which signals the conditional code
|
||||
# return the bit pattern for the @attributes[:condition_code] variable,
|
||||
# which signals the conditional code
|
||||
def cond_bit_code
|
||||
COND_CODES[@attributes[:condition_code]] or throw "no code found for #{@attributes[:condition_code]}"
|
||||
end
|
||||
@ -96,7 +98,8 @@ module Arm
|
||||
op = 0
|
||||
#codes that one can shift, first two probably most common.
|
||||
# l (in lsr) means logical, ie unsigned, a (in asr) is arithmetic, ie signed
|
||||
{'lsl' => 0b000, 'lsr' => 0b010, 'asr' => 0b100, 'ror' => 0b110, 'rrx' => 0b110}.each do |short, bin|
|
||||
shift_codes = {'lsl' => 0b000, 'lsr' => 0b010, 'asr' => 0b100, 'ror' => 0b110, 'rrx' => 0b110}
|
||||
shift_codes.each do |short, bin|
|
||||
long = "shift_#{short}".to_sym
|
||||
if shif = @attributes[long]
|
||||
# TODO delete this code, AFTER you understand it
|
||||
|
@ -36,8 +36,10 @@ module Arm
|
||||
if arg.is_a?(Virtual::Block) or arg.is_a?(Parfait::Method)
|
||||
#relative addressing for jumps/calls
|
||||
diff = arg.position - self.position
|
||||
# but because of the arm "theoretical" 3- stage pipeline, we have to subtract 2 words (fetch/decode)
|
||||
# But, for methods, this happens to be the size of the object header, so there it balances out, but not blocks
|
||||
# but because of the arm "theoretical" 3- stage pipeline,
|
||||
# we have to subtract 2 words (fetch/decode)
|
||||
# But, for methods, this happens to be the size of the object header,
|
||||
# so there it balances out, but not blocks
|
||||
diff -= 8 if arg.is_a?(Virtual::Block)
|
||||
arg = diff
|
||||
end
|
||||
|
@ -19,12 +19,12 @@ module Arm
|
||||
attr_accessor :to , :from
|
||||
|
||||
# arm intructions are pretty sensible, and always 4 bytes (thumb not supported)
|
||||
# but not all constants fit into the part of the instruction that is left after the instruction code,
|
||||
# so large moves have to be split into two instructions.
|
||||
# but not all constants fit into the part of the instruction that is left after the instruction
|
||||
# code, so large moves have to be split into two instructions.
|
||||
# we handle this "transparently", just this instruction looks longer
|
||||
# alas, full transparency is not achieved as we only know when to use 2 instruction once we know where the
|
||||
# other object is, and that position is only set after code positions have been determined (in link) and so
|
||||
# see below in assemble
|
||||
# alas, full transparency is not achieved as we only know when to use 2 instruction once we
|
||||
# know where the other object is, and that position is only set after code positions have been
|
||||
# determined (in link) and so see below in assemble
|
||||
def mem_length
|
||||
@extra ? 8 : 4
|
||||
end
|
||||
@ -52,13 +52,14 @@ module Arm
|
||||
operand = op_with_rot
|
||||
immediate = 1
|
||||
else
|
||||
# unfortunately i was wrong in thinking the pi is armv7. The good news is the code below implements
|
||||
# the movw instruction (armv7 for moving a word) and works
|
||||
# unfortunately i was wrong in thinking the pi is armv7. The good news is the code
|
||||
# below implements the movw instruction (armv7 for moving a word) and works
|
||||
#armv7 raise "Too big #{right} " if (right >> 16) > 0
|
||||
#armv7 operand = (right & 0xFFF)
|
||||
#armv7 immediate = 1
|
||||
#armv7 rn = (right >> 12)
|
||||
# a little STRANGE, that the armv7 movw (move a 2 byte word) is an old test opcode, but there it is
|
||||
# a little STRANGE, that the armv7 movw (move a 2 byte word) is an old test opcode,
|
||||
# but there it is
|
||||
#armv7 @attributes[:opcode] = :tst
|
||||
raise "No negatives implemented #{right} " if right < 0
|
||||
# and so it continues: when we notice that the const doesn't fit, first time we raise an
|
||||
@ -74,7 +75,8 @@ module Arm
|
||||
raise "no fit for #{right}" unless operand
|
||||
immediate = 1
|
||||
@extra = ArmMachine.add( to , to , (right & 0xFF) )
|
||||
#TODO: this is still a hack, as it does not encode all possible values. The way it _should_ be done
|
||||
#TODO: this is still a hack, as it does not encode all possible values.
|
||||
# The way it _should_ be done
|
||||
# is to check that the first part is doabe with u8_with_rr AND leaves a u8 remainder
|
||||
end
|
||||
elsif (right.is_a?(Symbol) or right.is_a?(::Register::RegisterReference))
|
||||
|
@ -43,16 +43,16 @@ module Arm
|
||||
syscall( function.insertion_point , 1 ) # 1 == exit
|
||||
end
|
||||
|
||||
|
||||
|
||||
# the number (a Virtual::integer) is (itself) divided by 10, ie overwritten by the result
|
||||
# and the remainder is overwritten (ie an out argument)
|
||||
# not really a function, more a macro,
|
||||
# not really a function, more a macro,
|
||||
def div10 function, number , remainder
|
||||
# Note about division: devision is MUCH more expensive than one would have thought
|
||||
# And coding it is a bit of a mind leap: it's all about finding a a result that gets the
|
||||
# And coding it is a bit of a mind leap: it's all about finding a a result that gets the
|
||||
# remainder smaller than an int. i'll post some links sometime. This is from the arm manual
|
||||
tmp = function.new_local
|
||||
function.instance_eval do
|
||||
function.instance_eval do
|
||||
sub( remainder , number , 10 )
|
||||
sub( number , number , number , shift_lsr: 2)
|
||||
add( number , number , number , shift_lsr: 4)
|
||||
@ -67,7 +67,8 @@ module Arm
|
||||
end
|
||||
|
||||
def syscall block , num
|
||||
# This is very arm specific, syscall number is passed in r7, other arguments like a c call ie 0 and up
|
||||
# This is very arm specific, syscall number is passed in r7,
|
||||
# other arguments like a c call ie 0 and up
|
||||
sys = Virtual::Integer.new( Virtual::RegisterReference.new(SYSCALL_REG) )
|
||||
ret = Virtual::Integer.new( Virtual::RegisterReference.new(RETURN_REG) )
|
||||
block.add_code mov( sys , num )
|
||||
@ -78,4 +79,3 @@ module Arm
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
module Arm
|
||||
|
||||
# Arm stores the return address in a register (not on the stack)
|
||||
# The register is called link , or lr for short . Maybe because it provides the "link" back to the caller (?)
|
||||
# The register is called link , or lr for short .
|
||||
# Maybe because it provides the "link" back to the caller (?)
|
||||
|
||||
# the vm defines a register for the location, so we store it there.
|
||||
# the vm defines a register for the location, so we store it there.
|
||||
|
||||
class SaveImplementation
|
||||
def run block
|
||||
|
@ -12,7 +12,7 @@ module Virtual
|
||||
# PS: can't say i fancy the << self syntax and am considerernig adding a
|
||||
# keyword for it, like meta
|
||||
# In effect it is a very similar construct to def self.function(...)
|
||||
# So one could write def meta.function(...) and thus define on the meta-class
|
||||
# So one could write def meta.function(...) and thus define on the meta-class
|
||||
class MetaClass < Object
|
||||
# no name, nor nothing. as this is just the object really
|
||||
|
||||
@ -44,7 +44,8 @@ module Virtual
|
||||
# get the function and if not found, try superclasses. raise error if not found
|
||||
def resolve_method name
|
||||
fun = get_function name
|
||||
# TODO THE BOOK says is class A derives from B , then the metaclass of A derives from the metaclass of B
|
||||
# TODO THE BOOK says is class A derives from B , then the metaclass of
|
||||
# A derives from the metaclass of B
|
||||
# just get to it ! (and stop whimpering)
|
||||
raise "Method not found #{name} , for #{inspect}" unless fun
|
||||
fun
|
||||
|
@ -14,7 +14,8 @@ module Parfait
|
||||
# The Space contains all objects for a program. In functional terms it is a program, but in oo
|
||||
# it is a collection of objects, some of which are data, some classes, some functions
|
||||
|
||||
# The main entry is a function called (of all things) "main", This _must be supplied by the compling
|
||||
# The main entry is a function called (of all things) "main".
|
||||
# This _must be supplied by the compled code (similar to c)
|
||||
# There is a start and exit block that call main, which receives an List of strings
|
||||
|
||||
# While data ususally would live in a .data section, we may also "inline" it into the code
|
||||
|
@ -1,12 +1,13 @@
|
||||
module Register
|
||||
class LinkException < Exception
|
||||
end
|
||||
# Assmble the object space into a binary.
|
||||
# Assemble the object space into a binary.
|
||||
# Link first to get positions, then assemble
|
||||
# link and assemble functions for each class are close to each other, so to get them the same.
|
||||
# meaning: as the link function determines the length of an object and the assemble 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.
|
||||
|
||||
# The link function determines the length of an object and the assemble 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
|
||||
TYPE_REF = 0
|
||||
TYPE_INT = 1
|
||||
@ -119,7 +120,9 @@ module Register
|
||||
# write type and layout 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 assemble_self( object , variables )
|
||||
raise "Object(#{object.object_id}) not linked #{object.inspect}" unless @objects[object.object_id]
|
||||
unless @objects[object.object_id]
|
||||
raise "Object(#{object.object_id}) not linked #{object.inspect}"
|
||||
end
|
||||
type = type_word(variables)
|
||||
@stream.write_uint32( type )
|
||||
write_ref_for(object.layout[:names] )
|
||||
@ -207,9 +210,8 @@ module Register
|
||||
|
||||
private
|
||||
|
||||
# write means we write the resulting address straight into the assembler stream (ie don't return it)
|
||||
# write means we write the resulting address straight into the assembler stream
|
||||
# object means the object of which we write the address
|
||||
# and we write the address into the self, given as second parameter
|
||||
def write_ref_for object
|
||||
@stream.write_sint32 object.position
|
||||
end
|
||||
|
@ -2,7 +2,8 @@
|
||||
module Builtin
|
||||
module Integer
|
||||
module ClassMethods
|
||||
# The conversion to base10 is quite a bit more complicated than i thought. The bulk of it is in div10
|
||||
# The conversion to base10 is quite a bit more complicated than i thought.
|
||||
# The bulk of it is in div10
|
||||
# We set up variables, do the devision and write the result to the string
|
||||
# then check if were done and recurse if neccessary
|
||||
# As we write before we recurse (save a push) we write the number backwards
|
||||
|
@ -4,7 +4,8 @@ module Builtin
|
||||
|
||||
# return the index of the variable. Now "normal" code can't really do anything with that, but
|
||||
# set/get instance variable use it.
|
||||
# This is just a placeholder, as we code this in ruby, but the instance methods need the definition before.
|
||||
# This is just a placeholder, as we code this in ruby,
|
||||
# but the instance methods need the definition before.
|
||||
def index_of context , name = Virtual::Integer
|
||||
index_function = Virtual::CompiledMethodInfo.create_method("Object" , "index_of" , [Virtual::Reference] )
|
||||
index_function.info.return_type = Virtual::Integer
|
||||
@ -22,7 +23,8 @@ module Builtin
|
||||
# i = self.index_of(var)
|
||||
# return at_index(i)
|
||||
# end
|
||||
# The at_index is just "below" the api, something we need but don't want to expose, so we can't code the above in ruby
|
||||
# The at_index is just "below" the api, something we need but don't want to expose,
|
||||
# so we can't code the above in ruby
|
||||
def _get_instance_variable context , name = Virtual::Integer
|
||||
get_function = Virtual::CompiledMethodInfo.create_method("Object","_get_instance_variable" , [ Virtual::Reference ] )
|
||||
return get_function
|
||||
|
@ -2,7 +2,8 @@ module Register
|
||||
|
||||
# RegisterReference is not the name for a register, "only" for a certain use of it.
|
||||
# In a way it is like a variable name, a storage location. The location is a register off course,
|
||||
# but which register can be changed, and _all_ instructions sharing the RegisterReference then use that register
|
||||
# but which register can be changed, and _all_ instructions sharing the RegisterReference then
|
||||
# use that register
|
||||
# In other words a simple level of indirection, or change from value to reference sematics.
|
||||
|
||||
class RegisterReference
|
||||
|
@ -15,8 +15,8 @@ module Virtual
|
||||
|
||||
# They also have local variables.
|
||||
|
||||
# Code-wise Methods are made up from a list of Blocks, in a similar way blocks are made up of Instructions
|
||||
# The function starts with one block, and that has a start and end (return)
|
||||
# Code-wise Methods are made up from a list of Blocks, in a similar way blocks are made up of
|
||||
# Instructions. The function starts with one block, and that has a start and end (return)
|
||||
|
||||
# Blocks can be linked in two ways:
|
||||
# -linear: flow continues from one to the next as they are sequential both logically and
|
||||
@ -59,7 +59,9 @@ module Virtual
|
||||
# add an instruction after the current (insertion point)
|
||||
# the added instruction will become the new insertion point
|
||||
def add_code instruction
|
||||
raise instruction.inspect unless (instruction.is_a?(Instruction) or instruction.is_a?(Register::Instruction))
|
||||
unless (instruction.is_a?(Instruction) or instruction.is_a?(Register::Instruction))
|
||||
raise instruction.inspect
|
||||
end
|
||||
@current.add_code(instruction) #insert after current
|
||||
self
|
||||
end
|
||||
@ -79,21 +81,24 @@ module Virtual
|
||||
used.uniq
|
||||
end
|
||||
|
||||
# control structures need to see blocks as a graph, but they are stored as a list with implict branches
|
||||
# control structures need to see blocks as a graph, but they are stored as a list with implict
|
||||
# branches
|
||||
# So when creating a new block (with new_block), it is only added to the list, but instructions
|
||||
# still go to the current one
|
||||
# With this function one can change the current block, to actually code it.
|
||||
# This juggling is (unfortunately) neccessary, as all compile functions just keep puring their code into the
|
||||
# method and don't care what other compiles (like if's) do.
|
||||
# This juggling is (unfortunately) neccessary, as all compile functions just keep puring their
|
||||
# code into the method and don't care what other compiles (like if's) do.
|
||||
|
||||
# Example: while, needs 2 extra blocks
|
||||
# 1 condition code, must be its own blockas we jump back to it
|
||||
# - the body, can actually be after the condition as we don't need to jump there
|
||||
# 2 after while block. Condition jumps here
|
||||
# After block 2, the function is linear again and the calling code does not need to know what happened
|
||||
# After block 2, the function is linear again and the calling code does not need to know what
|
||||
# happened
|
||||
|
||||
# But subsequent statements are still using the original block (self) to add code to
|
||||
# So the while expression creates the extra blocks, adds them and the code and then "moves" the insertion point along
|
||||
# So the while expression creates the extra blocks, adds them and the code and then "moves"
|
||||
# the insertion point along
|
||||
def current block
|
||||
@current = block
|
||||
self
|
||||
|
@ -79,7 +79,9 @@ module Virtual
|
||||
|
||||
#attr_reader :left, :right
|
||||
def self.compile_assignment expression , method
|
||||
raise "must assign to NameExpression , not #{expression.left}" unless expression.left.instance_of? Ast::NameExpression
|
||||
unless expression.left.instance_of? Ast::NameExpression
|
||||
raise "must assign to NameExpression , not #{expression.left}"
|
||||
end
|
||||
r = Compiler.compile(expression.right , method )
|
||||
raise "oh noo, nil from where #{expression.right.inspect}" unless r
|
||||
index = method.has_arg(Virtual.new_word name)
|
||||
|
@ -4,7 +4,8 @@ module Virtual
|
||||
|
||||
def self.compile_if expression , method
|
||||
# to execute the logic as the if states it, the blocks are the other way around
|
||||
# so we can the jump over the else if true ,and the else joins unconditionally after the true_block
|
||||
# so we can the jump over the else if true ,
|
||||
# and the else joins unconditionally after the true_block
|
||||
merge_block = method.info.new_block "if_merge" # last one, created first
|
||||
true_block = method.info.new_block "if_true" # second, linked in after current, before merge
|
||||
false_block = method.info.new_block "if_false" # directly next in order, ie if we don't jump we land here
|
||||
|
@ -10,7 +10,8 @@ module Virtual
|
||||
puts "Created class #{clazz.name.inspect}"
|
||||
expression.expressions.each do |expr|
|
||||
# check if it's a function definition and add
|
||||
# if not, execute it, but that does means we should be in salama (executable), not ruby. ie throw an error for now
|
||||
# if not, execute it, but that does means we should be in salama (executable), not ruby.
|
||||
# ie throw an error for now
|
||||
raise "only functions for now #{expr.inspect}" unless expr.is_a? Ast::FunctionExpression
|
||||
#puts "compiling expression #{expression}"
|
||||
expression_value = Compiler.compile(expr,method )
|
||||
|
@ -15,7 +15,9 @@ module Virtual
|
||||
if expression_value.is_a?(IntegerConstant) or expression_value.is_a?(ObjectConstant)
|
||||
return_reg.load into , expression_value
|
||||
else
|
||||
return_reg.move( into, expression_value ) if expression_value.register_symbol != return_reg.register_symbol
|
||||
if expression_value.register_symbol != return_reg.register_symbol
|
||||
return_reg.move( into, expression_value )
|
||||
end
|
||||
end
|
||||
#function.set_return return_reg
|
||||
return return_reg
|
||||
|
@ -1,10 +1,11 @@
|
||||
module Virtual
|
||||
|
||||
|
||||
# the first instruction we need is to stop. Off course in a real machine this would be a syscall, but that is just
|
||||
# an implementation (in a programm it would be a function).
|
||||
# the first instruction we need is to stop. Off course in a real machine this would be a syscall,
|
||||
# but that is just an implementation (in a programm it would be a function).
|
||||
# But in a virtual machine, not only do we need this instruction,
|
||||
# it is indeed the first instruction as just this instruction is the smallest possible programm for the machine.
|
||||
# it is indeed the first instruction as just this instruction is the smallest possible programm
|
||||
# for the machine.
|
||||
# As such it is the next instruction for any first instruction that we generate.
|
||||
class Halt < Instruction
|
||||
end
|
||||
|
@ -1,7 +1,9 @@
|
||||
module Virtual
|
||||
|
||||
# Get a instance variable by _name_ . So we have to resolve the name to an index to trnsform into a Slot
|
||||
# The slot may the be used in a set on left or right hand. The transformation is done by GetImplementation
|
||||
# Get a instance variable by _name_ . So we have to resolve the name to an index to
|
||||
# transform into a Slot
|
||||
# The slot may the be used in a set on left or right hand.
|
||||
# The transformation is done by GetImplementation
|
||||
class InstanceGet < Instruction
|
||||
def initialize name
|
||||
@name = name.to_sym
|
||||
|
@ -1,8 +1,8 @@
|
||||
module Virtual
|
||||
|
||||
# class for Set instructions, A set is basically a mem move.
|
||||
# to and from are indexes into the known objects(frame,message,self and new_message), these are represented as slots
|
||||
# (see there)
|
||||
# to and from are indexes into the known objects(frame,message,self and new_message),
|
||||
# these are represented as slots (see there)
|
||||
# from may be a Constant (Object,Integer,String,Class)
|
||||
class Set < Instruction
|
||||
def initialize to , from
|
||||
|
@ -1,34 +1,37 @@
|
||||
module Virtual
|
||||
# The Virtual Machine is a value based virtual machine in which ruby is implemented. While it is value based,
|
||||
# it resembles oo in basic ways of object encapsulation and method invokation, it is a "closed" / static sytem
|
||||
# in that all types are know and there is no dynamic dispatch (so we don't bite our tail here).
|
||||
# The Virtual Machine is a value based virtual machine in which ruby is implemented.
|
||||
# While it is value based, it resembles oo in basic ways of object encapsulation and method
|
||||
# invocation, it is a "closed" / static sytem in that all types are know and there is no
|
||||
# dynamic dispatch (so we don't bite our tail here).
|
||||
#
|
||||
# It is minimal and realistic and low level
|
||||
# - minimal means that if one thing can be implemented by another, it is left out. This is quite the opposite from
|
||||
# ruby, which has several loops, many redundant if forms and the like.
|
||||
# - realistic means it is easy to implement on a 32 bit machine (arm) and possibly 64 bit. Memory access, a stack,
|
||||
# some registers of same size are the underlying hardware. (not ie byte machine)
|
||||
# - low level means it's basic instructions are realively easily implemented in a register machine. ie send is not
|
||||
# a an instruction but a function.
|
||||
# - minimal means that if one thing can be implemented by another, it is left out. This is quite
|
||||
# the opposite from ruby, which has several loops, many redundant if forms and the like.
|
||||
# - realistic means it is easy to implement on a 32 bit machine (arm) and possibly 64 bit.
|
||||
# Memory access,some registers of same size are the underlying hardware. (not ie byte machine)
|
||||
# - low level means it's basic instructions are realively easily implemented in a register machine.
|
||||
# ie send is not a an instruction but a function.
|
||||
#
|
||||
# So the memory model of the machine allows for indexed access into an "object" . A fixed number of objects exist
|
||||
# (ie garbage collection is reclaming, not destroying and recreating) although there may be a way to increase that number.
|
||||
# So the memory model of the machine allows for indexed access into an "object" .
|
||||
# A fixed number of objects exist (ie garbage collection is reclaming, not destroying and
|
||||
# recreating) although there may be a way to increase that number.
|
||||
#
|
||||
# The ast is transformed to virtaul-machine objects, some of which represent code, some data.
|
||||
#
|
||||
# The next step transforms to the register machine layer, which is what actually executes.
|
||||
#
|
||||
|
||||
# More concretely, a virtual machine is a sort of oo turing machine, it has a current instruction, executes the
|
||||
# instructions, fetches the next one and so on.
|
||||
# More concretely, a virtual machine is a sort of oo turing machine, it has a current instruction,
|
||||
# executes the instructions, fetches the next one and so on.
|
||||
# Off course the instructions are not soo simple, but in oo terms quite so.
|
||||
#
|
||||
# The machine is virtual in the sense that it is completely modeled in software,
|
||||
# it's complete state explicitly available (not implicitly by walking stacks or something)
|
||||
|
||||
# The machine has a no register, but local variables, a scope at each point in time.
|
||||
# Scope changes with calls and blocks, but is saved at each level. In terms of lower level implementation this means
|
||||
# that the the model is such that what is a variable in ruby, never ends up being just on the pysical stack.
|
||||
# Scope changes with calls and blocks, but is saved at each level. In terms of lower level
|
||||
# implementation this means that the the model is such that what is a variable in ruby,
|
||||
# never ends up being just on the pysical stack.
|
||||
#
|
||||
class Machine
|
||||
|
||||
|
@ -1,21 +1,23 @@
|
||||
module Virtual
|
||||
# So when an object calls a method, or sends a message, this is what it sends: a Message
|
||||
|
||||
# A message contains the sender, return and exceptional return addresses,the arguments, and a slot for the frame.
|
||||
# A message contains the sender, return and exceptional return addresses,the arguments,
|
||||
# and a slot for the frame.
|
||||
|
||||
# As such it is a very run-time object, deep in the machinery as it were, and does not have meaningful
|
||||
# methods you could call at compile time.
|
||||
# As such it is a very run-time object, deep in the machinery as it were, and does not have
|
||||
# meaningful methods you could call at compile time.
|
||||
|
||||
# The methods that are there, are nevertheless meant to be called at compile time and generate code, rather than
|
||||
# executing it.
|
||||
# The methods that are there, are nevertheless meant to be called at compile time and generate
|
||||
# code, rather than executing it.
|
||||
|
||||
# The caller creates the Message and passes control to the receiver's method
|
||||
|
||||
# The receiver create a new Frame to hold local and temporary variables and (later) creates default values for
|
||||
# arguments that were not passed
|
||||
# The receiver create a new Frame to hold local and temporary variables and (later) creates
|
||||
# default values for arguments that were not passed
|
||||
|
||||
# How the actual finding of the method takes place (acording to the ruby rules) is not simple, but as there is a
|
||||
# guaranteed result (be it method_missing) it does not matter to the passing mechanism described
|
||||
# How the actual finding of the method takes place (acording to the ruby rules) is not simple,
|
||||
# but as there is a guaranteed result (be it method_missing) it does not matter to the passing
|
||||
# mechanism described
|
||||
|
||||
# During compilation Message and frame objects are created to do type analysis
|
||||
|
||||
|
@ -25,7 +25,9 @@ module FakeMem
|
||||
end
|
||||
#TODO, this is copied from module Positioned, maybe avoid duplication ?
|
||||
def position
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...1000]}" if @position == nil
|
||||
if @position == nil
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...1000]}"
|
||||
end
|
||||
@position
|
||||
end
|
||||
def set_position pos
|
||||
|
@ -3,14 +3,18 @@ module Virtual
|
||||
|
||||
# Frames and Message are very similar apart from the class name
|
||||
# - All existing instances are stored in the space for both
|
||||
# - Size is currently 2, ie 16 words (TODO a little flexibility here would not hurt, but the mountain is big)
|
||||
# - Unused instances for a linked list with their first instance variable. This is HARD coded to avoid any lookup
|
||||
# - Size is currently 2, ie 16 words
|
||||
# (TODO a little flexibility here would not hurt, but the mountain is big)
|
||||
# - Unused instances for a linked list with their first instance variable.
|
||||
# This is HARD coded to avoid any lookup
|
||||
|
||||
# Just as a reminder: a message object is created before a send and holds return address/message and arguemnts + self
|
||||
# frames are created upon entering a method and hold local and temporary variables
|
||||
# as a result one of each is created for every single method call. A LOT, so make it fast luke
|
||||
# Note: this is off course the reason for stack based implementations that just increment a known pointer/register or
|
||||
# something. But i think most programs are memory bound and a few extra instructions don't hurt.
|
||||
# Just as a reminder: a message object is created before a send and holds return address/message
|
||||
# and arguemnts + self frames are created upon entering a method and hold local and temporary
|
||||
# variables as a result one of each is created for every single method call.
|
||||
# A LOT, so make it fast luke
|
||||
# Note: this is off course the reason for stack based implementations that just increment
|
||||
# a known pointer/register or something. But i think most programs are memory bound
|
||||
# and a few extra instructions don't hurt.
|
||||
# After all, we are buying a big prize:oo, otherwise known as sanity.
|
||||
|
||||
class FrameImplementation
|
||||
|
@ -1,7 +1,9 @@
|
||||
module Virtual
|
||||
# This implements instance variable get (not the opposite of Set, such a thing does not exists, their slots)
|
||||
# This implements instance variable get (not the opposite of Set,
|
||||
# such a thing does not exists, their slots)
|
||||
|
||||
# Ivar get needs to acces the layout, find the index of the name, and shuffle the data to return register
|
||||
# Ivar get needs to acces the layout, find the index of the name,
|
||||
# and shuffle the data to return register
|
||||
# In short it's so complicated we implement it in ruby and stick the implementation here
|
||||
class GetImplementation
|
||||
def run block
|
||||
|
@ -4,9 +4,11 @@ module Virtual
|
||||
# That off course opens up an endless loop possibility that we stop by
|
||||
# implementing Class and Module methods
|
||||
|
||||
# Note: I find it slightly unsemetrical that the NewMessage object needs to be created before this instruction
|
||||
# This is because all expressions create a (return) value and that return value is overwritten by the next
|
||||
# expression unless saved. And since the message is the place to save it it needs to exist. qed
|
||||
# Note: I find it slightly unsymmetrical that the NewMessage object needs to be created
|
||||
# before this instruction.
|
||||
# This is because all expressions create a (return) value and that return value is
|
||||
# overwritten by the next expression unless saved.
|
||||
# And since the message is the place to save it it needs to exist. qed
|
||||
class SendImplementation
|
||||
def run block
|
||||
block.codes.dup.each do |code|
|
||||
|
@ -4,18 +4,22 @@ module Virtual
|
||||
# Data in a Block is usefull in the same way data in objects is. Plocks being otherwise just code.
|
||||
#
|
||||
# But the concept is not quite straigtforwrd: If one thinks of a Plock embedded in a normal method,
|
||||
# the a data in the Plock would be static data. In OO terms this comes quite close to a Proc, if the data is the local
|
||||
# variables. Quite possibly they shall be used to implement procs, but that is not the direction now.
|
||||
# the a data in the Plock would be static data. In OO terms this comes quite close to a Proc,
|
||||
# if the data is the local variables.
|
||||
# Quite possibly they shall be used to implement procs, but that is not the direction now.
|
||||
#
|
||||
# For now we use Plocks behaind the scenes as it were. In the code that you never see, method invocation mainly.
|
||||
#
|
||||
# In terms of implementation the Plock is a Block with data (Not too much data, mainly a couple of references).
|
||||
# The block writes it's instructions as normal, but a jump is inserted as the last instruction. The jump is to the
|
||||
# next block, over the data that is inserted after the block code (and so before the next)
|
||||
# For now we use Plocks behaind the scenes as it were. In the code that you never see,
|
||||
# method invocation mainly.
|
||||
#
|
||||
# In terms of implementation the Plock is a Block with data
|
||||
# (Not too much data, mainly a couple of references).
|
||||
# The block writes it's instructions as normal, but a jump is inserted as the last instruction.
|
||||
# The jump is to the next block, over the data that is inserted after the block code
|
||||
# (and so before the next)
|
||||
#
|
||||
# It follows that Plocks should be linear blocks.
|
||||
class Plock < Block
|
||||
|
||||
|
||||
def initialize(name , method , next_block )
|
||||
super
|
||||
@data = []
|
||||
|
@ -2,7 +2,9 @@ require_relative "type"
|
||||
|
||||
module Positioned
|
||||
def position
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...500]}" if @position == nil
|
||||
if @position == nil
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...500]}"
|
||||
end
|
||||
@position
|
||||
end
|
||||
def set_position pos
|
||||
|
Loading…
Reference in New Issue
Block a user