WebAssembly的未来:潜在新特性一览
WebAssembly开发团队的描述:
WebAssembly(或wasm)是一种适用于Web的可移植编译格式,提供更小的文件尺寸和更快的加载速度。
实际上,WebAssembly旨在成为高级语言的编译目标。目前可以使用C、C++、Rust、Go、Java、C#编译器(还有更多)来创建wasm模块。
WebAssembly模块以二进制的格式发送到浏览器,并在专有虚拟机上执行。这个虚拟机与JavaScript虚拟机共享资源,如内存和线程。WebAssembly模块总是与JavaScript代码一起使用,在必要的时候可以执行一些有用的操作。
目前正在进行中的很多提案旨在让WebAssembly成为更好的编译目标,并减少JavaScript“胶水”代码。
WebAssembly提案
WebAssembly提案的流程是分阶段的,从阶段1(不成熟)到阶段5(完成标准化)。以下是目前所有提案的清单(前4个阶段),每个提案都将在后面详细介绍。
第1阶段(功能提案)包括以文本格式自定义注解、主机绑定、尾调用、批量内存操作、ECMA模块集成、垃圾回收、异常处理、固定宽度SIMD、线程。第2阶段(规范提议)包括BigInt转换。第2阶段(实现)包括引用类型和返回多个值。第4阶段(标准化)包括导出和导入可变全局变量和有符号扩展操作。
引用类型
目前的WebAssembly类型系统还很小,只有四种数字类型。目前,如果要使用复杂类型(例如字符串、对象、数组、结构体),需要将它们序列化为线性内存,并提供它们所在位置的引用。这个提案对类型系统进行了扩展,添加了一个新的anyref类型,模块可以持有对主机环境对象的引用,也就是说,你可以将JS对象传给wasm模块。
通过anyref引用的对象对于wasm模块来说意义不是很大,关键在于模块可以持有在JS堆上分配的对象的引用,这意味着在wasm执行期间需要对这些引用进行跟踪。该提案被视为垃圾回收提案的垫脚石。
返回多个值
WebAssembly的虚拟机是基于栈的,操作对象在函数被调用之前是放在栈上的,函数会使用这些对象并替换为返回值。在目前的规范中,函数只能返回单个值。新的提案允许指令、函数和块返回多个值(例如,整数除法可以返回除数和余数)。
下面是一个简单的“swap”函数,它可以返回多个值(result i32 i32)。
(func $swap (param i32 i32) (result i32 i32)
(get_local 1) (get_local 0)
)
导出和导入可变全局变量
目前,wasm支持全局变量和局部变量。全局变量可以被导入和导出,并与JS宿主共享,不过必须将它们定义为不可变的。
新的提案将WebAssembly.Global构造函数添加到JS API中,允许导出和导入可变的全局变量。该特性对于跨多个wasm模块共享状态来说非常有用。
有符号扩展操作
符号扩展是一种在保留符号的同时增加二进制数位数的方法。这个提案添加了少量符号扩展指令,例如i32.extend8_s——将有符号的8位整数扩展为32位整数。
FireFox已经实现了这个特性,并计划在9月发布。
BigInt转换
JavaScript只有一个数字类型,即IEEE 754浮点数——这种表示法存在一定的局限性。现在有一个新兴的标准,即增加对“大整数”的支持,目前处于TC39流程的第3阶段。最终确定后,将为开发人员提供任意精度的整数。
WebAssembly的四种数字类型之一是64位整数。新提案将提供完全的互操作,让JS 的BigInt和wasm的64位整数实现双向转换。
顺便提一下,现在已经有一个用于在JavaScript中执行64位运算的库,叫作long.js。最近,他们移除了基于JavaScript的实现,改用更简单的WebAssembly!
线程
JavaScript已经通过WebWorker实现了多线程,但是在worker之间只能使用postMessage进行较慢的消息传递。共享内存提案已经在TC39中定稿,并在2017年2月成为ECMAScript的一部分。共享数组缓冲区和原子性让线程之间共享数据变得更加容易。
WebAssembly的这个提案也是允许共享访问线性内存,并提供原子操作。但值得注意的是,提案并没有引入创建线程的机制(引起了很多争议),而是由宿主提供此功能,也就是我们熟悉的WebWorker。
我相信会有一些人对这个提案感到失望。不过,WebAssembly之所以能够快速发展到MVP版本,跟团队专注于简单性不无关系。利用成熟的宿主功能(WebWorker)是非常有意义的。
将来可能会添加原生线程,但会作为单独的提案,不过这可能还需要几年的时间!
固定宽度SIMD
单指令多数据流(SIMD)是一组支持矢量风格处理的指令,例如,将一个向量添加到另一个向量种。所有现代CPU都支持这些指令。有一个SIMD.js TC39提案,增加了很多128位类型,例如float32 x 4,以及相应的操作(如add、multiply),但最近删除了这部分内容,因为WebAssembly中已经添加了类似的功能。这样做是有道理的,因为这些是低级指令,而WebAssembly恰好是低级运行时。
WebAssembly的SIMD提案非常简单,为wasm添加一个新的128位类型,可以表示四个数字的向量,以及用于创建和操作这些新类型的简单指令集。这将为某些算法类别的性能带来改进。
异常处理
程序会在出现异常时中断控制流,异常会顺着调用栈向上传播,直到遇到合适的“catch”块。异常处理是大多数现代编程语言的共同特征,尽管Swift在早期版本中并不支持它们。
WebAssembly MVP在当前控制流指令中没有任何类似于异常处理的东西。因此不得不使用Emscripten这样的工具来模拟这个功能,但这是以牺牲性能为代价——目前,默认情况下捕获C++异常是关闭的,其他语言也面临类似的问题。
异常处理提案概述了构建一个与宿主环境“良好配合”的概念所涉及的大量复杂性。有趣的是,这是第二次尝试创建这个提案,可见他们目前面临的挑战有多严峻!
该提案不仅要向WebAssembly添加异常处理,更是要引入一种更通用的事件概念,看起来很像中断。当事件发生时,执行被暂停,在events处安插一个恰当的处理程序。
除了事件,可能还会添加标准的try/catch指令:
try block_type
instruction*
catch
instruction*
end
这将减少WebAssembly编译器的一些复杂性。
垃圾回收
大多数现代编程语言(不包括系统级语言)使用垃圾回收器进行内存管理。简而言之,垃圾回收器(GC)让开发人员无需过多考虑内存管理,他们可以创建对象、传递对象、在函数/变量之间共享对象,并且在不再使用这些对象时依靠GC来清理它们。
WebAssembly没有垃圾回收器。事实上,它没有任何可用于内存管理的工具,它只是为你提供了一块“内存”。不使用GC的编程语言仍然需要某种机制来管理内存分配,例如Rust使用了一个小型的WebAssembly优化分配器。
目前,需要垃圾回收器的编程语言没有其他选择,只能将GC编译为wasm,并将其作为二进制文件的一部分,例如AssemblyScript就在二进制文件中包含了一个“makeshift GC”。但这样会增加二进制文件的大小,同时GC算法的效率也会受到影响。缺少GC是Scala和Elm等语言还不支持编译成WebAssembly的原因。
这个提案将GC功能带到WebAssembly中。有趣的是,它不会有自己的GC,而是与宿主环境的GC集成。还有其它各种其他提案(宿主绑定、引用类型)旨在改进与宿主的互操作性,从而更容易共享状态和调用API。使用单个GC来管理内存会让这些变得更容易。
这个提案是一个重大变更,wasm的类型系统因此添加了很多新的东西,包括简单的元组、结构体和数组。还有一些讨论是关于添加字符串类型的。
在WebAssembly中使用GC是可选的,这样Rust/C++就可以使用内存分配器和线性内存。新类型将在新的WebAssembly堆上进行分配,尽管提案中未明确说明。我猜宿主堆也可以使用,但可能会带来很大的开销。
这个提案增加了很多新的指令,这是结构体的一个例子:
;; structures with fields
(type $point (struct (field $x f64) (field $y f64) (field $z f64)))
;; allocated with new
(call $g (new $point (i32.const 1) (i32.const 2) (i32.const 3)))
;; field accessors - type checked when validated
(load_field $point $x (get_local $p)
ECMA模块集成
ECMAScript模块(ESM)是一个相对较新的规范,所有主流浏览器现在都已支持。
目前,wasm模块是通过HTTP进行加载的,然后使用JS API进行实例化:
const req = fetch("./myModule.wasm");
const instance = await WebAssembly.instantiateStreaming(req)
instance.exports.foo()
这个提案引入了一种机制,可以通过ESM导入的方式来加载wasm模块:
import {foo} from "./myModule.wasm";
foo()
这让实例化wasm模块变得更简单,而更大的好处是它们成为JS模块图的一部分,也可以进行摇树优化(tree shaking)、捆绑、代码分割和ESM支持的其他优化。
批量内存操作
该提案增加了复制/填充线性内存区域的新操作。它们将为某些场景带来更好的性能。
尾调用
递归函数调用可能会导致很深的调用栈,会带来各种问题(性能、内存消耗、堆栈溢出的可能性)。通过尾调用优化,递归函数调用将被替换为迭代。这种技术对于函数式语言来说非常重要。为此,该提案引入了新的return_call指令。
宿主绑定
基于多种因素(wasm类型系统太过简单、缺少引用类型等等),WebAssembly与JavaScript宿主之间的当前接口非常有限。如果你想要编写一个操作DOM或使用其他浏览器API的wasm模块,必须编写大量“胶水”代码。
这个提案允许WebAssembly模块创建、传递、调用和操作JavaScript/DOM对象。它添加了一部分与宿主绑定相关的内容,其中包括用于描述绑定机制或接口的注解。
Rust已经有了一个工具,叫作wasm-bindgen,它的作用与该提案很相似。使用wasm-bindgen,你就可以轻松地跨越wasm和JS传递字符串等对象。该工具将绑定元数据添加到wasm模块中,并生成所需的JS胶水代码。
以文本格式自定义注解
wasm二进制格式支持将元数据添加到模块中。这个提案为文本格式也添加了类似的功能,这对宿主绑定来说非常有用。举个例子:
(module
(func (export "f") (param i32 (@js unsigned)) ...)
)
@js unsigned注解添加了用于生成宿主绑定的其他元数据。
结论
希望这些能让你对WebAssembly未来的发展方向有所了解。这些提案中的一些小改进可能很快就能完成,但大的改进可能需要几年时间才能完全实现。
查看英文原文:https://blog.scottlogic.com/2018/07/20/wasm-future.html
本文文字及图片出自 InfoQ
你也许感兴趣的:
- Java 极客眼中的 WebAssembly
- WebAssembly 真能取代 Kubernetes?
- WebAssembly成为浏览器第二编程语言?
- WebAssembly(wasm)到底是什么
- WebAssembly 到底处于编译阶段的哪个环节?
- 恕我直言,90% 的应用场景都不需要用 WebAssembly!
- 专访:在 WebAssembly 的浏览器上运行《毁灭战士 3》
- WebAssembly 和 Go语言:对未来的观望
- WebAssembly 能干什么?8个WebAssembly 应用案例
- WebAssembly得到了所有浏览器的支持
你对本文的反应是: