Future Improvements to TJC

The TJC compiler could benefit from additional work in the following areas, sorted by priority.

Inline Additional Commands

Quite a few Tcl commands that are suitable for inlining have not had inline support added to TJC. Commands that should be inlined include the following:

Inlined Integer Expr Operations

It is very common for a math expression to deal only with integer values. Tcl's expr command is written in such a way that either double or integer values can be used in an expression at any time. The actual type used for calculations changes depending on the type of the operands. The compiled expr implementation in TJC could be improved so that it would be possible to determine what types were being used at each stage of a calculation and emit inline code for the case of an integer operation. Additional testing is needed to determine how much this could speed up math operations and if the performance gain would be worth the effort required.

For example, the following Tcl code:

expr {$a == 0}

Would be compiled into an inlined expr like the following:

            // Binary operator: $a == 0
            TclObject tmp0 = interp.getVar("a", null, 0);
            ExprValue tmp1 = TJC.exprGetValue(interp, tmp0);
            ExprValue tmp2 = TJC.exprGetValue(interp, 0, null);
            TJC.exprBinaryOperator(interp, TJC.EXPR_OP_EQUAL, tmp1, tmp2);
            // End Binary operator: ==
            boolean tmp3 = tmp1.getBooleanValue(interp);
            TJC.exprReleaseValue(interp, tmp1);


The above code works as expected, but it could be further optimized. For example, the exprBinaryOperator() method must always check the type of each argument to the operator. In the Tcl expr command above, the type of the constant 0 is always the integer type. The compiler is currently unable to take advantage of this information to emit optimized logic when the type of the right hand operator is known. When used with integer arguments, the binary operators * + - < > <= >= == != eq ne & ^ and | a Tcl expr operator can be represented by a single Java statement. An improved TJC compiler could emit optimized code for the code above, like the following:

            // Binary operator: $a == 0
            TclObject tmp0 = interp.getVar("a", null, 0);
            ExprValue tmp1 = TJC.exprGetValue(interp, tmp0);
            if (tmp1.isIntType()) {
                tmp1.setIntValue( tmp1.getIntValue() == 0 );
            } else {
                ExprValue tmp2 = TJC.exprGetValue(interp, 0, null);
                TJC.exprBinaryOperator(interp, TJC.EXPR_OP_EQUAL, tmp1, tmp2);
            }
            // End Binary operator: ==
            boolean tmp3 = tmp1.getBooleanValue(interp);
            TJC.exprReleaseValue(interp, tmp1);


The optimized integer version of this expr command would execute more quickly because type checking for the right hand operator need not be done. Also, the operator logic is inlined in the code, which will execute more quickly than a call to the exprBinaryOperator() method.

Shared Constant Table

Currently, each command maintains a separate constant table. For example, the string "foo" is saved in a TclObject of type TclString. This implementation makes access to the constant string "foo" very efficient. Trouble is, there is no way to share constants across multiple commands. If a command c1 defines a constant string "foo" and another command c2 also defines a constant string "foo", then two instances of a constant TclObject are created for the same string. It is likely that memory usage could be significantly reduced if commands were able to share a common constant pool.

Eliminate Unused Interp Result Operations

Consider the following Tcl code:

proc p {} {
    set items [list ONE TWO THREE]
    set e [lindex $items 0]
    return $e
}

The proc defined above will set the interp result 5 times, twice for the set command invocations, once for the list command, once for the lindex command, and once for the return command. It should be possible to optimize away 4 of these result set operations. The two calls to the set command are not nested commands and they are followed by a return command, so these result set operations can easily be removed. The two nested commands can also avoid the interp result set operations by directly setting the value of the variable instead of the interp result. The final interp result set operation invoked by the return command would set the interp result for the whole command and could not be optimized away.

To illustrate how this would actually work, a portion of the source code generated for the proc defined above is shown here. This is the code for the first set command that defines the items variable in the proc p.

        { // Invoke: set items [...]
            TclObject tmp0;
            { // Invoke: list ONE TWO THREE
                TclObject tmp1 = TclList.newInstance();
                try {
                    TclList.append(interp, tmp1, const0);
                    TclList.append(interp, tmp1, const1);
                    TclList.append(interp, tmp1, const2);
                } catch (TclException ex) {
                    tmp1.release();
                    throw ex;
                }
                interp.setResult(tmp1);
            } // End Invoke: list
            tmp0 = interp.getResult();
            tmp0 = setVarScalar(interp, "items", tmp0, 0, varcache1, 1);
            interp.setResult(tmp0);
        } // End Invoke: set

