2013/11/26

PyPy ソースコード読み会に参加してきた

PyPy ソースコード読み会に参加してきました。
通算3日くらい。
バージョンは Python は 2.7.2 で pypy は 2.2-dev くらいで commit は 67874:84a635eb05a7 なところ。
環境は Mountain Lion。

これだけ大きいソースを追っていくのは初。
割と分かってないところが多いので間違っていることを書いているかもしれません。

PyPy

Python で言語を定義するとそれを pypy なレベルに落として、最終的にバイナリにしてくれるみたいです。
それが rpython なスクリプトで、例えば pypy は Python の実装を Python Code で書いて、それを実行すると一旦 C なファイルになって、それをコンパイルしてバイナリを生成するみたいです。

pypy-c

公式のページにある
$ pypy ../../rpython/bin/rpython -Ojit targetpypystandalone 
をすると python で実装された python が C に落ちて pypy-c という Python 実行系なバイナリができます。
その時には、  rpython/path/to/*.py は rpython_path_to_*.c みたいに / が _ になった C コードが生成される様子。
--lldebug とかを付けて
$ python ../../rpython/bin/rpython --lldebug -Ojit targetpypystandalone 
にすると lldb とかで追えるようになります。
生成されたコードは変数に5ケタとかの番号が付いてたり goto しまくりで直接読むのは厳しいです。

ちなみに pypy は 生成した C コードや debug object を /private に置くので、しばらくしていると消えてしまうようです。
なので PYPY_USESSION_DIR や PYPY_USESSION_KEEP を設定すると良さそう

とりあえず一旦 C に落ちてからバイナリが作られるので、バイナリを追っても python code は読めなさそう、という結論に。

Python Code を読んでいく

Pypy には大量のモジュールがあるので、それらの知りたいところだけを追おう、ということに。
なのでテストルーチンを最小限に切り出して、それを実行していく形式にしました。
個人的にテストを直接 pdb で追おうとしていましたが、 py.test で書かれてるようで、どうも通常のコードとは勝手が違って断念。
ということで test くらいからめぼしいものを拾ってきて、method 1つを実行できるようにして pdb debug で trace していくことに。
これだと python 側のコードで読めます。
ちなみにテストコード側には space とかの環境っぽいコードは無かったりしたので、 pypy/bin/pyinterpreter.py とかの インタプリタコードな pypy/interpreter/main.py から StdObjSpace のコードを拝借したりで動かし動かし。
おそらくすぐ動く小さいコードは pypy/bin/pyinterpreter.py なので足りないパーツはその辺りにあるんじゃないかな、とか思ってます。

テストを切り出してみる

parser とか astcompiler とかのソースを追いたいので
pypy/interpreter/astcompiler/test/test_compiler.py
の compile_with_astcompiler を拾ってくる。
足りない space とかは
pypy/interpreter/main.py
から拾ったり。mainmodule とか w_globals  とかも。
そんなんで作ったコードがこんな感じ。

pyparser / astcompiler

そんな感じのコードを使って pyparser を読んだり。
tokenize で lines を split したりと割と富豪的。
おかげで高速化とかされていないわけなので素直で読みやすいといえば読みやすいかも。
astcompiler は buld_ast な parse 後のコードから ast を生成する部分をちょろっと読むなど。
token の type に応じて handle_expr やら handle_binop やらの関数で parse した node を拾ってきて AST オブジェクトを new っていく感じらしいです。
あと Python VM なやつは pyinterpreter を追っていったら出てきたような。

symbol table

token から ast にする時に expr_node.type を確認していったりするのですが、 この type が syms.stmt やら syms.simple_stmt やらとガンガン if で比較していく形式。if-elif祭り。
syms.stmt の値は定数の数値なので、 expr_node.type の数値が分かっても syms の何に相当するのかが謎。
なので dir(syms) とかで syms にある attr を拾ってきて getattr(syms, attr) とかしていって逆引きテーブルを作ったりして対応を取ることに。
この辺の対応を確認する便利コードが pypy にありそうだけれどなー、とかなりつつ。
というかこいう enum みたいなコードって 書く/読む 分にはマジックナンバーが隠れて良いけれど、実行していく分にはチェックするの大変だなー、とか思うなど。特にマジックナンバーな値しか分からない時。
そういう時はやっぱり debug helper チックなのでも書いてたら良いのかな。逆引きとか。

所感 / tips / もろもろ

読んでいくというよりは実行していく、といった感じで読んでいきました。
流れが分かりやすいし使わないところは読まなくて良いので、こんな感じで読むのかー、と。

ただ、実行の仕方分からないとだいぶ大変そうだなー、と。 python 分からない。
一番単純な example とか how to reading sources とかあれば嬉しいよなー、とか。

object の class を見て関連しそうなところとかを探していったり、関数に入ったら l っていってざっくり見てから n/s るのが自然な読み方なのかも?

一見見えないところでも lldb で stepi するとアセンブラに書かれてたりするのでそれを頼りに読むとかあってすごいなー、と。

lldb の debug symbols が /private にあったので読んでたら消えたり、なんか一部のコードを単体で実行したら pypy が動かなくなったり謎ハプニングも。
ちなみに動かなくなったら hg clone しなおしでどうにか直る。
でも hg diff には引っかからなかったのでどこが壊れたのかも謎でした。

とかとかいろいろあった読み会でした。

0 件のコメント:

コメントを投稿