In the ever present quest for speed (when concerning Java and Gaming), I took the step of trying to get GCJ running.
This is really just a quick glance at GCJ as I only downloaded it late last night and have spent most of the morning double checking the results.
I am sure almost all are familiar with GCJ, and its significance, but for those that dont GCJ is part of the GNU GCC which stands for the GNU Compiler Collection (formally the GNU C Compiler) which can be found at http://gcc.gnu.org .
GNU is part of the Free Software Foundation or FSF (www.fsf.org) and so unlike SUN are interested in promoting freedom of information (NOT just free software as some people seem to think).
And as such represents, IMHO, one of the most significant development to happen to Java.
So what is GCJ (http://gcc.gnu.org/java/)?
GCJ is a portable, optimising, ahead-of-time compiler for the Java Programming Language. It can compile:
[] Java source code directly to native machine code,
[] Java source code to Java bytecode (class files),
[*] Java bytecode to native machine code.
*As taken from their web site.
Now before you get all excited I have to say that I did not try compiling to native code, that was not my (current) intention.
Neither did I successfully compile anything fully (!) but I got very close.
This all stems from the fact that I am utterly fed up with the Javac compiler, it’s slow and does not create impressive output in both file size and speed. Of course this is partly due to the wonderful design of the JVM (being a stack based processor) and that Javac no longer optimises the byte codes.
(Some speculation as to whether the optimisation was disabled for 3rd parties to sell their compilers or that the latest Hot Spot’s might just be doing pattern matches on byte codes and replacing with relevant machine code and so wont work on non-standard byte code combinations are both welcomed. Although unproven!)
It has gotten to the stage where the source files zipped is usually smaller than all the class files, so why not provide the source code and get much better optimisation from it?? Of course this is a discussion for another thread entirely.
It’s also interesting to note that old versions of Javac (like 1.1.6) can actually produce smaller class files. (As opposed to 1.3.0 which I regularly use.) Although this does lead to some compatibility problems.
Right then, to the point! (In an attempt to stop rambling!)
Rather than just throw a mickey mouse Applet at it, I though Id just got for broke and throw .:Redemption:. at GCJ (Version 3.2) some ~500K of source code to see what happened.
As already stated it fell over. But I then proceeded to see why.
First up : “malformed UTF-8 character.”
This seemed to be caused by my use of storing data in strings (thus using ASCII values > 127, quite a common practice dating back way before poking machine code into REM statements).
I tried using the “–encoding” switch to force a different type other than Unicode, such as Cp437 (stand DOS format), 8859-1 (ISO Latin-1) and even ASCII but to no avail it just would not take these strings.
I got round the problem by not using ASCII values > 127. It worked…
Next up was my audio system, I figured this would cause a problem and though it might not even compile. It was generating loads of missing class errors, but oddly they were all in the standard Javax.audio package.
So I assumed it could not find the classes, as I could see them, a typical CLASSPATH problem. After correcting the class path it still could not find them. Still this did not surprise me, as Java seems to have so many of these little quirks.
Instead I un-JARed the Runtime library that comes with 1.3 and made sure that the class path was now pointing to the correct location.
Still no joy, but I was sure it could see them, none of the other classes were causing trouble just the Javax.audio package.
So I decided to change the import lines to explicitly request the required classes rather than just .*
So:
import javax.sound.sampled.*;
became
import javax.sound.sampled.AudioSystem;
etc.
It worked, not one error compiling the audio system, much to my surprise!
Compiling the rest went relatively smoothly (just some unicode and audio problems, adding the current directory to the class path solved these). It then occurred to me why not try the Optimisation tags. –O2 worked, resulting in much smaller files and then –O3 worked making them a bit smaller again.
And so to execution, trying to get .:Redemption:. to boot was a no go, until I realised that I was not providing all the data files, typical mistake!
I.E. Hated it and would not even boot with a:
java.lang.InstantiationException:
at com/ms/applet/BrowserAppletFrame.newInstance
IBMs 1.3 & SUNs 1.3.1 JRE started but then fell over with:
java.lang.VerifyError: (class: ???, method: signature: (L_;Ljava/lang/ThreadGroup;Ljava/lang/String;)V) Expecting to find object/array on stack
Which is an unusual one, and looked like it was caused by my use of anonymous inner classes, as the threads seem to be defined in reverse order (not that should matter).
So copying over the threads generated by Javac was worth a try and sort of worked as .:Redemption:. would now boot get up to the point when it starts loading external data and crash with a:
java.lang.NoSuchMethodError
I then decided to copy the class that controls all the threads, that was compiled with Javac…
And BEHOLD! It worked!
.:Redemption:. was running under any browser or Applet viewer (although I have yet to try all).
But a quick dabble proved that all was not well, my audio system was not working correctly, the default setting was fine, try changing the interpolation and it would go all funny.
On investigating this code it turns out GCJ was creating the wrong set of byte codes (it does not matter whether optimisation is on or not) for a specific situation:
For example:
public class Test1
{
private int[] iBuffer = new int[ 100 ];
public Test1()
{
int iScale1 = 128;
int iScale2 = 64;
for( int c = 0; c < iBuffer.length; )
{
double dValue = Math.random();
iBuffer[ c++ ] += dValue * iScale1;
iBuffer[ c++ ] += dValue * iScale2;
}
}
};
Would produce the main loop as follows, (which is wrong):
for(int c = 0; c < iBuffer.length;)
{
double dValue = Math.random();
iBuffer[c++] = (int)((double)iBuffer[c++] + dValue * (double) iScale1);
iBuffer[c++] = (int)((double)iBuffer[c++] + dValue * (double) iScale2);
}
(Note this error only seems to appear when using doubles, using ints did not cause it to occur.)
This can be fixed by changing the code to as follows:
for(int c = 0; c < iBuffer.length;)
{
double dValue = Math.random();
iBuffer[c++] = (int)((double)iBuffer[c] + dValue * (double) iScale1);
iBuffer[c++] = (int)((double)iBuffer[c] + dValue * (double) iScale2);
}
No big deal, most obfuscation and optimisation program usually have similar quirks.
Which fixed it, although there is something else wrong with my audio system but I have not bothered tracking it down yet.
So at the moment I appear to have a ~stable version of .:Redemption:. mainly compiled with GCJ.
GCJ compiled the lot considerably quicker than Javac.
It would be nice if the gaming community started trying to fix/document some of these problems we are having/going to have so that GCJ can be fixed and fully working. As we stand to gain the most from it.
I would be very much interested in hearing anybodys elses experiences with GCJ.
Also if there is enough interest in this topic I will quite happy write a quick tutorial on how to get GCJ up and running on a Windows 98 system (I assume Unix guys will already know) for anybody willing to have a go. It is not that difficult either, which is a bonus.
Woz.
P.S.
As always sorry for the big post!