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:
- append
(done)
- global
(done)
- incr
(done)
- info exists
- lappend (done)
- lindex
(done)
- list
(done)
- llength
(done)
- lrange
- lset
- regexp
- regsub
- set
(done)
- string index (done)
- string length (done)
- string compare (done)
- string equal (done)
- string range (done)
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.