WebAssembly 是一种用于在 Web 上执行代码的二进制格式,可以实现快速启动时间(与 JS 或 asm.js 相比,下载量更小,浏览器解析速度更快)。Emscripten 默认情况下会编译到 WebAssembly,但您也可以编译到 JS 以供旧版浏览器使用。
默认情况下会发出 WebAssembly,无需任何特殊标志。
注意
如果您**不**想要 WebAssembly,可以使用以下方法禁用它
emcc [..args..] -sWASM=0
注意
决定编译到 Wasm 还是 JS 可以链接阶段完成:它不会影响目标文件。
Emscripten 使用**上游 LLVM Wasm 后端**发出 WebAssembly,从 1.39.0
版本(2019 年 10 月)开始。以前,emscripten 也支持旧的**fastcomp** 后端,该后端在 2.0.0
(2020 年 8 月)中删除了。
如果您从 fastcomp 升级到上游,您可能会注意到两个后端之间存在一些差异
Wasm 后端对链接具有不同功能集的文件非常严格 - 例如,如果一个文件使用原子操作构建,而另一个文件没有,则它会在链接时出错。这可以防止潜在的错误,但可能意味着您需要进行一些构建系统修复。
WASM=0
在两个后端中的行为不同。在 fastcomp 中,我们发出 asm.js,而在上游中,我们发出 JS(因为并非所有 Wasm 结构都可以用 asm.js 表示)。此外,JS 支持实现相同的外部 WebAssembly.*
API,因此,特别是启动默认情况下将是异步的,就像 Wasm 一样,您可以使用 WASM_ASYNC_COMPILATION
(即使是 WASM=0
)来控制它。
Wasm 后端默认使用 Wasm 目标文件。这意味着它在编译步骤中进行代码生成,这使得链接步骤快得多 - 就像普通的原生编译器一样。相比之下,在 fastcomp 中,编译步骤在目标文件中发出 LLVM IR。
您通常不会注意到这一点,但一些编译器标志会影响代码生成,例如 DISABLE_EXCEPTION_CATCHING
。此类标志必须在代码生成期间传递。简单而安全的做法是在编译和链接时都传递所有 -s
标志。
您可以使用通常的 llvm 标志(-flto
、-flto=full
、-flto=thin
,在编译和链接时都使用;但是请注意,thin LTO 目前尚未经过大量测试,因此建议使用常规 LTO)。
对于 fastcomp,LTO 优化传递默认情况下不会运行;为此,必须传递 --llvm-lto 1
。使用 llvm 后端,LTO 传递将在任何处于位代码格式的目标文件上运行。
您可能还会注意到,即使没有设置 LTO,fastcomp 的链接阶段也能够执行一些次要类型的链接时优化。LLVM 后端要求实际设置 LTO 才能执行这些操作。
wasm-ld 是 Wasm 后端使用的链接器,它要求库(.a 档案)包含符号索引。这与原生 GNU 链接器的行为一致。虽然 emar 默认情况下会创建此类索引,但 GNU ar 和 GNU strip 等原生工具不了解 WebAssembly 对象格式,因此无法创建档案索引。特别是,如果您在包含 WebAssembly 对象文件的档案文件上运行 GNU strip,它将删除索引,这使得档案在链接时无法使用。
另请参阅有关 Wasm 后端的 阻断错误,以及 Wasm 后端标记的问题。
WebAssembly 可能会陷入困境 - 抛出异常 - 在诸如除以零、将非常大的浮点数四舍五入为整数等操作上。在 asm.js 中,此类操作会被静默忽略,因为在 JavaScript 中它们不会抛出,因此这是 JavaScript 和 WebAssembly 之间的区别,您可能会注意到,浏览器会报告类似于 float unrepresentable in integer range
、integer result unrepresentable
、integer overflow
或 Out of bounds Trunc operation
的错误。
LLVM Wasm 后端通过在每个可能的陷阱周围添加更多代码来避免陷阱(如果会发生陷阱,则基本上会对值进行钳位)。如果您不需要这些额外的代码,这会增加代码大小并降低速度。对此的正确解决方案是使用不会发生陷阱的较新 Wasm 指令,方法是在使用 -mnontrapping-fptoint
调用 emcc 或 clang 时进行调用。但是,该代码可能无法在较旧的 VM 中运行。
使用 emcc
构建到 WebAssembly 时,您将看到一个包含该代码的 .wasm
文件,以及通常的 .js
文件,它是编译的主要目标。这两个文件是为一起工作而构建的:运行 .js
(或 .html
,如果您要求这样做)文件,它将为您加载和设置 WebAssembly 代码,正确设置导入和导出等。基本上,您无需关心编译后的代码是 asm.js 还是 WebAssembly,它只是一个编译器标志,否则一切应该正常工作(除了 WebAssembly 应该更快)。
请注意,.wasm
文件不是独立的 - 很难在没有该 .js
代码的情况下手动运行它,因为它依赖于获取与 JS 集成的正确导入。例如,它接收对系统调用的导入,以便它可以执行诸如打印到控制台之类的操作。目前正在进行的工作旨在创建独立的 .wasm
文件,请参阅 WebAssembly 独立页面。
您可能还会看到生成的其他文件,例如如果您将文件预加载到虚拟文件系统中,则会看到 .data
文件。所有这些与构建到 asm.js 时完全相同。您可能注意到的一个区别是缺少 .mem file
,该文件用于 asm.js,包含静态内存初始化数据,在 WebAssembly 中,我们可以将其更有效地打包到 WebAssembly 二进制文件本身中。
所有主流浏览器都支持 WebAssembly,可以追溯到 Firefox 52、Chrome 57、Safari 11 和 Opera 44。
有关各种浏览器中支持的 WebAssembly 功能的更多信息,请参阅 WebAssembly 路线图
.wasm
文件和编译¶WebAssembly 代码的准备方式与 asm.js 有些不同。asm.js 可以捆绑在主 JS 文件中,而如前所述,WebAssembly 是一个单独的二进制文件,因此您将有多个文件要分发。
另一个显着的效果是 WebAssembly 默认情况下是异步编译的,这意味着您必须等待编译完成才能调用编译后的代码(通过等待 main()
或 onRuntimeInitialized
回调等,您还需要在执行任何其他导致启动异步的操作时执行此操作,例如 asm.js 的 .mem
文件或预加载的文件数据等)。您可以通过设置 WASM_ASYNC_COMPILATION=0
来关闭异步编译,但这可能无法在 Chrome 中使用,因为那里存在当前限制。
请注意,即使关闭了异步编译,获取 WebAssembly 二进制文件也可能需要异步操作(因为 Web 不允许在主线程上进行同步二进制下载)。如果您能够自行获取二进制文件,则可以设置 Module['wasmBinary']
,它将从那里使用,然后(在关闭异步编译的情况下)编译应该是同步的。
为了通过网络以最有效的方式提供 Wasm,请确保您的 Web 服务器为 .wasm
文件设置了正确的 MIME 类型,即 application/wasm。这将允许流式编译,浏览器可以在下载代码时开始编译。
在 Apache 中,您可以通过以下方式实现:
AddType application/wasm .wasm
同时也要确保启用了 gzip。
AddOutputFilterByType DEFLATE application/wasm
如果您提供大型 .wasm
文件,Web 服务器会在每次请求时动态压缩它们,从而消耗 CPU 资源。您可以预先将它们压缩为 .wasm.gz
,并使用内容协商。
Options Multiviews
RemoveType .gz
AddEncoding x-gzip .gz
AddType application/wasm .wasm