原文在这里[1]。
由 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)[2]是一种最初设计用于Web的二进制指令格式。它代表了一个标准,允许开发人员在Web浏览器中以接近本机速度直接运行高性能、低级别的代码。
Go首次在1.11版本中添加了对编译成Wasm的支持,通过js/wasm端口实现。这允许使用Go编译器编译的Go代码在Web浏览器中执行,但需要一个JavaScript执行环境。
随着Wasm的使用增加,除了在浏览器之外的用例也增多。许多云提供商现在提供服务,允许用户直接执行Wasm可执行文件,利用新的WebAssembly系统接口(WASI)[3]系统调用API。
WebAssembly 系统接口
WASI定义了一个用于Wasm可执行文件的系统调用API,允许它们与系统资源进行交互,如文件系统、系统时钟、随机数据工具等等。WASI规范的最新版本被称为wasi_snapshot_preview1,从中我们派生出了GOOS名称wasip1。新版本的API正在开发中,未来在Go编译器中支持它们可能意味着添加一个新的GOOS。
WASI的创建使得许多Wasm运行时(宿主)能够围绕其标准化它们的系统调用API。一些Wasm/WASI宿主的示例包括Wasmtime[4]、Wazero[5]、WasmEdge[6]、Wasmer[7]和NodeJS[8]。还有许多云提供商提供Wasm/WASI可执行文件的托管服务。
Go 中如何使用 WebAssembly
请确保已安装至少1.21版本的Go。对于此演示,我们将使用Wasmtime主机[9]来执行我们的二进制文件。让我们从一个简单的 main.go
开始:
package main
import "fmt"
func main() {
fmt.Println("Hello world!")
}
使用如下命令进行编译:
代码语言:javascript复制$ 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一起工作的详细信息,请参阅提案[10]。
测试 wasip1
构建和运行二进制文件很容易,但有时我们希望能够直接运行 go test
,而无需手动构建和执行二进制文件。与 js/wasm
端口类似,Go安装中包含的标准库分发版本附带一个文件,使这个过程变得非常简单。在运行Go测试时,将 misc/wasm
目录添加到 PATH
中,它将使用你选择的Wasm主机来运行测试。这是通过 go test
在PATH中找到此文件时自动执行[11] 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[12]和#60097[13])。
也可以使用 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
函数[14],并通过runtime包中定义的函数包装器[15]暴露给Go标准库。它看起来像这样:
//go:wasmimport wasi_snapshot_preview1 random_get
//go:noescape
func random_get(buf unsafe.Pointer, bufLen size) errno
然后,将这个函数包装器包装在标准库中供使用的更人性化的函数[16]中:
代码语言:javascript复制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提案[17]。
局限性
虽然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功能的开端。请密切关注问题跟踪器[18],了解有关将Go函数导出到Wasm(go:wasmexport)、32位端口和未来WASI API兼容性的提案。
参与其中
如果你正在尝试并希望为Wasm和Go做出贡献,请参与其中!Go问题跟踪器跟踪所有正在进行的工作,Gophers Slack[19]上的 #webassembly 频道是讨论Go和WebAssembly的好地方。我们期待听到你的声音!
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)[20]进行许可,使用时请注明出处。 Author: mengbin[21] blog: mengbin[22] Github: mengbin92[23] cnblogs: 恋水无意[24]