目次
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に渡す方法
大まかな流れは下記の通りになります。
- JSでtyped arrayを作成
- 作成したtyped arrayの要素数から必要な領域を計算し、wasm側のメモリを確保
- 確保した領域にtyped arrayの値をコピー
- wasm側の和を計算する処理を実行
- 不要になった領域を解放
実際に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と計算結果が出力されているのが確認できます。
6. まとめ
この記事ではtyped arrayをwasm側に渡す処理を紹介しました。
重要なポイントはJS側でmalloc, freeができてポインタを取得できること、wasmのメモリ(HEAP32, HEAPF64など)のインデックスをポインタから計算できることです。
この技術を応用すれば、ASCIIコードの文字列や、バイト列をやり取りすることもできます。
より具体的には、JS側で通信して取得した画像やzipのバイナリをwasmに渡して処理できます。
この辺はまた別の記事で紹介できればと思います。