Version 1 of Proc to bytecodes: when, how does it happen

Updated 2002-10-16 14:36:23

MS: Following a question on the chat, here is an explanation of when and how a proc is compiled to bytecodes. Why compile to bytecode is a separate question.

This reflects my present understanding of all this, AFAIK essentially correct - I am aware of a lot of simplifications here, which I hope result in more clarity and not confusion. In particular, there is no mention of Tcl_Obj

I also do not address the behaviour of variables here.

In case of doubt, read the source, Luke ... and do correct me please!


What happens when you *define* a proc?

Not much, actually: the source is saved as text, a command is created in the corresponding namespace.

What happens when you *invoke* a proc?

  • IF there is a corresponding bytecode, it is checked for validity: if it is not valid, 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.

What happens when a command B is invoked in the body of proc A

  • 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.

What happens when bytecodes are executed?

  • 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.