Mercurial > hg > Document > Growi
view software/pypy.md @ 93:04cff9568106
backup 2021-10-05
author | autobackup |
---|---|
date | Tue, 05 Oct 2021 00:10:04 +0900 |
parents | e12992dca4a0 |
children |
line wrap: on
line source
# コンパイラ構成論ソース読み会 〜pypy〜 ## インストール - 公式ホームページ(http://pypy.org/download.html#installing)を参考に。 - hg clone で落としてくる。 - `$ hg clone https://bitbucket.org/pypy/pypy` - pypyをコンパイルする。 hg cloneしてきたpypy上で、ディレクトリを移動する。 - `$ cd pypy/pypy/goal` - pypyをコンパイルする。 - `$ python ../../rpython/bin/rpython --lldebug -Ojit targetpypystandalone` - lldbでデバッグしたい場合のコンパイルは以下のコマンド。 - `$ python ../../rpython/bin/rpython --lldebug -Ojit targetpypystandalone` pypy-cというバイナリが出来上がる! ## lldbデバッグする場合 - 簡単なpythonのファイルを作成する。 - 今回の場合は &ref(tmp.py); ``` $ lldb -- [読む側のプログラム] tmp.py ``` 例 : tmp.pをpypy-cで読んでいく様子をlldbでみていく。 ``` $ lldb -- ./pypy-c tmp.py ``` lldbのための debug symbol は /private/var/の下にできるのでしばらくすると消えてしまいます。 なので PYPY_USESSION_DIR や PYPY_USESSION_KEEP を設定すると[[良さそうです。:http://doc.pypy.org/en/latest/getting-started-dev.html#where-to-start-reading-the-sources]] ## pypy interepreter をpdbデバッグする - lldbの場合と同様に、tmp.pyを読んでいく様子をみていく。 ``` $ python -m pdb pypy/bin/pyinteractive.py tmp.py ``` - 各オプションについて - pdb で break するには - `b <filename>:<lineno>` - pdb で b を消すには - `clear <breakpointid>` - pdb で condition をかけるには - `condition <breakpointid> <condition>` - pdb で condition を消す - `condition <breakpointid>` - 毎回コマンドを実行するには - `commands <breakpointid>` の後に command を打つ 終わる時に end とか continue を書く ## 1日目 ## どこでパースしているのか探そうという話 ``` $ lldb -- ./pypy-c tmp.py ``` - ファイルを開いているはずなので、まずは open を追う。 (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デバッグしていく `$ python -m pdb pypy/bin/pyinteractive.py tmp.py` - break point に commnads を設定して、ファイル名を出力させながらトレースしていく。 - ほしいファイル名はtmp.py。 `/Users/e115747/Desktop/pypy/pypy/bin/pyinteractive.py` - このコードの関数で重要な部分らしい。 - do_start() : Python実行用環境が作られる関数 - doit() : 実際にコードが実行される関数 - これらが main.run_toplevel() に渡されて実行される 95行目 : spacce.setitem() - 第3引数argvがtmp.pyとなっていた 174行目 : main.run_toplevel() - 第2引数がdoit()であった場合にPythonコードが実行される pypy/pypy/interpreter/main.py - 103行目 : f() が doit() と一致。 - 一連の流れをみてみると、pythonに変換されたコードが返ってきていることがわかった。 ## 2日目 ## python・pypyのコンパイラの仕組み。 - 図&ref(blackbord.jpg)を載せる。 - pyinteractiveがpythonを呼ぶかpypyに呼ぶかはスペースによって決まるらしい。 - pyinteractive は Python 側にパースなども任せている様子 ## スペースの切り替え部分を探す。 pypy/pypy/interpreter/main.py - このソースの space.wrap('softspace')) - でスペースで選択されているらしい。 - pypyのスペースに切り替えたいなぁ... - この段階ではpdbで追っていくと Python VM なコードになったので VM を読むことに。 - Python VM の frame や pyopcode(python の byte codeの様子)などを追う pypy/pypy/interpreter/baseobjspace.py - コードの中身(関数やクラス等)を定義している pypy/pypy/interpreter/pyframe.py - フレームが作成される - フレーム : 関数の戻り値等をスタックしておいたりするやつ pypy/pypy/interpreter/pyopcode.py - バイトコードの大部分(printやplus等)が書かれている frame を作成して exec するところまでは追った。 - execute_frame での pdb のバックトレース ``` (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 が読みたい、ということになってので、これをコピって編集する。 `pypy/interpreter/pyparser/test/test_pyparse.py` - 編集したもの -> &ref(hoge.py); - hoge.pyでparseする。 $ python hoge.py tmp.py - すごい文字列いっぱい出てきた! - ここからpdbで読んでいく。 $ python -m pdb hoge.py tmp.py - textsrcの中身をprintすると、うまくtmp.pyのsourceがとれている。 pypy/interpreter/pyparser/pyparse.py を読んだ - 141行目からトークナイズの部分。 pypy/interpreter/pyparser/pytokenizer.py を読んでる。 - アルファベット、数字、改行、コメントアウト等の判別等〜 - endDFA - 決定性有限オートマトン( 状態遷移 ) - 行末を検出するのに使われている。 -- - 230行目 python_opmap はリストになっている - 演算子がハッシュになっている! -- - 簡単な計算の流れをみて演算子がハッシュになっているのをみた。 - 簡単なfor文の流れをみて段落がどのように判別されているのかをみた。 - ソースのインデント調べ終え、tree化されていた。 ## バイトコードが生成された。 pypy/interpreter/test/test_compiler.py - これを編集して -> &ref(hogest.py); - バイトコードが生成された。 `$ python hogest.py tmp.py` `$ python -m pdb hogest.py tmp.py` - ノードの塊をこれでastにするみたい。 pypy/pypy/interpreter/astcompiler/astbuilder.py - 時間の関係上この日は、この部分は読まなかった。 ## バイトコードをディスパッチに食わせて、tmp.pyを実行できるかを試した - 移植した部分 pypy/pypy/interpreter/eval.py(33) - ↑ 内の関数 exec_code() pypy/pypy/interpreter/main.py - ↑ 内の関数 ensure__main__(space) - 他にもあったかな〜〜〜〜? ## 3日目! コンパイラは、与えられたソースコードをいろいろと変換し、 メモリ上に完全構文木(cst)にしたあと、それを抽象構文気(ast)に変換している。 - astはpypy/interpreter/astcompiler/astbuilder.py:(52) build_ast()で作られている - stmt の type を見て handle していく感じ? - 例えば expr_node_type は以下のように handle されていった test -> or_test -> and_test -> not_test -> comparison -> expr -> xor_expr -> and_expr -> shift_expr -> arith_expr -> term -> factor -> power hanldle_expr - どんどんif文でchildrenを取っていっている - children が 1 の場合は children[0] を取ってきて終了 - power まできたら handle_power して handle_atom する - おそらく、演算などがかかりそうな部分にすべて handler があって、 children が 1である(特に演算が無い場合)は次の演算をチェックする、という形で潜っていく様子 file_input - 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 があるはず - 書いた逆引きコードは以下。 ``` 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 らしい。 ``` p dir(syms) p syms.small_s ``` とかも、覚えておくといいかも fileinputに潜る - stmt.type : 323 --> stmt type を識別する巨大な if-elif 文があり ``` children[0] の type --> simple_stmt children[0] の type --> small_stmt children[0] の type --> expr_stmt ``` 的な感じで分岐する様子 - handle_expr_stmt handle_expr の時点での btのログ ``` /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): children[1].type : 22 --> EQUAL ``` - y などの変数を ast 化すると Ast.NAME が返ってくる - +等の処理は handle_binop とかでやってる - ast 初期化の initialize_stateは何かのフラグ? 値が 15 とか 8 とかの決め打ちなので - ast.py に AST の定義が書かれているが、これは生成されたコードらしい 生成されたコード pypy/interpreter/astcompiler/ast.py ast.pyを生成するコード /pypy/interpreter/astcompiler/tools/asdl_py.py 生成の定義等が書かれているやつ← 超重要!! pypy/interpreter/astcompiler/tools/Python.asdl ソースをpypyの中で適当に書いてると環境が壊れることがあるので注意 最悪hg clone等で再構築