Generating Python Bytecode with ``peak.util.assembler`` |
Generating Python Bytecode with ``peak.util.assembler`` |
======================================================= |
======================================================= |
|
|
|
-------------- |
|
Programmer API |
|
-------------- |
|
|
|
Opcode API |
|
========== |
|
|
|
|
Simple usage:: |
Simple usage:: |
|
|
>>> from peak.util.assembler import Code |
>>> from peak.util.assembler import Code |
>>> c = Code() |
>>> c = Code() |
|
>>> c.set_lineno(15) # set the current line number (optional) |
>>> c.LOAD_CONST(42) |
>>> c.LOAD_CONST(42) |
|
>>> c.set_lineno(16) # set it as many times as you like |
>>> c.RETURN_VALUE() |
>>> c.RETURN_VALUE() |
|
|
>>> eval(c.code()) |
>>> eval(c.code()) |
|
|
>>> from dis import dis |
>>> from dis import dis |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 0 (42) |
15 0 LOAD_CONST 1 (42) |
3 RETURN_VALUE |
16 3 RETURN_VALUE |
|
|
|
Labels and backpatching forward references:: |
|
|
|
>>> c = Code() |
|
>>> ref = c.JUMP_ABSOLUTE() # jump w/unspecified target |
|
>>> c.LOAD_CONST(1) |
|
>>> ref() # resolve the forward reference |
|
>>> c.RETURN_VALUE() |
|
>>> dis(c.code()) |
|
0 0 JUMP_ABSOLUTE 6 |
|
3 LOAD_CONST 1 (1) |
|
>> 6 RETURN_VALUE |
|
|
|
|
|
>>> c = Code() |
|
>>> lbl = c.label() # create a label at this point in the code |
|
>>> c.LOAD_CONST(1) |
|
>>> ref = c.JUMP_ABSOLUTE(lbl) # and jump to it |
|
>>> dis(c.code()) |
|
0 >> 0 LOAD_CONST 1 (1) |
|
3 JUMP_ABSOLUTE 0 |
|
|
|
|
|
Code Generation API |
|
=================== |
|
|
|
Code generation from tuples, lists, dicts, and local variable names:: |
|
|
|
>>> c = Code() |
|
>>> c( ['x', ('y','z')] ) # push a value on the stack |
|
>>> dis(c.code()) |
|
0 0 LOAD_FAST 0 (x) |
|
3 LOAD_FAST 1 (y) |
|
6 LOAD_FAST 2 (z) |
|
9 BUILD_TUPLE 2 |
|
12 BUILD_LIST 2 |
|
|
|
And with constants, dictionaries, globals, and calls:: |
|
|
|
>>> from peak.util.assembler import Const, Call, Global |
|
|
|
>>> c = Code() |
|
>>> c.Return( [Global('type'), Const(27)] ) # push and RETURN_VALUE |
|
>>> dis(c.code()) |
|
0 0 LOAD_GLOBAL 0 (type) |
|
3 LOAD_CONST 1 (27) |
|
6 BUILD_LIST 2 |
|
9 RETURN_VALUE |
|
|
|
>>> c = Code() |
|
>>> c( {Const('x'): Const(123)} ) |
|
>>> dis(c.code()) |
|
0 0 BUILD_MAP 0 |
|
3 DUP_TOP |
|
4 LOAD_CONST 1 ('x') |
|
7 LOAD_CONST 2 (123) |
|
10 ROT_THREE |
|
11 STORE_SUBSCR |
|
|
|
>>> c = Code() |
|
>>> c(Call(Global('type'), (Const(1),))) |
|
>>> dis(c.code()) |
|
0 0 LOAD_GLOBAL 0 (type) |
|
3 LOAD_CONST 1 (1) |
|
6 CALL_FUNCTION 1 |
|
|
|
>>> c = Code() |
|
>>> c(Call(Global('getattr'), (Const(1), Const('__class__')))) |
|
>>> dis(c.code()) |
|
0 0 LOAD_GLOBAL 0 (getattr) |
|
3 LOAD_CONST 1 (1) |
|
6 LOAD_CONST 2 ('__class__') |
|
9 CALL_FUNCTION 2 |
|
|
|
``Call`` objects take 1-4 arguments: the expression to be called, a sequence |
|
of positional arguments, a sequence of keyword/value pairs for explicit keyword |
|
arguments, an "*" argument, and a "**" argument. To omit any of the optional |
|
arguments, just pass in an empty sequence in its place:: |
|
|
|
>>> c = Code() |
|
>>> c.Return( |
|
... Call(Global('foo'), ['q'], [('x',Const(1))], 'starargs', 'kwargs') |
|
... ) |
|
>>> dis(c.code()) |
|
0 0 LOAD_GLOBAL 0 (foo) |
|
3 LOAD_FAST 0 (q) |
|
6 LOAD_CONST 1 ('x') |
|
9 LOAD_CONST 2 (1) |
|
12 LOAD_FAST 1 (starargs) |
|
15 LOAD_FAST 2 (kwargs) |
|
18 CALL_FUNCTION_VAR_KW 257 |
|
21 RETURN_VALUE |
|
|
|
Code generation is extensible: any 1-argument callables passed in will |
|
be passed the code object during generation. (The return value, if any, is |
|
ignored.) You can even use ``Code`` methods, if they don't have any required |
|
arguments:: |
|
|
|
>>> c = Code() |
|
>>> c.LOAD_GLOBAL('foo') |
|
>>> c(Call(Code.DUP_TOP, ())) |
|
>>> dis(c.code()) |
|
0 0 LOAD_GLOBAL 0 (foo) |
|
3 DUP_TOP |
|
4 CALL_FUNCTION 0 |
|
|
|
This basically means you can create an AST of callable objects to drive code |
|
generation, with a lot of the grunt work automatically handled for you. |
|
|
|
|
|
--------- |
|
Internals |
|
--------- |
|
|
Line number tracking:: |
Line number tracking:: |
|
|
3 CALL_FUNCTION 0 |
3 CALL_FUNCTION 0 |
6 POP_TOP |
6 POP_TOP |
7 LOAD_GLOBAL 1 (foo) |
7 LOAD_GLOBAL 1 (foo) |
10 LOAD_CONST 0 (1) |
10 LOAD_CONST 1 (1) |
13 LOAD_CONST 1 ('x') |
13 LOAD_CONST 2 ('x') |
16 LOAD_CONST 2 (2) |
16 LOAD_CONST 3 (2) |
19 CALL_FUNCTION 257 |
19 CALL_FUNCTION 257 |
22 POP_TOP |
22 POP_TOP |
|
|
|
|
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 RAISE_VARARGS 0 |
0 0 RAISE_VARARGS 0 |
3 LOAD_CONST 0 (1) |
3 LOAD_CONST 1 (1) |
6 RAISE_VARARGS 1 |
6 RAISE_VARARGS 1 |
|
|
Sequence building, unpacking, dup'ing:: |
Sequence building, unpacking, dup'ing:: |
AssertionError: Stack underflow |
AssertionError: Stack underflow |
|
|
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 LOAD_CONST 0 (1) |
0 0 LOAD_CONST 1 (1) |
3 LOAD_CONST 1 (2) |
3 LOAD_CONST 2 (2) |
6 BUILD_TUPLE 2 |
6 BUILD_TUPLE 2 |
9 UNPACK_SEQUENCE 2 |
9 UNPACK_SEQUENCE 2 |
12 DUP_TOPX 2 |
12 DUP_TOPX 2 |
15 LOAD_CONST 2 (3) |
15 LOAD_CONST 3 (3) |
18 BUILD_LIST 5 |
18 BUILD_LIST 5 |
21 UNPACK_SEQUENCE 5 |
21 UNPACK_SEQUENCE 5 |
24 BUILD_SLICE 3 |
24 BUILD_SLICE 3 |
>>> c.RETURN_VALUE() |
>>> c.RETURN_VALUE() |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 JUMP_ABSOLUTE 6 |
0 0 JUMP_ABSOLUTE 6 |
3 LOAD_CONST 0 (1) |
3 LOAD_CONST 1 (1) |
>> 6 RETURN_VALUE |
>> 6 RETURN_VALUE |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.RETURN_VALUE() |
>>> c.RETURN_VALUE() |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 0 JUMP_FORWARD 3 (to 6) |
0 0 JUMP_FORWARD 3 (to 6) |
3 LOAD_CONST 0 (1) |
3 LOAD_CONST 1 (1) |
>> 6 RETURN_VALUE |
>> 6 RETURN_VALUE |
|
|
>>> c = Code() |
>>> c = Code() |
>>> c.LOAD_CONST(1) |
>>> c.LOAD_CONST(1) |
>>> ref = c.JUMP_ABSOLUTE(lbl) |
>>> ref = c.JUMP_ABSOLUTE(lbl) |
>>> dis(c.code()) |
>>> dis(c.code()) |
0 >> 0 LOAD_CONST 0 (1) |
0 >> 0 LOAD_CONST 1 (1) |
3 JUMP_ABSOLUTE 0 |
3 JUMP_ABSOLUTE 0 |
|
|
|
|
|
"Call" combinations:: |
|
|
|
|
|
>>> c = Code() |
|
>>> c.set_lineno(1) |
|
>>> c(Call(Global('foo'), ['q'], [('x',Const(1))], 'starargs')) |
|
>>> c.RETURN_VALUE() |
|
>>> dis(c.code()) |
|
1 0 LOAD_GLOBAL 0 (foo) |
|
3 LOAD_FAST 0 (q) |
|
6 LOAD_CONST 1 ('x') |
|
9 LOAD_CONST 2 (1) |
|
12 LOAD_FAST 1 (starargs) |
|
15 CALL_FUNCTION_VAR 257 |
|
18 RETURN_VALUE |
|
|
|
|
|
>>> c = Code() |
|
>>> c.set_lineno(1) |
|
>>> c(Call(Global('foo'), ['q'], [('x',Const(1))], None, 'kwargs')) |
|
>>> c.RETURN_VALUE() |
|
>>> dis(c.code()) |
|
1 0 LOAD_GLOBAL 0 (foo) |
|
3 LOAD_FAST 0 (q) |
|
6 LOAD_CONST 1 ('x') |
|
9 LOAD_CONST 2 (1) |
|
12 LOAD_FAST 1 (kwargs) |
|
15 CALL_FUNCTION_KW 257 |
|
18 RETURN_VALUE |
|
|
|
|
|
Cloning:: |
|
|
|
>>> c = Code.from_function(lambda (x,y):1, True) |
|
>>> dis(c.code()) |
|
1 0 LOAD_FAST 0 (.0) |
|
3 UNPACK_SEQUENCE 2 |
|
6 STORE_FAST 1 (x) |
|
9 STORE_FAST 2 (y) |
|
|
|
>>> c = Code.from_function(lambda x,(y,(z,a,b)):1, True) |
|
>>> dis(c.code()) |
|
1 0 LOAD_FAST 1 (.1) |
|
3 UNPACK_SEQUENCE 2 |
|
6 STORE_FAST 2 (y) |
|
9 UNPACK_SEQUENCE 3 |
|
12 STORE_FAST 3 (z) |
|
15 STORE_FAST 4 (a) |
|
18 STORE_FAST 5 (b) |
|
|
|
TODO |
|
==== |
|
|
|
* Test free/cell ops (LOAD_CLOSURE, LOAD_DEREF, STORE_DEREF) |
|
* Test MAKE_FUNCTION/MAKE_CLOSURE |
|
* Test code flags generation/cloning |
|
|
|
|
|
|