- 追加された行はこの色です。
- 削除された行はこの色です。
- pypy へ行く。
*コンパイラ構成論ソース読み会 〜pypy〜 [#i7250e84]
**インストール [#zb658e48]
-公式ホームページ(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デバッグする場合 [#va72536c]
--簡単な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デバッグする [#pa438941]
--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日目 [#yed0cad0]
**どこでパースしているのか探そうという話 [#g2611265]
$ 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デバッグしていく [#h7b23e2a]
$ 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日目 [#q8df6622]
**python・pypyのコンパイラの仕組み。 [#g8a0185a]
-- 図&ref(blackbord.jpg)を載せる。
-- pyinteractiveがpythonを呼ぶかpypyに呼ぶかはスペースによって決まるらしい。
-- pyinteractive は Python 側にパースなども任せている様子
**スペースの切り替え部分を探す。 [#o49c69c5]
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
-- バイトコードの大部分(printやplus等)が書かれている
pypy/pypy/interpreter/pyopcode.py
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)
**テストのソースを編集し、それを動かしながらデバッグしていく。 [#y2c04c79]
-- 直接読みたいパーツをテストしているコードを動かして、パーツのコードを読んでいくことに。
-- 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化されていた。
**バイトコードが生成された。 [#nae0efe5]
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を実行できるかを試した [#y92206fc]
-- 移植した部分
pypy/pypy/interpreter/eval.py(33)
-- ↑ 内の関数 exec_code()
pypy/pypy/interpreter/main.py
-- ↑ 内の関数 ensure__main__(space)
-- 他にもあったかな〜〜〜〜?
**3日目! [#le2129ea]
コンパイラは、与えられたソースコードをいろいろと変換し、
メモリ上に完全構文木(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等で再構築