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等で再構築