Я хотел бы протестировать 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()
(увидеть распечатка документов ) и кажется, что я не могу получить массив, переданный в качестве аргумента моей скомпилированной функции wasm.
Например, с помощью следующей функции C ++:
extern "C" {
int first(int * arr) {
return arr[0];
}
}
Результатом при вызове в JavaScript является случайное целое число вместо ожидаемого значения из массива, который я передал в качестве аргумента.
Что мне не хватает?
NB Я почти ничего не знаю о C ++, поэтому прошу прощения, если это вопрос новичка, связанный с моим незнанием C ++ …
Ваш вопрос очень похож на этот: WebAssembly поддерживает только i32
/ i64
/ f32
/ f64
типы значений так же как i8
/ i16
для хранения.
Это означает, что вы не можете передать в указатели. То, что вы делаете, совершенно нормально, когда вы переходите с точки зрения C ++ (не нужно извиняться за невежество!), Но это не совсем так, как работает граница WebAssembly. Это удивительно и для экспертов C ++.
Как и в вопросе строки, вам нужно либо:
set(size_t index, int value)
).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 с максимальным размером кучи 4 ГБ, он просто работает.
Это имеет интересные последствия для косвенных вызовов функций и указателей функций! Я оставлю это для другого вопроса 😉
Это, однако, означает, что JavaScript может «говорить» об указателях на WebAssembly: i32
является i32
, Если вы знаете, что значение находится где-то в куче, то вы можете передать это i32
в JavaScript, и JavaScript может изменить его и передать обратно в WebAssembly. Если JavaScript имеет доступ к куче ArrayBuffer
затем имея i32
позволяет вам знать, где находятся кучи, и изменять кучи, как в C ++.
Куча WebAssembly отличается от большинства куч C ++: у нее нет доступа к исполняемым страницам и нет доступа к стеку вызовов (или, скорее, к большей части стека вызовов: такие компиляторы, как LLVM, могут «пролить» некоторый адрес). -внесенные значения в кучу вместо использования локальных WebAssembly). Это в основном то, что делают Гарвардские архитектуры (в отличие от фон Неймана).
Так какой у тебя hello._array_add(result, a, b)
делать? принуждение a
а также b
из массивов, использующих ToInteger
, Это становится 0
, который в WebAssembly является допустимым местоположением кучи! Вы получаете доступ к очень неожиданной части вашей кучи!
Так что благодаря другим аналогичным вопросам:
Передать массив функции C с помощью emscripten
Как обработать передачу / возврат указателей массива на скомпилированный код 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>
Обратите внимание arrayToPtr
а также ptrToArray
функции здесь … именно они выполняют работу по передаче / возвращению массива.