https://github.com/WebAssembly
https://www.zhihu.com/question/34186498?sort=created
新时代
WebAssembly(简称Wasm)是一种新的适合于编译到Web的,可移植的,大小和加载时间高效的格式。这是一个新的与平台无关的二进制代码格式,目标是解决JavaScript性能问题。这个新的二进制格式远小于JavaScript,可由浏览器的JavaScript引擎直接加载和执行,这样可节省从JavaScript到字节码,从字节码到执行前的机器码所花费的即时编译JIT(Just-In-Time)时间。 作为一种低级语言,它定义了一个抽象语法树(Abstract Syntax Tree,AST),开发人员可以以文本格式进行调试。
WebAssembly描述了一个内存安全的沙箱执行环境,可以在现有的JavaScript虚拟机中实现。 当嵌入到Web中时,WebAssembly将强制执行浏览器的同源和权限安全策略。因此,和经常出现安全漏洞的Flash插件相比,WebAssembly是一个更加安全的解决方案。
WebAssembly可由C/C++等语言编译而来。此外,WebAssembly由Google、Mozilla、微软以及苹果公司牵头的W3C社区组共同努力,基本覆盖主流的浏览器厂商,因此其可移植性相较Silverlight等有极大提升,平台兼容问题将不复出现。
在Web平台的很多项目中,对于原生新功能的支持需要Web浏览器或Runtime提供复杂的标准化的API来实现,但是JavaScript API往往较慢。使用WebAssembly,这些标准API可以更简单,并且操作在更低的水平。例如,对于一个面部识别的Web项目,对于访问数据流我们可以由简单的JavaScript API实现,而把面部识别原生SDK做的事情交由WebAssembly实现。
需要了解的是,WebAssembly不是将C/C++等其他语言编译到JavaScript,更不是一种新的编程语言。
探究
asm.js
上文的c语言求和代码经由编译器生成asm.js后如代码3所示。
代码3
上述代码转换为WebAssembly的文本格式稍显复杂,为了理解方便,我们从精简的asm.js开始(见代码4)。
代码4
wast文本文件
将asm.js代码转换为WebAssembly的文本格式 add.wast(转换工具见本文工具链章节,如代码5所示)。
代码4
WebAssembly中代码的可装载和可执行单元被称为一个模块(module)。在运行时,一个模块可以被一组import值实例化,多个模块实例能够访问相同的共享状态。目前文本格式中的module主要用S表达式来表示。虽然S表达格式不是正式的文本格式,但它易于表示AST。WebAssembly也被设计为与ES6的modules集成。
一个单一的逻辑函数定义包含两个部分:功能部分声明在模块中每个内部函数定义的签名,代码段部分包含由功能部分声明的每个函数的函数体。WebAssembly是带有返回值的静态类型,并且所有参数都含有类型。上面的add.wast可以解读为:
- 声明了一个名为$add的函数;
- 包含两个参数a和b,两者都是32位整型;
- 结果是一个32位整型;
- 函数体是一个32位的加法:
- 上面是局部变量$a得到的值;
- 下面是局部变量$b得到的值;
- 由于没有明确的返回节点,因此return是该加法函数的最后加载指令。
二进制Wasm文件
如图1所示,由C语言求和代码经过编译生成二进制文件,通读文件可以找到相应的头部、类型、导入、函数以及代码段等。通过JavaScript API载入Wasm二进制文件后,最终转换到机器码执行。
图1 经过编译的二进制文件
工具链
开发人员现在可以使用相应的工具链从C / C ++源文件编译WebAssembly模块。WebAssembly由许多工具支持,以帮助开发人员构建和处理源文件和生成的二进制内容。
Emscripten
Emscripten是其中无法回避的工具之一,如图2所示。在图2中,Emscripten SDK管理器(emsdk)用于管理多个SDK和工具,并且指定当前正被使用到编译代码的特定SDK和工具集。
图2 Emscripten工具链流程图及生成JavaScript(asm.js)流程
Emscripten的主要工具是Emscripten编译器前端(emcc),它是例如GCC的标准编译器的简易替代实现。
Emcc使用Clang将C/C++文件转换为LLVM(源自于底层虚拟机Low Level Virtual Machine)字节码,使用Fastcomp(Emscripten的编译器核心,一个LLVM后端)把字节码编译成JavaScript。输出的JavaScript可以由Node.js执行,或者嵌入HTML在浏览器中运行。这带来的直接结果就是,C和C++程序经过编译后可在JavaScript上运行,无需任何插件。
WABT和Binaryen
除此之外,对于想要使用由其他工具(如Emscripten)生成的WebAssembly二进制文件感兴趣的开发者,目前http://webassembly.org/官方额外提供了另外两组不同的工具:
- WABT ——WebAssembly二进制工具包;
- Binaryen——编译器和工具链。
WABT工具包支持将二进制WebAssembly格式转换为可读的文本格式。其中wasm2wast命令行工具可以将WebAssembly二进制文件转换为可读的S表达式文本文件。而wast2wasm命令行工具则执行完全相反的过程。
Binaryen则是一套更为全面的工具链,是用C++编写成用于WebAssembly的编译器和工具链基础结构库(如图3所示)。WebAssembly是二进制格式(Binary Format)并且和Emscripten集成,因此该工具以Binary和Emscript-en的末尾合并命名为Binaryen。它旨在使编译WebAssembly容易、快速、有效。它包含且不仅仅包含下面的几个工具。
图3 Binaryen生成WebAssembly流程
- wasm-as:将WebAssembly由文本格式(当前为S表达式格式)编译成二进制格式;
- wasm-dis:将二进制格式的WebAssembly反编译成文本格式;
- asm2wasm:将asm.js编译到WebAssembly文本格式,使用Emscripten的asm优化器;
- s2wasm:在LLVM中开发,由新WebAssembly后端产生的.s格式的编译器;
- wasm.js:包含编译为JavaScript的Binaryen组件,包括解释器、asm2wasm、S表达式解析器等。
Binaryen目前提供了两个生成WebAssembly的流程,由于emscripten的asm.js生成已经非常稳定,并且asm2wasm是一个相当简单的过程,所以这种将C/C ++编译为WebAssembly的方法已经可用(如图4所示)。
图4 Emscripten+Binaryen生成WebAssembly的完整流程
由此可见,Emscripten以及Binaryen提供了完整的C/C++到WebAssembly的解决方案。而Binaryen则帮助提升了WebAssembly的工具链生态。
提示
由于WebAssembly正处于活跃开发阶段,各项编译步骤和编译工具会有大幅变更和改进,相信最终的编译工具和步骤会趋于便捷,开发者需要留意官方网站的最新动态。
实战
Linux和mac OS平台编译原生代码到WebAssembly可由如下步骤实现。
编译环境准备
操作系统必须有可以工作的编译器工具链,因此需要安装GCC、cmake环境,此外Python、node.js及Java环境也是需要的(其中Java为可选,如图5所示)。
图5 编译环境安装
如果是以其他方式安装了Node.js,可能需要更新~/.emscripten文件的NODE_JS属性。
安装正确的emscripten分支
要编译原生代码到WebAssembly,我们需要emscripten的incoming分支。由于emscripten不仅仅是用于WebAssembly的编译工具链,选择正确的分支尤为重要(如图6所示)。
图6 安装emscripten的incoming分支
其中URLTO具体的URL是https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz。
处理安装异常
可运行emcc -v命令进行验证安装。如果遇到如图7所示的错误,表明带有JavaScript后端的LLVM编译器并未被生成。
图7 emcc -v命令报错
图8 emcc -v命令报错解决方案
通过图8步骤,可以解决该问题,并且在~/.emscripten 文件中修改如下配置:
开始编译程序
现在一个完整的工具链已经具备,我们可以使用它来编译简单的程序到WebAssembly。但是,还有一些其他注意事项:
- 必须通过参数-s Wasm=1到emcc(否则默认emcc将编译出asm.js);
- 除了Wasm二进制文件和JavaScript wrapper外,如果还希望emscripten生成一个可直接运行的程序的HTML页面,则必须指定一个扩展名为.html的输出文件。
在编译之前,首先准备一个最基本的add.c程序,见代码6。
代码6
按代码7所示的命令编辑好add.c程序并编译:
运行WebAssembly应用
以Chrome浏览器为例,如果直接在浏览器内本地打开HTML文件,会有图9所示的错误:
图9 XMLHttpRequest本地访问的跨域请求错误
由于XMLHttpRequest跨域请求不支持file://协议,必须经由HTTP实际输出,可以由python的SimplHTTPServer改进,见代码8:
代码8
在浏览器中输入http://127.0.0.1:8080并打开add.html,就能直接看到转换成WebAssembly的应用程序输出结果。
创建独立WebAssembly
默认情况下,emcc会创建JavaScript文件和WebAssembly的组合,其中JS加载包含编译代码的WebAssembly。对于C/C++开发人员,他们可能更倾向于创建独立的WebAssembly,用于JavaScript开发人员调用,见代码9。
代码9
上述命令运行后,我们可以得到独立的Wasm文件。需要说明的是,该参数仍然在开发中,可能随时发生规范和实现变更。
JavaScript API调用
从C/C++程序编译获得一个.wasm模块之后,JavaScript开发人员可以通过如下方式进行载入.wasm文件并执行。WebAssembly社区组也有计划通过Streams使用streaming以及异步编译,见代码10。
代码10
最后一行调用导出的WebAssembly函数,它反过来调用我们导入的JS函数,最终执行add(201700, 2),并且在控制台获得期望的结果输出(如图10所示)。
图10 WebAssembly求和函数在控制台的输出
性能
那么,WebAssembly的真实性能如何呢?首先我们用一直被用来作为CPU基准测试的斐波那契 (Fibonacci)数列来进行对比,这里使用的是性能较差的递归算法,在Node.js v7.2.1环境下,能够看到WebAssembly性能优势越发明显(如图11所示)。
图11 CPU基准测试反应WebAssembly的真实性能
再看看最基本的1000毫秒时间内,求和计算的运算量统计,在同一台计算机的Firefox 50.1.0版本的运算结果如图12所示。
图12 1000毫秒内求和计算的运算量统计
尽管重复测试时结果不尽相同,重启浏览器并多次测试取平均值后依然可以看到WebAssembly的运算量比JavaScript快了近一个量级。
Demo
图13展示了Angry Bots Demo,它是由WebAssembly项目发布的一个Demo,由Unity游戏移植而来。
图13 Angry Bots Demo / Google Chrome 55.0.2883.87
通过如下方式可以体验WebAssembly在浏览器中的强大性能。即便Google Chrome较新的稳定版也已支持WebAssembly,还是推荐使用canary版及Firefox的nightly版进行测试。
- 下载浏览器:
1-1. Google Chrome;
1-2. Mozilla Firefox;
1-3. Opera;
1-4. Vivaldi。 - 打开 WebAssembly支持 :
2-1. Google Chrome:chrome://flags/#enable-webassembly;
2-2. Mozilla Firefox:about:config→接受→搜索javascript.options.wasm→设置为true;
2-3. Opera:opera://flags/#enable-webassembly;
2-4. Vivaldi:vivaldi://flags#enable-webassembly。
访问:http://webassembly.org/demo/。
使用W、A、S、D等键实现移动操作,点击鼠标进行射击。该WebAssembly游戏在浏览器中运行相当流畅,媲美原生性能。
除了最新的浏览器开始对WebAssembly逐步支持外,Intel开源技术中心开发的Crosswalk项目(https://crosswalk-project.org/)早在2016年11月初的Crosswalk 22稳定版(Windows及Android 平台)即已加入对WebAssembly实验性的支持,开发者可以使用该版本体验Angry Bots Demo。
开发者
WebAssembly对于Web有显著的性能提升,对于开发者尤其是前端或者JavaScript开发人员而言,并不意味着WebAssembly将会取代JavaScript(如图14所示)。
图14 WebAssembly与JavaScript引擎的关系
WebAssembly被设计为对JavaScript的补充,而不是替代,是为了提供一种方法来获得应用程序的关键部分接近原生性能。随着时间的推移,虽然WebAssembly将允许多种语言(不仅仅是C/C++)被编译到Web,但是JavaScript的发展势头不会因此被削弱,并且仍然将保持Web的单一动态语言。此外,由于WebAssembly构建在JavaScript引擎的基础架构上,JavaScript和WebAssembly将在许多场景中配合使用。
那么WebAssembly是不是仅仅面向C/C++开发者呢?答案依旧是否定的。WebAssembly最初实现的重点是C/C++,由Mozilla主导开发的注重高效、安全和并行的Rust也能在2016年末被成功编译到WebAssembly了,未来还会继续增加其他语言的支持,见代码11。
代码11
在未来,通过ES6模块接口与JavaScript集成,Web开发人员并不需要编写C++,而是可以直接利用其他人编写的库,重用模块化C++库可以像使用JavaScript中的modules一样简单。
进展
依据开发路线图,2016年10月31日,WebAssembly到达浏览器预览的里程碑。Google Chrome V8引擎及Mozilla Firefox SpiderMonkey引擎都已经在trunk上支持WebAssembly浏览器预览。2016年12月下旬,Microsoft Edge浏览器使用的JavaScript引擎ChakraCore v1.4.0启用了WebAssembly浏览器预览支持。而Webkit JavaScriptCore引擎对于该支持也在积极进行中。
目前,WebAssembly社区组已经有初始(MVP)二进制格式发布候选和JavaScript API在多个浏览器中实现。作为浏览器预览期间的一部分,WebAssembly社区组(WebAssembly Community Group)现在正在征求更广泛的社区反馈。社区组的初步目标是浏览器预览在2017年第一季度结束,但在浏览器预览期间的重大发现可能会延长该周期。当浏览器预览结束时,社区组将产生WebAssembly的草案规范,并且浏览器厂商可以开始默认提供符合规范的实现。预计在2017年上半年,四大主流浏览器对原生的WebAssembly支持将到达稳定版。
具体到Google V8引擎的最新进展,asm.js代码将不再通过Turbofan JavaScript编译器而是编译到WebAssembly后,在WebAssembly的原生执行环境中执行最终的机器码。这种改变带来的好处有,为asm.js将预先编译(AOT,Ahead Of Time Compilation)带到了Chrome,且完全向后兼容。新的WebAssembly编译渠道重用了一些Turbofan JavaScript编译器后端部分,因此能够在少了很多编译和优化消耗的前提下,产生类似的代码。在Google Chrome中,WebAssembly将很快在Canary版中默认启用,开发团队也期望能够发布到2017年第一季度末的稳定版中。
社区
包含所有主要浏览器厂商代表的W3C Web——Assembly社区组于2015年4月底成立。该小组的任务是,在编译到适用于Web的新的、便携的、大小和加载时间高效的格式上,促进早期的跨浏览器协作。该社区组也正在将WebAssembly设计为W3C开放标准。目前,除了文中所述主流浏览器厂商Mozilla、Google、微软、及苹果公司之外,Opera CTO及Intel的8位该领域专家均参与了该社区组。当然,并不是只有社区组成员才能参与标准的制定,任何人都可以在https://github.com/WebAssembly做出贡献。
展望
由于主要的浏览器厂商对WebAssembly支持表现积极,并且都在实现WebAssembly的各项功能,因此在Web中高性能需求的应用例如在线游戏、音乐、视频流、AR/VR、平台模拟、虚拟机、远程桌面、压缩及加密等都能够获得接近于原生的性能。相信WebAssembly将会开创Web的新时代。