|
Tcl 8.0 introduced an on-the-fly byte-code compiler for Tcl.
The compiler results in faster code execution.
However, in some cases, particularly error cases, it can cause
scripts to behave differently than in previous versions of Tcl.
This page describes differences in the semantics of Tcl scripts between
Tcl 8.0 and previous releases of Tcl.
A major goal for the Tcl 8.0 compiler is no changes or very few changes
to Tcl/Tk scripts or C extensions. I have tried very hard to ensure
that the behavior documented in the man pages is preserved but I have
made some changes that are visible at the level of Tcl scripts. Most
of these changes allow Tcl programs to execute faster. The key changes
are the following.
- Compilation errors return errors immediately, before executing
a script
Tcl 8.0 now has separate compilation and execution phases. If an error
is found during compilation, an error is returned immediately and the
script is not executed (not even partially). This differs from Tcl
7.6 where a script would be executed up to the first command with an
error.
Compilation errors are now returned for scripts with malformed expression
words (e.g., words with missing close braces or quotes). In addition,
several commands are treated specially by the Tcl bytecode compiler
and are compiled into an inline sequence of instructions for better
execution speed. In Tcl 8.0, these commands are break,
catch, continue, expr, for,
foreach, if, incr, set,
and while. If a script includes one of these commands
with the wrong number of argument words (e.g., set a xxx yyy),
a compilation error is returned immediately and the script is not executed.
For example, the body of the following procedure has a syntax error:
proc p {} {
global x
set x 1
incr x 1 2 ;# error: wrong number of arguments
set x 2
}
When p is called the first time, its body is compiled.
In Tcl 8.0, a syntax error is returned immediately and the global variable
x is never modified. In Tcl 7.6, x would
be set to 1.
Returning a compilation error immediately lets you find typos and other
syntax errors in your scripts as soon as possible. It also avoids the
Tcl 7.6 problem that a bad procedure body or eval'ed script
could leave your data structures in an inconsistent state because they
only executed partially.
- Catch no longer catches compilation errors
One consequence of the fact that in Tcl 8.0 compilation errors in scripts
are returned immediately is that catch no longer prevents
compilation errors from aborting command interpretation. For example,
catch {set}
immediately reports the error
wrong # args: should be "set varName ?newValue?"
To have catch handle compile time errors, give it a script
that is only determined at runtime: for example,
catch [concat {set}]
- Scripts are now completely compiled
Tcl 7.6 would ignore any characters in a script after the last command
executed. This allowed you, for example, to put comments after the
exit command that terminated a script without having to
start each comment line with a #:
initialize ;# the commands
compute ;# of the
finalize ;# program
exit ;# stop execution
This is the first line in the script's change log.
A second line in the change log.
Tcl 8.0 now attempts to compile those lines so you should put a
# at the start of each comment line.
- Lists are now aggressively parsed
Lists are now a real (and significantly faster) data type and not just
strings that are rescanned each time. You still use them the same way,
but strings are converted to a faster internal representation behind
the scenes on the first list operation. This means that lists are now
completely parsed. It used to be the case that if you had a misformed
list but the erroneous part was after the point you inserted or extracted
an element, then you never saw an error. Tcl 8.0 now reports an error.
For example, in Tcl 8.0
lindex {a b "c"d e} 1
returns the error
list element in quotes followed by "d" instead of space
while in Tcl 7.6, it returned b.
- List operations don't preserve the exact white space between
elements
List operations in Tcl 7.6 always retained the white space between
list elements. This is no longer true in Tcl 8.0, where list operations
return lists whose elements are separated by a single space. In the
following example, the first command sets the variable x
to a string containing two tab characters:
set x {one two three}
lrange $x 0 1
In Tcl 8.0, the lrange command returns one two.
Programs that need to preserve white space should either use string
operations or use a combination of the split and join
commands. For example,
join [lrange [split $x { }] 0 1] { }
will preserve the tab between the list elements (there is a tab character
inside each pair of braces). Note that the Tcl man pages never promised
to preserve the white space.
- Fewer floating-to-string conversions (and the associated rounding) may change program behavior
Both Tcl 8.0 and 7.6 use full IEEE floating-point precision
(about 17 decimal digits) when computing expressions.
They both round floating-point values when converting them to strings
(although by default Tcl 8.0 retains 12 digits
while Tcl 7.6 keeps 6 digits).
However, the new object system in Tcl 8.0 causes fewer floating-to-string
conversions (and the associated rounding) to occur than in Tcl 7.6.
These changes mean that floating-point computation
is more accurate and faster in Tcl 8.0,
but there are sometimes behavioral changes.
For example, the command
for {set x 0.0} {$x != 4.0} {set x [expr $x+0.1]} {puts $x}
terminates in Tcl 7.6 but loops forever in Tcl 8.0.
This is because the fraction 0.1 can not be exactly
represented as an IEEE floating-point value
and so repeatedly adding it to 0.0
will never produce an floating-point value that is exactly 4.0.
This loop terminated in Tcl 7.6 only because of the rounding
that was done at every assignment to the variable x.
The solution in Tcl 8.0 (and most other programming languages)
is to use approximate comparisons for floating-point.
That is, when looping until the value of a floating-point expression
reaches some target value
you need to test whether the expression is "close enough" to the target.
For example,
for {set x 0.0} {abs($x-4.0) > 0.001} {set x [expr $x+0.1]} {puts $x}
stops when the variable x reaches 4.0.
- The original strings in expressions are retained
For example,
expr {"0y" < "0x3"}
yields 0 (not 1) in Tcl 8.0 because the original string for "0x3" is
not lost. Tcl always tries to convert expression operands to numbers
if possible. In Tcl 7.6, this meant that the string "0x3" was lost
when the expression implementation converted it to a number (3) and
then back into a string when it realized that a string comparison had
to be done. This meant Tcl 7.6 did a string comparison between "0y"
and "3". Since Tcl values are now stored in Tcl object that hold an
internal form as well as a string, the original string isn't lost and
Tcl 8.0 can do the string comparison between "0y" and "0x3".
- info cmdcount is no longer accurate
Compiled commands are "compiled away" and their execution is no longer
counted. In Tcl 8.0, this includes the commands break,
catch, continue, expr, for,
foreach, if, incr, set,
and while. It was felt that the cost of keeping info
cmdcount accurate is not worth the cost of doing so.
- append no longer triggers read traces
The append command no longer triggers read traces when
fetching the old values of variables before doing the append operation.
It only triggers a write trace after each append. It was felt that
these read traces were not very useful and not worth the extra execution
overhead they require.
Note that the lappend command still triggers read traces
in this situation. lappend will be changed to behave like
append.
- lappend triggers only one write trace
lappend triggers only one write trace after appending
all arguments to the list.
- Error tracebacks are shorter
There are fewer recursive calls to Tcl_Eval now since some commands
like while and if have been "compiled away"
into a sequence of bytecoded instructions. This means that tracebacks
often have fewer traceback levels that in the past.
- Comments and white space are "free"
Only the bytecode compiler sees these. They do not effect the execution
speed of scripts.
- The length of (local) variable names doesn't matter.
For most commands, the compiler looks up local variables in a procedure
at compile time and emits instructions that, at execution time, take
the same amount of time regardless of the length of the variable names.
Frequently asked questions
- Will the Tcl 8.0 compiler will support the major extensions
to Tcl/Tk?
The compiler will work with any extension that doesn't modify the Tcl
core since Tcl 8.0 includes all the old C API's and the old tcl commands.
These extensions just need recompiling. Extensions that require source
changes to the core will be delayed because the Tcl 8.0 core is substantially
different than in Tcl7.6.
- I tie in to the Tcl event loop to support OLE, etc. Can
I use the compiler?
The Tcl event loop won't be changed and will operate just as before.
- Can I redefine the built-in commands?
Yes, but this causes all code that is currently cached for scripts
and procedure bodies to be thrown away. The scripts and procedure bodies
will be recompiled the next time they are used. This means you should
redefine any builtin commands at program startup.
- What is slower?
Expressions that are not enclosed in braces may be slower in Tcl 8.0.
The compiler must generate additional code for such expressions.
Also, the bytecode interpreter
may need to invoke the expr command for these expressions
at runtime after doing one round of substitutions in order
to preserve Tcl's two level substitution semantics for expressions.
This runtime call to the expr command
causes code to be generated, executed, and then thrown away,
which significantly adds to the execution cost.
The best speed in Tcl 8.0 is always obtained
by enclosing all expressions, even those in control structure commands,
inside braces.
Tk bindings and variable traces are often slower in Tcl 8.0 since they
compute new scripts that they then eval. Code is generated
for these scripts, executed once, then thrown away. If the code is
not iterative or doesn't execute for long, the time to compile it can
outweigh the savings in execution time.
- Can I save the Tcl bytecodes for my program to disk?
TclPro Compiler provides this feature.
This is most useful for companies and
individuals that want to "hide" their programs by
distributing byte-codes instead of Tcl scripts.
- Why doesn't Tcl use the Java bytecodes?
I had originally hoped to use Java bytecodes because they have a mature
design and because Java is widely available. I chose to use my own
Tcl-specific bytecodes because I was concerned that using the Java
virtual machine would be too slow or take too much memory. The basic
problem is the semantic mismatch between Java bytecodes and Tcl. Consider
the Tcl set command. Tcl variables behave very differently
than Java variables. I can't use a Java instruction like astore
(store object reference in local variable) to store a Tcl value into
a Tcl variable since it doesn't handle by itself such Tcl details as
variable traces, unset, or global. The best
I could do would be to translate a Tcl set command into
a sequence of several Java instructions that did the appropriate checks.
Unfortunately, the number of Java instructions to implement each Tcl
command would make the compiled program too big. A more realistic scheme
is to generate Java bytecodes that call one or more Java methods to
do the actual work for each Tcl command. With this number of Java method
calls, acceptable performance will depend on using a Java JIT (bytecode-to-machine
code) compiler. With Java JIT compilers becoming more widely available
this might be realistic. One possibility would be to translate the
relatively high-level Tcl bytecodes into Java bytecodes. However, there
is another problem with using Java bytecodes. Much of the interesting
code in Tcl/Tk and its extensions is in C. Java code can call native
methods implemented in C, and vice-versa, but this is awkward, not
portable, and the capability is disabled in Netscape (and many other
Java implementations) for safety reasons.
Brian Lewis, brian.lewis@eng.sun.com Last edited
August 21, 1997.
Top of page Developer Home
| Scriptics
| Getting Started
| Tcl Advocacy
| Software Resources
| Documents
| Services
| Community
Search
| Site Map
| Feedback
| Contact Us
| webmaster@scriptics.com
Increase page width
© 1998-2000 Scriptics Corporation. All rights reserved.
Legal Notice |
Privacy Statement
Last modified: March 09, 2000 |