Raspberry PiでAArch64なGHCを動かす

2023年の時点で、GHCupはAArch64 Linuxに対応しています。StackはGHCup経由で入ります。詳しくは以下の記事を参照してください：

* [Haskellの環境構築2023](https://zenn.dev/mod_poppo/articles/haskell-setup-2023)

---

最近のRaspberry Pi（3以降）のCPUのアーキテクチャは64bitのARM (AArch64)である。開発中のHaskellパッケージをAArch64でもテストできると良いので、Raspberry Piを使ってAArch64のHaskell環境を整えてみた。

環境の概要は次の通り：

* Raspberry Pi 3 Model B
* Ubuntu Server 18.04.4 LTS aarch64
* Stack 2.1.3
* GHC 8.8.3

記事タイトルにラズパイを銘打っているが特にラズパイ依存な部分はない（IoTめいたこともしない）ので、Linuxが動く他のAArch64環境でも同様にできると思う。

# 64bit Linuxのインストール

まず、Raspbianは32bit版しか用意されていないので、64bitのバイナリを動かしたければRaspbian以外のディストリビューションを入れる必要がある。

* https://www.raspberrypi.org/downloads/
* https://ubuntu.com/download/raspberry-pi

筆者が入れたのは、 Ubuntu Server 18.04.4 LTS (64-bit) である。Raspberry Pi 3向けのバイナリを落としてSDカードに書き込む。詳しい手順はわざわざここに書かなくても良いだろう。

# Haskell環境のインストール

最近はGHC公式でLinux (AArch64)向けのバイナリが配布されているようだ。しかし、管理のことを考えるとghcupやstackなどでGHCをインストールしたい。

ghcupは残念ながら、2020年3月時点でAArch64に対応していない。

* [#102: aarch64 support · Issues · Haskell / ghcup · GitLab](https://gitlab.haskell.org/haskell/ghcup/issues/102)

一方、StackはAArch64に対応しており、公式の手順でインストールできる。しかし `stack setup` でGHCをインストールした際に行われるsanity check（実際にテストプログラムをコンパイルする）には失敗した。

（失敗するのはGHCを使ったファイルのコンパイルであり、GHC自体のインストールは完了している。また、sanity checkに失敗してもGHCiは動作した。）

ちなみに、2020年3月現在のstack (2.1.3)でGHC 8.8系を使うと

> Stack has not been tested with GHC versions above 8.6, and using 8.8.3, this may fail
> Stack has not been tested with Cabal versions above 2.4, but version 3.0.1.0 was found, this may fail

というふうなメッセージが出るが、動作している限りにおいては特に気にしなくて良い。

## Stack 2.3.1

Stack 2.1.3ではStack公式がAArch64向けのバイナリを配布していたが、Stack 2.3.1ではそれがなくなった。

筆者の環境では、Stack 2.1.3で `stack upgrade` を実行するとStack 2.3.1がソースからビルドされた。この際、ビルドに使われるGHC 8.6.5が入っていない場合は自動でダウンロードされる。ラズパイのような低スペックのマシンでは、メモリとストレージの枯渇に気をつけよう（Raspberry Pi 4 8GBモデル／外付けSSDの環境では、時間はかかるが問題なくビルドが完了した）。

Stackが入っていない状態でscriptからインストールを試みた場合にどうなるかは未検証である。

関連：

* [Feature Request: Official ARM support · Issue #2103 · commercialhaskell/stack](https://github.com/commercialhaskell/stack/issues/2103)

## LLVMのセットアップ

ターゲットがx86系の場合はGHC自体がコード生成器（NCG）を持っているため、LLVMを使わなくてもバイナリを出力できる。一方、ARM系に対してはNCGが対応していないため、デフォルトでLLVMバックエンドが使用される。

よって、GHCにAArch64向けのバイナリを吐かせるにはLLVMをインストールする必要がある。

GHCが使用するLLVMのバージョンは、GHCのバージョンによって異なる。GHC 8.6系であればLLVM 6、GHC 8.10ではLLVM 9という具合だ。今回インストールされたGHC 8.8系に対応するのはLLVM 7である。

UbuntuでのLLVM 7のパッケージ名は `llvm-7` なので

```terminal
$ sudo apt install llvm-7
```

でLLVM 7を入れる。

GHCが使用するのはLLVMの `opt` コマンドと `llc` コマンドである。しかし、Ubuntuが提供するこれらのコマンド名はそれぞれ `opt-7`, `llc-7` であり、GHCが期待するコマンド名と食い違っている。食い違いを解消するにはGHCにオプション `-pgmlo=opt-7 -pgmlc=llc-7` を指定すれば良いのだが、いちいち指定すると面倒である。

`/usr/lib/llvm-7/bin` 以下にはサフィックスのつかない `opt` コマンドや `llc` コマンドが存在するので、それをPATHに含めるという手もあるが、一つの環境で複数の異なるGHCを使い分ける場合に環境変数を切り替える必要がある。

そこで、 `stack setup` の際に使用するLLVMのコマンドの名前を環境変数で指定する。具体的には、次のようなコマンドを実行する（GHCの当該バージョンがインストール済みの場合は `--reinstall` を指定し、未インストールの場合は指定しない）：

```shell-session
$ LLC=llc-6.0 OPT=opt-6.0 stack setup 8.6.5 --reinstall
$ LLC=llc-7 OPT=opt-7 stack setup 8.8.3 --reinstall
$ LLC=llc-9 OPT=opt-9 stack setup 8.10.1 --reinstall
```

これで、StackによってインストールされたGHCは `-pgmlo` や `-pgmlc` を指定しなくても正しいLLVMのコマンドを拾ってくれるようになる。

（`stack setup` 時に環境変数を指定すると具体的に何が変わるかというと、 `~/.stack/programs/aarch64-linux/ghc-X.X.X/lib/ghc-X.X.X/settings` というファイルにllcとoptのコマンド名が記入される。）

## libnuma-dev

LLVMのセットアップを完了させてファイルをコンパイルすると、今度は libnuma が見つからないというようなエラー（リンクエラー）が出る。`libnuma-dev` をインストールすれば良い。

```terminal
$ sudo apt install libnuma-dev
```

これで、筆者のラズパイではGHCを使ったHaskellプログラムのコンパイルができるようになった。

## Unable to find installation URLs for OS key: linux-aarch64-tinfo6

実行の際に

```
Unable to find installation URLs for OS key: linux-aarch64-tinfo6
```

というエラーが出る場合は

```shell-session
$ sudo apt install libtinfo5
```

をすると直るかもしれない。

# メモリ消費

一部のパッケージをビルドする際にメモリが足りなくなることがある。具体的には、vectorパッケージをRaspberry Pi 3 Model B（物理メモリ1GB）でビルドしたらOOMが発生した。

このケースでは1GBのswapを用意してやるとうまくいった。物理メモリを2GB以上搭載したRaspberry Pi 4であればswapを用意しなくても大丈夫かもしれない。

# 雑感

当たり前だが、遅い。コンパイル自体も遅いが、筆者の使っているラズパイはLANが100Mbpsとかなのでインターネットからのダウンロードも遅い。ダウンロードに関してはギガビットに対応しているRaspberry Pi 4ならもうちょっとマシかもしれない。

真面目に開発したかったらラズパイとかじゃなくてもっと高性能なARMマシンを用意するとか、クロスコンパイルを検討するべきだろう。（GHCでのクロスコンパイルは茨の道という感じがするが……。）