Pass a JavaScript array as an argument to the WebAssembly function

I would like to test WebAssembly to perform complex array calculations.

So, I wrote a simple C ++ function that adds two int arrays containing three elements each:

 // 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]; } } } 

And compiled it with:

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

What generates among others is the js and wasm . I load them with the following html page:

 <!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> 

But somehow the result array is always [0, 0, 0] .

I tried various things, including calling a function with ccall() (see emscripten docs ), and it seems like I cannot get the array passed as an argument to my compiled wasm function.

For example, with the following C ++ function:

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

The result when called in JavaScript is an integer of random numbers, not the expected value from the array passed as an argument.

What am I missing?

NB : I know almost nothing about C ++, so all apologies if this is a newbie related to my ignorance of C ++ ...

+6
source share
2 answers

Your question is very similar to this : WebAssembly supports only i32 / i64 / f32 / f64 type values , as well as i8 / i16 for storage.

This means that you cannot pass pointers. What you do is absolutely reasonable when you go from a C ++ point of view (no need to apologize for not knowing!), But that’s not how the WebAssembly border works. This is surprising for C ++ experts.

As in the lowercase question, you need to either:

  • Copy the array one at a time, calling export once for each record (for example, set(size_t index, int value) ).
  • Select the heap of the WebAssembly instance as an ArrayBuffer for JavaScript and write directly to the ArrayBuffer values ​​you want.

You can do the latter with the same code that I suggested in another answer:

 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); 

From C ++ you are probably wondering: “But how do pointers work?”. Above, I explain that WebAssembly ↔ JavaScript, you cannot pass pointers! WebAssembly pointers provide simple i32 values. Empscripten relies on LLVM to do this, and since WebAssembly presents itself as ILP32 with a maximum 4GiB heap size, it just works.

It has interesting implications for indirect function calls and function pointers! I will leave this for another question :-)

However, this means that JavaScript can "talk" about pointers to WebAssembly: i32 is i32 . If you know that the value is somewhere on the heap, you can pass this i32 to JavaScript, and JavaScript can change it and pass it back to WebAssembly. If JavaScript has access to the ArrayBuffer heap, then having i32 allows you to find out where the heaps are and change the heap as it would in C ++.

The WebAssembly heap is different from most C ++ heaps: it does not have access to executable pages and does not have access to the call stack (or rather, most of the call stack: compilers, such as LLVMs, can spill some values ​​accepted by addresses instead of using local WebAssembly sites.) This is basically what Harvard architectures do (unlike von Neumann).


So what does your hello._array_add(result, a, b) do? Extruding a and b from arrays using ToInteger . This becomes 0 , which in WebAssembly is a valid heap place! You get a very unexpected part of your heap!

+7
source

So thanks to other similar questions:

Pass an array to C function with emscripten

How to handle transmit / return array pointers to emscripten compiled code?

And API docs:

https://kripken.imtqy.com/emscripten-site/docs/api_reference/preamble.js.html#getValue

I understood that. To talk about how to pass an array to the wasm / function, get the array back, I implemented a simple copy of the array in 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; } } 

What you can compile as follows:

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

And run in a browser like this:

 <!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> 

Notice the arrayToPtr and ptrToArray here ... these are the ones that do the job of the transmit / return array.

+3
source

Source: https://habr.com/ru/post/1014462/


All Articles