Apple Silicon MacでのHaskell/GHCの現状・2022年3月編

この記事は [Haskell Advent Calendar 2021](https://qiita.com/advent-calendar/2021/haskell) の21日目の記事です。

2020年に発表されたApple Silicon (Arm) Macは、2021年に新チップ “M1 Pro/Max” が、2022年には新チップ “M1 Ultra” が登場し、ますます勢いを増しています。Mac使いの皆さんはもう手にされましたか？

新しいアーキテクチャーにはハードウェアだけではなくソフトウェアの対応も重要です。この記事では、2022年3月時点のApple Silicon MacへのHaskellエコシステムの対応状況をまとめます。

この記事では、なるべくRosetta 2を使わず、Armネイティブに動作する環境を構築することを目指します。

# インストールにどれを使うか

UnixでHaskell環境を構築する場合、

* パッケージマネージャーを使う
* GHCupを使う
* Stackを使う

の3つの選択肢があるかと思います。

Mac向けのパッケージマネージャーと言えばHomebrewやMacPortsです。HomebrewはArmネイティブなGHCのインストールに対応しています。一方、MacPortsでGHCをインストールするとIntel版のGHCがインストールされるようです（[当該Portfile](https://github.com/macports/macports-ports/blob/master/lang/ghc/Portfile)）。

GHCupはArmネイティブなGHC等のインストールに対応しています。現時点で最もおすすめな方法です。

後述しますが、stackの公式のインストール方法を使うとIntel版のstackがインストールされるので、要注意です。

# GHC

Macに限らない最近のGHCの動向については以前記事を書きました：

* [GHC 9.2の新機能と、GHCの動向2021](https://zenn.dev/mod_poppo/articles/ghc-9-2-and-future)
* [GHC 8.10とGHC 9.0の新機能](https://zenn.dev/mod_poppo/articles/ghc-8-10-and-9-0)

ここではArmあるいはMac特有のGHCの事情について書いていきます。

## GHC 8.10系

GHC 8.10系は8.10.5以降でApple Siliconに対応しています。

最新のGHC 8.10.7は2021年8月27日にリリースされました。この記事の初出の時点（2021年12月）ではこれが一番安定した選択肢でしたが、その後9.0.2や9.2.2がリリースされたので、現時点（2022年3月）でどれが一番安定しているかは筆者からは何とも言えません。

実行ファイルの生成にはLLVMの `opt` コマンドと `llc` コマンドが必要になります。これについてはGHCupまたはstackの項目で説明します。

8.10.7の既知の問題：

* GHCiでCtrl-A等のキーボードショートカットを使うと表示がバグります。起動時の環境変数に `TERM=dumb` を指定すると回避できます。関連issue：
    * [Unreasonable line-editor behaviour (#20022) · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/20022)
    * [Bump terminfo to fix varargs (#20307) · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/20307)
    * [GHCi: buggy terminal behaviour on Apple Silicon (#20722) · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/20722)
    * [Bump terminfo submodule to 0.4.1.5 (!6489) · Merge requests · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6489)
        * backport needed:8.10 タグがついているのでもしも 8.10.8 が出る場合は修正されるはずです。
* Intel版のstackからArm版のGHC 8.10.7を起動すると、アセンブリーに関するエラー（`error: invalid instruction mnemonic` とか `error: unexpected token at start of statement`）が出る場合があります。Arm版のstackを使うか、より新しいGHCを使いましょう。
    * [GHC should pass --target to CC when available (#20162) · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/20162)

## GHC 9.0系

GHC 9.0系は9.0.2以降でApple Siliconに対応しています。

最新のGHC 9.0.2は2021年12月25日にリリースされました。

実行ファイルの生成にはLLVMの `opt` コマンドと `llc` コマンドが必要になります。これについてはGHCupまたはstackの項目で説明します。

9.0.2の既知の問題：

* FFIを使うプログラムをコンパイルすると `'ffitarget_arm64.h' file not found` と言われる。
    * cabalに ``--ghc-option="`pkg-config --cflags libffi`"`` を指定するか、stackに ``--extra-include-dirs="`xcrun --show-sdk-path`/usr/include/ffi"`` を指定するか、環境変数で ``C_INCLUDE_PATH="`xcrun --show-sdk-path`/usr/include/ffi"`` を指定すると回避できます。
    * [GHC 9.2.1 on an m1 Mac error: `ffitarget_arm64.h` file not found (#20592) · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/20592)

## GHC 9.2系

GHC 9.2系は最初からApple Siliconに対応しています。また、AArch64 NCGが実装されているためLLVMがなくても実行ファイルを生成できます（GHCのバックエンドについては[この記事](https://blog.miz-ar.info/2021/11/backends-of-ghc/)を参照）。

ただ、9.2.1には深刻なバグがあるため、必ず9.2.2（2022年3月5日リリース）以降を使うようにしてください。

# GHCup

[公式ページ](https://www.haskell.org/ghcup/)の手順

```sh
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
```

に従うとArmネイティブなGHCupがインストールされます。そこでインストールされるツール類もArmネイティブなものになります。

もしもRosetta 2でx86\_64なGHCupを導入したい場合は、

```sh
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | arch -x86_64 sh
```

という風に実行すると良いでしょう（`arch -x86_64` に注意）。

GHCupをすでにインストール済みで、自分がどちらを使っているか確認したい場合は、

```sh
file ~/.ghcup/bin/ghcup
```

を実行すると良いでしょう。Armネイティブな場合は `Mach-O 64-bit executable arm64` という風に表示されるはずです。もしもIntel版のGHCupがインストール済みで、Arm版のGHCupを入れ直したい場合は `~/.ghcup` 以下を全削除するか `ghcup nuke` コマンドを実行してから、改めてインストールしましょう。

## LLVMのセットアップ

GHC 8.10系とGHC 9.0系でArmネイティブな実行ファイルを出力するにはLLVMの `opt` コマンドと `llc` コマンドが必要です。HomebrewやMacPortsでLLVM 12を入れましょう。

`opt` コマンドと `llc` コマンドにPATHが通っていれば良いのですが、HomebrewやMacPortsのデフォルトではPATHが通っていなかったり、コマンド名にサフィックスがついたりしています。その場合、GHCの実行時に `-pgmlo` / `-pgmlc` コマンドでコマンド名を教えてあげるというのが一つの方法ですが、毎回それをするのは面倒なのでセットアップ時に環境変数で指定するのが良いでしょう。

Homebrewの場合は `brew install llvm@12` して

```sh
# GHC 8.10.7を入れる場合
OPT=/opt/homebrew/opt/llvm@12/bin/opt LLC=/opt/homebrew/opt/llvm@12/bin/llc ghcup install ghc 8.10.7 --force

# GHC 9.0.2を入れる場合
OPT=/opt/homebrew/opt/llvm@12/bin/opt LLC=/opt/homebrew/opt/llvm@12/bin/llc ghcup install ghc 9.0.2 --force
```

という感じで、MacPortsの場合は `sudo port install llvm-12` して

```sh
# GHC 8.10.7を入れる場合
OPT=/opt/local/bin/opt-mp-12 LLC=/opt/local/bin/llc-mp-12 ghcup install ghc 8.10.7 --force

# GHC 9.0.2を入れる場合
OPT=/opt/local/bin/opt-mp-12 LLC=/opt/local/bin/llc-mp-12 ghcup install ghc 9.0.2 --force
```

という感じで `ghcup` を実行します。`--force` オプションにより、すでに当該バージョンのGHCがインストール済みであっても新しい設定で再インストールを行うことができます。

再インストールするとデフォルトのGHCの設定がリセットされるので、適宜

```sh
ghcup set ghc 8.10.7 # または ghcup set ghc 9.0.2
```

を実行しましょう。

# cabalコマンド (cabal-install)

GHCupで3.4.0.0以降のcabalコマンドを導入できます。

# haskell-language-server

GHCupを使うことで1.4.0以降のhaskell-language-serverコマンドを導入できます。

なお、現時点ではVisual Studio CodeのHaskell拡張でHLSを自動インストールさせるとIntel版のHLSが入るようです。

関連Issue：

* [Support for macOS Big Sur (11.x) and aarch64/M1/Silicon arch · Issue #2008 · haskell/haskell-language-server](https://github.com/haskell/haskell-language-server/issues/2008)

# Stack

Stack 2.7.5の時点ではMac向けの公式バイナリーはIntel版しかありません。[公式のインストール方法](https://docs.haskellstack.org/en/stable/README/#how-to-install)

```
curl -sSL https://get.haskellstack.org/ | sh
```

ではIntel版のstackが入ります。

一方、ArmネイティブなGHCupやHomebrewを使うとArm版のstackをインストールすることができます。

なお、Stack本体のアーキテクチャーと関係なく、 `--arch x86_64`, `--arch aarch64` のオプションで使用するGHCのアーキテクチャーを選択することができます。とはいえ、GHC 8.10.7の既知の問題のところに書いた理由で、Armネイティブなstackを使うのがおすすめです。

GHCupでstackをインストールした場合は、 `stack upgrade` を使ってはいけません。GHCupを使ってstackのバージョンを管理してください。

## LLVMのセットアップ

GHC 8.10系とGHC 9.0系でArmネイティブな実行ファイルを出力するにはLLVMの `opt` コマンドと `llc` コマンドが必要です。HomebrewやMacPortsでLLVM 12を入れましょう。

`opt` コマンドと `llc` コマンドにPATHが通っていれば良いのですが、HomebrewやMacPortsのデフォルトではPATHが通っていなかったり、コマンド名にサフィックスがついたりしています。その場合、GHCの実行時に `-pgmlo` / `-pgmlc` コマンドでコマンド名を教えてあげるというのが一つの方法ですが、毎回それをするのは面倒なのでセットアップ時に環境変数で指定するのが良いでしょう。

Homebrewの場合は `brew install llvm@12` して

```sh
# GHC 8.10.7を入れる場合
OPT=/opt/homebrew/opt/llvm@12/bin/opt LLC=/opt/homebrew/opt/llvm@12/bin/llc stack setup 8.10.7 --arch aarch64 --reinstall

# GHC 9.0.2を入れる場合
OPT=/opt/homebrew/opt/llvm@12/bin/opt LLC=/opt/homebrew/opt/llvm@12/bin/llc stack setup 9.0.2 --arch aarch64 --reinstall
```

という感じで、MacPortsの場合は `sudo port install llvm-12` して

```sh
# GHC 8.10.7を入れる場合
OPT=/opt/local/bin/opt-mp-12 LLC=/opt/local/bin/llc-mp-12 stack setup 8.10.7 --arch aarch64 --reinstall

# GHC 9.0.2を入れる場合
OPT=/opt/local/bin/opt-mp-12 LLC=/opt/local/bin/llc-mp-12 stack setup 9.0.2 --arch aarch64 --reinstall
```

という感じで `stack setup` を実行します。`--reinstall` オプションにより、すでに当該バージョンのGHCがインストール済みであっても新しい設定で再インストールを行うことができます。