WebAssembly 概念

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

什麼是 WebAssembly?

WebAssembly 是一種可以在現代 Web 瀏覽器中執行的新型程式碼,提供新功能和顯著的效能提升。它並非主要用於手寫,而是旨在成為 C、C++、Rust 等源語言的有效編譯目標。

這對於 Web 平臺具有巨大的影響——它提供了一種以接近原生速度在 Web 上執行多種語言編寫的程式碼的方式,使得以前無法在 Web 上執行的客戶端應用程式得以實現。

更重要的是,你甚至不需要知道如何建立 WebAssembly 程式碼就可以利用它。WebAssembly 模組可以匯入到 Web(或 Node.js)應用程式中,透過 JavaScript 暴露 WebAssembly 函式供使用。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 應用程式可以呼叫它們來控制 Web 瀏覽器/裝置功能並實現各種操作(DOMCSSOMWebGLIndexedDBWeb Audio API 等)。

歷史上,虛擬機器只能載入 JavaScript。這對於我們來說一直運作良好,因為 JavaScript 強大到足以解決當今 Web 上人們遇到的大多數問題。然而,當試圖將 JavaScript 用於更密集的用例時,我們遇到了效能問題,例如 3D 遊戲、虛擬和增強現實、計算機視覺、影像/影片編輯以及許多其他需要原生效能的領域(有關更多想法,請參見 WebAssembly 用例)。

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

WebAssembly 是一種不同於 JavaScript 的語言,但它並非旨在取代 JavaScript。相反,它旨在補充 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 中的概念一一對應。

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

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

由於 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++ 編譯器工具鏈,例如作為 macOS 上 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 npm 包,並在示例 Web 應用程式中使用它,請參閱我們的 從 Rust 編譯到 WebAssembly 文章。

使用 AssemblyScript

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

總結

本文向你解釋了 WebAssembly 是什麼,為什麼它如此有用,它如何融入 Web,以及你如何利用它。

另見