WASI support in Go

2023-09-15 21:00:34

原文在這裡

由 Johan Brandhorst-Satzkorn, Julien Fabre, Damian Gryski, Evan Phoenix, and Achille Roussel 釋出於 2023年9月13日

Go 1.21新增了一個新的埠,通過新的GOOS值wasip1來定位WASI預覽1系統呼叫API。該埠建立在Go 1.11引入的現有WebAssembly埠的基礎上。

WebAssembly 是什麼

WebAssembly(Wasm)是一種最初設計用於Web的二進位制指令格式。它代表了一個標準,允許開發人員在Web瀏覽器中以接近本機速度直接執行高效能、低階別的程式碼。

Go首次在1.11版本中新增了對編譯成Wasm的支援,通過js/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宿主的範例包括WasmtimeWazeroWasmEdgeWasmerNodeJS。還有許多雲提供商提供Wasm/WASI可執行檔案的託管服務。

Go 中如何使用 WebAssembly

請確保已安裝至少1.21版本的Go。對於此演示,我們將使用Wasmtime主機來執行我們的二進位制檔案。讓我們從一個簡單的 main.go 開始:

package main

import "fmt"

func main() {
    fmt.Println("Hello world!")
}

使用如下命令進行編譯:

$ 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,而無需手動構建和執行二進位制檔案。與 js/wasm 埠類似,Go安裝中包含的標準庫分發版本附帶一個檔案,使這個過程變得非常簡單。在執行Go測試時,將 misc/wasm 目錄新增到 PATH 中,它將使用你選擇的Wasm主機來執行測試。這是通過 go test 在PATH中找到此檔案時自動執行 misc/wasm/go_wasip1_wasm_exec 來實現的:

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

這將使用 Wasmtime 執行 go test。可以使用環境變數 GOWASIRUNTIME 來控制所使用的Wasm主機。目前支援的變數值包括 wazerowasmedgewasmtimewasmer。請注意,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是一個沒有並行性的單執行緒架構。排程器仍然可以排程goroutine以並行執行,標準輸入/輸出/錯誤是非阻塞的,因此一個goroutine可以在另一個讀取或寫入時執行,但是任何主機函數呼叫(例如使用上面的範例請求亂資料)都會導致所有goroutine阻塞,直到主機函數呼叫返回。

wasip1 API中一個顯著缺失的功能是完整的網路通訊端實現。wasip1只定義了對已經開啟的通訊端進行操作的函數,這使得無法支援Go標準庫的一些最流行的功能,如HTTP伺服器。像Wasmer和WasmEdge這樣的主機實現了wasip1 API的擴充套件,允許開啟網路通訊端。儘管Go編譯器沒有實現這些擴充套件,但存在第三方庫,github.com/stealthrocket/net,使用go:wasmimport允許在支援的Wasm主機上使用net.Dial和net.Listen。這允許在使用此包時建立net/http伺服器和其他與網路相關的功能。

Go中的Wasm的未來

wasip1/wasm 埠的新增只是我們希望引入Go的Wasm功能的開端。請密切關注問題跟蹤器,瞭解有關將Go函數匯出到Wasm(go:wasmexport)、32位元埠和未來WASI API相容性的提案。

參與其中

如果你正在嘗試並希望為Wasm和Go做出貢獻,請參與其中!Go問題跟蹤器跟蹤所有正在進行的工作,Gophers Slack上的 #webassembly 頻道是討論Go和WebAssembly的好地方。我們期待聽到你的聲音!


孟斯特

宣告:本作品採用署名-非商業性使用-相同方式共用 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請註明出處。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 戀水無意