この記事は Haskell Advent Calendar 2022 の 19 日目の記事です.
はじめに
2022 年 11 月,Glasgow Haskell Compiler (GHC) に WebAssembly バックエンドがマージされたという投稿がありました.
2023 年の早い時期にリリースが予定されている GHC 9.6.1 で, 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 の開発者が開発しているものです.
Haskell を WebAssembly にクロスコンパイルする試みとして,これまで Asterius や WebGHC といったプロジェクトが存在していましたが,そのうち 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/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 に,お試し用の資材が格納されています.
このリポジトリは Nix Flake となっていて,Nix 環境さえあれば,WebAssembly 対応版の 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 に,最新の 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 を参考にしました).
- 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 のリリースが楽しみですね!