将JavaScript数组作为参数传递给WebAssembly函数

时间:2017-01-26 14:28:58

标签: javascript c++ browser emscripten webassembly

我想测试WebAssembly进行一些复杂的数组计算。

所以我编写了一个简单的C ++函数,添加了两个int数组,每个数组包含3个元素:

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

其中包括jswasm文件。我使用以下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),似乎我无法将一个数组作为mym编译函数的参数传递。

例如,使用以下C ++函数:

extern "C" {

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

}

在JavaScript中调用的结果是一个random-ish整数,而不是我作为参数传递的数组的期望值。

我错过了什么?

NB :我对C ++几乎一无所知,所以如果这是一个与我的C ++无知相关的初学者问题,那么所有道歉......

2 个答案:

答案 0 :(得分:15)

您的问题与this one非常相似:WebAssembly仅支持i32 / i64 / f32 / f64 value types以及{ {1}} / i8用于存储。

这意味着你无法传入指针。当你从C ++的角度来看时,你所做的事情是完全理智的(不需要为无知而道歉!),但这并不是WebAssembly的边界是如何运作的。这对C ++专家来说也是令人惊讶的。

在字符串问题中,您需要:

  • 每个条目调用一次导出(例如i16),一次复制一个数组。
  • 将您的WebAssembly实例的堆公开为set(size_t index, int value)到JavaScript,并直接写入ArrayBuffer您想要的值。

您可以使用我在另一个答案中提出的相同代码来执行后者:

ArrayBuffer

来自C ++你可能想知道:“但指针是如何工作的?”上面我解释一下WebAssembly↔JavaScript你无法传递指针!内部WebAssembly指针表示为简单的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); 值。 Empscripten依赖于LLVM来实现这一点,并且由于WebAssembly将自身呈现为ILP32,其最大堆大小为4GiB,它只是工作。

它确实对间接函数调用和函数指针有意义!我会留下另一个问题; - )

但这意味着JavaScript可以“谈论”指向WebAssembly的指针:i32i32。如果你知道某个值在堆中的某个位置,那么你可以将i32传递给JavaScript,JavaScript可以修改它并将其传递回WebAssembly。如果JavaScript可以访问堆的i32,那么拥有ArrayBuffer可以让你知道堆中的位置,并像在C ++中那样修改堆。

WebAssembly堆与大多数C ++堆不同:它无法访问可执行页面,也无法访问调用堆栈(或者更确切地说,大多数调用堆栈:LLVM等编译器可能会溢出“一些地址采用的值到堆而不是使用WebAssembly的本地人。这基本上就是哈佛架构所做的(而不是冯·诺伊曼)。

那你的i32在做什么?使用hello._array_add(result, a, b)从数组中强制ab。这变为ToInteger,在WebAssembly中是有效的堆位置!您正在访问堆中非常意外的部分!

答案 1 :(得分:8)

感谢其他类似的问题:

Pass array to C function with emscripten

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

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

请注意这里的arrayToPtrptrToArray函数......他们正在做传递/返回数组的工作。