Emscriptenのstandaloneモードについて


TL;DR;

-o オプションに wasm ファイルを指定すると、wasmerwasmtimeのような WASM ランタイム向けのwasmファイルが出力されます。

Standalone モード

v8.dev にも投稿されていましたが、Web以外での実行を念頭においたモードが Emscripten に実装されました。リリースノートによれば、v1.38.46(2019年9月25日リリース)から実装されていたそうです。

詳しい内容は Emscripten のドキュメント、もしくは v8.dev の記事 "Outside the web: standalone WebAssembly binaries using Emscripten" を読んでいただくとして、簡単にまとめると次のような特徴を持っています。

  • JS が出力されない
  • WASIを出来るだけ利用する
  • WASIで定義されたエントリーポイント_startが定義され、エキスポートされる

JS のコードが出力されない

#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b){
  return a + b;
}

このコードを次のようにコンパイルすると、standalone モードでコンパイルされ、wasm ファイルだけが出力されます。

% emcc -o add-standalone.wasm add.cpp
% ls 
add.cpp add-standalone.wasm

次のように-s STANDALONE_WASMをつけても、standalone モードでコンパイルされます。js ファイルも出力されますが、気にしなくて大丈夫です。

% emcc -o add.cpp -s STANDALONE_WASM
% ls 
add.cpp a.out.js a.out.wasm

出力された wasm ファイルを wasmer を使って実行します。

% wasmer a.out.wasm

なにも出力されません。エントリポイントとなる関数(_start)から実行されるユーザ定義関数がないからです。次のように main 関数を定義すると、1 + 1 の結果がターミナルに出力されます。

#include <stdio.h>

int add(int a, int b){
  return a + b;
}

int main(int argc, char** argv){
  printf("1 + 1 = %d\n", add(1, 1));
  return 0;
}

wasmerでの実行例:

% wasmer a.out.wasm
1 + 1 = 2

WASI が利用される

上記の例では、いくつかのシステムインタフェースが利用されています。例えば

  • コマンドライン引数の数を取得する
  • コマンドライン引数を取得する
  • 標準出力への文字列の出力
  • 0 を実行環境に返す

などです。

通常モードでビルドすると、これらのシステムインタフェースの実装は JS によって与えられます。これに対して standalone モードでは、wasmer などの持つ WASI の実装が利用されます。

wasm2wat を使って作った wat ファイルの先頭を見ると、次のように WASI のシンボルがインポートされていることがわかります。

(import "wasi_unstable" "args_sizes_get" (func (;0;) (type 4)))
(import "wasi_unstable" "args_get" (func (;1;) (type 4)))
(import "wasi_unstable" "proc_exit" (func (;2;) (type 5)))
(import "wasi_unstable" "fd_write" (func (;3;) (type 6)))

なお、wasi-core に存在しないシステムコールは、変わらずenvからインポートできることが期待されています:

(import "env" "__syscall5" (func (;1;) (type 0)))

エントリーポイント:_start

WASI では WASM の使用に定義されている start セクションではなく、_start 関数をエントリーポイントとして利用します。

(export "_start" (func 9))

まとめ

Emscripten に実装された standalone モードを使うことで、簡単に WASI 向けのコードが出力できるようになりました。

Python や R のような言語に対して WASM プラグインを書くときには便利だと思います。

一方、WASI はまだ議論中であることには注意が必要です。定義されているが実装されていないシステムインタフェースもまだありますし、ランタイムごとにも実装状況は異なります。

またファイルアクセスのセマンティックスが UNIX とは異なるため、fstat が wasi-core に定義されていないなど、ファイル入出力を扱うプログラムのコンパイルには追加の作業が必要となります。