WebAssembly核心編程[1]:wasm模塊實例化的N種方式

當我們在一個Web應用中使用WebAssembly,最終的目的要麼是執行wasm模塊的入口程序(通過start指令指定的函數),要麼是調用其導出的函數,這一切的前提需要創建一個通過WebAssembly.Instance對象表示的wasm模塊實例(源代碼)。

一、wasm模塊實例化總體流程
二、利用WebAssembly.Module創建實例
三、通過字節內容創建創建實例
四、利用XMLHttpRequest加載wasm模塊
五、極簡編程方式

一、wasm模塊實例化總體流程

雖然編程模式多種多樣,但是wasm模塊的實例化總體採用如下的流程:

  • 步驟一:下載wasm模塊文件;
  • 步驟二:解析文件並創建通過WebAssembly.Module類型表示的wasm模塊;
  • 步驟三:根據wasm模塊,結合提供的導入對象,創建通過WebAssembly.Instance類型表示的模塊實例。

二、利用WebAssembly.Module創建實例

我們照例通過一個簡單的實例來演示針對wasm模塊加載和模塊實例創建的各種編程模式。我們首先利用WebAssembly Text Format(WAT)形式定義如下一個wasm程序,定義的文件名爲app.wat。如代碼所示,我們定義了一個用於輸出指定浮點數(i64)絕對值的導出函數absolute。絕對值通過f64.abs指令計算,具體得輸出則通過導入的print函數完成。

(module
   (func $print (import "imports" "print") (param $op f64) (param $result f64))
   (func (export "absolute") (param $op f64)
      (local.get $op)
      (f64.abs (local.get $op))
      (call $print)
   )
)

我們通過指定wat2wasm (源代碼壓縮包種提供了對應的.exe)命令(wat2wasm app.wat –o app.wasm)編譯app.wat並生成app.wasm後,定義如下這個index.html頁面,作爲宿主程序的JavaScript腳本完全按照上面所示的步驟完成了針對wasm模塊實例的創建。

<html>
    <head></head>
    <body>
        <div id="container"></div>
        <script>
            var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`;
           fetch("app.wasm")
                .then((response) => response.arrayBuffer())
                .then(bytes => {
                    var module = new WebAssembly.Module(bytes);
                    var instance = new WebAssembly.Instance(module, {"imports":{"print": print}});
                    instance.exports.absolute(-3.14);
                })
        </script>
    </body>
</html>

具體來說,我們調用fetch函數將app.wasm文件下載下來後,我們將獲得的字節內容作爲參數調用構建函數創建了一個WebAssembly.Module對象。然後將這個Module對象和創建的導入對象({"imports":{"print": print}})作爲參數調用構造函數創建了一個WebAssembly.Instance對象,該對象正是我們需要的wasm模塊實例。我們從模塊實例中提取並執行導出的absolute函數。導入的print函數會將絕對值計算表達式以如下的形式輸出到頁面中。

image

除了調用構造函數以同步(阻塞)的方式根據WebAssembly.Module對象創建WebAssembly.Instance對象外,我們還可以調用WebAssembly.instantiate靜態方法以異步的方式“激活”wasm模塊實例,它返回一個Promise<WebAssembly.Instance>對象。

var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`;
fetch("app.wasm")
    .then((response) => response.arrayBuffer())
    .then(bytes => {
        var module = new WebAssembly.Module(bytes);
        return WebAssembly.instantiate(module, { "imports": { "print": print } });
    })
    .then(instance => instance.exports.absolute(-3.14));

三、通過字節內容創建創建實例

靜態方法WebAssembly.instantiate還提供了另一個重載,我們可以直接指定下載wasm模塊文件得到的字節內容作爲參數。這個重載返回一個Promise<WebAssembly.WebAssemblyInstantiatedSource>對象,WebAssemblyInstantiatedSource對象的instance屬性返回的正是我們需要的wasm模塊實例。

var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`;
fetch("app.wasm")
    .then((response) => response.arrayBuffer())
    .then(bytes => WebAssembly.instantiate(bytes, {"imports":{"print": print}}))
    .then(result =>result.instance.exports.absolute(-3.14));

四、利用XMLHttpRequest加載wasm模塊

fetch函數是我們推薦的用於下載wasm模塊文件的方式,不過我們一定義要使用傳統的XMLHttpRequest對象也未嘗不可。上面的三種激活wasm模塊實例的方式可以採用如下的形式來實現。

var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`;
const request = new XMLHttpRequest();
request.open("GET", "app.wasm");
request.responseType = "arraybuffer";
request.send();

request.onload = () => {
    var bytes = request.response;
    var module = new WebAssembly.Module(bytes);
    var instance = new WebAssembly.Instance(module, {"imports":{"print": print}});
    instance.exports.absolute(-3.14);
};

上面演示的利用創建的WebAssembly.Module對象和導入對象調用構造函數創建WebAssembly.Instance的同步形式。下面則是將二者作爲參數調用靜態方式WebAssembly.instantiate以異步方式激活wasm模塊實例的方式。

var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`;
const request = new XMLHttpRequest();
request.open("GET", "app.wasm");
request.responseType = "arraybuffer";
request.send();

request.onload = () => {
    var bytes = request.response;
    WebAssembly
        .instantiate(request.response, {"imports":{"print": print}})
        .then(result => result.instance.exports.absolute(-3.14));
};

下面演示WebAssembly.instantiate靜態方法的另一個重載。

var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`;
const request = new XMLHttpRequest();
request.open("GET", "app.wasm");
request.responseType = "arraybuffer";
request.send();

request.onload = () => {
    var bytes = request.response;
    WebAssembly
        .instantiate(request.response, {"imports":{"print": print}})
        .then(result => result.instance.exports.absolute(-3.14));
};

五、極簡編程方式

其實我們有“一步到位”的方式,那就是按照如下的形式執行靜態方法WebAssembly.instantiateStreaming。該方法的第一個參數用於提供下載.wasm模塊文件的PromiseLike<Response>對象,第二個參數則用於指定導入對象。該方法同樣返回一個Promise<WebAssembly.WebAssemblyInstantiatedSource>對象,WebAssemblyInstantiatedSource的instance屬性返回的正是我們所需的wasm模塊實例。

var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`;
WebAssembly
    .instantiateStreaming(fetch("app.wasm"), {"imports":{"print": print}})
    .then(result => result.instance.exports.absolute(-3.14))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章