WebAssembly 概念

本文介紹了 WebAssembly 工作原理背後的概念,包括其目標、解決的問題以及在 Web 瀏覽器 JavaScript 引擎中如何執行。

什麼是 WebAssembly?

WebAssembly 是一種新型程式碼,可以在現代 Web 瀏覽器中執行,並提供新功能和效能上的重大提升。它主要不是為了手動編寫,而是被設計為 C、C++、Rust 等源語言的有效編譯目標。

這對 Web 平臺具有重大意義——它提供了一種方法,可以在 Web 上以接近本機的速度執行用多種語言編寫的程式碼,使以前無法在 Web 上執行的客戶端應用程式能夠執行。

更重要的是,您甚至無需瞭解如何建立 WebAssembly 程式碼即可利用它。WebAssembly 模組可以匯入到 Web(或 Node.js)應用程式中,公開 WebAssembly 函式以供透過 JavaScript 使用。JavaScript 框架可以利用 WebAssembly 來實現巨大的效能優勢和新功能,同時仍然使 Web 開發人員能夠輕鬆使用這些功能。

WebAssembly 目標

WebAssembly 正在 W3C WebAssembly 社群組 內建立為一個開放標準,其目標如下

  • 快速、高效且可移植——WebAssembly 程式碼可以透過利用 常見的硬體功能,以接近本機的速度在不同的平臺上執行。
  • 可讀且可除錯——WebAssembly 是一種低階組合語言,但它確實具有可供人閱讀的文字格式(其規範仍在最終確定中),允許手動編寫、檢視和除錯程式碼。
  • 保持安全——WebAssembly 被指定在安全、沙箱化執行環境中執行。與其他 Web 程式碼一樣,它將強制執行瀏覽器的同源策略和許可權策略。
  • 不要破壞 Web——WebAssembly 的設計使它能夠與其他 Web 技術很好地配合,並保持向後相容性。

注意:WebAssembly 也將在 Web 和 JavaScript 環境之外使用(參見 非 Web 嵌入)。

WebAssembly 如何融入 Web 平臺?

可以認為 Web 平臺有兩個部分

  • 執行 Web 應用程式程式碼的虛擬機器 (VM),例如為您的應用程式提供支援的 JavaScript 程式碼。
  • 一組 Web API,Web 應用程式可以呼叫這些 API 來控制 Web 瀏覽器/裝置功能並使事情發生 (DOMCSSOMWebGLIndexedDBWeb 音訊 API 等)。

歷史上,VM 只能載入 JavaScript。這對我們來說一直很好,因為 JavaScript 足夠強大,可以解決當今人們在 Web 上遇到的絕大多數問題。但是,當嘗試將 JavaScript 用於更密集的用例(如 3D 遊戲、虛擬現實和增強現實、計算機視覺、影像/影片編輯以及需要原生效能的其他領域)時,我們遇到了效能問題(參見 WebAssembly 使用案例,瞭解更多想法)。

此外,下載、解析和編譯非常大的 JavaScript 應用程式的成本可能過高。移動和其他資源受限平臺會進一步放大這些效能瓶頸。

WebAssembly 是一種與 JavaScript 不同的語言,但它不是作為替代品出現的。相反,它被設計為補充並與 JavaScript 協同工作,使 Web 開發人員能夠利用兩種語言的優勢

  • JavaScript 是一種高階語言,靈活且富有表現力,足以編寫 Web 應用程式。它有許多優勢——它是動態型別的,不需要編譯步驟,並且擁有龐大的生態系統,提供了強大的框架、庫和其他工具。
  • WebAssembly 是一種低階組合語言,具有緊湊的二進位制格式,可以以接近本機的效能執行,併為具有低階記憶體模型的語言(如 C++ 和 Rust)提供編譯目標,以便它們可以在 Web 上執行。(請注意,WebAssembly 具有 高階目標,即在將來支援具有垃圾回收記憶體模型的語言。)

