2008-03-31

Generating JVM bytecode 2

Constant pools
If you're compiling a language other than Java, you may find the fact that the class file constant pool can only contain JVM primitive types or instances of java.lang.String somewhat frustrating. I wanted a constant pool for my boxed integers and boxed reals.

Disappointingly, the "getstatic", "bipush", "aaload" to load a boxed real constant from my private static final Object[] home-made "constant pool" seems to perform about the same as the "ldc" and "invokestatic" for boxing a JVM double (using something like java.lang.Double).

If you've got a more expensive constructor, though, it's a win. BigInteger constants benefit, and I imagine Pattern constants would too, if you have regular expression literals in your language. I'm not sure why none of the bytecode generation libraries seem to have any support for this. Surely it's a common enough need? Mine is only 100 lines of code including comments, but that still seems a bit weak.

Strange performance problem of the week
I finally tracked down a huge slowdown that had been dogging me for ages now. My most modest test case (not Java) which finally produced little enough bytecode that I could spot the problem by inspection looked like this:

X1 := 0.1;
for (p := 1.0; p > 0.003; p *= 0.98) {
X2 := 0.1;
for (y := -1.2; y < 1.2; y += 0.07) {
for (x := -1.6; x < 1.0; x += 0.03) {
for (c := 6.0; c < 20.0; ++c) {
tmp := X2;
}
}
}
}

Running this on Java 6 took about 2s on my machine. Commenting out the definition of X1 (which you'll note is unused) took the run time down to 0.2s. (The types are all effectively java.lang.Double, but that's being inferred by the compiler in this example.)

Running with -Xint increased the run time, but the presence or absence of X1 no longer made a difference.

So why was the unused variable making my program 10x slower? It turned out to be a bug in my code generator. There was no "pop" to get rid of the value of X1 after computing it for its initialization. Leaving that on the operand stack somehow combines with a "sufficiently complicated" method (changing those nested loops to just be one loop that counts to 30,000,000, for example, makes the problem disappear) to upset Hotspot.

I don't really have any particularly useful advice here. I don't know of a tool that spots such mistakes, and I don't know the exact conditions to reproduce the problem (and, since it seems to involve leaving junk on the operand stack, I can't provide a Java example). But if you're having weird performance problems with your generated code, I guess this is one extra avenue to explore.

In my case, Hotspot doesn't appear to compile the method at all. You can see this with the output of +XX:PrintOptoAssembly on a fastdebug build.

Annoyingly vague JVM output of the week
Why is ArrayStoreException so vague? It needs the same battering with the clue stick as those annoying human bug reporters who give you no more to go on than "something's gone wrong". Tell me what type you expected, and what type you saw, damn it! Sun's JVM tells you one of those things, but doesn't make it clear which (and the JavaDoc for the exception doesn't help; I ended up looking at the Hotspot source, which really shouldn't be necessary). jamvm(1) was even less helpful, with no detail message at all. I've sent a patch to the author.

My mistake was the usual gotcha: "anewarray" does what it says on the tin, and creates a new array of the reference type you give it. That is, you don't give it an array type if you want a single-dimensional array. Adding an example to that instruction's section in the JVMS would go a long way.

Building jamvm(1) on Ubuntu
To hack better diagnostics into jamvm(1), I needed to build from source. It took me a while to work out that to get a working VM, you need to configure with --with-classpath-install-dir=/usr (and not /usr/share/classpath). It's clear if you look at the source, but not very clear from the documentation.