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.2: |
|
|
|
* The ``repr()`` of AST nodes doesn't include a trailing comma for 1-argument |
|
node types any more. |
|
|
|
* Added a ``Pass`` symbol that generates no code, a ``Compare()`` node type |
|
that does n-way comparisons, and ``And()`` and ``Or()`` node types for doing |
|
logical operations. |
|
|
|
* The ``COMPARE_OP()`` method now accepts operator strings like ``"<="``, |
|
``"not in"``, ``"exception match"``, and so on, as well as numeric opcodes. |
|
See the standard library's ``opcode`` module for a complete list of the |
|
strings accepted (in the ``cmp_op`` tuple). ``"<>"`` is also accepted as an |
|
alias for ``"!="``. |
|
|
Changes since version 0.1: |
Changes since version 0.1: |
|
|
* Constant handling has been fixed so that it doesn't confuse equal values of |
* Constant handling has been fixed so that it doesn't confuse equal values of |
>>> 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 |
|
|
|
The ``COMPARE_OP`` method takes an argument which can be a valid comparison |
|
integer constant, or a string containing a Python operator, e.g.:: |
|
|
|
>>> c = Code() |
|
>>> c.LOAD_CONST(1) |
|
>>> c.LOAD_CONST(2) |
|
>>> c.COMPARE_OP('not in') |
|
>>> dis(c.code()) |
|
0 0 LOAD_CONST 1 (1) |
|
3 LOAD_CONST 2 (2) |
|
6 COMPARE_OP 7 (not in) |
|
|
|
The full list of valid operator strings can be found in the standard library's |
|
``opcode`` module. ``"<>"`` is also accepted as an alias for ``"!="``:: |
|
|
|
>>> c.LOAD_CONST(3) |
|
>>> c.COMPARE_OP('<>') |
|
>>> dis(c.code()) |
|
0 0 LOAD_CONST 1 (1) |
|
3 LOAD_CONST 2 (2) |
|
6 COMPARE_OP 7 (not in) |
|
9 LOAD_CONST 3 (3) |
|
12 COMPARE_OP 3 (!=) |
|
|
|
|
High-Level Code Generation |
High-Level Code Generation |
========================== |
========================== |
AssertionError: Label previously defined |
AssertionError: Label previously defined |
|
|
|
|
|
N-Way Comparisons |
|
----------------- |
|
|
|
You can generate N-way comparisons using the ``Compare()`` node type:: |
|
|
|
>>> from peak.util.assembler import Compare |
|
|
|
>>> c = Code() |
|
>>> c(Compare(Local('a'), [('<', Local('b'))])) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (a) |
|
3 LOAD_FAST 1 (b) |
|
6 COMPARE_OP 0 (<) |
|
|
|
3-way comparisons generate code that's a bit more complex. Here's a three-way |
|
comparison (``a<b<c``):: |
|
|
|
>>> c = Code() |
|
>>> c.return_(Compare(Local('a'), [('<', Local('b')), ('<', Local('c'))])) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (a) |
|
3 LOAD_FAST 1 (b) |
|
6 DUP_TOP |
|
7 ROT_THREE |
|
8 COMPARE_OP 0 (<) |
|
11 JUMP_IF_FALSE 10 (to 24) |
|
14 POP_TOP |
|
15 LOAD_FAST 2 (c) |
|
18 COMPARE_OP 0 (<) |
|
21 JUMP_FORWARD 2 (to 26) |
|
>> 24 ROT_TWO |
|
25 POP_TOP |
|
>> 26 RETURN_VALUE |
|
|
|
And a four-way (``a<b>c!=d``):: |
|
|
|
>>> c = Code() |
|
>>> c.return_( |
|
... Compare( Local('a'), [ |
|
... ('<', Local('b')), ('>', Local('c')), ('!=', Local('d')) |
|
... ]) |
|
... ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (a) |
|
3 LOAD_FAST 1 (b) |
|
6 DUP_TOP |
|
7 ROT_THREE |
|
8 COMPARE_OP 0 (<) |
|
11 JUMP_IF_FALSE 22 (to 36) |
|
14 POP_TOP |
|
15 LOAD_FAST 2 (c) |
|
18 DUP_TOP |
|
19 ROT_THREE |
|
20 COMPARE_OP 4 (>) |
|
23 JUMP_IF_FALSE 10 (to 36) |
|
26 POP_TOP |
|
27 LOAD_FAST 3 (d) |
|
30 COMPARE_OP 3 (!=) |
|
33 JUMP_FORWARD 2 (to 38) |
|
>> 36 ROT_TWO |
|
37 POP_TOP |
|
>> 38 RETURN_VALUE |
|
|
|
|
Constant Detection and Folding |
Constant Detection and Folding |
============================== |
============================== |
|
|
``globals()``, in other words. |
``globals()``, in other words. |
|
|
|
|
|
Logical And/Or |
|
-------------- |
|
|
|
You can evaluate logical and/or expressions using the ``And`` and ``Or`` node |
|
types:: |
|
|
|
>>> from peak.util.assembler import And, Or |
|
|
|
>>> c = Code() |
|
>>> c.return_( And([Local('x'), Local('y')]) ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (x) |
|
3 JUMP_IF_FALSE 4 (to 10) |
|
6 POP_TOP |
|
7 LOAD_FAST 1 (y) |
|
>> 10 RETURN_VALUE |
|
|
|
>>> c = Code() |
|
>>> c.return_( Or([Local('x'), Local('y')]) ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (x) |
|
3 JUMP_IF_TRUE 4 (to 10) |
|
6 POP_TOP |
|
7 LOAD_FAST 1 (y) |
|
>> 10 RETURN_VALUE |
|
|
|
|
|
True or false constants are folded automatically, avoiding code generation |
|
for intermediate values that will never be used in the result:: |
|
|
|
>>> c = Code() |
|
>>> c.return_( And([1, 2, Local('y')]) ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (y) |
|
3 RETURN_VALUE |
|
|
|
>>> c = Code() |
|
>>> c.return_( And([1, 2, Local('y'), 0]) ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (y) |
|
3 JUMP_IF_FALSE 4 (to 10) |
|
6 POP_TOP |
|
7 LOAD_CONST 1 (0) |
|
>> 10 RETURN_VALUE |
|
|
|
>>> c = Code() |
|
>>> c.return_( Or([1, 2, Local('y')]) ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_CONST 1 (1) |
|
3 RETURN_VALUE |
|
|
|
>>> c = Code() |
|
>>> c.return_( Or([False, Local('y'), 3]) ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (y) |
|
3 JUMP_IF_TRUE 4 (to 10) |
|
6 POP_TOP |
|
7 LOAD_CONST 1 (3) |
|
>> 10 RETURN_VALUE |
|
|
|
|
Custom Code Generation |
Custom Code Generation |
====================== |
====================== |
|
|
|
|
If you want to incorporate constant-folding into your AST nodes, you can do |
If you want to incorporate constant-folding into your AST nodes, you can do |
so by checking for constant values and folding them at either construction |
so by checking for constant values and folding them at either construction |
or code generation time. For example, this ``And`` node type folds constants |
or code generation time. For example, this ``And`` node type (a simpler |
during code generation, by not generating unnecessary branches when it can |
version of the one included in ``peak.util.assembler``) folds constants during |
|
code generation, by not generating unnecessary branches when it can |
prove which way a branch will go:: |
prove which way a branch will go:: |
|
|
>>> from peak.util.assembler import NotAConstant |
>>> from peak.util.assembler import NotAConstant |
code that might be unreachable. For example, consider this ``If`` |
code that might be unreachable. For example, consider this ``If`` |
implementation:: |
implementation:: |
|
|
>>> def Pass(code=None): |
>>> from peak.util.assembler import Pass |
... if code is None: |
|
... return Pass |
|
|
|
>>> def If(cond, then, else_=Pass, code=None): |
>>> def If(cond, then, else_=Pass, code=None): |
... if code is None: |
... if code is None: |
... return cond, then, else_ |
... return cond, then, else_ |