簡體   English   中英

將 JavaScript 數組作為參數傳遞給 WebAssembly 函數

[英]Pass a JavaScript array as argument to a WebAssembly function

我想測試 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++ 無知有關的初學者問題,我深表歉意......

您的問題與問題非常相似:WebAssembly 僅支持i32 / i64 / f32 / f64 值類型以及i8 / i16用於存儲。

這意味着您不能傳入指針。 當您從 C++ 的角度來看時,您正在做的事情是完全理智的(無需為無知道歉!),但這並不是 WebAssembly 邊界的工作方式。 這也讓 C++ 專家感到驚訝。

與字符串問題一樣,您需要:

  • 通過為每個條目調用一次導出(例如set(size_t index, int value)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 將自身呈現為 ILP32,最大堆大小為 4GiB,因此它可以正常工作。

它確實對間接函數調用和函數指針有有趣的影響! 我會把它留給另一個問題;-)

然而,這確實意味着 JavaScript 可以“談論”指向 WebAssembly 的指針:一個i32就是一個i32 如果您知道某個值在堆中的某個位置,那么您可以將該i32傳遞給 JavaScript,JavaScript 可以修改它並將其傳遞回 WebAssembly。 如果 JavaScript 可以訪問堆的ArrayBuffer那么擁有i32可以讓您知道堆中的位置,並像從 C++ 一樣修改堆。

不過,WebAssembly 堆與大多數 C++ 堆不同:它無權訪問可執行頁面,也無權訪問調用堆棧(或者更確切地說,大多數調用堆棧:諸如 LLVM 之類的編譯器可能會“溢出”一些地址- 將值取到堆中,而不是使用 WebAssembly 的局部變量)。 這基本上是哈佛架構所做的(與馮諾依曼相反)。


那么你的hello._array_add(result, a, b)在做什么? 使用ToInteger從數組中強制ab 那變成了0 ,這在 WebAssembly 中是一個有效的堆位置! 您正在訪問堆的一個非常意外的部分!

所以感謝其他類似的問題:

使用 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>

注意這里的arrayToPtrptrToArray函數......它們是傳遞/返回數組的工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM