1 of 23

Go Toolchain

in WebAssembly

2020-02-25 Go 1.14 Release Party

Hajime Hoshi (星一)

2 of 23

自己紹介

  • 星一 (@hajimehoshi)
  • ソフトウェアエンジニア
  • 趣味で Go
    • Ebiten (2D ゲームライブラリ)
    • Oto (音がなる io.Writer)
    • go-mp3 (MP3 デコーダの移植)
    • Asobiba (実験的 Playground) ← 今日の発表
    • GopherJS の Go 1.12 対応
    • Gomobile の Go modules 対応

3 of 23

アジェンダ

  • Go と WebAssembly
  • 作ったもの
  • どう動いているのか

4 of 23

Go と WebAssembly

5 of 23

Go をブラウザで動かすには?

  • GopherJS
    • Go → JavaScript
  • WebAssembly
    • Go → WebAssembly

6 of 23

GopherJS

  • https://github.com/gopherjs/gopherjs
  • Go を JavaScript に変換するツール
  • Go 1.12 で止まっている
    • メンテされていないし、あまりしなくてよいことで�合意している
  • 原作者が Go / Wasm 実装者と同じ
    • Richard Musiol 氏
  • アイコンがかわいい

7 of 23

WebAssembly

  • ブラウザで動作するバイナリフォーマット
  • 実行速度が早い
  • C、C++、Rust、C#、Go などからコンパイルできる
  • Web API は直接叩けない
    • JavaScript API を経由

8 of 23

