まずは ghc の内部構造の話から。
ここ を参考にしながら ghc がどのような流れでバイナリを吐くのか、みたいな話とか。
まずは Core に変換されて、STG に変換したあと LLVM なり gcc なりのバックエンドで実行コードを吐く、みたいな流れみたいです。
Core は Haskell でついてる型からどうやって Primitive 型に変換するか、みたいなのも処理する様子。
あと型変数とかも処理するので、関数に型を渡したりもできる言語っぽいです。
ほとんど Agda みたいに見える。
case で実行するので、 case を使う場所によって遅延評価にしたり正則評価にしたり変えられるみたい。
Core の定義そのものはシンプルっ。Exprは6種類しか無いとか。
ただ ghc 自体は 200k 行とかあって、シンプルな言語だとしても処理系側でカバーしてるだけなのかなー、とか。Core しかり Haskell しかり。
あとは Haskell はグラフだぜー、とか要素いろいろ。良い資料っぽさげ。
次に実際にデバッガで追っていこう、ということに。
Vagrant で box 作ってその中で実行したのでログとか。
- $ vagrant init chef/ubuntu-14.04
- $ vim Vagrantfile
- メモリとか CPU 割り当てとか編集
- $ vagrant up
- $ vagrant ssh
- $ sudo apt-get update
- $ sudo apt-get install git cabal-install vim
- $ sudo apt-get build-dep -y ghc
- $ vim ~/.bashrc
- export PATH=$HOME/.cabal/bin:$PATH
- $ source .bashrc
- $ cabal update
- $ cabal install cabal-install
- $ cabal install happy alex
- $ git clone git://github.com/ghc/ghc.git
- $ cd ghc
- $ ./sync-all -r git://github.com/ghc get
- $ perl boot
- $ ./configure --with-ghc=/usr/bin/ghc
- $ vi mk/buil.mk
- buildFlavor を devel2 に
- GhcDebugged = YES を末尾に
- $ make MAKE="make -j" -j
- 1時間くらい。付けなかったら3時間とかかった
- $ sudo make install
- $ ghci -fobject-code -cpp -DSTAGE=2 -I. -I./stage2 -I./stage2/build -illvmGen:prelude:cbits:specialise:stranal:coreSyn:stgSyn:basicTypes:main:stage2:iface:simplCore:simplStg:cmm:types:codeGen:typecheck:profiling:parser:rename:utils:nativeGen:hsSyn:deSugar:ghci:vectorise:stage2/build: main/DriverPipeline.hs
- すると .o ができる
- DriverPipeline は読みたいので DriverPipeline.hi を消して
- $ ghci -cpp -DSTAGE=2 -I. -I./stage2 -I./stage2/build -illvmGen:prelude:cbits:specialise:stranal:coreSyn:stgSyn:basicTypes:main:stage2:iface:simplCore:simplStg:cmm:types:codeGen:typecheck:profiling:parser:rename:utils:nativeGen:hsSyn:deSugar:ghci:vectorise:stage2/build: main/DriverPipeline.hs
- で b compileFile できる
- $ s <- SysTools.initSysTools (Just "/usr/local/lib/ghc-7.9.20141205")
- $ env <- HscMain.newHscEnv $ DynFlags.defaultDynFlags s
- $ DriverPipeline.compileFile env (DriverPhases.Hsc DriverPhases.HsSrcFile) ("/home/vagrant/hoge.hs", Nothing)
とりあえず compileFile って関数があるので呼んでみよう、というログが上のもの。
ghc の main を直接 ghci で実行すると、「今呼んだらもう遅い」みたいな abort で落ちたので main 以外を実行しようとして見つけたのが compileFile 。
stage 2 の ghc の library とかを使って ghc の DriverPipeline.hs なるソースを読もうとしたログ。
読もうとした時に問題発生。
そもそも Haskell ってどうデバッグしたり追っていくものなのだろう、という疑問。
調べると ghci が出るんだけれど、 ghci の trace って読むために使うものだろうか。
とりあえずそれ以外の選択肢は無かったので ghci で実行してた。
実行することはできたのだけれど問題がいくつかあって
- -I の指定順とか結構謎。オプションの順番変えると動かないとかあったり。
- .o がある .hs を ghci で読み込むと、そのファイルの関数に break point が付けられない
- けど -boot file は .o が無いと参照できないので .o を作らないといけないソースもある
- 実行するためには HscEnv とかが必要なのだけれどそれを作るのが大変
とか。追おうにも追いづらい。
test から見ようにもテスト単位がユニットごとじゃなくて全体用のテストのみで単体の動作を追うのも無理。
Haskeller の人達ってどうやってデバッグ やソースコードリーディングしているのだろう。
あと、小さい単位に分けたりしなくても大丈夫なのだろうか。テストとか。HscEnv とか。(値が20個とかある型だった)
型チェックとかでどうにかしてるのかなー。その辺りの情報が謎。
ghc の Travis とか見たら結構 build コケてたりしていて実際どうやってるのか不安。
最後には Parser や Lexer をソースから読むことに。
Alex とか Happy とか使いつつ。
Parser も実際に動かすことはできなくて、 Haskeller はどうやってデバッグしてるんだろうか、というところでタイムアップ。
結局 Haskeller はどんな風にデバッグしたりソース読んだりテストしたりするのだろう、という疑問が募っておしまい、という感じ。
Core の構造とかを知ることができたのが収穫かなー、といったところ。
0 件のコメント:
コメントを投稿