複雑な配列計算を行うためにWebAssemblyをテストしたいと思います。
したがって、それぞれ3つの要素を含む2つのint
配列を追加する単純なC++関数を作成しました。
_// hello.cpp
extern "C" {
void array_add(int * summed, int* a, int* b) {
for (int i=0; i < 3; i++) {
summed[i] = a[i] + b[i];
}
}
}
_
そしてこれをコンパイルしました:
_emcc hello.cpp -s WASM=1 -s "MODULARIZE=1" -s "EXPORT_NAME='HELLO'" -s "BINARYEN_METHOD='native-wasm'" -s "EXPORTED_FUNCTIONS=['_array_add']" -o build/hello.js
_
これにより、js
およびwasm
ファイルが生成されます。これらを次のhtmlページでロードします。
_<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="build/hello.js"></script>
<script type="text/javascript">
function reqListener () {
// Loading wasm module
var arrayBuffer = oReq.response
HELLO['wasmBinary'] = arrayBuffer
hello = HELLO({ wasmBinary: HELLO.wasmBinary })
// Calling function
var result = new Int32Array(3)
var a = new Int32Array([1, 2, 3])
var b = new Int32Array([4, 5, 2])
hello._array_add(result, a, b)
console.log('result', result)
}
var oReq = new XMLHttpRequest();
oReq.responseType = "arraybuffer";
oReq.addEventListener("load", reqListener);
oReq.open("GET", "build/hello.wasm");
oReq.send();
</script>
</head>
<body>
</body>
</html>
_
しかし、どういうわけか、result
配列は常に_[0, 0, 0]
_です。
ccall()
( emscripten docs を参照)で関数を呼び出すなど、さまざまなことを試しましたが、wasmコンパイル済み関数の引数として配列を渡すことができないようです。
たとえば、次のC++関数の場合:
_extern "C" {
int first(int * arr) {
return arr[0];
}
}
_
JavaScriptで呼び出されたときの結果は、引数として渡した配列からの期待値ではなく、ランダムな整数です。
何が欠けていますか?
[〜#〜] nb [〜#〜]:C++についてほとんど何も知らないので、これが私の初心者の質問である場合、すべての謝罪C++の無知...
あなたの質問は this one に非常に似ています:WebAssemblyは_i32
_/_i64
_/_f32
_/_f64
_ value types のみをサポートしています=ストレージ用の_i8
_/_i16
_と同様に。
つまり、ポインタを渡すことはできません。あなたがしていることは、あなたがC++の観点から来ているとき(無知について謝罪する必要はありません!)、まったく正気ですが、WebAssemblyの境界が機能する方法ではありません。 C++の専門家にとっても驚くべきことです。
文字列の質問と同様に、次のいずれかを行う必要があります。
set(size_t index, int value)
など)。ArrayBuffer
としてJavaScriptに公開し、必要な値をArrayBuffer
に直接書き込みます。あなたは私が他の答えで提案したのと同じコードで後者を行うことができます:
_const bin = ...; // WebAssembly binary, I assume below that it imports a memory from module "imports", field "memory".
const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 }); // Size is in pages.
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);
_
C++から来ると、「ポインタはどのように機能するのでしょうか?」上記では、WebAssembly↔JavaScriptではポインタを渡すことができないことを説明しました。 WebAssembly内部のポインターは、単純な_i32
_値として表されます。 EmpscriptenはLLVMに依存してこれを実行します。WebAssemblyは、自身を4GiBの最大ヒープサイズを持つILP32として提示するため、機能します。
これは、間接的な関数呼び出しと関数ポインターに興味深い影響を及ぼします。私は別の質問にそれを残します;-)
ただし、これは、JavaScriptがWebAssemblyへのポインターについて「話す」ことができることを意味します。_i32
_は_i32
_です。値がヒープ内のどこかにあることがわかっている場合は、その_i32
_をJavaScriptに渡すことができ、JavaScriptはそれを変更してWebAssemblyに戻すことができます。 JavaScriptがヒープのArrayBuffer
にアクセスできる場合、_i32
_を使用すると、ヒープ内のどこにあるかを把握し、C++から行うようにヒープを変更できます。
WebAssemblyヒープは、ほとんどのC++ヒープとは異なりますが、実行可能ページへのアクセス権も、呼び出しスタック(または、ほとんどの呼び出しスタック)へのアクセス権もありません。LLVMなどのコンパイラーがアドレスを「こぼす」場合があります。 -WebAssemblyのローカルを使用する代わりに、ヒープに取得した値)。これは基本的にハーバードアーキテクチャが行うことです(フォンノイマンとは対照的です)。
では、hello._array_add(result, a, b)
は何をしていますか? a
を使用して配列からb
およびToInteger
を強制します。これは_0
_になり、WebAssemblyでは有効なヒープの場所です!ヒープの予想外の部分にアクセスしています!
他の同様の質問に感謝します:
emscriptenコンパイル済みコードへの配列ポインタの受け渡しを処理する方法
そしてAPIドキュメント:
https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#getValue
私はそれを理解しました。 wasm関数に配列を渡す方法/配列を取得する方法を例示するために、C++で単純な配列のコピーを実装しました。
#include <stdint.h>
extern "C" {
int* copy_array(int* in_array, int length) {
int out_array[length];
for (int i=0; i<length; i++) {
out_array[i] = in_array[i];
}
return out_array;
}
}
次のようにコンパイルできます:
emcc wasm_dsp.cpp -s WASM=1 -s "MODULARIZE=1" -s "EXPORT_NAME='WasmDsp'" -s "BINARYEN_METHOD='native-wasm'" -s "EXPORTED_FUNCTIONS=['_copy_array']" -o build/wasm_dsp.js
そして、このようにブラウザで実行します:
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="build/wasm_dsp.js"></script>
<script type="text/javascript">
function reqListener () {
// Loading wasm module
var arrayBuffer = oReq.response
WasmDsp['wasmBinary'] = arrayBuffer
wasmDsp = WasmDsp({ wasmBinary: WasmDsp.wasmBinary })
var inArray = new Int32Array([22, 44, 66, 999])
var nByte = 4
copyArray = wasmDsp.cwrap('copy_array', null, ['number', 'number']);
// Takes an Int32Array, copies it to the heap and returns a pointer
function arrayToPtr(array) {
var ptr = wasmDsp._malloc(array.length * nByte)
wasmDsp.HEAP32.set(array, ptr / nByte)
return ptr
}
// Takes a pointer and array length, and returns a Int32Array from the heap
function ptrToArray(ptr, length) {
var array = new Int32Array(length)
var pos = ptr / nByte
array.set(wasmDsp.HEAP32.subarray(pos, pos + length))
return array
}
var copiedArray = ptrToArray(
copyArray(arrayToPtr(inArray), inArray.length)
, inArray.length)
console.log(copiedArray)
}
var oReq = new XMLHttpRequest();
oReq.responseType = "arraybuffer";
oReq.addEventListener("load", reqListener);
oReq.open("GET", "build/wasm_dsp.wasm");
oReq.send();
</script>
</head>
<body>
</body>
</html>
ここでarrayToPtr
およびptrToArray
関数に注意してください...これらは、配列の受け渡し/戻りの作業を行うものです。