*コンパイラ構成論ソース読み会 〜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等で再構築

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS