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


# Goで実現する拡張可能なWasmアプリケーション

[Extensible Wasm Applications with Go](https://go.dev/blog/wasmexport) by Cherry Mui

Go 1.24では、`go:wasmexport` ディレクティブの追加とWebAssembly System Interface（WASI）向けのリアクターをビルドできる機能によって、WebAssembly（Wasm）対応が強化されました。
これらの機能により、Goの開発者はGo関数をWasmへエクスポートできるようになり、Wasmホストとのより良い統合が可能になるとともに、GoベースのWasmアプリケーションの可能性が広がります。

## WebAssemblyとWebAssembly System Interface

[WebAssembly (Wasm)](https://webassembly.org/) は、もともとWebブラウザ向けに作られたバイナリ命令フォーマットで、ネイティブに近い速度で高性能かつ低レベルなコードを実行できます。
その後Wasmの用途は広がり、現在ではブラウザ以外のさまざまな環境でも使われています。
特にクラウドプロバイダーは、[WebAssembly System Interface (WASI)](https://wasi.dev/) というシステムコールAPIを活用して、Wasm実行ファイルを直接実行するサービスを提供しています。
WASIによって、これらの実行ファイルはシステムリソースとやり取りできるようになります。

Goは1.11リリースで `js/wasm` ポートを通じて、Wasmへのコンパイルサポートを初めて追加しました。
Go 1.21では、新しい `GOOS=wasip1` ポートを通じて、WASI preview 1のシステムコールAPIをターゲットとする新しいポートが追加されました。

## `go:wasmexport` によるGo関数のWasmへのエクスポート

Go 1.24では新しいコンパイラディレクティブ `go:wasmexport` が導入されました。
これにより開発者は、Wasmモジュールの外部（典型的にはWasmランタイムを実行するホストアプリケーション）から呼び出せるようにGo関数をエクスポートできます。
このディレクティブは、注釈を付けた関数を、生成されるWasmバイナリの中でWasmの[エクスポート](https://webassembly.github.io/spec/core/valid/modules.html?highlight=export#exports)として利用可能にするようコンパイラに指示します。

`go:wasmexport` ディレクティブを使うには、関数定義に単純に追加するだけです。

```go
//go:wasmexport add
func add(a, b int32) int32 { return a + b }
```

これで、Wasmモジュールにはホストから呼び出せる `add` という名前のエクスポート関数ができます。

これは、C言語から呼び出せるように関数を公開する[cgoの `export` ディレクティブ](/cmd/cgo#hdr-C_references_to_Go)に似ていますが、 `go:wasmexport` は異なる、よりシンプルな仕組みを使っています。

## WASIリアクターを作る

WASIリアクターとは、継続的に動作し、イベントやリクエストに反応するために何度も呼び出せるWebAssemblyモジュールです。
main関数が終了すると終了する「コマンド」モジュールとは異なり、リアクターのインスタンスは初期化後も生き続け、そのエクスポートはアクセス可能なままになります。

Go 1.24では、 `-buildmode=c-shared` ビルドフラグを使ってWASIリアクターをビルドできます。

```
$ GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o reactor.wasm
```

このビルドフラグは、リンカーに対して（コマンドモジュールのエントリポイントである）`_start` 関数を生成しないように指示し、代わりにランタイムとパッケージの初期化を行う `_initialize` 関数と、エクスポートされた関数およびその依存関係を生成するように指示します。
`_initialize` 関数は、他のどのエクスポート関数よりも先に呼び出す必要があります。
`main` 関数は自動的には呼び出されません。

WASIリアクターを使うには、ホストアプリケーションはまず `_initialize` を呼び出して初期化を行い、その後エクスポートされた関数を呼び出すだけです。
以下は、Go製のWasmランタイム実装である[Wazero](https://wazero.io/)を使った例です。

```go
// Wasmランタイムを作成し、WASIをセットアップする。
r := wazero.NewRuntime(ctx)
defer r.Close(ctx)
wasi_snapshot_preview1.MustInstantiate(ctx, r)

// リアクターを初期化するようにモジュールを設定する。
config := wazero.NewModuleConfig().WithStartFunctions("_initialize")

// モジュールをインスタンス化する。
wasmModule, _ := r.InstantiateWithConfig(ctx, wasmFile, config)

// エクスポートされた関数を呼び出す。
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)

// インスタンスはまだ生きている。もう一度関数を呼び出せる。
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` ディレクティブとリアクタービルドモードにより、GoベースのWasmコードを呼び出すことでアプリケーションを拡張できるようになります。
これは、明確に定義されたインターフェースを持つプラグインや拡張の仕組みとしてWasmを採用しているアプリケーションにとって、特に価値があります。
Go関数をエクスポートすることで、アプリケーション全体を再コンパイルすることなく、Go製のWasmモジュールを利用して機能を提供できます。
さらに、リアクターとしてビルドすることで、再初期化を必要とせずにエクスポートされた関数を何度も呼び出せるようになり、長時間稼働するアプリケーションやサービスに適したものになります。

## ホストとクライアント間でのリッチな型のサポート

Go 1.24では、 `go:wasmimport` 関数の入力および結果のパラメーターとして使える型の制約も緩和されました。
たとえば、bool、string、 `int32` へのポインタ、あるいは `structs.HostLayout` を埋め込み、サポート対象のフィールド型を含む構造体へのポインタを渡せるようになりました（詳細は[ドキュメント](/cmd/compile#hdr-WebAssembly_Directives)を参照してください）。
これにより、GoのWasmアプリケーションをより自然で扱いやすい形で書けるようになり、不要な型変換の一部を取り除けます。

## 制限事項

Go 1.24はWasm機能に大幅な強化をもたらしましたが、依然としていくつかの注目すべき制限が残っています。

Wasmは並列処理のないシングルスレッドアーキテクチャです。
`go:wasmexport` 関数は新しいゴルーチンを起動できます。
ただし、ある関数がバックグラウンドでゴルーチンを生成しても、その `go:wasmexport` 関数がリターンした後は、GoベースのWasmモジュールに再度呼び出しが行われるまで、そのゴルーチンの実行は継続しません。

Go 1.24では一部の型の制約が緩和されましたが、 `go:wasmimport` と `go:wasmexport` 関数で使える型には依然として制限があります。
クライアント側の64ビットアーキテクチャとホスト側の32ビットアーキテクチャの間に不幸なミスマッチがあるため、メモリ内のポインタを渡すことはできません。
たとえば、 `go:wasmimport` 関数は、ポインタ型のフィールドを含む構造体へのポインタを引数に取ることはできません。

## 結論

Go 1.24でWASIリアクターをビルドする機能とGo関数をWasmへエクスポートする機能が追加されたことは、GoのWebAssembly機能にとって大きな前進です。
これらの機能により、開発者はより多様で強力なGoベースのWasmアプリケーションを作成できるようになり、Wasmエコシステムの中でGoの新たな可能性が広がります。

By Cherry Mui