隨著 WebAssembly 在瀏覽器中出現,我們前面提到的虛擬機器現在將載入和執行兩種型別的程式碼——JavaScript 和 WebAssembly。

不同的程式碼型別可以根據需要相互呼叫——WebAssembly JavaScript API 將匯出的 WebAssembly 程式碼包裝在可以正常呼叫的 JavaScript 函式中,而 WebAssembly 程式碼可以匯入和同步呼叫普通的 JavaScript 函式。事實上,WebAssembly 程式碼的基本單位稱為模組,WebAssembly 模組在許多方面與 ES 模組對稱。

WebAssembly 關鍵概念

要了解 WebAssembly 如何在瀏覽器中執行,需要了解幾個關鍵概念。所有這些概念都在 WebAssembly JavaScript API 中 1:1 地反映出來。

  • 模組:表示 WebAssembly 二進位制檔案,該檔案已由瀏覽器編譯為可執行的機器程式碼。模組是無狀態的,因此,類似於 Blob,可以在視窗和工作執行緒之間顯式共享(透過 postMessage())。模組宣告匯入和匯出,就像 ES 模組一樣。
  • 記憶體:一個可調整大小的 ArrayBuffer,包含 WebAssembly 的低階記憶體訪問指令讀取和寫入的線性位元組陣列。
  • :一個可調整大小的型別化陣列,包含引用(例如指向函式的引用),這些引用不能以原始位元組的形式儲存在記憶體中(出於安全性和可移植性原因)。
  • 例項:模組與其在執行時使用的所有狀態配對,包括記憶體、表和一組匯入值。例項就像一個 ES 模組,它已載入到特定全域性範圍內,並具有一組特定的匯入項。

JavaScript API 為開發人員提供了建立模組、記憶體、表和例項的能力。在給定 WebAssembly 例項的情況下,JavaScript 程式碼可以同步呼叫其匯出項,這些匯出項作為普通的 JavaScript 函式公開。任意 JavaScript 函式也可以透過將這些 JavaScript 函式作為匯入傳遞給 WebAssembly 例項,由 WebAssembly 程式碼同步呼叫。

由於 JavaScript 能夠完全控制 WebAssembly 程式碼的下載、編譯和執行方式,因此 JavaScript 開發人員甚至可以將 WebAssembly 視為僅用於高效生成高效能函式的 JavaScript 功能。

將來,WebAssembly 模組將 像 ES 模組一樣可載入(使用 <script type='module'>),這意味著 JavaScript 將能夠像 ES 模組一樣輕鬆地獲取、編譯和匯入 WebAssembly 模組。

如何在應用程式中使用 WebAssembly?

上面我們討論了 WebAssembly 新增到 Web 平臺的原始原語:用於程式碼的二進位制格式以及用於載入和執行此二進位制程式碼的 API。現在讓我們談談如何在實踐中使用這些原語。

WebAssembly 生態系統仍處於起步階段;將來無疑會湧現更多工具。目前,有四個主要的切入點

  • 使用 Emscripten 移植 C/C++ 應用程式。
  • 直接在彙編級別編寫或生成 WebAssembly。
  • 編寫 Rust 應用程式,並以 WebAssembly 作為輸出目標。
  • 使用 AssemblyScript,它看起來類似於 TypeScript,並編譯為 WebAssembly 二進位制檔案。

讓我們談談這些選項

從 C/C++ 移植

建立 Wasm 程式碼的眾多選項中有兩個是:線上 Wasm 彙編程式或 Emscripten。線上 Wasm 彙編程式有很多選擇,例如

這些對於嘗試瞭解從哪裡開始的人來說是極好的資源,但它們缺乏 Emscripten 中的一些工具和最佳化。

Emscripten 工具能夠獲取幾乎任何 C/C++ 原始碼,並將其編譯為 Wasm 模組,以及載入和執行模組所需的必要 JavaScript "粘合" 程式碼,以及一個 HTML 文件以顯示程式碼的結果。

