WebAssembly 匯入的全域性字串常量

WebAssembly 匯入的全域性字串常量透過消除傳統字串匯入所需的大量樣板程式碼,使在 Wasm 模組中處理 JavaScript 字串更加容易。

本文將解釋匯入的全域性字串常量的工作原理。

傳統字串匯入的問題

讓我們從探索 WebAssembly 中字串匯入的傳統方式開始。在 Wasm 模組中,您可以使用以下程式碼片段從名為 "string_constants" 的名稱空間匯入一些字串:

wat
(global (import "string_constants" "string_constant_1") externref)
(global (import "string_constants" "string_constant_2") externref)

在您的 JavaScript 中,您將提供要在 importObject 中匯入的字串:

js
importObject = {
  // …
  string_constants: {
    string_constant_1: "hello ",
    string_constant_2: "world!",
    // …
  },
};

在編譯/例項化模組以使用其函式之前。

js
WebAssembly.instantiateStreaming(fetch("my-module.wasm"), importObject).then(
  (obj) => obj.instance.exports.exported_func(),
);

出於幾個原因,這不太理想:

  1. 每次匯入新字串時,下載大小都會增加,而這種增加不僅僅是字串本身的大小——對於您需要的每個字串,都需要 Wasm 模組中匯入的全域性變數的定義,以及 JavaScript 端的值的定義。對於包含數千個匯入字串的 Wasm 模組,這會累加起來。
  2. 在 Wasm 模組可以例項化之前,所有這些位元組也需要時間來解析。
  3. 對於 Wasm 模組的最佳化,不得不修改配套的 JavaScript 檔案(例如在編譯時刪除未使用的字串常量)是一個額外的麻煩。

匯入名稱可以是任何您喜歡的 Unicode 字串,因此開發人員通常為了方便起見,會將整個字串設定為匯入名稱(例如,在除錯時)。這將導致我們上面的 Wasm 程式碼片段重寫如下:

wat
(global (import "string_constants" "hello ") externref)
(global (import "string_constants" "world!") externref)

以及配套的 importObject 如下:

js
importObject = {
  // …
  string_constants: {
    "hello ": "hello ",
    "world!": "world!",
    // …
  },
};

檢視上面的程式碼,讓瀏覽器自動處理其中一些樣板程式碼是有意義的,而這正是匯入的全域性字串常量功能的作用。

使用匯入的全域性字串常量

現在我們將介紹匯入的全域性字串常量的使用方式。

JavaScript API

透過在呼叫編譯和/或例項化模組的方法時包含 compileOptions.importedStringConstants 屬性來啟用匯入的全域性字串常量。它的值是 Wasm 引擎將自動填充的匯入全域性字串常量的匯入名稱空間。

js
WebAssembly.compile(bytes, {
  importedStringConstants: "string_constants",
});

就是這樣!匯入物件中不需要字串列表。

compileOptions 物件可用於以下函式:

WebAssembly 模組功能

在您的 WebAssembly 模組中,您現在可以匯入字串字面量,指定在 JavaScript 的 importedStringConstants 中指定的相同名稱空間。

wat
(global $h (import "string_constants" "hello ") externref)
(global $w (import "string_constants" "world!") externref)

然後,Wasm 引擎會檢視 string_constants 名稱空間中的所有匯入的全域性變數,併為每個指定的匯入名稱建立一個相等的字串。

關於名稱空間選擇的說明

上面的示例為了說明目的,使用 "string_constants" 作為匯入的全域性字串名稱空間。然而,在生產環境中,最佳實踐是使用空字串("")來節省模組檔案大小。名稱空間會為每個字串字面量重複,而實際的模組可能有數千個,因此節省量可能很可觀。

如果您已經為其他目的使用了 "" 名稱空間,您應該考慮為您的字串使用單字元名稱空間,例如 "s""'""#"

名稱空間的選擇通常由生成 Wasm 模組的工具鏈的作者決定。一旦您有了 .wasm 檔案並想將其嵌入到您的 JavaScript 中,您就不能再自由地選擇這個名稱空間了;您必須使用 .wasm 檔案所期望的。

匯入的全域性字串示例

您可以在 一個使用匯入的全域性字串的示例 中看到它正在執行——開啟您瀏覽器的 JavaScript 控制檯以檢視示例輸出。該示例定義了一個 Wasm 模組中的函式,該函式將兩個匯入的字串連線在一起並將結果列印到控制檯,然後匯出它,再從 JavaScript 呼叫匯出的函式。

下面顯示了示例的 JavaScript。您可以看到我們是如何使用 importedStringConstants 來啟用匯入的全域性字串的。

js
const importObject = {
  // Regular import
  m: {
    log: console.log,
  },
};

const compileOptions = {
  builtins: ["js-string"], // Enable JavaScript string builtins
  importedStringConstants: "string_constants", // Enable imported global string constants
};

fetch("log-concat.wasm")
  .then((response) => response.arrayBuffer())
  .then((bytes) => WebAssembly.instantiate(bytes, importObject, compileOptions))
  .then((result) => result.instance.exports.main());

我們的 WebAssembly 模組程式碼的文字表示如下——請注意它是如何匯入指定名稱空間中的兩個字串,這些字串稍後在 $concat 函式中使用。

wat
(module
  (global $h (import "string_constants" "hello ") externref)
  (global $w (import "string_constants" "world!") externref)
  (func $concat (import "wasm:js-string" "concat")
    (param externref externref) (result (ref extern)))
  (func $log (import "m" "log") (param externref))
  (func (export "main")
    (call $log (call $concat (global.get $h) (global.get $w))))
)

注意: 此示例還使用了 JavaScript String 內建函式。有關這些內容以及上述示例的完整演練,請參閱 WebAssembly JavaScript 內建函式