2017-01-26 1 views
5

Je voudrais tester WebAssembly pour effectuer des calculs de tableau complexes.Passer un tableau JavaScript en tant qu'argument à une fonction WebAssembly

J'ai écrit une simple fonction C++ ajouter deux int tableaux contenant 3 éléments chacun:

// 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é cela 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

qui génère entre autres, une js et un fichier wasm. Je charge ceux-ci 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 en quelque sorte, le tableau est toujours result[0, 0, 0].

J'ai essayé une variété de choses, y compris l'appel de la fonction avec ccall() (voir emscripten docs) et il semble que je ne peux pas obtenir un tableau passé en argument de ma fonction wasm compilé.

Par exemple, la fonction C++ suivante:

extern "C" { 

int first(int * arr) { 
    return arr[0]; 
} 

} 

Résultat lorsqu'il est appelé dans JavaScript est un entier aléatoire à la rigueur, au lieu de la valeur prévue à partir du tableau I en tant qu'argument.

Qu'est-ce qui me manque?

NB: Je ne sais rien à peu près sur le C++, donc toutes mes excuses si cela est une question débutant liée à l'ignorance de mon C ...

Répondre

7

Votre question est très similaire à this one: WebAssembly ne supporte que i32/i64/f32/f64value types ainsi que i8/i16 pour le stockage.

Cela signifie que vous ne pouvez pas passer de pointeurs. Ce que vous faites est totalement sain quand vous venez d'un point de vue C++ (pas besoin de vous excuser pour l'ignorance!), Mais ce n'est pas le fonctionnement de la limite de WebAssembly. C'est aussi surprenant pour les experts C++.

Comme dans la question de chaîne, vous devez soit:

  • Copiez le tableau dans un à la fois en appelant une exportation une fois par l'entrée (comme set(size_t index, int value)).
  • Expose votre tas de WebAssembly exemple comme ArrayBuffer JavaScript et écrire directement dans les ArrayBuffer les valeurs que vous voulez.

Vous pouvez faire ce dernier avec le même code que je proposais 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 des pointeurs!Les pointeurs Inside WebAssembly sont représentés par des valeurs i32 simples. Empscripten s'appuie sur LLVM pour cela, et puisque WebAssembly se présente comme ILP32 avec une taille de segment de 4GiB maximum, il fonctionne simplement.

Cela a des implications intéressantes pour les appels de fonction indirects et les pointeurs de fonction! Je laisse cela pour une autre question ;-)

Cela signifie cependant que JavaScript peut "parler" des pointeurs vers WebAssembly: un i32 est un i32. Si vous savez qu'une valeur est quelque part dans le tas, alors vous pouvez passer cette i32 à JavaScript, et JavaScript peut le modifier et le transmettre à WebAssembly. Si JavaScript a accès au ArrayBuffer du tas, alors avoir un i32 vous permet de savoir où sont les choses dans le tas, et de modifier le tas comme vous le feriez depuis C++. Le tas WebAssembly est différent de la plupart des tas C++: il n'a pas accès aux pages exécutables et n'a pas accès à la pile d'appels (ou plutôt, la plupart des piles d'appels: des compilateurs tels que LLVM peuvent " déverser "certaines valeurs prises par l'adresse sur le tas au lieu d'utiliser les locales de WebAssembly). C'est essentiellement ce que font les architectures de Harvard (par opposition à von Neumann).


Alors, que fait votre hello._array_add(result, a, b)? Coercition a et b à partir de tableaux en utilisant ToInteger. Cela devient 0, qui dans WebAssembly est un emplacement de tas valide! Vous accédez à une partie très inattendue de votre tas!

3

Ainsi, grâce à d'autres questions similaires:

Pass array to C function with emscripten

How to handle passing/returning array pointers to emscripten compiled code?

Et API docs:

https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#getValue

j'ai compris. Pour examplify comment passer un tableau à la fonction wasm/obtenir un tableau arrière, je l'ai mis en place 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; 
} 

} 

qui 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écuter 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 ici arrayToPtr et ptrToArray ... ils sont ceux qui font le travail de passer/retourner le tableau.