什么是 WebAssembly,它从何而来?

什么是 WebAssembly,它从何而来?

Thomas Steiner

自从 Web 不仅成为了文档的平台,也成为了应用的平台以来,一些最先进的应用已经将 Web 浏览器推到了极限。在许多高级语言中,都会采用通过与低级语言交互来“贴近硬件”以提高性能的方法。例如,Java 具有 Java 原生接口。对于 JavaScript,这种低级语言是 WebAssembly。在本文中,您将了解什么是汇编语言,以及为什么它在 Web 上很有用,然后了解如何通过 asm.js 临时解决方案创建 WebAssembly。

汇编语言

您是否曾使用汇编语言进行编程?在计算机编程中,汇编语言(通常简称为汇编,通常缩写为 ASM 或 asm)是任何低级编程语言,该语言的指令与架构的机器码指令之间存在非常强烈的对应关系。

例如,查看 Intel® 64 和 IA-32 架构 (PDF),MUL 指令(用于mul)会对第一个操作数(目标操作数)和第二个操作数(源操作数)执行无符号乘法,并将结果存储在目标操作数中。简而言之,目标操作数是位于寄存器 AX 中的隐式操作数,而源操作数位于通用寄存器(如 CX)中。结果再次存储在寄存器 AX 中。请参考以下 x86 代码示例:

mov ax, 5 ; Set the value of register AX to 5.

mov cx, 10 ; Set the value of register CX to 10.

mul cx ; Multiply the value of register AX (5)

; and the value of register CX (10), and

; store the result in register AX.

举个例子,如果任务是将 5 和 10 相乘,您可能会在 JavaScript 中编写类似于以下代码的代码:

const factor1 = 5;

const factor2 = 10;

const result = factor1 * factor2;

采用汇编方式的优势在于,这种经过机器优化的低级代码比经过人工优化的高级代码效率要高得多。在前面的示例中,这并不重要,但您可以想象,对于更复杂的操作,差异可能会很大。

顾名思义,x86 代码依赖于 x86 架构。如果有一种编写汇编代码的方法,不依赖于特定架构,但可以继承汇编的性能优势,该怎么办?

asm.js

编写不依赖于架构的汇编代码的第一步是使用 asm.js,它是 JavaScript 的严格子集,可用作编译器的高效低级目标语言。此子语言有效地描述了适用于 C 或 C++ 等非内存安全型语言的沙盒化虚拟机。通过结合使用静态和动态验证,JavaScript 引擎可以针对有效的 asm.js 代码采用提前优化编译策略 (AOT)。使用静态类型语言(例如 C)编写且采用手动内存管理的代码由源代码到源代码编译器(例如早期 Emscripten [基于 LLVM])进行转换。

通过将语言功能限制为适用于 AOT 的功能,提升了性能。Firefox 22 是第一个支持 asm.js 的浏览器,以 OdinMonkey 的名称发布。Chrome 在 61 版中添加了 asm.js 支持。虽然 asm.js 仍可在浏览器中运行,但已被 WebAssembly 取代。目前使用 asm.js 的原因是,它可以作为不支持 WebAssembly 的浏览器的替代方案。

WebAssembly

WebAssembly 是一种类似于汇编的低级语言,采用紧凑的二进制格式,运行时性能接近原生,并提供 C/C++、Rust 等语言以及许多其他语言,这些语言都具有编译目标,可在 Web 上运行。我们正在开发对 Java 和 Dart 等内存管理型语言的支持,应该很快就会推出,或者已经推出(例如 Kotlin/Wasm)。WebAssembly 旨在与 JavaScript 一起运行,让二者能够协同工作。

除了浏览器之外,WebAssembly 程序还可以在其他运行时中运行,这得益于 WebAssembly 系统接口 (WASI),它是 WebAssembly 的模块化系统接口。WASI 旨在实现跨操作系统的可移植性,并确保安全性和能够在沙盒环境中运行。

WebAssembly 代码(二进制代码,即字节码)旨在在便携式虚拟堆栈机 (VM) 上运行。与 JavaScript 相比,字节码的解析和执行速度更快,并且代码表示形式更紧凑。

指令的概念执行是通过传统的程序计数器进行的,该计数器会按指令顺序推进。在实践中,大多数 Wasm 引擎都会将 Wasm 字节码编译为机器码,然后执行该机器码。说明分为两类:

用于构成控制结构并从堆栈中弹出其参数值的控制指令可能会更改程序计数器,并将结果值推送到堆栈。

简单指令:从堆栈中弹出其参数值,对值应用运算符,然后将结果值推送到堆栈,然后隐式推进程序计数器。

回到前面的示例,以下 WebAssembly 代码与文章开头的 x86 代码等效:

i32.const 5 ; Push the integer value 5 onto the stack.

i32.const 10 ; Push the integer value 10 onto the stack.

i32.mul ; Pop the two most recent items on the stack,

; multiply them, and push the result onto the stack.

虽然 asm.js 完全通过软件实现,也就是说,其代码可以在任何 JavaScript 引擎中运行(即使未经过优化),但 WebAssembly 需要所有浏览器供应商都同意的新功能。WebAssembly 于 2015 年宣布,并于 2017 年 3 月首次发布,于 2019 年 12 月 5 日成为 W3C 推荐标准。W3C 会维护该标准,并接受所有主要浏览器供应商和其他利益相关方的贡献。自 2017 年起,浏览器支持范围涵盖所有主流浏览器。

WebAssembly 有两种表示法:文本和二进制。您在上面看到的是文本表示法。

文本表示法

文本表示法基于 S-表达式,通常使用文件扩展名 .wat(适用于 WebAssembly text 格式)。如果您真的想这样做,可以手写。我们将上面的乘法示例改为不再对因子进行硬编码,使其更实用,您或许可以理解以下代码:

