WebAssembly介紹

一、WebAssembly工做原理

分點介紹

官方解讀

它能夠從各種現有的其餘高級語言寫的業務庫編譯而來,好比下文提到的bullet庫,就是一種C++語言編寫的剛體動力學與
碰撞檢測計算的庫。根據調研,還有Haskell、Go、C#的語言的一些WebAssembly編譯工具或者已經編譯成的WebAssembly代碼庫,
OK,既然是通過編譯而得來,能夠將WebAssembly理解爲是該庫的低級語言代碼版本,是一種類彙編語言。html

另類理解

能夠把它理解成一個ES6語法寫的js模塊,既能夠有導入又有導出,也能夠沒有導入只有導出。前端

兩類文件

WebAssembly文件格式與源碼閱讀->.wasm文件和.wast文件c++

WebAssembly代碼存儲在.wasm文件內,這類文件是要瀏覽器直接執行的。
由於.wasm文件內是二進制文件,難以閱讀,爲了方便開發者查看,官方給出了對.wasm文件的閱讀方法,
經過把.wasm文件經過工具轉爲.wast的文本格式,開發者能夠在必定程度上理解這個.wast文件。
.wast文件是經過S-表達式(一種相似lisp語言的代碼書寫風格)來寫成的,
至於怎麼讀懂S-表達式,請去看官方介紹。
.wast文件和.wasm文件的關係,他們之間的相互轉化,能夠經過工具wabt(https://github.com/WebAssembl...
實現。git

工做流程

某高級語言寫的某功能庫-->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>github

二、WebAssembly工具集

emscripten:是基於LLVM的一系列編譯工具的集合,包括LLVM,clang等。下載比較費時,且易出錯。
該工具集的一大做用是將c/c++編寫的庫編譯成wasm格式的代碼。
使用方式是經過命令行進行命令操做。web

目前來講,WebAssembly程序的工做方式是和js程序相結合,互相調用,因此將合適的其餘語言的庫編譯移植到web的過程,算是開發
中的相對獨立的一塊工做,正好emscripten工具也是命令行方式來工做。固然若是移植庫須要開發者本身開發,就不算
獨立,不過這脫離寫前端的範疇。
真正開發時,更多的是直接拿已編譯好的現成的移植代碼加載到js代碼中,來開發。segmentfault

開發帶有WebAssembly的程序須要開發者具有使用移植代碼庫的API使用能力或者說閱讀其餘語言代碼的能力。瀏覽器

Binaryen:一套更爲全面的工具鏈,是用C++編寫成用於WebAssembly的編譯器和工具鏈基礎結構庫。
WebAssembly是二進制格式(Binary Format)而且和Emscripten集成,所以該工具以Binary和Emscript-en的末
尾合併命名爲Binaryen。它旨在使編譯WebAssembly容易、快速、有效。異步

WABT工具包:支持將二進制WebAssembly格式轉換爲可讀的文本格式。其中wasm2wast命令行工具
能夠將WebAssembly二進制文件轉換爲可讀的S表達式文本文件。而wast2wasm命令行工具則執行徹底相反的過程。函數

三、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的第二張重載調用功能上更接近。

四、一個簡單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語言方法,瀏覽器運行,控制檯打印出正確結果。

五、基於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...

六、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...

相關文章
相關標籤/搜索