WebAssembly 简介
9 min read

WebAssembly 简介

WebAssembly ,也可以称为wasm,本文统一用WebAssembly。

本文主要介绍一下WebAssembly,从以下几个方面:

  • WebAssembly的由来;
  • WebAssembly是什么?
  • WebAssembly目前的进展情况;
  • 简单的演示示例。

WebAssembly的由来

目前在浏览器中,大部分开发者所使用的编程语言是JavaScript,如果使用其它的语言可以吗?JavaScript是否可以先编译,浏览器直接运行编译后的结果呢?

Arstechnica上发布了一篇 Are video codecs written in JavaScript really the future? | Ars Technica ,其中Raniz的评论引来了广泛讨论:

我认为我们主要的精力应该是在浏览器上支持更过的语言,而不是任何事情都用JavaScript来实现。如果浏览器端存在一个标准的字节码,开发者可以选择更多的语言;源代码对浏览器和使用者是透明的。
二进制数据也可以节省宽带。

目前多种语言都对应的LLVM编译器,有人建议将LLVM位码用作中间“字节码”;
AlonZakai在 azakai’s blog: The Elusive Universal Web Bytecode中列举了使用字节码面临的困难 :

  • 位码方案与目标平台强绑定;
  • 字节码目前的不统一;
  • 字节码标准化,浏览器优化JavaScript有了限制等;

如此看来,字节码的成功的机会不大,但在其他语言带向Web开发语言方面,有两个重要的尝试。

Mozilla: C/C++ –> LLVM位码 –>Emscripten –> asm.js –> 浏览器
Google:C/C++ –> LLVM位码 –>PNaCl –> 浏览器

asm.js 尝试标准化能够在任何浏览器中运行的一个JavaScript子集,其设计便于JavaScript引擎优化。Emscripten( Home · kripken/emscripten Wiki · GitHub )是另一个项目,可以从LLVM位码生成asm.js。据Sakai介绍,在Firefox中通过asm.js运行的C++代码速度是原生代码的50%,他们预计随着时间的推移,性能会有所改进。

PNaCl 旨在增强Chrome中对原生应用的支持。NaCl是一个在浏览器端可运行编译后的C或C++代码的沙河,PnaCl在NaCl的基础上提出了这样的一种解决方案:为编译的NaCl模块提供一种独立于指令集体系结构的格式,无需重新编译即可支持多种目标平台。PNaCl仍然使用原来的NaCl沙盒机制,客户端从服务器请求位码,然后针对自己的架构将其转换为原生的可执行代码。

如果要在浏览器中使用字节码,只能寄希望于某种理想的字节码。AlonZakai总结了这样的字节码可能需要满足以下几个条件:

  • 支持所有语言;
  • 高速运行代码;
  • 便于编译器生成;
  • 格式紧凑,容易转换;
  • 标准化;
  • 平台独立;
  • 安全;

为此,Mozilla、谷歌、微软和苹果决定开发一种面向Web的二进制格式,该格式名为WebAssembly。

WebAssembly可以作为任何编程语言的编译目标,使应用程序可以运行在浏览器或其它代理中。

WebAssembly是什么?

WebAssembly实际上是一个经过压缩的AST编码,以二进制形式存在,可与当前的Web平台集成并在Web环境中,最终实现在各类平台上以接近原生的速度调用常见的硬件功能。

下面是一段WebAssembly的演示示例, S-表达式 - 维基百科,自由的百科全书 AST:

;; Iterative factorial named
(func $fac-iter (param $n i64) (result i64)
    (local $i i64)
        (local $res i64)
    (set_local $i (get_local $n))
    (set_local $res (i64.const 1))
        (label $done
        (loop
            (if
                (i64.eq (get_local $i) (i64.const 0))
                    (break $done)
                (block
                    (set_local $res (i64.mul (get_local $i) (get_local $res)))
                        (set_local $i (i64.sub (get_local $i) (i64.const 1)))
                    )
                )
            )
        )
        (return (get_local $res)
    )
)

使用二进制的原因是:可以提供更高的效率:它减少了下载文件大小,并加快了解码速度。

与WebAssembly相关的概念有:源代码、编译器、汇编、字节码、机器码。WebAssembly为Web打造一套专用的字节码,这项标准在未来应用场景可能是这样的:

  • 开发应用,但使用任何一门可被编译为WebAssembly的语言编写源代码;
  • 用编译器将源代码转换为WebAssembly字节码,也可按需转换为汇编代码;
  • 在浏览器中加载字节码并运行。

webassembly.png

关于WebAssembly,需要了解的是(具体内容可参考: 关于WebAssembly你需要了解的7件事 ):

  • WebAssembly不会替代JavaScript;
  • WebAssembly将超出JavaScript所需的功能进行扩展,例如多线程;
  • WebAssembly由参与组建asm.js和(P)NaCl的团队共同开发;
  • WebAssembly向后兼容,提供了Polypill;
  • WebAssembly比CPU汇编代码更简单易懂;
  • 支持在浏览器中开启Source-maps功能调试编译后代码;
  • 即刻运行,无须等待;

WebAssembly目前的进展情况

