(Learn the Hard Way)nodejs-8でのWebAssembly自体を調べてみた

この文書は、nodejs-8で有効化されている[WebAssembly](http://webassembly.org/)がどういう仕組なのかを調査したものです。主題は**WebAssemblyそのものの仕組み**についてであり、普通のWebAssembly記事でやってるような既存のC/C++資産利用やemscripten使用法や速度比較といったブラックボックス利用を主題にした話ではありません。

# WebAssemblyとは

WebAssemblyは、ブラウザ上で実行可能なプログラム実行環境の[標準](http://webassembly.org/)の一つです。

ただし現時点では直接実行させるのではなく、**JavaScript環境から実行させる**ものとなっています。`WebAssembly`オブジェクトを介して、WebAssemblyプログラムのバイナリを`WebAssembly.Module`としてロードし、そこからJavaScript側で関数として取り出して呼び出すことでコード実行させることができるもの、となっています。

そして現在、[v8](https://v8project.blogspot.jp/)等のブラウザに搭載されるJavaScriptエンジン自体が、WebAssemblyプログラムの実行環境を備え、ブラウザ上で利用可能となっています。node.jsではバージョン8から**デフォルトで**ブラウザと同じ`WebAssembly`オブジェクトが利用可能となりました。

## WebAssemblyのMinimum Viable Product(MVP)

WebAssemblyの最初の仕様は、実用性を損なわない範囲で最低限必要な機能のみを備えたものを早期に出荷することを目指して作られ、MVPと呼ばれています。スレッドやGCなどの複雑な機能の追加は、MVP以後にされるべきものとされています。

たとえば、以下は、WebAssemblyの設計指針上の制約ではなく、MVPな要素だとしています:

- module形式だけ実行できる
- 関数の戻り値が一つ
- 扱えるのは32ビットと64ビットの値だけ

将来的には、Post-MVP部分として[追加されていく](https://github.com/WebAssembly/design/blob/master/FutureFeatures.md)ことでしょう。現状の制限がWebAssemblyの設計指針からくるものなのか、単にMVP以後へ後回しされたものでしかないのか注意してみていく必要があります。(この文の全体にわたって、MVP的な内容への言及には"現在"、"現時点"、"現状"と言った形容をつけてあります)

## WebAssemblyプログラムの2つのフォーマット

WebAssemblyのプログラムファイルには、2つの表現形式があります。

- [バイナリフォーマット(.wasm)](http://webassembly.org/docs/binary-encoding/)
- [テキストフォーマット(.wast)](http://webassembly.org/docs/text-format/)

テキストフォーマット(=.wastファイル)では、`(`と`)`で木構造データを表す、[S式](https://github.com/WebAssembly/spec/tree/master/interpreter/#s-expression-syntax)スタイルを採用しています。

そして、JavaScriptから実行させるには、バイナリフォーマット(=.wasmファイル)を使用します。

## WebAssemblyのmodule

現在WebAssemblyプログラムは["module"](http://webassembly.org/docs/modules/)の単位で提供します。すなわちwastファイルのS式のルート要素は`(module)`になります。

moduleは以下のトップレベル要素をバンドルしたものとなっています。

- `(global)`: 名前付きグローバル変数値(要初期値)
- `(memory)`: インデックス値でアクセスするリニアな可変ヒープ領域
- `(table)`: インデックス値でアクセスする固定型要素のリスト
- `(func)`: 関数のコード

- `(import)`: ロード時に渡す必要のある名前付きの値
- `(export)`: 外からモジュールへアクセスできる名前付きの値
- `(type)`: funcの型シグネチャ定義

- `(data)`: memoryの初期化値
- `(elem)`: tableの初期化値
- `(start)`: ロード時に呼び出される関数の指定

`global`/`memory`/`table`/`func`は、それぞれ固有に存在するアクセス命令セットの対象となるオブジェクトであり、外部のJavaScriptと連携するための`import`/`export`の対象にもなります。

`import`/`export`式は、トップレベルで、名前付き`global`/`memory`/`table`/`func`に対して割り当てます。`type`は`func`の型(引数と戻り値の型)の定義で、トップレベルに存在します。ただし、テキストフォーマットでは、`import`/`export`/`type`は`func`/`global`/`memory`/`table`式の内部に埋め込む表現も可能です。

`data`/`elem`/`start`はロード時の初期化用の仕組みです。存在しなくても問題ありません。

ちなみに`table`は、現状ではmoduleにひとつだけ置くことができ、要素は`"anyfunc"`(関数)のみ可能となっています。命令の中に`table`の要素の関数を呼び出す`call_indirect`があります。静的に型チェックする通常の`func`への`call`命令と違って、`call_indirect`では実行時に型チェックをするという違いがあります。

## ツールbinaryen

WebAssembyフォーマットを扱うためのツールは数種類あるけれど、["binaryen"](https://github.com/WebAssembly/binaryen)が、homebrewですでにパッケージとして用意されており、導入しやすいです。

```shell-session
# on macOS
$ brew install binaryen
```

binaryenには以下のコマンドが含まれています: 

- `wasm-as`: wastからwasmへ変換する
- `wasm-dis`: wasmかwastへ変換
- `wasm-shell`: wasmファイルを実行する(unittest用?)
- `asm2wasm`: asm.jsモジュール形式のJavaScriptファイルをwasm/wastに変換する
- `s2wasm`: アセンブリスタイルのWebAssembly(LLVMでのWebAssemblyバックエンド出力)である.sファイルを
  wasm/wastに変換する

今回は、`wasm-as`/`wasm-dis`/`asm2wasm`だけを使います。

## WebAssembly JavaScript API

WebAssemblyでは、JavaScript上での[`WebAssembly`オブジェクトの仕様](https://github.com/WebAssembly/design/blob/master/JS.md)も決められています。

- `WebAssembly.validate(buf)`
- `WebAssembly.compile(buf)`
- `WebAssembly.instantiate(buf, imports)`
- `new WebAssembly.Module(buf)`
- `new WebAssembly.Instance(module, imports)`
- `new WebAssembly.Memory(opts)`
- `new WebAssembly.Table(opts)`
- `new WebAssembly.CompileError`
- `WebAssembly.LinkError`
- `WebAssembly.RuntimeError`

`compile`と`instantiate`は`Promise`です。

また、このAPI仕様で、WebAssemblyの値とJavaScriptの値のマッピングも規定されています。

# 例1: 手書きwastファイルでwasmファイルを作り、nodejsから呼び出すまで

(他の言語は使わずに)簡単なWebAssemblyプログラムをテキストフォーマットで記述して、それをnodejsで実行させることから始めます。

## `adder.wast`を記述

[テキストフォーマット仕様](https://github.com/WebAssembly/spec/tree/master/interpreter/#s-expression-syntax)と[オペレータ仕様](http://webassembly.org/docs/semantics/)を元に、整数加算をする関数addだけを入れたモジュールのwastファイル"adder.wast"は以下のようになります: 

```scm
(module
    (func (export "add") (param $a i32) (param $b i32) (result i32)
        (return (i32.add (get_local $a) (get_local $b))))
)
```

WebAssemblyの関数は(固定サイズの)ローカル変数(`param`及び`local`式)のあるスタックマシンであり、その範疇ではglobalやmemoryは不要です。

現状、変数や値の[型](http://webassembly.org/docs/semantics/#types)は、`i32`、`i64`、`f32`、`f64`の4種類のみです。`add`等[演算命令](http://webassembly.org/docs/semantics/#2-bit-integer-operators)には必ず型指定がつき、引数も戻り値も全て同じ型を使います。このため、4型で[相互変換](http://webassembly.org/docs/semantics/#datatype-conversions-truncations-reinterpretati
ons-promotions-and-demotions)をする命令が多数存在しています。

注意点として、[JavaScript APIの仕様](https://github.com/WebAssembly/design/blob/master/JS.md)により、現状`i64`型なシグネチャの関数は、JavaScript側から直接アクセスできません。

## `adder.wasm`へ変換

binaryenの`wasm-as`コマンドを使って、wastファイルをバイナリのwasmファイルに変換します。

```shell-session
$ wasm-as adder.wast > adder.wasm
```

## 非同期版`use-adder-async.js`

ブラウザ等での外部リソースアクセスは通常非同期処理のため、基本は非同期にロードして使うのがよいでしょう。

```js
//use-adder-async.js
"use strict";

const {promisify} = require("util");
const fs = require("fs");
const readFile = promisify(fs.readFile);

readFile("./adder.wasm").
    then(buf => WebAssembly.instantiate(buf, {})).
    then(source => console.log(source.instance.exports.add(10, 20)));      
```

`instantiate(buf, imports)`の結果は`{module: WebAssembly.Module, instance: WebAssembly.Instance}`であり、`WebAssembly.Instance`の`exports`プロパティにwasmからexportした関数があります。

このコードは、以下のように、特別なオプション等の必要なく実行可能です。

```shell-session
$ ls
adder.wasm      adder.wast      use-adder-async.js
$ node use-adder-async.js
30
```

## 同期版`use-adder-sync.js`

一方、nodejs APIの`fs`にある同期ファイルアクセスを使えば、同期的にもwasmモジュールをロードできます。

```js
//use-adder-sync.js
"use strict";

const fs = require("fs");

const buf = fs.readFileSync("./adder.wasm");
const mod = new WebAssembly.Module(buf);
const inst = new WebAssembly.Instance(mod, {});
const {add} = inst.exports;
console.log(add(10, 20));
```

ちなみに、[WebAssembly JavaScript API](https://github.com/WebAssembly/design/blob/master/JS.md)には、非同期的な`compile`や`instantiate`と、同期的な`Module`と`Instance`が用意されていますが、`compile`で作った`Module`をつかって`new Instance`するなど、ミックスさせて使うことも可能なようです。

# 例2: JavaScriptにはないWebAssembly演算を使ってみる

現在、64ビット整数演算や、ビット処理rotr/rotl/popcnt/ctz等はJavaScriptには存在していません。
そこでWebAssemblyに現在実装済みのこれらを呼ぶだけの関数をexportするmodule "bitops.wast"を書いてみます:

```scm
(module
    (func (export "popcnt") (param $a i32) (result i32)
        (return (i32.popcnt (get_local $a))))

    (func (export "pack") (param $a f64) (result f64)
        (return (f64.reinterpret/i64 (i64.trunc_s/f64 (get_local $a)))))
    (func (export "unpack") (param $a f64) (result f64)
        (return (f64.convert_s/i64 (i64.reinterpret/f64 (get_local $a)))))
      
    (func (export "clz64") (param $a f64) (result i32)
        (return (i32.wrap/i64 (i64.clz (i64.reinterpret/f64 (get_local $a))))))
)
```

現在、64ビット整数なシグネチャを持つ関数はJavaScriptから**直接アクセスする手段がない**ので、なんらかの工夫をする必要がでてきます。そこで、以下のような手段が考えられます。

- memory(ArrayBuffer)を使う
- 32ビット整数2つに割る
- ビット列として埋め込んだ64ビット浮動小数を使う

今回は最後のを採用するために、64ビット整数なビット列を作る機能が必要で、(JavaScript側で処理した)浮動小数の数値を64ビット整数ビット列にする`pack`と64ビット整数ビット列から(JavaScript側で処理する)浮動小数値にする`unpack`の2つの関数をWebAssembly内で定義しました。

`reinterpret`は同じビット列として型情報だけ差し替える命令であり、一方`convert`や`truncate`は数値として型変換する命令です。53ビットを超える64ビット整数値は、64ビット浮動小数で表せられない値があるので、`truncate`では切り詰められることになります。

以降、"wasm-as"コマンドでwastファイルをバイナリwasmにして、JavaScriptでロードして利用する手段は前と一緒です。

そこで今回は、nodejsモジュールっぽい"bitops.js"を作成し、それを使ったコードにしてみました。

```js
//bitops.js
"use strict";

const fs = require("fs");

const buf = fs.readFileSync("./bitops.wasm");
const mod = new WebAssembly.Module(buf);
const inst = new WebAssembly.Instance(mod, {});
module.exports = inst.exports;
```

```js
//use-bitops.js
"use strict";

const {popcnt, pack, unpack, clz64} = require("./bitops.js");

console.log(popcnt(0b101101000)); // => 4
console.log(popcnt(-1)); // => 32
console.log(popcnt(5.5)); // => 2 (as 5.5|0 => 5)
console.log(pack(1)); // => 5e-324: pack (f64) number as i64 bitfield
console.log(unpack(pack(1))); // => 1: unpack i64 bitfield to (f64) number
console.log(clz64(pack(1))); // => 63
```

```shell-session
$ node use-bitops.js 
4
32
2
5e-324
1
63
```

# 例3: asm.jsなモジュールをwasm化して使う

まず、asm.jsなモジュール"adder.asm.js"を手書きします。

```js
// adder.asm.js
function () {
  "use asm";
  function add(a, b) {
    a = a | 0;
    b = b | 0;
    return a + b | 0;
  }
  return {add: add};
}
```

binaryenの"asm2wasm"コマンドでwasmファイルへ変換します。また、asm2wasmは"-o file.wasm"オプションが存在しなければ(色付きの)wast形式で標準出力します。

```shell-session
$ asm2wasm adder.asm.js -o adder.wasm
$ asm2wasm adder.asm.js 
(module
 (import "env" "memory" (memory $0 256 256))
 (import "env" "table" (table 0 0 anyfunc))
 (import "env" "memoryBase" (global $memoryBase i32))
 (import "env" "tableBase" (global $tableBase i32))
 (export "add" (func $add))
 (func $add (param $a i32) (param $b i32) (result i32)
  (return
   (i32.add
    (get_local $a)
    (get_local $b)
   )
  )
 )
)
```

今度のwastには、**`import`式が入っている**点に注目です。`WebAssembly.Instance`の第二引数として、ここで指定された名前の位置に適切なデータを与える必要があります(func内で使ってなくても)。

- 初期ブロック256、最大ブロック256の`memory`
- 初期長0、最大長0の`table`

この"adder.wasm"を呼び出すJavaScriptコードは、以下のようになります。

```js
//use-asmjs-adder.js
"use strict";

const fs = require("fs");

const buf = fs.readFileSync("./bitops.wasm");
const mod = new WebAssembly.Module(buf);
const imports = {
    env: {
        memory: new WebAssembly.Memory({initial: 256, maximum: 256}),
        table: new WebAssembly.Table({
            initial: 0, maximum: 0, element: "anyfunc"}),
        memoryBase: 0,
        tableBase: 0,
    },
};
const inst = new WebAssembly.Instance(mod, imports);

const {add} = inst.exports;
console.log(add(10, 20));
```

wastでの`(import "env" "memory" (memory ...))`は、実行時JavaScript側で`imports.env.memory`に`WebAssembly.Memory`をセットしてある、という意味になります。`table`も同様です。実質使っていない`memoryBase`や`tableBase`は`0`にしています。

この一連の`import`は、"asm.js"言語を成立させるための環境を構成するのに必要となる要素です。他の言語からwasmを生成する場合は、**その言語環境のために**埋め込まれた`import`に、何を与えてやらなければいけないのかを把握し、それをJavaScript側で埋め合わせてやる必要があります。

# 例4: emscriptenからwasmを生成して使う

emscriptenは、llvmを使ってcやc++からJavaScriptコードに変換するシステムですが、その主要コード部分をasm.jsスタイルなコードで生成ができるようになり、さらにその部分をbinaryenによってWebAssemblyに置き換える事ができるように発展していった経緯があります。

## emscriptenの設定

homebrewでインストールしたバイナリがそのままで使えますが、設定の調整が必須です。

```shell-session
$ brew install emscripten binaryen
$ emcc
```

最初にemscriptenのコマンド(Cコンパイラemccなど)を空実行すると"~/.emscripten"ファイル等が生成されます。

しかし、**この"~/.emscripten"のうち、`EMSCRIPTEN_ROOT`、`LLVM_ROOT`、`BINARYEN_ROOT`が正しくない**ので、以下のように修正します(インストールしたパッケージのバージョンに注意)。

```py
EMSCRIPTEN_ROOT = "/usr/local/Cellar/emscripten/1.37.10/libexec"
LLVM_ROOT = "/usr/local/Cellar/emscripten/1.37.10/libexec/llvm/bin"
BINARYEN_ROOT = "/usr/local/Cellar/binaryen/33"
```

ほかは実行可能コマンドのあるディレクトリだけど、**`BINARYEN_ROOT`だけは`bin/asm2wasm`のあるベースの位置になる**のにも注意です。

設定ファイル変更後の最初のemcc等を実行したときにはキャッシュ再生成するため、終了までにすこし時間がかかり、キャッシュ関係の余計な出力がでてきます。設定が間違っている場合はここでエラーや警告がでてきます。

## `adder.c`

wasm化するためのCコードのadder.cです。

```c
/* adder.c */
#include <emscripten.h>

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

`EMSCRIPTEN_KEEPALIVE`マクロは、emccのコマンドラインオプション`-s EXPORTED_FUNCTIONS="['_add']"`でも代用できますが、ここではオプションの単純化のためソースファイル内に埋め込みました。
(C言語での名前`add`は、ABIとしての外部名`_add`になり、外部JavaScriptからはこの`_add`を使います。)

## emscriptenモジュールとしてWebAssembly利用

emscriptenは、C言語の機能を実現するための(`malloc`等の)システム環境を用意した上で、JavaScript上で実行させます。このため、WebAssemblyが使えると言っても、**`WebAssembly`オブジェクトを直接呼び出すのではなく**、emscriptenで生成する**emscriptenのモジュール(.jsファイル)として呼び出し**、その内部の生成コード内で使われるという関係になります。

また、**emscriptenのモジュールには非同期性がある**ので、JavaScript側から呼び出すには、モジュールへセットした初期化終了のコールバック`onRuntimeInitialized`のなかで関数を呼び出すことになります。

```js
// use-emscripten.js
"use strict";

const adder = require("./adder.js");
adder.onRuntimeInitialized = () => {
    console.log(adder._add(10, 20)); // "add()"'s binary name is "_add"
};
```

このemscriptenモジュール"adder.js"を生成するコマンドは、以下のとおりです。

```shell-session
$ emcc adder.c -o adder.js -s WASM=1
$ ls
adder.c    adder.js     adder.wasm
```

`-s WASM=1`オプションを付けたemccコマンドによって"adder.js"と同時に"adder.wasm"も生成されます。

この"adder.wasm"を"wasm-dis"コマンドで変換してみるとわかりますが、emscriptenが生成したwasmファイルに含まれるimport式はかなり多く、自前でimportsオブジェクトを用意するのは難しいものになっています。**importが多くなるのは、C標準ライブラリ実装のための部分が大きい**からです。

## 別手法: `ONLY_MY_CODE`でwasm生成

しかし、この"addeer.c"では、標準ライブラリの機能を一切使用していません。そこで、`-s ONLY_MY_CODE=1`オプションをつかって、(ライブラリ部分を省いた)ソースにあるコードだけでwasmを生成するようにしてみます。

```shell-session
$ emcc adder.c -o adder.js -s WASM=1 -s ONLY_MY_CODE=1 -s SIDE_MODULE=1
$ ls
adder.c    adder.wasm
```

ちなみに、`-s SIDE_MODULE=1`は、外側のemscriptenモジュールjsファイルを生成しないオプションです。この"adder.wasm"の中身を"wasm-dis"でみると、

```shell-session
$ wasm-dis adder.wasm
(module
 (type $0 (func (param i32)))
 (type $1 (func (param i32 i32) (result i32)))
 (import "env" "STACKTOP" (global $import$0 i32))
 (import "env" "STACK_MAX" (global $import$1 i32))
 (import "env" "abortStackOverflow" (func $import$2 (param i32)))
 (import "env" "memory" (memory $0 256 256))
 (import "env" "table" (table 0 0 anyfunc))
 (import "env" "memoryBase" (global $import$5 i32))
 (import "env" "tableBase" (global $import$6 i32))
 (global $global$0 (mut i32) (get_global $import$0))
 (global $global$1 (mut i32) (get_global $import$1))
 (global $global$2 (mut f32) (f32.const 0))
 (export "_add" (func $0))
 ...
```

と、importは7つだけになりました。そして、この7つを自前で用意するコードは以下のようになりました。

```js
//use-onlymycode.js
"use strict";

const fs = require("fs");

const buf = fs.readFileSync("./adder.wasm");
const mod = new WebAssembly.Module(buf);
const imports = {
    env: {
        STACKTOP: 4064,
        STACK_MAX: 5246944,
        abortStackOverflow: function (i32) {throw Error("stack oveflow");},
        memory: new WebAssembly.Memory({initial: 256, maximum: 256}),
        table: new WebAssembly.Table({
            initial: 0, maximum: 0, element: "anyfunc"}),
        memoryBase: 1024,
        tableBase: 0,
    },
};
const inst = new WebAssembly.Instance(mod, imports);
const {_add} = inst.exports;
console.log(_add(10, 20));
```


`STACKTOP`、`STACK_MAX`、`memoryBase`、`tableBase`はemscriptenモジュールの"adder.js"にあった値です。`STACKTOP`や`STACK_MAX`は生成コード内で使われているので、その関係を満たす必要があります。
もしTOPとMAXをともに0にすると`_add`から`abortStackOverflow`が呼ばれます。

# 例5: メモリで数値配列を扱うWASTプログラムを書く

ここまでは、WebAssembly環境調査のために単純な単数値のみを扱う`add`関数のみで扱っていました。ここでは、WebAssembly言語自体の調査として、より高度な処理を書く導入のために、memoryをつかいループを行うプログラムを書いてみます。

## "vector.wast"

メモリを用意し、メモリ上の2つの配列を足し合わせる`addTo`を行うモジュール"vector.wast"は、以下のようになります。

```scm
(module
    (memory $0 (export "heap") 1 256)
    (func (export "addTo") (param $a i32) (param $b i32) (param $len i32) (result i32)
        (loop $loop
            (if (i32.eqz (get_local $len))
                (return (i32.const 0)))
            (i32.store 
                (get_local $a)
                (i32.add (i32.load (get_local $a))
                         (i32.load (get_local $b))))
            (set_local $a (i32.add (get_local $a) (i32.const 4)))
            (set_local $b (i32.add (get_local $b) (i32.const 4)))
            (set_local $len (i32.sub (get_local $len) (i32.const 1)))
            (br $loop)
        )
    )
)
```

32ビット整数配列を指定した長さ部分だけ、ループで足し合わせるコードです。引数の`$a`と`$b`にメモリ上のアドレス、`$len`には数値配列の長さを想定します。

- 現状、`(memory)`は一つだけ指定できます(ただし、binaryen/33では、ローカル名`$0'でないと失敗する)
- 現状、シグネチャとして`(result )`は必ず数値一つ返す必要があります。
- `(loop label ...)`は、内部の`(br label)`によって繰り返し処理します。`(br)`しなければ繰り返しません。
- `(i32.store address value)`や`(i32.load address)`のアドレスはメモリのバイトオフセットです。
- メモリデータのバイトオーダーはJavaScriptの`TypedArray`と同じ(リトルエンディアン)です。

これまで見てきたツール生成コードのように`import`するのではなく、メモリは`export`して使うこともできます。他とメモリを共有しない単独のwastモジュールとしてはexportのほうが使いやすいでしょう。

ちなみに、WebAssemblyの各命令の呼びだしかたは、[WebAssemblyのtestsuite](https://github.com/WebAssembly/testsuite)の例を参考に類推しました。

同様に、"wasm-as"コマンドでwasmファイルに変換します。

```shell-session
$ wasm-as vector.wast > vector.wasm
```

## "use-vector.js"

このvector.wasmを呼び出すコードは以下のようになります。

```js
// use-vector.js
"use strict";

const fs = require("fs");

const buf = fs.readFileSync("./vector.wasm");
const mod = new WebAssembly.Module(buf);
const imports = {};
const inst = new WebAssembly.Instance(mod, imports);
const {heap, addTo} = inst.exports;

const len = 8;
const a = 0 * Int32Array.BYTES_PER_ELEMENT,
      b = 8 * Int32Array.BYTES_PER_ELEMENT;
const memA = new Int32Array(heap.buffer, a, len);
const memB = new Int32Array(heap.buffer, b, len);
for (let i = 0; i < len; i++) memA[i] = i + 1;
for (let i = 0; i < len; i++) memB[i] = (i + 1) * 10;

addTo(a, b, len);
console.log(memA); //=> [11, 22, 33, 44, 55, 66, 77, 88]
console.log(memB); //=> [10, 20, 30, 40, 50, 60, 70, 80]
```

importもしくはexportした`WebAssembly.Memory`オブジェクトの`buffer`プロパティが`ArrayBuffer`であるので、JavaScript側でそこから処理させる型にあった`TypedArray`に切り出して使えます。


# 追記: WebAssembyは速い?のか

「[なぜWebAssemblyはasm.jsより速いのか](http://qiita.com/chikoski/items/cd59df64b4d820125864)」という記事がありますが、注意して読めば、その「速い」は**条件付きでの「速い」**であるいう話なのです:

- コードのファイルサイズが小さい => 起動時だけ速い
- 64ビット整数演算ができる => 64ビット整数値を使わないなら同じ
- メモリオフセットが使える => (構造体無しで)数値配列を使うだけなら同じ
- popcntやcopysignが使える => ビット演算しないなら同じ
- emscriptenが賢くなった => 最初からコンパクトなコードなら同じ

という意味です。

ということで、実際に比較可能な数値配列のみのコードを書いてみました。

- https://gist.github.com/bellbind/dd1c0cd9cbe422caff8dcdae1010ad37

ES6コードはmap/reduceを多用する関数型スタイルのコードで、これがベースとなっています。wasmやasm.jsのコードは、この畳込み部分をメモリを使うように差し替えたものです。

ベンチマークとして、セルオートマトンの次ステップ生成の時間を図っています。初期化時間は計測していません。結果は、以下の通りでした。

|      | firefox-54 | chrome-59 |
|------|-----------:|----------:|
| ES6  | 70ms       | 240ms     |
|wasm  | 80ms       | 130ms     |
|asm.js| 80ms       | 210ms     |

記事にあるように**firefoxではasm.jsコードとwasmコードでは速度にほぼ差がない**ようです。ただし、Arrayの`map`や`reduce`メソッドを使ったコードのほうが若干速いです。最初のTypedArrayの`set`をしないコードでnext全体を移植すれば早くなるように見えますが、実行する計算の数から考えれば結果はほぼ変わりません。実際に`next()`呼び出し全体を囲って図ったとしてもほぼ同じ結果になりました(chromeも含めて)。

確かにchromeではwasmコードが最速でしたが、ただこれはv8でArrayのインデックスアクセスがかなり高速化されたのに、`map`や`reduce`等のメソッドがいまだ高速化されていない、という現状によるものです。ここ2年のchromeとfirefoxの速度の関係はこのようなものです。

ただ、**wasmでは実行速度面でのブラウザ間の違いが小さい**、とは言えるでしょう。

# まとめ

- WebAssemblyは、JavaScriptから呼び出す実行環境で、JavaScriptエンジンが備える機能である
- WebAssemblyプログラムは、バイナリフォーマットwasmとテキストフォーマットwastがある
- WebAssemblyプログラムはmodule構造である
- wasm moduleにはimportsとexportsがあり、JavaScript側ではimportsを用意し、exportsを利用する
- wasm moduleは、JavaScriptからは非同期でロードするものであるが、nodejsでは同期ロードも可能である
- WebAssemblyには、JavaScriptにはないビット演算や64ビット整数演算が存在する
- 64ビット整数のシグネチャを持つ関数は、JavaScript側から直接呼び出せない
- 他プログラミング言語から変換したwasmには、その言語環境固有のimportsを用意する必要がある
- emscriptenでのWebAsemblyは、基本emscripten moduleの内部で利用し、直接触らないものである
- 追記: WebAssemblyだからといって劇的に早くなるわけじゃないが、ブラウザ間の速度差は小さい