Nik Shaylor
Version 0.02
8-May-97
A beta level evaluation copy of the program may be downloaded from the Internet, that will convert command line applications such as Sun's javac on WIN32 systems. JCC may produce code that is compatible with other platforms, but this has not been tried, and a certain amount of porting of the run time library would be necessary.
The main changes since the last version are:
The idea behind JCC is to convert Java source code into C in such a way that a good optimising C compiler will be able to build an efficient program. To this end the translated program looks like a normal C program which means the typical optimisation techniques used in most modern C compilers are effective.
Java software built with JCC is efficient for the following reasons:
The conversion process is started by specifying just one class name that must contain the normal static main() method. This method is translated first and from this any other classes that are needed are marked and subsequently translated, which will lead to further classes being translated etc. Only the methods and fields actually used in a program are produced making the resulting source code as small as possible.
The resulting set of .c and .h files are then all compiled together in one go, and linked to the C runtime library in the normal way.
JCC used a couple of the "sun.*" classes so these must also be accessible from the class path.
java nshaylor.jcc.Main [switches] qualified.class.Name
Switches:
-v Verbose mode
-e Copy output messages to System.err
-s Output class summary table
-p Don't cast parameters in method definitions.
This produces lots of compiler error messages
but makes debugging the C code easier.
-t Generate threaded code
-sourcepath Specify the source path
The main parameter is the name of a class that contains the main() method. This class must be in the sourcepath which is the logical equivalent of the classpath except this is where the Java source for the program is to be found. If the -sourcepath switch is not specified the current classpath is used. For any normal programs the source of the java.* classes must be present on the source path. Sun's SDK for the WIN32 platform has these in a file called src.zip.
This file must be unzipped and placed on the sourcepath along with the other directories that contain the program to be converted. JCC can read files from .zip containers, but unfortunately the src.zip file contains files that start in a directory called "src". The simplest solution is to unzip the contents into the root of a disk and include "\src;" in the sourcepath.
The converted files will be placed in the default directory.
WARNING - Any program being translated must be fully debugged and tested using a normal Java system. The error messages output by JCC are not always very explicit, and not many checks are made of a program's correctness. Additionally the odd compilation error may be present when the C code is compiled. None are known to exist that matter, and the others are being worked on at present.
The following may help to get things running:
The following is a list of the known language restrictions. There are in addition many functions missing from the runtime library that will prevent some programs being built. Most notably there is no support for the AWT classes at present.
static final int x = foo();
result in a variable that can be used in a switch expression. Because JCC translates Java switch statement directly into a C switch statement this is not allowed. This restriction had not caused any programs I know of to fail to be translated.
My implementation of this (for WIN32 only) works a different way that has some advantages and some disadvantages. In the same way as above the threads used by JCC are normal operating system threads, but JCC only allows one thread at a time to execute 'within' the JCC system. There is a formal definition of being 'inside' or 'outside' the JCC environment. A thread is inside with it executes code that lies between calls to jcc_enterCriticalSection() and jcc_exitCriticalSection(). When a thread is started it immediately is placed inside the system where it is guaranteed to be the only thread executing in this state. When the thread has to perform I/O then it exits from the JCC environment (by calling jcc_exitCriticalSection()), performs the I/O and then re-enters (by calling jcc_enterCriticalSection()). If a thread tries to enter the system when another is already inside then is it blocked until the current thread exits.
The results of this system is that the threads are not preemptable like they are in the normal JDK. The is nothing actually in the Java spec that disallows this approach, but some programs may not execute correctly because of this.
The main advantage of this arrangement is that synchronisation comes virtually for free. The time penalty incurred entering and exiting synchronised code sections is little more that pushing an address on a special stack. If by the time the thread next exits from the system there is anything on this stack real monitor lock objects are created. This will never be necessary for synchronised methods that do not perform I/O. A good example here is java.lang.Hashtable.
The only other disadvantage is that programs may not run particularly efficiently on a symmetric multiprocessor (SMP) machine. An exception here maybe programs that are highly I/O bound, because I/O, and code outside the JCC environment, can occur simultaneously even if the code within it cannot.
WARNING - The garbage collector used in this package is not thought to be 100% solid with threaded code. Personally I have had no problems, but the use of this feature (like everything else) is entirely at your own risk.
double POSITIVE_INFINITY = 1.0 / 0.0;
will not compile. Further work is needed in this area.
When there have been problems I have solved them by making dummy calls into the primary class. This can look something like:
public class Main
{
public static void main( String args[] )
{
if( false )
{
new Runtime();
new System();
}
whatever...
}
This example will cause the class initialisers in Runtime() and System() to be called before the others. (The fact that the above code is not legal in the normal Java systems does not trouble JCC.)
javac -verbose -classpath g:\java\lib\classes.zip JavaLex.java
Results using the 1.0.2 JDK:
[parsed JavaLex.java in 17030ms] [loaded g:\java\lib\classes.zip(java/lang/Object.class) in 280ms] [checking class CAccept] [wrote CAccept.class] [checking class CLexGen] [loaded g:\java\lib\classes.zip(java/io/InputStream.class) in 60ms] [loaded g:\java\lib\classes.zip(java/io/DataOutputStream.class) in 50ms] [loaded g:\java\lib\classes.zip(java/util/Hashtable.class) in 110ms] [loaded g:\java\lib\classes.zip(java/lang/String.class) in 160ms] [loaded g:\java\lib\classes.zip(java/io/FileNotFoundException.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/IOException.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/Exception.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/FileInputStream.class) in 60ms] [loaded g:\java\lib\classes.zip(java/io/FileDescriptor.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/File.class) in 160ms] [loaded g:\java\lib\classes.zip(java/lang/System.class) in 110ms] [loaded g:\java\lib\classes.zip(java/io/PrintStream.class) in 50ms] [loaded g:\java\lib\classes.zip(java/io/FilterOutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/OutputStream.class) in 50ms] [loaded g:\java\lib\classes.zip(java/io/FileOutputStream.class) in 60ms] [loaded g:\java\lib\classes.zip(java/io/BufferedOutputStream.class) in 50ms] [loaded g:\java\lib\classes.zip(java/util/Dictionary.class) in 60ms] [loaded g:\java\lib\classes.zip(java/lang/Character.class) in 160ms] [loaded g:\java\lib\classes.zip(java/lang/Integer.class) in 110ms] [loaded g:\java\lib\classes.zip(java/lang/Number.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/Cloneable.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/Throwable.class) in 60ms] [loaded g:\java\lib\classes.zip(java/io/DataOutput.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/StringBuffer.class) in 170ms] [loaded g:\java\lib\classes.zip(java/util/Vector.class) in 110ms] [loaded g:\java\lib\classes.zip(java/util/Enumeration.class) in 0ms] [loaded g:\java\lib\classes.zip(java/util/BitSet.class) in 60ms] [wrote CLexGen.class] [checking class CEmit] [wrote CEmit.class] [checking class CNfaPair] [wrote CNfaPair.class] [checking class CAlloc] [wrote CAlloc.class] [checking class CSpec] [wrote CSpec.class] [checking class CDTrans] [wrote CDTrans.class] [checking class CError] [loaded g:\java\lib\classes.zip(java/lang/Error.class) in 0ms] [wrote CError.class] [checking class CDfa] [wrote CDfa.class] [checking class CMakeNfa] [wrote CMakeNfa.class] [checking class CNfa] [wrote CNfa.class] [checking class CUtility] [wrote CUtility.class] [checking class JavaLex] [wrote JavaLex.class] [checking class CBunch] [wrote CBunch.class] [checking class CMinimize] [wrote CMinimize.class] [checking class CInput] [wrote CInput.class] [checking class CSet] [wrote CSet.class] [checking class CAcceptAnchor] [wrote CAcceptAnchor.class] [checking class CNfa2Dfa] [loaded g:\java\lib\classes.zip(java/util/Stack.class) in 0ms] [wrote CNfa2Dfa.class] [done in 35970ms]Results using JCC:
[parsed JavaLex.java in 1160ms] [loaded g:\java\lib\classes.zip(java/lang/Object.class) in 0ms] [checking class CNfa2Dfa] [loaded g:\java\lib\classes.zip(java/lang/System.class) in 0ms] [loaded g:\java\lib\classes.zip(java/util/Vector.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/PrintStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/String.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/FilterOutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/OutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/Cloneable.class) in 0ms] [loaded g:\java\lib\classes.zip(java/util/BitSet.class) in 0ms] [loaded g:\java\lib\classes.zip(java/util/Stack.class) in 0ms] [loaded g:\java\lib\classes.zip(java/util/Hashtable.class) in 0ms] [loaded g:\java\lib\classes.zip(java/util/Dictionary.class) in 0ms] [wrote CNfa2Dfa.class] [checking class CError] [loaded g:\java\lib\classes.zip(java/lang/Error.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/Throwable.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/StringBuffer.class) in 50ms] [wrote CError.class] [checking class CMakeNfa] [loaded g:\java\lib\classes.zip(java/io/IOException.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/Exception.class) in 0ms] [wrote CMakeNfa.class] [checking class JavaLex] [loaded g:\java\lib\classes.zip(java/io/FileNotFoundException.class) in 0ms] [wrote JavaLex.class] [checking class CMinimize] [wrote CMinimize.class] [checking class CBunch] [wrote CBunch.class] [checking class CSpec] [loaded g:\java\lib\classes.zip(java/lang/Integer.class) in 60ms] [loaded g:\java\lib\classes.zip(java/lang/Number.class) in 0ms] [wrote CSpec.class] [checking class CAcceptAnchor] [wrote CAcceptAnchor.class] [checking class CDfa] [wrote CDfa.class] [checking class CNfa] [wrote CNfa.class] [checking class CAlloc] [wrote CAlloc.class] [checking class CDTrans] [wrote CDTrans.class] [checking class CAccept] [wrote CAccept.class] [checking class CInput] [loaded g:\java\lib\classes.zip(java/io/InputStream.class) in 170ms] [wrote CInput.class] [checking class CNfaPair] [wrote CNfaPair.class] [checking class CLexGen] [loaded g:\java\lib\classes.zip(java/io/DataOutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/FileInputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/FileDescriptor.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/File.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/FileOutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/BufferedOutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/Character.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/DataOutput.class) in 0ms] [loaded g:\java\lib\classes.zip(java/util/Enumeration.class) in 0ms] [wrote CLexGen.class] [checking class CSet] [wrote CSet.class] [checking class CUtility] [wrote CUtility.class] [checking class CEmit] [wrote CEmit.class] [done in 3130ms]It is interesting to note here that although the overall speed up was over eleven times, the parsing phase (which presumably does not involve any I/O) was 14.6 times faster.
Another test that involved a single class with 100 methods all containing the same code produced an overall speedup of nearly 14 times. This slightly contrived test was unusually CPU bound because only a few classes needed to be loaded. Some fine tuning of the garbage collection parameters was also involved that made the result about 22% faster than it would otherwise have been.
Results using the 1.0.2 JDK:
G:\jcc\Test>javac -verbose -classpath g:\java\lib\classes.zip Test.java [parsed Test.java in 45580ms] [loaded g:\java\lib\classes.zip(java/lang/Object.class) in 110ms] [checking class Test] [loaded g:\java\lib\classes.zip(java/lang/System.class) in 110ms] [loaded g:\java\lib\classes.zip(java/io/PrintStream.class) in 110ms] [loaded g:\java\lib\classes.zip(java/lang/String.class) in 170ms] [loaded g:\java\lib\classes.zip(java/io/FilterOutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/OutputStream.class) in 60ms] [loaded g:\java\lib\classes.zip(java/lang/StringBuffer.class) in 1260ms] [wrote Test.class] [done in 185870ms]Results using the JCC:
G:\jcc\Test>cjavac -verbose -classpath g:\java\lib\classes.zip Test.java ** GC_free_space_divisor = 2 ** ** GC_expand_hp( 4000000 ) ** [parsed Test.java in 2910ms] [loaded g:\java\lib\classes.zip(java/lang/Object.class) in 0ms] [checking class Test] [loaded g:\java\lib\classes.zip(java/lang/System.class) in 60ms] [loaded g:\java\lib\classes.zip(java/io/PrintStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/String.class) in 50ms] [loaded g:\java\lib\classes.zip(java/io/FilterOutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/io/OutputStream.class) in 0ms] [loaded g:\java\lib\classes.zip(java/lang/StringBuffer.class) in 0ms] [wrote Test.class] [done in 13510ms]Note - In order to convert javac one needs the source code from Sun. This is free of charge, but you must sign a licence agreement. Because of the current incompatibility of floating point numbers the isInfinite() methods of java.lang.Float and java.lang.Double must be changed to simply return false. The next release should have this problem solved.
In this case the converted program is running 22.36 times faster.
Results using the 1.0.2 JDK:
12:51:30.32 - G:\jcc\JavaLex> 12:51:30.38 - G:\jcc\JavaLex>java nshaylor.jlx.JavaLex Yylex.tmp Processing first section -- user code. Processing second section -- Java-Lex declarations. Processing third section -- lexical rules. Creating NFA machine representation. NFA comprised of 1026 states. Creating DFA transition table. Working on DFA states........................................................... ................................................................................ ................................................................................ ................................................................................ ................................................................................ .......................................................... Minimizing DFA transition table. 340 states after removal of redundant states. Outputting lexical analyzer code. 13:00:11.84 - G:\jcc\JavaLex>Results using JCC:
12:48:30.28 - G:\jcc\JavaLex> 12:48:30.39 - G:\jcc\JavaLex>javalex Yylex.tmp Processing first section -- user code. Processing second section -- Java-Lex declarations. Processing third section -- lexical rules. Creating NFA machine representation. NFA comprised of 1026 states. Creating DFA transition table. Working on DFA states........................................................... ................................................................................ ................................................................................ ................................................................................ ................................................................................ .......................................................... Minimizing DFA transition table. 340 states after removal of redundant states. Outputting lexical analyzer code. 12:48:55.38 - G:\jcc\JavaLex>
Results using the 1.0.2 JDK:
10:57:00.35 - G:\jcc\quicksort>java nshaylor.jcc.tests.quicksort4.QuickSort 3000000 Sorting integers Finished 11:09:53.26 - G:\jcc\quicksort>Results using JCC:
10:54:50.34 - G:\jcc\quicksort>quicksort 3000000 Sorting integers Finished 10:55:04.13 - G:\jcc\quicksort>
There are many cases where building Java programs in this way is inappropriate, or indeed impossible, but as the language becomes popular there will probably be a growing body of software where performance or program size becomes an issue. I do not see programs like JCC changing greatly the way most Java software is run, but rather extending the use of the language into areas where in cannot currently be used.
I can see no reason why an operating system device driver could not be built with JCC using some custom native methods. Such a project would only require two issues to be addressed: the extra memory required for object headers, and the cost of garbage collection. Modern garbage collection techniques are very sophisticated and often impose a very small performance overhead, but if this proved to be too high, an option might exist to design the program not to generate any garbage. This would mean the program managing its use of memory manually. It could be argued this represents the loss of a very important feature, but it may be an acceptable compromise in such a case. The object header overhead is only eight bytes plus whatever the garbage management software requires.
Software for imbedded microprocessor systems can be subject to similar restrictions in space and time. The programs produced by JCC are fast, small, uncomplicated in there requirements, and easily interfaces to other software written in C. There is also no reason in principal why ROMable 16 bit code could not also be produced.
Thanks to Hans-Juergen Boehm for his kind advice, and Jonathan Souster for his help with the run time library.
Please report any bugs to me, Nik Shaylor, at nshaylor@tcp.co.uk You are visitor: