译者注:这篇文章是《WebAssembly 权威指南》一书的第一章,介绍了 WebAssembly 的基本概念和特点。WebAssembly 是一种新的编程语言,可以在不同的平台和环境中高效地执行。文章阐述了 WebAssembly 的设计目标、优势、应用场景和发展历史。文章还对比了 WebAssembly 和 JavaScript 的区别和关系,以及 WebAssembly 和其他编译目标语言的异同。

本章介绍 WebAssembly 及其诞生的背景。从某种意义上说,它是过去几年来 Web 发展的顶峰。我们有相当多的历史要讲。如果你不喜欢历史论述,你可以跳过这一章,直接去看第二章,但我希望你不要这样做。理解为什么这项技术如此重要以及它的起源是很重要的。

WebAssembly 提供什么

我认为工程师最大的技能是能够评估新技术能带来什么。正如北卡罗来纳大学的 Fred Brooks 博士提醒我们的那样,没有“银弹”,任何事情都需要权衡取舍。复杂性通常不会被新技术消除,而只是转移到别处。因此,当某件事确实有所作为或引导我们朝着积极的方向发展时,值得关注并找出原因。

当我试图理解新事物的含义时,我通常首先尝试确定其背后的动机。了解备选方案的不足之处可以为我们提供洞察力。之前有什么,它是如何影响这项新技术的?就像艺术和音乐一样,我们不断地从多个来源借鉴好的想法,所以要真正理解为什么 WebAssembly 值得关注,以及它能提供什么,我们必须先看看它发挥作用之前发生了什么。

在正式向世界介绍 WebAssembly 的论文中,作者表示他们的动机是满足现代 Web 交付的需求,就软件应用程序而言,JavaScript 无法做到1。最终,它是一种交付软件的追求:

  • 安全
  • 快速
  • 可移植
  • 紧凑

在这个愿景中,WebAssembly 以软件开发、Web、Web 的历史以及它如何在广阔的地理空间中提供功能的交叉点为中心。随着时间的推移,这个想法已经大大超出了一个无处不在、安全、高性能的计算平台几乎触及我们生活的方方面面的最初想法。WebAssembly 将影响客户端 Web 开发、桌面和企业应用程序、服务器端应用程序、游戏、教育、云计算、移动平台、物联网 (IoT) 生态系统、无服务器和微服务等。我希望通过这本书让你相信这一点。

今天的部署平台比以往任何时候都更加多样化,因此我们需要代码和应用程序级别的可移植性。通用指令集或字节码目标使算法能够在不同的环境中工作,因为我们只需要将逻辑步骤映射到特定的机器架构。程序员使用应用程序编程接口 (API),例如 OpenGL2、POSIX3 或 Win324,因为它们提供了打开文件、启动子进程或在屏幕上绘图的功能。它们很方便,减少了开发人员需要编写的代码量,但它们依赖于函数库。如果 API 在目标环境中不可用,应用程序将无法运行。这是微软利用其在操作系统市场的主导地位,同时在应用程序套件中取得主导地位的原因之一。另一方面,开放标准可以使软件更容易移植到不同的环境。

软件运行时的另一个问题是不同的主机有不同的硬件能力(CPU 核心数量,是否有 GPU)或安全限制(是否可以打开文件、发送和接收网络流量)。软件通常通过使用功能测试方法来确定应用程序可以利用哪些资源来适应现有事物,但这通常会使业务功能复杂化。我们根本负担不起为多个平台不断重写软件所需的时间和金钱。相反,我们需要更好的重用策略。我们还需要这种灵活性,而不需要修改代码以支持它将运行的平台的复杂性。为不同的主机环境编写不同的代码会增加复杂性,使测试和部署策略复杂化。

经过几十年的发展,开源软件的价值主张已经变得清晰。我们倾向于使用其他开发人员为我们自己的需要编写的有价值的、可重用的组件5。然而,并非所有可用代码都是可信的,当我们执行从 Internet 下载的不可信代码时,我们很容易受到软件供应链攻击。通过网络钓鱼攻击、数据泄露、恶意软件和勒索软件,我们很容易受到不安全软件系统的风险、业务影响和个人成本的影响。