(module

(func $mul (param $factor1 i32) (param $factor2 i32) (result i32)

local.get $factor1

local.get $factor2

i32.mul)

(export "mul" (func $mul))

)

二进制表示法

使用文件扩展名 .wasm 的二进制格式不适合供人使用,更不用说由人创建。使用 wat2wasm 等工具,您可以将上述代码转换为以下二进制表示法。(注释通常不是二进制表示法的一部分,而是由 wat2wasm 工具添加的,以便更好地理解。)

0000000: 0061 736d ; WASM_BINARY_MAGIC

0000004: 0100 0000 ; WASM_BINARY_VERSION

; section "Type" (1)

0000008: 01 ; section code

0000009: 00 ; section size (guess)

000000a: 01 ; num types

; func type 0

000000b: 60 ; func

000000c: 02 ; num params

000000d: 7f ; i32

000000e: 7f ; i32

000000f: 01 ; num results

0000010: 7f ; i32

0000009: 07 ; FIXUP section size

; section "Function" (3)

0000011: 03 ; section code

0000012: 00 ; section size (guess)

0000013: 01 ; num functions

0000014: 00 ; function 0 signature index

0000012: 02 ; FIXUP section size

; section "Export" (7)

0000015: 07 ; section code

0000016: 00 ; section size (guess)

0000017: 01 ; num exports

0000018: 03 ; string length

0000019: 6d75 6c mul ; export name

000001c: 00 ; export kind

000001d: 00 ; export func index

0000016: 07 ; FIXUP section size

; section "Code" (10)

000001e: 0a ; section code

000001f: 00 ; section size (guess)

0000020: 01 ; num functions

; function body 0

0000021: 00 ; func body size (guess)

0000022: 00 ; local decl count

0000023: 20 ; local.get

0000024: 00 ; local index

0000025: 20 ; local.get

0000026: 01 ; local index

0000027: 6c ; i32.mul

0000028: 0b ; end

0000021: 07 ; FIXUP func body size

000001f: 09 ; FIXUP section size

; section "name"

0000029: 00 ; section code

000002a: 00 ; section size (guess)

000002b: 04 ; string length

000002c: 6e61 6d65 name ; custom section name

0000030: 01 ; name subsection type

0000031: 00 ; subsection size (guess)

0000032: 01 ; num names

0000033: 00 ; elem index

0000034: 03 ; string length

0000035: 6d75 6c mul ; elem name 0

0000031: 06 ; FIXUP subsection size

0000038: 02 ; local name type

0000039: 00 ; subsection size (guess)

000003a: 01 ; num functions

000003b: 00 ; function index

000003c: 02 ; num locals

000003d: 00 ; local index

000003e: 07 ; string length

000003f: 6661 6374 6f72 31 factor1 ; local name 0

0000046: 01 ; local index

0000047: 07 ; string length

0000048: 6661 6374 6f72 32 factor2 ; local name 1

0000039: 15 ; FIXUP subsection size

000002a: 24 ; FIXUP section size

编译为 WebAssembly

如您所见,.wat 和 .wasm 都不是特别人性化。这时,Emscripten 等编译器就派上用场了。它可让您从 C 和 C++ 等高级语言进行编译。还有适用于 Rust 等其他语言的其他编译器。请考虑以下 C 代码:

#include

int main() {

printf("Hello World\n");

return 0;

}

通常,您会使用编译器 gcc 编译此 C 程序。

$ gcc hello.c -o hello

安装 Emscripten 后,您可以使用 emcc 命令和几乎相同的参数将其编译为 WebAssembly:

$ emcc hello.c -o hello.html

这将创建一个 hello.wasm 文件和 HTML 封装容器文件 hello.html。从 Web 服务器传送文件 hello.html 时,您会看到 "Hello World" 输出到开发者工具控制台。

您还可以不使用 HTML 封装容器,直接将代码编译为 WebAssembly:

$ emcc hello.c -o hello.js

与之前一样,这将创建一个 hello.wasm 文件,但这次创建的是 hello.js 文件,而不是 HTML 封装容器。如需进行测试,您可以使用 Node.js 等工具运行生成的 JavaScript 文件 hello.js:

$ node hello.js

Hello World

了解详情

本文对 WebAssembly 的简要介绍只是冰山一角。

如需详细了解 WebAssembly,请参阅 MDN 上的 WebAssembly 文档,并参阅 Emscripten 文档。说实话,使用 WebAssembly 有点像在使用 How to draw an owl meme(如何画猫头鹰表情包),尤其是因为熟悉 HTML、CSS 和 JavaScript 的 Web 开发者不一定精通 C 等要编译的语言。幸运的是,有 StackOverflow 的 webassembly 标签等渠道,如果您礼貌地提出问题,专家通常很乐意提供帮助。

注意: 请参阅将 mkbitmap 编译为 WebAssembly一文,了解如何将一个不完全简单但也不太复杂的 C 程序编译为 WebAssembly,该文档适合新手。本文通过 mkbitmap 示例介绍了如何在 JavaScript 中将 Wasm 程序用作库,以便使用文件作为输入并输出图片。

致谢

本文由 Jakob Kummerow、Derek Schuff 和 Rachel Andrew 审核。

相关推荐

哔哩哔哩电脑版怎么下载视频?
mobile365体育投注网站

哔哩哔哩电脑版怎么下载视频?

🌍 10-05 👁️ 9429
成语词典
365bet不能提现

成语词典

🌍 08-18 👁️ 5408
2025年最新版!这些真人化绝不踩雷!盘点超还原粉丝也说赞的十部漫改日本电影