main関数について。
GHCのコードを眺めていてmain-isというオプションがあることに気づきました。これを使うとmainとして使用する関数を変える事ができるみたいです。
試しにC言語風のmainを書いてみました。
-- Cmain.hs module Cmain() where import Main (main) import System (getArgs, getProgName) cmain :: IO () cmain = do progname <- getProgName args <- getArgs let argv = progname : args argc = length argv in argv `seq` argc `seq` main(argc, argv)
引数の第0要素にはCと同じく実行ファイル名*1を入れました。
これを使って、catプログラムを書いてみました。(見た目上手続き言語っぽく書いてみようと思いましたが、微妙でした。)
-- Main.hs module Main where import Control.Monad import System.Exit main :: (Int, [String]) -> IO () main(argc, argv) = do { when (argc < 2)$do { putStrLn( usage(argv!!0) ); exitWith( ExitFailure(1) ); }; putStr =<< readFile(argv!!1); }; usage :: String -> String usage prog = "usage : " ++ prog ++ " <file name>"
コンパイルするときには
% ghc --make -main-is Cmain.cmain Cmain.hs -o cat
みたいにCmain.hs内のcmainをmainに指定します。
実行例
% ./cat usage : cat <file name> % ./cat Main.hs -- Main.hs module Main where import Control.Monad import System.Exit main :: (Int, [String]) -> IO () main(argc, argv) = do { ...
これ自体はただのお遊びなのですが、どういう流れでmainがコンパイルされるかに興味がでてきたのでメモ書き程度ですが、調べた事を書きます。
GHCのコンパイラ自体のmainは、compiler/main/Main.hsから始まっていて、
- doMake
- ソースファイルをメイク。
- mapM (compileFile ... ) ... とか何かかなりわかりやすい
プリプロセッサとかアセンブラとかをパイプライン実行
compiler/main/DrivePipeline.hs
- compileFile (省略)
- runPipeline (省略)
- pipeLoop
Hscフェーズ。hscCompileOneShotを呼び出しコンパイル開始。
- hscCompileOneShot
- hscFileFrontEnd
- ソースをパース->リネーム->型検査->脱糖している。
- 調べたところ型検査時にmainを検出している模様
型検査とリネーム。
- compiler/typecheck/TcRnDriver.lhs
- tcRnModule
- import文の解析などをしている
- tcRnSrcDecls
- tc_rn_src_decls
- 基本的に型検査とリネームを行うが、ここで現在のモジュールがMainであるかどうかをチェックしている。
- checkMain
- Mainモジュールの名前, main関数の名前を見つけて、check_mainに渡す
- check_main
- main関数があるかどうかを検索する。(Mainモジュールにはmain関数がなければいけないというチェックをここでしているようだ)
- main関数を見つけたら、runMainIO関数にmain関数を引数として渡すという内容の関数呼び出しを作って、root_main_idという変数にバインドしている。
とりあえずここで終了。
これを逆にたどると、
root_main_idを呼ぶ -> runMainIO mainが呼ばれる -> mainが実行される
という感じですね。
GHCのコンパイラをざっと読んでみて思いましたが、すごく読みやすいんじゃないかと思います。(他のコンパイラのコードと比較してという話)
Haskellは参照透明なので、いちいち変数がどこで更新されているかといった状態遷移を追わなくていいってところが大きいんじゃなかと思います。普段は参照透明であることのありがたみはあまり感じないですが、規模が大きくなってくると違うんではないでしょうか。
とりあえずroot_main_idというアセンブリにつながりそうなキーワードが見えて来たので、最終的にどんな風にアセンブリが生成されるのかを追ってみようかと思います。目指すはHaskellのコードから生成されるアセンブリが分かる人(笑)