ruby-x.github.io/app/views/pages/rubyx/builder.html.haml
2018-08-20 09:42:22 +03:00

122 lines
5.2 KiB
Plaintext

= render "pages/rubyx/menu"
%h1=title "Generating assembler by dsl"
%p
In the end everything boils down to risc instruction generation. From there on down,
it is pretty much mechanics. And everything between ruby and the risc layer is to make
the step towards risc easier.
%p
Risc is after all very much like assembler, and programming any system in assembler is
challenging. The simple object model helps, and so does the simple risc instruction set,
but for a long time i was struggling to create readable code that generates the risc.
%p
The main places we find this code is the mom layer, eg in the
=link_to "calling convention" , "calling.html"
and in the Builtin module, generating code that can not be expressed in ruby.
%h2 Compiler
%p
When the code goes from ruby to vool, or vool to mom, methods on the previous (tree)
structure return the next structure. But going to risc the code evolved to use a different
approach. The unit of compilation is a block or method, and the respective
%b Compiler
is passed into the generating method.
%p
The risc code is then added directly to the compiler. If anything is returned it is
used by the caller for other things. This change happened as the compiler needed to
be passed in as it carries the scope, ie is needed for variable resolution.
%p
It is also easier to understand (albeit maybe after getting used to it), as the generation
of the structure after the return was not intuitive.
The Compiler's central method to capture the code, is
%b add_code,
and the argument must be a risc instruction.
%p
While this worked (and works) the resulting code is not very concise, cluttered with all
kind of namespace issues and details.
%h2 Builder
%p
From these difficulties was born the DSL approach that is implemented in the
%b Builder.
The Builder uses method_missing to resolve names to registers. Those registers, or more
precisely RegisterValues, are then used to generate risc instruction by overloading
operators.
%p
Most of the work at the risc level , probably 60% or more, is shuffling data around.
This is covered by the four instruction SlotToReg, RegToSlot, Transfer and LoadConstant
using the
%b <<
operator with different arguments. See below:
.container_full
.half_left
:coderay
#!ruby
def build_message_data( builder )
builder.build do
space? << Parfait.object_space
next_message? << space[:next_message]
next_message_reg! << next_message[:next_message]
space[:next_message] << next_message_reg
....
end
.half_right
%ul
%li Variable definitions with ? or ! . Variables must be defined before use.
%li space becomes a variable, or a named register
%li First line generates a constant_load, because the right side is a constant
%li second (and third) line generates a SlotToReg , because the left is a register and the right is a slot
%li fourth line generates a RegToSlot, as the left side is a slot, and the right a register
%li all generated instructions are automatically added to the compiler
%p
As you can see the code is quite readable. Mapping names to the registers makes them
feel like normal variables. (They are not off course, they are instances of RegisterValue
stores in a hash in Builder).
The overloading of [] , which just creates an intermediate RValue, makes the it look like
the result of the array access is transferred to the register. And that is exactly what is
happening. The next_message on the right is a register, and the array indexing access the
"register" is an object. The "index", also called next_message is an instance variable.
The builder magic looks up the index in the type (Message) and does an indexed
memory access, which is exactly what a SlotToReg is.
%p{style: "clear: both;"}
There are also other ways to use the Builder: One is by just using the instance
methods, ie any code can be added with add_code, also inside the block
.container_full
.half_left
:coderay
#!ruby
if_zero ok_label
...
branch while_start_label
add_code exit_label
.half_right
%ul
%li Here we use the if_zero method, that generates a IfZero instruction
%li The second line is a creates a Branch, much the same way
%li The last line adds a label, using the add_code
%p{style: "clear: both;"}
And in these last examples we see how operators can be called, or indeed any
generating function can be called
.container_full
.half_left
:coderay
#!ruby
builder.build do
integer_const! << 1
integer_tmp.op :>> , integer_const
...
Risc::Builtin::Object.emit_syscall( builder , :exit )
...
.half_right
%ul
%li The first line load a fixnum, which is LoadData in risc terms
%li
The second line calls an operator >>, which gets translated to an
OperatorInstruction, that leaves the result in the second argument
%li
The last line invokes some shared code that does an system exit.
This is in essence much like inlining the exit code.