到目前为止,JavaScript 一直是其中一些问题的唯一解决方案。它在沙盒环境中运行时为我们提供了一定程度的安全性。它无处不在且可移植。引擎已经足够快,生态系统正在蓬勃发展。但是,一旦离开基于浏览器的保护,仍然会有安全问题。作为客户端运行的 JavaScript 代码与在服务器上运行的 JavaScript 代码之间存在差异。单线程设计使长时间运行或高度并发的任务变得复杂。由于其作为动态语言的起源,其他编程语言中有几类优化可用,并且不会作为最快和最现代的 JavaScript 运行时的选项提供。

此外,很容易添加 JavaScript 依赖项而没有意识到其中存在的风险。不花时间仔细考虑依赖风险的开发人员最终可能会在上游软件测试、部署和使用的各个方面造成麻烦。每个脚本在通过 Web 传输后都必须加载和验证。这会增加加载时间并使一切变得缓慢。当修改或删除依赖包时,它有可能破坏大量已部署的软件6

一些观察家将 WebAssembly 视为对 JavaScript 的攻击,但事实并非如此。当然,如果你愿意,你可以避免使用 JavaScript,但它主要是让你可以选择用你选择的语言解决问题,而不需要单独的运行时,也不必关心另一个软件是用什么语言编写的。现在可以在不知道 WebAssembly 模块是如何构建的情况下使用它。这将增加我们从软件中获得的商业价值,同时允许我们在采用新语言时进行创新,而不会影响其他部分。

几十年来,我们尝试了多种工具、语言、平台和框架来尝试解决这些问题,但 WebAssembly 是第一个做到这一点的。它的设计师并没有试图过度指定任何东西。他们从过去的经验中学习,拥抱网络,并将问题空间思维应用于多维问题。在我们深入研究之前,让我们看看这项技术是如何产生的。

Web 的历史

WebAssembly 社区中流传着一个笑话,即 WebAssembly“不是 Web,也不是 Assembly”7。虽然在某些方面确实如此,但这个名称足以说明它所提供的功能。它是一个目标平台,有一系列的指令,有点像汇编8。事实上,WebAssembly 模块经常作为另一种类型的 URL 可寻址资源在 Web 上传递,这证明在名称中包含 Web 一词是合理的。

“传统软件开发”和“网络开发”的主要区别在于,一旦有了浏览器,其实就不需要再安装软件了。在交付成本、错误处理和新功能请求方面,这种快速迭代新版本的能力改变了游戏规则。在其他跨平台技术生态系统中,如互联网和 Web,也更容易支持多种硬件和软件环境。

万维网的发明者 Tim Berners-Lee 爵士曾在 CERN 工作,在那里他提交了一份互连文档、图像和数据的提案,以实现 CERN 更大的研究目标9。虽然事后看来影响很明显,但在此之前他不得不多次在内部推广他的想法10。作为一个组织,欧洲核子研究中心 (CERN) 由来自世界各地的数十家研究机构代表,他们向科学家派遣他们自己的计算机、应用程序和数据。没有真正的能力强制每个人都使用相同的操作系统或平台,所以他认为需要一个技术方案来解决这个问题。

在 Web 出现之前,有像 Archie11、Gopher12 和 WAIS13 这样的服务,但他设想了一个对用户更友好的平台,最终将作为 Internet 层次结构之上的应用层出现。他还从标准通用标记语言 (SGML) 14 中汲取灵感,后者成为超文本标记语言 (HTML) 的基础。

这些设计的结果很快成为向世界提供信息、文档和最终应用程序功能的主要机制。它通过定义标准交换来做到这一点,包括如何提出请求和返回什么,而不需要各个利益相关者就特定技术或平台达成一致。任何理解这些标准的软件都可以与理解这些标准的任何其他软件进行通信。这给了我们选择的自由,以及任何一个独立于另一个进化的能力。

JavaScript 的起源

Web 的交互模型被称为超文本传输协议(HTTP)。它是基于一组受限制的动词来交换基于文本的信息。虽然这是一个简单而有效的模型,易于实现,但由于不断返回服务器的固有延迟,它很快就被认为不适合交互式的现代应用。将代码下发到浏览器的想法一直很有说服力。如果它在用户的交互中运行,那么不是每一个活动都需要返回服务器。这将极大的提高 Web 应用的互动性、响应性和使用乐趣。如何实现这一点这并不完全清楚。哪种编程语言最合适?如何将其设计的浅显易懂又有强大的表达能力,以便更多的人能够参与到开发过程中?如何保护客户端的敏感资源不受恶意软件的侵害?

浏览器领域最初的大部分创新都是由网景公司(Netscape Communications Corp)推动的。信不信由你,Netscape Navigator 最初是一个付费软件,但该公司更大的兴趣是销售服务器端软件 15。通过扩展客户端的功能,它可以创造和销售更强大和有利可图的服务器功能。

