> Source: https://www.ymotongpoo.com/works/goblog-ja/wasi/


# GoにおけるWASIサポート

[WASI support in Go](https://go.dev/blog/wasi) by Johan Brandhorst-Satzkorn, Julien Fabre, Damian Gryski, Evan Phoenix, and Achille Roussel

Go 1.21では、新しい `GOOS` の値 `wasip1` を通じて、WASI preview 1のシステムコールAPIをターゲットとする新しいポートが追加されました。
このポートは、Go 1.11で導入された既存のWebAssemblyポートの上に構築されています。

## WebAssemblyとは何か

[WebAssembly (Wasm)](https://webassembly.org/) は、もともとWeb向けに設計されたバイナリ命令フォーマットです。
これは、開発者が高性能かつ低レベルなコードをWebブラウザ内でネイティブに近い速度で直接実行できるようにする標準規格です。

Goは1.11リリースで `js/wasm` ポートを通じて、Wasmへのコンパイルを初めてサポートしました。
これにより、Goコンパイラでコンパイルされたコードは、JavaScriptの実行環境を必要とするものの、Webブラウザ内で実行できるようになりました。

Wasmの利用が広がるにつれて、ブラウザ以外でのユースケースも増えてきました。多くのクラウドプロバイダーが、
新しい[WebAssembly System Interface (WASI)](https://wasi.dev/)のシステムコールAPIを活用して、
ユーザーがWasm実行ファイルを直接実行できるようにするサービスを提供するようになっています。

## WebAssembly System Interface

WASIは、Wasm実行ファイル向けのシステムコールAPIを定義しており、ファイルシステム、システムクロック、乱数生成ユーティリティといった
システムリソースとやりとりできるようにします。WASI仕様の最新リリースは `wasi_snapshot_preview1` と呼ばれており、
ここから `GOOS` の名前である `wasip1` が導かれています。このAPIの新しいバージョンが開発中であり、将来Goコンパイラでそれらを
サポートする場合は、おそらく新しい `GOOS` の追加という形になるでしょう。

WASIが生まれたことで、多くのWasmランタイム（ホスト）がそのシステムコールAPIを軸に標準化できるようになりました。
WasmやWASIに対応したホストの例としては、[Wasmtime](https://wasmtime.dev)、[Wazero](https://wazero.io/)、
[WasmEdge](https://wasmedge.org/)、[Wasmer](https://wasmer.io/)、[NodeJS](https://nodejs.org)などが挙げられます。
WasmやWASIの実行ファイルをホスティングするクラウドプロバイダーも数多く存在します。

## Goでどのように使うか

Go 1.21以上がインストールされていることを確認してください。このデモでは、バイナリの実行に
[Wasmtimeホスト](https://docs.wasmtime.dev/cli-install.html)を使用します。まずはシンプルな `main.go` から始めましょう。

```go
package main

import "fmt"

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

次のコマンドで `wasip1` 向けにビルドできます。

```shell
$ GOOS=wasip1 GOARCH=wasm go build -o main.wasm main.go
```

これにより `main.wasm` というファイルが生成され、これを `wasmtime` で実行できます。

```shell
$ wasmtime main.wasm
Hello world!
```

WasmとWASIを始めるにはこれだけで十分です！ Goのほとんどすべての機能が `wasip1` でもそのまま動作すると期待できます。
GoでWASIがどのように動作しているかの詳細をより深く学びたい方は、[プロポーザル](https://go.dev/issue/58141)を参照してください。

## wasip1でgo testを実行する

> Go 1.24ではWasmサポート用のファイルが `lib/wasm` に移動しました。Go 1.21から1.23までは `misc/wasm` ディレクトリを使用してください。

バイナリのビルドと実行は簡単ですが、バイナリを手動でビルドして実行しなくても直接 `go test` を実行できると便利な場合があります。
`js/wasm` ポートと同様に、Goのインストールに含まれる標準ライブラリの配布物には、これを簡単にするファイルが同梱されています。
Goのテストを実行する際に `lib/wasm` ディレクトリを `PATH` に追加すれば、選択したWasmホストを使ってテストを実行できます。
これは、`go test` が `PATH` 内にこのファイルを見つけると、[`lib/wasm/go_wasip1_wasm_exec` を自動的に実行する](https://pkg.go.dev/cmd/go#hdr-Compile_and_run_Go_program)
という仕組みによって実現されています。

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

これにより、Wasmtimeを使って `go test` が実行されます。使用するWasmホストは、環境変数 `GOWASIRUNTIME` で制御できます。
現在この変数でサポートされている値は `wazero`、`wasmedge`、`wasmtime`、`wasmer` です。このスクリプトはGoのバージョン間で
破壊的な変更が入る可能性があります。なお、Goの `wasip1` バイナリは、まだすべてのホスト上で完璧に動作するわけではないことに
注意してください（[#59907](https://go.dev/issue/59907)と[#60097](https://go.dev/issue/60097)を参照）。

この機能は `go run` を使う場合にも動作します。

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

## go:wasmimportでWasm関数をGoでラップする

新しい `wasip1/wasm` ポートに加えて、Go 1.21では新しいコンパイラディレクティブ `go:wasmimport` が導入されました。
これは、注釈が付けられた関数への呼び出しを、ホストのモジュール名と関数名で指定された関数への呼び出しに変換するよう
コンパイラに指示するものです。この新しいコンパイラの機能によって、新しいポートをサポートするために標準ライブラリ内で
`wasip1` のシステムコールAPIを定義できるようになりましたが、これは標準ライブラリだけに限定して使われるものではありません。

たとえば、wasip1のシステムコールAPIは[`random_get` 関数](https://github.com/WebAssembly/WASI/blob/a51a66df5b1db01cf9e873f5537bc5bd552cf770/legacy/preview1/docs.md#-random_getbuf-pointeru8-buf_len-size---result-errno)
を定義しており、これはruntimeパッケージ内で定義された[関数ラッパー](https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/runtime/os_wasip1.go;l=73-75)
を通じてGoの標準ライブラリに公開されています。これは次のようになっています。

```go
//go:wasmimport wasi_snapshot_preview1 random_get
//go:noescape
func random_get(buf unsafe.Pointer, bufLen size) errno
```

この関数ラッパーは、さらに標準ライブラリ内で使うために、[より扱いやすい関数](https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/runtime/os_wasip1.go;l=183-187)
でラップされています。

```go
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` のプロポーザル](https://go.dev/issue/59149)を参照してください。

## 制限事項

`wasip1` ポートは標準ライブラリのすべてのテストに合格していますが、Wasmアーキテクチャには、ユーザーを驚かせるかもしれない
いくつかの根本的な制限が存在します。

Wasmは並列処理のないシングルスレッドのアーキテクチャです。スケジューラはゴルーチンを並行に実行するようスケジュールできますし、
標準入力や標準出力、標準エラー出力はノンブロッキングなので、あるゴルーチンが読み書きしている間に別のゴルーチンを実行できます。
しかし、（上記の例のように乱数データを要求するといった）ホスト関数の呼び出しが発生すると、その呼び出しが返るまで
すべてのゴルーチンがブロックされます。

`wasip1` APIに顕著に欠けている機能として、ネットワークソケットの完全な実装が挙げられます。`wasip1` はすでに開かれている
ソケットに対して操作を行う関数のみを定義しているため、HTTPサーバーのようなGo標準ライブラリの中でも特に人気の高い機能の
一部をサポートできません。WasmerやWasmEdgeのようなホストは、ネットワークソケットを開けるようにする
`wasip1` APIの拡張を実装しています。これらの拡張はGoコンパイラでは実装されていませんが、サードパーティのライブラリである
[`github.com/stealthrocket/net`](https://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との互換性についての
提案が今後出てくるかもしれないので、[イシュートラッカー](https://github.com/golang/go/issues?q=is%3Aopen+is%3Aissue+label%3Aarch-wasm)
に注目しておいてください。

## 参加するには

WasmとGoに関して実験していて、貢献したいと考えている方は、ぜひ参加してください！ Goのイシュートラッカーでは進行中の
作業すべてを追跡していますし、[Gophers Slack](https://invite.slack.golangbridge.org/)の#webassemblyチャンネルは、
GoとWebAssemblyについて議論する絶好の場所です。皆さんからの連絡をお待ちしています！

By Johan Brandhorst-Satzkorn, Julien Fabre, Damian Gryski, Evan Phoenix, and Achille Roussel

