13 min read

wasm 历史

WebAssembly技术源于浏览器,在Web前端领域,Js语言是编写运行在浏览器上的Web应用首选,然而,随着应用功能逐渐复杂化,对性能的要求越来越高,而js本身一些特性,已经很难满足日益增长的应用性能需求

为了满足日益增长的性能需求,针对js语言本身的缺陷,逐步形成了三个阶段性优化产物: asm.js,NaCI/PNaCI以及WebAssembly,这三个阶段的优化推动了WebAssembly技术的发展

asm.js 阶段

由于Js本身是一种动态、弱类型编译语言,因此,只有在程序运行时才能确认执行上下文中对象的具体数据类型;不仅如此,Js语言还允许同一个变量在不同时刻可以绑定不同类型的对象。Js的如类型导致虚拟机只能在执行时刻进行类型推断,同时,其动态性又进一步导致当前的执行结果无法复用,因为代码所执行的对象类型和逻辑都可能随时改变

为了解决Js语言自身带来的弊端,asm.js应运而生。asm.js设计的出发点直指Js语言的缺陷,asm.js可以在Js代码运行之前便确定程序中变量的具体类型;与此同时,它还进一步保证程序中的变量类型不会再运行时发生改变。基于asm.js对弱类型和动态性的约束,虚拟机再执行过程中可以利用确定性的类型进行编译优化,并且编译结果可以复用而不需要重复执行相同源代码的优化过程,从而提升web应用的运行效率。

asm.js始于2013年8月,它是Js的一个严格子集,是一种可用于编译器的低层级的、高效的目标语言。相较于Js,asm.js采用了一种名为Annotation(注解)的类型声明来对变量进行约束,其中annotation的形式采用| T表示; 如下述代码所示,赋值语句n = n|0通过对变量与0进行使用按位或操作的生命反射光和i,可以让虚拟机在解析asm.js代码时强制将该变量 n 视为一个32位的证书,并且该变量能够存储的数据类型在运行过程中无法被更改

function fast_lib_moudle(stdlib, foreign, heap) {
  "use asm";
  function fib(n)
  {
    n = n|0;
    if (n >>> 0 < 3) {
      return 1|0;
    }
    return (fib((n-1)|0) + fib((n-2)|0))|0;
  }
  return fib;
}

从整体来看,asm.js模块是一个标准的Js函数,在asm.js的模块语法规则中,不仅需要对函数所传入的参数进行类型声明,而且这些是必须且强制的;此外,asm.js 标准中只定义了对数值类型的 Annotation 声明方式,这使得 asm.js 的应用场景很大程度上集中在数值计算密集型 Web 应用的优化处理上;再加上各大浏览器厂商对 asm.js 标准的支持程度和实现方式也不尽相同

asm.js 技术本身存在的问题和局限性,大大阻碍了 asm.js 的发展,从而也推动了 Web 应用优化进入了 NaCl 和 PNaCl 阶段。

NaCl 和 PNaCl 阶段

NaCl(Google Native Client)是2011 Google Chrome 团队意在把基于原生C/C++语言编写的本地应用安全、高效的运行在Web浏览器的一项技术。该技术主要方式是将C/C++语言编写的本地应用编译为标准的NaCl模块,每个NaCl模块是一个以“.nexe”为后缀的ELF格式的二进制文件,该文件可以直接在Chrome中加载并运行。基于NaCl技术开发出来的应用可以以接近原生C/C++应用的效率在浏览器端稳定运行,但实际项目使用时。需要为每种不同的处理器架构分别单独编译处对应版本的NaCl二进制模块文件;这种方式极不方便也不符合开源软件的便携特性;不仅如此,NaCl模块这种直接存储对底层处理器架构机器码的放肆也使得模块本身失去了可移植性。

为了解决由于 NaCl 模块的平台依赖性导致的互联网上自由地分发的问题,又推出了新的 PNaCl (Chrome Portable Native Client) 技术。PNaCl 并不会直接将应用的 C/C++ 源代码编译成依赖特定处理器架构的底层机器码,PNaCl 首先会将应用的 C/C++ 源代码编译成一种基于 LLVM 生成的抽象中间二进制模块 ,以 ".pexe" 为后缀。这种模块不依赖具体的处理器架构,因此可以在互联网上被随意地分发。在浏览器中运行 PNaCl 应用时,浏览器会首先将 pexe 二进制模块加载到内存中,并根据当前处理器架构通过内置的 AOT 编译器将 pexe 二进制模块编译为特定处理器架构的机器码,随后被浏览器直接执行。

