pypy

pypy

# コンパイラ構成論ソース読み会 〜pypy〜

# インストール

1
 $ hg clone https://bitbucket.org/pypy/pypy
  • pypyをコンパイルする。 hg cloneしてきたpypy上で、ディレクトリを移動する。
1
 $ cd pypy/pypy/goal
  • pypyをコンパイルする。
1
 $ python ../../rpython/bin/rpython --lldebug -Ojit targetpypystandalone
  • lldbでデバッグしたい場合のコンパイルは以下のコマンド。
1
 $ python ../../rpython/bin/rpython --lldebug -Ojit targetpypystandalone

pypy-cというバイナリが出来上がる!

# lldbデバッグする場合

* 簡単なpythonのファイルを作成する。
* 今回の場合は [tmp.py](tmp.py)
1
$ lldb -- [読む側のプログラム] tmp.py

例 : tmp.pをpypy-cで読んでいく様子をlldbでみていく。

1
$ lldb -- ./pypy-c tmp.py

lldbのための debug symbol は /private/var/の下にできるのでしばらくすると消えてしまいます。 なので PYPY_USESSION_DIR や PYPY_USESSION_KEEP を設定すると良さそうです。

# pypy interepreter をpdbデバッグする

* lldbの場合と同様に、tmp.pyを読んでいく様子をみていく。
1
$ python -m pdb pypy/bin/pyinteractive.py tmp.py
  • 各オプションについて
    • pdb で break するには

      • b :
    • pdb で b を消すには

      • clear
    • pdb で condition をかけるには

      • condition
    • pdb で condition を消す

      • condition
    • 毎回コマンドを実行するには

      • commands の後に command を打つ 終わる時に end とか continue を書く

# 1日目

# どこでパースしているのか探そうという話

1
$ lldb -- ./pypy-c tmp.py
* ファイルを開いているはずなので、まずは open を追う。
1
(lldb) b open
* 絶対パスでpythonモジュールを読み込んでるっぽい。
* なので先頭が/で無いものが tmp.py だとして break point に condition を付ける
* br m -c ((char*)$rdi)[0]!=\'/\' 3.1
* で 3.1 にある open で $rdi の先頭が / じゃないやつを止めとく
* $rdi に引数な文字列をが残っていたりするので x $rdi とかする

* pyinteractive.py を読む
* パーサを読みたい

# pdbデバッグしていく

1
   $ python -m pdb pypy/bin/pyinteractive.py tmp.py
* break point に commnads を設定して、ファイル名を出力させながらトレースしていく。
* ほしいファイル名はtmp.py。
1
   /Users/e115747/Desktop/pypy/pypy/bin/pyinteractive.py
* このコードの関数で重要な部分らしい。
* do_start() : Python実行用環境が作られる関数
* doit()     : 実際にコードが実行される関数
* これらが main.run_toplevel() に渡されて実行される
1
   95行目  : spacce.setitem()
* 第3引数argvがtmp.pyとなっていた
1
   174行目 : main.run_toplevel()
* 第2引数がdoit()であった場合にPythonコードが実行される
1
   pypy/pypy/interpreter/main.py
* 103行目 : f() が doit() と一致。

* 一連の流れをみてみると、pythonに変換されたコードが返ってきていることがわかった。

# 2日目

# python・pypyのコンパイラの仕組み。

* 図&ref(blackbord.jpg)を載せる。
* pyinteractiveがpythonを呼ぶかpypyに呼ぶかはスペースによって決まるらしい。
* pyinteractive は Python 側にパースなども任せている様子

# スペースの切り替え部分を探す。

1
 pypy/pypy/interpreter/main.py
* このソースの
1
 space.wrap('softspace'))
* でスペースで選択されているらしい。
* pypyのスペースに切り替えたいなぁ...

* この段階ではpdbで追っていくと Python VM なコードになったので VM を読むことに。
* Python VM の frame や pyopcode(python の byte codeの様子)などを追う

* コードの中身(関数やクラス等)を定義している
1
 pypy/pypy/interpreter/baseobjspace.py
* フレームが作成される
* フレーム : 関数の戻り値等をスタックしておいたりするやつ
1
 pypy/pypy/interpreter/pyframe.py