当时,Java 正从其作为计算机设备的嵌入式语言的雏形中出现,但它还没有太多的成功记录。作为 C++ 的简化版本,它是一个引人注目的想法,它运行在一个虚拟平台上,因此本质上是跨平台的。作为一个旨在运行通过 Web 下载的软件的环境,它通过语言设计、沙盒容器和细粒度的许可模型来实现安全。

在不同的操作系统之间移植应用程序是一件棘手的事情,而现在不用再考虑兼容性问题,人们对软件开发的未来产生了狂热的期待。太阳微系统公司发现自己处于一个令人羡慕的位置,正在讨论将 Java 引入到浏览器,但还没搞清具体如何实施,以及何时发布。

Web 的交互模型称为超文本传输协议 (HTTP)。它基于一组受限制的动词来交换基于文本的信息。虽然这是一个简单有效的模型,并且易于实施,但由于不断返回服务器的固有延迟,它很快被认为不适合交互式现代应用程序。将代码向下发送到浏览器的想法一直很引人注目。如果它在用户交互上运行,那么并不是每个活动都需要返回到服务器。这将大大提高使用 Web 应用程序的交互性、响应性和乐趣。这是如何实现的还不完全清楚。哪种编程语言最合适?如何做到通俗易懂,表达能力强,让更多人参与到开发过程中?如何保护客户端的敏感资源免受恶意软件的侵害?

浏览器领域的许多原始创新都是由 Netscape Communications Corp 推动的。不管你是否相信,Netscape Navigator 最初是作为付费软件开始的,但该公司更感兴趣的是销售服务器端软件15。通过扩展其客户的功能,它可以创建和销售更强大、更有利可图的服务器功能。

当时,Java 作为一种用于计算机设备的嵌入式语言刚刚起步,但并没有太多成功的记录。作为在虚拟平台上运行的 C++ 的简化版本,这是一个引人注目的想法,因此本质上是跨平台的。作为一个旨在运行通过 Web 下载的软件的环境,它通过语言设计、沙盒容器和细粒度许可模型实现安全性。

在不同操作系统之间移植应用程序是一件棘手的事情,现在兼容性问题不再是一个问题,人们对软件开发的未来充满了期待。Sun Microsystems 发现自己在谈论将 Java 引入浏览器方面处于令人羡慕的位置,但尚未弄清楚它将如何以及何时发布。

作为一种面向对象的编程 (OOP) 语言,Java 包含复杂的语言特性,例如线程和继承。在 Netscape,有人担心非专业软件开发人员可能太难掌握,因此公司聘请 Brendan Eich 创建“浏览器的解决方案16”,设想一种更简单、轻量级的脚本语言17。Brendan 可以自由决定他想在语言中包含什么,但他迫于尽快完成的压力。用于交互式应用程序的语言被认为是这个新生平台向前迈出的关键一步,每个人都希望它尽快完成。正如 Sebastián Peyrott 在他的博客文章“JavaScript 简史”中指出的那样,出现的是“Scheme 和 Self 的早产儿,具有 Java 外观”18

最初,浏览器中的 JavaScript 仅限于简单的交互,例如动态菜单、弹出对话框和对按钮点击的响应。这些是每次操作都必须往返于服务器的重大改进,但与当时台式机和工作站上的可能性相比,它仍然是一个玩具。

我在 Web 早期工作的公司创建了第一个地球可视化环境,涉及数 TB 的地形信息、高光谱图像和从无人机视频中提取的视频帧19。当然,这最初需要一个 Silicon Graphics 工作站,但在几年内,它就能够在配备消费级图形处理单元 (GPU) 的 PC 上运行。当时在 Web 上,这样的事情根本不可能,但是,由于 WebAssembly,情况不再如此20

真正的软件开发和网络开发之间根本没有混淆。正如我们所注意到的,客户端和服务器之间关注点分离的好处之一是客户端可以独立于服务器发展。当 Java 和 Java Enterprise 模型开始主导后端时,JavaScript 在浏览器中发展并最终成为其主导力量。

Web 平台的演变

随着 Java applet 和 JavaScript 在 Netscape 浏览器中的引入,开发人员开始尝试动态网页、动画和更复杂的用户界面组件。几年来,这些还只是玩具应用,但人们对它的未来充满了想象。

