Translating VCODE into Java
Rather than translating VCODE into Java and then compiling and running the resulting Java code, we could have implemented a VCODE interpreter in Java. There were two main reasons for rejecting this approach: simplicity and efficiency. Performing the translation is very easy, whereas writing a new interpreter would have been a much more ambitious undertaking, and adding an additional level of interpretation would inevitably slow down the resulting code.
The Java program generated by the translation process defines a single Java class of the same name as the original VCODE program. This class contains a Java method for each of the user-defined VCODE functions. A Java compiler is then used to produce Java bytecode from this source code. The bytecode is a portable executable representation of the program and can be run by any Java virtual machine, just as the original VCODE program can be run by any VCODE interpreter.
A simple Perl script called vcodetojava performs the translation, using an associative array to map most VCODE operations into the matching Java method calls. However, a few operations require extra processing. In particular, the function-defining operation FUNC is mapped to the opening of a new user-defined Java method, and the function-calling operation CALL is mapped to a Java call of the corresponding user-defined method. Both of these operations also require some name-demangling to ensure that VCODE function names remain legal in Java. The only other VCODE control flow operation is the IF...ELSE construct. This construct is mapped into a Java if...else block, where the if condition contains a method call that returns the boolean value on top of the vector stack.
Figure 5 shows an example of the translation process for a dot-product function, which multiplies the elements of two equal-length sequences together and returns the sum of the products. The NESL definition is a single line of code, and is compiled into a seven-line VCODE function. The VCODE function takes two pairs of segment descriptors and data vectors as input, corresponding to the X and Y sequences. The initial POP operation throws away a segment descriptor that is not needed by the following unsegmented elementwise multiplication * FLOAT (in general, POP means ``pop elements from a depth of ''). The COPY operation copies a segment descriptor to the top of the stack, where it is used by the plus-reduce (sum) operation. Finally, another POP discards the segment descriptor produced by the plus-reduce operation, and the function returns.
NESL:
function dotproduct (X, Y) = sum ({x * y: x in X; y in Y})VCODE:FUNC DOTPRODUCT_47 POP 1 1 * FLOAT COPY 1 1 +_REDUCE FLOAT POP 1 1 RETJava:private static void DOTPRODUCT_47 () { s.Pop (1,1); s.MultF (); s.Copy (1,1); s.AddReduceF (); s.Pop (1,1); }Figure 5: NESL, VCODE, and Java representations of a dot-product function.
The Java method at the bottom of Figure 5 is generated by vcodetojava. As can be seen, the translation is very simple and can be applied on a line-by-line basis. Note that the Java method calls are being applied to the object s, which is an instance of the VcodeEmulation class.
The vcodetojava script consists of about 210 lines of Perl, of which 80 constitute the actual algorithm while the rest initialize the associative array. On a Sun SPARCstation 5/85 workstation, the script translates the 13,500 lines of VCODE corresponding to the NESL test suite in about 3.5 seconds. This time compares to about 19 seconds for the NESL compiler to generate the VCODE, and 71 seconds for Sun's portable compiler javac to compile the Java into bytecode. The times on a low-end PC are roughly comparable, although a faster native Java compiler can be used in place of Sun's portable compiler. For example, Microsoft's Visual J++ development environment takes just over 3 seconds to compile the same Java file on a DX4-120 system.