136 lines
6.3 KiB
Plaintext
136 lines
6.3 KiB
Plaintext
= render "pages/rubyx/menu"
|
||
|
||
%h1=title "RubyX architectural layers"
|
||
%p
|
||
To implement an object system to execute object oriented languages takes a large system.
|
||
The parts or abstraction layers are detailed below.
|
||
%p
|
||
It is important to understand the approach first though, as it differs from the normal
|
||
interpretation. The idea is to
|
||
%strong compile
|
||
ruby. The argument is often made that
|
||
typed languages are faster, but i don’t believe in that. I think dynamic languages
|
||
just push more functionality into the “virtual machine” and it is in fact only the
|
||
compilation to binaries that gives static languages their speed. This is the reason
|
||
to compile ruby.
|
||
%p.center.three_width
|
||
= image_tag "architecture.png" , alt: "Architectural layers"
|
||
|
||
%h3#ruby Ast + Ruby
|
||
%p
|
||
To compile and run ruby, we first need to parse ruby. While parsing ruby is quite
|
||
a difficult task, it has already been implemented in pure ruby
|
||
= succeed "." do
|
||
=ext_link "here" ,"https://github.com/whitequark/parser"
|
||
The output of the parser is
|
||
an ast, which holds information about the code in instances of a single
|
||
%em Node
|
||
class.
|
||
Nodes have a type attribute (which you sometimes see in s-expressions) and a list of children.
|
||
%p
|
||
The first layer in RubyX is the
|
||
%b Ruby Layer
|
||
that basically models the ast in a concrete syntax tree. This means there is one class
|
||
per node type.
|
||
%p
|
||
The Ruby layer is then used to transform the data into the Vool layer. Eg: Implicit
|
||
block passing is made explicit, conditions are simplified, arguments are simplified,
|
||
and syntactic sugar is removed.
|
||
|
||
%h3#virtual-object-oriented-language Virtual Object Oriented Language
|
||
%p
|
||
Virtual, in this context, means that there is no syntax for this language; it is an
|
||
intermediate representation which
|
||
%em could
|
||
be targeted by several real languages.
|
||
%p
|
||
The main purpose is to simplify existing oo languages down to it’s core components: mostly
|
||
calling, assignment, continuations and exceptions. Typed classes for each language
|
||
construct exist and are responsible to transform a statement into Mom level below.
|
||
%p
|
||
Examples for things that exist in ruby but are broken down in Vool are
|
||
%em unless
|
||
,
|
||
%em ternary operator
|
||
,
|
||
%em do while
|
||
or
|
||
%em for
|
||
loops and other similar syntactic sugar.
|
||
|
||
%h3#minimal-object-machine Minimal Object machine
|
||
%p
|
||
We compile Vool statements into Mom instructions. Mom is a machine, which means it has
|
||
instructions. But unlike a cpu (or the risc layer below) it does not have memory, only
|
||
objects.
|
||
It also has no registers, and together these two things mean that all information is
|
||
stored in objects. Also the calling convention is object based and uses Frame and
|
||
Message instances to save state.
|
||
%p
|
||
Objects are typed, and are in fact the same objects the language operates on. Just the
|
||
functionality is expressed through instructions. While source methods are defined on
|
||
classes as Vool, when they are compiled to binary, they are made type specific. These
|
||
TypedMethods hold the binary and are stored in the Type.
|
||
%p
|
||
The Mom level exists to make the transition to Risc easier. It has a very abstract,
|
||
high level instruction set, where each single instruction may resolve to many (even
|
||
tens of) lower level instructions. But it breaks down Vool's tree into an instruction
|
||
list, which is conceptually a much easier input for the next layer.
|
||
%p
|
||
Mom instruction may also be used to extend the systm functionality. This feature is used
|
||
in the Builtin layer of functions. These functions are coded at the Mom level, as they
|
||
can not be expressed in ruby. Examples include instance variable access, integer ops...
|
||
|
||
%h3#risc Risc
|
||
%p
|
||
The Register machine layer is a relatively close abstraction of risc hardware,
|
||
but without the quirks that for example arm has.
|
||
The Risc machine has registers, indexed addressing, operators, branches and everything
|
||
needed to implement Mom. It does not try to abstract every possible machine feature
|
||
(like llvm), but rather “objectifies” the general risc view to provide what is needed for
|
||
the Mom layer, the next layer up (and actually Builtin functions).
|
||
%p
|
||
The machine has it’s own (abstract) instruction set, and the mapping to arm is quite
|
||
straightforward. Since the instruction set is implemented as derived classes, additional
|
||
instructions may be defined and used later, as long as translation is provided for them too.
|
||
In other words the instruction set is extensible (unlike cpu instruction sets).
|
||
%p
|
||
Basic object oriented concepts are needed already at this level, to be able to generate
|
||
a whole self contained system. Ie what an object is, a class, a method etc.
|
||
This minimal runtime is called parfait, and the same objects will be used at runtime
|
||
and compile time.
|
||
%p
|
||
Since working with at this low machine level (essentially assembler) is not easy to
|
||
follow for everyone (me :-), an interpreter was created (by me:-). Later a graphical
|
||
interface, a kind of
|
||
%a{:href => "https://github.com/ruby-x/rubyx-debugger"} visual debugger
|
||
was added.
|
||
Visualizing the control flow and being able to see values updated immediately helped
|
||
tremendously in creating this layer. And the interpreter helps in testing, ie keeping it
|
||
working in the face of developer change.
|
||
|
||
%h3 Target assembler
|
||
%p
|
||
Risc is the last abstract layer, it is then translated into machine dependent code.
|
||
This is not binary yet, more an oo version of assembler, where each instruction
|
||
is represented by an object.
|
||
%p
|
||
Arm is a risc architecture, but anyone who knows it will attest, with it’s own quirks.
|
||
For example any instruction may be executed conditionally, ie
|
||
%em every
|
||
instruction carries bits to make it check the status register. Or the fact that there
|
||
is no 32bit register load instruction. It is possible to create very dense code using
|
||
all the arm special features, but this is not implemented yet.
|
||
%p
|
||
The Arm::Translator translates RegisterInstructions to ArmInstructions.
|
||
|
||
%h3#binary-and-elf Elf and Binary
|
||
%p
|
||
A physical machine will run binaries containing instructions that the cpu understands,
|
||
in a format the operating system understands (elf).
|
||
%p
|
||
The previously generated objects must be able to convert themselves to binary.
|
||
These binary codes are wrapped and stored into binary files of elf format.
|
||
Arm and elf subdirectories hold the code for these layers.
|
||
and the Elf::ObjectWriter creates Linux binaries.
|