Go 部落格
使用 Go 實現可擴充套件的 Wasm 應用
Go 1.24 透過新增 go:wasmexport
指令以及構建 WebAssembly System Interface (WASI) 的 reactor 的能力,增強了其 WebAssembly (Wasm) 功能。這些功能使 Go 開發者能夠將 Go 函式匯出到 Wasm,從而更好地與 Wasm 主機整合,並擴充套件基於 Go 的 Wasm 應用的可能性。
WebAssembly 和 WebAssembly System Interface
WebAssembly (Wasm) 是一種二進位制指令格式,最初是為 Web 瀏覽器建立的,能夠以接近原生效能的速度執行高效能的底層程式碼。自那時以來,Wasm 的應用範圍已得到擴充套件,現在已在瀏覽器之外的各種環境中得到使用。值得注意的是,雲服務提供商提供直接執行 Wasm 可執行檔案的服務,並利用 WebAssembly System Interface (WASI) 系統呼叫 API。WASI 允許這些可執行檔案與系統資源進行互動。
Go 在 1.11 版本中首次透過 js/wasm
埠添加了對編譯到 Wasm 的支援。Go 1.21 透過新的 GOOS=wasip1
埠添加了一個針對 WASI preview 1 syscall API 的新埠。
使用 go:wasmexport
將 Go 函式匯出到 Wasm
Go 1.24 引入了一個新的編譯器指令 go:wasmexport
,它允許開發者匯出 Go 函式,以便從 Wasm 模組外部呼叫,通常是從執行 Wasm 執行時的主應用程式中呼叫。此指令指示編譯器在生成的 Wasm 二進位制檔案中將帶註解的函式作為 Wasm 匯出 (export) 提供。
要使用 go:wasmexport
指令,只需將其新增到函式定義中
//go:wasmexport add
func add(a, b int32) int32 { return a + b }
這樣,Wasm 模組將擁有一個名為 add
的匯出函式,該函式可以從主機呼叫。
這類似於 cgo 的 export
指令,後者使函式可以從 C 呼叫,但 go:wasmexport
使用的是一種不同的、更簡單的機制。
構建 WASI Reactor
WASI reactor 是一種持續執行的 WebAssembly 模組,可以被多次呼叫以響應事件或請求。與在主函式完成後終止的“command”模組不同,reactor 例項在初始化後保持活動狀態,並且其匯出保持可訪問。
使用 Go 1.24,可以使用 -buildmode=c-shared
構建標誌來構建 WASI reactor。
$ GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o reactor.wasm
構建標誌指示連結器不要生成 _start
函式(command 模組的入口點),而是生成一個 _initialize
函式,該函式執行執行時和包的初始化,以及任何匯出函式及其依賴項。必須在呼叫任何其他匯出函式之前呼叫 _initialize
函式。main
函式不會自動呼叫。
要使用 WASI reactor,主機應用程式首先透過呼叫 _initialize
來初始化它,然後只需呼叫匯出函式即可。以下是一個使用 Wazero(一個基於 Go 的 Wasm 執行時實現)的示例
// Create a Wasm runtime, set up WASI.
r := wazero.NewRuntime(ctx)
defer r.Close(ctx)
wasi_snapshot_preview1.MustInstantiate(ctx, r)
// Configure the module to initialize the reactor.
config := wazero.NewModuleConfig().WithStartFunctions("_initialize")
// Instantiate the module.
wasmModule, _ := r.InstantiateWithConfig(ctx, wasmFile, config)
// Call the exported function.
fn := wasmModule.ExportedFunction("add")
var a, b int32 = 1, 2
res, _ := fn.Call(ctx, api.EncodeI32(a), api.EncodeI32(b))
c := api.DecodeI32(res[0])
fmt.Printf("add(%d, %d) = %d\n", a, b, c)
// The instance is still alive. We can call the function again.
res, _ = fn.Call(ctx, api.EncodeI32(b), api.EncodeI32(c))
fmt.Printf("add(%d, %d) = %d\n", b, c, api.DecodeI32(res[0]))
go:wasmexport
指令和 reactor 構建模式允許透過呼叫基於 Go 的 Wasm 程式碼來擴充套件應用程式。這對於已將 Wasm 作為具有明確定義的介面的外掛或擴充套件機制的應用尤其有價值。透過匯出 Go 函式,應用程式可以利用 Go Wasm 模組來提供功能,而無需重新編譯整個應用程式。此外,構建為 reactor 可確保匯出函式可以被多次呼叫而無需重新初始化,使其適用於長時間執行的應用程式或服務。
在主機和客戶端之間支援豐富的型別
Go 1.24 還放寬了 go:wasmimport
函式的輸入和結果引數可以使用的型別限制。例如,您可以傳遞一個 bool、一個 string、一個指向 int32
的指標,或者一個指向嵌入了 structs.HostLayout
幷包含支援的欄位型別的結構體的指標(有關詳細資訊,請參閱文件)。這使得 Go Wasm 應用程式的編寫方式更加自然和符合人體工程學,並消除了不必要的型別轉換。
侷限性
雖然 Go 1.24 對其 Wasm 功能進行了重大增強,但仍然存在一些顯著的限制。
Wasm 是一個單執行緒架構,沒有並行性。go:wasmexport
函式可以生成新的 goroutine。但是,如果一個函式建立了一個後臺 goroutine,那麼在 go:wasmexport
函式返回之前,它不會繼續執行,直到再次呼叫基於 Go 的 Wasm 模組。
雖然 Go 1.24 中放寬了一些型別限制,但在 go:wasmimport
和 go:wasmexport
函式中可使用的型別仍然存在限制。由於客戶端的 64 位架構和主機的 32 位架構之間不幸的不匹配,無法在記憶體中傳遞指標。例如,go:wasmimport
函式不能接受包含指標型別欄位的結構體的指標。
結論
Go 1.24 中增加了構建 WASI reactor 和將 Go 函式匯出到 Wasm 的能力,這標誌著 Go 在 WebAssembly 功能方面邁出了重要一步。這些功能使使用者能夠建立更通用、更強大的基於 Go 的 Wasm 應用程式,從而為 Go 在 Wasm 生態系統中開闢了新的可能性。
下一篇文章: 使用 testing/synctest 測試併發程式碼
上一篇文章: Go 1.24 釋出!
部落格索引