[Subversion] / BytecodeAssembler / README.txt  

Diff of /BytecodeAssembler/README.txt

Parent Directory | Revision Log

version 2198, Tue Jun 20 01:34:07 2006 UTC version 2205, Tue Jul 4 22:21:25 2006 UTC
Line 14 
Line 14 
 the work needed to transform tree-like structures into linear bytecode  the work needed to transform tree-like structures into linear bytecode
 instructions, and includes the ability to do compile-time constant folding.  instructions, and includes the ability to do compile-time constant folding.
   
   Changes since version 0.1:
   
   * Added stack tracking across jumps, globally verifying stack level prediction
     consistency and rejecting dead code.
   
 Changes since version 0.0.1:  Changes since version 0.0.1:
   
 * Added massive quantities of new documentation and examples  * Added massive quantities of new documentation and examples
Line 190 
Line 195 
 method::  method::
   
     >>> c = Code()      >>> c = Code()
     >>> where = c.here()         # get a location at the start of the code  
   
     >>> c.LOAD_CONST(42)      >>> c.LOAD_CONST(42)
       >>> where = c.here()         # get a location near the start of the code
       >>> c.DUP_TOP()
       >>> c.POP_TOP()
     >>> c.JUMP_ABSOLUTE(where)   # now jump back to it      >>> c.JUMP_ABSOLUTE(where)   # now jump back to it
   
     >>> dis(c.code())      >>> dis(c.code())
       0     >>    0 LOAD_CONST               1 (42)        0           0 LOAD_CONST               1 (42)
                   3 JUMP_ABSOLUTE            0              >>    3 DUP_TOP
                     4 POP_TOP
                     5 JUMP_ABSOLUTE            3
   
 But if you are jumping *forward*, you will need to call the jump or setup  But if you are jumping *forward*, you will need to call the jump or setup
 method without any arguments.  The return value will be a "forward reference"  method without any arguments.  The return value will be a "forward reference"
Line 205 
Line 213 
 been reached::  been reached::
   
     >>> c = Code()      >>> c = Code()
     >>> forward = c.JUMP_ABSOLUTE() # create a jump and a forward reference      >>> c.LOAD_CONST(99)
       >>> forward = c.JUMP_IF_TRUE() # create a jump and a forward reference
   
     >>> c.LOAD_CONST(42)            # this is what we want to skip over      >>> c.LOAD_CONST(42)            # this is what we want to skip over
       >>> c.POP_TOP()
   
     >>> forward()   # calling the reference changes the jump to point here      >>> forward()   # calling the reference changes the jump to point here
     >>> c.LOAD_CONST(23)      >>> c.LOAD_CONST(23)
     >>> c.RETURN_VALUE()      >>> c.RETURN_VALUE()
   
     >>> dis(c.code())      >>> dis(c.code())
       0           0 JUMP_ABSOLUTE            6        0           0 LOAD_CONST               1 (99)
                   3 LOAD_CONST               1 (42)                    3 JUMP_IF_TRUE             4 (to 10)
             >>    6 LOAD_CONST               2 (23)                    6 LOAD_CONST               2 (42)
                   9 RETURN_VALUE                    9 POP_TOP
               >>   10 LOAD_CONST               3 (23)
                    13 RETURN_VALUE
   
     >>> eval(c.code())      >>> eval(c.code())
     23      23
Line 439 
Line 451 
   
     >>> c = Code()      >>> c = Code()
     >>> c.return_()      >>> c.return_()
       >>> dis(c.code())
         0           0 LOAD_CONST               0 (None)
                     3 RETURN_VALUE
   
       >>> c = Code()
     >>> c( Return() )      >>> c( Return() )
     >>> dis(c.code())      >>> dis(c.code())
       0           0 LOAD_CONST               0 (None)        0           0 LOAD_CONST               0 (None)
                   3 RETURN_VALUE                    3 RETURN_VALUE
                   4 LOAD_CONST               0 (None)  
                   7 RETURN_VALUE  
   
   
 Labels and Jump Targets  Labels and Jump Targets
Line 455 
Line 470 
 current location.  For example::  current location.  For example::
   
     >>> c = Code()      >>> c = Code()
     >>> forward = c.JUMP_FORWARD()      >>> c.LOAD_CONST(99)
     >>> c( 1, 2, forward, Return(3) )      >>> forward = c.JUMP_IF_FALSE()
       >>> c( 1, Code.POP_TOP, forward, Return(3) )
     >>> dis(c.code())      >>> dis(c.code())
       0           0 JUMP_FORWARD             6 (to 9)        0           0 LOAD_CONST               1 (99)
                   3 LOAD_CONST               1 (1)                    3 JUMP_IF_FALSE            4 (to 10)
                   6 LOAD_CONST               2 (2)                    6 LOAD_CONST               2 (1)
              >>   9 LOAD_CONST               3 (3)                    9 POP_TOP
                  12 RETURN_VALUE              >>   10 LOAD_CONST               3 (3)
                    13 RETURN_VALUE
   
 However, there's an easier way to do the same thing, using ``Label`` objects::  However, there's an easier way to do the same thing, using ``Label`` objects::
   
Line 470 
Line 487 
     >>> c = Code()      >>> c = Code()
     >>> skip = Label()      >>> skip = Label()
   
     >>> c(skip.JUMP_FORWARD, 1, 2, skip, Return(3))      >>> c(99, skip.JUMP_IF_FALSE, 1, Code.POP_TOP, skip, Return(3))
     >>> dis(c.code())      >>> dis(c.code())
       0           0 JUMP_FORWARD             6 (to 9)        0           0 LOAD_CONST               1 (99)
                   3 LOAD_CONST               1 (1)                    3 JUMP_IF_FALSE            4 (to 10)
                   6 LOAD_CONST               2 (2)                    6 LOAD_CONST               2 (1)
              >>   9 LOAD_CONST               3 (3)                    9 POP_TOP
                  12 RETURN_VALUE              >>   10 LOAD_CONST               3 (3)
                    13 RETURN_VALUE
   
 This approach has the advantage of being easy to use in complex trees.  This approach has the advantage of being easy to use in complex trees.
 ``Label`` objects have attributes corresponding to every opcode that uses a  ``Label`` objects have attributes corresponding to every opcode that uses a
Line 660 
Line 678 
                  14 POP_TOP                   14 POP_TOP
                  15 END_FINALLY                   15 END_FINALLY
   
   
 The ``ast_curry()`` utility function returns an ``instancemethod`` chain that  The ``ast_curry()`` utility function returns an ``instancemethod`` chain that
 binds the given arguments to the given function, creating a hashable and  binds the given arguments to the given function, creating a hashable and
 comparable data structure -- a trivial sort of "AST node".  Just follow the  comparable data structure -- a trivial sort of "AST node".  Just follow the
Line 907 
Line 926 
   
 stack_size  stack_size
     The predicted height of the runtime value stack, as of the current opcode.      The predicted height of the runtime value stack, as of the current opcode.
     Its value is automatically updated by most opcodes, but you may want to      Its value is automatically updated by most opcodes, but if you are doing
     save and restore it for things like try/finally blocks.  If you increase      something sufficiently tricky (as in the ``Switch`` demo, below) you may
     the value of this attribute, you should also update the ``co_stacksize``      need to explicitly set it.
     attribute if it is less than the new ``stack_size``.  
       The ``stack_size`` automatically becomes ``None`` after any unconditional
       jump operations, such as ``JUMP_FORWARD``, ``BREAK_LOOP``, or
       ``RETURN_VALUE``.  When the stack size is ``None``, the only operations
       that can be performed are the resolving of forward references (which will
       set the stack size to what it was when the reference was created), or
       manually setting the stack size.
   
 co_freevars  co_freevars
     A tuple of strings naming a function's "free" variables.  Defaults to an      A tuple of strings naming a function's "free" variables.  Defaults to an
Line 954 
Line 979 
   
 co_stacksize  co_stacksize
     The maximum amount of stack space the code will require to run.  This      The maximum amount of stack space the code will require to run.  This
     value is usually updated automatically as you generate code.  However, if      value is updated automatically as you generate code or change
     you manually set a new ``stack_size`` that is larger than the current      the ``stack_size`` attribute.
     ``co_stacksize``, you should increase the ``co_stacksize`` to match, so  
     that ``co_stacksize`` is always the largest stack size the code will  
     generate at runtime.  
   Stack Size Tracking and Dead Code Detection
   ===========================================
   
   ``Code`` objects automatically track the predicted stack size as code is
   generated, by updating the ``stack_size`` attribute as each operation occurs.
   A history is kept so that backward jumps can be checked to ensure that the
   current stack height is the same as at the jump's target.  Similarly, when
   forward jumps are resolved, the stack size at the jump target is checked
   against the stack size at the jump's origin.  If there are multiple jumps to
   the same location, they must all have the same stack size at the origin and
   the destination.
   
   In addition, whenever any unconditional jump code is generated (i.e.
   ``JUMP_FORWARD``, ``BREAK_LOOP``, ``CONTINUE_LOOP``, ``JUMP_ABSOLUTE``, or
   ``RETURN_VALUE``), the predicted ``stack_size`` is set to ``None``.  This
   means that the ``Code`` object does not know what the stack size will be at
   the current location.  You cannot issue *any* instructions when the predicted
   stack size is ``None``, as you will receive an ``AssertionError``::
   
       >>> c = Code()
       >>> fwd = c.JUMP_FORWARD()
       >>> print c.stack_size  # forward jump marks stack size as unknown
       None
   
       >>> c.LOAD_CONST(42)
       Traceback (most recent call last):
         ...
       AssertionError: Unknown stack size at this location
   
   Instead, you must resolve a forward reference (or define a previously-jumped to
   label).  This will propagate the stack size at the source of the jump to the
   current location, updating the stack size::
   
       >>> fwd()
       >>> c.stack_size
       0
   
   Note, by the way, that this means it is impossible for you to generate static
   "dead code".  In other words, you cannot generate code that isn't reachable.
   You should therefore check if ``stack_size`` is ``None`` before generating
   code that might be unreachable.  For example, consider this ``If``
   implementation::
   
       >>> def Pass(code=None):
       ...     if code is None:
       ...         return Pass
   
       >>> def If(cond, then, else_=Pass, code=None):
       ...     if code is None:
       ...         return ast_curry(If,cond,then,else_)
       ...     else_clause = Label()
       ...     end_if = Label()
       ...     code(cond, else_clause.JUMP_IF_FALSE, Code.POP_TOP, then)
       ...     code(end_if.JUMP_FORWARD, else_clause, Code.POP_TOP, else_)
       ...     code(end_if)
   
   It works okay if there's no dead code::
   
       >>> c = Code()
       >>> c( If(23, 42, 55) )
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (23)
                     3 JUMP_IF_FALSE            7 (to 13)
                     6 POP_TOP
                     7 LOAD_CONST               2 (42)
                    10 JUMP_FORWARD             4 (to 17)
               >>   13 POP_TOP
                    14 LOAD_CONST               3 (55)
   
   But it breaks if you end the "then" block with a return::
   
       >>> c = Code()
       >>> c( If(23, Return(42), 55) )
       Traceback (most recent call last):
         ...
       AssertionError: Unknown stack size at this location
   
   What we need is something like this instead::
   
       >>> def If(cond, then, else_=Pass, code=None):
       ...     if code is None:
       ...         return ast_curry(If,cond,then,else_)
       ...     else_clause = Label()
       ...     end_if = Label()
       ...     code(cond, else_clause.JUMP_IF_FALSE, Code.POP_TOP, then)
       ...     if code.stack_size is not None:
       ...         end_if.JUMP_FORWARD(code)
       ...     code(else_clause, Code.POP_TOP, else_, end_if)
   
   As you can see, the dead code is now eliminated::
   
       >>> c = Code()
       >>> c( If(23, Return(42), 55) )
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (23)
                     3 JUMP_IF_FALSE            5 (to 11)
                     6 POP_TOP
                     7 LOAD_CONST               2 (42)
                    10 RETURN_VALUE
               >>   11 POP_TOP
                    12 LOAD_CONST               3 (55)
   
   
 Blocks, Loops, and Exception Handling  Blocks, Loops, and Exception Handling
