在 Go 中支持 WASI-Go 1-21 新特性-

Go 1.21 通過添加新的 wasip1 值來支持針對 WASI 預覽版 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 preview 1, 我們的 wasi_snapshot_preview1 名稱也由此而來。新的 API 版本正在開發中, 未來在 Go 編譯器中支持它們可能意味着添加新的 GOOS 值。

WASI 的出現使許多 Wasm 運行時 (主機) 能圍繞它標準化其系統調用 API。Wasm/WASI 主機的例子包括 Wasmtime、Wazero、WasmEdge、Wasmer 和 NodeJS。許多雲提供商也提供了託管 Wasm/WASI 可執行文件的服務。

我們如何在 Go 中使用它?

確保您已安裝至少 1.21 版本的 Go。在本演示中, 我們將使用 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 測試

構建和運行二進制文件很簡單, 但有時我們希望能夠直接 go test, 而不必手動構建和執行二進制文件。與 js/wasm 端口類似, Go 發行版中的標準庫包含一個 misc/wasm 目錄, 可以非常容易地實現這一點。在運行 Go 測試時, 將該目錄添加到您的 PATH 中, 它將使用您選擇的 Wasm 主機來運行測試。當它在 misc/wasm/go_wasip1_wasm_exec 中找到 go test 時:

$ export PATH=$PATH:$(go env GOROOT)/misc/wasm
$ GOOS=wasip1 GOARCH=wasm go test ./...

這將使用 Wasmtime 來運行 go test。可以使用環境變量 GOWASIRUNTIME 來控制使用的 Wasm 主機。該變量目前支持的值爲 wazerowasmedgewasmtimewasmer。這個腳本可能會在不同的 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。它指示編譯器將對帶註釋的函數的調用轉換爲對由主機模塊名稱和函數名稱指定的函數的調用。這個新的編譯器功能使我們能夠爲 wasip1 端口定義系統調用 API 以支持它, 但它不僅限於在標準庫中使用。

例如, wasip1 syscall API 定義了一個 random_get 函數, 並通過運行時包中定義的函數包裝器將其暴露給 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 是一個單線程架構, 沒有並行性。調度程序仍然可以安排 goroutine 併發運行, 標準的輸入 / 輸出 / 錯誤是非阻塞的, 因此一個 goroutine 可以在另一個 goroutine 讀取或寫入時執行, 但任何主機函數調用 (如上例中的請求隨機數) 都將導致所有 goroutine 阻塞, 直到主機函數返回。

值得注意的是,wasip1 API 中缺少完整的網絡套接字實現。wasip1 只定義了操作已經打開的套接字的函數, 因此無法支持 Go 標準庫中一些最流行的功能, 如 HTTP 服務器。Wasmer 和 WasmEdge 等主機實現了 wasip1 API 的擴展, 允許打開網絡套接字。雖然這些擴展不是由 Go 編譯器實現的, 但有一個第三方庫 github.com/stealthrocket/net 通過 go:wasmimport 使用 net.Dialnet.Listen 允許在支持的 Wasm 主機上使用它。這意味着使用此包可以創建 net/http 服務器和其他網絡相關功能。

Wasm 在 Go 中的未來

添加 wasip1/wasm 端口只是我們希望爲 Go 帶來的 Wasm 功能的開始。請密切關注問題跟蹤器, 瞭解有關導出 Go 函數到 Wasm (go:wasmexport)、32 位端口和未來 WASI API 兼容性的建議。

參與貢獻

如果您正在嘗試 Wasm 和 Go 並想爲其做出貢獻, 請加入進來! Go 問題跟蹤器跟蹤所有正在進行的工作, Gophers Slack 上的 #webassemble 頻道是討論 Go 和 WebAssembly 的好地方。我們期待您的參與!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/NM6vFV7cNYzcTKRchYjfLg