如何将 ArrayBuffer 从 JS 传递到 AssemblyScript/Wasm?

Posted

技术标签:

【中文标题】如何将 ArrayBuffer 从 JS 传递到 AssemblyScript/Wasm?【英文标题】:How can I pass an ArrayBuffer from JS to AssemblyScript/Wasm? 【发布时间】:2019-07-11 07:13:33 【问题描述】:

我有一段非常简单的 Typescript 代码可以解析特定的数据格式,输入是 UInt8Array。我已经尽可能地对其进行了优化,但我认为这个相当简单的解析器应该能够比我让它作为 JS 运行得更快。我想尝试使用 AssemblyScript 在 Web 汇编中编写它,以确保我没有遇到 javascript 引擎的任何怪癖。

正如我现在所知道的,我不能只将 TypedArray 传递给 Wasm 并让它自动工作。据我了解,我可以将指针传递给数组,并且应该能够直接从 Wasm 访问它而无需复制数组。但我无法让它与 AssemblyScript 一起使用。

以下是一个最小示例,显示了我如何无法将 ArrayBuffer 传递给 Wasm。

设置 Wasm 导出的代码大部分来自自动生成的样板:

const fs = require("fs");
const compiled = new WebAssembly.Module(
  fs.readFileSync(__dirname + "/build/optimized.wasm")
);
const imports = 
  env: 
    abort(msgPtr, filePtr, line, column) 
      throw new Error(`index.ts: abort at [$line:$column]`);
    
  
;
Object.defineProperty(module, "exports", 
  get: () => new WebAssembly.Instance(compiled, imports).exports
);

下面的代码调用了WASM,index.js就是上面的胶水代码。

const m = require("./index.js");
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
const result = m.parse(data.buffer);

而编译成 WASM 的 AssemblyScript 如下:

import "allocator/arena";

export function parse(offset: usize): number 
  return load<u8>(offset);

我在执行该代码时收到“RuntimeError: memory access out of bounds”。

主要问题是我从 Wasm 得到的错误对我自己解决这个问题没有帮助。我显然错过了这在幕后实际运作的一些主要方面。

如何使用 AssemblyScript 将 TypedArray 或 ArrayBuffer 从 JS 传递到 Wasm?

【问题讨论】:

offset 是否应该是Unit8Array 的属性?你想得到byteOffset 吗? parse 是否期望参数是 Unit8Array 或整数? @guest271314 ArrayBuffer 实际上并没有传递给 WASM,这就是我最初的想法。但是每次我尝试访问像数组长度这样的属性时,它都会抛出一个错误。据我了解,传递给 WASM 的只是一个指向内存中 ArrayBuffer 位置的指针。但我可能错了,虽然我很确定我是对的,真正的 ArrayBuffer 没有传入。 parse 期望什么作为参数? @guest271314 我可以用来执行加载命令的内存地址。在真正的代码中,我会遍历数组,所以我也会传入长度。我尝试将参数视为 TypedArray,但没有奏效。据我所知,如果我传入一个 ArrayBuffer,我会得到一个指向 WASM 端数组的指针。 我不熟悉 AssemblyScript,但对 WASM 的 C/C++ 经验丰富。在您使用的任何语言中,您都不能传递数组对象,而是将其复制到 WAS 的堆区域。这是因为 WASM 严格禁止接触 WASM 堆之外的任何内存,并且 WASM 只能将 arrary 理解为一种类型,而只能理解为数字类型。在 WASM for C 中,你应该调用与 ArrayBuffer 大小相同的malloc,将指针传递给 JS,然后将其转换为 Uint8Array,并使用 Uinit8Array.set() 方法将 ArrayBuffer 复制到 Uint8Array。真的没有别的办法了。我希望这会有所帮助。 【参考方案1】:

在 AssemblyScript 中,有多种方法可以从内存中读取数据。获取这些数据的最快和最快的方法是在模块的函数导入中使用链接函数来返回指向数据本身的指针。

let myData = new Float64Array(100); // have some data in AssemblyScript

// We should specify the location of our linked function
@external("env", "sendFloat64Array")
declare function sendFloat64Array(pointer: usize, length: i32): void;

/**
 * The underlying array buffer has a special property called `data` which
 * points to the start of the memory.
 */
sendFloat64Data(myData.buffer.data, myData.length);

然后在 JavaScript 中,我们可以在链接函数中使用 Float64Array 构造函数直接返回值。

/**
 * This is the fastest way to receive the data. Add a linked function like this.
 */
imports.env.sendFloat64Array = function sendFloat64Array(pointer, length) 
  var data = new Float64Array(wasmmodule.memory.buffer, pointer, length);
;

但是,有一种更清晰的方法来获取数据,它涉及从 AssemblyScript 返回一个引用,然后使用 AssemblyScript 加载器。

let myData = new Float64Array(100); // have some data in AssemblyScript

export function getData(): Float64Array 
  return myData;

然后在 JavaScript 中,我们可以使用 AssemblyScript 提供的 ASUtil 加载器。

import  instantiateStreaming  from "assemblyscript/lib/loader";

let wasm: ASUtil = await instantiateStreaming(fetch("myAssemblyScriptModule.wasm"), imports);

let dataReference: number = wasm.getData();
let data: Float64Array = wasm.getArray(Float64Array, dataReference);

出于代码清晰的原因,我强烈建议使用第二个示例,除非性能绝对关键。

祝您的 AssemblyScript 项目好运!

【讨论】:

这可能是个愚蠢的问题,但是“getArray”应该来自哪里(或者它应该是什么样子,因为它似乎是在我们的 AS 模块中定义的东西)? 谢谢!这确实是我需要(和发现)的!

以上是关于如何将 ArrayBuffer 从 JS 传递到 AssemblyScript/Wasm?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过Websocket将arraybuffer作为二进制发送?

有没有办法在Android上将arraybuffer从javascript传递到java?

如何将带有 ArrayBuffer 的 JSON 对象发送到 websocket?

哪位朋友使用过js中的ArrayBuffer,请教一个转换问题

在JavaScript中如何将ArrayBuffer转换为Float64Array?

如何从 Blob 转到 ArrayBuffer