«

《WebAssembly标准入门》读书笔记之诞生背景

image.png

—— 一切可编译为WebAssembly的,终将被编译为WebAssembly。

[toc]

《WebAssembly标准入门》读书笔记系列


本文是《WebAssembly标准入门》第0章节主要内容,重点介绍 WebAssembly 的诞生背景。

WebAssembly是一种新兴的网页虚拟机标准,它的设计目标包括:高可移植性、高安全性、高效率(包括载入效率和运行效率)、尽可能小的程序体积。

JavaScript简史

只有了解了过去才能理解现在,只有理解了现在才可能掌握未来的发展趋势。JavaScript语言因为互联网而生,紧随着浏览器的出现而问世。JavaScript语言是Brendan Eich为网景(Netscape)公司的浏览器设计的脚本语言,据说前后只花了10天的时间就设计成型。为了借当时的明星语言Java的东风,这门新语言被命名为JavaScript。其实Java语言和JavaScript语言就像是雷锋和雷峰塔一样没有什么关系。

JavaScript语言从诞生开始就是严肃程序员鄙视的对象:语言设计垃圾、运行比蜗牛还慢、它只是给不懂编程的人用的玩具等。当然出现这些观点也有一定的客观因素:JavaScript运行确实够慢,语言也没有经过严谨的设计,甚至没有很多高级语言标配的块作用域特性。

但是到了2005年,Ajax(Asynchronous JavaScript and XML)方法横空出世,JavaScript终于开始火爆。据说是Jesse James Garrett发明了这个词汇。谷歌当时发布的Google Maps项目大量采用该方法标志着Ajax开始流行。Ajax几乎成了新一代网站的标准做法,并且促成了Web 2.0时代的来临。作为Ajax核心部分的JavaScript语言突然变得异常重要。

然后是2008年,谷歌公司为Chrome浏览器而开发的V8即时编译器引擎的诞生彻底改变了JavaScript低能儿的形象。V8引擎下的JavaScript语言突然成了地球上最快的脚本语言!在很多场景下的性能已经和C/C++程序在一个数量级(作为参考,JavaScript比Python要快10~100倍或更多)。

JavaScript终于手握Ajax和V8两大神器,此后真的是飞速发展。2009年,Ryan Dahl创建Node.js项目,JavaScript开始进军服务器领域。2013年,Facebook公司发布React项目,2015年发布React Native项目。目前JavaScript语言已经开始颠覆iOS和Android等手机应用的开发。

回顾整个互联网技术的发展历程,可以发现在Web发展历程中出现过各种各样的技术,例如,号称跨平台的Java Applet、仅支持IE浏览器的ActiveX控件、曾经差点称霸浏览器的Flash等。但是,在所有的脚本语言中只有JavaScript语言顽强地活了下来,而且有席卷整个软件领域的趋势。

JavaScript语言被历史选中并不完全是偶然的,偶然之中也有着必然的因素。它的优点同样不可替代。

因为Web是一个开放的生态,所以如果一个技术太严谨注定就不会流行,XHTML就是一个活生生的反面教材。超强容错是所有Web技术流行的一个必备条件。JavaScript刚好足够简单和稳定、有着超强的容错能力、语言本身也有着极强的表达力。同时,Ajax、WebSocket、WebGL、WebWorker等标准的诞生也为JavaScript提供了更广阔的应用领域。

asm.js的尝试

JavaScript是弱类型语言,由于其变量类型不固定,使用变量前需要先判断其类型,这样无疑增加了运算的复杂度,降低了执行效能。谋智公司的工程师创建了Emscripten项目,尝试通过LLVM工具链将C/C++语言编写的程序转译为JavaScript代码,在此过程中创建了JavaScript子集asm.js,asm.js仅包含可以预判变量类型的数值运算,有效地避免了JavaScript弱类型变量语法带来的执行效能低下的问题。根据测试,针对asm.js优化的引擎执行速度和C/C++原生应用在一个数量级。

下图给出了ams.js优化的处理流程,其中上一条分支是经过高度优化的执行分支。

image.png

因为增加了类型信息,所以asm.js代码采用定制优化的AOT(Ahead Of Time)编译器,生成机器指令执行。如果中途发现语法错误或违反类型标记的情况出现,则回退到传统的JavaScript引擎解析执行。

