2023年の時点で、GHCupはAArch64 Linuxに対応しています。StackはGHCup経由で入ります。詳しくは以下の記事を参照してください:
最近の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以外のディストリビューションを入れる必要がある。
筆者が入れたのは、 Ubuntu Server 18.04.4 LTS (64-bit) である。Raspberry Pi 3向けのバイナリを落としてSDカードに書き込む。詳しい手順はわざわざここに書かなくても良いだろう。
Haskell環境のインストール
最近はGHC公式でLinux (AArch64)向けのバイナリが配布されているようだ。しかし、管理のことを考えるとghcupやstackなどでGHCをインストールしたい。
ghcupは残念ながら、2020年3月時点でAArch64に対応していない。
一方、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からインストールを試みた場合にどうなるかは未検証である。
関連:
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
なので
$ 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
を指定し、未インストールの場合は指定しない):
$ 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
をインストールすれば良い。
$ 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
というエラーが出る場合は
$ 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でのクロスコンパイルは茨の道という感じがするが……。)