Line 988 
Line 1114 
     >>> c.code()      >>> c.code()
     <code object <lambda> ...>      <code object <lambda> ...>
   
 ``Code`` objects also check that the stack level as of a ``POP_BLOCK`` is the  
 same as it was when the block was set up::  
   
     >>> c = Code()  
     >>> c.SETUP_LOOP()  
     >>> c.LOAD_CONST(23)  
     >>> c.POP_BLOCK()  
     Traceback (most recent call last):  
       ...  
     AssertionError: Stack level mismatch: actual=1 expected=0  
   
   
 Exception Stack Size Adjustment  Exception Stack Size Adjustment
 -------------------------------  -------------------------------
   
 When you ``POP_BLOCK`` for a ``SETUP_EXCEPT`` or ``SETUP_FINALLY``, the code's  When you issue a ``SETUP_EXCEPT`` or ``SETUP_FINALLY``, the code's maximum
 maximum stack size is raised to ensure that it's at least 3 items higher than  stack size is raised to ensure that it's at least 3 items higher than
 the current stack size.  That way, there will be room for the items that Python  the current stack size.  That way, there will be room for the items that Python
 puts on the stack when jumping to a block's exception handling code::  puts on the stack when jumping to a block's exception handling code::
   
     >>> c = Code()      >>> c = Code()
     >>> c.SETUP_FINALLY()      >>> c.SETUP_FINALLY()
     >>> c.stack_size, c.co_stacksize      >>> c.stack_size, c.co_stacksize
     (0, 0)  
     >>> c.POP_BLOCK()  
     >>> c.END_FINALLY()  
     >>> c.stack_size, c.co_stacksize  
     (0, 3)      (0, 3)
   
 As you can see, the current stack size is unchanged, but the maximum stack size  As you can see, the current stack size is unchanged, but the maximum stack size
Line 1024 
Line 1135 
     >>> c = Code()      >>> c = Code()
     >>> c(1,2,3,4, *[Code.POP_TOP]*4)   # push 4 things, then pop 'em      >>> c(1,2,3,4, *[Code.POP_TOP]*4)   # push 4 things, then pop 'em
     >>> c.SETUP_FINALLY()      >>> c.SETUP_FINALLY()
     >>> c.POP_BLOCK()  
     >>> c.END_FINALLY()  
     >>> c.stack_size, c.co_stacksize      >>> c.stack_size, c.co_stacksize
     (0, 4)      (0, 4)
   
Line 1034 
Line 1143 
   
     >>> c = Code()      >>> c = Code()
     >>> c.SETUP_LOOP()      >>> c.SETUP_LOOP()
     >>> break_to = c.POP_BLOCK()  
     >>> c.stack_size, c.co_stacksize      >>> c.stack_size, c.co_stacksize
     (0, 0)      (0, 0)
   
Line 1042 
Line 1150 
 Try/Except Blocks  Try/Except Blocks
 -----------------  -----------------
   
 In the case of ``SETUP_EXCEPT``, the *current* stack size is also increased by  In the case of ``SETUP_EXCEPT``, the *current* stack size is increased by 3
 3, because the code following the ``POP_BLOCK`` will be the exception handler  after a ``POP_BLOCK``, because the code that follows will be an exception
 and will thus always have exception items on the stack::  handler and will thus always have exception items on the stack::
   
     >>> c = Code()      >>> c = Code()
     >>> c.SETUP_EXCEPT()      >>> c.SETUP_EXCEPT()
