Goブログ

GoにおけるWASIサポート

Johan Brandhorst-Satzkorn、Julien Fabre、Damian Gryski、Evan Phoenix、Achille Roussel
2023年9月13日

Go 1.21では、新しいGOOSwasip1を通じてWASIプレビュー1システムコールAPIをターゲットとする新しいポートが追加されました。このポートは、Go 1.11で導入された既存のWebAssemblyポートを基盤としています。

WebAssemblyとは?

WebAssembly (Wasm)は、もともとWeb用に設計されたバイナリ命令フォーマットです。これは、開発者がWebブラウザ内でネイティブに近い速度で高性能な低レベルコードを直接実行できるようにする標準を表します。

Goは、1.11リリースでjs/wasmポートを通じて、Wasmへのコンパイルのサポートを最初に追加しました。これにより、Goコンパイラを使用してコンパイルされたGoコードをWebブラウザで実行できましたが、JavaScript実行環境が必要でした。

Wasmの利用が拡大するにつれて、ブラウザ外でのユースケースも増えてきました。多くのクラウドプロバイダーは現在、新しいWebAssembly System Interface (WASI)システムコールAPIを活用して、Wasm実行可能ファイルを直接実行できるようにするサービスを提供しています。

WebAssembly System Interface

WASIは、Wasm実行可能ファイルがファイルシステム、システムクロック、乱数データユーティリティなどのシステムリソースと対話できるようにするシステムコールAPIを定義します。WASI仕様の最新リリースはwasi_snapshot_preview1と呼ばれ、ここからGOOSwasip1を導き出しました。新しいバージョンのAPIが開発されており、将来Goコンパイラでそれらをサポートするには、新しいGOOSを追加する必要があるでしょう。

WASIの作成により、多くのWasmランタイム(ホスト)がそのシステムコールAPIを標準化できるようになりました。Wasm/WASIホストの例としては、WasmtimeWazeroWasmEdgeWasmerNodeJSがあります。また、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 1.24では、Wasmサポートファイルがlib/wasmに移動しました。Go 1.21〜1.23の場合は、misc/wasmディレクトリを使用してください。

バイナリのビルドと実行は簡単ですが、手動でバイナリをビルドして実行することなく、直接go testを実行できると便利な場合があります。js/wasmポートと同様に、Goインストールに含まれる標準ライブラリディストリビューションには、これを非常に簡単にするファイルが付属しています。Goテストを実行するときにlib/wasmディレクトリをPATHに追加すると、選択したWasmホストを使用してテストが実行されます。これは、go testPATHでこのファイルを見つけると、自動的にlib/wasm/go_wasip1_wasm_execを実行することで機能します。

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

これにより、Wasmtimeを使用してgo testが実行されます。使用するWasmホストは、環境変数GOWASIRUNTIMEを使用して制御できます。現在、この変数でサポートされている値は、wazerowasmedgewasmtime、およびwasmerです。このスクリプトは、Goバージョン間で変更される可能性があります。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関数を定義しており、これはランタイムパッケージで定義された関数ラッパーを通じて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が実行できますが、ホスト関数呼び出し(上記の例を使用してランダムデータを要求するなど)を行うと、ホスト関数呼び出しが返されるまですべてのgoroutineがブロックされます。

wasip1 APIの注目すべき欠落機能は、ネットワークソケットの完全な実装です。wasip1は、すでに開いているソケットで動作する関数のみを定義しており、HTTPサーバーなどのGo標準ライブラリの最も人気のある機能の一部をサポートすることが不可能になっています。WasmerやWasmEdgeなどのホストは、ネットワークソケットのオープンを可能にするwasip1 APIの拡張機能を実装しています。これらの拡張機能はGoコンパイラによって実装されていませんが、サードパーティライブラリであるgithub.com/stealthrocket/netが存在し、go:wasmimportを使用して、サポートされているWasmホストでnet.Dialnet.Listenを使用できるようにします。これにより、このパッケージを使用する場合にnet/httpサーバーやその他のネットワーク関連機能を作成できます。

GoにおけるWasmの将来

wasip1/wasmポートの追加は、Goに導入したいWasm機能の始まりにすぎません。WasmへのGo関数のエクスポート(go:wasmexport)、32ビットポート、および将来のWASI APIの互換性に関する提案については、issue trackerに注目してください。

参加する

WasmとGoを試していて貢献したい場合は、ぜひご参加ください!Go issue trackerでは、進行中のすべての作業を追跡し、Gophers Slackの#webassemblyチャネルは、GoとWebAssemblyについて議論するのに最適な場所です。皆様からのご連絡をお待ちしております!

次の記事:Go 1.22でのForループの修正
前の記事:成長するGoエコシステムに対応したgoplsのスケーリング
ブログインデックス