Rubinius is important


I predict that parts of this blog posts will make certain people uncomfortable, annoyed and possibly foamy mouthed. If you feel that you’re of this disposition, please don’t read any further.

As I’m working on JRuby, I obviously think that JRuby is the best solution for many problems I perceive in the MRI implementation currently. I have been quite careful to never say anything along the lines that JRuby is better than anything else, though. I will continue with that stance. However, I won’t restrict myself in the same way regarding Rubinius.

In fact, I’m getting more and more convinced that for the people that don’t need the things Java infrastructure can give you, Rubinius is the most important project around, in Ruby-land. More than that, Rubinius is MRI done right. If nothing substantial changes in the current timeline and plans for Ruby 1.9.1, I predict that Rubinius will be the CRuby implementation of choice within 6 months. Rubinius is an implementation done the way MRI should have been. Of course, Matz have always focused on the language, not the implementation. I’m very happy about that, since it means that we have an outstanding language.

But still. Rubinius will win over MRI and YARV. I’ve had this thought for a while, and I’m finally more or less convinced that it’s true. Of course, there are a few preconditions. The first and most important one is that Rubinius delivers 1.0 as planned, by the end of the year and that it doesn’t have abysmal performance. Or if YARV would happen to be totally finished and perfectly usable in the same time frame, things might take a different turn.

Why is Rubinius so good, compared to the existing C implementations? There are a number of good reasons for this:

  • It is byte code based. This means it’s easier to handle performance.
  • It has a pluggable, very clean architecture, meaning that for example garbage collection/object memory can be switched out to use another algorithm.
  • It is designed to be thread safe (though this is not really true yet), and Multi-VM capable.
  • It works with existing MRI extensions.
  • Most of the code is written in Ruby.
  • It gives you access to all the innards, directly from your Ruby code (stuff like MethodContexts/BlockContexts, etc).
  • The project uses Valgrind to ensure that the C code written is bullet proof.

Anyway. I put my money on Rubinius. Of course, that doesn’t mean I don’t think JRuby have a place to fill in the eco system. In fact, the real interesting question is what will happen when both Rubinius and JRuby have become more mature. I’d personally love to see more cooperation and sharing between the projects. Not a merging, since the goals are too separate, but it would be wonderful if JRuby could use the same Ruby code for all the primitive operations as Rubinius does.

Right now we have a simple Rubinius engine in JRuby, that can interpret and run some simpler byte codes from Rubinius.

JRuby and Rubinius are both extremely important. Right now I believe JRuby is more important, since it opens up a totally different market for Ruby, and gives the benefits of Java to Ruby. Rubinius has another place to fill.

Of course, being who I am, I have also looked into what would be required to port Rubinius to Java, using the same approach directly instead of going through JRuby. If you decide to use Java’s Garbage Collector, Java Threads, and reuse the JRuby parser you would end up with about 40 files of C code to port. Most of these are extremely easy, and none is really that hard. And what you would end up with is something that would run the same things Rubinius does, but with the possibility of invoking Java code at the same time. (Of course, I hope that Evan reserves a block of about 8-16 bytecodes that can be implementation dependent – these Jubinius would use to interop with Java code).



YARV tail call optimization in JRuby


After some further developments on the YARVMachine, I can report that JRuby trunk supports most of the optimized instructions that YARV makes use of. The big thing missing is the local cache optimization. But that is just an aside from the main course.

Because right now there is support in JRuby’s YARVMachine to handle a limited form of tail call optimization. Specifically, if the last call in a method is to the method we’re in and the receiver of that call is the same as the current self, tail call optimization will happen. That allows JRuby to run this script:

def fact(n, acc)
if n == 0
acc
else
fact n-1, n*acc
end
end

p fact(25000, 1)

That will explode in both YARV and MRI, long before getting anywhere, since the stack goes out. But this will work correctly in JRuby with -y, provided that the tail call optimization is actually turned on. To turn it on, use $JAVA_OPTS and set the variable jruby.tailcall.enabled to true, like this:

JAVA_OPTS="-Djruby.tailcall.enabled=true"
jruby -y testRecFact.rbc

Of course, you need latest trunk for this to work, and you need to compile the YARV file as described in my previous post. But provided you have done that, the above will actually work perfectly, without deepening the stack at all. The reason this is not turned on by default in the YARVMachine is that it isn’t completely safe yet. There is one small kink, which is that if you redefine the current method within the current method, and then call self, this will not work as expected. It should be easy to add checks for this behavior, though, so that will come soon.

Of course, the big problem with something like this is the stack trace. How to handle it? The implementation behavior would make it look like those 25000 calls to fact was just a single one. In the same manner, trace methods won’t get called.

But trunk will actually report when these tail calls have been done. Say you have a script like this:

def a(n)
if n > 20000
raise "hell"
end
a(n+1)
end

a(0)

(it is designed to explode at a certain depth, of course.) In current JRuby trunk, executed with -y, this will generate an output that looks like this:

testRaise.rb:-1:in `a' TAIL CALL(20001): hell (RuntimeError)
from testRaise.rb:-1

So it is quite obvious that the exception was raised in the 20001:th invocation of ‘a’.

This work goes forward very fast, and it’s funny to see progress happen this quickly. Yesterday SASADA Koichi visited the #jruby and #rubinius IRC channels, and we had some very interesting discussions on further collaboration. The future looks bright indeed, and this weekend I’ll probably have time enough to take a first crack at a YARV compiler for JRuby.



Executing YARV (in JRuby)


I guess the title says it all. Oh, it doesn’t? You want what? A tutorial on how to play with this? Well, alright then.

What you will need to get started is first and foremost JRuby trunk. Download and build that. You will also need either a very recent version of YARV or Ruby trunk. Download either and build that too. I have the source in $YARV_SRC and $RUBY_TRUNK_SRC. I’ve also installed them with suffixes, so I have ruby-yarv and ruby-trunk globally available. That’s a quite good setup.

Before compiling some Ruby, we need to change the change the compile-script slightly. So open up the file in $YARV_SRC/tool/compile.rb and disable the peephole_optimization and inline_const_cache, since JRuby doesn’t really support these features yet. If you use Ruby trunk, you also need to do a global search-and-replace in this file, from YARVCore to VM.

Now you’re finished to compile something. The goal for this execution have been to get an iterative fibonacci sequence running, so save this code in a file called fib.rb:

def fib_iter_ruby(n)
i = 0
j = 1
cur = 1
while cur <= n
k = i
i = j
j = k + j
cur = cur + 1
end
i
end

puts fib_iter_ruby(5000)

Next, compile this file:

ruby-yarv $YARV_SRC/tool/compile.rb -o fib.rbc fib.rb

Modify as needed if you use Ruby trunk instead. Now, how to run this? With the y-flag to JRuby:

jruby -y fib.rbc

As you can see, simple files like this just work. Of course, there are much missing yet, but basic interpretation works really nice. Some more additions to the YARV machine will come quite soon, probably. But the next step will be to implement a compiler for YARV too. From that point it should be possible to execute Ruby-files in JRuby in several different ways; by regular interpretation, YARV compilation, YARV interpretation, Java bytecode AOT compiling, Java bytecode JIT compiling and also possibly Rubinius interpretation. The future is, as always, interesting.