Go と WebAssembly

  • 標準ツールチェーンで Go プログラムを WebAssembly 形式出力できる

  • $GOROOT/misc/wasm_exec.js とセットで使用する
  • 一部標準ライブラリは動かない
    • ソケットなど
  • バイナリが大きい
    • ランタイムを丸ごと含む
    • Hello World で 1.25 [MB] (Go 1.14rc1)
  • 未だ不安定
    • Go 1.13 ではメモリリークする (golang/go#35111)

GOOS=js GOARCH=wasm go build ./path/to/yourpackage

9 of 23

WebAssembly (Go) から JavaScript 関数を呼ぶには?

  • syscall/js 経由で JavaScript API を呼ぶ
  • いまだに不安定
    • Go 1.12 〜 Go 1.14 で全て非互換
    • Go 1.14 で js.Value の == の比較が不可能になった (golang/go#35111)
  • ファイルシステム、プロセスなどは node.js の場合のみ利用可
    • fs module、 process module を利用
    • ブラウザの場合は空実装
    • 無理やり偽造も可能 (後述)

WebAsesmbly (Go)

Go Assembly (CallImport)

syscall/js

wasm_exec.js

call

Browser / node.js

go.importObject.go

10 of 23

作ったもの

11 of 23

Go ツールチェーンと Go

  • Go ツールチェーンは Go で書かれている、ということは…?
  1. Go コンパイラは Go を WebAssembly に変換できる
  2. Go コンパイラは Go で書かれている
  3. 故に Go コンパイラは Go コンパイラを WebAssembly に変換できる

GOOS=js GOARCH=wasm go build cmd/go

12 of 23

Asobiba (遊び場)

  • https://soko.hajimehoshi.com/asobiba/
  • いわゆる Playground
    • Go プログラムを実行して結果を表示する
  • Cgo 以外全て動く
    • 時間はかかるが、モジュールも動く
  • サーバーサイドにほぼ負担をかけない
    • ファイルサーバーだけで Playground になる
  • ソース: github.com/hajimehoshi/asobiba

13 of 23

デモ

14 of 23

Playground 先行事例

  • The Go Playground
    • いわゆる公式の Playground
    • 外部モジュールも動く
    • サーバーサイドでビルド�(NaCl VM だったが gVisor に移行予定)
  • GopherJS Playground
    • GopherJS
    • 外部モジュールは動かない
    • クライアントサイドでビルド
  • wasm-go-playground
    • Go ツールチェインを Wasm 化してビルド
    • Asobiba のインスパイア元
    • 標準ライブラリがほぼ動かない (runtime のみ)

15 of 23

どう動いているのか

16 of 23

処理の流れ

  • "go build main.go"
    • 疑似ファイルシステムを準備
    • go.wasm を fetch して compile し、wasm_exec.js で定義されている go.importObject と組み合わせて instantiate
    • wasm_exec.js で定義されている go.run 関数を呼んで実行
  • go.wasm が os/exec.Run 経由で他プロセスを呼ぶことがある
    • exec.Run が syscall/js 経由で JavaScript を呼び、 wasm を起動

JavaScript

疑似ファイルシステム (fs)

標準ライブラリ

キャッシュ

go.wasm

compile.wasm

asm.wasm

link.wasm

17 of 23

ファイルシステムの偽造

  • node.js の fs と同じインターフェイスを実装
    • 例: open, read, write, fstat, etc.
    • グローバル変数の fs に入れておくと、 wasm_exec.js がこれを使う
    • O_APPEND などを含めて正しく実装 (そうでないと意味不明なエラーが出る)
    • 一部は手抜き実装 (例: symbolic link などない)
  • ファイルは単なる Map
    • 例: "/go/src/runtime/sys_wasm.go"{contents: Uint8Array}
  • 標準ライブラリなど必要なファイルを突っ込んでおく
  • 環境変数
    • TMPDIR=/tmp
    • HOME=/root
    • GOROOT=/go
    • GOCACHE=/var/cache

18 of 23

プロセスの偽造

  • go コマンドは内部で色々呼び出す
    • compile、asm、link
    • なお GOPROXY 使う限り git 等は呼ばれない
  • exec.Run のみ実装
    • 完了まで待つ
    • プロセスは同時に 1 つだけ
  • 標準 (エラー) 出力はプロセス終了時に�まとめて出力
    • wasm_exec.js / fs が管理できる標準入出力は 1 個のみ
    • 間違えると無限ループする
    • インタラクティブな処理はいまのところないので OK

go.wasm (親)

compile.wasm (子)

標準 (エラー) 出力

標準入力

JavaScript

標準 (エラー) 出力

標準入力

19 of 23

標準ライブラリの修正

  • go.wasm、compile.wasm のための修正
    • go.wasm を作るときに単に go build cmd/go するのではなく、標準ライブラリを一部修正する必要がある
    • 注: go.wasm 自身は無修正標準ライブラリを使用する
  • os/exec
    • Run のみ実装
    • JavaScript で定義された関数 execCommand を呼び出す
    • execCommand は Wasm を起動する
  • cmd/go/internal/lockedfile/internal/filelock
    • js 実装だと panic してしまう
    • ロックのみの実装でごまかす

20 of 23

GOPROXY の設定

  • CORS (Cross-Origin Resource Sharing)
    • ブラウザから proxy.golang.org にアクセス不可
  • 代わりに第三者のを使う
    • GOPROXY=cache.greedo.xeserv.us
    • Thank you, Christine Dodrill!
  • チェックサムは諦める
    • GOSUM=off

21 of 23

その他

  • ビルドが遅い
    • キャッシュを作成
    • TMPDIR を指定して go build std した後、結果をごっそりコピー
  • メモリを食いまくってやばい
    • WebWorker 上で実行し、終わったら破棄
  • バイナリがでかすぎてやばい
    • データ転送にお金がかかってしまう
    • WebAssembly.compile の結果をキャッシュ
    • gz 圧縮してクライアントサイドで展開

22 of 23

まとめ

  • https://soko.hajimehoshi.com/asobiba/
  • Go コンパイラは Go で書かれているから go build cmd/go すればいいんじゃね? → そう簡単にはいかない
    • ファイルシステムの偽造
    • 標準ライブラリの修正
    • プロキシの修正
    • キャッシュの準備
    • etc.

JavaScript

疑似ファイルシステム (fs)

標準ライブラリ

キャッシュ

go.wasm

compile.wasm

asm.wasm

link.wasm

23 of 23

今後の課題

  • もうちょっと早くしたい
    • 特に外部モジュールを使うと遅い
  • もうちょっと軽くしたい
    • モバイルブラウザで動かすと死ぬ
  • ゲーム動かしたい