The code above invokes interp.setResult() two times, once for the nested list command and once for the set command.  Ignoring the second result set operation and optimizing the first would result in functionally equivilent code. The following code example shows how the interp.setResult() operations could be commented out and replaced with inlined logic that avoids the unneeded invocations.

        { // Invoke: set items [...]
            TclObject tmp0;
            { // Invoke: list ONE TWO THREE
                TclObject tmp1 = TclList.newInstance();
                try {
                    TclList.append(interp, tmp1, const0);
                    TclList.append(interp, tmp1, const1);
                    TclList.append(interp, tmp1, const2);
                } catch (TclException ex) {
                    tmp1.release();
                    throw ex;
                }
                //interp.setResult(tmp1);
                tmp0 = tmp1;
            } // End Invoke: list
            //tmp0 = interp.getResult();
            tmp0 = setVarScalar(interp, "items", tmp0, 0, varcache1, 1);
            //interp.setResult(tmp0);
        } // End Invoke: set

Avoiding invocations of the interp.setResult() method in cases where it is not needed will increase performance of TJC compiled commands. Implementing this feature in TJC will require addition of logic to detect and handle these situations and addition of a number of test cases to validate the correctness of the optimized code.

Testing shows that the following Tcl command:

proc result1 {} {
    set sum 0
    for {set i 0} {$i < 1000} {incr i} {
        incr sum $i
    }
    return $sum
}

Executes in about 850 ms when interp result setting is emitted. When modified to remove unneeded result setting operations, the same code executes in about 650 ms. These results show that this result set optimization can improve runtime performance by 25% in some cases.

Local Variable Type Determination

A number of optimizations could be applied if the compiler were able to tell that a given variable was used only as a single type throughout a command. For example, a variable that was accessed only as an integer could be saved in a local variable of type int. Another example would be a variable that was accessed only as a list. Currently, there is currently no type determination support in TJC.

Consider the following Tcl for loop:

for {set i 0} {$i < 1000} {incr i} {
    # Do Something
}

Currently, code like the following is emitted. This example is significantly simplified, the set and incr command invocations have been replaced by a comment showing where they would be emitted:

        { // Invoke: for {set i 0} {$i < 1000} {incr i} ...
            // Invoke: set i 0
            for ( boolean skip = true ; true ; ) {
                if ( skip ) {
                    skip = false;
                } else {
                    // Invoke: incr i
                }

                // Binary operator: $i < 1000
                TclObject tmp1 = getVarScalar(interp, "i", 0, varcache1, 1);
                ExprValue tmp2 = TJC.exprGetValue(interp, tmp1);
                ExprValue tmp3 = TJC.exprGetValue(interp, 1000, null);
                TJC.exprBinaryOperator(interp, TJC.EXPR_OP_LESS, tmp2, tmp3);
                // End Binary operator: <
                boolean tmp4 = tmp2.getBooleanValue(interp);
                TJC.exprReleaseValue(interp, tmp2);
                if ( ! tmp4 ) { break; }

                // Do Something
            }
            interp.resetResult();
        } // End Invoke: for

If the compiler was able to determine that the loop variable $i is always used as an int, then code like the following could be emitted.

        { // Invoke: for {set i 0} {$i < 1000} {incr i} ...
            for ( int i = 0 ; true ; i++ ) {
                // Binary operator: $i < 1000
                ExprValue tmp0 = TJC.exprGetValue(interp, i, null);
                ExprValue tmp1 = TJC.exprGetValue(interp, 1000, null);
                TJC.exprBinaryOperator(interp, TJC.EXPR_OP_LESS, tmp0, tmp1);
                // End Binary operator: <
                boolean tmp4 = tmp2.getBooleanValue(interp);
                TJC.exprReleaseValue(interp, tmp2);
                if ( ! tmp4 ) { break; }

                // Do Something
            }
            interp.resetResult();
        } // End Invoke: for