微软觉得有必要跟上,但对直接支持其竞争对手的技术兴趣不大。它(正确地)相信这种 Web 演变最终会颠覆其操作系统的主导地位。当 Microsoft 发布其支持脚本的 IE 浏览器时,该公司将其称为 JScript 以避免法律问题并对 Netscape 的解释器进行逆向工程。Microsoft 的版本支持与基于 Windows 的组件对象模型 (COM) 元素交互,使得脚本在浏览器之间不兼容。它最初对将 JavaScript 标准化为 ECMAScript 的努力的支持减弱了一段时间,最终浏览器大战开始了21。对于开发人员来说,这是一个令人沮丧的时期,最终导致美国政府对微软提起反竞争诉讼。

随着 Netscape 的衰落和 Internet Explorer 开始主导浏览器领域,即使 JavaScript 经历了标准化,跨平台创新也暂时消失了。Java 小程序在某些圈子中被广泛使用,但它们运行在沙盒环境中,因此将它们用作驱动动态网页活动的基础是有技巧的。你当然可以使用 Sun 的图形和用户界面 API 来做富有成效和有趣的事情,但它们在与 HTML 文档对象模型 (DOM) 22 不同的内存空间中运行。它们不兼容并且具有不同的编程和事件模型。沙盒元素和 Web 元素之间的用户界面看起来不一样。总的来说,这是一个完全不合适的开发体验。

其他非可移植技术,如 ActiveX,在 Microsoft Web 开发领域变得流行起来。Macromedia 的 Flash 变成了 Adobe 的 Flash,并在大约十年的时间里短暂而活跃地流行起来。然而,所有这些次要选项都是有问题的。内存空间相互隔离,安全模型并不像人们想象的那么强大。这些引擎是新的并且在不断开发中,因此错误很常见。ActiveX 提供代码签名保护,但没有沙箱保护,因此如果可以伪造证书,则可能发生可怕的攻击。

Firefox 从 Mozilla 中脱颖而出,成为 Netscape 的继任者。谷歌的 Chrome 浏览器终于成为 Internet Explorer 的合适替代品。每个阵营都有自己的追随者,但人们越来越有兴趣解决他们之间的不相容问题。在浏览器领域引入选择迫使每个供应商更加努力地工作,做得更好,以超越对方,以此作为实现技术主导地位和吸引市场份额的手段。

结果,JavaScript 引擎的速度显着提高。虽然 HTML 4 仍然“古怪”并且难以跨不同的浏览器和平台使用,但开始有可能隔离差异。这些发展与在基于标准的环境结构中工作的愿望相结合,鼓励 Jesse James Garrett 考虑一种不同的 Web 开发方法。他创造了 Ajax 一词,它代表一组组合的标准:异步 JavaScript 和 XML。这个想法是让数据从后端系统流向前端应用程序,前端应用程序将动态响应新的输入。通过在操作 DOM 的级别上工作,而不是拥有一个单独的沙盒用户界面空间,浏览器可以成为基于 Web 的客户端/服务器体系结构中的通用应用程序消费者。

在此期间,漫长的 HTML 5 标准化进程也开始了,试图提高浏览器的一致性,引入新的输入元素和元数据模型。在其他功能中,Ajax 是一个非常重要的元素,它提供硬件加速的 2D 图形和视频元素。Ajax 样式的融合、ECMAScript 作为一种语言的出现和成熟、更容易的跨浏览器支持以及功能日益丰富的基于 Web 的环境都导致了创新的爆炸式增长。我们已经看到无数基于 JavaScript 的应用程序框架来来去去,但就可能性而言,它正在稳步向前发展。随着开发人员的努力工作,浏览器供应商改进了他们的引擎,使他们能够进一步推动事情的发展。这是一个良性循环,它带来了关于安全、可移植、零安装软件系统潜力的新愿景。

随着其他障碍和限制被移除,这种奇怪的小语言在其核心成为前进道路上越来越大的惯性。该引擎正在成为世界一流的开发环境,具有更好的调试和分析工具。新的编程范例,例如基于承诺的风格,允许更好的模块化和异步友好的应用程序代码,在 JavaScript 臭名昭著的单线程环境中取得强大的结果23。但该语言本身并不像 C 或 C++ 等其他语言那样优化。从语言性能的角度来看,只要有一些简单的约束,它是可以实现的。

随着 WebGL24 和 WebRTC25 等技术的开发和采用,Web 平台标准不断进步。不幸的是,JavaScript 的性能限制使其不适合在浏览器中扩展涉及低级 Web、多线程代码、图形和流视频编解码器的功能。