Diagram: Emscripten compiles C/C++ source code and into a Wasm module, an HTML document along with the JavaScript glue code.

簡而言之,這個過程的工作原理如下

  1. Emscripten 首先將 C/C++ 程式碼輸入 clang+LLVM——一個成熟的開源 C/C++ 編譯器工具鏈,例如,作為 OSX 上 XCode 的一部分提供。
  2. Emscripten 將 clang+LLVM 的編譯結果轉換為 Wasm 二進位制檔案。
  3. WebAssembly 本身目前無法直接訪問 DOM;它只能呼叫 JavaScript,傳入整數和浮點數等基本資料型別。因此,要訪問任何 Web API,WebAssembly 需要呼叫 JavaScript,然後由 JavaScript 進行 Web API 呼叫。因此,Emscripten 建立了實現此目的所需的 HTML 和 JavaScript 膠水程式碼。

注意: 未來計劃 允許 WebAssembly 直接呼叫 Web API

JavaScript 膠水程式碼並不像你想象的那麼簡單。首先,Emscripten 實現了流行的 C/C++ 庫,例如 SDLOpenGLOpenAL 和部分 POSIX。這些庫是使用 Web API 實現的,因此每個庫都需要一些 JavaScript 膠水程式碼將 WebAssembly 連線到底層的 Web API。

因此,膠水程式碼的一部分是實現 C/C++ 程式碼使用的每個庫的功能。膠水程式碼還包含呼叫上述 WebAssembly JavaScript API 來獲取、載入和執行 Wasm 檔案的邏輯。

生成的 HTML 文件載入 JavaScript 膠水檔案並將標準輸出寫入到 <textarea>。如果應用程式使用 OpenGL,則 HTML 還包含一個 <canvas> 元素,用作渲染目標。修改 Emscripten 輸出並將其轉換為所需的任何 Web 應用程式非常容易。

您可以在 emscripten.org 上找到 Emscripten 的完整文件,以及在 從 C/C++ 編譯到 WebAssembly 中實現工具鏈和將自己的 C/C++ 應用程式編譯到 Wasm 的指南。

直接編寫 WebAssembly

您是否想要構建自己的編譯器或自己的工具,或者建立一個在執行時生成 WebAssembly 的 JavaScript 庫?

與物理組合語言類似,WebAssembly 二進位制格式具有文字表示形式——兩者之間存在一對一的對應關係。您可以手動編寫或生成此格式,然後使用多個 WebAssembly 文字到二進位制工具 之一將其轉換為二進位制格式。

有關如何執行此操作的簡單指南,請參見我們的 將 WebAssembly 文字格式轉換為 Wasm 文章。

編寫針對 WebAssembly 的 Rust 程式碼

由於 Rust WebAssembly 工作組的不懈努力,您還可以編寫 Rust 程式碼並將其編譯到 WebAssembly。您可以在我們的 從 Rust 編譯到 WebAssembly 文章中開始安裝必要的工具鏈、將示例 Rust 程式編譯為 WebAssembly npm 包,並在示例 Web 應用程式中使用它。

使用 AssemblyScript

對於希望嘗試 WebAssembly 但不想學習 C 或 Rust 細節的 Web 開發人員,在熟悉 TypeScript 等語言的舒適環境中,AssemblyScript 將是最佳選擇。AssemblyScript 將 TypeScript 的嚴格變體編譯為 WebAssembly,允許 Web 開發人員繼續使用他們熟悉的 TypeScript 相容工具,例如 Prettier、ESLint、VS Code IntelliSense 等。您可以在 https://www.assemblyscript.org/ 上檢視其文件。

總結

本文解釋了 WebAssembly 是什麼,為什麼它如此有用,它如何適應 Web 以及如何使用它。

另請參閱