原文在這裡。 由 Johan Brandhorst-Satzkorn, Julien Fabre, Damian Gryski, Evan Phoenix, and Achille Roussel 發佈於 2023年9月13日 Go 1.21添加了一個新的埠,通過新的GOOS值wasip1來定位W ...
原文在這裡。
由 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宿主的示例包括Wasmtime、Wazero、WasmEdge、Wasmer和NodeJS。還有許多雲提供商提供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主機。目前支持的變數值包括 wazero
、wasmedge
、wasmtime
和 wasmer
。請註意,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: 戀水無意