A VM in Java with tail-calls and continuations

Thursday, August 11, 2005

Wow - it's been two weeks!

Indeed... But what have I been up to?

Well, if you'd kept up with CVS, you'd know that a week ago I was dealing with symbolic name resolution as per the JVM Spec.

I was kinda delayed by a bug in javac's compile-time field resolution. I kept testing my algorithm against javac's, and so I had to redo it to match the JVM's dynamic resolution method - which is the correct one, according to the specification. I filed a bug with Sun (no response yet) - but it's a corner case (not that important). I just wanted to get it right the first time.

That's not the main reason for this post, though. I've just committed a version of the code that finally does something - like visible stuff.

The interpreter now “loads” the necessary classes (that was the step following resolution), parsing the bytecode and building a structure suitable for the interpretation process.

I was going to go with an int[] and the traditional switch interpreter, but after implementing just “a couple” of opcodes I was rapidly approaching the maximum method size limit (besides having a huge source file, but that's manageable with code folding).

So I gave up, and I'm using an Insn[] - that's an “instructions” array. Insns are objects that represent an instruction and all its arguments, and which have an execute(...) virtual method.

The interpreter cycle is now just this (code which I believe explains itself):

while (this.frame != null) this.code[this.cp++].execute(this);

I've implemented a couple of opcodes already, quite enough for dear “Hello World!” - which was in fact the initial goal:
  • static and virtual, interpreted and native invocations;
  • static and instance field getting and setting;
  • local variable loading and storing
  • constant loading;
  • simple stack operations.

It's not much (no math yet, amongst other things) but as I said enough to test.

Want to give it a try? Easy steps then:
cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/jauvm login
cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/jauvm co -P jauvm
cd jauvm
ant dist
Notes: leave the password blank; you'll need a recent version of ant (mine is 1.6.2) and a 1.5 JDK (mine is 1.5.0_02); and, there will be some warnings about missing serialVersionUIDs.

Hopefully, and if all goes out as expected, you'll have 3 files in the dist folder:
  • jauvm.jar (java archive, no dependencies);
  • jauvm-all.jar (java archive, all dependencies included);
  • and, jauvm-src.tgz (source tarball).

You'll want the jauvm-all.jar. Add that to your classpath, compile and run the following example:


import net.sf.jauvm.Interpreter;
import net.sf.jauvm.interpretable;

public class Test implements Runnable {
public static void main(String[] args) {
new Interpreter(new Test()).run();

public @interpretable void run() {
System.out.println("Hello World!");

public @interpretable void itest() {

public void ntest() {

public static @interpretable void iprint(double d) {

public static void nprint(double d) {

The output should be something like:
Hello World!

I'll leave it up to you to figure out what's being interpreted and what's not.

Next steps will be: getting interface and special invocations working (including constructors and private methods - super invocations may be a problem). In the process I hope to implement tail-calls (which seem pretty easy) and a bunch more opcodes, and soften a few rough edges. That's all before getting into continuations.

This will now get exiting, at least for me!

No comments: