macOS (Apple Silicon) で GHC の WASM バックエンドを使ってみる実験
自分で WASM Backend をビルドしてもいいが、せっかく GHCup で cross compiler の Linux 向け prerelease が容易されているので、これを使って Devcontainer で楽をできないかやってみる。Docker エンジンには、ARM なので、あるていど Intel コンテナも速いらしい Orbstackを使ってみる。
追記:成果物は以下のレポジトリに蓄積していく:
とりあえず Ubuntu をベースにする。
Create Dev Container... で作成をすると案の定 aarch64 になっている。
$ arch
aarch64
たぶん GHCup で配付されているものは aarch64 はサポートしていないはず。要確認。
とりあえずデフォの状態で以下を実行してみる:
$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
$ ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml
$ ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-cross-0.0.8.yaml
$ ghcup install ghc wasm32-wasi-9.10.0.20240412
[ Warn ] New ghc version available. If you want to install this latest version, run 'ghcup install ghc 9.8.2'
[ Error ] [GHCup-00010] Unable to find a download for GHC version 'wasm32-wasi-9.10.0.20240412' on detected platform aarch64-linux-ubuntu-22.04
[ Error ] Also check the logs in /home/vscode/.ghcup/logs
やっぱり aarch には来てない。Intel たてよう。
--platform
を直接指定する方法はあんまりなくて、Dockerfile を使うのがよいようだ
公式のドキュメントを参考に Dockerfile を使うように変えてゆく。
デフォルトの devcontainer.json
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
{
"name": "Ubuntu",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:jammy"
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "uname -a",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
Dockerfile ないし docker-compose 固有の説明はこっちか
devcontainer の Dockerfile を雑に書いたらビルドに失敗してしまった。これローカルのどこに Dockerfile あるんだっけ
Orbstack の volume 上にいそう
Dockerfile で GHCup 使うのはこのへんが参考になりそう
こんな感じにしてみたが、ghcup での wasm backend のインストールに失敗している。
FROM simonwhitaker/gibo AS gibo
FROM mcr.microsoft.com/devcontainers/base:jammy
RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh
RUN pwd
RUN ls ~/
ENV PATH=${PATH}:/root/.local/bin
ENV PATH=${PATH}:/root/.ghcup/bin
RUN apt-get update && \
apt-get -y install --no-install-recommends git sudo jq bc make automake rsync htop curl build-essential lsb-release pkg-config libffi-dev libgmp-dev software-properties-common libssl-dev libtinfo-dev libsystemd-dev zlib1g-dev make g++ wget libncursesw5 libtool autoconf && \
apt-get clean
RUN ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml
RUN ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-cross-0.0.8.yaml
RUN ghcup install cabal 3.11.0.0.2024.4.19
RUN ghcup install ghc wasm32-wasi-9.10.0.20240412
COPY /gibo /usr/local/bin/gibo
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
{
"name": "GHC 9.10 WASM Dev Container",
"build": {
"dockerfile": "Dockerfile"
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "uname -a",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"haskell.haskell",
"mads-hartmann.bash-ide-vscode",
"castello-dev.bash-snippets",
"arahata.linter-actionlint",
"chunsen.bracket-select",
"mindaro-dev.file-downloader",
"mkxml.vscode-filesize",
"vinirossa.vscode-gitandgithub-pack",
"github.vscode-github-actions",
"GitHub.copilot",
"GitHub.vscode-pull-request-github",
"GitHub.remotehub",
"eamodio.gitlens",
"oderwat.indent-rainbow",
"pascalsenn.keyboard-quickfix",
"yzhang.markdown-all-in-one",
"DavidAnson.vscode-markdownlint",
"christian-kohler.path-intellisense",
"ionutvmi.path-autocomplete",
"earshinov.permute-lines",
"mutantdino.resourcemonitor",
"emeraldwalk.RunOnSave",
"timonwong.shellcheck",
"wayou.vscode-todo-highlight",
"DeepInThought.vscode-shell-snippets",
"Swellaby.workspace-config-plus"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "/usr/bin/zsh"
}
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
コンテナ内で直接実行してみるとこんなエラーである:
$ ghcup install ghc wasm32-wasi-9.10.0.20240412
[ Info ] downloading: https://github.com/amesgen/ghc-wasm-bindists/releases/download/20240414T232345/wasm32-wasi-ghc-9.10.tar.xz as file /root/.ghcup/tmp/ghcup-62532b224a424835/ghc-9.10.0.20240412-x86_64-linux-alpine3_18-wasm-cross_wasm32-wasi-release+fully_static.tar.xz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 291M 100 291M 0 0 41.6M 0 0:00:06 0:00:06 --:--:-- 45.9M
[ Info ] verifying digest of: ghc-9.10.0.20240412-x86_64-linux-alpine3_18-wasm-cross_wasm32-wasi-release+fully_static.tar.xz
[ Info ] Unpacking: ghc-9.10.0.20240412-x86_64-linux-alpine3_18-wasm-cross_wasm32-wasi-release+fully_static.tar.xz to /root/.ghcup/tmp/ghcup-ac1be7cefad993e2
[ Info ] Installing GHC (this may take a while)
[ ghc-configure ] checking for unistd.h... yes
[ ghc-configure ] checking size of void *... 8
[ ghc-configure ] configure: error: This binary distribution produces binaries for a target with
[ ghc-configure ] word size of 4, but your target toolchain produces binaries
[ ghc-configure ] with a word size of 8. Are you sure your toolchain
[ ghc-configure ] targets the intended target platform of this compiler?
[ Error ] [GHCup-00841] Process "sh" with arguments ["./configure",
[ ... ] "--prefix=/root/.ghcup/ghc/wasm32-wasi-9.10.0.20240412",
[ ... ] "--target=wasm32-wasi"] failed with exit code 1.
[ Error ] Also check the logs in /root/.ghcup/logs
GHCup で WASM バックエンドを入れる方法を(GHCup の開発者である)hasufell さんが解説していた
これで試し中(Intel on ARM なのでさすがにinstall に数分かかる)
FROM simonwhitaker/gibo AS gibo
FROM mcr.microsoft.com/devcontainers/base:jammy
RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh
RUN pwd
RUN ls ~/
ENV PATH=${PATH}:/root/.local/bin
ENV PATH=${PATH}:/root/.ghcup/bin
RUN apt-get update && \
apt-get -y install --no-install-recommends git sudo jq bc make automake rsync htop curl build-essential lsb-release pkg-config libffi-dev libgmp-dev software-properties-common libssl-dev libtinfo-dev libsystemd-dev zlib1g-dev make g++ wget libncursesw5 libtool autoconf && \
apt-get clean
RUN ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml
RUN ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-cross-0.0.8.yaml
RUN ghcup install cabal 3.11.0.0.2024.4.19
# Sets-up GHC WASM32 backend
RUN apt-get -y install --no-install-recommends zstd
RUN \
git clone https://gitlab.haskell.org/ghc/ghc-wasm-meta.git && \
cd ghc-wasm-meta && \
bash setup.sh && \
source ~/.ghc-wasm/env && \
ghcup install ghc wasm32-wasi-9.10.0.20240412 -- \
--host=x86_64-linux --with-intree-gmp --with-system-libffi
COPY /gibo /usr/local/bin/gibo
ウオー5分くらいかかった挙句失敗した…… sh
だから source
がないんだな。以下のようにしてリトライ:
FROM simonwhitaker/gibo AS gibo
FROM mcr.microsoft.com/devcontainers/base:jammy
RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh
RUN pwd
RUN ls ~/
ENV PATH=${PATH}:/root/.local/bin
ENV PATH=${PATH}:/root/.ghcup/bin
RUN apt-get update && \
apt-get -y install --no-install-recommends git sudo jq bc make automake rsync htop curl build-essential lsb-release pkg-config libffi-dev libgmp-dev software-properties-common libssl-dev libtinfo-dev libsystemd-dev zlib1g-dev make g++ wget libncursesw5 libtool autoconf && \
apt-get clean
RUN ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml
RUN ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-cross-0.0.8.yaml
RUN ghcup install cabal 3.11.0.0.2024.4.19
# Sets-up GHC WASM32 backend
RUN apt-get -y install --no-install-recommends zstd
RUN \
git clone https://gitlab.haskell.org/ghc/ghc-wasm-meta.git && \
cd ghc-wasm-meta && \
bash setup.sh && \
. ~/.ghc-wasm/env && \
ghcup install ghc wasm32-wasi-9.10.0.20240412 -- \
--host=x86_64-linux --with-intree-gmp --with-system-libffi
COPY /gibo /usr/local/bin/gibo
10分でビルド完了した。ただ、root ユーザになってしまっているが、VSCode で開くと今度は vscode
というユーザ名になってしまう。このあたり絶対良く知られた対処法があるはずなので探してみよう。
あと何もしてなくても Orbastack が全 CPU 使っている。ちょっと設定をいじることにする。CPU limit を50 % くらいに。
root でないユーザまわりのはなしはこれやね
面倒なので一旦 "remoteUser": "root",
を devcontainer.json に足す。そうするといいかんじに実行ファイル群はみつかってくれる。
$ wasm32-wasi-ghc-9.10
wasm32-wasi-ghc-9.10.0.20240412: no input files
Usage: For basic information, try the `--help' option.
とはいえ root にするのはなんかやなので、 remoteUser をちゃんと vscode にしてインストールする方向にする(また15分待ちか……)
遂に動いた!!!!
$ cat hello.hs
module Main where
main :: IO ()
main = putStrLn "Hello, WASM World from GHC 9.10!"
$ wasm32-wasi-ghc --make hello.hs
[1 of 2] Compiling Main ( hello.hs, hello.o )
[2 of 2] Linking hello.wasm
$ wasmtime ./hello.wasm
Hello, WASM World from GHC 9.10!
Git レポジトリにおいた
Devcontainer はじめてつかってどちゃんと gpg forward されてるっぽいのうれしすぎるな
なんか extension がちゃんとインストールされてないな。これは devcontainer の固有のことだと思うけどなんなんだろう
extension がインストールされない問題については、とりあえず以下の対症療法で対処した
HLS とか使いたいなと思ったが、wasm32-ghc は GHCi をサポートしていないようで、HLS は武装強化された GHCi みたいなものだから、あんまり devcontainersにする意味はなかったかもしれない。方針としては、イメージのビルドだけ作った Docker image 内でやり、残りは手元に対応する GHC 9.10 alpha (通常版) を入れて、HLS をそれに向けてビルドする、みたいなのが現実的かもしれない。
あと、WASM 向けのライブラリである GHC.Wasm.Prim
とか Wasm の JS FFI については普通の GHC ではサポートされていないので(GHC.Wasm.Prim が通常版にないのは確認できているが JS FFI については確証なし)、そのへんのコンパチをつくるところからやるべきかな
よくかんがえると Dockerfile ベースのビルドシステムなので、WASM の扱いだけ Earthly に任せる戦法がいいかもしれない。Lugendre さんの Earthly Haskell を拡張してつかってみるか
フラグや Hackage HEAD を使って色々細工しても、現状の HLS は GHC 9.10-alpha でビルドできないようだ(GHC API の非互換にやられる)。まあパッチすれば素の ghcide 部分くらいはつかえるだろうが、それするくらいなら手元では GHC 9.8 を使う、くらいで良い気がしてきた
Dockerfile をルートに移した上で、Earthly で WASM をビルドするようにした。
VERSION 0.8
FROM DOCKERFILE --platform=linux/amd64 -f ./Dockerfile -
WORKDIR /workdir
hello:
COPY --keep-ts . .
RUN env
RUN cat /root/.ghc-wasm/env
RUN wasm32-wasi-ghc --make hello.hs
SAVE ARTIFACT hello.wasm AS LOCAL _build/hello.wasm
いったん HLS は GHC 9.8 のものを使う。
とりあえず、0b9ab8d
の時点でこんな感じでやると WASM がビルドできるようになった。
初回実行時はコンテナのビルドに15分ほどかかる。
Earthly さえ入っていれば、以下のようにしてホストの macOS上 に直接 WASM が降ってくるようになる:
$ earthly build +hello
Init 🚀
————————————————————————————————————————————————————————————————————————————————
buildkitd | Found buildkit daemon as docker container (earthly-buildkitd)
Build 🔧
————————————————————————————————————————————————————————————————————————————————
...
$ tree _build
_build
└── hello.wasm
1 directory, 1 file
$ wasmtime ./_build/hello.wasm
Hello, WASM World from GHC 9.10!
TH で環境に応じて WASM 側では FFI を使って native 側ではダミーを生成したりするぞ!と意気込んだが、WASM backend は TH に対応していない(今後対応予定)のでうまくいかなかった。
とりあえずコンパイルは通るが、実行ができない。Tweag の例
があるので、これの frontend/build.sh
を参考にすべし
JS FFI と相互呼出で での で実行できるところまでいった 🎉
$ earthly +hello-js
...
$ cd ./_build/console-log/
$ deno run --allow-read run.ts console-log.wasm
Hello, world! (cabalised, JS FFI!)