该平台的开发需要 W3C 组织成员的努力,以确定在推出各种浏览器实现之前设计和构建的重要内容。随着人们对将 Web 用作重量级交互式应用程序平台的兴趣日益增长,这个过程被认为越来越站不住脚——一切都必须用 JavaScript 编写(或重写),或浏览器实现者必须标准化行为和接口,这意味着新的发展需要几年的时间。

出于这些原因,Google 开始考虑另一种安全、快速和可移植的客户端 Web 开发方法。

始考虑采用另一种方式,以实现安全、快速和可移植的客户端 Web 开发。

原生客户端(NaCI)

2011 年,Google 发布了一个名为 Native Client (NaCl) 的新开源项目。这个想法是在浏览器中提供接近本机的速度代码执行,同时出于安全原因在有限权限的沙箱中运行。你可以把它想象成有点像 ActiveX,背后有一个真正的安全模型。该技术非常符合谷歌的一些大目标,例如支持 ChromeOS 以及将任务从桌面应用程序转移到 Web 应用程序。它最初并不是要为每个人扩展开放网络的功能。

这些用例主要用于支持基于浏览器的计算密集型软件的交付,例如:

  • 游戏
  • 音视频编辑系统
  • 科学计算和 CAD 系统
  • 模拟

最初的重点是 C 和 C++ 作为源语言,但因为它基于 LLVM 编译器工具链26。可以支持其他可以生成 LLVM Intermediate Representation (IR) 27 的语言。正如你将看到的,这将是我们向 WebAssembly 过渡过程中反复出现的主题。

这里有两种形式的可分发代码。第一个是同名的 NaCl,它生产针对特定硬件架构(例如 ARM 或 x86-64)的“nexe”模块,并且只能通过 Google Play 商店分发。另一种是称为 PNaCl28 的可移植式形式。它将以 LLVM 的 Bitcode 格式表示,使其与目标无关。这些模块称为“pexe”模块,需要在客户端的主机环境中转换为本机架构。

该技术是成功的,因为浏览器和本机执行之间的速度差异很小。通过使用软件故障隔离 (SFI) 技术,可以从 Web 下载高性能、安全的代码并在浏览器中运行它。一些流行的游戏如 Quake 和 Doom 被编译成这种格式以显示最终可能的结果。问题是需要为每个目标平台生成和维护 NaCl 二进制文件,并且只能在 Chrome 上运行。它们还在进程外空间中运行,因此它们无法直接与其他 Web API 或 JavaScript 代码交互。

虽然在有限权限的沙箱中运行是可以实现的,但它确实需要对二进制文件进行静态验证,以确保它们不会尝试直接调用操作系统服务。生成的代码必须遵循特定的地址边界对齐模式,以确保它不会违反分配的内存空间。

如上所述,PNaCl 模块更便于携带。LLVM 基础设施可以生成 NaCl 本机代码或可移植的 Bitcode,无需修改,也无需修改原始源代码。这是一个很好的结果,但代码可移植性和应用程序可移植性之间存在差异。应用程序需要它们所依赖的 API 可用才能工作。Google 提供了一个名为 Pepper API29 的应用程序二进制接口 (ABI)。对于低级服务,如 3D 图形库、音频播放、文件访问(通过 IndexedDB 或 LocalStorage 模拟)等。虽然由于 LLVM,PNaCl 模块可以在不同平台的 Chrome 浏览器中运行,但它们只能在提供合适的浏览器中运行 Pepper API 的实现。虽然 Mozilla 最初表示有兴趣这样做,但他们最终决定尝试一种不同的方法,这就是后来为人所知的 asm.js。NaCl 被誉为推动行业朝这个方向发展,但它最终变得过于繁琐且对 Chrome 来说过于具体,无法推动开放网络向前发展。Mozilla 在这方面的尝试相对成功,即使它没有提供与本地客户端方法相同级别的性能。

asm.js

至少 asm.js 项目的部分动机是为 Web 带来竞争。这很快扩展到希望允许任意应用程序安全地传递到浏览器的沙箱中,而无需修改现有代码。

正如我们之前所讨论的,浏览器生态系统已经发展到能够以基于标准的跨平台方式交付 2D 和 3D 图形、音频处理、硬件加速视频等。这个想法是,在此环境中运行将允许应用程序使用任何定义为可从 JavaScript 调用的功能。JavaScript 引擎高效,具有强大的沙箱环境,并且经过了重大的安全审计,因此没有人觉得要从头开始。真正的问题还是没有办法提前选择,所以运行时性能还可以进一步提升。

