polish docs
and a bit of code style
This commit is contained in:
parent
d6a2ea4cfc
commit
f7aac1d1a4
@ -2,25 +2,26 @@ require_relative "attributed"
|
||||
|
||||
module Arm
|
||||
|
||||
# A Machines main responsibility in the named_listwork is to instantiate Instructions
|
||||
# A Machines main responsibility is to instantiate Instructions.
|
||||
# Arm instructions live in their own directory and are derived from their Risc
|
||||
# couterparts to inherit list functionality
|
||||
|
||||
# Value functions are mapped to machines by concatenating the values class name + the methd name
|
||||
# Example: IntegerValue.plus( value ) -> Machine.signed_plus (value )
|
||||
|
||||
# Also, shortcuts are created to easily instantiate Instruction objects.
|
||||
# Example: pop -> StackInstruction.new( {:opcode => :pop}.merge(options) )
|
||||
# Shortcuts are created to easily instantiate Instruction objects.
|
||||
# Example: ArmMachine.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
|
||||
|
||||
class ArmMachine
|
||||
|
||||
# conditions specify all the possibilities for branches. Branches are b + condition
|
||||
# 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]
|
||||
|
||||
# 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
|
||||
# here we create the shortcuts for the "standard" arm instructions that we use.
|
||||
# (note that it is possible to add instructions by adding new classes and optionally
|
||||
# new factory functions to this class)
|
||||
def self.init
|
||||
[:push, :pop].each do |inst|
|
||||
define_instruction_one(inst , StackInstruction)
|
||||
@ -40,7 +41,8 @@ module Arm
|
||||
[:b, :call , :swi].each do |inst|
|
||||
define_instruction_one(inst , CallInstruction)
|
||||
end
|
||||
# create all possible brach instructions, but the CallInstruction demangles the
|
||||
|
||||
# create all possible branch instructions, but the CallInstruction demangles the
|
||||
# code, and has opcode set to :b and :condition_code set to the condition
|
||||
CONDITIONS.each do |suffix|
|
||||
define_instruction_one("b#{suffix}".to_sym , CallInstruction)
|
||||
@ -64,13 +66,10 @@ module Arm
|
||||
|
||||
#defining the instruction (opcode, symbol) as an given class.
|
||||
# the class is a Risc::Instruction derived base class and to create machine specific function
|
||||
# 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 Risc::MoveInstruction
|
||||
# for an Arm machine, a class Arm::MoveInstruction < Risc::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
|
||||
# Example, a mov() opcode instantiates a Arm::MoveInstruction < Risc::MoveInstruction ,
|
||||
#
|
||||
def self.define_instruction_one(inst , clazz , defaults = {} )
|
||||
clazz = class_for(clazz)
|
||||
def_method(inst) do |first , options = nil|
|
||||
|
@ -1,7 +1,7 @@
|
||||
module Arm
|
||||
class MachineCode
|
||||
|
||||
def function_call into , call
|
||||
def function_call( into , call )
|
||||
raise "Not CallSite #{call.inspect}" unless call.is_a? Risc::CallSite
|
||||
raise "Not linked #{call.inspect}" unless call.function
|
||||
into.add_code call( call.function )
|
||||
@ -9,28 +9,28 @@ module Arm
|
||||
call.function.return_type
|
||||
end
|
||||
|
||||
def main_start context
|
||||
def main_start( context )
|
||||
entry = Risc::Block.new("main_entry",nil,nil)
|
||||
entry.add_code mov( :fp , 0 )
|
||||
entry.add_code call( context.function )
|
||||
entry
|
||||
end
|
||||
def main_exit context
|
||||
def main_exit( context )
|
||||
exit = Risc::Block.new("main_exit",nil,nil)
|
||||
syscall(exit , 1)
|
||||
exit
|
||||
end
|
||||
def function_entry block, f_name
|
||||
def function_entry( block, f_name )
|
||||
block.add_code push( [:lr] )
|
||||
block
|
||||
end
|
||||
def function_exit entry , f_name
|
||||
def function_exit( entry , f_name )
|
||||
entry.add_code pop( [:pc] )
|
||||
entry
|
||||
end
|
||||
|
||||
# assumes string in standard receiver reg (r2) and moves them down for the syscall
|
||||
def write_stdout function #, string
|
||||
def write_stdout( function ) #, string
|
||||
# TODO save and restore r0
|
||||
function.mov( :r0 , 1 ) # 1 == stdout
|
||||
function.mov( :r1 , receiver_register )
|
||||
@ -39,7 +39,7 @@ module Arm
|
||||
end
|
||||
|
||||
# stop, do not return
|
||||
def exit function #, string
|
||||
def exit( function )#, string
|
||||
syscall( function.insertion_point , 1 ) # 1 == exit
|
||||
end
|
||||
|
||||
@ -47,7 +47,7 @@ module Arm
|
||||
# the number (a Risc::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,
|
||||
def div10 function, number , remainder
|
||||
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
|
||||
# remainder smaller than an int. i'll post some links sometime. This is from the arm manual
|
||||
@ -66,7 +66,7 @@ module Arm
|
||||
end
|
||||
end
|
||||
|
||||
def syscall block , num
|
||||
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
|
||||
sys = Risc::Integer.new( Risc::RiscValue.new(SYSCALL_REG) )
|
||||
|
@ -5,20 +5,20 @@ module Arm
|
||||
# for each instruction we call the translator with translate_XXX
|
||||
# with XXX being the class name.
|
||||
# the result is replaced in the stream
|
||||
def translate instruction
|
||||
def translate( instruction )
|
||||
class_name = instruction.class.name.split("::").last
|
||||
self.send( "translate_#{class_name}".to_sym , instruction)
|
||||
end
|
||||
|
||||
# don't replace labels
|
||||
def translate_Label code
|
||||
def translate_Label( code )
|
||||
nil
|
||||
end
|
||||
|
||||
# arm indexes are
|
||||
# in bytes, so *4
|
||||
# if an instruction is passed in we get the index with index function
|
||||
def arm_index index
|
||||
def arm_index( index )
|
||||
index = index.index if index.is_a?(Risc::Instruction)
|
||||
raise "index error 0" if index == 0
|
||||
index * 4
|
||||
|
@ -4,7 +4,7 @@ module Common
|
||||
# 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
|
||||
def set_next( nekst )
|
||||
raise "Next already set #{@next}" if @next
|
||||
@next = nekst
|
||||
nekst
|
||||
@ -12,7 +12,7 @@ module Common
|
||||
alias :<< :set_next
|
||||
|
||||
# during translation we replace one by one
|
||||
def replace_next nekst
|
||||
def replace_next( nekst )
|
||||
old = @next
|
||||
@next = nekst
|
||||
@next.append old.next if old
|
||||
@ -27,7 +27,7 @@ module Common
|
||||
# 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
|
||||
def insert( instruction )
|
||||
instruction.set_next @next
|
||||
@next = instruction
|
||||
end
|
||||
@ -41,11 +41,11 @@ module Common
|
||||
|
||||
# set next for the last (see last)
|
||||
# so append the given code to the linked list at the end
|
||||
def append code
|
||||
def append( code )
|
||||
last.set_next code
|
||||
end
|
||||
|
||||
def length labels = []
|
||||
def length( labels = [] )
|
||||
ret = 1
|
||||
ret += self.next.length( labels ) if self.next
|
||||
ret
|
||||
|
@ -24,6 +24,9 @@ module Common
|
||||
def [](i)
|
||||
@statements[i]
|
||||
end
|
||||
def <<(o)
|
||||
@statements << o
|
||||
end
|
||||
def collect(arr)
|
||||
@statements.each { |s| s.collect(arr) }
|
||||
super
|
||||
|
@ -1,18 +1,18 @@
|
||||
Minimal elf support
|
||||
===================
|
||||
# Minimal elf support
|
||||
|
||||
This is really minimal and works only for our current use case
|
||||
|
||||
- no external functions (all syscalls)
|
||||
- only position independant code (no relocation)
|
||||
- only position independent code (no relocation)
|
||||
- embedded data (into text), no data section
|
||||
|
||||
I was close to going the wilson way, ie assmble, load into memory and jump
|
||||
|
||||
But it is nice to produce executables. Also easier to test, what with segfaults and such.
|
||||
|
||||
Executable files are not supported (yet?), but object files work. So the only thing that remains is to
|
||||
call the linker on the produced object file. The resulting file is an executable that actually works!!
|
||||
Executable files are not supported (yet?), but object files work. So the only thing that
|
||||
remains is to call the linker on the produced object file. The resulting file is an
|
||||
executable that actually works!!
|
||||
|
||||
Thanks to Mikko for starting this arm/elf project in the first place: https://github.com/cyndis/as
|
||||
|
||||
|
@ -18,10 +18,6 @@ module Mom
|
||||
flat
|
||||
end
|
||||
|
||||
def <<(o)
|
||||
@statements << o
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,4 +1,4 @@
|
||||
### Parfait: a thin layer
|
||||
# Parfait: a thin layer
|
||||
|
||||
Parfait is the run-time of the object system.
|
||||
To be more precise, it is that part of the run-time needed to boot.
|
||||
@ -6,8 +6,8 @@ To be more precise, it is that part of the run-time needed to boot.
|
||||
The run-time needs to contain quite a lot of functionality for a dynamic system.
|
||||
And a large part of that functionality must actually be used at compile time too.
|
||||
|
||||
We reuse the Parfait code at compile-time, to create the data for the compiled vm.
|
||||
To do this the vm (re) defines the object memory (in parfait_adapter).
|
||||
We reuse the Parfait code at compile-time, to create the data for the compiled code.
|
||||
To do this the compiler (re) defines the object memory (in parfait_adapter).
|
||||
|
||||
A work in progress that started from here : http://ruby-x.org/2014/06/10/more-clarity.html
|
||||
went on here http://ruby-x.org/2014/07/05/layers-vs-passes.html
|
||||
@ -22,10 +22,10 @@ It's too simple: just slips off the mind like a fish into water.
|
||||
Parfait has a brother, the Builtin module. Builtin contains everything that can not be coded in
|
||||
ruby, but we still need (things like List access).
|
||||
|
||||
### Vm vs language- core
|
||||
## Vm vs language- core
|
||||
|
||||
Parfait is not the language core library. Core library functionality differs between
|
||||
languages and so the language core lib must be on top of the vm parfait.
|
||||
languages and so the language core lib must be on top of parfait.
|
||||
|
||||
To make this point clear, i have started using different names for the core classes. Hopefully
|
||||
more sensible ones, ie List instead of Array, Dictionary instead of Hash.
|
||||
|
@ -1,5 +1,4 @@
|
||||
Risc Machine
|
||||
================
|
||||
# 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
|
||||
@ -17,31 +16,29 @@ 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
|
||||
------------------
|
||||
## Calls and syscalls
|
||||
|
||||
The RiscMachine only uses 1 fixed register, the currently worked on Message.
|
||||
The RiscMachine only uses 1 fixed register, the currently worked on Message. (and assumes a
|
||||
program counter and flags, neither of which are directly manipulated)
|
||||
|
||||
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.
|
||||
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
|
||||
===========
|
||||
## Interpreter
|
||||
|
||||
There is an interpreter that can interpret compiled register machine programs.
|
||||
This is very handy for debugging (an nothing else).
|
||||
This is very handy for debugging (and nothing else).
|
||||
|
||||
Even more handy is the graphical interface for the interpreter, which is in it's own repository:
|
||||
rubyx-debugger.
|
||||
|
||||
Arm / Elf
|
||||
=========
|
||||
## Arm / Elf
|
||||
|
||||
There is also a (very strightforward) transformation to arm instructions.
|
||||
There is also a (very straightforward) 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
|
||||
|
@ -1,18 +1,19 @@
|
||||
### Builtin module
|
||||
## 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! ,
|
||||
The functions are organised by their respective classes 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.
|
||||
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.
|
||||
The Builtin module is scattered into several files, but that is just so the file
|
||||
doesn't get too long.
|
||||
|
@ -5,7 +5,7 @@ module Risc
|
||||
# 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.
|
||||
# From code, the next step down is Vool, then Mom (in two steps)
|
||||
#
|
||||
# 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.
|
||||
|
@ -5,7 +5,7 @@ Virtual Object Oriented Language
|
||||
|
||||
in other words, ruby without the fluff.
|
||||
|
||||
Possibly later other languages can compile to this level, eg by running in the same jvm.
|
||||
Possibly later other languages can compile to this level and use rx-file as code definition.
|
||||
|
||||
## Syntax tree
|
||||
|
||||
@ -13,7 +13,7 @@ Vool is the layer of concrete syntax tree. The Parser gem is used to parse ruby.
|
||||
an abstract syntax tree which is then transformed.
|
||||
|
||||
The next layer down is the Mom, Minimal object Machine, which uses an instruction tree.
|
||||
That is on the way down we create instructions, but stay in tree format. Only the next step
|
||||
That is on the way down we create instructions, but stays in tree format. Only the next step
|
||||
down to the Risc layer moves to an instruction stream.
|
||||
|
||||
The nodes of the syntax tree are all the things one would expect from a language, if statements
|
||||
|
@ -12,9 +12,6 @@ module Vool
|
||||
@statements.each{ |s| s.create_objects }
|
||||
end
|
||||
|
||||
def <<(o)
|
||||
@statements << o
|
||||
end
|
||||
end
|
||||
|
||||
class ScopeStatement < Statements
|
||||
|
Loading…
Reference in New Issue
Block a user