PythonのバイトコードをGRINEditで可視化
上の図は下のPythonで書かれた関数のバイトコードをGRINEditを使って可視化したものです。濃い青の矢印は「矢印先のインストラクションがスタックに積んだ値を矢印根本のインストラクションが消費した」という意味です。薄い水色の矢印はもうちょっと弱い「先が積んだ値を消費したわけじゃないけど、先がさわったスタックを根本もさわったか見た」という関係です。 赤矢印はジャンプです。
JUMP_IF_FALSEでの分岐で、条件式をポップせずに分岐して、分岐先の両方でいきなりポップしているのはなにか深遠な理由があるのでしょうかね?「JUMP_IF_FALSEは条件式をポップする」という仕様にすればelse節が省略されたときにポップするために4バイト使わないで済むのに。
def facto(n, ans=1):
if n == 0:
return 1
return facto(n - 1, n * ans)
バイトコードは下のようになります。
0 7C_00_00 LOAD_FAST(n)
3 64_01_00 LOAD_CONST(0)
6 6A_02_00 COMPARE_OP(==)
9 6F_08_00 JUMP_IF_FALSE() -> POP_TOP@20
12 01 POP_TOP()
13 64_02_00 LOAD_CONST(1)
16 53 RETURN_VALUE()
17 6E_01_00 JUMP_FORWARD() -> LOAD_GLOBAL@21
20 01 POP_TOP()
21 74_00_00 LOAD_GLOBAL(facto)
24 7C_00_00 LOAD_FAST(n)
27 64_02_00 LOAD_CONST(1)
30 18 BINARY_SUBTRACT()
31 7C_00_00 LOAD_FAST(n)
34 7C_01_00 LOAD_FAST(ans)
37 14 BINARY_MULTIPLY()
38 83_02_00 CALL_FUNCTION()
41 53 RETURN_VALUE()
いろいろ見ているとハッシュリテラルの作成に大量のROT_TWOが入っていたりとおもしろいです。どう考えてもSTORE_SUBSCRの仕様が間違っているようにしか思えません。
def foo():
{"a": 1, "b": 2, (1, 2): (1, 2)}
0 68_00_00 BUILD_MAP()
3 04 DUP_TOP()
4 64_01_00 LOAD_CONST(1)
7 02 ROT_TWO()
8 64_02_00 LOAD_CONST(a)
11 3C STORE_SUBSCR()
12 04 DUP_TOP()
13 64_03_00 LOAD_CONST(2)
16 02 ROT_TWO()
17 64_04_00 LOAD_CONST(b)
20 3C STORE_SUBSCR()
21 04 DUP_TOP()
22 64_05_00 LOAD_CONST((1, 2))
25 02 ROT_TWO()
26 64_06_00 LOAD_CONST((1, 2))
29 3C STORE_SUBSCR()
30 01 POP_TOP()
31 64_00_00 LOAD_CONST(None)
34 53 RETURN_VALUE()
でもGRINEditの最新版をさっさと公開すべきですね、すみません。 GRINEditのドキュメントも書かないといけないですね。 このディスアセンブルや可視化のためのコードは先日PyCodeObjectを書き換えるで紹介したCodeHackライブラリに入れました。そのうちにバージョン0.03で公開したいと思います。