😎

GoとWebAssembly

2023/12/10に公開

この記事はWebAssembly Advent Calendar 2023 10日目の記事です.


2023年はGo1.21でWASI(WebAssembly System Interface)がサポートされるといった大きなリリースがありました.Go1.21のWasmのサポート状況,今後のWasmサポートについてまとめていきたいと思います.

Go 1.20以前とWebAssembly

2018年のGo 1.11のリリース[1]以降,GoではWasmがサポートされています.

package main

import "fmt"

func main() {
    fmt.Println("Hello, WebAssembly!")
}

GOOS=jsGOARCH=wasmを指定してビルドすると,Wasmにビルドすることができます.

GOOS=js GOARCH=wasm go build -o main.wasm main.go

このビルドされたWasmを実行するには,Goが提供しているJSのグルーコードを利用します.

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

main.wasmwasm_exec.jsを準備できたら,Wasmを実行するための起動スクリプト(JS)を書きます.

import "./wasm_exec.js";

const path = new URL("main.wasm", import.meta.url);

const go = new Go();
const { instance } = await WebAssembly.instantiateStreaming(
  fetch(path),
  go.importObject,
);
go.run(instance);

ここまで準備ができると,ウェブ上でGoからビルドしたWasmを利用することができました.
(ここでは Deno を使った実行例を示します.)

deno run --allow-read main.js

Go 1.21とWebAssembly

Go1.21では,Wasmについて2つの機能が入りました.

  1. WASI(プレビュー1)[2] のサポート
  2. go:wasmimport コメントの追加

WASI

WASI は Wasm をブラウザ以外でも利用しようという動きから策定されている システムインターフェースの仕様です.
GOOS=wasip1GOARCH=wasmを指定することで,WASIに対応したWasmにビルドすることができます.

GOOS=wasip1 GOARCH=wasm go build -o main.wasi.wasm main.go

WASI版のWasmは,WASIを実装している Wasmtime[3]などのランタイムを用いて実行することができます.

wasmtime main.wasi.go

WASIについては,技術書典15にて解説本を出していますので,興味があればこちらも読んでもらえると良いかもしれません.

https://techbookfest.org/product/acW1EbS9XxshmBnDUeZtxj?productVariantID=xyfxeWNuEVQiEzwhnYhMgV

「WASIを誰が使うんだ?」と思うかもしれませんが,コンテナのイメージファイルとして利用などが期待されています.

go:wasmimport

WASIと合わせて go:wasmimport というビルドコメントが追加されました.
この go:wasmimport は,GoがWasmをサポートするために用意されている特別なコメントで,次のように書くことができます.

package main

import "fmt"

//go:wasmimport env fib
func fib(n int32) int32

func main() {
    fmt.Println(fib(10))
}
import "./wasm_exec.js";

const path = new URL("fib.wasm", import.meta.url);

const go = new Go();
const { instance } = await WebAssembly.instantiateStreaming(
  fetch(path),
  {
    env: {
      fib: function fib(n) {
        if (n < 2) {
          return n;
        }
        return fib(n - 1) + fib(n - 2);
      },
    },
    ...go.importObject,
  },
);
go.run(instance);

このように,JSから実行時に直接関数をGoにインポートすることができます.
実際に,WASIのサポートなどでGoの標準パッケージで多用されている[4]ため,興味があれば探してみると面白いかもしれません.

この go:wasmimportAsakusa.go #1のLTでも紹介しました.

Go1.22以降とWebAssembly

Goが今後Wasmをどうサポートしていくかについてですが,label:arch-wasm が付与されているIssue[5]を見ることで動向を知ることができます.
いくつか気になるものを紹介します.

GOARCH=wasm32

https://github.com/golang/go/issues/63131

現在Wasmの GOARCHwasm のみですが,将来に備えて wasm32 を追加しようというものです.
Wasmのメモリは32ビットのアドレス空間のため,4GB以上のデータを扱えません.4GB以上のデータを扱いたいということで,64ビットのアドレス空間のメモリを追加しようというプロポーザルがWasmには存在します.
そういった背景もあり,将来的に wasm64が追加される可能性もあるため,wasm32を追加しましょうというものです.
Goの慣例的にwasmだけだと64ビットだと思ってしまう,というのもあります.

//go:wasmexport

https://github.com/golang/go/issues/42372

1.21でgo:wasmimportが追加されましたが,これは関数をexportしたい,というものです.
Wasmにはimportexportが定義されているため,Wasmを使っていると自然に「欲しいよね」となる機能です.

//go:wasmexport hello_world
func helloWorld() {
  println("Hello world!")
}

一般的に,Wasmのexportを利用する場合は次のように記述することができます.

const { instance } = await WebAssembly.instantiateStreaming(fetch(new URL("hello.wasm", import.meta.url)));

instance.exports.hello_world();

Wasmのライブラリモジュールを作る場面ではかなり有用なexportではあるのですが,GO自体が基本的に実行ファイルしか作らないので,exportしてそれを外から呼び出せるようにするのか,その場合のスレッド管理がどうなるのか,などなど課題は多そうなものの何かしら動きがありそうなので注目したいプロポーザルです.

WASM-GC

https://github.com/golang/go/issues/63904

WasmGC,みんな気になりますよね.
WasmGCって何?という疑問については下記の記事をお勧めします.

https://zenn.dev/askua/articles/afe3a3b43b82cb

https://zenn.dev/tanishiking/articles/learn-wasm-gc

WasmGCに対応したいというモチベーションはありそうだけれども,Goが扱っているデータ型をWasmGCのデータ型に合わせるようにする必要があるので修正が大きくなることなど,実装するまでに議論するべき課題がまだ多いようようです.

thread support for webassembly

https://github.com/golang/go/issues/28631

Wasmのスレッドをサポートしようというもの.
Wasmのスレッドのプロポーザル[6]の内容としては,Wasm自体にスレッド制御する命令を追加するのではなくスレッド制御はWeb Workerとかがやるから,アトミックなメモリ操作を追加しようぜ,というもののようです.

Goにどのように組み込まれるのかという議論はまだされていないようなので,これからどうするのかといった議論がされるかもしれない,という印象です.
実装するとなるとどうなるのでしょう? Goroutine に Web Workerを利用しよう,などそういう話になるんでしょうか?

その他

Issueとしてはなさそうですが,WASIのプレビュー2[7]に対する言及も GOARCH=wasm32の検討の中で存在しているので,将来的にIssueが出てくるのではないかと期待しています.

まとめ

興味あるIssueを幾つかピックアップしてみました.
WasmGCやらThreadsなどは議論することが多かったりと,すぐにこれらがGoに入るのはもう少し先なのかなという印象です.
個人的にはgo:wasmexportの提案をコミュニティはどういう方向に持っていくのか,というのが一番気になっています.

今後のWasmのサポートが楽しみですね🌲

脚注
  1. https://go.dev/doc/go1.11#wasm ↩︎

  2. https://wasi.dev/ ↩︎

  3. https://github.com/bytecodealliance/wasmtime ↩︎

  4. https://github.com/golang/go/blob/5e724ccb2b092a6bc294b63c4e33ed1da317ca87/src/syscall/fs_wasip1.go#L176-L180 ↩︎

  5. https://github.com/golang/go/issues?q=is%3Aissue+label%3Aarch-wasm+is%3Aopen ↩︎

  6. https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md ↩︎

  7. https://github.com/WebAssembly/WASI/tree/main/preview2 ↩︎

Discussion