阅读 181

WebAssembly 对象

WebAssembly 系列

WebAssembly 基本介绍

WebAssembly 对象

WebAssembly 文本格式

WebAssembly 对象

WebAssembly 是一个对象,但不是一个构造函数,所以不能 new,主要有以下几个方法:

  • WebAssembly.instantiate() :函数加载 WebAssembly 代码
  • WebAssembly.Memory()/ WebAssembly.Table() :创建新的内存和表实例
  • WebAssembly.CompileError()/WebAssembly.LinkError()/WebAssembly.RuntimeError() :提供 WebAssembly 中的错误信息。

关键概念

  • 模块:表示一个已经被浏览器编译为可执行机器码的WebAssembly二进制代码。一个模块是无状态的,并且像一个二进制大对象(Blob)一样能够被缓存到 IndexedDB 中或者在 windows 和 workers 之间进行共享(通过postMessage)。一个模块能够像一个 ES2015 的模块一样声明导入和导出。
  • 内存:ArrayBuffer,大小可变。本质上是连续的字节数组,WebAssembly 的低级内存存取指令可以对它进行读写操作。
  • 表格:带类型数组,大小可变。表格中的项存储了不能作为原始字节存储在内存里的对象的引用(为了安全和可移植性的原因)。
  • 实例:一个模块及其在运行时使用的所有状态,包括内存、表格和一系列导入值。一个实例就像一个已经被加载到一个拥有一组特定导入的特定的全局变量的 ES2015 模块。

因为 JavaScript 能够完全控制 WebAssembly 代码如何下载、编译运行,所以,JavaScript 开发者甚至可以把 WebAssembly 想象成一个高效地生成高性能函数的 JavaScript 特性。

将来,WebAssembly 模块将会像ES2015模块那样加载(使用<script type='module'>),这也就意味着JavaScript 代码能够像轻松地使用一个ES2015模块那样来获取、编译和导入一个 WebAssembly 模块。

初始化

再回顾一下上一章加载 wasm 模块的内容:再获取到 wasm 模块资源之后,通过 arrayBuffer 转换成带类型数组。最后,使用 WebAssembly.instantiate 函数一步实现编译和实例化带类型数组。

fetch('module.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  // Do something with the compiled results!
});
复制代码

instantiate 函数接受待编译的字节码作为参数并且返回一个 promise 并且该 promise 可以解析为一个包含已编译的模块对象及其实例的对象。

{
  module : Module // 新编译的WebAssembly.Module对象,
  instance : Instance // 新的模块对象实例
}
复制代码

除此之外,还有另外一种实例化的方法:WebAssembly.instantiateStreaming。此函数可以直接接受二进制资源,而不需要转换成带类型数组。


fetch('module.wasm').then(response =>
  WebAssembly.instantiateStreaming(response, importObject)
).then(results => {
  // Do something with the compiled results!
});
复制代码

此外,importObject 是一个可选参数,但是省略的前提是在 wasm 文件中没有使用到导入函数或导入属性。否则一定要传 importObject。这个 importObject 提供了你在 wasm 中引用的函数或属性,因此 importObject 的结构和 wasm 的导入结构相关,具体在讲 wasm 文本格式的时候再细说。

内存创建

和原生 C/C++ 程序可用内存范围跨越整个进程不同,特定 WebAssembly 实例可访问的内存被限制在由WebAssembly Memory 对象包含的一个特定的、可能非常小的范围内。

在 JavaScript 中,内存实例可以被认为是可调整大小的 ArrayBuffer,一个 Web 应用程序可以创建许多独立的内存对象。 可以使用 WebAssembly.Memory 构造函数创建,参数为初始大小和(可选)最大大小。

var memory = new WebAssembly.Memory({initial:10, maximum:100});
console.log(memory)
复制代码

image.png

创建出来的 memory 对象上有一个类型为 ArrayBuffer 的 buffer 属性。我们可以直接获取或修改这个ArrayBuffer 的缓冲区,从而更改 WebAssembly 内存。

new Uint32Array(memory.buffer)[0] = 42;
console.log(new Uint32Array(memory.buffer)[0]);
复制代码

另外,可以通过 Memory.prototype.grow 来增加内存,只是不能超过创建时设置的最大值,不然会报错(WebAssembly.RangeError)。

memory.grow(1)
复制代码

由于 ArrayBuffer 的 byteLength 是不可变的,所以在 Memory.prototype.grow 操作之后,缓冲区将返回一个新的 ArrayBuffer 对象。

具体示例链接地址:memory.html

表格

WebAssembly 表格是一个可变大小带类型的引用数组,其中的引用可以被JavaScript和WebAssembly代码存取。

表格有一个元素类型,其限制了可以存储在表格的引用类型。在当前的 WebAssembly 版本中,只有一种WebAssembly 代码所需要的引用类型——函数,是唯一合法的元素类型。在将来的版本中,更多的元素类型会被加入。所以,表格中存储的就是函数引用,类似 C/C++ 中的函数指针。

总结

最后,总结一下模块、实例、内存和表格之间的关系:

  • 一个模块可以有 N 个实例,这与一个函数可以产生 N 个闭包值一样。
  • 一个模块实例可以使用 0-1 个内存实例,它为这个实例提供了“地址空间”。将来的WebAssembly版本可能允许每个模块实例拥有0-N个内存实例。
  • 一个模块实例可以使用 0-1 个表格实例,这是该实例的“函数地址空间”,可以用来实现C函数指针。将来的WebAssembly 版本可能允许每个模块实例拥有 0-N 个表格实例。
  • 一个内存或表格实例能够被 0-N 个模块实例使用,这些实例全部共享相同的地址空间。
文章分类
前端
文章标签