2017-01-26 19 views
5

Tôi muốn thử nghiệm WebAssembly để thực hiện một số tính toán mảng phức tạp.Vượt qua một mảng JavaScript làm đối số cho hàm WebAssembly

Vì vậy, tôi đã viết một đơn giản C++ chức năng thêm hai int mảng chứa 3 yếu tố mỗi:

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

} 

Và biên soạn này vớ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

nào tạo số những người khác, một js và tệp wasm. Tôi tải những điều này với các trang html sau:

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

Nhưng bằng cách nào đó, các mảng result luôn là [0, 0, 0].

Tôi đã thử nhiều thứ, bao gồm gọi hàm ccall() (xem emscripten docs) và có vẻ như tôi không thể nhận được một mảng được truyền làm đối số của hàm được biên dịch của tôi.

Ví dụ, với các chức năng sau C++:

extern "C" { 

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

} 

quả khi gọi trong JavaScript là một số nguyên ngẫu nhiên-ish, thay vì giá trị mong đợi từ các mảng tôi trôi qua như là đối số.

Tôi đang thiếu gì?

NB: Tôi biết khá nhiều gì về C++, vì vậy tất cả lời xin lỗi nếu điều này là một câu hỏi mới bắt đầu liên quan đến C của tôi ++ ngu dốt ...

Trả lời

7

Câu hỏi của bạn rất giống với this one: WebAssembly chỉ hỗ trợ i32/i64/f32/f64value types cũng như i8/i16 để lưu trữ.

Điều này có nghĩa là bạn không thể truyền con trỏ. Những gì bạn đang làm là hoàn toàn lành mạnh khi bạn đến từ một điểm nhìn C++ (không cần phải xin lỗi vì sự thiếu hiểu biết!), Nhưng nó không chỉ là cách mà ranh giới của WebAssembly hoạt động. Điều đó cũng gây ngạc nhiên cho các chuyên gia C++.

Như trong câu hỏi chuỗi, bạn cần phải hoặc là:

  • Sao chép mảng trong cùng một lúc bằng cách gọi một xuất khẩu một lần cho mỗi mục (ví dụ như set(size_t index, int value)).
  • Hiển thị vùng chứa của cá thể WebAssembly dưới dạng ArrayBuffer thành JavaScript và ghi trực tiếp vào các giá trị bạn muốn.

Bạn có thể làm sau này với cùng mã tôi đã đề nghị trong câu trả lời khác:

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

Đến từ C++ có lẽ bạn đang tự hỏi: "nhưng làm thế nào để con trỏ làm việc?". Ở trên tôi giải thích rằng WebAssembly ↔ JavaScript bạn không thể vượt qua con trỏ xung quanh!Các con trỏ WebAsembly bên trong được biểu diễn dưới dạng các giá trị đơn giản i32. Empscripten dựa vào LLVM để làm điều này, và kể từ khi WebAssembly trình bày chính nó như là ILP32 với một kích thước tối đa 4GiB nó chỉ hoạt động.

Nó có ý nghĩa thú vị cho các cuộc gọi hàm gián tiếp và con trỏ hàm! Tôi sẽ để lại câu hỏi đó cho một câu hỏi khác ;-)

Điều này có nghĩa là JavaScript có thể "nói" về con trỏ đến WebAssembly: i32i32. Nếu bạn biết một giá trị là một nơi nào đó trong heap thì bạn có thể vượt qua đó i32 ngoài JavaScript, và JavaScript có thể sửa đổi nó và chuyển nó trở lại vào WebAssembly. Nếu JavaScript có quyền truy cập vào số ArrayBuffer của heap thì có một số i32 cho phép bạn biết vị trí trong vùng heap và sửa đổi vùng heap như bạn làm từ C++.

Heap WebAssembly khác với hầu hết các vùng C++ mặc dù: nó không có quyền truy cập vào các trang thực thi, cũng không có quyền truy cập vào ngăn xếp cuộc gọi (hoặc đúng hơn, hầu hết các ngăn xếp cuộc gọi: trình biên dịch như LLVM có thể " tràn "một số giá trị địa chỉ đưa vào heap thay vì sử dụng các địa phương của WebAssembly). Đây là những kiến ​​trúc cơ bản của Harvard (trái với von Neumann).


Vì vậy, số hello._array_add(result, a, b) của bạn sẽ làm gì? Xỏ lỗ ab từ mảng sử dụng ToInteger. Điều đó trở thành 0, mà trong WebAssembly là một vị trí heap hợp lệ! Bạn đang truy cập vào một phần rất bất ngờ của heap của bạn!

3

Vì vậy, nhờ vào câu hỏi tương tự khác:

Pass array to C function with emscripten

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

Và API docs:

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

Tôi đã figured it out. Để examplify làm thế nào để vượt qua một mảng đến chức năng wasm/nhận được một mảng trở lại, tôi đã thực hiện một bản sao đơn giản mảng trong 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; 
} 

} 

Mà bạn có thể biên dịch như thế này:

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

Và chạy trong trình duyệt như thế này:

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

Lưu ý arrayToPtrptrToArray chức năng ở đây ... họ là những người làm công việc truyền/trả về mảng.

Các vấn đề liên quan