Je voudrais tester WebAssembly pour effectuer des calculs de tableau complexes.
J'ai donc écrit une simple fonction C++ ajoutant deux tableaux int
contenant chacun 3 éléments:
// 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];
}
}
}
Et compilé ceci avec:
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
Ce qui génère entre autres un fichier js
et wasm
. Je les charge avec la page html suivante:
<!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>
Mais d'une manière ou d'une autre, le tableau result
est toujours [0, 0, 0]
.
J'ai essayé une variété de choses, y compris l'appel de la fonction avec ccall()
(voir documentation emscripten ) et il semble que je ne puisse pas faire passer un tableau comme argument de ma fonction compilée par wasm.
Par exemple, avec la fonction C++ suivante:
extern "C" {
int first(int * arr) {
return arr[0];
}
}
Le résultat lorsqu'il est appelé en JavaScript est un entier aléatoire, au lieu de la valeur attendue du tableau que j'ai passé en argument.
Qu'est-ce que je rate?
[~ # ~] nb [~ # ~] : Je ne connais pratiquement rien en C++, donc toutes mes excuses s'il s'agit d'une question de débutant liée à ma L'ignorance C++ ...
Votre question est très similaire à celle-ci : WebAssembly ne prend en charge que i32
/i64
/f32
/f64
types de valeurs ainsi que i8
/i16
pour le stockage.
Cela signifie que vous ne pouvez pas passer de pointeurs. Ce que vous faites est totalement sain d'esprit lorsque vous venez d'un point de vue C++ (pas besoin de vous excuser pour l'ignorance!), Mais ce n'est tout simplement pas comment fonctionne la frontière de WebAssembly. Cela surprend aussi les experts C++.
Comme dans la question des chaînes, vous devez soit:
set(size_t index, int value)
).ArrayBuffer
dans JavaScript et écrivez directement dans le ArrayBuffer
les valeurs souhaitées.Vous pouvez faire ce dernier avec le même code que j'ai proposé dans l'autre réponse:
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);
Venant de C++, vous vous demandez probablement: "mais comment fonctionnent les pointeurs?". Ci-dessus, j'explique que WebAssembly ↔ JavaScript, vous ne pouvez pas passer de pointeurs! Les pointeurs WebAssembly sont représentés sous la forme de simples valeurs i32
. Empscripten s'appuie sur LLVM pour ce faire, et comme WebAssembly se présente comme ILP32 avec une taille de segment maximale de 4 Go, il fonctionne simplement.
Cela a des implications intéressantes pour les appels de fonction indirecte et les pointeurs de fonction! Je laisse ça pour une autre question ;-)
Cela signifie cependant que JavaScript peut "parler" de pointeurs vers WebAssembly: un i32
Est un i32
. Si vous savez qu'une valeur se trouve quelque part dans le tas, vous pouvez transmettre ce i32
À JavaScript, et JavaScript peut le modifier et le renvoyer à WebAssembly. Si JavaScript a accès au ArrayBuffer
du tas, avoir un i32
Vous permet de savoir où se trouvent les tas et de modifier le tas comme vous le feriez à partir de C++.
Cependant, le tas WebAssembly est différent de la plupart des tas C++: il n'a pas accès aux pages exécutables, ni à la pile d'appels (ou plutôt, la plupart de la pile d'appels: des compilateurs tels que LLVM peuvent "renverser" une adresse -prises de valeurs au tas au lieu d'utiliser les sections locales de WebAssembly). C'est essentiellement ce que font les architectures Harvard (par opposition à von Neumann).
Alors, que fait votre hello._array_add(result, a, b)
? Contrainte a
et b
à partir de tableaux à l'aide de ToInteger
. Cela devient 0
, Qui dans WebAssembly est un emplacement de segment de mémoire valide! Vous accédez à une partie très inattendue de votre tas!
Merci donc à d'autres questions similaires:
Passer le tableau à la fonction C avec emscripten
Comment gérer le passage/le retour de pointeurs de tableau vers le code compilé emscripten?
Et les documents API:
https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#getValue
Je l'ai compris. Pour examiner comment passer un tableau à la fonction wasm/récupérer un tableau, j'ai implémenté une simple copie de tableau en 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;
}
}
Que vous pouvez compiler comme ceci:
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
Et exécutez dans le navigateur comme ceci:
<!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>
Notez les fonctions arrayToPtr
et ptrToArray
ici ... ce sont elles qui font le travail de passage/retour de tableau.