由于其动态特性和缺乏适当的整数支持,在将代码加载到浏览器之前,有几个性能障碍无法进行有意义的管理。完成此操作后,即时 (JIT) 编译器可以得到很好的加速,但仍然存在固有问题,例如缓慢的边界检查数组引用。虽然无法提前优化整个 JavaScript,但它的一个子集可以。

这里的细节与我们的历史叙述关系不大,但最终结果是 asm.js 也使用了基于 LLVM 的 clang30 前端解析器,使用 Emscripten 工具链31 编译的 C 和 C++ 代码是 ahead-of-time 优化,所以生成的指令可以通过现有的优化流水线非常快。LLVM 是一个干净的模块化架构,因此它的某些部分可以被替换,包括生成后端机器代码。本质上,Emscripten 团队可以重用前两个阶段(解析和优化),然后将这个 JavaScript 子集作为自定义后端发出。因为输出是“纯 JavaScript”,所以它比 NaCl/PNaCl 方法更可移植。不幸的是,代价是性能。与直接使用 JavaScript 相比,这是一个巨大的改进,但它的性能不如 Google 的方法。不过,这足以让开发人员感到惊讶。然而,除了适度的性能改进之外,你可以将现有的 C 和 C++ 应用程序部署到具有合理性能且几乎不需要代码修改的浏览器这一事实本身就很有吸引力。有一些非常有说服力的演示涉及 Unity 引擎32。让我们看一个简单的例子。“Hello, world!”似乎是一个好的开始。

#include <stdio.h>
int main () {
  printf ("Hello, world!\n");
	return 0;
}

请注意,此版本的经典程序没有任何异常。如果你将它存储在一个名为 hello.c 的文件中,Emscripten 工具链将允许你发出一个名为 a.out.js 的文件,它可以直接在 Node.js33 中运行或通过一些脚手架在浏览器中运行。

brian@tweezer ~/s/w/ch01> emcc hello.c
brian@tweezer ~/s/w/ch01> node a.out.js
Hello, world!

很酷,不是吗?

只是有一个问题:

brian@tweezer ~/s/w/ch01> ls -lah a.out.js
-rw-r--r-- 1 brian staff 119K Aug 17 19:08 a.out.js

快速浏览一下本地可执行文件。119KB(译者注:使用 emcc 3.1.31 版本编译后的 a.out.js 文件大小为 62KB,比坐着写作本书时大小小了近一半),这是一个非常大的"Hello, world!“程序。

brian@tweezer ~/s/w/ch01> clang hello.c 
brian@tweezer ~/s/w/ch01> ls -lah a.out
-rwxr-xr-x 1 brian staff 48K Aug 17 19:11 a.out

为什么我们所谓的优化的 JavaScript 程序比本地版本(译者注:使用 clang 14.0.0 编译后的 a.out 大小为 32KB)大了近三倍?这不仅仅作为文本文件,JavaScript 的体积更大。再看一下这个程序。

#include <stdio.h> ①
int main () {
  printf ("Hello, world!\n"); 
  return 0;
}
  1. 头部确定了标准 IO 相关函数定义的来源。
  2. printf () 函数的引用将由一个在运行时加载的动态库来满足。

如果我们使用 nm 查看编译后的可执行文件中定义的符号,我们会发现二进制文件中 34 不包含 printf () 函数的定义。它被标记为 “U”,表示 “未定义”。

brian@tweezer ~/s/w/ch01> nm -a a.out
0000000100002008 d dyld_private
0000000100000000 T mh_execute_header
0000000100000f50 T _main
                 U _printf
                 U dyld_stub_binder

当 Clang 生成可执行文件时,它会将占位符指向它希望由操作系统提供的函数。对于浏览器,没有可用的标准库,至少在动态加载的意义上没有,因此必须提供库函数和任何需要的东西。此外,此版本无法直接与浏览器的控制台通信,因此需要 hook 以调用浏览器的 console.log() 等函数。为了让它在浏览器中工作,该功能必须与应用程序一起交付,这就是它最终变得如此庞大的原因。

这有效地突出了可移植代码和可移植应用程序之间的区别,这是本书的另一主题。现在,我们可以惊叹它是如何工作的,但这本书不叫《asm.js 权威指南》是有原因的。asm.js 是一个强大的铺垫,它证明了可以从各种可优化的语言中生成具有合理性能的 JavaScript 代码沙箱。JavaScript 本身也可以进一步优化,这是超集无法实现的。该子集是使用基于 LLVM 的工具链和自定义后端生成的,比以前更省力。

