Monday, March 10, 2008

All day Sunday I had the stupids. It was probably related to a few beers on Saturday night, or the couple glasses of red wine before that. Whatever it was, I didn't trust myself to work on any JRuby bugs for fear of introducing problems if my brain clicked off mid-stream. So I started playing around with my old bytecode generating DSL from a few months back. Then things got interesting.

We've long wanted to have a "Ruby-like" language, probably a subset of Ruby syntax, that we could compile to solid, fast, idiomatic JVM bytecode. Not a compiler for Ruby, with all the bells and whistles that make Ruby both difficult to support and inefficient to use for implementing itself. A real subset language that produces clean, tight JVM bytecode on par with what you'd get from compiled Java. But better, because it still looks and feels mostly like Ruby.

This is normal ruby code. Given a Fixnum input, it calculates the appropriate fibbonaci number and returns it. It's slow in Ruby for a few reasons:

In JRuby, it uses a boxed integer value. Matz's Ruby and Rubinius use tagged integers to improve performance, and we rely on the JVM to optimize as much as it can (which turns out to be a *lot*). But it's still way slower than using primitives directly.

The comparison operations, integer math operations, and fib operations are all dynamic invocations. So there's at least a bit of method lookup cost, and then a bunch of abstraction cost. You can reduce it, but you can't eliminate it.

There are many Ruby features that influence performance negatively even when you're not using them. It's very difficult, for example, to optimally store local variables when the local scope can be captured at any time. So either you rely on tricks, or you store local variables on the heap and deal with them being slow.

When working with a statically-typed language, you can eliminate all of this. In Java, for example, you have both object types and primitive types; primitive operations are extremely fast and eventually JIT down to machine-code equivalents; and the feature set is suitably narrow to allow current JVMs to do amazing levels of optimization.

But of course Java has its problems too. For one, it does very little guessing or "inferring" of types for you, which means you generally have to declare them all over the place. On local variables, on parameters, on return types. C# 3.0 aims to correct this by adding type inference all over, but then there's still other syntactic hassle using any C-like language: curly braces, semicolons, and other gratuitous syntax that make up a lot of visual noise.

Wouldn't be nice if we could take the code above, add some type inference logic, and turn it into a fast, static-typed language?

And there it is! This is the same code as before, but now it's been decorated with a little type declaration block (in the form of a Ruby hash/map) immediately preceding the body of the method. The type decl describes that the 'n' argument is to be mapped to a primitive int, and the method itself will return a primitive int (and yes, I know those could be inferred too...it shall be soon). The rest of the method just works like you'd expect, except that it's all primitive operations, chosen based on the inferred types. For the bold, here's the javap disassembly output from the compiler:

Notice here that the + operation was detected as acting on two strings, so it was compiled to call String#concat rather than try to do a numeric operation. These sorts of mappings are simple to add, and since there's type information everywhere it's also easy to come up with cool ways to map Ruby syntax to Java types.

My working name for this is going to be Duby, pronounced "Doobie", after Duke plus Ruby. Duby! It has a nice ring to it. It may be subject to change, but that's what we'll go with for the moment.

Currently, Duby supports only the features you see here. It's a very limited subset of Ruby right now, and the subset doesn't support all Java primitive types, for example, so there's a lot of blanks to be filled. It also doesn't support static (class) methods, doesn't wire up "initialize" methods, doesn't support packages (for namespacing) or imports (to shrink type names) or superclasses or interfaces or enums or generics or what have you. But it's already functional for simple code, as you see here, so I think it's a great start for 10 hours of work. The rest will come, as needed and as time is available.

What are my plans for it? Well many of you may know Rubinius, an effort to reimplement Ruby in Ruby, modeled after the design of many Smalltalk VMs. Well, in order to make JRuby more approachable to Ruby developers, Duby seems like the best way to go. They'll be able to write mostly idiomatic Ruby code and know it will both perform at Java speeds and provide compile-type checking that all is wired up correctly. So we can avoid the overhead of "turtles all the way down" by just teaching one turtle how to speak JVM bytecode and building on that.

I also hope this will lead to lots of new ideas about how to implement languages for the JVM. It's certainly attractive to language users to be able to contribute to language implementations using the language in question, and if we can come up with compilers and sub-languages like Duby it could make the JVM more approachable to a wide range of developers.

Oh, and for those of you curious: the Duby compiler is written mostly in Ruby too.

@martin I've been looking into exactly that sort of thing, trying to decide on a minimally-invasive way to allow people to mark up code for performance. I'm interested to see what the reaction is to Duby to see if others are open to this kind of language-bending. I also want to get imports working (after some sleep) so that declarations can just be like {:a => int, :b => String}. There's a lot more to do, but I think this could be very powerful.

@sgwong To be clear, this is definitely not running as normal Ruby after it's been compiled. By then it's been turned into fairly standard JVM bytecodes, without dynamic invocation and many other Ruby features. But these techniques could be easily added to the main JRuby compiler to allow you to specify the runtime characteristics of a given method, or to allow directly embedding a Duby method into your Ruby code. There's a world of possibilities.

@patrick It will eventually look exactly like that, once I support "import" for types. You would then import java.lang.String or java.lang.Integer::TYPE and use them directly as "String" and "int" in type declarations. And of course those common types would probably be auto-imported for you automatically.

I guess that type declarations may be needed for local variables, instance variables and class variables, as well as for method parameters and result types. At first sight the use of colons in declarations doesn't clash with their use elsewhere in Ruby syntax.

Like Patrick, I'd prefer the typing to be in terms of Ruby's types, rather than Java's, so that the same optimisation could be used in other implementations.

@scot I think I've heard just about every permutation of liquor rhyme possible, and you know what? They're all right, because if you're drinking enough to worry about one after another, you're already in trouble.

@justinforder That would certainly be nice, but not directly possible in current Ruby grammars. We could certainly put our heads together and come up with a grammar modification that looks more pleasing, but I don't know how well that would be received by the larger Ruby community. Perhaps it wouldn't be so bad?

@jherber That's another one of the options, for sure. The advantage is that it truly is a no-op, and would have no effect on performance when running as normal Ruby. On the other hand, it would require additional parsing smarts. It's still on the table though.

@johnathan It's already in JRuby trunk, though it won't be supported or used for anything in the 1.1 release. Look under lib/ruby/site_ruby/1.8/compiler.

@chris Are you sure it isn't type inference? Notice in the second example no type is ever declared for the 'b' variable, but because it's assigned a string that's the type that gets used. I should come up with a more complex example that allows a variable's type to be determined from a method it calls, but that also works right now. If this isn't type inference, can you point me toward a better definition?

Generally, type inference means that the types are fully omitted from a program. The types are then "reconstructed" by an algorithm. Most of these algorithms are based on Algorithm W by Damas/Milner. What you do is often also referred to as type-checking, and indeed, you only need the types of the function arguments to be able to type-check a full program.

Things are going to get more interesting when you are going to add things like mixins, and there are some other catches.

That being said, I have been thinking about doing something similar, and I think it's really cool that there's actually somebody who's doing this. Did you implement all this in Ruby?

@laurence Me too...but I don't plan to shelve it away, and I'm already devising ways to use it for certain pieces of JRuby internals.

@chris Ok, so would you say then that Scala is not a type-inferred language, since it requires you to specify argument types? And to answer your question, the main compiler logic is all in Ruby, though it uses our Java integration layer heavily to reflectively inspect Java types. The bytecode layer is a Ruby wrapper around ASM.

@superfast Hey, thanks for those links, they both look interesting. We're probably going to start looking into profile-based JIT after 1.1, since we have mixed-mode execution already. So these will be very useful to read. Thank you!

I guess you're after something that looks more like Ruby. By the way, this algo isn't tail recursive, so larger numbers passed in could cause other performance problems. A little searching on the web and I found this tail-recursive algorithm:

Sorry to crash your party with Scala code. I'm sure you're looking for a more Ruby-like syntax, but when you started talking about wanting a language with concise expression, type inference, and optimized bytecodes for the JVM, it rang a bell for me.

Someone knows of any "backend" of Ruby or subset of ruby (a general computing library) that uses GPU?

GPUs dont have the control unit needed for general Ruby usage but they have a whole bunch of useful instructions and of course a huge amount or ALU to become a very good backend for some Ruby operations.

If I am interested in developing something like that, the only option would be write a C library and make a ruby wrap to it?

Simply because Ruby's integer numbers go from Fixnum to Bignum when necessary - but java.lang.Integer implies the Java 32 bit signed integer, which will go titsup once it gets to big. So I'd call the 2nd fib implementation to be buggy... why in the world should one fib invocation fail, just because it's result happens to be greater than 31 bit?

@murphee Duby doesn't (currently) support arithmetic overflow into arbitrary-precision because the JVM doesn't support it. Duby is being designed to put a Ruby face on what works well on the JVM. The primary motivator is to give a static-typed lookalike for Ruby we can use to implement static signatures on compiled Ruby classes and to implement JRuby internals without all the Java fuss. Anything outside that...I'm open to contributions.

I am not suggesting compulsory explicit typing, but allowing for the programmer to declare the types can help a lot in the debugging phase and also for some automated tools like IDEs, code generators, etc.

It would be pretty funny if you did it as comments, and then rdoc went "oh look, some people are putting type information in the comments, let's generate docs from it", and then IDE developers went "oh look, some people are putting type information in the comments, let's make autocomplete work much better".