1、WebAssembly工作原理
分点介绍
官方解读
它可以从各类现有的其他高级语言写的业务库编译而来,比如下文提到的bullet库,就是一种C++语言编写的刚体动力学与
碰撞检测计算的库。根据调研,还有Haskell、Go、C#的语言的一些WebAssembly编译工具或者已经编译成的WebAssembly代码库,
OK,既然是经过编译而得来,可以将WebAssembly理解为是该库的低级语言代码版本,是一种类汇编语言。
另类理解
可以把它理解成一个ES6语法写的js模块,既可以有导入又有导出,也可以没有导入只有导出。
两类文件
WebAssembly文件格式与源码阅读->.wasm文件和.wast文件
WebAssembly代码存储在.wasm文件内,这类文件是要浏览器直接执行的。
因为.wasm文件内是二进制文件,难以阅读,为了方便开发者查看,官方给出了对.wasm文件的阅读方法,
通过把.wasm文件通过工具转为.wast的文本格式,开发者可以在一定程度上理解这个.wast文件。
.wast文件是通过S-表达式(一种类似lisp语言的代码书写风格)来写成的,
至于怎么读懂S-表达式,请去看官方介绍。
.wast文件和.wasm文件的关系,他们之间的相互转化,可以通过工具wabt(https://github.com/WebAssembl...)
实现。
工作流程
某高级语言写的某功能库-->emscripten编译-->.wasm文件-->结合WebAssembly JS API-->浏览器中运行
完成一部分 用js写,而后依靠浏览器解释执行,会比较消耗性能 的工作,比如视频解码,OpenGL,OpenCV等。
简单来说,加载运行wasm代码的过程如下图所示。
<div align="center">
<img src="https://github.com/cunzaizhuy...;>
</div>
详细的过程以及每个过程调用的API如下图。
<div align="center">
<img src="https://github.com/cunzaizhuy...;>
</div>
2、WebAssembly工具集
emscripten:是基于LLVM的一系列编译工具的集合,包括LLVM,clang等。下载比较费时,且易出错。
该工具集的一大作用是将c/c++编写的库编译成wasm格式的代码。
使用方式是通过命令行进行命令操作。
目前来说,WebAssembly程序的工作方式是和js程序相结合,互相调用,所以将合适的其他语言的库编译移植到web的过程,算是开发
中的相对独立的一块工作,正好emscripten工具也是命令行方式来工作。当然如果移植库需要开发者自己开发,就不算
独立,不过这脱离写前端的范畴。
真正开发时,更多的是直接拿已编译好的现成的移植代码加载到js代码中,来开发。
开发带有WebAssembly的程序需要开发者具备使用移植代码库的API使用能力或者说阅读其他语言代码的能力。
Binaryen:一套更为全面的工具链,是用C++编写成用于WebAssembly的编译器和工具链基础结构库。
WebAssembly是二进制格式(Binary Format)并且和Emscripten集成,因此该工具以Binary和Emscript-en的末
尾合并命名为Binaryen。它旨在使编译WebAssembly容易、快速、有效。
WABT工具包:支持将二进制WebAssembly格式转换为可读的文本格式。其中wasm2wast命令行工具
可以将WebAssembly二进制文件转换为可读的S表达式文本文件。而wast2wasm命令行工具则执行完全相反的过程。
3、WebAssembly API一览
一级API | 二级API | 描述 |
---|---|---|
table() | length、set()、get()、grow() | 方法 |
memory() | buffer、grow() | |
instantiate() | ||
instance | 属性 | |
module | 对象 | |
compile() | ||
validate() | bool | |
CompileError() | ||
LinkError() | ||
RuntimeError() |
WebAssembly.Mudule和WebAssembly.compile()
都是用来把一个wasm的arraybuffer对象编译成一个模块,前者是同步的,后者是异步的,后者使用更多。
前者使用方式:new WebAssembly.Mudule(buffer);后者使用方式:WebAssembly.compile(buffer);
WebAssembly.Mudule本身也是抽象意义上的模块对象。这两种方式调用以后,返回值都是一个模块对象,该对象
有导入对象、导出对象和自定义片段(custom section)。
WebAssembly.Instance和WebAssembly.instantiate()
都是用来做实例化,前者是同步的,后者是异步的,后者使用更多。
前者使用方式:new WebAssembly.Instance();后者使用方式:WebAssembly.instantiate();
前者有两个重载,一个是传入buffer和imports对象,这种调用一次性完成了编译和实例化两个步骤,
第二个重载是传模块对象和imports对象,这种调用只完成实例化步骤。
因此,实际上WebAssembly.instantiate()和WebAssembly.Instance的第二张重载调用功能上更接近。
4、一个简单Demo
一段c语言代码
int add (int x, int y) {
return x + y;
}
int square (int x) {
return x * x;
}
通过emcc工具编译为.wasm文件,
编译命令:
emcc input.c -s WASM=1 -s SIDE_MODULE=1 -o out.js
上述命令运行后,我们可以得到独立的Wasm文件。如果想看懂这个wasm文件,可以将其装换为wast文本格式,
使用上面介绍的工具WABT工具包(https://github.com/WebAssembl...
使用这个工具需要安装
cmake自己build一下,生成相应的可执行程序,然后用命令行wasm2wast test.wasm -o test.wast就可以查看
test.wast了。下面是上面c代码生成的wasm的wast对应文件。
S-表达式形式的wast文件
(module
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (param i32) (result i32)))
(type (;2;) (func))
(import "env" "memoryBase" (global (;0;) i32))
(import "env" "memory" (memory (;0;) 256))
(import "env" "table" (table (;0;) 0 anyfunc))
(import "env" "tableBase" (global (;1;) i32))
(func (;0;) (type 0) (param i32 i32) (result i32)
get_local 1
get_local 0
i32.add)
(func (;1;) (type 1) (param i32) (result i32)
get_local 0
get_local 0
i32.mul)
(func (;2;) (type 2)
nop)
(func (;3;) (type 2)
block ;; label = @1
get_global 0
set_global 2
get_global 2
i32.const 5242880
i32.add
set_global 3
call 2
end)
(global (;2;) (mut i32) (i32.const 0))
(global (;3;) (mut i32) (i32.const 0))
(export "_add" (func 0))
(export "__post_instantiate" (func 3))
(export "_square" (func 1))
(export "runPostSets" (func 2)))
得到wasm文件后,就可以使用js加载该模块,实例化该模块,运行该模块中的函数。
<script>
function loadWebAssembly (path, imports = {}) {
return fetch(path)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
imports.env = imports.env || {}
// 开辟内存空间
imports.env.memoryBase = imports.env.memoryBase || 0
if (!imports.env.memory) {
imports.env.memory = new WebAssembly.Memory({ initial: 256 })
}
// 创建变量映射表
imports.env.tableBase = imports.env.tableBase || 0
if (!imports.env.table) {
// 在 MVP 版本中 element 只能是 "anyfunc"
imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
// 创建 WebAssembly 实例
return new WebAssembly.Instance(module, imports)
})
}
loadWebAssembly('./math.wasm',imports)
.then(instance => {
//const { add, square } = instance.exports;
const add = instance.exports._add;
const square = instance.exports._square;
// ...
console.log(add(5,5));
console.log(square(add(5,5)));
});
</script>
如上,通过js调用这两个c语言方法,浏览器运行,控制台打印出正确结果。
5、基于WebAssembly模块库ammo.js的Demo
Demo介绍
基于three.js构建了三维场景,场景中有一个图片纹理拼成的ground地面,和两个THREE.Mesh()方法创建的
球体,这两个球体在地面上一左一右有固定的位置。
然后使用ammo构建了一个刚体动力学环境,这是一个有重力、考虑物体惯性等的物理环境,在这个环境中创建了
一个球体(界面中不可见),给该球体设置了一些刚体动力学的参数,如平移、旋转等,设置完这些参数再使用相反的
API获取这些参数,然后把这些参数赋给three.js创建的第二个球体(图1中右边那个),一秒后重新渲染threejs场景,该球体
则获得了一个平移的参数,移动到相应的(本例中是更靠右)的位置。
图1 使用ammo库前
图2 调用ammo相关代码后
Demo源代码地址
https://github.com/cunzaizhuy...
如需测试使用,请注意替换掉以下两行
<script src="../../builds/ammo.js"></script>
<script src="../js/three/three.min.js"></script>
本Demo参考链接
(1)Bullet类库API http://bulletphysics.org/Bull...
(2)Ammo库地址 https://github.com/kripken/am...
6、WebAssembly资源推荐
(1)英文官网 http://webassembly.org/
(2)中文官网 http://webassembly.org.cn/
(3)MDN网址 https://developer.mozilla.org...
(4)资料齐全 https://github.com/mbasso/awe...
(5)一篇讲解详细的博客 https://segmentfault.com/a/11...
(6)一篇讲解详细的博客 https://segmentfault.com/a/11...
(7)有编译工具链简单介绍 http://geek.csdn.net/news/det...
(0)本篇博客中的一些资源 https://github.com/cunzaizhuy...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。