翻譯自 Dirk andthe VS Code team 的博客
Web 版 VS 代碼? 已經(jīng)推出一段時(shí)間了,支持瀏覽器中的完整編輯/編譯/調(diào)試周期一直是我們的目標(biāo)。這對(duì)于 JavaScript 和 TypeScript 等語言來說相對(duì)容易,因?yàn)闉g覽器附帶了 JavaScript 執(zhí)行引擎。對(duì)于其他語言來說更難,因?yàn)槲覀儽仨毮軌驁?zhí)行(并因此調(diào)試)代碼。例如,要在瀏覽器中運(yùn)行 Python 源代碼,就需要有一個(gè)可以運(yùn)行 Python 解釋器的執(zhí)行引擎。這些語言運(yùn)行時(shí)通常用 C/C++ 編寫。
WebAssembly 是虛擬機(jī)的二進(jìn)制指令格式。WebAssembly 虛擬機(jī)今天在現(xiàn)代瀏覽器中發(fā)布,并且有工具鏈可以將 C/C++ 編譯為 WebAssembly 代碼。為了找出 WebAssemblies 的可能性,我們決定使用一個(gè)用 C/C++ 編寫的 Python 解釋器,將其編譯為 WebAssembly,然后在 VS Code for the Web 中運(yùn)行它。幸運(yùn)的是,Python 團(tuán)隊(duì)已經(jīng)開始著手將 CPython 編譯為 WASM,我們很高興地利用了他們的努力。探索的結(jié)果可以在下圖中看到:
?
它看起來與在 VS Code 桌面中執(zhí)行 Python 代碼并沒有什么不同。那么,為什么這很酷呢?
Python 源代碼(app.py 和 hello.py)托管在 GitHub 存儲(chǔ)庫(kù)中,可直接從 GitHub 讀取。Python 解釋器可以完全訪問工作區(qū)中的文件,但不能訪問任何其他文件
示例代碼是多文件。app.py 依賴于 hello.py
輸出很好地顯示在 VS Code 的終端中
您可以運(yùn)行 Python REPL 并與其完全交互
當(dāng)然,它在網(wǎng)絡(luò)上運(yùn)行
此外,編譯為 WebAssembly(WASM)代碼的 Python 解釋器無需修改即可在 VS Code for the Web 中運(yùn)行。這些位是一對(duì)一的,與 CPython 團(tuán)隊(duì)創(chuàng)建的相同。
? ? ? 它是如何工作的?
WebAssembly 虛擬機(jī)不附帶 SDK(例如 Java 或 .NET)。因此,開箱即用的 WebAssembly 代碼無法打印到控制臺(tái)或讀取文件的內(nèi)容。WebAssembly 規(guī)范定義的是 WebAssembly 代碼如何在運(yùn)行虛擬機(jī)的主機(jī)中調(diào)用函數(shù)。對(duì)于 Web 的 VS Code,主機(jī)是瀏覽器。因此,虛擬機(jī)可以調(diào)用在瀏覽器中執(zhí)行的 JavaScript 函數(shù)。
Python 團(tuán)隊(duì)提供了兩種解釋器的 WebAssembly 二進(jìn)制文件:一種是使用 emscripten 編譯的,另一種是使用 WASI SDK 編譯的。盡管它們都創(chuàng)建 WebAssembly 代碼,但它們?cè)谧鳛樗拗鲗?shí)現(xiàn)提供的 JavaScript 函數(shù)方面具有不同的特征:
emscripten - 特別關(guān)注 Web 平臺(tái)和 Node.js。除了生成 WASM 代碼外,它還生成 JavaScript 代碼,作為宿主在瀏覽器或 Node.js 環(huán)境中執(zhí)行 WASM 代碼。例如,JavaScript 代碼提供了將 C printf 語句的內(nèi)容打印到瀏覽器控制臺(tái)的功能。
WASI SDK - 將 C/C++ 代碼編譯為 WASM 并假定主機(jī)實(shí)現(xiàn)符合 WASI 規(guī)范。WASI 代表 WebAssembly 系統(tǒng)接口。它定義了幾個(gè)類似操作系統(tǒng)的特性,包括文件和文件系統(tǒng)、套接字、時(shí)鐘和隨機(jī)數(shù)。使用 WASI SDK 編譯 C/C++ 代碼只會(huì)生成 WebAssembly 代碼,不會(huì)生成任何 JavaScript 函數(shù)。主機(jī)必須提供打印 C printf 語句內(nèi)容所需的 JavaScript 函數(shù)。例如,Wasmtime 是一個(gè)運(yùn)行時(shí),它提供將 WASI 連接到操作系統(tǒng)調(diào)用的 WASI 主機(jī)實(shí)現(xiàn)。
對(duì)于 VS Code,我們決定支持 WASI。雖然我們的主要重點(diǎn)是在瀏覽器中執(zhí)行 WASM 代碼,但我們實(shí)際上并不是在純?yōu)g覽器環(huán)境中運(yùn)行它。我們必須在 VS Code 的擴(kuò)展主機(jī)工作器中運(yùn)行 WebAssemblies,這是擴(kuò)展 VS Code 的標(biāo)準(zhǔn)方式。除了瀏覽器的 worker API 之外,擴(kuò)展主機(jī) worker 還提供整個(gè) VS Code 擴(kuò)展 API。因此,我們實(shí)際上不想將 C/C++ 程序中的 printf 調(diào)用連接到瀏覽器的控制臺(tái),而是將其連接到 VS Code 的終端 API。在 WASI 中這樣做比在 emscripten 中更容易。
我們當(dāng)前 VS Code 的 WASI 主機(jī)實(shí)現(xiàn)基于 WASI 快照預(yù)覽1,本文中描述的所有實(shí)現(xiàn)細(xì)節(jié)均參考該版本。
? ? 如何運(yùn)行我自己的 WebAssembly 代碼?
在 VS Code for Web 中運(yùn)行 Python 后,我們很快意識(shí)到我們采用的方法允許我們執(zhí)行任何可以編譯為 WASI 的代碼。因此,本節(jié)演示如何使用 WASI SDK 將小型 C 程序編譯為 WASI,并在 VS Code 的擴(kuò)展主機(jī)中執(zhí)行它。該示例假定讀者熟悉 VS Code 的擴(kuò)展 API,并且知道如何為 Web 編寫 VS Code 的擴(kuò)展。
我們運(yùn)行的 C 程序是一個(gè)簡(jiǎn)單的“Hello World”程序,如下所示:
?
#include假設(shè)您安裝了最新的 WASI SDK 并且它在您的 PATH 中,可以使用以下命令編譯 C 程序:int main(void) { printf("Hello, World "); return 0; }
?
?
clang hello.c -o ./hello.wasm
?
這會(huì)在 hello.c 文件旁邊生成一個(gè) hello.wasm 文件。
新功能通過擴(kuò)展添加到 VS Code,我們?cè)趯?WebAssemblies 集成到 VS Code 時(shí)遵循相同的模型。我們需要定義一個(gè)加載和運(yùn)行 WASM 代碼的擴(kuò)展。擴(kuò)展的 package.json 清單的重要部分如下:
?
{ "name": "...", ..., "extensionDependencies": [ "ms-vscode.wasm-wasi-core" ], "contributes": { "commands": [ { "command": "wasm-c-example.run", "category": "WASM Example", "title": "Run C Hello World" } ] }, "devDependencies": { "@types/vscode": "1.77.0", }, "dependencies": { "@vscode/wasm-wasi": "0.11.0-next.0" } }ms-vscode.wasm-wasi-core 擴(kuò)展提供了將 WASI API 連接到 VS Code API 的 WebAssembly 執(zhí)行引擎。節(jié)點(diǎn)模塊 @vscode/wasm-wasi 提供了一個(gè)外觀來在 VS Code 中加載和運(yùn)行 WebAssembly 代碼。
?
下面是加載和運(yùn)行 WebAssembly 代碼的實(shí)際 TypeScript 代碼:
?
import { Wasm } from '@vscode/wasm-wasi'; import { commands, ExtensionContext, Uri, window, workspace } from 'vscode'; export async function activate(context: ExtensionContext) { // Load the WASM API const wasm: Wasm = await Wasm.load(); // Register a command that runs the C example commands.registerCommand('wasm-wasi-c-example.run', async () => { // Create a pseudoterminal to provide stdio to the WASM process. const pty = wasm.createPseudoterminal(); const terminal = window.createTerminal({ name: 'Run C Example', pty, isTransient: true }); terminal.show(true); try { // Load the WASM module. It is stored alongside the extension's JS code. // So we can use VS Code's file system API to load it. Makes it // independent of whether the code runs in the desktop or the web. const bits = await workspace.fs.readFile( Uri.joinPath(context.extensionUri, 'hello.wasm') ); const module = await WebAssembly.compile(bits); // Create a WASM process. const process = await wasm.createProcess('hello', module, { stdio: pty.stdio }); // Run the process and wait for its result. const result = await process.run(); if (result !== 0) { await window.showErrorMessage(`Process hello ended with error: ${result}`); } } catch (error) { // Show an error message if something goes wrong. await window.showErrorMessage(error.message); } }); }下圖顯示了在 VS Code for Web 中運(yùn)行的擴(kuò)展。
?
我們使用 C/C++ 代碼作為 WebAssembly 的源代碼,因?yàn)?WASI 是一個(gè)標(biāo)準(zhǔn),所以還有其他支持 WASI 的工具鏈。例如:Rust、.NET 或 Swift。
? VS Code 的 WASI 實(shí)現(xiàn)
WASI 和 VS Code API 共享文件系統(tǒng)或 stdio(例如,終端)等概念。這使我們能夠在 VS Code?API 之上實(shí)施 WASI 規(guī)范。然而,不同的執(zhí)行行為是一個(gè)挑戰(zhàn):WebAssembly 代碼執(zhí)行是同步的(例如,一旦 WebAssembly 執(zhí)行開始,JavaScript worker 就會(huì)被阻塞直到執(zhí)行完成),而 VS Code 和瀏覽器的大多數(shù) API 都是異步的。例如,從 WASI 中的文件讀取是同步的,而相應(yīng)的 VS Code API 是異步的。這個(gè)特性導(dǎo)致在 VS Code 擴(kuò)展宿主 worker 中執(zhí)行 WebAssembly 代碼的兩個(gè)問題:
我們需要防止擴(kuò)展主機(jī)在執(zhí)行 WebAssembly 代碼時(shí)被阻塞,因?yàn)檫@會(huì)阻止其他擴(kuò)展的執(zhí)行。
需要一種機(jī)制來在異步 VS Code 和瀏覽器 API 之上實(shí)現(xiàn)同步 WASI API。
第一種情況很容易解決:我們?cè)趩为?dú)的工作線程中運(yùn)行 WebAssembly 代碼。第二種情況更難解決,因?yàn)閷⑼酱a映射到異步代碼需要暫停同步執(zhí)行線程,并在異步計(jì)算結(jié)果可用時(shí)恢復(fù)它。WebAssembly 的 JavaScript-Promise Integration Proposal 解決了 WASM 層上的這個(gè)問題,并且在 V8 中有該提案的實(shí)驗(yàn)性實(shí)現(xiàn)。然而,當(dāng)我們開始努力時(shí),V8 實(shí)現(xiàn)還不可用。所以我們選擇了一個(gè)不同的實(shí)現(xiàn),它使用 SharedArrayBuffer 和 Atomics 將同步 WASI API 映射到 VS Code 的異步 API。
該方法的工作原理如下:
WASM 工作線程創(chuàng)建一個(gè) SharedArrayBuffer,其中包含有關(guān)應(yīng)在 VS Code?端調(diào)用的代碼的必要信息。
它將共享內(nèi)存發(fā)布到 VS Code 的擴(kuò)展主機(jī)工作程序,然后等待擴(kuò)展主機(jī)工作程序使用 Atomics.wait 完成其工作。
擴(kuò)展主機(jī)工作線程獲取消息,調(diào)用適當(dāng)?shù)?VS Code?API,將結(jié)果寫回 SharedArrayBuffer,然后使用 Atomics.store 和 Atomics.notify 通知 WASM 工作線程喚醒。
WASM worker 然后從 SharedArrayBuffer 中讀取任何結(jié)果數(shù)據(jù)并將其返回給 WASI 回調(diào)。
這種方法的唯一困難是 SharedArrayBuffer 和 Atomics 要求站點(diǎn)跨源隔離,因?yàn)?CORS 非常流行,這本身就是一項(xiàng)努力。這就是為什么它目前僅在 Insiders 版本 insiders.vscode.dev 上默認(rèn)啟用,并且必須在 vscode.dev 上使用查詢參數(shù) ?vscode-coi=on 啟用。
下圖更詳細(xì)地顯示了我們編譯為 WebAssembly 的上述 C 程序的 WASM worker 和擴(kuò)展主機(jī) worker 之間的交互。橙色框中的代碼是 WebAssembly 代碼,綠色框中的所有代碼都在 JavaScript 中運(yùn)行。黃色框表示 SharedArrayBuffer。
? A Web Shell ?
現(xiàn)在我們能夠?qū)?C/C++ 和 Rust 代碼編譯為 WebAssembly 并在 VS Code 中執(zhí)行它,我們探索了是否也可以在 VS Code for the Web 中運(yùn)行一個(gè) shell。
我們研究了將其中一個(gè) Unix shell 編譯為 WebAssembly。但是,某些 shell 依賴于操作系統(tǒng)功能(生成進(jìn)程...),這些功能目前在 WASI 中不可用。這導(dǎo)致我們采取了一種稍微不同的方法:我們?cè)?TypeScript 中實(shí)現(xiàn)了一個(gè)基本的 shell,并嘗試僅將 Unix 核心實(shí)用程序(如 ls、cat、date 等)編譯為 WebAssembly。由于 Rust 對(duì) WASM 和 WASI 有很好的支持,我們嘗試了 uutils/coreutils,這是 GNU coreutils 在 Rust 中的跨平臺(tái)重新實(shí)現(xiàn)。我們有了第一個(gè)最小的 web shell。
如果您不能執(zhí)行自定義 WebAssemblies 或命令,則 shell 非常有限。為了擴(kuò)展 web shell,其他擴(kuò)展可以為文件系統(tǒng)提供額外的掛載點(diǎn),以及在將它們鍵入 web shell 時(shí)調(diào)用的命令。通過命令的間接訪問將具體的 WebAssembly 執(zhí)行與終端中鍵入的內(nèi)容分離。從一開始就使用 Python 擴(kuò)展中的此支持,允許您通過在提示符中輸入 python app.py 或列出默認(rèn)的 Python 3.11 庫(kù)(通常安裝在/usr/local/lib/python3.11下)直接從 shell 中執(zhí)行 Python 代碼。
? 接下來是什么?
WASM 執(zhí)行引擎擴(kuò)展和 Web Shell 擴(kuò)展都是實(shí)驗(yàn)性的預(yù)覽版,不應(yīng)用于使用 WebAssemblies 實(shí)現(xiàn)生產(chǎn)就緒擴(kuò)展。它們已公開提供,以獲得對(duì)該技術(shù)的早期反饋。如果您有任何問題或反饋,請(qǐng)?jiān)谙鄳?yīng)的 vscode-wasm GitHub 存儲(chǔ)庫(kù)中打開問題。此存儲(chǔ)庫(kù)還包含 Python 示例以及 WASM 執(zhí)行引擎和 Web Shell 的源代碼。
我們將進(jìn)一步探討以下主題:
WASI 團(tuán)隊(duì)正在研究規(guī)范的預(yù)覽版 2 和預(yù)覽版 3,我們也計(jì)劃支持該規(guī)范。新版本將改變 WASI 主機(jī)的實(shí)現(xiàn)方式。但是,我們有信心可以保持在 WASM 執(zhí)行引擎擴(kuò)展中公開的 API 基本穩(wěn)定。
還有 WASIX 努力擴(kuò)展 WASI,增加了類似操作系統(tǒng)的特性,例如進(jìn)程或 futex。我們將繼續(xù)關(guān)注這項(xiàng)工作。
VS Code 的許多語言服務(wù)器都是用不同于 JavaScript 或 TypeScript 的語言實(shí)現(xiàn)的。我們計(jì)劃探索將這些語言服務(wù)器編譯為 wasm32-wasi 并在 VS Code for Web 中運(yùn)行它們的可能性。
改進(jìn) Web 上 Python 的調(diào)試。我們已經(jīng)開始著手解決這個(gè)問題,敬請(qǐng)期待。
添加支持,以便擴(kuò)展 B 可以運(yùn)行由擴(kuò)展 A 貢獻(xiàn)的 WebAssembly 代碼。例如,這將允許任意擴(kuò)展通過重用貢獻(xiàn) Python WebAssembly 的擴(kuò)展來執(zhí)行 Python 代碼。
確保為 wasm32-wasi 編譯的其他語言運(yùn)行時(shí)在 VS Code 的 WebAssembly 執(zhí)行引擎之上運(yùn)行。VMware Labs 提供 Ruby 和 PHP wasm32-wasi 二進(jìn)制文件,兩者都在 VS Code 中運(yùn)行。
Happy Coding!
審核編輯:湯梓紅
評(píng)論
查看更多