asm.js中只有有符号整数、无符号整数和浮点数这几种类型。下图展示了asm.js中几种数值类型之间的关系。

image.png

而字符串等其他类型则必须在TypedArray中提供,同时通过类似指针的技术访问TypedArray中的字符串数据。

asm.js的高明之处就是通过JavaScript已有的语法和语义为变量增加了类型信息:

var x = 9527;

var i = a | 0;   // int32  
var u = a >>> 0; // uint32  
var f = +a;      // float64  

以上代码中a|0表示一个整数,而a >>> 0表示一个无符号整数,+a表示一个浮点数。即使不支持asm.js的引擎也可以产生正确的结果。

然后通过ArrayBuffer来模拟真正的内存:

var buffer = new ArrayBuffer(1024*1024*8);  
var HEAP8 = new Int8Array(buffer);  

基于HEAP8模拟的内存,就可以采用类似C语言风格计算的字符串长度。而C语言的工作模型和冯·诺伊曼计算机体系结构是高度适配的,这也是C语言应用具有较高性能的原因。下面是asm.js实现的strlen函数:

function strlen(s) {  
    var p = s|0;
    while(HEAP8[p]|0 != 0) {
        p = (p+1)|0
    }
    return (p-s)|0;
}

所有的asm.js代码将被组织到一个模块中:

function MyasmModule(stdlib, foreign, heap) {  
    "use asm";

    var HEAP8 = new Int8Array(heap);

function strlen(s) {  
       // ...
    }

    return {
strlen: strlen,  
    };
}

asm.js模块通过stdlib提供了基本的标准库,通过foreign可以传入外部定义的函数,通过heap为模块配置堆内存。最后,模块可以将asm.js实现的函数或变量导出。模块开头通过"use asm"标注内部是asm.js规格的实现,即使是旧的引擎也可以正确运行。

asm.js优越的性能让浏览器能够运行很多C/C++开发的3D游戏。同时,Lua、SQLite等C/C++开发的软件被大量编译为纯JavaScript代码,极大地丰富了JavaScript社区的生态。asm.js诸多技术细节我们就不详细展开了,感兴趣的读者可以参考它的规范文档。

WebAssembly的救赎

在整个Web技术变革的过程之中,不断有技术人员尝试在浏览器中直接运行C/C++程序。自1995年起包括Netscape Plugin API(NPAPI)在内的许多知名项目相继开发。微软公司的IE浏览器甚至可以直接嵌入运行本地代码的ActiveX控件。同样,谷歌公司在2010年也开发了一项Native Clients技术(简称NaCL)。然而这些技术都太过复杂、容错性也不够强大,它们最终都未能成为行业标准。

除尝试直接运行本地代码这条路之外,也有技术人员开始另辟蹊径,将其他语言直接转译为JavaScript后运行,2006年,谷歌公司推出Google Web Toolkit(GWT)项目,提供将Java转译成JavaScript的功能,开创了将其他语言转为JavaScript的先河。之后的CoffeeScript、Dart、TypeScript等语言都是以输出JavaScript程序为最终目标。

在众多为JavaScript提速的技术中,Emscripten是与众不同的一个。它利用LLVM编译器前端编译C/C++代码,生成LLVM特有的跨平台中间语言代码,最终再将LLVM跨平台中间语言代码转译为JavaScript的asm.js子集。这带来的直接结果就是,C/C++程序经过编译后不仅可在旧的JavaScript引擎上正确运行,同时也可以被优化为机器码之后高速运行。

2015年6月谋智公司在asm.js的基础上发布了WebAssembly项目,随后谷歌、微软、苹果等各大主流的浏览器厂商均大力支持。WebAssembly不仅拥有比asm.js更高的执行效能,而且由于使用了二进制编码等一系列技术,WebAssembly编写的模块体积更小且解析速度更快。目前不仅C/C++语言编写的程序可以编译为WebAssembly模块,而且Go、Kotlin、Rust等新兴的编程语言都开始对WebAssembly提供支持。2018年7月,WebAssembly 1.0标准正式发布,这必将开辟Web开发的新纪元!

分享