23

我想测试 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

其中生成一个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 中调用的结果是一个随机整数,而不是我作为参数传递的数组中的预期值。

我错过了什么?

注意:我对 C++ 几乎一无所知,所以如果这是一个与我的 C++ 无知有关的初学者问题,我深表歉意......

4

2 回答 2

22

您的问题与此非常相似:WebAssembly 仅支持i32/ i64/ f32/f64 值类型以及i8/i16用于存储。

这意味着您不能传递指针。当您从 C++ 的角度来看时,您正在做的事情是完全理智的(无需为无知道歉!),但这不是 WebAssembly 的边界工作方式。这也让 C++ 专家感到惊讶。

与字符串问题一样,您需要:

  • 通过对每个条目调用一次导出(例如set(size_t index, int value)),一次复制一个数组。
  • 将您的 WebAssembly 实例的堆公开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 的指针:an i32is an i32. 如果你知道一个值在堆中的某个地方,那么你可以将它传递i32给 JavaScript,JavaScript 可以修改它并将其传递回 WebAssembly。如果 JavaScript 可以访问堆,ArrayBuffer那么拥有一个i32可以让您知道堆中的东西在哪里,并像在 C++ 中一样修改堆。

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


那你hello._array_add(result, a, b)在做什么?使用. a_ b_ ToInteger这变成0了 ,在 WebAssembly 中它是一个有效的堆位置!您正在访问堆中非常意想不到的部分!

于 2017-01-26T16:34:16.677 回答
12

所以感谢其他类似的问题:

使用 emscripten 将数组传递给 C 函数

如何处理传递/返回数组指针到 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>

注意这里的arrayToPtrandptrToArray函数......它们是做传递/返回数组工作的函数。

于 2017-01-26T17:02:40.187 回答