GoとWebAssembly
この記事は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=js
とGOARCH=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.wasm
とwasm_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つの機能が入りました.
- WASI(プレビュー1)[2] のサポート
-
go:wasmimport
コメントの追加
WASI
WASI は Wasm をブラウザ以外でも利用しようという動きから策定されている システムインターフェースの仕様です.
GOOS=wasip1
とGOARCH=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にて解説本を出していますので,興味があればこちらも読んでもらえると良いかもしれません.
「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:wasmimport
はAsakusa.go #1のLTでも紹介しました.
Go1.22以降とWebAssembly
Goが今後Wasmをどうサポートしていくかについてですが,label:arch-wasm
が付与されているIssue[5]を見ることで動向を知ることができます.
いくつか気になるものを紹介します.
GOARCH=wasm32
現在Wasmの GOARCH
は wasm
のみですが,将来に備えて wasm32
を追加しようというものです.
Wasmのメモリは32ビットのアドレス空間のため,4GB以上のデータを扱えません.4GB以上のデータを扱いたいということで,64ビットのアドレス空間のメモリを追加しようというプロポーザルがWasmには存在します.
そういった背景もあり,将来的に wasm64
が追加される可能性もあるため,wasm32
を追加しましょうというものです.
Goの慣例的にwasm
だけだと64ビットだと思ってしまう,というのもあります.
//go:wasmexport
1.21でgo:wasmimport
が追加されましたが,これは関数をexport
したい,というものです.
Wasmにはimport
とexport
が定義されているため,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
WasmGC,みんな気になりますよね.
WasmGCって何?という疑問については下記の記事をお勧めします.
WasmGCに対応したいというモチベーションはありそうだけれども,Goが扱っているデータ型をWasmGCのデータ型に合わせるようにする必要があるので修正が大きくなることなど,実装するまでに議論するべき課題がまだ多いようようです.
thread support for webassembly
Wasmのスレッドをサポートしようというもの.
Wasmのスレッドのプロポーザル[6]の内容としては,Wasm自体にスレッド制御する命令を追加するのではなくスレッド制御はWeb Workerとかがやるから,アトミックなメモリ操作を追加しようぜ,というもののようです.
Goにどのように組み込まれるのかという議論はまだされていないようなので,これからどうするのかといった議論がされるかもしれない,という印象です.
実装するとなるとどうなるのでしょう? Goroutine に Web Workerを利用しよう,などそういう話になるんでしょうか?
その他
Issueとしてはなさそうですが,WASIのプレビュー2[7]に対する言及も GOARCH=wasm32
の検討の中で存在しているので,将来的にIssueが出てくるのではないかと期待しています.
まとめ
興味あるIssueを幾つかピックアップしてみました.
WasmGCやらThreadsなどは議論することが多かったりと,すぐにこれらがGoに入るのはもう少し先なのかなという印象です.
個人的にはgo:wasmexport
の提案をコミュニティはどういう方向に持っていくのか,というのが一番気になっています.
今後のWasmのサポートが楽しみですね🌲
Discussion