Thursday, May 14, 2009

BiteScript 0.0.2 Scripting Examples

I just released BiteScript 0.0.2, which mainly fixes some issues defining packages and non-public classes.

BiteScript is basically just a simple DSL for generating JVM bytecode. I use it in Duby and now in the "ruby2java" compiler we'll be using to turn Ruby classes into Java classes.

I've blogged about BiteScript here before, but I realized today I never posted any simple "hello world" examples. So here's a few of them, all using the command-line "scripting" mode.

First, the simplest version:

main do
ldc "Hello, world!"
aprintln
returnvoid
end

Obviously this is using a predefined "aprintln" macro, since there's no "aprintln" opcode on the JVM. Here's a longer version that shows how a macro would be defined, and accepts one argument
import java.lang.System
import java.io.PrintStream

macro :aprintln do
getstatic System, :out, PrintStream
swap
invokevirtual PrintStream, println, [Object]
end

macro :aprint do
getstatic System, :out, PrintStream
swap
invokevirtual PrintStream, print, [Object]
end

main do
ldc "Hello, "
aprint
aload 0
aaload 0
aprintln
returnvoid
end

And of course this is just Ruby code, so you can just use Ruby to alter the generation of code:
main do
5.times do
ldc "Wow!"
aprintln
end
returnvoid
end

These "BiteScripts" can all be either run with the "bite" command or compiled with the "bitec" command:
$ bite examples/using_ruby.bs 
Wow!
Wow!
Wow!
Wow!
Wow!

$ bitec examples/using_ruby.bs

$ javap -c examples/using_ruby
Compiled from "examples.using_ruby.bs"
public class examples.using_ruby extends java.lang.Object{
public static void main(java.lang.String[]);
Code:
0: ldc #9; //String Wow!
2: getstatic #15; //Field java/lang/System.out:Ljava/io/PrintStream;
5: swap
6: invokevirtual #21; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
9: ldc #9; //String Wow!
11: getstatic #15; //Field java/lang/System.out:Ljava/io/PrintStream;
14: swap
15: invokevirtual #21; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: ldc #9; //String Wow!
20: getstatic #15; //Field java/lang/System.out:Ljava/io/PrintStream;
23: swap
24: invokevirtual #21; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
27: ldc #9; //String Wow!
29: getstatic #15; //Field java/lang/System.out:Ljava/io/PrintStream;
32: swap
33: invokevirtual #21; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
36: ldc #9; //String Wow!
38: getstatic #15; //Field java/lang/System.out:Ljava/io/PrintStream;
41: swap
42: invokevirtual #21; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
45: return

}

This last example shows the resulting JVM bytecode as well.

Future plans for BiteScript include making it have better error detection (right now it just falls back on the JVM bytecode verifier, which is not the most descriptive thing in the world) and improving the API to more easily handle all the various combinations of class, field, and method modifiers. I'd also like to make it detect if you're doing bad things to the stack to save you the hassle of interpreting verification errors that may not happen until runtime.

Anyway, give it a try and feel free to contribute; the code is all Ruby, wrapping the ASM bytecode library, so anyone that knows Ruby can tweak it. The project page and wiki are hosted at Kenai.com: http://kenai.com/projects/jvmscript

And if you're not up on the JVM or JVM bytecodes, the JVM Specification is an easy-to-read complete reference for code targeting the JVM, and here is my favorite JVM opcode quickref.

Help JRuby by Fixing RubySpecs

A number of you have asked how you can help JRuby development. Well there's actually an easy way: fix RubySpec failures.

You may have noticed we periodically update our RubySpec "stable" revision number, and usually have to file a few bugs. This isn't because we don't want to fix those issues...on the contrary, we would love to fix them. We just don't have enough manpower, and there's usually harder issues we need to tackle first.

But most of the failures are easy to fix, and a lot of JRuby newcomers have gotten their feet wet fixing them. So here's a short guide on how to run the specs and fix them quickly.

1. Get a JRuby working copy and build it