asm.js 代表了不支持 WebAssembly 标准的浏览器可选项,但现在是引出本书主题的时候了。

WebAssembly 的兴起

我们通过使用 NaCl 找到了沙箱和性能解决方案。PNaCl 提供平台可移植性,但缺乏浏览器可移植性。asm.js 解决了浏览器的可移植性和沙盒问题,但性能并不理想。此外,由于我们只能处理 JavaScript,我们无法使用新功能扩展平台,例如更高效的 64 位整数。因为它由国际标准组织管理,所以没有办法快速解决。

除此之外,JavaScript 在浏览器中加载和验证方面存在问题。浏览器必须等待下载完成所有引用的文件后才能开始验证和优化,而后续的优化需要我们等到应用程序开始运行。鉴于我们之前讨论过开发人员使用大量水平依赖项填充他们的应用程序,JavaScript 在 Web 传输和加载时间方面的性能是另一个需要克服的瓶颈。

正是在这种背景下,2015 年,除 JavaScript 创造者 Brendan Eich 之外的其他人宣布 WebAssembly35 的工作已经开始。他强调了几个具体原因,称其为“低级安全代码的二进制语法,最初与 asm.js 共同表达,但从长远来看能够脱离 JS 的语义,以便最好地服务作为多种源代码级编程语言的通用对象级格式。”

他进一步说:“例如可能的长期分歧:零成本异常、动态链接、调用。是的,我们的目标是为 Web 开发一种多语言编程语言目标文件格式。”

至于为什么对这些方感兴趣,他给出了以下推理:“asm.js 很好,但一旦引擎对其进行优化,解析器就会变得很热——尤其是在移动设备上。传输压缩是必要的,可以节省带宽,但解压之前解析它会很痛苦。”

最后,也许公告中最令人惊讶的部分是谁将参与其中:

W3C 社区组 WebAssembly CG 对所有人开放。从 GitHub 日志可以看出,WebAssembly 目前正在由谷歌、微软、Mozilla 和其他几家公司完成。抱歉,最初这项工作是通过私人 GitHub 账号完成的,但这是临时措施,帮助一些大公司达成共识并接受长期合作,必须玩好这个游戏才能成功。

短时间内,Apple、Adobe、AutoCAD、Unity、Figma 等公司都支持了这项工作。几十年前的愿景,涉及无休止的冲突和自身利益问题,正在转变为统一的倡议,最终将为我们带来安全、快速、可移植且与 Web 兼容的操作环境。

在构建这个平台的过程中,潜在的问题不断增加。目前还不完全清楚解决方案。并不是所有的语言都支持原生线程,也不是所有的语言都使用异常。例如,C/C++ 和 Rust,它们的运行时不需要垃圾收集。问题往往出在细节上,但合作的意愿是有的。正如他们所说,有志者事竟成。

在接下来的一年左右,CG 成为 W3C 工作组 (WG),负责定义事实上的标准。他们做出了一系列决定来定义 WebAssembly 平台的最小可行产品(MVP),所有主要浏览器供应商都将支持该产品。此外,Node.js 社区很兴奋,因为这可以为管理需要用低级语言编写的 Node 应用程序部分的本机库提供解决方案,这是很麻烦的事情。Node.js 应用程序可以拥有一个 WebAssembly 库,而不是依赖于 Windows、Linux 和 macOS 库,该库可以加载到 V8 环境中并动态转换为本机汇编代码。突然间,WebAssembly 似乎准备超越在浏览器中部署代码的目标,但我们不要太过分了。本书接下来的部分将告诉你这部分故事。

