最近のところ、WebAssemblyがアツいですね。NimでもWebAssemblyを試してみたという内容の記事がいくつか見つかります。
というわけで、$n$番煎じ($n \geq 2$)どころかもはや出涸らし気味ですが、Nimでもお手軽にWebAssemblyできるよということを知ってもらうために、この場を借りて私もやってみたいと思います。
必要なもの
- Nim
- emscripten
以上です。
やり方
1. Nimのプロジェクトを作る
まずは普通に作ります。プロジェクト名は nim_emscripten
として話を進めます。
$ nimble init
できたnimbleファイルの設定のうち、次の2つを以下のように書き換えます。
# 拡張子まで書く必要あり。
bin = @["nim_emscripten.html"]
# c以外はダメ(ではないが、諸々面倒なのでcだけにする)
backend = "c"
注意点として、binに指定する出力ファイル名には拡張子まで含める必要があるというものがあります。.html
であればemscriptenが出力するデフォルトのHTMLが出力され、そのままプレビューできますし、.js
であれば、wasmを読み込むjsのコードが出力されます。拡張子を指定しなかった場合、たとえばWindowsであれば拡張子が .exe
なのに中身がjavascriptという謎ファイルが出力されますし、Linux/MacOSでも同様に拡張子なしのjsファイルが出力されます。
2. コンパイラオプションを書く
emscriptenでは、gccのかわりにemccを使います。Nimが吐いたCのソースコードをemccでコンパイルするために、コンパイラオプションを設定する必要があります。そこで、nimbleファイルと同じディレクトリに以下のような nim.cfg
ファイルを作ります。
cc = gcc
# windowsの場合は、"emcc.bat"にする
gcc.exe = "emcc"
# windowsの場合は、"emcc.bat"にする
gcc.linkerexe = "emcc"
gcc.options.linker = ""
# WebAssemblyのVMは32bitアーキテクチャです。
# なので、i386(32bit architecture)をターゲットにします。
cpu = "i386"
# windows.hなどのOS固有のヘッダは利用できないため、常にlinuxをターゲットとしてビルドします。
os = "linux"
# コンパイラに渡すオプションです。
# WASM=1 を指定しなかった場合、emccはデフォルトでasm.jsを出力します。
# EXIT_RUNTIMEが1 (またはNO_EXIT_RUNTIMEが0)の場合、main関数の実行が完了した後にjsから
# Nimで書いたコードが呼べなくなります。
# EXPORTED_RUNTIME_METHODSには、ccall または cwrap、もしくはその両方を指定します。
# 両方を指定する場合は、EXPORTED_RUNTIME_METHODS=ccall,cwarp のようにコンマで区切ります。
passC = "-s WASM=1 -s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS=ccall -O3"
# リンカに渡すオプションは、コンパイラに渡すものと揃えます。
passL = "-s WASM=1 -s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS=ccall -O3"
これでプロジェクトのセットアップは完了です。あとは好きにコードを書くことができます。
3. コードを書いてみる
例えばこんなコードを書いてみます。
var memo = newSeq[int]()
# void __attribute__((used)) hello();
proc hello() {.exportc, codegenDecl: "$# __attribute__((used)) $#$#".} =
echo "Hello, World!"
# int __attribute__((used)) fibonacci(int n);
proc fibonacci(n: int): int {.exportc, codegenDecl: "$# __attribute__((used)) $#$#".} =
if (n < 2):
return n
else:
if memo.len < (n-1):
memo.add(fibonacci(n - 1) + fibonacci(n - 2))
return memo[n-2]
hello()
echo fibonacci(10)
javascriptから呼びたいコードには、exportc
プラグマと、 codegenDecl
プラグマをつける必要があります。codegenDecl
プラグマで変なコード片を生成する必要があるのは、emccがデッドコードを削除してしまったり、インライン関数化する最適化を施してしまったりするからです。
コンパイラ対して、コンパイラの把握していない場所で関数が使用されていることを明示的に通知しなかった場合、吐かれたWebAssemblyのバイナリにはその関数が含まれないことがあります。
ちなみに、emscriptenのツールキットにおいては、この__attribute__((used))
はEMSCRIPTEN_KEEPALIVE
マクロとして提供されています。
このコードを保存したら、普段通りに nimble build
します。
生成された nim_emscripten.html
を開き、こんな画面が表示されたら成功です。
javascriptからfibonacciを呼び出すには、以下のようにします。
console.log(ccall('fibonacci', Int32Array, Int32Array, new Int32Array([10])));
// -> 55
最後に
WebAssemblyのツールキットが随分と充実してきているように感じます。昔はもっとしんどかった。
NimがCのコードを吐いてくれるので、かなりお手軽にWebAssemblyを試すことができました。