Line 1210 
Line 1318 
 And ``CONTINUE_LOOP`` is automatically replaced with a ``JUMP_ABSOLUTE`` if  And ``CONTINUE_LOOP`` is automatically replaced with a ``JUMP_ABSOLUTE`` if
 it occurs directly inside a loop block::  it occurs directly inside a loop block::
   
       >>> c.LOAD_CONST(57)
     >>> c.SETUP_LOOP()      >>> c.SETUP_LOOP()
       >>> fwd = c.JUMP_IF_TRUE()
     >>> c.CONTINUE_LOOP(c.here())      >>> c.CONTINUE_LOOP(c.here())
       >>> fwd()
     >>> c.BREAK_LOOP()      >>> c.BREAK_LOOP()
     >>> c.POP_BLOCK()()      >>> c.POP_BLOCK()()
     >>> dis(c.code())      >>> dis(c.code())
       0           0 SETUP_LOOP               5 (to 8)        0           0 LOAD_CONST               1 (57)
             >>    3 JUMP_ABSOLUTE            3                    3 SETUP_LOOP               8 (to 14)
                   6 BREAK_LOOP                    6 JUMP_IF_TRUE             3 (to 12)
                   7 POP_BLOCK              >>    9 JUMP_ABSOLUTE            9
               >>   12 BREAK_LOOP
                    13 POP_BLOCK
   
 In other words, ``CONTINUE_LOOP`` only really emits a ``CONTINUE_LOOP`` opcode  In other words, ``CONTINUE_LOOP`` only really emits a ``CONTINUE_LOOP`` opcode
 if it's inside some other kind of block within the loop, e.g. a "try" clause::  if it's inside some other kind of block within the loop, e.g. a "try" clause::
   
     >>> c = Code()      >>> c = Code()
       >>> c.LOAD_CONST(57)
     >>> c.SETUP_LOOP()      >>> c.SETUP_LOOP()
     >>> loop = c.here()      >>> loop = c.here()
     >>> c.SETUP_FINALLY()      >>> c.SETUP_FINALLY()
       >>> fwd = c.JUMP_IF_TRUE()
     >>> c.CONTINUE_LOOP(loop)      >>> c.CONTINUE_LOOP(loop)
       >>> fwd()
     >>> c.POP_BLOCK()      >>> c.POP_BLOCK()
     >>> c.END_FINALLY()      >>> c.END_FINALLY()
     >>> c.POP_BLOCK()()      >>> c.POP_BLOCK()()
     >>> dis(c.code())      >>> dis(c.code())
       0           0 SETUP_LOOP              12 (to 15)        0           0 LOAD_CONST               1 (57)
             >>    3 SETUP_FINALLY            7 (to 13)                    3 SETUP_LOOP              15 (to 21)
                   6 CONTINUE_LOOP            3              >>    6 SETUP_FINALLY           10 (to 19)
                   9 POP_BLOCK                    9 JUMP_IF_TRUE             3 (to 15)
                  10 LOAD_CONST               0 (None)                   12 CONTINUE_LOOP            6
             >>   13 END_FINALLY              >>   15 POP_BLOCK
                  14 POP_BLOCK                   16 LOAD_CONST               0 (None)
               >>   19 END_FINALLY
                    20 POP_BLOCK
   
   
 ----------------------  ----------------------
Line 1286 
Line 1402 
   
 Stack size tracking::  Stack size tracking::
   
     >>> c = Code()      >>> c = Code()          # 0
     >>> c.LOAD_CONST(1)      >>> c.LOAD_CONST(1)     # 1
     >>> c.POP_TOP()      >>> c.POP_TOP()         # 0
     >>> c.LOAD_CONST(2)      >>> c.LOAD_CONST(2)     # 1
     >>> c.LOAD_CONST(3)      >>> c.LOAD_CONST(3)     # 2
     >>> c.co_stacksize      >>> c.co_stacksize
     2      2
     >>> c.BINARY_ADD()      >>> c.stack_history
     >>> c.LOAD_CONST(4)      [0, ..., 1, 0, ..., 1]
       >>> c.BINARY_ADD()      # 1
       >>> c.LOAD_CONST(4)     # 2
     >>> c.co_stacksize      >>> c.co_stacksize
     2      2
       >>> c.stack_history
       [0, ..., 1, 0, 1, ..., 2, ..., 1]
     >>> c.LOAD_CONST(5)      >>> c.LOAD_CONST(5)
     >>> c.LOAD_CONST(6)      >>> c.LOAD_CONST(6)
     >>> c.co_stacksize      >>> c.co_stacksize
Line 1332 
Line 1452 
                   3 LOAD_ATTR                1 (bar)                    3 LOAD_ATTR                1 (bar)
                   6 DELETE_FAST              0 (baz)                    6 DELETE_FAST              0 (baz)
   
   
   Stack tracking on jumps::
   
       >>> c = Code()
       >>> else_ = Label()
       >>> end = Label()
       >>> c(99, else_.JUMP_IF_TRUE, Code.POP_TOP, end.JUMP_FORWARD)
       >>> c(else_, Code.POP_TOP, end)
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (99)
                     3 JUMP_IF_TRUE             4 (to 10)
                     6 POP_TOP
                     7 JUMP_FORWARD             1 (to 11)
               >>   10 POP_TOP
   
       >>> c.stack_size
       0
       >>> c.stack_history
       [0, 1, 1, 1, 1, 1, 1, 0, None, None, 1]
   
       >>> c = Code()
       >>> fwd = c.JUMP_FORWARD()
       >>> c.LOAD_CONST(42)    # forward jump marks stack size unknown
       Traceback (most recent call last):
         ...
       AssertionError: Unknown stack size at this location
   
       >>> c.stack_size = 0
       >>> c.LOAD_CONST(42)
       >>> fwd()
       Traceback (most recent call last):
         ...
       AssertionError: Stack level mismatch: actual=1 expected=0
   
   
   
   
   
 Sequence operators and stack tracking:  Sequence operators and stack tracking:
   
   
Line 1597 
Line 1755 
     ...     if code is None:      ...     if code is None:
     ...         return Pass      ...         return Pass
   
     >>> def NewConst(value, code=None):  
     ...     if code is None:  
     ...         return ast_curry(NewConst, value)  
     ...     code.emit_arg(LOAD_CONST, len(code.co_consts))  
     ...     code.co_consts.append(value)  
     ...     code.stackchange((0,1))  
   
     >>> import sys      >>> import sys
     >>> WHY_CONTINUE = {'2.3':5, '2.4':32, '2.5':32}[sys.version[:3]]      >>> WHY_CONTINUE = {'2.3':5, '2.4':32, '2.5':32}[sys.version[:3]]
   
Line 1618 
Line 1769 
     ...      ...
     ...     code(      ...     code(
     ...         end_switch.SETUP_LOOP,      ...         end_switch.SETUP_LOOP,
     ...             Call(NewConst(d.get), [expr]),      ...             Call(Const(d.get), [expr]),
     ...         else_block.JUMP_IF_FALSE,      ...         else_block.JUMP_IF_FALSE,
     ...             WHY_CONTINUE, Code.END_FINALLY      ...             WHY_CONTINUE, Code.END_FINALLY
     ...     )      ...     )
     ...      ...
     ...     cursize = code.stack_size      ...     cursize = code.stack_size - 1   # adjust for removed WHY_CONTINUE
     ...     for key, value in cases:      ...     for key, value in cases:
     ...         d[const_value(key)] = code.here()      ...         d[const_value(key)] = code.here()
     ...         code(value, cleanup.JUMP_FORWARD)      ...         code.stack_size = cursize
       ...         code(value)
       ...         if code.stack_size is not None: # if the code can fall through,
       ...             code(cleanup.JUMP_FORWARD)  # jump forward to the cleanup
     ...      ...
     ...     code(      ...     code(
     ...         else_block,      ...         else_block,
Line 1650 
Line 1804 
     27      27
   
     >>> dis(c.code())      >>> dis(c.code())
       0           0 SETUP_LOOP              36 (to 39)        0           0 SETUP_LOOP              30 (to 33)
                   3 LOAD_CONST               1 (<...method get of dict...>)                    3 LOAD_CONST               1 (<...method get of dict...>)
                   6 LOAD_FAST                0 (x)                    6 LOAD_FAST                0 (x)
                   9 CALL_FUNCTION            1                    9 CALL_FUNCTION            1
                  12 JUMP_IF_FALSE           18 (to 33)                   12 JUMP_IF_FALSE           12 (to 27)
                  15 LOAD_CONST               2 (...)                   15 LOAD_CONST               2 (...)
                  18 END_FINALLY                   18 END_FINALLY
                  19 LOAD_CONST               3 (42)                   19 LOAD_CONST               3 (42)
                  22 RETURN_VALUE                   22 RETURN_VALUE
                  23 JUMP_FORWARD            12 (to 38)                   23 LOAD_CONST               4 ('foo')
                  26 LOAD_CONST               4 ('foo')                   26 RETURN_VALUE
                  29 RETURN_VALUE              >>   27 POP_TOP
                  30 JUMP_FORWARD             5 (to 38)                   28 LOAD_CONST               5 (27)
             >>   33 POP_TOP                   31 RETURN_VALUE
                  34 LOAD_CONST               5 (27)                   32 POP_BLOCK
                  37 RETURN_VALUE              >>   33 LOAD_CONST               0 (None)
             >>   38 POP_BLOCK                   36 RETURN_VALUE
             >>   39 LOAD_CONST               0 (None)  
                  42 RETURN_VALUE  
   
   
 TODO  TODO


Generate output suitable for use with a patch program
Legend:
Removed from v.2198  
changed lines
  Added in v.2205

cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help