【WebAssembly】JS側で作成したtyped arrayをwasm側に渡す

f:id:shogonir:20170514025514p:plain

目次

  1. この記事の目的
  2. wasm側のソースコードを準備
  3. wasmにコンパイルするスクリプトを準備
  4. JSのtyped arrayをwasmに渡す方法
  5. サンプルの動作確認を行うHTMLを準備
  6. まとめ

 

1. この記事の目的

この記事では、JS側で作成したtyped arrayをwasm側に渡す方法を紹介します。
今回はwasm側に渡した配列の和を求めるサンプルコードで紹介します。

GitHubソースコードを上げました。
github.com

 

2. wasm側のソースコードを準備

double型の配列やint型の配列の和を求める処理をC++で書きます。
配列の先頭ポインタと配列長を受け取ることを考えます。
下記の通りsum.cppを保存します。

extern "C"
{

    double DoubleSum(double * doubleArray, int size) {
        double sum = 0;
        for (int i=0; i<size; ++i) {
            sum += doubleArray[i];
        }
        return sum;
    }

    int IntSum(int * intArray, int size) {
        int sum = 0;
        for (int i=0; i<size; ++i) {
            sum += intArray[i];
        }
        return sum;
    }
}

 

3. wasmにコンパイルするスクリプトを準備

まずはC++のコードからwasmファイルを作成するシェルスクリプト(cpp2wasm.sh)を準備します。

emcc sum.cpp \
    -s WASM=1 \
    -s "MODULARIZE=1" \
    -s "EXPORTED_FUNCTIONS=['_IntSum', '_DoubleSum']" \
    -s "DEMANGLE_SUPPORT=1" \
    -o sum.js

 
上記のcpp2wasm.shをDockerコンテナ内で実行するスクリプト(compile.sh)を準備します。

docker run --rm -t -v $(pwd):/src gifnksm/emscripten-incoming sh cpp2wasm.sh

 
この状態で、compile.shを実行するとsum.js, sum.wasmが生成されます。

 

4. JSのtyped arrayをwasmに渡す方法

大まかな流れは下記の通りになります。

  1. JSでtyped arrayを作成
  2. 作成したtyped arrayの要素数から必要な領域を計算し、wasm側のメモリを確保
  3. 確保した領域にtyped arrayの値をコピー
  4. wasm側の和を計算する処理を実行
  5. 不要になった領域を解放

実際にInt32Arrayを受け取ってwasm側で和を計算する処理は下記の通りです。

function intSum(int32Array) {
  var pointer = module._malloc(int32Array.ength * 4); // 32bits=4bytes
  var offset = pointer / 4;
  module.HEAP32.set(int32Array, offset);
  var result = functions.IntSum(pointer, int32Array.length);
  module._free(pointer);
  return result;
}

 
このソースコードを少し説明します。
受け取ったInt32Arrayの要素数*4(int32=32bits=4bytes)分のメモリを確保。
確保した領域のHEAP32における先頭indexを求める。(pointer / 4(int32=4bytes)) HEAP32.set()でtyped arrayの値をwasmのメモリにコピー。
wasmの関数を呼び出して、領域を解放して結果を返却。

 

5. サンプルの動作確認を行うHTMLを準備

Float32Arrayの和を計算するメソッドと、wasmの読み込みを実装したHTMLが下記です。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <title>share typed array</title>
  </head>
  <body>
    <script src="sum.js"></script>
    <script>

var module;
var functions = {};

function readFunctions() {
  functions.IntSum = module.cwrap('IntSum', 'number', ['number', 'number']);
  functions.DoubleSum = module.cwrap('DoubleSum', 'number', ['number', 'number']);
}

function doubleSum(float64Array) {
  var pointer = module._malloc(float64Array.length * 8); // 64bits=8bytes
  var offset = pointer / 8;
  module.HEAPF64.set(float64Array, offset);
  var result = functions.DoubleSum(pointer, float64Array.length);
  module._free(pointer);
  return result;
}

function intSum(int32Array) {
  var pointer = module._malloc(int32Array.ength * 4); // 32bits=4bytes
  var offset = pointer / 4;
  module.HEAP32.set(int32Array, offset);
  var result = functions.IntSum(pointer, int32Array.length);
  module._free(pointer);
  return result;
}

fetch('sum.wasm')
  .then(response => response.arrayBuffer())
  .then(buffer => new Uint8Array(buffer))
  .then(binary => {
    var moduleArgs = {
      wasmBinary: binary,
      onRuntimeInitialized: function () {
        readFunctions();
        console.log(doubleSum(new Float64Array([0.1, 0.2, 0.3])));
        console.log(intSum(new Int32Array([1, 2, 3])));
      }
    };
    module = Module(moduleArgs);
  });

    </script>
  </body>
</html>

 
http-serverを実行してブラウザで確認すると、コンソールに0.6, 6と計算結果が出力されているのが確認できます。

f:id:shogonir:20170523232723p:plain

 

6. まとめ

この記事ではtyped arrayをwasm側に渡す処理を紹介しました。
重要なポイントはJS側でmalloc, freeができてポインタを取得できること、wasmのメモリ(HEAP32, HEAPF64など)のインデックスをポインタから計算できることです。
この技術を応用すれば、ASCIIコードの文字列や、バイト列をやり取りすることもできます。
より具体的には、JS側で通信して取得した画像やzipのバイナリをwasmに渡して処理できます。
この辺はまた別の記事で紹介できればと思います。