This is part of a series
I started in March 2008 - you may want to go back and look at older parts if you're
new to this series.

I promised to get a second part out in December somewhere, and it seems like I only
barely made it. I'm still hoping to push two out in January, but that's contingent on
actually getting time to finish the next one too. But there'll be at least one in January,
sometimes towards the middle of the month. Anyway...

Stringing things together

With the new, though basic, Fixnum support, it makes sense to want to print the numbers
without having to resort to %s(). Let us add some basic output and conversion functionality.

First lets make compiler.feature actually run again. This turned out to be an embarrassing
case of me forgetting to check in two files. With 66d56ef you can run these tests again.

Even more embarrassingly that reveals a few regressions - a motivation for the next part,
where we will discuss testing og code generation in more detail, since that's definitively a
current weak spot.

We will see soon to what extent these regressions affect what comes next, though.

For now we will only support Kernel#puts and Kernel#print, and only the simplest of use
cases, though we will actually put them in Object, again due to lack of include support.

We will assume that the output field separator ($,) is always a linefeed, and we will not
do anything to explicitly support array arguments.

We add this test case to compiler.feature:

putsputs"Should be one line down due to linefeed above"print"Hello "puts42print"World\n"print"This","is",1,"test\n"puts"This","is","test",2

We hope to see at least part of this:

$ ruby features/inputs/06print.rb
Should be one line down due to linefeed above
Hello 42
World
Thisis1test
This
is
test
2

Instead we get a segmentation fault. There's a number of issues here:

There are missing methods

The primitive method_missing "handler" that we added some time ago has broken.

As you will see, even when we stub out the methods, there are regression with argument passing.

Stubbing out the basics

To begin with, we put a stub Fixnum#to_s with an error, and a String#to_s which simply returns
self in place, as well as #puts and #print versions in Object (rather than Kernel, as
noted above):

The "naked" puts will likely break horribly as the methods specify a single argument, with
no default value, and we don't trap that in any way.

Notice how the puts 42 line has resulted in an empty argument list (!)

We obviously will fail on the parameter lists with multiple values as the methods don't
use splats at all. However we really need to actually test for that...

Now, while the methods we added are obviously broken, some stuff ought to get executed anyway
(subject to buffering, some of it ought to make it to output too), so lets deal with puts 42
first, then add splats to the methods, try to get just puts to work, and then see if we get
through any of the above at that point.

Tracking down the 'puts 42' regression

Passing --notransform shows us the likely culprit is the rewrites in transform.rb"

We'll use that to add tests targetting specific the transforms as a whole, and then specifically
for rewrite_fixnumconst since that seems like a reasonable place to start.

But first, since running this code from gdb and doing a backtrace consistently shows crashes
related to our __send__ fallback or method_missing - neither which should get triggered
at this stage, lets change __send__ to make it more resilient to implementation
bugs for now (in 1e23ae0) and give better debug output:

Fixing this gets us the following output instead for the compiler.featureputs 42 test:

expected: "42\n"
got: "Fixnum#to_s is not implemented\n" (using ==)

... which is reasonable, given that we haven't implemented it yet.

Implementing to_s for Fixnum

Handling this in pure Ruby should be possible with what we have available, though probably not
very efficiently. And again in pure laziness, let's defer to the C libs snprintf. And leak
some memory in the process, but what's a little memory leak amongst friends who aren't freeing
anything at all until exit at the moment? ... (yeah, another fun subject we'll have to bite
the bullet on)

Handling variable arguments for puts an print

We now want to make puts (with no arguments) and various other variations work. I'll go through
tracking down these regressions in some detail debugging code generation is frankly often one
of the most annoying and time consuming parts of writing a compiler. And just maybe it'll teach
me to write more tests...

So lets start by adding one:

|inputs/01ctrivial.rb|outputs/01ctrivial.txt|Aputswithnoargument|

Where the input looks like this:

puts

and the output like this:

... and it fails (I've run it directly rather than bothering to let Cucumber run it
yet):

That's def puts str. First problem is that we're currently expecting an argument,
but we're not getting any. In reality the compiler should throw an ArgumentError when
the method is declared like this and called with too few arguments. But we've not gotten
that far. Anyway, first step is to change this to def puts *str. But we still need to
handle the difference in argument numbers and it currently won't be very elegant, as we
don't convert str to an actual Ruby array or anything, so we have to dip down to %s(numargs) etc.

We expect na to be 2 because of self + the optional __closure__ argument we pass
(but that is always present).

This fixes puts on its own, but breaks puts 42. The reason is the splat operator -
we can't any longer refer directly to str and expect to get an object - str now
refers to an address to an array of objects.

Spot the problem? We use %ebx to hold the number of arguments we calculate, but with our
new "proper" support for strings, we now call __get_string on the string argument, and
each one of them causes %ebx to get clobbered. As, incidentally does __get_string
itself, as we don't actually store this value anywhere. We have two choices: Load it
"closer" (That's generally a good idea anyway) or calculate it closer (for splat arguments).

This again underlines that we need proper register allocation including ability to "spill"
registers to the stack. This is complicated in that if we push and pop values all over the
place, we need to be able to adjust all accesses to %esp all over.

For now we "fake" spilling of %ebx by pushing %ebx indiscriminately onto the
stack, and adjusting the generation of the arguments accordingly "manualy" so that when
we pop it off the stack again everything happens to work. We end up with changing the
central parts of Compiler#compile_callm_args as follows (in 6901567)

@e.with_stack(args.length+1,true)doifsplat@e.addl(:ecx,:ebx)end# we're for now going to assume that %ebx is likely# to get clobbered later, in the case of a splat,# so we store it here until it's time to call the method.@e.pushl(:ebx)ret=compile_eval_arg(scope,ob)@e.save_to_stack(ret,1)args.each_with_indexdo|a,i|param=compile_eval_arg(scope,a)@e.save_to_stack(param,i+2)end# This is where the actual call gets done# This differs depending on whether it's a normal# method call or a closure call.# But first we pull the number of arguments off the stack.@e.popl(:ebx)yieldend

The main change here is the @e.pushl(:ebx) and @e.popl(:ebx) lines. But
notice that we change the offsets for self and method arguments accordingly.

This finally allows us to handle #puts and #print reasonably close to can see the result of this in 4511cea:

It's not pretty, and there's certainly a lot of room for improvement.
In particular "proper" access to the splat arguments would help tremendously,
so that's on the agendy for soon too, I think, after code generation testing
and some cleaner register allocation.

Some string interpolation

While we're at it, we might as well take a look at string interpolation
and see what kind of trouble we might run into.

While this is simple - it just allocates buffers and copies data into them - it isn't very elegant
code. So I might as well admit: Some of the simplicity is because of bugs that rears its head if
we make the expressions too advanced - likely due to memory protection issues.

But we will take care of that in our part on testing code generation, next time. For now, this
works.

I'm a Norwegian technologist who has been living in London since 2000. I have a son, Tristan, born in 2009.

I've got development and management experience from a number of startups as well as more established companies, after co-founding my first company at age 19. I currently split my time between being the Technical Director of Nudge CRM Ltd, and running my consulting business, primarily related to development and devops. (More...)