fish shellを捌く(前編)_pfor文の仮実装まで

これは、東京大学電気電子・電気情報工学科の3年次の実験、「大規模ソフトウェアを手探る」実験においてfish_shellのソースコードを手探り、機能の追加を行ったものです。

目的                             

通常のfor文で並列処理を行うためには
for i in (seq 10)
echo $i &
end
というように&をつけて実行する必要がありますが、それを省略し、
pfor i in (seq 10)
echo $i
end
とするだけで並列処理を実行できるようにしたいというのが目的です。

環境はubuntuを用いています。

 

インストール&ビルド                     

まずはfish-2.7.1-1~artful(Bionic)をfish_2.7.1.orig.tar.gzの形で公式サイトhttps://fishshell.com/からダウンロードします。
その後、適当なフォルダにダウンロードを入れ、ターミナルでそのフォルダに移動します。別にfishのバイナリをインストールするためのフォルダも作っておくと良いでしょう。
この状態で、
tar xfv fish_2.7.1.orig.tar.gz
cd fish-2.7.1/
env CXXFLAGS = “-O0 -g” ./configure --prefix=(インストール先のフォルダのフルパス)
make
make install
とすることでfishがインストールされます。
注意1 --prefixの引数では/から始まるフルパスを記述する必要があります。(例)/home/deno/fish_install

 

中身を覗くためのツール                    

使用するツールとしては2つ。linuxに標準で備わっているgrepコマンドとgdbデバッガです。
grepコマンドでは、find -print | xargs grep -n "(探したい文字列)" とすることで特定
の文字列を含んだファイルの行とその位置を表示することができます。
また-vオプションで条件を満たすものを除外して表示するなど非常に便利なコマンドです。
gdbデバッガはオーソドックスなデバッガで、プログラムのステップ実行など一通りのデバッグを行うことができます。
この2つを併用しながらソースコードの中身に切り込んでいくことにしました。

 

for文の処理を発見するまで                   

まず最初にforと言うワードをgrepしつつ、for (int i=)などの言語仕様による文章を除外して検索していったところ、L"for"という怪しいワードが散見されます。

f:id:fish_osashimi:20181025104229p:plain

これをgrepで調べたところ、ファイルbuiltin.cppのビルトイン関数のforを実行している部分に行き着きました。最初はこれがfor文を実行している部分だと考えましたが、デバッグではその部分を通らず、調べてみたところforと単体で打ち込まれた場合にヘルプを表示するだけの関数だとわかりました。

f:id:fish_osashimi:20181025104748p:plain

この部分を弄って新しいビルトイン関数を実装するのも面白そうではあったのですが、仮題とは無関係なので今は割愛します。

 

一旦仕切りなおし、探索の際出てきたfor文と関係のありそうなワード "parse_keyword_for","symbol_for_header"などのgrep及びgdbによる執念のデバッグの結果、ファイルparse_execution.cpp内にfor文が実際に動いていると思わしき関数run_for_statementを発見しました。

f:id:fish_osashimi:20181025104834p:plain

この関数に内在するfor文にブレークポイントを当ててfor文を実行したところ、確かにこの中でループを実行していることがわかりました。
また、処理を追っていくことで実際に命令を実行している関数も発見しました。

 

f:id:fish_osashimi:20181025105020p:plain

f:id:fish_osashimi:20181025105029p:plain

 

 

pfor文の仮実装まで                      

run_for_statementの呼び出し元を調べ、この関数が呼び出される条件を探ったところ、symbol_for_headerが条件になっているのが確認できました。

f:id:fish_osashimi:20181025105136p:plain

これは処理のブロックを識別するのに使用されていると推測できます。
また、命令のパース部分ではparse_keyword_forが使用されており、こちらは単語としてのforを識別するのに使用されていると推測されます。
これら2つを結びつけている部分がマクロで定義されていたこと、
またparse_keyword_forとL"for"を結びつける場所が見つかっていたことからfor文を識別する要素をこの2つだと判断し、これら2つに対応するsymbol_pfor_header,parse_keyword_pforを挿入(ただし、実際に呼び出される関数はfor文と同じまま)したところ、for文の代わりにpforを使用しても動作するようになりました。

実際の変更点まとめ                      

・parse_keyword_pforに同じ処理をfor->pforとだけ変えて追加
/parse_productions.cpp:151: case parse_keyword_for:
./parse_productions.cpp:295: case parse_keyword_for: {
./parse_productions.cpp:311:RESOLVE_ONLY(for_header, KEYWORD(parse_keyword_for), parse_token_type_string,
./parse_constants.h:128: parse_keyword_for,
./parse_constants.h:143: {parse_keyword_for, L"for"}, {parse_keyword_function, L"function"},

・symbol_for_headerに同じ処理をfor->pforとだけ変えて追加
./parse_constants.h:25: symbol_for_header,
./parse_constants.h:96: {symbol_for_header, L"symbol_for_header"},
./parse_util.cpp:1244: case symbol_for_header: {

./highlight.cpp:1071: case symbol_for_header: {
./parse_execution.cpp:416: case symbol_for_header: {
./parse_execution.cpp:446: assert(header.type == symbol_for_header);
./parse_tree.cpp:170: case symbol_for_header: {
./parse_productions.cpp:289: P(forh, symbol_for_header);

・その他をfor->pforと変えて追加
./parse_tree.cpp:171: return L"for loop";
./parse_productions.cpp:296: return forh;
./parse_productions.cpp:465: TEST(for_header)

注意

./parse_constants.h:128: parse_keyword_for,
./parse_constants.h:143: {parse_keyword_for, L"for"}, {parse_keyword_function, L"function"}
./parse_constants.h:96: {symbol_for_header, L"symbol_for_header"},
この部分に追加する場合、変数の順序がアルファベット順になるようにしなければなりません。

 

処理部分としてはrun_pfor_statementという関数を作ってrun_block_statement()からrun_for_statementの代わりに呼び出させるといいでしょう。今のところは関数の内容は全くのコピーで問題ありません。

f:id:fish_osashimi:20181025113543p:plain

makeから再実行しインストール先のフォルダでfishを実行してみて、このようにできれば成功です。

 

後編では、実際に並列処理を実行していきます。