WebAssembly 是一种字节码格式,几乎每个浏览器都可以将其编译为其宿主系统的机器代码。与 JavaScript 和 WebGL 一起,WebAssembly 满足了在 Web 浏览器中移植应用程序以实现平台无关使用的需求。作为 C++ 和 Rust 的编译目标,WebAssembly 使 Web 浏览器能够以接近原生速度执行代码。
当您谈论 WebAssembly 应用程序时,您必须区分三种状态
- 源代码(例如,C++ 或 Rust): 您有一个用兼容语言编写的应用程序,您想在浏览器中执行它。
- WebAssembly 字节码: 您选择 WebAssembly 字节码作为您的编译目标。结果,您得到一个
.wasm
文件。 - 机器代码(操作码): 浏览器加载
.wasm
文件并将其编译为其宿主系统的相应机器代码。
WebAssembly 也有一种文本格式,以人类可读的文本表示二进制格式。为了简单起见,我将其称为 WASM-文本。WASM-文本可以比作高级汇编语言。当然,您不会基于 WASM-文本编写完整的应用程序,但了解其底层工作原理是很有好处的(特别是对于调试和性能优化)。
本文将指导您在 WASM-文本中创建经典的 Hello World 程序。
创建 .wat 文件
WASM-文本文件通常以 .wat
结尾。从头开始,创建一个名为 helloworld.wat
的空文本文件,用您最喜欢的文本编辑器打开它,然后粘贴以下内容
(module
;; Imports from JavaScript namespace
(import "console" "log" (func $log (param i32 i32))) ;; Import log function
(import "js" "mem" (memory 1)) ;; Import 1 page of memory (54kb)
;; Data section of our module
(data (i32.const 0) "Hello World from WebAssembly!")
;; Function declaration: Exported as helloWorld(), no arguments
(func (export "helloWorld")
i32.const 0 ;; pass offset 0 to log
i32.const 29 ;; pass length 29 to log (strlen of sample text)
call $log
)
)
WASM-文本格式基于 S-表达式。为了实现交互,JavaScript 函数使用 import
语句导入,WebAssembly 函数使用 export
语句导出。对于此示例,从 console
模块导入 log
函数,该函数接受两个 i32
类型的参数作为输入,并需要一页内存 (64KB) 来存储字符串。
字符串将被写入偏移量为 0
的 data
部分。data
部分是您内存的覆盖,内存是在 JavaScript 部分分配的。
函数用关键字 func
标记。进入函数时,堆栈为空。函数参数在调用另一个函数之前被推入堆栈(此处为偏移量和长度)(参见 call $log
)。当函数返回 f32
类型时(例如),离开函数时,f32
变量必须保留在堆栈上(但本例中并非如此)。
创建 .wasm 文件
WASM-文本和 WebAssembly 字节码具有 1:1 的对应关系。这意味着您可以将 WASM-文本转换为字节码(反之亦然)。您已经有了 WASM-文本,现在您想创建字节码。
转换可以使用 WebAssembly Binary Toolkit (WABT) 执行。克隆该链接的存储库并按照安装说明进行操作。
构建工具链后,通过打开控制台并输入以下内容将 WASM-文本转换为字节码
wat2wasm helloworld.wat -o helloworld.wasm
您还可以使用以下命令将字节码转换为 WASM-文本
wasm2wat helloworld.wasm -o helloworld_reverse.wat
从 .wasm
文件创建的 .wat
文件不包含任何函数或参数名称。默认情况下,WebAssembly 使用索引标识函数和参数。
编译 .wasm 文件
目前,WebAssembly 仅与 JavaScript 共存,因此您必须编写一个简短的脚本来加载和编译 .wasm
文件并执行函数调用。您还需要定义将在 WebAssembly 模块中导入的函数。
创建一个空的文本文件并将其命名为 helloworld.html
,然后打开您最喜欢的文本编辑器并粘贴以下内容
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Simple template</title>
</head>
<body>
<script>
var memory = new WebAssembly.Memory({initial:1});
function consoleLogString(offset, length) {
var bytes = new Uint8Array(memory.buffer, offset, length);
var string = new TextDecoder('utf8').decode(bytes);
console.log(string);
};
var importObject = {
console: {
log: consoleLogString
},
js : {
mem: memory
}
};
WebAssembly.instantiateStreaming(fetch('helloworld.wasm'), importObject)
.then(obj => {
obj.instance.exports.helloWorld();
});
</script>
</body>
</html>
WebAssembly.Memory(...)
方法返回一页大小为 64KB 的内存。函数 consoleLogString
根据长度和偏移量从该内存页读取字符串。这两个对象都作为 importObject
的一部分传递给您的 WebAssembly 模块。
在您可以运行此示例之前,您可能需要允许 Firefox 访问此目录中的文件,方法是在地址栏中键入 about:config
并将 privacy.file_unique_origin
设置为 true

(Stephan Avenwedde, CC BY-SA 4.0)
注意: 这将使您容易受到 CVE-2019-11730 安全问题的攻击。
现在,在 Firefox 中打开 helloworld.html
并输入 Ctrl+K 以打开开发者控制台。

(Stephan Avenwedde, CC BY-SA 4.0)
了解更多
这个 Hello World 示例只是 MDN 理解 WebAssembly 文本格式 文档中详细教程之一。如果您想了解更多关于 WebAssembly 及其底层工作原理的信息,请查看这些文档。
评论已关闭。