The idea of combining interpreted and native-code text could be traced back to early work by Ershov and others [2], and continues to this day, currently in the active field of partial evaluation.
For languages such as C, which have long been implemented primarily by compilation to native code (an exception is the si system [4]), interpreters are experiencing somewhat of a comeback. For one explanation, consider that Proebsting and others are making interpreters much more efficient [19], by combining compiled with interpreted text. However, even optimized interpretation cannot compete with the speed of execution of native code. Through the use of superoperators, Proebsting has achieved interpretation speeds only 3-9 times slower than executing unoptimized native code. Without superoperators that value jumps up to 8-16 [19].
Interpretation also has several other features to recommend it [16]. Foremost is the interactive nature of interpretation. Interpreted environments can respond immediately to change, without the impediment of lengthy compile times. In interactive applications this can be extremely important.
A couple of other advantages of interpretation that are sometimes overlooked are the possibilities of platform independence and smaller distribution file size. Native executables are by nature bound to the specific platform for which they were compiled. In contrast, interpretable code, whether a high-level source language or an intermediate bytecode representation, can be designed to be independent of any particular machine. In addition, an efficient bytecode representation can be much smaller than the equivalent native code.
Support for interactive, source-level debugging is also more innate with interpretation. Furnishing such support in a compiled environment is a much more arduous task, required the preservation of a mapping from the generated native code to the original source code.
Finally, when comparing the performance of an interpreted environment to that of a compiled environment, the comparison is usually between the speed of interpretation and the speed of execution of native code, without taking into account the time spent producing the native code. This may be an acceptable comparison in some circumstances, but in other situations, such as with mobile computing and software development as described in Chapter 1, the time spent in translation is of vital importance.
In other related work, adaptive optimization has been considered for the Self language, with the idea of using a fast compiler to generate initial code while an optimizing compiler recompiles heavily used parts [9]. Kastens has considered how to generate interpreters automatically from compiler specifications [12].
In comparing our work with the above, note that we are not as interested in maintaining two forms of language processors (a compiler and interpreter) as we are in examining the role of a common, machine-independent representation for applications. While speeding up interpretation is always a win, there is inevitably a performance gap between interpretation and heavily optimized target-machine code.
Perhaps closest in spirit to the paradigm we propose are the new ``just-in-time'' (JIT) compilers that are being developed for the Java language [11]. However, these compilers differ from the continuous compilation paradigm that we are proposing in several important aspects.
First of all, the JIT compiler does not work in tandem with the interpreter; rather it is meant to replace the interpreter. As classes are loaded into the run-time virtual machine (Java is an object-oriented language), the method pointers in the virtual method table, which in the interpreted version of Java would point to the bytecode of the corresponding methods, are replaced with pointers to the JIT compiler. Then, the first time each method is called, the JIT compiler is invoked to compile the method. The pointer in the virtual method table is then patched to point to the native-code version of the method so that future calls to the method will jump to the native-code. With the JIT compiler in place, no interpretation of the methods ever takes place.
While this is a valid, and valuable, technique, it differs significantly from the continuous compilation model. With the current implementations of the JIT compilers, compilation is only done on demand (i.e., upon the first call to each method) and execution of the method must wait until compilation is complete. As long as the average size of an individual method tends to be rather small, the wait is not likely to be noticeable. However, it means that compilation speed is of great importance. Consequently, time cannot be spent on optimizing the generated code.
In addition, using a compile-on-demand model in an interactive environment (which is where Java seems to be aimed) leads to inefficiency. In environments characterized by user interaction, the CPU often remains idle, or runs well below 100% capacity, for much of the time while waiting for user input. If some sort of ``precompiling'' (analogous to prefetching in memory management or disk caching) model is used in the compile-on-demand system, then this idle time could be put to use compiling methods that are likely to be called in the near future. However, the current JIT compiler implementations do not seem to support precompiling (although they almost certainly will in the future).
The JIT compilers are closely tied to the Java language and run-time system. They do not produce independently executable object files. They are meant to only speed up execution of class methods called by the Java run-time virtual machine.
While the continuous compilation paradigm can be applied to Java, it is a considerably more generic approach. It is meant to be applicable to any situation in which traditional compilation or interpretation is used.