WebAssembly Community Group已经有了后续MVP版本的一个候选 — 浏览器预览版,部分浏览器已经实现了相应的API,在Chrome Canary(金丝雀)、Firefox中已经可以尝鲜了。目前CG正在从社区收集反馈。

里程碑事件

其它

各浏览器目前对该功能的支持还处于实验性阶段,距离真正使用还有较长的时间周期;

另一方面,这项新技术对实际能够产生什么样的效果,无法预知。这种方法能带来的好处是,我们从头开始设计,没有历史包袱,或许会得到一个更优雅的解决方案。这种想法很吸引人,而且一般说来优雅的方案会带来更好的结果,但以前也争论过,在这一特定情况下,优雅的方案如果没有明显的技术优势,那就是为了优雅而优雅了。

JavaScript已经有20多年的历史了,Brendan Eich当时绝没料到这种小语言在今天竟然有了如此重要的地位。目前JavaScript广泛应用于所有主流浏览器,而且随着Node.js的流行,它还进入了服务器端。这并不是因为JavaScript设计得好,而是因为我们很难把所有的主要参与者聚到一起做出一个更好的方案,软件上的各种东西也很难切换了。和HTTP和HTML一样,尽管存在各种缺点,尽管我们都知道如果能够达成一致,我们可以做得更好,JavaScript还是会走向繁荣。

JavaScript上存在着缺点,WebAssembly是未来的解决方案吗?

WebAssembly演示示例

一切都是演示性的,如果没有达到理想的效果,请保持冷静(PLEASE KEEP CALM)。

查看官方DEMO,请访问:Angry Bots Demo;官网的案例编译的是C代码,安装时请耐心。

本演示示例在Mac上进行,需要的环境有:

  • Git;
  • CMake,可使用brew安装;
  • Xcode;
  • Python 2.7+;

准备工作

正式开始之前,先写一段asm.js代码,例如下面这段:

// my-math-module.asm.js
function MyMathModule(global) {
    "use asm";
    var exp = global.Math.exp;
    function doubleExp(value) {
        value = +value;
        return +(+exp(+value) * 2.0);
    }
    return { doubleExp: doubleExp };
}

注:这不是一个特别有用的函数,但符合规范,如果你觉得这很烂,但基本上每一个字符都是必须的。

在上面代码中,一元运算符 + 的作用是类型注解,这样编译器会知道那些变量是 double 类型的,运行时就不必再次分辨它们是什么。

在浏览器中运行,结果如下图所示:

result.png

尝试 WebAssembly

使用GitHub - WebAssembly/binaryen: Compiler infrastructure and toolchain library for WebAssembly, in C++ 提供的工具,将asm.js 代码片段编译为wasm;

git clone https://github.com/WebAssembly/binaryen.git
cd binaryen
// binaryen 目录下会有一个 bin 文件夹
cmake . & make
// 使用asm2wasm生成wast文件
./binaryen/bin/asm2wasm my-math-module.asm.js -o my-math-module.wast

生成后的内容如下:

(module
  (type $FUNCSIG$dd (func (param f64) (result f64)))
  (import "global.Math" "exp" (func $exp (param f64) (result f64)))
  (import "env" "memory" (memory $0 256 256))
  (import "env" "table" (table 0 0 anyfunc))
  (import "env" "memoryBase" (global $memoryBase i32))
  (import "env" "tableBase" (global $tableBase i32))
  (export "doubleExp" (func $doubleExp))
  (func $doubleExp (param $value f64) (result f64)
    (return
      (f64.mul
        (call $exp
          (get_local $value)
        )
        (f64.const 2)
      )
    )
  )
)

利用wasm-as将wast格式的代码转换为 wasm 二进制码;

./binaryen/bin/wasm-as my-math-module.wast -o my-math-module.wasm

如下图所示:

0061 736d 0d00 0000 0186 8080 8000 0160
017c 017c 02d6 8080 8000 050b 676c 6f62
616c 2e4d 6174 6803 6578 7000 0003 656e
7606 6d65 6d6f 7279 0201 8002 8002 0365
6e76 0574 6162 6c65 0170 0100 0003 656e
760a 6d65 6d6f 7279 4261 7365 037f 0003
656e 7609 7461 626c 6542 6173 6503 7f00
0382 8080 8000 0100 078d 8080 8000 0109
646f 7562 6c65 4578 7000 0109 8180 8080
0000 0a97 8080 8000 0191 8080 8000 0020
0010 0044 0000 0000 0000 0040 a20f 0b

下图演示了如何最低限度地加载模块:

<script>
fetch("my-math-module.wasm")
.then(function(response) {
    return response.arrayBuffer();
})
.then(function(buffer) {
    var dependencies = {
        "global": {},
        "env": {}
    };
    dependencies["global.Math"] = window.Math;
    var moduleBufferView = new Uint8Array(buffer);
    var myMathModule = Wasm.instantiateModule(moduleBufferView, dependencies);
    console.log(myMathModule.exports.doubleExp);
    for(var i = 0; i < 5; i++) {
        console.log(myMathModule.exports.doubleExp(i));
    }
});
</script>

把代码放到 html 文件中,启动本地文件服务器,在浏览器中加载页面(注意:浏览器要开启WebAssembly功能),就可以在控制台中查看了!

参考链接