Go 部落格
Go 對 WASI 的支援
Go 1.21 透過新的 GOOS
值 wasip1
添加了一個新的埠,目標是 WASI preview 1 系統呼叫 API。該埠構建於 Go 1.11 中引入的現有 WebAssembly 埠之上。
WebAssembly 是什麼?
WebAssembly (Wasm) 是一種最初為 Web 設計的二進位制指令格式。它代表了一種標準,允許開發者以接近原生的速度直接在 Web 瀏覽器中執行高效能、低級別程式碼。
Go 在 1.11 版本中首次透過 js/wasm
埠添加了編譯到 Wasm 的支援。這使得使用 Go 編譯器編譯的 Go 程式碼可以在 Web 瀏覽器中執行,但這需要一個 JavaScript 執行環境。
隨著 Wasm 使用的增長,瀏覽器之外的用例也越來越多。現在許多雲提供商提供允許使用者直接執行 Wasm 可執行檔案的服務,這利用了新的 WebAssembly 系統介面 (WASI) 系統呼叫 API。
WebAssembly 系統介面
WASI 定義了一個針對 Wasm 可執行檔案的系統呼叫 API,允許它們與檔案系統、系統時鐘、隨機資料工具等系統資源進行互動。WASI 規範的最新版本稱為 wasi_snapshot_preview1
,我們從中派生出 GOOS
名稱 wasip1
。新版本的 API 正在開發中,將來在 Go 編譯器中支援它們可能意味著新增一個新的 GOOS
。
WASI 的建立使得許多 Wasm 執行時(宿主)圍繞它標準化其系統呼叫 API。Wasm/WASI 宿主的示例包括 Wasmtime、Wazero、WasmEdge、Wasmer 和 NodeJS。也有許多雲提供商提供 Wasm/WASI 可執行檔案的託管服務。
如何與 Go 一起使用?
確保您已安裝 Go 1.21 或更高版本。對於此演示,我們將使用 Wasmtime 宿主來執行我們的二進位制檔案。讓我們從一個簡單的 main.go
開始
package main
import "fmt"
func main() {
fmt.Println("Hello world!")
}
我們可以使用以下命令為 wasip1
構建它
$ GOOS=wasip1 GOARCH=wasm go build -o main.wasm main.go
這將生成一個檔案 main.wasm
,我們可以使用 wasmtime
執行它
$ wasmtime main.wasm
Hello world!
這就是開始使用 Wasm/WASI 所需的全部!您可以期待 Go 的幾乎所有功能都能在 wasip1
上正常工作。要了解有關 WASI 如何與 Go 一起工作的更多詳細資訊,請參閱提案。
使用 wasip1 執行 go test
Go 1.24 將 Wasm 支援檔案移至
lib/wasm
。對於 Go 1.21 - 1.23,請使用misc/wasm
目錄。
構建和執行二進位制檔案很簡單,但有時我們希望能夠直接執行 go test
,而無需手動構建和執行二進位制檔案。與 js/wasm
埠類似,Go 安裝中包含的標準庫分發版帶有一個檔案,可以輕鬆實現此目的。在執行 Go test 時將 lib/wasm
目錄新增到 PATH
,它將使用您選擇的 Wasm 宿主執行測試。這是透過 go test
在 PATH
中找到 lib/wasm/go_wasip1_wasm_exec
檔案時自動執行它來實現的。
$ export PATH=$PATH:$(go env GOROOT)/lib/wasm
$ GOOS=wasip1 GOARCH=wasm go test ./...
這將使用 Wasmtime 執行 go test
。使用的 Wasm 宿主可以透過環境變數 GOWASIRUNTIME
控制。目前支援的值有 wazero
、wasmedge
、wasmtime
和 wasmer
。此指令碼在 Go 版本之間可能會發生重大變化。請注意,Go wasip1
二進位制檔案目前並非在所有宿主上都能完美執行(參見 #59907 和 #60097)。
此功能在使用 go run
時也適用
$ GOOS=wasip1 GOARCH=wasm go run ./main.go
Hello world!
使用 go:wasmimport 在 Go 中包裝 Wasm 函式
除了新的 wasip1/wasm
埠,Go 1.21 還引入了一個新的編譯器指令:go:wasmimport
。它指示編譯器將對帶註解函式的呼叫轉換為對由宿主模組名和函式名指定的函式的呼叫。這一新的編譯器功能使我們能夠在 Go 中定義 wasip1
系統呼叫 API 以支援新埠,但它不僅限於在標準庫中使用。
例如,wasip1 系統呼叫 API 定義了 random_get
函式,它透過 runtime 包中定義的函式包裝器暴露給 Go 標準庫。它看起來像這樣
//go:wasmimport wasi_snapshot_preview1 random_get
//go:noescape
func random_get(buf unsafe.Pointer, bufLen size) errno
然後,此函式包裝器在標準庫中透過一個更易用的函式進行包裝
func getRandomData(r []byte) {
if random_get(unsafe.Pointer(&r[0]), size(len(r))) != 0 {
throw("random_get failed")
}
}
這樣,使用者就可以使用位元組切片呼叫 getRandomData
,它最終將到達宿主定義的 random_get
函式。同樣,使用者也可以為宿主函式定義自己的包裝器。
要了解更多關於在 Go 中包裝 Wasm 函式的細節,請參閱go:wasmimport
提案。
限制
雖然 wasip1
埠通過了所有標準庫測試,但 Wasm 架構有一些顯著的根本限制可能會讓使用者感到意外。
Wasm 是一種單執行緒架構,沒有並行性。排程器仍然可以排程 goroutines 併發執行,標準輸入/輸出/錯誤是非阻塞的,所以一個 goroutine 可以在另一個 goroutine 讀寫時執行,但任何宿主函式呼叫(例如使用上面例子請求隨機資料)都會導致所有 goroutines 阻塞,直到宿主函式呼叫返回。
wasip1
API 中一個顯著缺失的功能是完整的網路套接字實現。wasip1
只定義了操作已開啟套接字的函式,這使得無法支援 Go 標準庫中一些最受歡迎的功能,例如 HTTP 伺服器。像 Wasmer 和 WasmEdge 這樣的宿主實現了 wasip1
API 的擴充套件,允許開啟網路套接字。雖然 Go 編譯器沒有實現這些擴充套件,但存在一個第三方庫 github.com/stealthrocket/net
,它使用 go:wasmimport
允許在支援的 Wasm 宿主上使用 net.Dial
和 net.Listen
。使用此包時,可以建立 net/http
伺服器和其他網路相關功能。
Wasm 在 Go 中的未來
新增 wasip1/wasm
埠只是我們希望為 Go 帶來的 Wasm 功能的開端。請關注問題追蹤器,瞭解關於將 Go 函式匯出到 Wasm (go:wasmexport
)、32 位埠和未來 WASI API 相容性的提案。
參與進來
如果您正在嘗試並希望為 Wasm 和 Go 做出貢獻,請參與進來!Go 問題追蹤器跟蹤所有正在進行的工作,並且Gophers Slack 上的 #webassembly 頻道是討論 Go 和 WebAssembly 的好地方。我們期待您的聲音!
下一篇文章:修復 Go 1.22 中的 for 迴圈
上一篇文章:為不斷發展的 Go 生態系統擴充套件 gopls
部落格索引