This is as simple as 'git clone git://github.com/jruby/jruby.git', then 'cd jruby' and 'ant'. You'll need Apache Ant 1.7 and Java 5/1.5 or higher (grab "Java SE Development Kit" from the Java SE downloads page.

2. Run the CI spec run

We have a clean spec run that should be clean for you before you start. Just run "ant spec-short" and it will pull the mspec and rubyspec repositories, roll them to the stable versions, and run all known good specs. Now you're ready to investigate specific failures.

3. Run specific spec files with bugs reported

You can look in Jira under the "RubySpec" category, or look under spec/tags/ruby for "tag" files listing failing specs and Jira bug numbers. Once you find something you'd like to investigate, run that spec file using "bin/jruby spec/mspec/bin/mspec <path/to/spec/file>". For example to set the Range#initialize failures I just reported, run "bin/jruby spec/mspec/bin/mspec spec/ruby/core/range/initialize_spec.rb".

Now you can proceed to fixing it.

4. Identify where the problem is.

Most of the core classes are pretty easy to locate. Any classes in the "core" specs will have a Java class named Ruby, like RubyArray, RubyRange, and so on. They're generally located in src/org/jruby. If you know any Java, these files are pretty easy to follow, and we're standing by on IRC or on the mailing list to hold your hand at the start.

5. Create a patch and submit it to the bug

Once you have a working fix, you can go ahead create a patch, either with "git diff > somefile.patch" or by committing it to your local repository and using "git format-patch -1" to create a formatted patch for the topmost commit. Some git-fu may be necessary, so I usually just use "git diff".

That's all there is to it! You'll be a JRuby contributor in no time!

fork and exec on the JVM? JRuby to the Rescue!

Today David R. MacIver pinged me in #scala and asked "headius: Presumably you guys have spent quite a lot of time trying to make things like system("vim") work correctly in JRuby and failing? i.e. I'm probably wasting my time to attempt similar?"

My first answer was "yes", since there's no direct way to exec a program like vim (which wants a real terminal) and have it work on the JVM. The JVM's process launching gives the newly-spawned processes the child side of piped streams, which you then have to manually pump (which is what we do in JRuby's system, backtick, and exec methods). Under these circumstances, vim may start up, but it's certainly not functional.

But then I got to thinking...if you were doing this in C, you'd fork+exec and all would be happy. But we can't fork+exec on the JVM..OR CAN WE?

As you should know by now, JRuby ships with FFI, a library that allows you to bind any arbitrary C function in Ruby code. So getting fork+exec to work was a simple matter of writing a little Ruby code:

require 'ffi'

module Exec
extend FFI::Library

attach_function :my_exec, :execl, [:string, :string, :varargs], :int
attach_function :fork, [], :int
end

vim1 = '/usr/bin/vim'
vim2 = 'vim'
if Exec.fork == 0
Exec.my_exec vim1, vim2, :pointer, nil
end

Process.waitall

Running that with JRuby (I tried master, David tried 1.3.0RC1, and 1.2.0 works too) brings up a full-screen vim session, just like you'd expect, and it all just works. No other JVM language can do this so quickly and easily.

We'll probably try to generalize this into an optional library JRubyists can load (require 'jruby/real_exec' or similar) and perhaps add fork and exec to jna-posix so that the other JVM languages can have sweet, sweet process launching too.

JRuby rocks.

Update: The biggest problem with using fork+exec in this way is that you can't guarantee *nothing* happens between the fork call and the exec call. If, for example, the JVM decides to GC or move memory around, you can have a fatal crash at the JVM process level. Because of that, I don't recommend using fork + exec via FFI in JRuby, even though it's pretty cool.

However, since this post I've learned of the "posix_spawn" function available on most Unix variants. It's basically fork + exec in a single function, plus most of the typical security and IO tweaks you might do after forking and before execing. It's definitely my recommended alternative to fork+exec for JRuby, and to make that easier I've bundled it up as the "spoon" gem (gem install spoon) which provides spawn and spawnp to JRuby users directly. Here's an example session using Spoon to launch JRuby as a daemon. If you just need fork+exec on the JVM, posix_spawn or the Spoon gem are the best way to do it.