Go 中的 WASI 支持

Go 1.21 通过 新增功能 GOOS 价值 wasip1.此端口基于现有的 WebAssembly 构建 Go 1.11 中引入了 port。

约翰·布兰德霍斯特-萨茨科恩、朱利安·法布尔、达米安·格里斯基、埃文·菲尼克斯和阿奇尔·鲁塞尔 


什么是WebAssembly?

WebAssembly(Wasm) 是一种二进制指令格式 最初是为网络设计的。它代表了一个标准,允许 开发人员直接在 Web 浏览器中运行高性能、低级代码 接近原生的速度。

Go 首先在 1.11 版本中添加了对编译到 Wasm 的支持,通过 js/wasm 港口。这允许使用 Go 编译器编译的 Go 代码 在 Web 浏览器中执行,但它需要一个 JavaScript 执行环境。

随着 Wasm 使用的增长,浏览器之外的用例也在增长。多 云提供商现在提供的服务允许用户执行Wasm。 直接可执行文件,利用新的 WebAssembly System Interface (WASI) syscall API.

WebAssembly 系统接口

WASI 为 Wasm 可执行文件定义了一个系统调用 API,允许它们与 系统资源,如文件系统、系统时钟、随机数据 公用事业等等。WASI规范的最新版本称为 wasi_snapshot_preview1,我们从中推导出 GOOS 名字 wasip1.新增功能 API的版本正在开发中,并在Go中支持它们 未来的编译器可能意味着添加一个新的 GOOS.

WASI的创建允许许多Wasm运行时(主机)能够 围绕它标准化他们的系统调用 API。Wasm/WASI 主机的示例包括 瓦斯姆泰姆 , ,瓦泽罗 WasmEdge Wasmer NodeJS. 还有许多云提供商 提供Wasm/WASI可执行文件的托管。

我们如何在 Go 中使用它?

确保您至少安装了 Go 的 1.21 版。对于此演示, 我们将使用 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 目录到您的 PATH 运行 Go 测试时,它会 使用您选择的 Wasm 主机运行测试。这工作由 go test 自动执行 misc/wasm/go_wasip1_wasm_exec 当它在 PATH.

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

这将运行 go test 使用Wasmtime。可以控制使用的Wasm主机 使用环境变量 GOWASIRUNTIME .当前支持的值 对于此变量是 wazero , wasmedge , wasmtime 和 wasmer .这 脚本可能会在 Go 版本之间进行重大更改。请注意,Go wasip1 二进制文件尚未在所有主机上完美执行(请参阅 #59907 #60097 )。

此功能在使用时也有效 go run:

$ GOOS=wasip1 GOARCH=wasm go run ./main.go
Hello world!

使用 go:wasmimport 在 Go 中包装 Wasm 函数

除了新的 wasip1/wasm port, Go 1.21 引入了一个新的编译器 命令: go:wasmimport.它指示编译器将调用转换为 将带批注的函数转换为对主机指定的函数的调用 模块名称和函数名称。这个新的编译器功能是允许的 我们来定义 wasip1 Go 中的系统调用 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是一个没有并行性的单线程架构。调度程序可以 仍然安排 goroutines 并发运行,标准输入/输出/错误是 非阻塞,因此一个 goroutine 可以在另一个读取或写入时执行,但任何 主机函数调用(例如使用上述示例请求随机数据) 将导致所有 GoRoutines 阻塞,直到主机函数调用返回。

一个值得注意的缺失功能 wasip1 API 是 网络套接字。 wasip1 仅定义对已打开的操作的函数 套接字,使其无法支持一些最流行的功能 Go 标准库,例如 HTTP 服务器。像Wasmer和WasmEdge这样的主机 实现对 wasip1 API,允许开放网络 插座。虽然这些扩展不是由 Go 编译器实现的,但 存在第三方库, github.com/stealthrocket/net哪 使用 go:wasmimport 以允许使用 net.Dial 和 net.Listen 上 支持的 Wasm 主机。这样可以创建 net/http 服务器和其他 使用此软件包时与网络相关的功能。

围棋中瓦斯姆的未来

添加 wasip1/wasm 港口只是瓦斯姆的开始 我们希望为 Go 带来的能力。请留意 问题跟踪器 有关将 Go 函数导出到 Wasm 的建议( go:wasmexport),一个 32 位 端口和未来的 WASI API 兼容性。

参与其中

如果您正在尝试并希望为 Wasm 和 Go 做出贡献,请获得 涉及!Go 问题跟踪器跟踪所有正在进行的工作和 #webassembly 地鼠松弛上的 频道是一个 讨论 Go 和 WebAssembly 的好地方。我们期待您的来信! 

© GVGNN 2013-2026