* バイトコードの大部分(printやplus等)が書かれている
1
 pypy/pypy/interpreter/pyopcode.py

frame を作成して exec するところまでは追った。

* execute_frame での pdb のバックトレース
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 (Pdb) bt
 /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/bdb.py(387)run()
 -> exec cmd in globals, locals
 <string>(1)<module>()
 pypy/pypy/bin/pyinteractive.py(204)<module>()
 -> sys.exit(main_(sys.argv))
 pypy/pypy/bin/pyinteractive.py(175)main_()
 -> verbose=interactiveconfig.verbose):
 pypy/pypy/interpreter/main.py(103)run_toplevel()
 -> f()
 pypy/pypy/bin/pyinteractive.py(159)doit()
 -> main.run_file(args[0], space=space)
 pypy/pypy/interpreter/main.py(68)run_file()
 -> run_string(istring, filename, space)
 pypy/pypy/interpreter/main.py(59)run_string()
 -> _run_eval_string(source, filename, space, False)
 pypy/pypy/interpreter/main.py(48)_run_eval_string()
 -> retval = pycode.exec_code(space, w_globals, w_globals)
 pypy/pypy/interpreter/eval.py(33)exec_code()
 -> return frame.run()
 pypy/pypy/interpreter/pyframe.py(140)run()
 -> return self.execute_frame()
 > pypy/pypy/interpreter/pyframe.py(168)execute_frame()
 -> next_instr = r_uint(self.last_instr + 1)

# テストのソースを編集し、それを動かしながらデバッグしていく。

* 直接読みたいパーツをテストしているコードを動かして、パーツのコードを読んでいくことに。
* parser が読みたい、ということになってので、これをコピって編集する。
1
 pypy/interpreter/pyparser/test/test_pyparse.py
* 編集したもの -> [hoge.py](hoge.py)
* hoge.pyでparseする。
1
 $ python hoge.py tmp.py
* すごい文字列いっぱい出てきた!

* ここからpdbで読んでいく。
1
 $ python -m pdb hoge.py tmp.py
* textsrcの中身をprintすると、うまくtmp.pyのsourceがとれている。
1
 pypy/interpreter/pyparser/pyparse.py を読んだ
* 141行目からトークナイズの部分。
1
 pypy/interpreter/pyparser/pytokenizer.py を読んでる。
* アルファベット、数字、改行、コメントアウト等の判別等〜

* endDFA
* 決定性有限オートマトン( 状態遷移 )
* 行末を検出するのに使われている。

* 230行目 python_opmap はリストになっている
* 演算子がハッシュになっている!

* 簡単な計算の流れをみて演算子がハッシュになっているのをみた。
* 簡単なfor文の流れをみて段落がどのように判別されているのかをみた。

* ソースのインデント調べ終え、tree化されていた。

# バイトコードが生成された。

1
 pypy/interpreter/test/test_compiler.py
* これを編集して -> [hogest.py](hogest.py)

* バイトコードが生成された。
1
 $ python hogest.py tmp.py
1
 $ python -m pdb hogest.py tmp.py
* ノードの塊をこれでastにするみたい。
1
 pypy/pypy/interpreter/astcompiler/astbuilder.py
* 時間の関係上この日は、この部分は読まなかった。

# バイトコードをディスパッチに食わせて、tmp.pyを実行できるかを試した

* 移植した部分
1
 pypy/pypy/interpreter/eval.py(33)
* ↑ 内の関数 exec_code()
1
 pypy/pypy/interpreter/main.py
* ↑ 内の関数 ensure__main__(space)

* 他にもあったかな〜〜〜〜?

# 3日目!

1
2
 コンパイラは、与えられたソースコードをいろいろと変換し、
 メモリ上に完全構文木(cst)にしたあと、それを抽象構文気(ast)に変換している。
* astはpypy/interpreter/astcompiler/astbuilder.py:(52) build_ast()で作られている
* stmt の type を見て handle していく感じ?

* 例えば expr_node_type は以下のように handle されていった
1
  test -> or_test -> and_test -> not_test -> comparison -> expr -> xor_expr -> and_expr -> shift_expr -> arith_expr -> term -> factor -> power

hanldle_expr

