What happens when you *define* a proc?Not much, actually: the source is saved as text, a command is created in the corresponding namespace. (The text may still have syntax errors or whatever - see procs as data structures).What happens when you *invoke* a proc?
- IF there is a corresponding bytecode, it is checked for validity: if it is not valid (validity is linked to whether certain commands - e.g. set, if - have their current configuration) it is regenerated as if it did not previously exist (see below).
- IF there is no corresponding bytecode, then the source text is parsed and compiled to bytecodes. The bytecodes are saved for later reuse
- The bytecodes are executed by the engine - see details below.
- Nothing when A is defined - remember that only text is saved
- (General case) When A is compiled, B's address is looked up in the hashtable of the corresponding namespace. A (pointer to a) structure is compiled into the bytecodes; the structure contains B's name and B's address (if found). B is *not* compiled at this time, as it was not invoked.
- (Special case) Some core commands (set, if, ...) are "bytecode compiled": their bytecodes are inlined with the bytecodes of the calling proc, their name and/or address are not present in the bytecodes.
- The bytecodes are first checked for validity (There are different situations that can invalidate a bytecode). If invalid, it is regenerated from the source.
- At each invocation of a command (B in the example above): check B's address for validity - if invalid, look up B by name in the corresponding hashtable.
- Prepare B's arguments, then invoke B - like a function call
Example
- Compilation time
# define B
proc B x {
if $x {
this is an error
} else {
set a b
}
}
# define A
proc A x {B $x}
# invoke A: this causes compilation of A, start running A,
# then invoke B (which cause its compilation). Both are
# compiled OK
A 0 ;# returns b
A 1 ;# runtime error: invalid command name "this"
A 0 ;# returns b, all is still OK
# define a proc "this"
proc this args {return "OK now"}
# invoke A; previous bytecodes of both A and B are used;
# at the invocation of 'this', it is found, parsed,
# compiled and run
A 1 ;# "this" is now found, returns "OK now"
# redefine B, now using a bcc'ed command. B's previous
# bytecodes are discarded, B is saved as text, no error
proc B x {
if $x {
# this is a compile-time error
set a b c
} else {
set a b
}
}
# invoke A: use existing compilation of A, start running A,
# then invoke B which cause its compilation. The error is at
# compile time due to the bcc'ed [set] (this is an open bug,
# actually), even the "correct" case fails:
A 0 ;# wrong # args: should be "set varName ?newValue?"The global variable 'tcl_traceCompile' permits observation of the compiler's activity - 1 tells you when compilation takes place, 2 shows you the bytecodes too.The variable 'tcl_traceExec' permits observation of the execution engine: 1 tells you whenever a proc is invoked, 2 whenever a command is invoked, 3 shows the actual bytecodes being executed.(You must compile tcl with the -DTCL_COMPILE_DEBUG flag, to make this work. de.)Is there any good reason that this isn't enabled by default? Does it impose a significant overhead? I suppose it must. Cmcc Indeed it does. --strickDKF: Compilation debugging is now (8.4) enable-able using a suitable flag to configure, at least on UNIX.
See also tcl::unsupported::disassemble, and be aware that it forces compilation of the things it disassembles. (Not very surprising that!)