注释


  1. Andreas Haas 等人,“Bringing the Web Up to Speed with WebAssembly”,在 2017 年 6 月举行的第 38 届 ACM SIG- PLAN 编程语言设计与实现会议上发表。https://dl.acm.org/doi/10.1145/3062341.3062363 ↩︎

  2. 多年来,OpenGL 一直是可移植 3D 图形应用程序的定义标准。如今,它正被 Vulkan 和 Metal 等更现代的 API 所取代,但你可以在 OpenGL 网站上 了解更多关于这些标准的信息。 ↩︎

  3. 可移植操作系统接口(POSIX) 是一个 IEEE 标准的集合,用于定义共同的应用功能,以便在多个操作系统上运行。 ↩︎

  4. Win32 是一个更大的 API 集合的一部分,它为开发者提供了对 Windows 操作系统中可用的通用功能的访问。 ↩︎

  5. 这种技术允许决策者建立最低标准来决定他们的需求何时被满足。满足的目标不是要找到一个 完美 的解决方案,而是要找到一个在当前情况下可以接受的解决方案。 ↩︎

  6. 维基百科上的 关于 npm 的页面强调了几个案例,在这些案例中,破碎的依赖关系产生了巨大的影响。 ↩︎

  7. 任何人都可以看出它是 J.F. BastienBastien 说的,但连他自己都不确定。 ↩︎

  8. Assembly 语言 是一种低级别的编程语言,通常与特定机器的处理器结构和指令集有关。 ↩︎

  9. 欧洲核子研究中心(CERN)这个名字来自于法国的欧洲核子研究委员会(Conseil Européen pour la Recherche Nucléaire)。其许多令人振奋的项目详见于其 主页。在 ↩︎

  10. 他自己的时间! ↩︎

  11. Archie 是一个早期的搜索引擎,帮助人们在 FTP 服务器上寻找文件。 ↩︎

  12. Gopher 是我们现在所依赖的基于 HTTP 的 Web 的一个令人兴奋的前奏。 ↩︎

  13. 广域信息服务器 (WAIS) 是另一个在分布式系统中搜索和请求文本信息的早期系统。 ↩︎

  14. SGML 是一个定义结构化、声明性文档的 ISO 标准,是 HTML、DocBook 和 LinuxDoc 的基础。 ↩︎

  15. 当时我买了一个 Netscape 1.0 Silicon Graphics IRIX 的许可证。由于…… 历史原因,我仍然保留着这张 CD,在某个地方吃灰。 ↩︎ ↩︎

  16. Scheme 是 Lisp 的一个相当轻量级的版本。 ↩︎

  17. 在这里 有一个对 JavaScript 的早期历史很好的总结。 ↩︎

  18. Self 是一种面向对象的编程语言,影响了 JavaScript 的基于原型的继承。 ↩︎

  19. Autometric 有一个疯狂的背景,涉及派拉蒙电影公司、Trinitron 电子管,以及帮助美国国家航空航天局(NASA) ,决定在哪里登陆月球!此后,它被波音公司收购。 ↩︎

  20. 谷歌地球 现在在浏览器中运行。 ↩︎

  21. 虽然现在的浏览器供应商在标准方面的合作更加紧密,但有一段时间他们的竞争非常激烈。这个时期的讨论见 维基百科。 ↩︎

  22. DOM 是由浏览器呈现的网页或应用程序的树状结构。它通常以 HTML 声明性的文本形式从服务器发送到客户端,但 JavaScript 能够在浏览器中对其进行操作。 ↩︎

  23. Promises(或称 future) 允许开发者在提供具有并发功能的应用程序的同时建立相对简单的编程模型。 ↩︎

  24. WebGL 将一个类似的 3D 图形模型从 OpenGL 世界带到了 Web。 ↩︎

  25. WebRTC 提供了建立对摄像机和麦克风的许可访问以及加密的点对点连接的机制。 ↩︎

  26. LLVM 并不代表什么,但它是一个非常有影响力的工具链,你应该多了解一下。我们将在本书中经常提到它。 ↩︎

  27. 通常情况下,软件被编译成二进制的可执行形式。IR 允许它以解析的、结构化的形式存在,以达到优化的目的,还有其他原因。 ↩︎

  28. 发音为“pinnacle”。 ↩︎

  29. 因为 NaCI,明白吗? ↩︎

  30. ClangClang 是一个用于 C、C++ 和 Objective-C 的 LLVM 编译器工具套件。 ↩︎

  31. 在本书中,我们将了解更多关于 Emscripten 的信息,但如果你很好奇。 ↩︎

  32. 在浏览器中获得零安装的游戏体验是推动这种创新的主要原因。你可以 在浏览器中 看到一个 Unity 引擎使用 WebGL 的例子。 ↩︎

  33. Node.js 是一个非常流行的服务器端 JavaScript 环境,我们将在第 8 章中进一步讨论。 ↩︎

  34. nm 是一条 Unix 命令,用于显示一个可执行文件的符号表。 ↩︎

  35. Brendan Eich,"从 ASM.JS 到 WebAssembly",2015 年 6 月 17 日。 ↩︎