NaCl 和 PNaCl 技术没有被除 Chrome 以外的任何其他浏览器支持;此外,基于 C/C++ 语言也大大增加了 NaCl 和 PNaCl 应用的开发难度和开发成本,使得 NaCl 和 PNaCl 基本脱离了技术快速迭代的前端开发领域。

NaCl 和 PNaCl 技术本身存在的问题和局限性,阻碍了它成为浏览器的事实标准,从而也推动了 Web 应用优化进入到了当前的 WebAssembly 阶段。

WebAssembly 阶段

基于前两个阶段的探索,为了提升Web应用的性能,2015年Mozilla在asm.js的基础上发布了一种新型的二进制代码格式"WebAssembly"。这种二进制文件可以用类似JS模块加载的方式被浏览器快速、高效的执行;不仅如此,WebAssembly并不像 NaCl 那样需要区分浏览器所运行的处理器架构,这使得它可以自由地在互联网上分发,具有很好的浏览器兼容性和用户体验。鉴于WebAssembly 的有事,2017年,四大浏览器厂商在WebAssembly MVP标准的设计上达成共识,同年WebAssembly Working Group成立,标志着WebAssembly称为W3C标准技术体系的一部分

随着WebAssembly 在开发者社区中越来越流行,WebAssembly 的潜在加载从Web逐渐开始向其它领域,比如云原生、AI以及区块链等;

WebAssembly 目标及其优势

  • 性能 WebAssembly是静态强类型低级语言,通过利用常见的硬件能力,WebAssembly代码在不同的平台上能够以接近本地速度运行,此外WebAssembly 是一个轻量的二进制格式,提供了友好的高效冷启动,轻量部署能力
  • 跨平台可移植 WebAssembly 是一个可移植、体积小、语言和平台无关的二进制格式,开发者可以使用各种自身熟悉的语言开发,生成的 WebAssembly 作为平台无关的应用发布形式;发布的 WebAssembly 模块可以在众多平台上运行,例如浏览器、后端、终端设备、移动设备、IoT等都有广阔的应用场景
  • 沙箱环境 WebAssembly 运行在一个独立的沙箱中,一方面可以避免了数据的泄露和侧信道攻击,另一方面恶意代码只能影响自身沙箱环境而不会影响应用本身
  • 标准化 W3C WebAssembly Working Group 制定相关的标准,可以保证标准的通用性和各厂商的兼容性。
  • 网络安全 WebAssembly 的设计原则是与其他网络技术和谐共处并保持向后兼容,WebAssembly 被限制运行在一个安全的沙箱执行环境中,像其他网络代码一样,它遵循浏览器的同源策略和授权策略
  • 灵活 WebAssembly 制定了标准化的中间指令格式,开发者可以使用多种不同的开发语言,如 C/C++,Java/Kotlin, TypeScript, Rust 等,利用工具链可以转化为统一的 WebAssembly 的中间指令格式

WebAssembly 为应用开发者提供了不一样的技术架构选型的可能性。WebAssembly 作为一个可安全隔离,高效,体积小、跨平台,多语言支持的可移植二进制中间形式,为解决上述问题提供了可实施的路径,不同的开发者可以利用 WebAssembly 的特性来满足业务需求。 对于 JavaScript、Python 等脚本语言来说,为了追求更高的性能,可以将性能热点模块通过 WebAssembly 来实现,从而获取高性能执行的收益。对于 Rust 开发者来说,利用语言的特性可以获取高性能和高安全性,但为了让开发者获得更低的开发门槛,可以编译为 WebAssembly 模块提供给类似 JavaScript、Python 等脚本语言使用,降低开发者门槛;对于 C++ 开发者来说,可以获得高性能,但 C++ 不完备的安全性机制可能会使应用存在安全隐患,可以将其编译为 WebAssembly 在轻量级安全沙箱中运行,从而使得安全机制做到开箱即用(安全性保障需要安全领域的专业支持,门槛很高)

WASM 应用场景

WASM 已经能够在包括浏览器在内的很多不同的环境中运行。这些场景包括:云原生场景、移动设备、物联网、区块链等多种不同的场景。在这些场景中,大家使用 WASM 的主要目标包括,但是不限于如下几个方面:

  • 加速基于浏览器的应用运行速度,在一定程度上替代js
  • 作为应用沙盒环境(SandBox)嵌入到应用程序中,为宿主环境(Host)提供一定的安全保障
  • 提供一种插件系统的实现,为产品最终用户提供一定的扩展能力
  • 将不同的编程语言编译成wasm来提供他们之间的互操作的可能