1
2
3
4
 どんどんif文でchildrenを取っていっている
 children が 1 の場合は children[0] を取ってきて終了
 power まできたら handle_power して handle_atom する
 おそらく、演算などがかかりそうな部分にすべて handler があって、 children が 1である(特に演算が無い場合)は次の演算をチェックする、という形で潜っていく様子

file_input

1
2
 pypy/interpreter/pyparser/data/Grammar2.7の中に、simple_stmtの構成等が書かれていた。
 pypy/interpreter/pyparser/pytoken.pyの中に、演算子に対応する一覧が書かれている。
* type は syms の attribute として管理されているので、内部の値が分かっていてもそれが何に相当るのかは分からない
* 例えば syms.stmt は 322 なのだけれど、 hoge.type = 320 とかだと、hoge は syms の何に相当するのか分からない
* なので syms の attributes と 値から逆引きできるハッシュを作成して読むなどした
* おそらく pypy にはそういう util があるはず
* 書いた逆引きコードは以下。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 sym_tab = {}
 for attr in dir(astbuilder.syms):
     v = getattr(astbuilder.syms, attr)
     if isinstance(v, int):
         sym_tab[v] = attr
 
 def show_token(n):
 
     tokens = []
     if n > 256:
         return sym_tab[n]
     else:
         tokens = pytoken.python_tokens.items()
         for k, v in tokens:
             if n == v:
                 return k
* ちなみに type の値が 256 以下なら pyop らしい。
1
2
 p dir(syms)
 p syms.small_s

とかも、覚えておくといいかも

fileinputに潜る * stmt.type : 323 –> stmt

type を識別する巨大な if-elif 文があり

1
2
3
 children[0] の type --> simple_stmt
 children[0] の type --> small_stmt 
 children[0] の type --> expr_stmt

的な感じで分岐する様子

* handle_expr_stmt

handle_expr の時点での btのログ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/bdb.py(387)run()
 -> exec cmd in globals, locals
 <string>(1)<module>()
 /Users/e115763/files/build/pypy2/run_pypy.py(58)<module>()
 -> pycode = compile_with_astcompiler(source, "exec", StdObjSpace())
 /Users/e115763/files/build/pypy2/run_pypy.py(37)compile_with_astcompiler()
 -> ast = astbuilder.ast_from_node(space, cst, info)
 /Users/e115763/files/build/pypy2/pypy/interpreter/astcompiler/astbuilder.py(12)ast_from_node()
 -> return ASTBuilder(space, node, compile_info).build_ast()
 /Users/e115763/files/build/pypy2/pypy/interpreter/astcompiler/astbuilder.py(63)build_ast()
 -> stmts.append(self.handle_stmt(stmt))
 /Users/e115763/files/build/pypy2/pypy/interpreter/astcompiler/astbuilder.py(626)handle_stmt()
 -> return self.handle_expr_stmt(stmt)
 /Users/e115763/files/build/pypy2/pypy/interpreter/astcompiler/astbuilder.py(694)handle_expr_stmt()
 -> target_expr = self.handle_testlist(target_node)
 /Users/e115763/files/build/pypy2/pypy/interpreter/astcompiler/astbuilder.py(710)handle_testlist()
 -> return self.handle_expr(tests.children[0])
 > /Users/e115763/files/build/pypy2/pypy/interpreter/astcompiler/astbuilder.py(721)handle_expr()
 -> if first_child.type in (syms.lambdef, syms.old_lambdef):
1
2
 children[1].type      : 22 --> EQUAL
   
* y などの変数を ast 化すると Ast.NAME が返ってくる
* +等の処理は handle_binop とかでやってる
* ast 初期化の initialize_stateは何かのフラグ?

値が 15 とか 8 とかの決め打ちなので

* ast.py に AST の定義が書かれているが、これは生成されたコードらしい
1
2
 生成されたコード
 pypy/interpreter/astcompiler/ast.py
1
2
 ast.pyを生成するコード
 /pypy/interpreter/astcompiler/tools/asdl_py.py
1
2
 生成の定義等が書かれているやつ← 超重要!!
 pypy/interpreter/astcompiler/tools/Python.asdl
1
2
 ソースをpypyの中で適当に書いてると環境が壊れることがあるので注意
 最悪hg clone等で再構築
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy