RHS's Bytecode Package

RHS: I've been working on a package to examine and modify the bytecodes for a proc. Since I've actually gotten something working, I figured its time I put up a link to what I've done so far. The actual code (which is a combination of Tcl and C) can be found at [L1 ] where the rest of the things I've been working on are located.

What is it?

The general idea of the package is to allow one to get the bytecodes for a procedure, and then modify them. The current code implements:

::bytecode::get procname

  • Gets a dict (really just nested lists, since its built for 8.4) with all the useful information about the bycodes for a given proc.

::bytecode::setExceptionRanges procname ranges

  • Sets the exception ranges for a procedure. Takes as input the same format one would get from [dict get [::bytecode::get procname] ExceptionRanges Exceptions]. The interesting thing is that one can change the jump location for breaks, continues, and catches. This means its possible to change a break to break 2 levels.

I've also used the forward compatible dict code from forward-compatible dict, because it made the output from the get command much easier to understand when using dicts.

As a note, the code is full of memory leaks. The two main places I'm aware of where there are leaks are:

  • Lots of Tcl_Obj creation, very little refcounting
  • When the new exception range is set, the old one cannot currently be freed, since its part of a larger malloc'd block

To Do

  • Clean up the memory leaks from Object creation
  • Change the exception range code to use an EnvPtr and then create an entirely new ByteCode structure allocated at once. This is both so that the structure isn't fractured, and to clean up that particular memory leak
  • Implement a command to set the following for a proc: CompiledLocals, CompiledLiterals, and actual ByteCodes

Once one can set all 4 of the above (the currently setable exception ranges being the 4th), I believe it will be possible to write a command that can define a proc from scratch using bytecodes, along the lines of:

 ::bytecode::byteproc args extraCompiledLocals compiledLiterals exceptionRanges byteCodes

Where:

  • args is an arglist, the same as in a normal proc definition now
  • extraCompiledLocals is a list of compiled locals the proc will use, in order, above and beyond the arguements
  • compiledLiterals is a list of literals and their values (ie, the number 1, the letter b)
  • byteCodes is an ordered list of bytecodes and their operands
 proc testme {} {
     set retval 0
     for {set i 0} {$i < 10} {incr i} {
         for {set j 0} {$j < 10} {incr j} {
             incr retval
             break
         }
     }
     return $retval
 }

Would be

 ::bytecode::byteproc testme {} {
     0 {Type scalar Name retval}
     1 {Type scalar Name i}
     2 {Type scalar Name j}
 } {
     0 1
     1 10
     2 {}
 }  {
     0 {Level 1 Type loop PC {12 37} Continue 39 Break 50}
     1 {Level 1 Type loop PC {39 41} Continue -1 Break 50}
     2 {Level 2 Type loop PC {19 23} Continue 25 Break 36}
     3 {Level 2 Type loop PC {25 27} Continue -1 Break 36}
 }  {
    {push1 0}
    {storeScalar1 0}
    {pop}
    {push1 0}
    {storeScalar1 1}
    {pop }
    {jump1 33}
    {push1 0}
    {storeScalar1 2}
    {pop }
    {jump1 12}
    {incrScalar1Imm 0 1}
    {pop}
    {break}
    {pop}
    {incrScalar1Imm 2 1}
    {pop}
    {loadScalar1 2}
    {push1 1}
    {lt}
    {jumpTrue1 -15}
    {push1 2}
    {pop}
    {incrScalar1Imm 1 1}
    {pop}
    {loadScalar1 1}
    {push1 1}
    {lt}
    {jumpTrue1 -36}
    {push1 2}
    {pop}
    {loadScalar1 0}
    {done}
    {done}
 }

As a note, if the 3rd exception (lindex 2) is changed from break 36 to break 50, breaks in the inner for loop now break the outter loop also, so proc returns 1 instead of 10 (that actuall the test I use for the setExceptionRange command).