我们在这里假设您已经有一个 .wasm 模块,无论是从 C/C++ 程序编译还是直接从 s-exprs 组装.
虽然未来有计划允许 WebAssembly 模块像 ES6 模块一样加载(使用<script type='module'>
),但目前 WebAssembly 必须由 JavaScript 加载和编译。对于基本加载,分为三个步骤:
.wasm
字节放入类型化数组或ArrayBuffer
WebAssembly.Module
WebAssembly.Module
导入实例化以获取可调用的导出让我们更详细地讨论这些步骤.
第一步,有很多方法可以获取字节的类型化数组或ArrayBuffer
: 通过网络k, 使用 XHR 或 fetch, 来自从 IndexedDB 检索的File
, 甚至直接在 JavaScript 中合成.
下一步是使用异步函数WebAssembly.compile
编译字节,该函数返回解析为WebAssembly.Module
的 Promise。Module
对象是无状态的,支持结构化克隆 ,这意味着编译的代码可以存储在 IndexedDB 中和/或通过 postMessage
在窗口和工作线程之间共享。
最后一步是通过构造一个新的 WebAssembly.Instance
来实例化Module
,该实例传入一个模块和该Module
请求的任何导入。实例对象类似于函数闭包,将代码与环境配对,并且不可结构化克隆。
我们可以将最后两个步骤组合成一个instantiate
操作,该操作同时接受字节和导入并异步返回一个Instance
:
function instantiate(bytes, imports) {
return WebAssembly.compile(bytes).then(m => new WebAssembly.Instance(m, imports));
}
为了实际演示这一点,我们首先需要引入另一段 JS API:
与 ES6 模块一样,WebAssembly 模块可以导入和导出函数(稍后我们还会看到其他类型的对象)。我们可以在这个模块中看到一个简单的示例,它从模块imports
中导入函数i
并导出函数 e
:
;; simple.wasm
(module
(func $i (import "imports" "i") (param i32))
(func (export "e")
i32.const 42
call $i))
(这里,我们不是用C/C++写模块再编译成WebAssembly,而是直接用文本格式写模块,可以直接 汇编 成二进制文件simple.wasm
。)
Looking at this module we can see a few things. First, WebAssembly imports have a two-level namespace; in this case the import with the internal name $i
is imported from imports.i
. Similarly, we must reflect this two-level namespace in the import object passed to instantiate
:
var importObject = { imports: { i: arg => console.log(arg) } };
Putting together everything in this section and the last, we can fetch, compile and instantiate our module with the simple promise chain:
fetch('simple.wasm').then(response => response.arrayBuffer())
.then(bytes => instantiate(bytes, importObject))
.then(instance => instance.exports.e());
The last line calls our exported WebAssembly function which, in turn, calls our imported JS function which ultimately executes console.log(42)
.
Linear memory is another important WebAssembly building block that is typically used to represent the entire heap of a compiled C/C++ application. From a JavaScript perspective, linear memory (henceforth, just “memory”) can be thought of as a resizable ArrayBuffer
that is carefully optimized for low-overhead sandboxing of loads and stores.
Memories can be created from JavaScript by supplying their initial size and, optionally, their maximum size:
var memory = new WebAssembly.Memory({initial:10, maximum:100});
The first important thing to notice is that the unit of initial
and maximum
is WebAssembly pages which are fixed to be 64KiB. Thus, memory
above has an initial size of 10 pages, or 640KiB and a maximum size of 6.4MiB.
Since most byte-range operations in JavaScript already operate on ArrayBuffer
and typed arrays, rather than defining a whole new set of incompatible operations, WebAssembly.Memory
exposes its bytes by simply providing a buffer
getter that returns an ArrayBuffer
. For example, to write 42
directly into the first word of linear memory:
new Uint32Array(memory.buffer)[0] = 42;
Once created, a memory can be grown by calls to Memory.prototype.grow
, where again the argument is specified in units of WebAssembly pages:
memory.grow(1);
If a maximum
is supplied upon creation, attempts to grow past this maximum
will throw a RangeError
exception. The engines takes advantage of this supplied upper-bounds to reserve memory ahead of time which can make resizing more efficient.
Since an ArrayBuffer
’s byteLength
is immutable, after a successful Memory.grow
operation, thebuffer
getter will return a new ArrayBuffer
object (with the new byteLength
) and any previous ArrayBuffer
objects become “detached” (zero length, many operations throw).
Just like functions, linear memories can be defined inside a module or imported. Similarly, a module may also optionally export its memory. This means that JavaScript can get access to the memory of a WebAssembly instance either by creating a new WebAssembly.Memory
and passing it in as an import or by receiving a Memory
export.
For example, let’s take a WebAssembly module that sums an array of integers (replacing the body of the function with “…”):
(module
(memory (export "mem") 1)
(func (export "accumulate") (param $ptr i32) (param $length i32) …))
Since this module exports its memory, given an Instance
of this module called instance
, we can use its exports’ mem
getter to create and populate an input array directly in the instance’s linear memory, as follows:
var i32 = new Uint32Array(instance.exports.mem);
for (var i = 0; i < 10; i++)
i32[i] = i;
var sum = instance.exports.accumulate(0, 10);
Memory imports work just like function imports, only Memory
objects are passed as values instead of JS functions. Memory imports are useful for two reasons:
Memory
object to be imported by multiple instances, which is a critical building block for implementing dynamic linking in WebAssembly.