GHC Coreのパーサー書いてたけど諦めた
-ddump-simpl
で出力されるSimplified Coreをいい感じに解析して読めるやつを作ろうかと思ってたけどつらすぎたので(少なくともこの方針では)やめた。
以下愚痴を述べますがこれは私が結構無理なことをやろうとしているだけでGHCに(そこまで)非はないと思うし直してくれという意味で言ってるわけでもない。いや直してくれるならありがたいんだけど。
様子
様子です
Coreに色がつき始めたぞ pic.twitter.com/M1SmAFgPOB
— みょん (@myuon_myon) 2019年5月16日
CoreSyn
CoreSynにCoreのSyntaxがある。ASTが用意されてて便利〜かと思いきや、これがなんとコンストラクタが公開されてないものがある(TypeとかCoercionとか)。
まずこの時点で嫌な予感がするよねという感じ。CoreSynは自作することになる。
PprCore
-ddump-simplの出力フォーマットはPprCoreによって制御されている。中を読むとわかるがこれが実はASTとあまり対応がない。
ASTに乗ってない情報が付加されていたり、あるいはASTの情報が一部出力されてなかったりする。
そもそもPprCoreは何かしらの規則やdatatypeに則って書かれたものではなくdumpするという目的を果たすだけのために書かれている感じがありアドホックな処理が大量に入っている。どう考えてもこれに合わせてパーサーを書くとバージョンアップで即死である。
また、面倒な問題の一つにIdInfoがあり、Coreは次のように識別子に関する統計情報をコードに埋め込んで出してくれる。
foobar :: Type
[GblId,
Str=<S,U>,
Unf=Unf{...なんやらかんやら, Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=True)
Tmpl= piyo `cast` ..}]
foobar = ...
当然こんなものを埋め込まれてもHaskellのコードとしてはvalidでないため、ここで専用のパーサーを書かなければいけないこともほぼ確定である。idinfoだけ剥がしてexpressionは組み込みのを使う手もあるがそれも後述の理由により多分上手く行かない。
また、上のCoreコードは実際にあるようなものであるが、他のフィールド間には区切りのカンマがあるのに Tmpl
の前にはカンマがないことや Guidance
だけなぜかHaskellのレコード構文と全然違う謎の構文になっているなど、不可解な点が多々ある。まぁ細かいことを気にしてはいけないのかもしれない。
識別子
ここまでで、パーサーとASTを自作することになった。Lexerはまぁなんとかなるだろうと期待したいところであるが、実はLexerですらGHC提供のものでは動かないことを見ていく。
(ちなみにGHCはGHC拡張をオンにするとParserどころかLexerもゴリゴリ挙動が変わるので結構すごいと思う。MagicHashとか典型例ですね。あとはCPP入れると複数行文字列リテラルを改行をエスケープすることで書けるようになるとか。変態的すぎると思う。)
識別子は主に $
問題と #
問題の2つ?ある。
GHC Coreにはworker/wrapper変換という有名な最適化が入っているがこれにより新たに導入されるworkerには識別子の先頭に $w
マークが付与される。ユーザー側で識別子に使えない文字を割り振ることで衝突等を回避してるんだろうか(しかしRenamerとかで上手く処理することも可能な気はする。そうでもないんだろうか)。しかしこれによりLexerが正しくtokenizeできなくなる(Module.$wfoobar
は qualified operator Module.$
と varid foobar
に分解される)。
次に、出現条件はよくわからないがラムダ式で束縛される変数等で、文字の途中に #
記号が付与されるものがある。 n#_a8qS
みたいな。おそらく元々の変数 n
に対してrenamerでuniqueな名前が振られた結果こういうコトになっているのだと思われる。これもGHCのLexerは(MagicHashを入れた状態で) n#
と _a8qS
としかtokenizeできない。
というわけでLexerも自作することになる。
この辺で諦めた
ここまで来ていや〜厳しいってなった。Lexer Parser ASTまで自作するとか何と戦ってるんだという感じだしそもそもCoreくらいちゃんと読んでって言われるとまぁそうね…という気持ちになる。そもそもここでこれを頑張ったところでGHCのCore付近は毎バージョンごとにめちゃくちゃ変わりまくるのでどうせすぐ動かなくなると思うとメンテも大変そうだしなぁという後ろ向きな感情しかない。
一応前を向いた話もしておくと、GHCにはPluginというコンパイラの処理の途中に手を入れる機能もあるので、一応これをCoreの中で使うという手はある。すると上述のコンストラクタの公開されておらずdumpされた文字列との対応もよくわからないASTと戦うことになるがまぁ全部自作するよりはずっとマシかなと言う感じ。プラグインはインストールとかそういう面倒事を避けたかったのでやりたくなかったがGHC的には正統派な方法な気もしている。
あと本当はSTGを解析するのが本来的な目的だったんだけどPluginでSTGに手を入れるって可能なのだろうか。。。(CoreToSTGを手で呼べばいい?まぁそれはそうかも)