Go1.24からWasmで使える型が緩和される話
WebAssembly Advent Calendar 2024の1日目の記事になります.
先月(11月2日)から行われていた技術書典17にて,WebAssembly Cookbook vol.2[1] (新刊)を配布しました.
Cookbook vol.2の第1章では,Go1.24から新しくサポートされるgo:wasmexport
ディレクティブについて紹介しています.
go:wasmexport
ディレクティブがサポートされることで,Pure GoでもWASI 0.1のリアクターモジュール[2]を作ることができるようになります.
Wasmで扱える型を緩和するプロポーザル
Go1.24では,go:wasmexport
ディレクティブのサポート以外にも,GoがWasm上で扱える型の緩和も予定されています.
Go1.23でWasmで扱える型の種類
Go1.21からgo:wasmimport
ディレクティブがサポートされ,GoでもJSを介さずに直接Wasmの値を利用することができるようになりましたが,現時点では次の型しが扱うことができません.
Go value | Wasm value |
---|---|
int32 , uint32
|
i32 |
int64 , uint64
|
i64 |
float32 |
f32 |
float64 |
f64 |
unsafe.Pointer |
i32 |
そのため,GoからWasmのインターフェースを経由して文字列を渡そうとした場合,次のようなコードを書く必要があります.
// main.go
package main
import "unsafe"
//go:wasmimport console log
func consoleLog(ptr unsafe.Pointer, len int32)
func main() {
msg := "Hello, 世界!"
ptr := unsafe.Pointer(unsafe.StringData(msg))
len := int32(len(msg))
consoleLog(ptr, len)
}
Goの文字列からポインターと長さを取得し,それらをインポート関数へと渡しています.
このGoのコードからビルドされるWasmを実行するJSのコードは,次のように書くことができます.
// bootstrap.js
import "./wasm_exec.js";
const go = new Go();
const { instance } = await WebAssembly.instantiateStreaming(
fetch(new URL("main.wasm", import.meta.url)),
{
...go.importObject,
console: {
log(ptr, len) {
console.log(
new TextDecoder("utf-8").decode(
new Uint8Array(instance.exports.mem.buffer, ptr, len),
),
);
},
},
},
);
go.run(instance);
JSのコードでは,Wasmから文字列のポインターと長さを受け取り,Wasmのメモリー上から文字列の範囲を取得してJSのstring
にデコードしています.
実際にこのコードを実行すると次のような実行結果を得ることができます.
$ deno run --allow-read bootstrap.js
Hello, 世界!
扱える型の緩和
このようにGoを用いてWasmを扱う場合,Wasmの外側とのデータのやり取りを行うためにメモリー上のデータ構造を意識する必要があり,unsafe.Pointer
にキャストしたりと冗長なコードを書く必要がありました.
今回紹介するプロポーザルでは,Goで扱える型を緩和することを提案しており,Go1.24以降では次の型をWasmで扱うことができるようなる予定です.
Go value | Wasm value |
---|---|
bool |
i32 |
int32 , uint32
|
i32 |
int64 , uint64
|
i64 |
float32 |
f32 |
float64 |
f64 |
uintptr , unsafe.Pointer , *T , *struct , *[...]T
|
i32 |
string |
(i32, i32) ※Goからの引数に限定 |
これにより,unsafe.Pointer
にキャストしなくともポインターを直接扱うことができるようになるなど,Go側のコードを書くコストが軽減されることが期待されます.
また,限定的ではありますが,string
を文字列のポインターと長さをもつタプル(i32, i32)
としてサポートするようになります.
そのため,先ほどのGoのコード例を次ように書き換えることができます.
// main.go
package main
//go:wasmimport console log
func consoleLog(msg string)
func main() {
msg := "Hello, 世界!"
consoleLog(msg)
}
string
のサポートは本記事執筆時点ですでに実装されているため,最新のGoをビルドすることで試すことができます.
$ go version
go version devel go1.24-733df2bc0a Mon Nov 25 02:23:41 2024 +0000 darwin/arm64
$ deno run --allow-read bootstrap.js
Hello, 世界!
まとめ
Go1.24のWasm関連のアップデートの目玉はなんといってもgo:wasmexport
ディレクティブのサポートですが,それ以外にもこのような嬉しい改善が行われています.
Goを使ってWasmを作ることが少しだけ簡単になるため,楽しみなアップデートの一つです.
-
CookbookシリーズはWebAssemblyの活用事例などをまとめたいと思い執筆しているシリーズで,今回初の合同誌として第4章をはくすけ (@hacusk)さんに執筆していただきました. ↩︎
-
プログラムを実行するための
main
関数を持つコマンドモジュールではなく,コマンドモジュールから利用するためのライブラリーモジュールのこと. ↩︎
Discussion