libjawt.so, java-gcjHEAD-compat, Big Merge

Here’s a screenshot of a JOGL demo running on Sun and libgcj:

JOGL Demo on Sun and libgcj

As you can see, it’s hard to tell which is which!

Big Merge

The screenshot represents several efforts coming together. First, the demo is running on natively-compiled GNU Classpath HEAD. Tom Tromey recently finished the Big Merge, making Classpath a sub-directory of gcc/libjava. To develop this demo, I replaced gcc/libjava/classpath with a checkout of Classpath HEAD and rebuilt libgcj. After patching a few places where divergences crept in I have a GCJ HEAD + Classpath HEAD development environment setup. Developing in this way is much more pleasant than the previous merge-every-patch approach; now I use the same source checkout for libgcj and JamVM+Classpath.

java-gcjHEAD-compat

The screenshot shows me running the java command in the exact same way in both windows, thanks to java-gcjHEAD-compat. It is an RPM spec file that allows you to create a first-class JPackage-style JVM alternative that points to a GCJ installation in a non-standard prefix. For example, I’ve installed GCJ HEAD in /home/fitzsim/install. java-gcjHEAD-compat points /usr/bin/java (through alternatives) to /home/fitzsim/install/bin/gij.

This is very convenient for developing libgcj as a JPackage JVM replacement since all JPackages assume the JVM is installed in /usr/lib/jvm. It’s also useful for building RPMs against libgcj HEAD, which I’ve done with JOGL in the screenshot. Now, JOGL is a heavy user of the AWT Native Interface which is implemented by a small library, libjawt.so. That brings me to my next topic.

libjawt.so Binary Compatibility

It has proved quite challenging to create a drop-in replacement for Sun’s libjawt.so. The jawt.h and jawt_md.h headers that Sun includes in the AWT Native Interface document are marked confidential and proprietary so I didn’t want to refer to them when creating an implementation for GNU Classpath. My initial libjawt.so attempt was not binary-compatible and so created problems for packages like JOGL that need to run on either libgcj or a proprietary JVM.

Using gdb and ptype I was able to determine the layout of JAWT types, so now libgcj’s libgcjawt.so is binary-compatible, except that its SONAME is versioned by libtool. But the SONAME of Sun’s libjawt.so is unversioned, simply “libjawt.so”, so even naming our library the same would not produce a binary-compatible result (e.g. “libjawt.so.6”) — and besides, doing so would cause confusion since libgcj’s “libjawt” is always present in the default library path, whereas Sun’s libjawt is not. So, what to do?

The last step in drop-in replaceability turned out to be a clever trick that Jakub Jelinek suggested:

libjawt.so:
	echo | $(GCJ_BIN_DIR)/gcc$(gcc_suffix) -shared -O2 -fpic -o libjawt.so -Wl,-soname,libjawt.so -xc - -lgcjawt

That’s a line from java-gcj-compat’s Makefile.am. It creates an empty library with the proper SONAME that links to the versioned libgcjawt.so. That’s it! java-gcj-compat’s libjawt.so gets installed in $JAVA_HOME/jre/lib/i386 just like Sun’s and apps needing libjawt.so to build or run don’t know the difference! I was relieved that this step was so simple — I was expecting linker script madness.

The proof that this all works is in the screenshot; I built Anthony Green‘s JOGL RPMs using java-gcjHEAD-compat and its libjawt.so and I’m running the JOGL demo on both Sun and java-gcjHEAD-compat. lsof shows the correct libjawt.so being loaded in each case:

$ /usr/sbin/lsof | grep libjawt.so
java      32435 fitzsim  mem       REG        3,2     2680     470785 /usr/lib/jvm/java-1.4.2-gcjHEAD-1.4.2.0/jre/lib/i386/libjawt.so
java      32460 fitzsim  mem       REG        3,2     3824     812009 /usr/java/jdk1.5.0_01/jre/lib/i386/libjawt.so

Now I want to see JOGL submitted to Fedora Extras and all this binary-compatibility work land in Fedora Core 5!