Assuming that inlined int expression optimizations were also implemented, it should be possible to further simplify this code into the following:

        { // Invoke: for {set i 0} {$i < 1000} {incr i} ...
            for ( int i = 0 ; i < 1000 ; i++ ) {
                // Do Something
            }
            interp.resetResult();
        } // End Invoke: for

Loop Invariant Determination

Consider the following Tcl for loop:

set max 1000
for {set i 0} {$i < $max} {incr i} {
    # Do Something
}

At the start of each loop, the expression {$i < $max} is evaluated by code like the following:

            // Binary operator: $i < $max
            TclObject tmp0 = interp.getVar("i", null, 0);
            ExprValue tmp1 = TJC.exprGetValue(interp, tmp0);
            TclObject tmp2 = interp.getVar("max", null, 0);
            ExprValue tmp3 = TJC.exprGetValue(interp, tmp2);
            TJC.exprBinaryOperator(interp, TJC.EXPR_OP_LESS, tmp1, tmp3);
            // End Binary operator: <
            boolean tmp4 = tmp1.getBooleanValue(interp);
            TJC.exprReleaseValue(interp, tmp1);
            if ( ! tmp4 ) {
                // break out of loop
            }

The value of the variable $max does not change during the body of the loop, but the compiler does not know about this invariant. This loop would execute more quickly if the compiler was able to query the value of $max and then extract the integer value contained in the TclObject once at the start of the loop.

Emit Compact Code

For many simple cases, the compiler could emit significantly smaller code for operations that appear frequently. Consider the following Tcl commands:

set i 0
set j 10
set k 100

These three set operations could be emitted as inlined code like the following:

        { // Invoke: set i 0
            TclObject tmp0;
            tmp0 = setVarScalar(interp, "i", const0, 0, varcache1, 1);
            interp.setResult(tmp0);
        } // End Invoke: set
        { // Invoke: set j 10
            TclObject tmp1;
            tmp1 = setVarScalar(interp, "j", const1, 0, varcache2, 2);
            interp.setResult(tmp1);
        } // End Invoke: set
        { // Invoke: set k 100
            TclObject tmp2;
            tmp2 = setVarScalar(interp, "k", const2, 0, varcache3, 3);
            interp.setResult(tmp2);
        } // End Invoke: set

This inlined code is already very compact, but it could be further simplified by creating a static method that invokes these same operations:

    public static void setScalarConstant(Interp interp, String varname, TclObject const, Var cache, int cacheID) {
        TclObject tmp;
        tmp = setVarScalar(interp, varname, const, 0, cache, cacheID);
        interp.setResult(tmp);
    }


This emitted code could then invoke the setScalarConstant() method for each instance of the command:

        { // Invoke: set i 0
           
setScalarConstant(interp, "i", const0, varcache1, 1);
        } // End Invoke: set
        { // Invoke: set j 10
            setScalarConstant(interp, "j", const1, varcache2, 2);
        } // End Invoke: set
        { // Invoke: set k 100
            setScalarConstant(interp, "k", const2, varcache3, 3);
        } // End Invoke: set

The example above is extremely simplified, but it shows how the size of emitted Java code can be significantly reduced for many common cases. At runtime, the hotspot compiler should inline the method invocation so there should be no decrease in runtime performance. This change should reduce memory requirements since the emitted class files will be much smaller. This change should also speed up compile times since far fewer lines of code are passed to javac.

Compile Itcl methods

Jacl supports the Itcl object system, but the TJC compiler does not compile the bodies of Itcl methods. The itcl::body command as well as method or proc declarations inside a class declaration should be supported as container commands.

Compile Time Usage Warnings

Users of the tjc executable could find it useful to be able to display warnings and possible usage errors at compile time. Currently, TJC does not attempt to report syntax errors or usage issues at compile time. For example, TJC will not compile math expressions unless they are brace quoted, but there is no way to have the compiler print a warning when it finds an expression that is not brace quoted.

Tcl Source Include

Users might find it useful to have a module file OPTIONS flag that would include the original Tcl source for a proc declaration at the top of the generated Java source. This feature would make it easier to compare the original Tcl to the generated source code.

Javac Flags Option

Users might want to compile generated Java source files with special javac command line options. Currently, all source code is compiled with the -g option to support debugging the generated source. For example, passing the -O option to some versions of javac will enable compile time inlining of certain methods. Some options that users might want to pass include -encoding, -source, -target, and others.