# 進捗報告 ## 10月2日 pythonをいじることに決定 ## 10月5日 gitの使い方を学び、それぞれがpythonのビルドに成功した。 ## 10月6日 gitlabへのグループやリポジトリを登録。 インクリメント演算子を追加する予定だったが、過去の実験で先輩がすでに行っていたため、変更してfor文をいじることに? pythonのfor文はc++でいう範囲for文なのでfor(int i = 0;……)的なものを作りたい。 班員それぞれでとりあえずpythonがどのように動くのかを探ることにした。 HelloWorldの実行を追うため、中身が ``` print('Hello, world!') ``` であるようなファイルa.pyを作り、 ``` (gdb) r a.py ``` として実行した。 すると、 "_PyEval_EvalCodeWithName"という関数の実行後に"Hello, world!"と出力されることが分かった。 そこで、sコマンドでその関数の中に入ってみたところ、その関数が複数回呼び出されているようだったが、何が起きているのか分からなかった。 ``` for i in range(10): print(1) ``` と書いたテキストファイルtest.pyを作成し、gdbで `(gdb) r test.py` として挙動を追ったが、上記の"_PyEval_EvalCodeWithName"内の ``` retval = PyEva_EvalFrameEx(f, 0); ``` をnextした時に結果が出力されたため、この内部を追うと ``` return tstate->interp->eval_frame(f, throwflag); ``` となっており、さらにsで探ると"Py_EvalFrameDefault(…)"という関数のポインタを構造体で保持しているらしい。この関数がとても長いのでこれから調べてみる。長すぎ。 "_PyEval_EvalCodeWithName"が複数回呼びだされてるというよりはその先の"Py_EvalFrameDefault(…)"がとても長い。関数内で"_Py_CheckFunctionResult(…)"などの、関数にreturnがあるかどうか判断していそうな関数が存在していたので、これはおそらくfor文がどのように読み込まれているかどうかとは無関係なのでは? 文法の問題なので、挙動をみるよりもC言語でどう表現されているかが見たいので、探索を打ちきり、別のそれっぽい関数を探すことにした。 ## 10月8日 - ceval.c:364の"Py_MakePendingCalls"って何? - Pendingが「保留」だから保留を呼び出す???? parsetok.cにある `for(;;)…` 内の関数"Pytokenizer_Get(tok, &a, &b)"の後にpで変数の中身を見ると、aに"for i in range(10):"、bにi in range(10):が格納されていた。tokenizerを見ればなんかわかりそうかも? "range"や"for"やなどの単語を文字列strに格納し、"Pyparser_AddToken()"によって、何に該当するか調べているっぽい。 **少し脱線** for文がどう読み込まれているのか探って、コンパイルしてそうな関数"PyAST_ComplileObject"の中を探索していると、"Pysymtable_BuildOBject"という関数の中で ``` int recursion_limit = Py_GetRecursionLimit(); ``` という行があり、そういえばPythonのデフォルトの再帰上限って小さくて3Sの「アルゴリズム」の授業でも再帰使うとRuntime Errorを吐いていたなぁと思い、デフォルトの再帰上限を変えるのは楽そうなので脱線して調べているとceval.cファイルの436行目にて ``` #ifndef Py_DEFAULT_RECURSION_LIMIT #define Py_DEFAULT_RECURSION_LIMIT 1000 #endif ``` とデフォルトの値が設定されて、 ``` int _Py_CheckRecursionLimit = Py_DEFAULT_RECURSION_LIMIT; void _PyEval_Initialize(struct _ceval_runtime_state *state) { state->recursion_limit = Py_DEFAULT_RECURSION_LIMIT; _Py_CheckRecursionLimit = Py_DEFAULT_RECURSION_LIMIT; _gil_initialize(&state->gil); } ``` と初期化されていた。 そこで新しく再帰上限を定義して、上記のPy_DEFAULT_RECURSIN_LIMITを置き換えた。 ``` #if 1 #define Py_NEW_DEFAULT_RECURSION_LIMIT 10000 #endif ``` すると、手元の普段使っているpython3.8.5では ![](https://i.imgur.com/8R5HIDj.png) となるが、変更してビルドしたpython3.7.9では ![](https://i.imgur.com/pOSmEgV.png) となって、デフォルトの再帰上限の変更に成功した。 しかし、再帰回数を3000にすると、再帰上限は10000なのにsegmentation faultが表示された。 デフォルトの値を変更するだけでいくらでも再帰上限を大きくすることができると思っていたが、どうやらそうではないようだ。 **今後の目標** for文をC言語っぽくする(イテラブルを利用しない)ことを目標にしており、まず構文がどのように読み取られて内容が出力されているかをみようとしたが、とても大きい関数にぶつかって挙動を追うことすら完全にできていない。 一方で、再帰上限の変更は、見つけてから割と簡単にとりかかれた。 for文をC言語っぽくすることは一旦おいておいて(割と何もわからん)、再帰上限のデフォルトの変更を完全にする(セグフォをなくす)ことの方が希望がある。 次の実験でfor文を諦めてとりあえず再帰上限変更をするかどうか決める??? アプローチの仕方が良くないのかもしれない(重要なのは文法だから挙動は見る必要はない??)→Grammarを読んでみる。 https://www.python.org/dev/peps/pep-0306/ これが参考になるかも(2003年に書かれているけど) ## 10月12日 - AST: <https://colab.research.google.com/drive/1o-NO9Gd564ctIC2p06hD3whDY9o2xT-N?usp=sharing> **For文の実装** 挙動を追うのは諦めて、トークンや文法から探索してみる。 〜一時中断〜 **再帰上限の変更** 謎のセグフォを解消する。 方針1 再帰はstackで管理されている?ので、stackのサイズを変更すれば良さそう 方針2 pythonには再帰上限を変更するsetrecursionlimit()という関数があるので、その中身を覗いてみれば良さそう 中身が ``` import sys sys.setrecursionlimit(10000) ``` であるファイルを作り、sysmodule.c:710(関数sys_setrecursionlimit)にブレークポイントを設定してデバッグした。関数内で呼び出されていた関数Py_SetRecursionLimit()の中に入っていくと、_PyRuntime.ceval.recursion_limitと_Py_CheckRecursionLimitという2つの変数が変更されていた。以前Py_DEFAULT_RECURSIN_LIMITを変更しただけだとセグフォが生じたのは、_Py_CheckRecursionLimitしか変更されていなかったのが原因の可能性がある? ↑これを含めて変更したが、やはりsegmentation faultが発生する。 あえてセグフォする再帰関数を書いたファイルをgdb上でrunさせると、frameobject.cの_Py_Frame_New_NoTrackという関数でセグフォしていることがわかった。この関数の周りを見れば良さそう。 https://note.nkmk.me/python-sys-recursionlimit/ 参考になりそうなサイトを見つけた。 Pythonのプロセスに割り当てられたメモリ量を超えるとセグフォする(それはそう)。 自分のMacでは、sys.setrecursionlimit(hoge)でhogeを大きくしても再帰回数が35000回あたりになるとセグフォした。 解決するべきセグフォはこのセグフォとは無関係(多分)だが、再帰上限を変更したpythonではsys.setrecursionlimit(hoge)をしても、やはり再帰回数を3000回にするとセグフォする(なんでだ)。 Pythonのプロセスに割り当てられているメモリ量は変わらないので、やはり何処かでスタックサイズが定義されていて、それを変更すると良い? Ubuntu20.04 on Windowsで、上に書いてある通りに変更してビルドしたところ、セグフォは起きなかった。 ![](https://i.imgur.com/hrhrBMI.png) 元々のPython 3.8.5だと再帰上限は1000回になっている。 ![](https://i.imgur.com/ei3pC8Z.png) ↑マジか ## 10月13日 どうやらセグフォするのは環境依存らしい?(大嘘でした)。 このサイト https://note.nkmk.me/python-sys-recursionlimit/ によると ``` import resource resource.getrlimit(resource.RLIMIT_STACK) ``` でそのpythonのプロセスに割り当てられたメモリ量がわかる。 getrlimit関数の返り値は(a, b)で与えられ、aがソフトリミット、bがハードリミットである(違いはよくわからないけど、ソフトリミットの方が重要そう) このaとbの値をどちらも-1(-1は無制限を表す)に設定したところsegmentation faultは吐かなくなった。 Macだと変更できない(サイト内でもシステムによって制限されているのかもしれないと書かれている)が、Ubuntuだと変更可能である。 同様に、デフォルトの再帰上限を変更していないPythonについてもソフトリミットとハードリミットを-1にすると再帰回数を10^6にしてもセグフォしなかったので、やはり割り当てられたメモリ量の問題であるとわかった。 resourceのあたりを見れば良さそう。 --- Py_NEW_DEFAULT_RECURSION_LIMITの値を10^7にしてビルドした。 そして、ディレクトリpyfiles内に、中身が ``` # import sys # sys.setrecursionlimit(int(input("RecursionLimit: "))) def rec(i, n): if i == n: print(":)") return rec(i + 1, n) n = int(input("n: ")) # 再帰回数 rec(0, n) ``` であるファイルrec.pyを作成した。そして、再帰回数n = 20000, 1000000の2つの場合について、ビルドしたpython3.7で実行した。その結果、いずれの場合も、Segmentation faultが発生した。 ``` $ ./python3.7 pyfiles/rec.py n: 20000 Segmentation fault ``` ``` $ ./python3.7 pyfiles/rec.py n: 1000000 Segmentation fault ``` 一方、中身が ``` import sys sys.setrecursionlimit(int(input("RecursionLimit: "))) # 再帰上限 def rec(i, n): if i == n: print(":)") return rec(i + 1, n) n = int(input("n: ")) # 再帰回数 rec(0, n) ``` であるファイルrec_limit.pyを作成し、再帰上限を10^7として、元々のpython3で同様に実行した。その結果、n = 1000000の場合のみ、Segmentation faultが発生した。 ``` $ python3 pyfiles/rec_limit.py RecursionLimit: 10000000 n: 20000 :) ``` ``` $ python3 pyfiles/rec_limit.py RecursionLimit: 10000000 n: 1000000 Segmentation fault ``` これらのSegmentation faultは、resourceモジュールをインポートし、関数setrlimitを用いて、リソースの消費制限を設定することで解決された。具体的には、中身がそれぞれ ``` # import sys # sys.setrecursionlimit(int(input("RecursionLimit: "))) import resource resource.setrlimit(resource.RLIMIT_STACK, (-1, -1)) def rec(i, n): if i == n: print(":)") return rec(i + 1, n) n = int(input("n: ")) rec(0, n) ``` ``` import sys sys.setrecursionlimit(int(input("RecursionLimit: "))) import resource resource.setrlimit(resource.RLIMIT_STACK, (-1, -1)) def rec(i, n): if i == n: print(":)") return rec(i + 1, n) n = int(input("n: ")) rec(0, n) ``` であるようなファイルsetrlimit_rec.py、setrlimit_rec_limit.pyを作成した。そして、それぞれ、ビルドしたpython3.7、元々のpython3で実行した。その結果、再帰回数n = 20000, 1000000のいずれの場合も、Segmentaion faultは発生しなかった。 以上の結果から、Segmentaion faultの原因として、リソースの消費制限が関係していると考えた。そこで、resourceモジュールの関数getrlimitを用いて、ビルドしたpython3.7、元々のpython3のそれぞれについて、リソースの消費制限を確認した。具体的には、中身が ``` import resource print(resource.getrlimit(resource.RLIMIT_STACK)) ``` であるようなファイルgetrlimit.pyを作成し、それぞれについて実行した。その結果、いずれの場合も出力が全く同じであった。 ``` $ ./python3.7 pyfiles/getrlimit.py (8388608, -1) ``` ``` $ python3 pyfiles/getrlimit.py (8388608, -1) ``` これは、ビルドしたpython3.7と、元々のpython3とで、Segmentaion faultが起きる再帰回数が異なっていた(n = 20000では前者のみSegmentation faultが起きた)ことを考えると、意外な結果であった。 この結果から考察すると、Segmentation Faultの原因はStackのサイズが足りないことではないのかもしれない。 https://qastack.jp/programming/3323001/what-is-the-maximum-recursion-depth-in-python-and-how-to-increase-it このサイトにも、8Mbyte(手元のpythonでは8.3M)のスタックサイズ(ソフトリミットがこれか)では簡単な再帰関数なら約30000スタックフレームに変換されるとかいてあり、これは上の実験において何も手をつけていないpythonでは再帰関数が30000を超えたあたりでセグフォするという結果を説明している。 **水曜日にやったこと** ``` def f(a, n): if a == n: print(a) return f(a + 1, n) f(0, 3000) ``` というあえてセグフォするのがわかってるプログラムを作り、gdbのback trace機能を用いてセグフォの原因をさぐってみた。 ![](https://i.imgur.com/5JTo85G.png) いろいろ調べると、ここでいきなり`_PyFrame_New_NoTrack()`とい関数を呼び出した時に生成される変数tstate, code, globals, localsが全て不正なメモリ(?)にアクセスしている。 また、普段使っているpythonのバージョンが3.8.5であることから、試しに何も手を加えていないpython3.7.9をビルドし、それについてsys.setrecursionlimit(10000)で再帰上限を変更して3000回の再帰をさせると、セグフォを吐いた。このことから3000回でのセグフォはpython3.7.9特有の現象かと思い、python3.8.5のソースコードを入手して10月8日の進捗に書いてある通りに同様にデフォルトの再帰上限を変更して再帰関数を呼んだところ、3000回の再帰ではセグフォを吐かなかったが、4000回にするとセグフォを吐いた。 普段使っているpython3.8.5ではsys.setrecursionlimit関数で再帰上限を変更してもセグフォを吐かないのでもしかしたらターミナルにおいて`Python3`でpythonを起動する場合と`./Python3`でpythonを起動する場合で割り当てられるスタックのメモリが異なるのではないかと思われる。 再帰の深さには関係ないけど有用な資料を発見(今更) https://yigarashi.hatenablog.com/entry/cpython **for文の足がかり?** compiler.cの2334行目にcompiler_forという関数を発見、これ絶対参考になるでしょ… 他にもcompiler_ifとかもあった。 ## 10月15日 **比較を変えたい** 具体的には ``` a = 1 b = '1' if a == b: print(a) ``` のようなコードを書いた時にエラーを吐かないのが気持ち悪いなぁ…ということで型が異なるものは警告を出したい。 警告を出すだけでそのままプログラムを読み続けさせるかどうかはまだ決まってない。 とりあえずの実装としてobject.cの710行目あたりに比較してそうな部分がある(画像) ![](https://i.imgur.com/Vs8XJsj.png) のでこのswitch文の前にPyErr_Format関数をおけばいいだけでは???と思い、実装してmakeすると ![](https://i.imgur.com/GzjjGbC.png) こんなエラーを吐いた。このエラー文を見るとどうやらNoneTypeとlistを比較しているからのようだが、generate-posix-varsを作成するのにこの比較が必要なようで、PyErr_Format関数をそのまま貼り付けるのはまずいようだ。 組み込み関数あたりをみて見る必要がありそう? 片方どちらかがNoneTypeの場合はエラーを吐かないようにしてmakeすると、今度は"int"と"str"を比較していてエラーを吐いた。 makeするのにはどこかの.pyファイル(setup.py?)でのint型とstr型の比較が必要なので、これを変更するのは無理かもしれない https://qiita.com/nakasan/items/bc9ba8eb57f5b7a22698 参考になりそうなサイト(for文の挙動や比較が同様にされているか書いてある) https://docs.python.org/3/reference/expressions.html#comparisons 6.10 にcomparisonsについて書いてある **再帰の実装の終わり** この日の発表にて、TAさんから生成されたバイナリファイルの問題、コンパイルの問題かもしれないという助言を得たので、手を加えていないpython3.7.9を`./conigure --prefix=~hoge~` してビルド。 すると、再帰が3000回を超えてもセグフォしない!!! もしやと思って変更したpython3.7.9を同様にコンパイルオプションをつけずにビルドするとやはり再帰が3000回を超えてもセグフォしない(以下の画像) ![](https://i.imgur.com/tgjiQ5y.png) 結局、`CFLAGS="-O0 -g"`の-O0が悪さをしているようだ。 -O0は実験テキストでは最適化レベルを落とすと書いてあったが、まさか落とした最適化レベルがここで影響してくるとは全く思わなかった。 3日間悩んだセグフォの原因がコンパイルの仕方というオチでした…。 ## イテラブルに頼らないfor文を作りたいなぁ **骨格の形成** https://docs.python.org/ja/3.7/reference/grammar.html とりあえずこれに、パーサジェネレータ(なんじゃそりゃ)がPython/grammarを読み込んで.pyの解析をすると書いてあるのでこれに新しいfor文を定義すれば良さそう。 `for i = 0, i < N, i += 1:`的な感じにしたいので、https://docs.python.org/ja/3.7/reference/simple_stmts.html#grammar-token-expression-stmt を読んでnewfor_stmtを以下のように追加 ![](https://i.imgur.com/u9jejP2.png) この情報をgraminit.h、graminit.cにも追加しなきゃいけないっぽいけどやり方がわからない… →3.9以降用のドキュメント(https://devguide.python.org/grammar/)を見たらmake regen-hoge すれば良さそう。 よくわからないから`make regen-all`したらなんか追加されてた ![](https://i.imgur.com/zt5MqSk.png) このmake regen-hogeをする前にPython/Parser.asdlも変更する必要があるっぽい…(Parser/Python.asdl may need changes to match the Grammarとあるので) それっぽいところを見つけたのでWhileとForの部分を参考にして試しに以下のように実装 ![](https://i.imgur.com/N6HXZiW.png) この状態でビルドしてみると ![](https://i.imgur.com/7nqIZY8.png) switch文のcaseにNewFor_kindがないと怒られるのでcompile.cのcompiler_visit_stmt関数のswitch文とsymtable.cのsymtable_visit_stmt関数のswitch文に以下のように追加 ・compile.cへの追加 ![](https://i.imgur.com/TnezNFZ.png) ・symtable.cへの追加 ![](https://i.imgur.com/igt5jZj.png) (symtable.cには他のFor_kindとかWhile_kindの見よう見まねでやった) compile.cに新しい関数compiler_new_for関数を追加しなきゃいけないけど、とりあえず以下の画像のように中身をいいかげんにして実装 ![](https://i.imgur.com/5Tqmvrm.png) warningが消えたのでビルドして一回試しに実行してみる ![](https://i.imgur.com/yZVv0uj.png) not definedにはならないけど、syntax errorになる、、、 →grammarでの定義を見直す ![](https://i.imgur.com/Z5Ad6aY.png) いろいろ試していると地獄のような文法のfor文が生成されていたのでやっぱりgrammarがおかしい だいぶ無理やりだけど以下のように実装したらsyntax errorは吐かなくなった(ほんまにこれでええんか?) ![](https://i.imgur.com/bFsvDc7.png) ![](https://i.imgur.com/kMbQSyR.png) 「test」が何なのかいまいちわからない…最初は比較のことだと思ったけど、ただの数値でもいいっぽい? 参考になりそうなサイト https://sudonull.com/post/76625-How-does-the-Python-parser-work-and-how-to-triple-its-memory-consumption これに伴ってPython.asdl、symtable.cも以下のように変更 ・Python.asdlの変更 ![](https://i.imgur.com/w8hGcKT.png) ・symtable.cの変更 ![](https://i.imgur.com/yorGWAQ.png) (気持ちとしては、`new_for i = 0, i < 6, += 2`ならiがNAME、0がvalue1、i < 6がtest、value2が2って感じ) **中身を実装していく** compile.cを実装すれば多分いいはず… 挙動自体はwhile文とまぁまぁ似ている気がするのでcompiler_whileを参考にしてみる。 実現させたいバイトコードはこんな感じ ![](https://i.imgur.com/Svlw6B7.png) ↑これは ``` i = 0 N = 5 while i < N: print(i) i += 1 ``` のバイトコード バイトコードとは…?→ https://yigarashi.hatenablog.com/entry/cpython を参照 ![](https://i.imgur.com/GgPpeHV.png) この画像のエラー文で検索するとどうやらPython/ast.cにも何か追加する必要があるっぽい… 抽象構文木に追加する?必要があるみたいです… http://www.dzeta.jp/~junjis/code_reading/index.php?Python%2FAST作成を読む ↑参考になりそう ## 10月19日 ### 異なる型同士を比較したときに警告を表示する Pythonでは ``` >>> 1 == '1' True ``` のように異なる型同士を比較することができる。これは、意図せず異なる型同士を比較してしまった場合に、バグの原因となりうる。そこで、異なる型同士を比較した場合に、警告文が表示されるように変更した。 変更箇所は、Object/object.cの、関数do_richcompare()内の、switch文の箇所である。 ``` switch (op) { case Py_EQ: #if 1 if (strcmp(v->ob_type->tp_name, w->ob_type->tp_name) != 0 && strcmp(v->ob_type->tp_name, "NoneType") != 0 && strcmp(w->ob_type->tp_name, "NoneType") != 0 ) { printf("Warning: '%.100s' and '%.100s' are different types but compared by '%s'\n", v->ob_type->tp_name, w->ob_type->tp_name, opstrings[op]); } #endif res = (v == w) ? Py_True : Py_False; break; case Py_NE: #if 1 if (strcmp(v->ob_type->tp_name, w->ob_type->tp_name) != 0 && strcmp(v->ob_type->tp_name, "NoneType") != 0 && strcmp(w->ob_type->tp_name, "NoneType") != 0 ) { printf("Warning: '%.100s' and '%.100s' are different types but compared by '%s'\n", v->ob_type->tp_name, w->ob_type->tp_name, opstrings[op]); } #endif res = (v != w) ? Py_True : Py_False; break; default: PyErr_Format(PyExc_TypeError, "'%s' not supported between instances of '%.100s' and '%.100s'", opstrings[op], v->ob_type->tp_name, w->ob_type->tp_name); return NULL; } ``` "NoneType"を除外したのは、./python3.7の起動時に"NoneType"と"list"が比較され、警告文が大量に表示されるのを防ぐためである。 ### イテラブルに頼らないfor文を追加する ast.cとcompile.cの実装に取り掛かる **ast.c** このファイル内では、どうやら受け取った文字列を分解して構文木に追加しているようだ。。。 ![](https://i.imgur.com/aOqP0w0.png) ↑普通のfor文を読み取るのast_for_for_stmt関数の冒頭 これをみるに、CHILD(hoge, num)でnum番目の文字列を取得して渡してるっぽいので真似して実装したら、以下のようなエラーを吐いた。 ![](https://i.imgur.com/xUO51tE.png) エラー文で検索するとstmtに属さないものが含まれているからおかしいらしい、、、、 一つだけいかにも型が異なってそうな"augassign"が怪しいのでそこを変更し、以下のように実装 ![](https://i.imgur.com/HNiUOgU.png) するとエラー分が消えてセグフォが表示された! ![](https://i.imgur.com/qTtUyuH.png) このセグフォはまだcompile.cの内容がないために怒っていると考えてcompile.cの実装へ **compile.c** 挙動自体はwhile文っぽいのでcompiler_while関数をコピってちょくちょく変更して実装 →セグフォが治らない。。。(この時の実装がどのような感じだったか記録し忘れてました><) このセグフォの原因を特定する。 今、実現したいバイトコードがこんな感じ ![](https://i.imgur.com/2Vsj8Lk.png) 今現在生成されているバイトコードがこんな感じ ![](https://i.imgur.com/tezHSt2.png) おそらく、INPLACE_ADDする前にiと2を呼び出す必要があるが、iについてはLOAD_NAMEではなくて、STORE_NAMEになっているからだと思う。 LOAD_NAMEをバイトコードに追加する関数が全くわからん。。。 とりあえずLOAD_NAMEで検索するとcompile.cのcompiler_annassign関数内でいかにも追加してそうな関数を発見。 ![](https://i.imgur.com/utNltXj.png) 引数が全くわからない。。。(__annotations__ってなんですか????) バイトコードを見る限り、条件判定(i < 6の部分)では確かにiがLOAD_NAMEされているので、NewFor.testを渡しているexpr_constant関数を見にいく。 さらに関数内のget_const_value関数を見ると、Constant_kindは定数、NameConstant_kindが変数に格納された値って感じがする。 ![](https://i.imgur.com/2U6lKMY.png) この返り値から、s->v.NewFor.target->v.NameConstant.valueでPyObject*が得られると判明。 マクロ、ADDOP_NAMEが示す関数compiler_addop_nameの引数は以下の画像のようになっている。 ![](https://i.imgur.com/D4wQr59.png) 3つ目の引数にs->v.NewFor.target->v.NameConstant.valueを入れるんだろうけど、4つ目がわからん… とりあえずcompiler_annassign関数内では、定義されていないよくわからない変数namesを使えばええか!w ということでcompiler_whileを基本に以下のように実装 ![](https://i.imgur.com/58qxs7v.png) ![](https://i.imgur.com/RYrpiuK.png) namesとかいう変数を使ってるからエラー吐くかなーと思ったらmakeできたのでinstallして実行してみる。 ![](https://i.imgur.com/cfnQYgq.png) うおおおおおおおおおおおおおおおおお 実装できました。 ### REPの追加 ``` REP i, 3: print(':)') ``` という中身のrep.pyを作成し、バイトコードを見ると、 ![](https://i.imgur.com/hKTogr6.png) となっている。2行目の`__annotatinos__`を`range`にすることができればおそらく完成なのだが、rangeをLOAD_NAMEする方法がわからない。 compiler_forに2行追加してcompiler_repを作成した。 ``` compiler_rep(struct compiler *c, stmt_ty s) { basicblock *start, *cleanup, *end; start = compiler_new_block(c); cleanup = compiler_new_block(c); end = compiler_new_block(c); if (start == NULL || end == NULL || cleanup == NULL) return 0; ADDOP_JREL(c, SETUP_LOOP, end); if (!compiler_push_fblock(c, LOOP, start)) return 0; ADDOP_NAME(c, LOAD_NAME, __annotations__, names); #追加 # ADDOP_NAME(c, LOAD_NAME, range, names); # ビルド時にエラー # ADDOP_NAME(c, LOAD_NAME, __range__, names); # ビルド時にエラー # ADDOP_NAME(c, LOAD_NAME, "range", names); # ビルド時にエラー VISIT(c, expr, s->v.For.iter); ADDOP(c, CALL_FUNCTION); #追加 ADDOP(c, GET_ITER); compiler_use_next_block(c, start); ADDOP_JREL(c, FOR_ITER, cleanup); VISIT(c, expr, s->v.For.target); VISIT_SEQ(c, stmt, s->v.For.body); ADDOP_JABS(c, JUMP_ABSOLUTE, start); compiler_use_next_block(c, cleanup); ADDOP(c, POP_BLOCK); compiler_pop_fblock(c, LOOP, start); VISIT_SEQ(c, stmt, s->v.For.orelse); compiler_use_next_block(c, end); return 1; } ``` https://devguide.python.org/compiler/ には > ADDOP_NAME(struct compiler *, int, PyObject *, TYPE) just like ADDOP_O, but name mangling is also handled; used for attribute loading or importing based on name とある。name manglingとは?