GHCの最新のソースコードをビルドし、すこしいじる

GHCの最新のソースコードをビルドし、すこしいじる
=================================================

はじめに
-------

GHCのソースコードを読んでみたい。あるいは、GHCに自分の考えた機能を追加してみたい。誰もが一度は思うことだ。そのとき、意外とハードルが高いのが「ダウンロードとビルドの手間」だ。難しいことはほとんどないけれど、道標がないと腰が重くなってしまう。この作業を記事にしよう。

難しいことはほとんどない。ビルドしたあと、すこしだけいじってみて、自分オリジナルのGHCをビルドして悦に入ってみようかと思う。

以下の記事を参考にした。@takenobu-hsさん、良記事をありがとうございます。

[Haskell GHCをソースからビルドする手順 @takenobu-hs](https://qiita.com/takenobu-hs/items/c1309b93ca17b87e5955)

上記の記事のGentoo版のような位置づけになるかと。ただし、できるだけGentooの機能を使わずにstackを使うようにしたので、ほかの環境でも応用がききやすいかとは思う。

前提とする環境
------------

いろいろとインストールずみのシステム上での作業だと、「何が必要なのか」が不明確になるため、最低限のところから作業をすすめていくことにした。この記事をテストする環境は以下の記事で作成される最低限のGentooシステムとする。

[Gentooの最小限のシステムを構築する(BIOS編)](https://qiita.com/YoshikuniJujo/items/ef414c42eb66285202c8)

ほかの環境でも、Haskellのパッケージ以外について、それぞれの環境でのやりかたに変えれば応用できるはずだ。

この記事の書きかた
----------------

GHCのソースコードを取得して、それをコンパイルするなかで、エラーメッセージが出る。そのエラーメッセージをひとつひとつ解決していくというやりかたとする。

コードの取得
----------

まずはコードを取得する。つぎのようなコマンドとなるだろう。

    % git clone --recursive https://gitlab.haskell.org/ghc/ghc.git

しばらく待つ。するとカレントディレクトリ下にディレクトリghcができているので、そこに移動する。

    % cd ghc

./boot
------

mk/build.mkを作成する。mk/build.mk.sampleをコピーして、編集する。

    % cp mk/build.mk.sample mk/build.mk
    % vi mk/build.mk
    BuildFlavour = quick

BuildFlavourはquickがRECOMMENDEDになっていたので、そのようにした。no profilingと書かれているので、profileを無効にすることでビルドがはやくなるのだろう(多分)。

./bootを実行する。

    % ./boot 

./configure
-----------

./configureをする。ここでは、いろいろなエラーが出る。それを解決していく。

    % ./configure
    ...
    checking for ghc... no
    configure: error: GHC is required.

ここでは「GHCがない」と言われている。場合によっては、「GHCのバージョンが古い」と言われるかもしれない。どちらにしても、対処はおなじようにする。

### stackとGHCの導入

stackを導入する。つぎのどちらかを実行する。curlとwgetのうち、システムにあるほうを使う。どちらもない場合は、どちらかをインストールしよう。

    curlでは
    % curl -sSL https://get.haskellstack.org/ | sh

    wgetでは
    % wget -qO- https://get.haskellstack.org/ | sh

パスワード入力が要求されたら、使っているアカウントのパスワードを入力する。また、今後stackを使っていくうえで、PATHに/home/[アカウント名]/.local/binを追加しておくと良い。

    zshでは
    % vi .zshrc
    path=($HOME/.local/bin(N-/) $path)

    bashでは
    % vi .bashrc
    PATH=$HOME/.local/bin:$PATH

GHCを導入する。つぎのようにコマンド入力する。

    % stack setup

するとGHCがホームディレクトリ下の以下のようなパスにインストールされる。

    .stack/programs/x86_64-linux/ghc-ncurses6-nopie-8.2.2/bin/ghc

システムによって、多少パスは変わるが、自分のシステムに置き換えて読み進めてほしい。この新しくインストールされたghcをコンパイルに使うことを、configureで設定する。

    % ./configure GHC=/home/[使用しているアカウント名]/.stack/programs/x86-64-linux/ghc-ncurses6-nopie-8.2.2/bin/ghc
    ...
    configure: error: Happy version 1.19.4 or later is required to compile GHC.

### Happyの導入

こんどはHappyがないと言っている。つぎのようなコマンドでHappyをインストールする。HappyはHaskellのパーサジェネレータだ。

    % stack install happy
    % echo $PATH
    /usr/local/bin:...

コマンドhappyはホームディレクトリ下の、つぎのパスにインストールされる。

    .local/bin/happy

なので~/.local/bin/にパスが通っている必要がある。ZshまたはBashへの設定はしてあるので、一度exitして再度シェルに入り直す。

    % echo $PATH
    /home/tatsuya/.local/bin:/usr/local/bin:...

./configureをもう一度やってみる。

    % cd ghc
    % ./configure --with-ghc=/home/[アカウント名]/.stack/programs/x86_64-linux/ghc-ncurses6-nopie-8.2.2/bin/ghc
    ...
    configure: error: Alex version 3.1.0 or later is required to compile GHC.

### Alexの導入

今度はAlexが必要とのこと。AlexはHaskellの字句解析器ジェネレータ。おなじようにインストールする。

    % stack install alex

再度./configureをする。

    % ./configure --with-ghc=...
    ...
    Confiture completed successfully.
    ...
    ...
    ...

うまく./configureできたようだ。

make
----

これでmakeできる。

    % make

初回のmakeは当然、時間がかかる。僕の環境だと1時間とか2時間とか、そのくらいだ。Core-i3のうえの仮想環境なので、まあ「極非力なマシンで」ということだ。

まずはstage1コンパイラをビルドして、そのstage1コンパイラを使ってstage2コンパイラをビルドするというかたちだ。理由はよく知らない。新しいコンパイラで採用されたコードの最適化の恩恵によって、コンパイラ自体を速くするため、とかか?

makeが成功すると、つぎのパスにコンパイラが作成される。

    ghc/inplace/bin/ghc-stage2

build.mkの編集
-------------

ここまでできたら、何はなくとも、まずやっておくことがある。mk/build.mkにstage=2を設定する。

    % vi mk/build.mk
    stage=2

これをしておかないと、ソースコードの些細な変更ごとにstage1コンパイラが更新され、そのためにstage2コンパイラを1から作り直すはめになる。

バージョン表示の変更
-----------------

いろいろといじりたいが、まずは簡単なところから。バージョン表示をすこし編集してみよう。

    % ./inplace/bin/ghc-stage2 --version
    The Glorious Glasgow Haskell Compilation System, version 8.5.20180403

このようにバージョンが表示されるが、これにYoshikuni Editionを追加してみよう。

    % vi ghc/Main.hs
    showVersion :: IO ()
    showVersion = putStrLn (cProjectName ++ ", version " ++ cProjectVersion ++ " Yoshikuni Edition")

これでmakeしてみよう。

    % make

試してみる。

    % ./inplace/bin/ghc-stage2 --version
    The Glorious Glasgow Haskell Compilation System, version 8.5.20180403 Yoshikuni Edition

言語拡張LambdaCaseに別名をつける
------------------------------

バージョン表示をいじっただけでは、あまりにも簡単すぎるので、言語拡張に別名をつけてみよう。まずは、LambdaCase拡張を使うコードを書く。

    % mkdir tmp
    % vi tmp/lc.hs
    {-# LANGUAGE LambdaCase #-}
    {-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

    main :: IO ()
    main = putStrLn $ hello "Yoshio"

    hello :: String -> String
    hello = \case
            "Yoshio" -> "Hello, darling!"
            n -> "Hi, " ++ n ++ "."

これをコンパイル、実行する。

    % ./inplace/bin/ghc-stage2 tmp/lc.hs
    % ./tmp/lc
    Hello, darling!

さて、このLambdaCase拡張にLCという別名をつけてみよう。まずはソースコードの海をgrepする。

    % grep '"LambdaCase"' */*/* 2>/dev/null
    compiler/main/DynFlags.hs: flagSpec "LambdaCase"   LangExt.LambdaCase,

compiler/main/DynFlags.hsを編集すれば良さそうだ。

    % vi compiler/main/DynFlags.hs
    flagSpec "LambdaCase"    LangExt.LambdaCase,
    flagSpec "LC"            LangExt.LambdaCase,

"LC"の行を追加した。makeする。

    % make

これでLambdaCase拡張の別名としてLC拡張が使えるはずだ。テスト用のコードを編集する。

    % vi tmp/lc.hs
    {-# LANGUAGE LC #-}

LambdaCaseのところをLCに修正した。

    % ./inplace/bin/ghc-stage2 tmp/lc.hs
    % ./tmp/lc
    Hello, darling!

言語拡張の一覧も表示してみよう。

    % ./inplace/bin/ghc-stage2 --supported-extensions
    ...
    LambdaCase
    NoLambdaCase
    LC
    NoLC
    ...

ちゃんと言語拡張LCが表示されている。

まとめ
-----

GHCの最新ソースコードの取得とコンパイルのしかたを、GHCさえインストールされてない状態から順に説明した。いろいろな環境で応用がきくように、できるだけ、ディストリビューション独自の機能は使わないようにして、stackを使うようにした。これで、オレオレGHCを作って遊ぶことができるようになった。

この先はコードリーディングするなり、新機能を実装するなり、各自楽しんでいただけたら幸いだ。