… 尤其是当 JavaScript 即将迎来 pthreads (Mozilla pthreads, Chromium pthreads) 和 SIMD (simd.js, Chromium SIMD, simd.js in asm.js) 的时候。
WebAssembly 具有如下两个主要的优点:
为 WebAssembly 设计的二进制格式可以被原生解码,这比 JavaScript 的解析要快很多(实验表明至少要快20倍以上)。在移动设备上,较大的编译后代码仅仅解析就需要占用 20-40 秒,所以原生解码(特别是为了比 gzip 更好的压缩效果时需要与其他技术例如 streaming 结合的时候)对于提供良好的零负载用户体验是非常重要的。
为了避免同步 asm.js 针对 AOT-编译的约束,并保证在没有针对 asm.js 的特殊优化的设备上也有好的性能表现,用一个新的标准使得我们能够更容易的添加这些特性 ,这需要具有原生层面的性能表现能力。
当然,每一个新标准势必都会引入抵消其带来便利之外的新的开销(维护成本、攻击面、代码大小等等)。 WebAssembly 通过在设计上允许(尽管不是必须的)浏览器基于已有的 JavaScript 引擎(从而可以重用 JavaScript 引擎现有的编译器后端,ES6 在前端的模块化加载,安全沙箱机制和其他支持的 VM 组件)去实现 WebAssembly,来达到最小化其新引入的开销的目的。因此,从根本上说 WebAssembly 应该被认为是 JavaScript 一个重大的新特性,而不是浏览器模型的基础延伸。比较两者,即使是对已经优化过 asm.js 的引擎来说,也是利大于弊的。
WebAssembly 在设计的时候就考虑到了广泛的用途.
我们是这样认为的。这里有一个早期原型附带演示页面 [1, 2], 他们展示了从类似 WebAssembly 的二进制格式解码为 asm.js 可以是高效的。而且随着 WebAssembly 设计的变化,更多 polyfilling 的例子涌现出来。
总体来说,由于浏览器厂商迅速采纳 WebAssembly 使得人们对其发展保持乐观的态度,但这削减了将它作为一个 polyfill 的工作热情。
另一方面也是由于存在替代品,使得将 WebAssembly polyfilling 到 asm.js 变得不是那么紧急,例如,存在一个反向的 polyfill - 能将 asm.js 编译到 WebAssembly - 而且他允许单独构建的版本在 asm.js 或者 WebAssembly 上运行。通过在 emscripten 中触发开关,也有可能建立一个拥有 asm.js 和 WebAssembly 两个并行的构建项目,这可以完全避免在客户端的 polyfill 时间消耗。第三点则是,对于非执行代码,可以使用编译的 WebAssembly 解释器例如 binaryen.js.
即便如此,WebAssembly polyfill 仍然是一个有趣的想法,而且是原则上可行的。
正如在长远目标一章中所说,为了实现最小可行性产品,发展之初会主要关注于 C/C++。
然而,通过使用 ES6 的模块化接口集成到 JavaScript 中, web 开发者并不需要通过写 C++ 就可以利用这些别人已经实现的库的优势了;重用一个模块化的 C++ 库就像在 JavaScript 中调用一个模块一样简单。
除 MVP 外,另一个长远目标是提高 WebAssembly 对 除 C/C++ 之外的编程语言的支持。这包括允许 WebAssembly 代码去分配和访问垃圾回收 (JavaScript, DOM, Web API) 对象 。即使在对垃圾回收对象的支持被添加到 WebAssembly 之前,将一门语言的 VM 编译成 WebAssembly (假设它是由可移植的 C/C++ 编写的)也是可能的,且这已经被证实 (1, 2, 3). 然而,”编译 VM” 的策略增加了分布式代码的大小,缺失了浏览器开发者工具的集成,可能存在有跨语言的循环收集问题以及错过一些需要浏览器集成的优化。
WebAssembly 最初关注于 C/C++,一个新的、干净的 WebAssembly 后端是由 clang/LLVM 开发的,之后可以在基于 LLVM 项目例如 Emscripten 和 PNaCl 中使用。
随着 WebAssembly 的发展,除了 C/C++ 外它会支持越来越多的语言,我们希望其他编译器也能支持它,即使是对 C/C++ 而言,例如 GCC. WebAssembly 工作组发现从支持 LLVM 开始会更加容易,因为他们对来自 Emscripten and PNaCl 工作中的工具链有更多的经验。
我们希望专有的编译器也可以获得 WebAssembly 的支持,但是对于第三方平台来说,我们让他们自己决定。
WebAssembly 社区小组 会很高兴和更多的编译器平台合作,将他们的付出考虑结合到 WebAssembly 中,和他们共同致力于 ABI 问题。
是的!WebAssembly 定义了一个用于呈现的文本格式,这允许开发人员在任何开发者工具中查看 WebAssembly 模块的源码。而且,该文本格式的具体目标是允许开发人员编写用于测试,实验,优化,学习和教学目的的 WebAssembly 模块。事实上,通过摈弃所有 asm.js 验证所需的强制, WebAssembly 文本格式在读写上会比 asm.js 更加自然。 在浏览器之外,用于文本和二进制相互转换的命令行和在线工具也将随时可用。最后,source maps 的可扩展形式也被考虑成为 WebAssembly 工具技术 之一。
现有的 Emscripten 用户将可以通过配置将他们的项目构建到 WebAssembly. 最初,Emscripten 的 asm.js 输出结果将会转化为 WebAssembly, 但最终 Emscripten 将会在这个处理流程中使用 WebAssembly. 这个平稳的转变过程是通过在长远目标中设定的目标,即 WebAssembly 和 Web 平台(包括允许同步的和 JavaScript 相互调用)的友好结合来实现的,这使得 WebAssembly 与 Emscripten 当前的 asm.js 编译模型兼容。
不!WebAssembly 是被设计成 JavaScript 的一个完善补充,而不是它的替代品。虽然随着时间的推移,WebAssembly 将能够把很多编程语言都编译到 Web 中,但 JavaScript 不可思议的能力仍将使它保持在 Web 动态语言独一、特有( 如上所述)的地位。此外通过多种配置, JavaScript 将可以和 WebAssembly 一起使用:
LLVM 的编译器构造有很多地方值得推荐:它具有现成的中间表示 (LLVM IR) 和二进制编码格式 (bitcode). 它有针对许多架构的代码生成后端,并由大型社区积极开发和维护。 事实上,PNaCl 已经使用 LLVM 作为二进制格式的基础。然而,LLVM 旨在满足的目标和要求和 WebAssembly 相比会略微不匹配。
WebAssembly 对其指令集架构 (ISA) 和二进制编码有一些要求和目标:
LLVM IR旨在使编译器的优化易于实现,并可代表在各种各样的操作系统和架构上表示 C,C++ 和其他语言所需的结构和语义。这意味着 IR 默认是不可移植的(同一个程序对于不同架构会有不同的表示)或稳定的(随着时间推移,会随着优化和语言要求的变化而变化)。它对各样的信息表示非常有用,这有利于实现中级编译器优化但对生成代码没有帮助(但它代表了很大一块代码生成/执行者可以处理的内容)。它也有未定义的行为(很大程度上类似于C和C ++),这使得一些优化类可行或更强大,但在运行时可能会导致不可预测的行为。 LLVM 的二进制格式(位代码)被设计用于 IR 临时磁盘序列化的链接时间优化,而不是稳定性或可压缩性(尽管它们确实有两个功能)。
这些问题都不是无法克服的。 例如PNaCl定义了一个小的具有减少未定义行为的IR的可移植子集以及位码编码的稳定版本。它还采用几种技术来改善启动性能。 然而,每个定制、解决方法和特殊解决方案意味着从共同基础设施中获的更少的收益。我们相信利用我们开发 LLVM 的经验和为我们目标和要求设计的 IR 和二进制编码,我们可以比适应以其他目的为设计的系统做得更好。
请注意,本讨论适用于使用 LLVM IR 作为标准化格式。LLVM 的 clang 前端和中级优化器仍然可以使用从 C 和 C++ 生成的 WebAssembly 代码,并在他们的实现中使用 LLVM IR, 就像如今 PNaCl 和 Emscripten 的做法一样。
优化编译器通常会允许编译器使用的 fast-math 标志来放宽浮点规则以达到更积极的优化效果。这包括假设 NaN 或者 infinities 不会发生,忽略负零和正零之间的差异,在代数操作时可能遇到的如何执行舍入或溢出、使用近似替换运算符的计算开销会更低。
这些优化有效地引入了非确定性;在不知道优化器具体如何选择的情况下是不可能确定代码会如何表现的。这通常不是本机代码场景中的一个严重问题,因为所有的非确定性都是由本机代码产生时所决定的。由于大多数硬件没有浮点非确定性,开发人员有机会测试生成的代码,然后依靠它使之后所有用户的行为一致。
WebAssembly 的实现在用户一端运行,所以开发者没有机会来测试代码的最终表现。这个层次的非确定性可能导致分布式 WebAssembly 程序在不同的实现下拥有不同的行为表现,或随时间推移而变化。权衡之下,WebAssembly 确实有一些非确定性,但是,fast-math 标记被认为是不够重要的:
sin
, cos
, exp
,
pow
等等。对于这些函数,WebAssembly 的策略是允许他们以库程序的方式被实现进 WebAssembly (请注意 x86 架构的 sin
和 cos
指令是缓慢且不精确的,这些日子以来任何形式下都需要避免使用他们)。希望在 WebAssembly 上使用更快更精确的数学函数的用户可以简单地选择一个符合要求的数学库实现。add
, sub
或者 mul
遇到 NaN 还有使它们更快的地方,因为 NaN 在所有现代平台上的硬件上的处理都是速度且透明的。mmap
又怎样呢?mmap
系统调用有很多有用的功能。虽然这些都被打包成POSIX中的一个重载系统调用程序,WebAssembly 将此功能解包为多个运算符:
grow_memory
运算符 MVP 开始拥有增长线性内存的能力;0
到 memory_size
的值。上述列表中缺少 mmap
的一个重要特征是分配不相交的虚拟地址范围的能力。这种遗漏的原因是:
mmap
, 这似乎是不连续的内存分配(但是在底层,只是协调使用 memory_resize
和 mprotect
/map_file
/map_shmem
/madvise
)。允许不连续的虚拟地址分配的好处是如果它允许引擎将 WebAssembly 模块的线性内存与其他内存分配在同一进程中进行交错(以减轻虚拟地址空间碎片)。这有两个问题:
size_t
?维护抽象 size_t
所需的线性内存数量也需要通过抽象过程来确定,然后将线性内存地址空间分割成不同块会更加复杂。每块的大小取决于有多少 size_t
大小的对象被存储
在里面。这在理论上是可行的,但会增加复杂性并且在应用程序启动时要做更多的工作。
此外,允许应用程序静态地知道指针大小可以允许它们被更激进的优化。当优化器具有对 Bitwidth 的全面了解时,可以更好地折叠和简化整数表达式。而且,知道各种类型的内存大小和布局允许人们知道在各种指针类型中有多少尾随零。
此外,C 和 C++ 与抽象 size_t
的概念深深地冲突。因为可以参与到类型检查中,像 sizeof
这样的构造需要在编译器前端进行全面的评估。即使在这之前,通常有预定义的宏来指示指针大小,允许代码在最早的汇编阶段专门处理指针大小。一旦专门处理完成,信息就会丢失,其他的尝试会引入抽象
最后,如果需求增长且实用性允许的话,未来仍然有可能添加一个抽象的 size_t
.
大量应用程序不需要高达4 GiB的内存。强制所有这些应用程序为他们存储的每个指针使用8个字节会显着增加他们需要的内存量,并降低它们利用重要的硬件资源的效率,如缓存、内存和带宽。
这里的动机和表现效果应该和那些为 Linux 推动 x32 ABI 发展的基本相同。
即便 Knuth 也认为这是值得的,他告诉了我们对这个问题的看法,见 a flame about 64-bit pointers.
是的,但是它取决于 WebAssembly embedder. 在浏览器中,你可以像使用常规 JavaScript 那样访问相同的 HTML5 和其他浏览器特定的 API. 然而,如果某个第三方实现提供了一个 wasm VM 作为 “应用执行平台”,它也许提供了对特定平台 APIs 的访问权限,比如说 Android / iOS.