45

WebAssembly的未来:潜在新特性一览

 5 years ago
source link: http://www.infoq.com/cn/news/2018/08/webassembly-future-features?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

感谢覃云对本文的审校。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK