|
|
.. _BytecodeAssembler reference manual: http://peak.telecommunity.com/DevCenter/BytecodeAssembler#toc |
.. _BytecodeAssembler reference manual: http://peak.telecommunity.com/DevCenter/BytecodeAssembler#toc |
|
|
|
|
|
Changes since version 0.6: |
|
|
|
* Experimental Python 3 support, including emulation of restored |
|
``BINARY_DIVIDE`` and ``UNARY_CONVERT`` functions. |
|
|
|
Changes since version 0.5.2: |
|
|
|
* Symbolic disassembly with full emulation of backward-compatible |
|
``JUMP_IF_TRUE`` and ``JUMP_IF_FALSE`` opcodes on Python 2.7 -- tests now |
|
run clean on Python 2.7. |
|
|
|
* Support for backward emulation of Python 2.7's ``JUMP_IF_TRUE_OR_POP`` and |
|
``JUMP_IF_FALSE_OR_POP`` instructions on earlier Python versions; these |
|
emulations are also used in BytecodeAssembler's internal code generation, |
|
for maximum performance on 2.7+ (with no change to performance on older |
|
versions). |
|
|
|
Changes since version 0.5.1: |
|
|
|
* Initial support for Python 2.7's new opcodes and semantics changes, mostly |
|
by emulating older versions' behavior with macros. (0.5.2 is really just |
|
a quick-fix release to allow packages using BytecodeAssembler to run on 2.7 |
|
without having to change any of their code generation; future releases will |
|
provide proper support for the new and changed opcodes, as well as a test |
|
suite that doesn't show spurious differences in the disassembly listings |
|
under Python 2.7.) |
|
|
|
Changes since version 0.5: |
|
|
|
* Fix incorrect stack size calculation for ``MAKE_CLOSURE`` on Python 2.5+ |
|
|
Changes since version 0.3: |
Changes since version 0.3: |
|
|
* New node types: |
* New node types: |
that maps each ``set_lineno()`` to the corresponding position in the bytecode. |
that maps each ``set_lineno()`` to the corresponding position in the bytecode. |
|
|
And of course, the resulting code objects can be run with ``eval()`` or |
And of course, the resulting code objects can be run with ``eval()`` or |
``exec``, or used with ``new.function`` to create a function:: |
``exec``, or used with ``new.function``/``types.FunctionType`` to create a |
|
function:: |
|
|
>>> eval(c.code()) |
>>> eval(c.code()) |
42 |
42 |
|
|
>>> exec c.code() # exec discards the return value, so no output here |
>>> exec(c.code()) # exec discards the return value, so no output here |
|
|
>>> import new |
>>> try: |
>>> f = new.function(c.code(), globals()) |
... from new import function |
|
... except ImportError: # Python 3 workarounds |
|
... from types import FunctionType as function |
|
... long = int |
|
... unicode = str |
|
|
|
>>> f = function(c.code(), globals()) |
>>> f() |
>>> f() |
42 |
42 |
|
|
This can be useful for testing or otherwise inspecting code you've generated. |
This can be useful for testing or otherwise inspecting code you've generated. |
|
|
|
|
|
Symbolic Disassembler |
|
===================== |
|
|
|
Python's built-in disassembler can be verbose and hard to read when inspecting |
|
complex generated code -- usually you don't care about bytecode offsets or |
|
line numbers as much as you care about labels, for example. |
|
|
|
So, BytecodeAssembler provides its own, simplified disassembler, which we'll |
|
be using for more complex listings in this manual:: |
|
|
|
>>> from peak.util.assembler import dump |
|
|
|
Some sample output, that also showcases some of BytecodeAssembler's |
|
`High-Level Code Generation`_ features:: |
|
|
|
>>> c = Code() |
|
>>> from peak.util.assembler import Compare, Local |
|
>>> c.return_(Compare(Local('a'), [('<', Local('b')), ('<', Local('c'))])) |
|
>>> dump(c.code()) |
|
LOAD_FAST 0 (a) |
|
LOAD_FAST 1 (b) |
|
DUP_TOP |
|
ROT_THREE |
|
COMPARE_OP 0 (<) |
|
JUMP_IF_FALSE L1 |
|
POP_TOP |
|
LOAD_FAST 2 (c) |
|
COMPARE_OP 0 (<) |
|
JUMP_FORWARD L2 |
|
L1: ROT_TWO |
|
POP_TOP |
|
L2: RETURN_VALUE |
|
|
|
As you can see, the line numbers and bytecode offsets have been dropped, |
|
making it esier to see where the jumps go. (This also makes doctests more |
|
robust against Python version changes, as ``dump()`` has some extra code to |
|
make conditional jumps appear consistent across the major changes that were |
|
made to conditional jump instructions between Python 2.6 and 2.7.) |
|
|
|
|
Opcodes and Arguments |
Opcodes and Arguments |
===================== |
===================== |
|
|
>>> c.RETURN_VALUE() |
>>> c.RETURN_VALUE() |
|
|
>>> eval(c.code()) # computes type(27) |
>>> eval(c.code()) # computes type(27) |
<type 'int'> |
<... 'int'> |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.LOAD_CONST(dict) |
>>> c.LOAD_CONST(dict) |
>>> c.POP_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()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 (42) |
LOAD_CONST 1 (42) |
>> 3 DUP_TOP |
L1: DUP_TOP |
4 POP_TOP |
POP_TOP |
5 JUMP_ABSOLUTE 3 |
JUMP_ABSOLUTE L1 |
|
|
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" |
>>> c.LOAD_CONST(23) |
>>> c.LOAD_CONST(23) |
>>> c.RETURN_VALUE() |
>>> c.RETURN_VALUE() |
|
|
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 (99) |
LOAD_CONST 1 (99) |
3 JUMP_IF_TRUE 4 (to 10) |
JUMP_IF_TRUE L1 |
6 LOAD_CONST 2 (42) |
LOAD_CONST 2 (42) |
9 POP_TOP |
POP_TOP |
>> 10 LOAD_CONST 3 (23) |
L1: LOAD_CONST 3 (23) |
13 RETURN_VALUE |
RETURN_VALUE |
|
|
>>> eval(c.code()) |
>>> eval(c.code()) |
23 |
23 |
>>> c = Code() |
>>> c = Code() |
>>> c.co_cellvars = ('a','b') |
>>> c.co_cellvars = ('a','b') |
|
|
|
>>> import sys |
>>> c.LOAD_CLOSURE('a') |
>>> c.LOAD_CLOSURE('a') |
>>> c.LOAD_CLOSURE('b') |
>>> c.LOAD_CLOSURE('b') |
|
>>> if sys.version>='2.5': |
|
... c.BUILD_TUPLE(2) # In Python 2.5+, free vars must be in a tuple |
>>> c.LOAD_CONST(None) # in real code, this'd be a Python code constant |
>>> c.LOAD_CONST(None) # in real code, this'd be a Python code constant |
>>> c.MAKE_CLOSURE(0,2) # no defaults, 2 free vars in the new function |
>>> c.MAKE_CLOSURE(0,2) # no defaults, 2 free vars in the new function |
|
|
|
>>> c.stack_size # This will be 1, no matter what Python version |
|
1 |
|
|
The ``COMPARE_OP`` method takes an argument which can be a valid comparison |
The ``COMPARE_OP`` method takes an argument which can be a valid comparison |
integer constant, or a string containing a Python operator, e.g.:: |
integer constant, or a string containing a Python operator, e.g.:: |
|
|
``None``, or Python code object, it is treated as though it was passed to |
``None``, or Python code object, it is treated as though it was passed to |
the ``LOAD_CONST`` method directly:: |
the ``LOAD_CONST`` method directly:: |
|
|
|
|
|
|
>>> c = Code() |
>>> c = Code() |
>>> c(1, 2L, 3.0, 4j+5, "6", u"7", False, None, c.code()) |
>>> c(1, long(2), 3.0, 4j+5, "6", unicode("7"), False, None, c.code()) |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 1 (1) |
0 0 LOAD_CONST 1 (1) |
3 LOAD_CONST 2 (2L) |
3 LOAD_CONST 2 (2...) |
6 LOAD_CONST 3 (3.0) |
6 LOAD_CONST 3 (3.0) |
9 LOAD_CONST 4 ((5+4j)) |
9 LOAD_CONST 4 ((5+4j)) |
12 LOAD_CONST 5 ('6') |
12 LOAD_CONST 5 ('6') |
15 LOAD_CONST 6 (u'7') |
15 LOAD_CONST 6 (...'7') |
18 LOAD_CONST 7 (False) |
18 LOAD_CONST 7 (False) |
21 LOAD_CONST 0 (None) |
21 LOAD_CONST 0 (None) |
24 LOAD_CONST 8 (<code object <lambda> at ...>) |
24 LOAD_CONST 8 (<code object <lambda>...>) |
|
|
Note that although some values of different types may compare equal to each |
Note that although some values of different types may compare equal to each |
other, ``Code`` objects will not substitute a value of a different type than |
other, ``Code`` objects will not substitute a value of a different type than |
the one you requested:: |
the one you requested:: |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c(1, True, 1.0, 1L) # equal, but different types |
>>> c(1, True, 1.0) # equal, but different types |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 1 (1) |
0 0 LOAD_CONST 1 (1) |
3 LOAD_CONST 2 (True) |
3 LOAD_CONST 2 (True) |
6 LOAD_CONST 3 (1.0) |
6 LOAD_CONST 3 (1.0) |
9 LOAD_CONST 4 (1L) |
|
|
|
Simple Containers |
Simple Containers |
----------------- |
----------------- |
|
|
|
|
>>> Getattr(Const(object), '__class__') # const expression, const result |
>>> Getattr(Const(object), '__class__') # const expression, const result |
Const(<type 'type'>) |
Const(<... 'type'>) |
|
|
Or the attribute name can be an expression, in which case a ``getattr()`` call |
Or the attribute name can be an expression, in which case a ``getattr()`` call |
is compiled instead:: |
is compiled instead:: |
>>> from peak.util.assembler import If |
>>> from peak.util.assembler import If |
>>> c = Code() |
>>> c = Code() |
>>> c( If(Local('a'), Return(42), Return(55)) ) |
>>> c( If(Local('a'), Return(42), Return(55)) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (a) |
LOAD_FAST 0 (a) |
3 JUMP_IF_FALSE 5 (to 11) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 1 (42) |
LOAD_CONST 1 (42) |
10 RETURN_VALUE |
RETURN_VALUE |
>> 11 POP_TOP |
L1: POP_TOP |
12 LOAD_CONST 2 (55) |
LOAD_CONST 2 (55) |
15 RETURN_VALUE |
RETURN_VALUE |
|
|
However, it can also be used like a Python 2.5+ conditional expression |
However, it can also be used like a Python 2.5+ conditional expression |
(regardless of the targeted Python version):: |
(regardless of the targeted Python version):: |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c( Return(If(Local('a'), 42, 55)) ) |
>>> c( Return(If(Local('a'), 42, 55)) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (a) |
LOAD_FAST 0 (a) |
3 JUMP_IF_FALSE 7 (to 13) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 1 (42) |
LOAD_CONST 1 (42) |
10 JUMP_FORWARD 4 (to 17) |
JUMP_FORWARD L2 |
>> 13 POP_TOP |
L1: POP_TOP |
14 LOAD_CONST 2 (55) |
LOAD_CONST 2 (55) |
>> 17 RETURN_VALUE |
L2: RETURN_VALUE |
|
|
|
|
Note that ``If()`` does *not* do constant-folding on its condition; even if the |
Note that ``If()`` does *not* do constant-folding on its condition; even if the |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c(If(Const([]), 42, 55)) |
>>> c(If(Const([]), 42, 55)) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 ([]) |
LOAD_CONST 1 ([]) |
3 JUMP_IF_FALSE 7 (to 13) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 2 (42) |
LOAD_CONST 2 (42) |
10 JUMP_FORWARD 4 (to 17) |
JUMP_FORWARD L2 |
>> 13 POP_TOP |
L1: POP_TOP |
14 LOAD_CONST 3 (55) |
LOAD_CONST 3 (55) |
|
|
|
|
Labels and Jump Targets |
Labels and Jump Targets |
>>> c.LOAD_CONST(99) |
>>> c.LOAD_CONST(99) |
>>> forward = c.JUMP_IF_FALSE() |
>>> forward = c.JUMP_IF_FALSE() |
>>> c( 1, Code.POP_TOP, forward, Return(3) ) |
>>> c( 1, Code.POP_TOP, forward, Return(3) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 (99) |
LOAD_CONST 1 (99) |
3 JUMP_IF_FALSE 4 (to 10) |
JUMP_IF_FALSE L1 |
6 LOAD_CONST 2 (1) |
LOAD_CONST 2 (1) |
9 POP_TOP |
POP_TOP |
>> 10 LOAD_CONST 3 (3) |
L1: LOAD_CONST 3 (3) |
13 RETURN_VALUE |
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:: |
|
|
>>> skip = Label() |
>>> skip = Label() |
|
|
>>> c(99, skip.JUMP_IF_FALSE, 1, Code.POP_TOP, skip, Return(3)) |
>>> c(99, skip.JUMP_IF_FALSE, 1, Code.POP_TOP, skip, Return(3)) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 (99) |
LOAD_CONST 1 (99) |
3 JUMP_IF_FALSE 4 (to 10) |
JUMP_IF_FALSE L1 |
6 LOAD_CONST 2 (1) |
LOAD_CONST 2 (1) |
9 POP_TOP |
POP_TOP |
>> 10 LOAD_CONST 3 (3) |
L1: LOAD_CONST 3 (3) |
13 RETURN_VALUE |
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 |
AssertionError: Label previously defined |
AssertionError: Label previously defined |
|
|
|
|
|
More Conditional Jump Instructions |
|
---------------------------------- |
|
|
|
In Python 2.7, the traditional ``JUMP_IF_TRUE`` and ``JUMP_IF_FALSE`` |
|
instructions were replaced with four new instructions that either conditionally |
|
or unconditionally pop the value being tested. This was done to improve |
|
performance, since virtually all conditional jumps in Python code pop the |
|
value on one branch or the other. |
|
|
|
To provide better cross-version compatibility, BytecodeAssembler emulates the |
|
old instructions on Python 2.7 by emitting a ``DUP_TOP`` followed by a |
|
``POP_JUMP_IF_FALSE`` or ``POP_JUMP_IF_TRUE`` instruction. |
|
|
|
However, since this decreases performance, BytecodeAssembler *also* emulates |
|
Python 2.7's ``JUMP_IF_FALSE_OR_POP`` and ``JUMP_IF_FALSE_OR_TRUE`` opcodes |
|
on *older* Pythons:: |
|
|
|
>>> c = Code() |
|
>>> l1, l2 = Label(), Label() |
|
>>> c(Local('a'), l1.JUMP_IF_FALSE_OR_POP, Return(27), l1) |
|
>>> c(l2.JUMP_IF_TRUE_OR_POP, Return(42), l2, Code.RETURN_VALUE) |
|
>>> dump(c.code()) |
|
LOAD_FAST 0 (a) |
|
JUMP_IF_FALSE L1 |
|
POP_TOP |
|
LOAD_CONST 1 (27) |
|
RETURN_VALUE |
|
L1: JUMP_IF_TRUE L2 |
|
POP_TOP |
|
LOAD_CONST 2 (42) |
|
RETURN_VALUE |
|
L2: RETURN_VALUE |
|
|
|
This means that you can immediately begin using the "or-pop" variations, in |
|
place of a jump followed by a pop, and BytecodeAssembler will use the faster |
|
single instruction automatically on Python 2.7+. |
|
|
|
BytecodeAssembler *also* supports using Python 2.7's conditional jumps |
|
that do unconditional pops, but currently cannot emulate them on older Python |
|
versions, so at the moment you should use them only when your code requires |
|
Python 2.7. |
|
|
|
(Note: for ease in doctesting across Python versions, the ``dump()`` function |
|
*always* shows the code as if it were generated for Python 2.6 or lower, so |
|
if you need to check the *actual* bytecodes generated, you must use Python's |
|
``dis.dis()`` function instead!) |
|
|
|
|
N-Way Comparisons |
N-Way Comparisons |
----------------- |
----------------- |
|
|
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.return_(Compare(Local('a'), [('<', Local('b')), ('<', Local('c'))])) |
>>> c.return_(Compare(Local('a'), [('<', Local('b')), ('<', Local('c'))])) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (a) |
LOAD_FAST 0 (a) |
3 LOAD_FAST 1 (b) |
LOAD_FAST 1 (b) |
6 DUP_TOP |
DUP_TOP |
7 ROT_THREE |
ROT_THREE |
8 COMPARE_OP 0 (<) |
COMPARE_OP 0 (<) |
11 JUMP_IF_FALSE 10 (to 24) |
JUMP_IF_FALSE L1 |
14 POP_TOP |
POP_TOP |
15 LOAD_FAST 2 (c) |
LOAD_FAST 2 (c) |
18 COMPARE_OP 0 (<) |
COMPARE_OP 0 (<) |
21 JUMP_FORWARD 2 (to 26) |
JUMP_FORWARD L2 |
>> 24 ROT_TWO |
L1: ROT_TWO |
25 POP_TOP |
POP_TOP |
>> 26 RETURN_VALUE |
L2: RETURN_VALUE |
|
|
And a four-way (``a<b>c!=d``):: |
And a four-way (``a<b>c!=d``):: |
|
|
... ('<', Local('b')), ('>', Local('c')), ('!=', Local('d')) |
... ('<', Local('b')), ('>', Local('c')), ('!=', Local('d')) |
... ]) |
... ]) |
... ) |
... ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (a) |
LOAD_FAST 0 (a) |
3 LOAD_FAST 1 (b) |
LOAD_FAST 1 (b) |
6 DUP_TOP |
DUP_TOP |
7 ROT_THREE |
ROT_THREE |
8 COMPARE_OP 0 (<) |
COMPARE_OP 0 (<) |
11 JUMP_IF_FALSE 22 (to 36) |
JUMP_IF_FALSE L1 |
14 POP_TOP |
POP_TOP |
15 LOAD_FAST 2 (c) |
LOAD_FAST 2 (c) |
18 DUP_TOP |
DUP_TOP |
19 ROT_THREE |
ROT_THREE |
20 COMPARE_OP 4 (>) |
COMPARE_OP 4 (>) |
23 JUMP_IF_FALSE 10 (to 36) |
JUMP_IF_FALSE L1 |
26 POP_TOP |
POP_TOP |
27 LOAD_FAST 3 (d) |
LOAD_FAST 3 (d) |
30 COMPARE_OP 3 (!=) |
COMPARE_OP 3 (!=) |
33 JUMP_FORWARD 2 (to 38) |
JUMP_FORWARD L2 |
>> 36 ROT_TWO |
L1: ROT_TWO |
37 POP_TOP |
POP_TOP |
>> 38 RETURN_VALUE |
L2: RETURN_VALUE |
|
|
|
|
Sequence Unpacking |
Sequence Unpacking |
|
|
>>> from peak.util.assembler import const_value |
>>> from peak.util.assembler import const_value |
|
|
>>> simple_values = [1, 2L, 3.0, 4j+5, "6", u"7", False, None, c.code()] |
>>> simple_values = [1, long(2), 3.0, 4j+5, "6", unicode("7"), False, None, c.code()] |
|
|
>>> map(const_value, simple_values) |
>>> list(map(const_value, simple_values)) |
[1, 2L, 3.0, (5+4j), '6', u'7', False, None, <code object <lambda> ...>] |
[1, 2..., 3.0, (5+4j), '6', ...'7', False, None, <code object <lambda>...>] |
|
|
Values wrapped in a ``Const()`` are also returned as-is:: |
Values wrapped in a ``Const()`` are also returned as-is:: |
|
|
>>> map(const_value, map(Const, simple_values)) |
>>> list(map(const_value, map(Const, simple_values))) |
[1, 2L, 3.0, (5+4j), '6', u'7', False, None, <code object <lambda> ...>] |
[1, 2..., 3.0, (5+4j), '6', ...'7', False, None, <code object <lambda>...>] |
|
|
But no other node types produce constant values; instead, ``NotAConstant`` is |
But no other node types produce constant values; instead, ``NotAConstant`` is |
raised:: |
raised:: |
``Const`` node instead of a ``Call`` node:: |
``Const`` node instead of a ``Call`` node:: |
|
|
>>> Call( Const(type), [1] ) |
>>> Call( Const(type), [1] ) |
Const(<type 'int'>) |
Const(<... 'int'>) |
|
|
Thus, you can also take the ``const_value()`` of such calls:: |
Thus, you can also take the ``const_value()`` of such calls:: |
|
|
passed in to another ``Call``:: |
passed in to another ``Call``:: |
|
|
>>> Call(Const(type), [Call( Const(dict), [], [('x',27)] )]) |
>>> Call(Const(type), [Call( Const(dict), [], [('x',27)] )]) |
Const(<type 'dict'>) |
Const(<... 'dict'>) |
|
|
Notice that this folding takes place eagerly, during AST construction. If you |
Notice that this folding takes place eagerly, during AST construction. If you |
want to implement delayed folding after constant propagation or variable |
want to implement delayed folding after constant propagation or variable |
>>> c = Code() |
>>> c = Code() |
>>> c( Call(Const(type), [1]) ) |
>>> c( Call(Const(type), [1]) ) |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 1 (<type 'int'>) |
0 0 LOAD_CONST 1 (<... 'int'>) |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c( Call(Const(type), [1], fold=False) ) |
>>> c( Call(Const(type), [1], fold=False) ) |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 1 (<type 'type'>) |
0 0 LOAD_CONST 1 (<... 'type'>) |
3 LOAD_CONST 2 (1) |
3 LOAD_CONST 2 (1) |
6 CALL_FUNCTION 1 |
6 CALL_FUNCTION 1 |
|
|
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.return_( And([Local('x'), Local('y')]) ) |
>>> c.return_( And([Local('x'), Local('y')]) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (x) |
LOAD_FAST 0 (x) |
3 JUMP_IF_FALSE 4 (to 10) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_FAST 1 (y) |
LOAD_FAST 1 (y) |
>> 10 RETURN_VALUE |
L1: RETURN_VALUE |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.return_( Or([Local('x'), Local('y')]) ) |
>>> c.return_( Or([Local('x'), Local('y')]) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (x) |
LOAD_FAST 0 (x) |
3 JUMP_IF_TRUE 4 (to 10) |
JUMP_IF_TRUE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_FAST 1 (y) |
LOAD_FAST 1 (y) |
>> 10 RETURN_VALUE |
L1: RETURN_VALUE |
|
|
|
|
True or false constants are folded automatically, avoiding code generation |
True or false constants are folded automatically, avoiding code generation |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.return_( And([1, 2, Local('y'), 0]) ) |
>>> c.return_( And([1, 2, Local('y'), 0]) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (y) |
LOAD_FAST 0 (y) |
3 JUMP_IF_FALSE 4 (to 10) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 1 (0) |
LOAD_CONST 1 (0) |
>> 10 RETURN_VALUE |
L1: RETURN_VALUE |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.return_( Or([1, 2, Local('y')]) ) |
>>> c.return_( Or([1, 2, Local('y')]) ) |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.return_( Or([False, Local('y'), 3]) ) |
>>> c.return_( Or([False, Local('y'), 3]) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (y) |
LOAD_FAST 0 (y) |
3 JUMP_IF_TRUE 4 (to 10) |
JUMP_IF_TRUE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 1 (3) |
LOAD_CONST 1 (3) |
>> 10 RETURN_VALUE |
L1: RETURN_VALUE |
|
|
|
|
Custom Code Generation |
Custom Code Generation |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c( TryFinally(ExprStmt(1), ExprStmt(2)) ) |
>>> c( TryFinally(ExprStmt(1), ExprStmt(2)) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_FINALLY 8 (to 11) |
SETUP_FINALLY L1 |
3 LOAD_CONST 1 (1) |
LOAD_CONST 1 (1) |
6 POP_TOP |
POP_TOP |
7 POP_BLOCK |
POP_BLOCK |
8 LOAD_CONST 0 (None) |
LOAD_CONST 0 (None) |
>> 11 LOAD_CONST 2 (2) |
L1: LOAD_CONST 2 (2) |
14 POP_TOP |
POP_TOP |
15 END_FINALLY |
END_FINALLY |
|
|
The ``nodetype()`` decorator is virtually identical to the ``struct()`` |
The ``nodetype()`` decorator is virtually identical to the ``struct()`` |
decorator in the DecoratorTools package, except that it does not support |
decorator in the DecoratorTools package, except that it does not support |
... if const_value(value): |
... if const_value(value): |
... continue # true constants can be skipped |
... continue # true constants can be skipped |
... except NotAConstant: # but non-constants require code |
... except NotAConstant: # but non-constants require code |
... code(value, end.JUMP_IF_FALSE, Code.POP_TOP) |
... code(value, end.JUMP_IF_FALSE_OR_POP) |
... else: # and false constants end the chain right away |
... else: # and false constants end the chain right away |
... return code(value, end) |
... return code(value, end) |
... code(values[-1], end) |
... code(values[-1], end) |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.return_( And([Local('x'), False, 27]) ) |
>>> c.return_( And([Local('x'), False, 27]) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (x) |
LOAD_FAST 0 (x) |
3 JUMP_IF_FALSE 4 (to 10) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 1 (False) |
LOAD_CONST 1 (False) |
>> 10 RETURN_VALUE |
L1: RETURN_VALUE |
|
|
The above example only folds constants at code generation time, however. You |
The above example only folds constants at code generation time, however. You |
can also do constant folding at AST construction time, using the |
can also do constant folding at AST construction time, using the |
>>> Getattr = nodetype()(Getattr) |
>>> Getattr = nodetype()(Getattr) |
|
|
>>> const_value(Getattr(1, '__class__')) |
>>> const_value(Getattr(1, '__class__')) |
<type 'int'> |
<... 'int'> |
|
|
The ``fold_args()`` function tries to evaluate the node immediately, if all of |
The ``fold_args()`` function tries to evaluate the node immediately, if all of |
its arguments are constants, by creating a temporary ``Code`` object, and |
its arguments are constants, by creating a temporary ``Code`` object, and |
... pass |
... pass |
|
|
>>> c = Code.from_function(f1) |
>>> c = Code.from_function(f1) |
>>> f2 = new.function(c.code(), globals()) |
>>> f2 = function(c.code(), globals()) |
|
|
>>> import inspect |
>>> import inspect |
|
|
>>> inspect.getargspec(f1) |
>>> tuple(inspect.getargspec(f1)) |
(['a', 'b'], 'c', 'd', None) |
(['a', 'b'], 'c', 'd', None) |
|
|
>>> inspect.getargspec(f2) |
>>> tuple(inspect.getargspec(f2)) |
(['a', 'b'], 'c', 'd', None) |
(['a', 'b'], 'c', 'd', None) |
|
|
Note that these constructors do not copy any actual *code* from the code |
Note that these constructors do not copy any actual *code* from the code |
>>> def f3(a, (b,c), (d,(e,f))): |
>>> def f3(a, (b,c), (d,(e,f))): |
... pass |
... pass |
|
|
>>> f4 = new.function(Code.from_function(f3).code(), globals()) |
>>> f4 = function(Code.from_function(f3).code(), globals()) |
>>> dis(f4) |
>>> dis(f4) |
0 0 LOAD_FAST 1 (.1) |
0 0 LOAD_FAST 1 (.1) |
3 UNPACK_SEQUENCE 2 |
3 UNPACK_SEQUENCE 2 |
unpacking process, and is designed so that the ``inspect`` module will |
unpacking process, and is designed so that the ``inspect`` module will |
recognize it as an argument unpacking prologue:: |
recognize it as an argument unpacking prologue:: |
|
|
>>> inspect.getargspec(f3) |
>>> tuple(inspect.getargspec(f3)) |
(['a', ['b', 'c'], ['d', ['e', 'f']]], None, None, None) |
(['a', ['b', 'c'], ['d', ['e', 'f']]], None, None, None) |
|
|
>>> inspect.getargspec(f4) |
>>> tuple(inspect.getargspec(f4)) |
(['a', ['b', 'c'], ['d', ['e', 'f']]], None, None, None) |
(['a', ['b', 'c'], ['d', ['e', 'f']]], None, None, None) |
|
|
You can also use the ``from_spec(name='<lambda>', args=(), var=None, kw=None)`` |
You can also use the ``from_spec(name='<lambda>', args=(), var=None, kw=None)`` |
>>> c.co_argcount |
>>> c.co_argcount |
3 |
3 |
|
|
>>> inspect.getargs(c.code()) |
>>> tuple(inspect.getargs(c.code())) |
(['b', ['c', 'd'], 'e'], 'f', 'g') |
(['b', ['c', 'd'], 'e'], 'f', 'g') |
|
|
|
|
>>> c.LOAD_CONST(42) |
>>> c.LOAD_CONST(42) |
>>> c.RETURN_VALUE() |
>>> c.RETURN_VALUE() |
|
|
>>> f = new.function(c.code(), globals()) |
>>> f = function(c.code(), globals()) |
>>> f(1,2,3) |
>>> f(1,2,3) |
42 |
42 |
|
|
>>> import inspect |
>>> import inspect |
>>> inspect.getargspec(f) |
>>> tuple(inspect.getargspec(f)) |
(['a', 'b', 'c'], None, None, None) |
(['a', 'b', 'c'], None, None, None) |
|
|
Although Python code objects want ``co_varnames`` to be a tuple, ``Code`` |
Although Python code objects want ``co_varnames`` to be a tuple, ``Code`` |
|
|
>>> c = Code() |
>>> c = Code() |
>>> fwd = c.JUMP_FORWARD() |
>>> fwd = c.JUMP_FORWARD() |
>>> print c.stack_size # forward jump marks stack size as unknown |
>>> print(c.stack_size) # forward jump marks stack size as unknown |
None |
None |
|
|
>>> c.LOAD_CONST(42) |
>>> c.LOAD_CONST(42) |
... return cond, then, else_ |
... return cond, then, else_ |
... else_clause = Label() |
... else_clause = Label() |
... end_if = Label() |
... end_if = Label() |
... code(cond, else_clause.JUMP_IF_FALSE, Code.POP_TOP, then) |
... code(cond, else_clause.JUMP_IF_FALSE_OR_POP, then) |
... code(end_if.JUMP_FORWARD, else_clause, Code.POP_TOP, else_) |
... code(end_if.JUMP_FORWARD, else_clause, Code.POP_TOP, else_) |
... code(end_if) |
... code(end_if) |
>>> If = nodetype()(If) |
>>> If = nodetype()(If) |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c( If(Local('a'), 42, 55) ) |
>>> c( If(Local('a'), 42, 55) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (a) |
LOAD_FAST 0 (a) |
3 JUMP_IF_FALSE 7 (to 13) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 1 (42) |
LOAD_CONST 1 (42) |
10 JUMP_FORWARD 4 (to 17) |
JUMP_FORWARD L2 |
>> 13 POP_TOP |
L1: POP_TOP |
14 LOAD_CONST 2 (55) |
LOAD_CONST 2 (55) |
|
|
But it breaks if you end the "then" block with a return:: |
But it breaks if you end the "then" block with a return:: |
|
|
... return cond, then, else_ |
... return cond, then, else_ |
... else_clause = Label() |
... else_clause = Label() |
... end_if = Label() |
... end_if = Label() |
... code(cond, else_clause.JUMP_IF_FALSE, Code.POP_TOP, then) |
... code(cond, else_clause.JUMP_IF_FALSE_OR_POP, then) |
... if code.stack_size is not None: |
... if code.stack_size is not None: |
... end_if.JUMP_FORWARD(code) |
... end_if.JUMP_FORWARD(code) |
... code(else_clause, Code.POP_TOP, else_, end_if) |
... code(else_clause, Code.POP_TOP, else_, end_if) |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c( If(Local('a'), Return(42), 55) ) |
>>> c( If(Local('a'), Return(42), 55) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (a) |
LOAD_FAST 0 (a) |
3 JUMP_IF_FALSE 5 (to 11) |
JUMP_IF_FALSE L1 |
6 POP_TOP |
POP_TOP |
7 LOAD_CONST 1 (42) |
LOAD_CONST 1 (42) |
10 RETURN_VALUE |
RETURN_VALUE |
>> 11 POP_TOP |
L1: POP_TOP |
12 LOAD_CONST 2 (55) |
LOAD_CONST 2 (55) |
|
|
|
|
Blocks, Loops, and Exception Handling |
Blocks, Loops, and Exception Handling |
>>> c.POP_TOP() |
>>> c.POP_TOP() |
>>> else_() |
>>> else_() |
>>> c.return_() |
>>> c.return_() |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_EXCEPT 4 (to 7) |
SETUP_EXCEPT L1 |
3 POP_BLOCK |
POP_BLOCK |
4 JUMP_FORWARD 3 (to 10) |
JUMP_FORWARD L2 |
>> 7 POP_TOP |
L1: POP_TOP |
8 POP_TOP |
POP_TOP |
9 POP_TOP |
POP_TOP |
>> 10 LOAD_CONST 0 (None) |
L2: LOAD_CONST 0 (None) |
13 RETURN_VALUE |
RETURN_VALUE |
|
|
In the example above, an empty block executes with an exception handler that |
In the example above, an empty block executes with an exception handler that |
begins at offset 7. When the block is done, it jumps forward to the end of |
begins at offset 7. When the block is done, it jumps forward to the end of |
... Return() |
... Return() |
... ) |
... ) |
|
|
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_EXCEPT 4 (to 7) |
SETUP_EXCEPT L1 |
3 POP_BLOCK |
POP_BLOCK |
4 JUMP_FORWARD 3 (to 10) |
JUMP_FORWARD L2 |
>> 7 POP_TOP |
L1: POP_TOP |
8 POP_TOP |
POP_TOP |
9 POP_TOP |
POP_TOP |
>> 10 LOAD_CONST 0 (None) |
L2: LOAD_CONST 0 (None) |
13 RETURN_VALUE |
RETURN_VALUE |
|
|
(Labels have a ``POP_BLOCK`` attribute that you can pass in when generating |
(Labels have a ``POP_BLOCK`` attribute that you can pass in when generating |
code.) |
code.) |
... ) |
... ) |
... ) |
... ) |
|
|
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_EXCEPT 8 (to 11) |
SETUP_EXCEPT L1 |
3 LOAD_CONST 1 (1) |
LOAD_CONST 1 (1) |
6 RETURN_VALUE |
RETURN_VALUE |
7 POP_BLOCK |
POP_BLOCK |
8 JUMP_FORWARD 43 (to 54) |
JUMP_FORWARD L4 |
>> 11 DUP_TOP |
L1: DUP_TOP |
12 LOAD_CONST 2 (<...exceptions.KeyError...>) |
LOAD_CONST 2 (<...KeyError...>) |
15 COMPARE_OP 10 (exception match) |
COMPARE_OP 10 (exception match) |
18 JUMP_IF_FALSE 10 (to 31) |
JUMP_IF_FALSE L2 |
21 POP_TOP |
POP_TOP |
22 POP_TOP |
POP_TOP |
23 POP_TOP |
POP_TOP |
24 POP_TOP |
POP_TOP |
25 LOAD_CONST 3 (2) |
LOAD_CONST 3 (2) |
28 JUMP_FORWARD 27 (to 58) |
JUMP_FORWARD L5 |
>> 31 POP_TOP |
L2: POP_TOP |
32 DUP_TOP |
DUP_TOP |
33 LOAD_CONST 4 (<...exceptions.TypeError...>) |
LOAD_CONST 4 (<...TypeError...>) |
36 COMPARE_OP 10 (exception match) |
COMPARE_OP 10 (exception match) |
39 JUMP_IF_FALSE 10 (to 52) |
JUMP_IF_FALSE L3 |
42 POP_TOP |
POP_TOP |
43 POP_TOP |
POP_TOP |
44 POP_TOP |
POP_TOP |
45 POP_TOP |
POP_TOP |
46 LOAD_CONST 5 (3) |
LOAD_CONST 5 (3) |
49 JUMP_FORWARD 6 (to 58) |
JUMP_FORWARD L5 |
>> 52 POP_TOP |
L3: POP_TOP |
53 END_FINALLY |
END_FINALLY |
>> 54 LOAD_CONST 6 (4) |
L4: LOAD_CONST 6 (4) |
57 RETURN_VALUE |
RETURN_VALUE |
>> 58 RETURN_VALUE |
L5: RETURN_VALUE |
|
|
|
|
Try/Finally Blocks |
Try/Finally Blocks |
|
|
And it produces code that looks like this:: |
And it produces code that looks like this:: |
|
|
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_FINALLY 4 (to 7) |
SETUP_FINALLY L1 |
3 POP_BLOCK |
POP_BLOCK |
4 LOAD_CONST 0 (None) |
LOAD_CONST 0 (None) |
>> 7 END_FINALLY |
L1: END_FINALLY |
|
|
The ``END_FINALLY`` opcode will remove 1, 2, or 3 values from the stack at |
The ``END_FINALLY`` opcode will remove 1, 2, or 3 values from the stack at |
runtime, depending on how the "try" block was exited. In the case of simply |
runtime, depending on how the "try" block was exited. In the case of simply |
>>> from peak.util.assembler import TryFinally |
>>> from peak.util.assembler import TryFinally |
>>> c = Code() |
>>> c = Code() |
>>> c( TryFinally(ExprStmt(1), ExprStmt(2)) ) |
>>> c( TryFinally(ExprStmt(1), ExprStmt(2)) ) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_FINALLY 8 (to 11) |
SETUP_FINALLY L1 |
3 LOAD_CONST 1 (1) |
LOAD_CONST 1 (1) |
6 POP_TOP |
POP_TOP |
7 POP_BLOCK |
POP_BLOCK |
8 LOAD_CONST 0 (None) |
LOAD_CONST 0 (None) |
>> 11 LOAD_CONST 2 (2) |
L1: LOAD_CONST 2 (2) |
14 POP_TOP |
POP_TOP |
15 END_FINALLY |
END_FINALLY |
|
|
|
|
Loops |
Loops |
... Return() |
... Return() |
... ) |
... ) |
|
|
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_LOOP 19 (to 22) |
SETUP_LOOP L3 |
3 LOAD_CONST 1 (5) |
LOAD_CONST 1 (5) |
>> 6 JUMP_IF_FALSE 7 (to 16) |
L1: JUMP_IF_FALSE L2 |
9 LOAD_CONST 2 (1) |
LOAD_CONST 2 (1) |
12 BINARY_SUBTRACT |
BINARY_SUBTRACT |
13 JUMP_ABSOLUTE 6 |
JUMP_ABSOLUTE L1 |
>> 16 POP_TOP |
L2: POP_TOP |
17 POP_BLOCK |
POP_BLOCK |
18 LOAD_CONST 3 (42) |
LOAD_CONST 3 (42) |
21 RETURN_VALUE |
RETURN_VALUE |
>> 22 LOAD_CONST 0 (None) |
L3: LOAD_CONST 0 (None) |
25 RETURN_VALUE |
RETURN_VALUE |
|
|
>>> eval(c.code()) |
>>> eval(c.code()) |
42 |
42 |
>>> fwd() |
>>> fwd() |
>>> c.BREAK_LOOP() |
>>> c.BREAK_LOOP() |
>>> c.POP_BLOCK()() |
>>> c.POP_BLOCK()() |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 (57) |
LOAD_CONST 1 (57) |
3 SETUP_LOOP 8 (to 14) |
SETUP_LOOP L3 |
6 JUMP_IF_TRUE 3 (to 12) |
JUMP_IF_TRUE L2 |
>> 9 JUMP_ABSOLUTE 9 |
L1: JUMP_ABSOLUTE L1 |
>> 12 BREAK_LOOP |
L2: BREAK_LOOP |
13 POP_BLOCK |
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.POP_BLOCK() |
>>> c.POP_BLOCK() |
>>> c.END_FINALLY() |
>>> c.END_FINALLY() |
>>> c.POP_BLOCK()() |
>>> c.POP_BLOCK()() |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 (57) |
LOAD_CONST 1 (57) |
3 SETUP_LOOP 15 (to 21) |
SETUP_LOOP L4 |
>> 6 SETUP_FINALLY 10 (to 19) |
L1: SETUP_FINALLY L3 |
9 JUMP_IF_TRUE 3 (to 15) |
JUMP_IF_TRUE L2 |
12 CONTINUE_LOOP 6 |
CONTINUE_LOOP L1 |
>> 15 POP_BLOCK |
L2: POP_BLOCK |
16 LOAD_CONST 0 (None) |
LOAD_CONST 0 (None) |
>> 19 END_FINALLY |
L3: END_FINALLY |
20 POP_BLOCK |
POP_BLOCK |
|
|
``for`` Loops |
``for`` Loops |
------------- |
------------- |
clause, and a loop body:: |
clause, and a loop body:: |
|
|
>>> from peak.util.assembler import For |
>>> from peak.util.assembler import For |
>>> y = Call(Const(range), (3,)) |
>>> y = Call(Const(list), (Call(Const(range), (3,)),)) |
>>> x = LocalAssign('x') |
>>> x = LocalAssign('x') |
>>> body = Suite([Local('x'), Code.PRINT_EXPR]) |
>>> body = Suite([Local('x'), Code.PRINT_EXPR]) |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c(For(y, x, body)) # for x in range(3): print x |
>>> c(For(y, x, body)) # for x in range(3): print x |
>>> c.return_() |
>>> c.return_() |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 ([0, 1, 2]) |
LOAD_CONST 1 ([0, 1, 2]) |
3 GET_ITER |
GET_ITER |
>> 4 FOR_ITER 10 (to 17) |
L1: FOR_ITER L2 |
7 STORE_FAST 0 (x) |
STORE_FAST 0 (x) |
10 LOAD_FAST 0 (x) |
LOAD_FAST 0 (x) |
13 PRINT_EXPR |
PRINT_EXPR |
14 JUMP_ABSOLUTE 4 |
JUMP_ABSOLUTE L1 |
>> 17 LOAD_CONST 0 (None) |
L2: LOAD_CONST 0 (None) |
20 RETURN_VALUE |
RETURN_VALUE |
|
|
The arguments are given in execution order: first the "in" value of the loop, |
The arguments are given in execution order: first the "in" value of the loop, |
then the assignment to a loop variable, and finally the body of the loop. The |
then the assignment to a loop variable, and finally the body of the loop. The |
>>> c = Code() |
>>> c = Code() |
>>> c(For(y, Code.PRINT_EXPR)) |
>>> c(For(y, Code.PRINT_EXPR)) |
>>> c.return_() |
>>> c.return_() |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 ([0, 1, 2]) |
LOAD_CONST 1 ([0, 1, 2]) |
3 GET_ITER |
GET_ITER |
>> 4 FOR_ITER 4 (to 11) |
L1: FOR_ITER L2 |
7 PRINT_EXPR |
PRINT_EXPR |
8 JUMP_ABSOLUTE 4 |
JUMP_ABSOLUTE L1 |
>> 11 LOAD_CONST 0 (None) |
L2: LOAD_CONST 0 (None) |
14 RETURN_VALUE |
RETURN_VALUE |
|
|
Notice, by the way, that ``For()`` does NOT set up a loop block for you, so if |
Notice, by the way, that ``For()`` does NOT set up a loop block for you, so if |
you want to be able to use break and continue, you'll need to wrap the loop in |
you want to be able to use break and continue, you'll need to wrap the loop in |
>>> c.co_filename |
>>> c.co_filename |
'testname' |
'testname' |
|
|
>>> inspect.getargs(c.code(p)) |
>>> tuple(inspect.getargs(c.code(p))) |
(['a', 'b'], 'c', 'd') |
(['a', 'b'], 'c', 'd') |
|
|
Notice that you must pass the parent code object to the child's ``.code()`` |
Notice that you must pass the parent code object to the child's ``.code()`` |
>>> c.return_(Function(Return(Local('a')), 'f', ['a'], defaults=[42])) |
>>> c.return_(Function(Return(Local('a')), 'f', ['a'], defaults=[42])) |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 1 (42) |
0 0 LOAD_CONST 1 (42) |
3 LOAD_CONST 2 (<... f ..., file "<string>", line -1>) |
3 LOAD_CONST 2 (<... f..., file ...<string>..., line ...>) |
6 MAKE_FUNCTION 1 |
6 MAKE_FUNCTION 1 |
9 RETURN_VALUE |
9 RETURN_VALUE |
|
|
>>> f |
>>> f |
<function f at ...> |
<function f at ...> |
|
|
>>> inspect.getargspec(f) |
>>> tuple(inspect.getargspec(f)) |
(['a'], None, None, (42,)) |
(['a'], None, None, (42,)) |
|
|
>>> f() |
>>> f() |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 1 (99) |
0 0 LOAD_CONST 1 (99) |
3 LOAD_CONST 2 (66) |
3 LOAD_CONST 2 (66) |
6 LOAD_CONST 3 (<... f ..., file "<string>", line -1>) |
6 LOAD_CONST 3 (<... f..., file ...<string>..., line ...>) |
9 MAKE_FUNCTION 2 |
9 MAKE_FUNCTION 2 |
12 RETURN_VALUE |
12 RETURN_VALUE |
|
|
>>> f |
>>> f |
<function f at ...> |
<function f at ...> |
|
|
>>> inspect.getargspec(f) |
>>> tuple(inspect.getargspec(f)) |
(['a', 'b'], 'c', 'd', (99, 66)) |
(['a', 'b'], 'c', 'd', (99, 66)) |
|
|
>>> dis(f) |
>>> dis(f) |
0 0 LOAD_CLOSURE 0 (a) |
0 0 LOAD_CLOSURE 0 (a) |
3 BUILD_TUPLE 1 |
... LOAD_CONST 1 (<... <lambda>..., file ...<string>..., line ...>) |
6 LOAD_CONST 1 (<... <lambda> ..., file "<string>", line -1>) |
... MAKE_CLOSURE 0 |
9 MAKE_CLOSURE 0 |
... RETURN_VALUE |
12 RETURN_VALUE |
|
|
|
>>> dis(f()) |
>>> dis(f()) |
0 0 LOAD_DEREF 0 (a) |
0 0 LOAD_DEREF 0 (a) |
>>> c = Code() |
>>> c = Code() |
>>> else_ = Label() |
>>> else_ = Label() |
>>> end = Label() |
>>> end = Label() |
>>> c(99, else_.JUMP_IF_TRUE, Code.POP_TOP, end.JUMP_FORWARD) |
>>> c(99, else_.JUMP_IF_TRUE_OR_POP, end.JUMP_FORWARD) |
>>> c(else_, Code.POP_TOP, end) |
>>> c(else_, Code.POP_TOP, end) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_CONST 1 (99) |
LOAD_CONST 1 (99) |
3 JUMP_IF_TRUE 4 (to 10) |
JUMP_IF_TRUE L1 |
6 POP_TOP |
POP_TOP |
7 JUMP_FORWARD 1 (to 11) |
JUMP_FORWARD L2 |
>> 10 POP_TOP |
L1: POP_TOP |
|
|
>>> c.stack_size |
>>> c.stack_size |
0 |
0 |
>>> c.stack_history |
>>> if sys.version>='2.7': |
[0, 1, 1, 1, 1, 1, 1, 0, None, None, 1] |
... print(c.stack_history == [0, 1, 1, 1, 0, 0, 0, None, None, 1]) |
|
... else: |
|
... print(c.stack_history == [0, 1, 1, 1, 1, 1, 1, 0, None, None, 1]) |
|
True |
|
|
|
|
>>> c = Code() |
>>> c = Code() |
>>> fwd = c.JUMP_FORWARD() |
>>> fwd = c.JUMP_FORWARD() |
>>> c = Code() |
>>> c = Code() |
>>> c(For((), Code.POP_TOP, Pass)) |
>>> c(For((), Code.POP_TOP, Pass)) |
>>> c.return_() |
>>> c.return_() |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 BUILD_TUPLE 0 |
BUILD_TUPLE 0 |
3 GET_ITER |
GET_ITER |
>> 4 FOR_ITER 4 (to 11) |
L1: FOR_ITER L2 |
7 POP_TOP |
POP_TOP |
8 JUMP_ABSOLUTE 4 |
JUMP_ABSOLUTE L1 |
>> 11 LOAD_CONST 0 (None) |
L2: LOAD_CONST 0 (None) |
14 RETURN_VALUE |
RETURN_VALUE |
|
|
>>> c.stack_history |
>>> c.stack_history |
[0, 1, 1, 1, 1, 2, 2, 2, 1, None, None, 0, 1, 1, 1] |
[0, 1, 1, 1, 1, 2, 2, 2, 1, None, None, 0, 1, 1, 1] |
... |
... |
AssertionError: Stack underflow |
AssertionError: Stack underflow |
|
|
>>> c.LOAD_CONST(1) |
>>> c = Code() |
>>> c.LOAD_CONST(2) # simulate being a function |
>>> c.LOAD_CONST(1) # closure |
>>> c.MAKE_CLOSURE(1, 0) |
>>> if sys.version>='2.5': c.BUILD_TUPLE(1) |
|
>>> c.LOAD_CONST(2) # default |
|
>>> c.LOAD_CONST(3) # simulate being a function |
|
>>> c.MAKE_CLOSURE(1, 1) |
>>> c.stack_size |
>>> c.stack_size |
1 |
1 |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.LOAD_CONST(1) |
>>> c.LOAD_CONST(1) |
>>> c.LOAD_CONST(2) |
>>> c.LOAD_CONST(2) |
|
>>> if sys.version>='2.5': c.BUILD_TUPLE(2) |
>>> c.LOAD_CONST(3) # simulate being a function |
>>> c.LOAD_CONST(3) # simulate being a function |
>>> c.MAKE_CLOSURE(1, 1) |
>>> c.MAKE_CLOSURE(0, 2) |
>>> c.stack_size |
>>> c.stack_size |
1 |
1 |
|
|
|
|
|
|
Labels and backpatching forward references:: |
Labels and backpatching forward references:: |
|
|
>>> c = Code() |
>>> c = Code() |
>>> where = c.here() |
>>> where = c.here() |
>>> c.LOAD_CONST(1) |
>>> c.LOAD_CONST(1) |
>>> c.JUMP_IF_TRUE(where) |
>>> c.JUMP_FORWARD(where) |
Traceback (most recent call last): |
Traceback (most recent call last): |
... |
... |
AssertionError: Relative jumps can't go backwards |
AssertionError: Relative jumps can't go backwards |
>>> c = Code() |
>>> c = Code() |
>>> c.return_(Call(Const(type), [], [], (1,))) |
>>> c.return_(Call(Const(type), [], [], (1,))) |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 1 (<type 'int'>) |
0 0 LOAD_CONST 1 (<... 'int'>) |
3 RETURN_VALUE |
3 RETURN_VALUE |
|
|
|
|
>>> def type_or_class(x): pass |
>>> def type_or_class(x): pass |
>>> c = Code.from_function(type_or_class) |
>>> c = Code.from_function(type_or_class) |
>>> c.return_(class_or_type_of(Local('x'))) |
>>> c.return_(class_or_type_of(Local('x'))) |
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 LOAD_FAST 0 (x) |
LOAD_FAST 0 (x) |
3 SETUP_EXCEPT 9 (to 15) |
SETUP_EXCEPT L1 |
6 DUP_TOP |
DUP_TOP |
7 LOAD_ATTR 0 (__class__) |
LOAD_ATTR 0 (__class__) |
10 ROT_TWO |
ROT_TWO |
11 POP_BLOCK |
POP_BLOCK |
12 JUMP_FORWARD 26 (to 41) |
JUMP_FORWARD L3 |
>> 15 DUP_TOP |
L1: DUP_TOP |
16 LOAD_CONST 1 (<...exceptions.AttributeError...>) |
LOAD_CONST 1 (<...AttributeError...>) |
19 COMPARE_OP 10 (exception match) |
COMPARE_OP 10 (exception match) |
22 JUMP_IF_FALSE 14 (to 39) |
JUMP_IF_FALSE L2 |
25 POP_TOP |
POP_TOP |
26 POP_TOP |
POP_TOP |
27 POP_TOP |
POP_TOP |
28 POP_TOP |
POP_TOP |
29 LOAD_CONST 2 (<type 'type'>) |
LOAD_CONST 2 (<... 'type'>) |
32 ROT_TWO |
ROT_TWO |
33 CALL_FUNCTION 1 |
CALL_FUNCTION 1 |
36 JUMP_FORWARD 2 (to 41) |
JUMP_FORWARD L3 |
>> 39 POP_TOP |
L2: POP_TOP |
40 END_FINALLY |
END_FINALLY |
>> 41 RETURN_VALUE |
L3: RETURN_VALUE |
|
|
>>> type_or_class.func_code = c.code() |
>>> type_or_class.__code__ = type_or_class.func_code = c.code() |
>>> type_or_class(23) |
>>> type_or_class(23) |
<type 'int'> |
<... 'int'> |
|
|
|
|
|
|
>>> from peak.util.assembler import LOAD_CONST, POP_BLOCK |
>>> from peak.util.assembler import LOAD_CONST, POP_BLOCK |
|
|
>>> import sys |
>>> import sys |
>>> WHY_CONTINUE = {'2.3':5, '2.4':32, '2.5':32}[sys.version[:3]] |
>>> WHY_CONTINUE = {'2.3':5}.get(sys.version[:3], 32) |
|
|
>>> def Switch(expr, cases, default=Pass, code=None): |
>>> def Switch(expr, cases, default=Pass, code=None): |
... if code is None: |
... if code is None: |
>>> c(Switch(Local('x'), [(1,Return(42)),(2,Return("foo"))], Return(27))) |
>>> c(Switch(Local('x'), [(1,Return(42)),(2,Return("foo"))], Return(27))) |
>>> c.return_() |
>>> c.return_() |
|
|
>>> f = new.function(c.code(), globals()) |
>>> f = function(c.code(), globals()) |
>>> f(1) |
>>> f(1) |
42 |
42 |
>>> f(2) |
>>> f(2) |
>>> f(3) |
>>> f(3) |
27 |
27 |
|
|
>>> dis(c.code()) |
>>> dump(c.code()) |
0 0 SETUP_LOOP 30 (to 33) |
SETUP_LOOP L2 |
3 LOAD_CONST 1 (<...method get of dict...>) |
LOAD_CONST 1 (<...method ...get of ...>) |
6 LOAD_FAST 0 (x) |
LOAD_FAST 0 (x) |
9 CALL_FUNCTION 1 |
CALL_FUNCTION 1 |
12 JUMP_IF_FALSE 12 (to 27) |
JUMP_IF_FALSE L1 |
15 LOAD_CONST 2 (...) |
LOAD_CONST 2 (...) |
18 END_FINALLY |
END_FINALLY |
19 LOAD_CONST 3 (42) |
LOAD_CONST 3 (42) |
22 RETURN_VALUE |
RETURN_VALUE |
23 LOAD_CONST 4 ('foo') |
LOAD_CONST 4 ('foo') |
26 RETURN_VALUE |
RETURN_VALUE |
>> 27 POP_TOP |
L1: POP_TOP |
28 LOAD_CONST 5 (27) |
LOAD_CONST 5 (27) |
31 RETURN_VALUE |
RETURN_VALUE |
32 POP_BLOCK |
POP_BLOCK |
>> 33 LOAD_CONST 0 (None) |
L2: LOAD_CONST 0 (None) |
36 RETURN_VALUE |
RETURN_VALUE |
|
|
|
|
TODO |
TODO |
* Exhaustive tests of all opcodes' stack history effects |
* Exhaustive tests of all opcodes' stack history effects |
|
|
* Test wide jumps and wide argument generation in general |
* Test wide jumps and wide argument generation in general |
|
|
|
* Remove/renumber local variables when a local is converted to free/cell |
|
|