Glasgow Haskell Compiler の WebAssembly バックエンドを一足早く動かしてみる

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

## はじめに

2022 年 11 月，Glasgow Haskell Compiler (GHC) に WebAssembly バックエンドがマージされたという投稿がありました．

* [WebAssembly backend merged into GHC - Tweag](https://www.tweag.io/blog/2022-11-22-wasm-backend-merged-in-ghc/)

2023 年の早い時期にリリースが予定されている [GHC 9.6.1](https://gitlab.haskell.org/ghc/ghc/-/milestones/375) で， WebAssembly バックエンドが利用可能になる見込みです．

ということで，開発途中の GHC を動かして，Haskell から WebAssembly へのクロスコンパイルを一足早く試してみたいと思います．


## GHC WebAssembly バックエンドについて

記事執筆時点では，GHC の WebAssembly コンパイラは，`ghc` 相当のコマンドである `wasm32-wasi-ghc` という名前のコマンドで提供されています．
このコマンドは，hs ファイルを入力として受け付け，出力として wasm バイナリを出力します．
この wasm バイナリは，WASI がサポートされている `wasmtime` などのランタイム上で実行できます．

GHC WebAssembly バックエンドは，Cmm 形式から wasm 形式への変換を担う NCG (Native Code Generator) が心臓部となっています．
Haskell のソースである hs 形式から Cmm 形式への変換には，従来どおり GHC が使われます．

GHC WebAssembly バックエンドの特徴として，GHC の RTS が使えるということが挙げられています．
RTS で提供される STM やプロファイリング機能も，GHC WebAssembly バックエンドで動くとされています．

今回 GHC に取り込まれた WebAssembly バックエンドは，[Asterius](https://github.com/tweag/asterius) の開発者が開発しているものです．
Haskell を WebAssembly にクロスコンパイルする試みとして，これまで Asterius や [WebGHC](https://webghc.github.io/) といったプロジェクトが存在していましたが，そのうち Asterius が WebAssembly バックエンドの前身となっています．
今回の GHC への WebAssembly バックエンドのマージを契機に，Asterius のリポジトリはアーカイブされ，代わりに GHC の使用が推奨されています．

GHC の WebAssembly 対応については，GHC の Wiki にいろいろと情報が載っています．

* https://gitlab.haskell.org/ghc/ghc/-/wikis/WebAssembly-goals
* https://gitlab.haskell.org/ghc/ghc/-/wikis/WebAssembly-backend
* https://gitlab.haskell.org/ghc/ghc/-/wikis/javascript

また，最新情報が [Haskell Discourse](https://discourse.haskell.org/) にてウィークリーアップデートとして随時発信されています．

* https://discourse.haskell.org/t/ghc-webassembly-weekly-update-2022-11-30/5380
* https://discourse.haskell.org/t/ghc-webassembly-weekly-update-2022-12-07/5417
* https://discourse.haskell.org/t/ghc-webassembly-weekly-update-2022-12-14/5448


## GHC WebAssembly バックエンドを動かしてみる

記事執筆時点では， GHC 9.6 はリリースされていません．
GHC の WebAssembly バックエンドを動かすには，開発途中の GHC のビルドを使用します．

GHC のリポジトリの 1 つである [ghc-wasm-meta](https://gitlab.haskell.org/ghc/ghc-wasm-meta) に，お試し用の資材が格納されています．
このリポジトリは Nix Flake となっていて，[Nix](https://nixos.org/) 環境さえあれば，WebAssembly 対応版の GHC のセットアップが簡単にできるようになっています．

[ghc-wasm-meta](https://gitlab.haskell.org/ghc/ghc-wasm-meta) の README 通りに進めてみます．

`nix shell` コマンドで， WebAssembly 対応版の GHC のダウンロードからセットアップまでを一気に行います．

```
% nix shell https://gitlab.haskell.org/ghc/ghc-wasm-meta/-/archive/master/ghc-wasm-meta-master.tar.gz
```

環境によっては，次のようにして `nix-command` や `flakes` 機能を有効化する必要があるかもしれません．

```
% nix --extra-experimental-features nix-command --extra-experimental-features flakes shell https://gitlab.haskell.org/ghc/ghc-wasm-meta/-/archive/master/ghc-wasm-meta-master.tar.gz
```

あとは，`wasm32-wasi-ghc` コマンドで hs ファイルを wasm 形式にビルドします．

```
% cat hello.hs
main = putStrLn "hello world"

% wasm32-wasi-ghc hello.hs -o hello.wasm
[1 of 2] Compiling Main             ( hello.hs, hello.o )
[2 of 2] Linking hello.wasm
```

`wasmtime` コマンドで `hello.wasm` を実行します．

```
% wasmtime hello.wasm
hello world
```

たしかに，WASI 環境上で Haskell プログラムが動くことが確認できました．

ちなみに，生成された wasm ファイルのファイルサイズは 1 MB ほどでした．

```
% file hello.wasm
hello.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

% ls -l hello.wasm
-rwxr-xr-x 1 owner owner 1182422 Dec 18 21:14 hello.wasm*
```

## ナイトリービルド版でも動かしてみる

GHC の [GitLab Pipelines](https://gitlab.haskell.org/ghc/ghc/-/pipelines) に，最新の GHC のナイトリービルド版があります．
記事執筆時点の最新ナイトリービルド版 (2022/12/16 版) でも，WebAssembly バックエンドを動かしてみたので，そのときのメモを残しておきます．
なお，筆者の環境は Ubuntu 22.10 です．

GHC では様々なプラットフォーム (Linux x86_64, Linux x86, Linux AArch64, Windows x86_64, ...) 向けのパッケージが提供されていますが，そうしたパッケージの 1 つとして，WebAssembly へのクロスコンパイル向けのパッケージがビルドされています．
筆者は，そのうちの 1 つである `ghc-x86_64-linux-ubuntu20_04-cross_wasm32-wasi-int_gmp-release.tar.xz` というパッケージを使用しました．

このパッケージを展開すると，`bin` ディレクトリには `wasm32-wasi-ghc` などのコマンドが格納されています．

```
% ls bin/
wasm32-wasi-ghc@                wasm32-wasi-ghc-pkg@                 wasm32-wasi-hsc2hs@
wasm32-wasi-ghc-9.5.20221216*   wasm32-wasi-ghc-pkg-9.5.20221216*    wasm32-wasi-hsc2hs-ghc-9.5.20221216*
wasm32-wasi-ghci@               wasm32-wasi-hp2ps@                   wasm32-wasi-unlit@
wasm32-wasi-ghci-9.5.20221216*  wasm32-wasi-hp2ps-ghc-9.5.20221216*  wasm32-wasi-unlit-ghc-9.5.20221216*
```

これらのコマンドを実際に使えるようにするためには，他のパッケージのときと同様，`configure` および `make install` によるセットアップが必要です．
セットアップ時には，少なくとも以下のツール群をあらかじめ用意しておく必要があります (入手先は [autogen.json](https://gitlab.haskell.org/ghc/ghc-wasm-meta/-/blob/master/autogen.json) を参考にしました)．

* wasi-sdk
* libffi-wasm

これらのツール群を用意しないで進めようとしたら，`configure` でエラーが出てハマりました...．

```
% ./configure --host=x86_64-linux --target=wasm32-wasi --prefix=$HOME/.ghc-wasm
...
checking size of void *... 8
configure: error: This binary distribution produces binaries for a target with
                  word size of 4, but your target toolchain produces binaries
                  with a word size of 8. Are you sure your toolchain
                  targets the intended target platform of this compiler?
```

次のように `configure` および `make install` を実行して，GHC をセットアップします．

```
% ./configure --host=x86_64-linux --target=wasm32-wasi --prefix=$HOME/.ghc-wasm
% make install
```

これでナイトリービルド版の GHC が動くようになりました．

```
% wasm32-wasi-ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.5.20221216

% wasm32-wasi-ghc hello.hs -o hello.wasm 
[1 of 2] Compiling Main             ( hello.hs, hello.o )
[2 of 2] Linking hello.wasm

% wasmtime hello.wasm
hello world
```

たしかに WebAssembly へのクロスコンパイルができるようになっていることを確認できました．


## まとめ

GHC に WebAssembly バックエンドがマージされたということで，開発途中の GHC を動かして，WebAssembly へのクロスコンパイルを試してみました．

まだまだ出ている情報が少なくて，Hello World を動かしてみただけで終わってしまいましたが，ポテンシャルは高そうなので，他にもいろいろと動かしてみたいと思いました．
JavaScript FFI はまだこれから (RTS のリファクタリングが必要のこと) だそうですが，前身である Asterius では JavaScript FFI もちゃんと動いていたので，今後に期待したいと思います．

GHC 9.6.1 のリリースが楽しみですね！

## 参考

* [WebAssembly backend merged into GHC - Tweag](https://www.tweag.io/blog/2022-11-22-wasm-backend-merged-in-ghc/)
* [Glasgow Haskell Compiler / ghc-wasm-meta · GitLab](https://gitlab.haskell.org/ghc/ghc-wasm-meta)
* [Haskell Wiki](https://gitlab.haskell.org/ghc/ghc/-/wikis/home)
* [Haskell Discourse](https://discourse.haskell.org/)