四家浏览器团队的共同计划

2015年6月17日,Mozilla工程师卢克·瓦格纳公开介绍WebAssembly计划:Mozilla正与Chromium、Edge和WebKit的工程师合作,为网页制定可移植、文件紧凑且加载高效的编译目标。当时已有万维网联盟的社区组、初步原型和高层设计共识,尚没有正式规范草案。2015年因此是项目公开形成的阶段。

这项合作聚集了不同浏览器引擎的实践。2017年设计论文的作者包括谷歌的安德烈亚斯·哈斯、安德烈亚斯·罗斯伯格、本·蒂策,Mozilla的瓦格纳、丹·戈曼、阿隆·扎凯,以及微软的迈克尔·霍尔曼和苹果的JF Bastien等。他们需要使同一种程序格式在彼此独立的实现中保持一致行为。

参考:[1] [2]

给已有程序一个网页编译目标

游戏、三维图形和音视频处理把大量计算带入浏览器。开发者已经能用Emscripten把C或C++程序转换成JavaScript,其中asm.js采用便于优化的受限写法。但这种路径仍受JavaScript语义约束,庞大的文本代码也增加传输、解析和启动负担。

WebAssembly把重点放在低层计算上:开发者可保留原有语言和代码库,由工具链生成浏览器能够验证和编译的二进制模块。它不指定某一种处理器的机器码,而以共同支持的整数、浮点运算和内存操作为基础。2015年的初期设想还包括借助asm.js实现兼容层,让尚未原生支持新格式的浏览器有过渡路径。

参考:[1] [2]

一个加法函数如何进入模块

以接收两个32位整数并返回两者之和的小函数为例,模块先声明参数和结果的类型,再保存函数体,并把函数以某个名称导出。其核心操作可以依次读入两个参数,再执行i32.add:两个值进入操作数栈,加法指令取出它们,把结果放回栈上。传入2和3,就得到5。

实际使用中,宿主先加载模块、验证指令和类型,再创建可调用的实例。网页侧的JavaScript可以调用导出的函数,并把结果用于界面。抽象的栈并不要求处理器真的反复压栈和出栈;引擎可以分析数据流,把这些操作编译成寄存器运算。这样,紧凑的程序表示与高效机器指令之间有了明确的转换途径。

参考:[2] [4]

明确边界的执行模型

模块中的函数、局部变量和操作数都有类型约束,分支则围绕块、循环和条件结构组织,不能随意跳进一条指令中间。这使实现能够在执行前快速检查程序的结构与类型。设计团队还用形式化规则描述验证和执行,并以OCaml编写参考解释器,供浏览器实现和规范相互核验。

数据放在线性内存中,越界访问会触发陷阱并中止相应执行。模块不能直接读写引擎的数据结构,也没有天然访问文件或网络的权限,外部能力须由宿主通过导入函数提供。这种隔离保护的是运行环境的边界;由不安全语言编译而来的程序,仍可能破坏自身线性内存里的数据布局。

参考:[2] [4]

从原型走向默认启用

各引擎在共同格式下采用了不同策略。2017年论文记载,V8、SpiderMonkey和JavaScriptCore利用现有优化编译器,在模块实例化前编译代码;Chakra则先把用到的函数转换成内部字节码,再优化热点函数。参考解释器和测试集帮助这些实现发现差异,早期部署经验也反过来影响设计。

2017年2月28日,瓦格纳、蒂策、Filip Pizlo和Abhijith Chatra联合公告,四家浏览器代表已就初始最小可用版本的API与二进制格式达成共识,浏览器预览阶段结束,厂商可以开始默认启用支持。公告同时要求后续特性保持向后兼容,并把成立工作组、形成正式规范列为下一步。

参考:[2] [3]

初始版本与正式标准

2019年12月5日,《WebAssembly核心规范》1.0版成为万维网联盟推荐标准。规范集中定义指令、二进制编码、验证规则、执行语义和文本表示;它与JavaScript接口及网页API文档分层配合。核心设计不依赖网页独有设施,因此也为浏览器以外的运行环境留下了空间。

公开发起、浏览器初始版本稳定和正式标准发布,分别对应2015、2017与2019年的不同进展。初始版本优先服务C、C++等低层代码,线程、向量指令和更丰富的语言运行时支持当时仍属于后续工作。这个分阶段路径让可用的共同基础先进入实现,再以兼容方式扩充能力。

参考:[2] [3] [4]