go/misc/wasm/wasm_exec.js は何をしているのか
はじめに
沖縄宜野湾エンジニア勉強会 の LT スライドです
右のアイコンから Start Presentation をクリック
https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.html の話
Commit Hash 7254cfc37b3a93a6e83dae22c4bfd6f777edb97e
WebAssembly の仕様については https://github.com/WebAssembly/spec を読んでいる
Commit Hash 03905dc8c245b0c661f6b76ceb0f361f8ce48313
eitaro
eitaro
宮崎 × 沖縄 JSやらNight! で発表したオレオレ英語学習アプリ
発表資料
青色の気持ち悪い感じのところにお気持ち程度の WebAssembly (Wasm)を使っている
Go 1.11で入る予定の WASM ビルドターゲットを利用
https://gyazo.com/0c4eb377ef6b71da0ba8060139160fef
実際はチュートリアルが動いただけみたいなもん
Some notes about the upcoming WebAssembly support in Go を参考にちょっとアレンジしただけ
よく分かっていないところが多い
特に https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js が何してるのか分かってない
伝えたいこと
Wasm ってどんなものかざっくり分かる
LT ではこの話がメイン
wasm_exec.js が何者なのか分かる
まず WebAssembly について知ろう
https://webassembly.org/docs/high-level-goals/
小さいバイナリフォーマットとして提供される
C++, Rust, Golang などのコンパイルターゲットとなる
mobile や IoT でも動くことを視野に、ネイティブに近いスピードで実行される
JavaScript の置き換えではなく足りないところをサポートできるもの
例えば実行が遅いところをサポートする
もっと詳しく知りたい場合
http://webassembly.github.io/spec/
コアは Vitual Machine の命令セットとなる
バイナリフォーマット以外にテキストフォーマットもある
どうして WebAssembly を使うのか
https://webassembly.org/docs/use-cases/
ブラウザで動かす場合は基本的に速さが重視される
JavaScript と比較することになる
例えば Web からの load、parse、compile, optimize が遅い
低レイテンシを要求されるゲーム・VR アプリなどで便利そう
Vim...? Vim を WebAssembly に移植した
WebAssembly と JavaScript API
JavaScript API では WebAssembly オブジェクトを通じてやり取りする
バイナリフォーマットのコンパイル
module の import / export インターフェースを用意したり
まあどんなものか見てみよう
http://webassembly.github.io/spec/js-api/index.html#sample より
code:demo.wat
(module
(import "js" "import1" (func $i1))
(import "js" "import2" (func $i2))
(func $main (call $i1))
(start $main)
(func (export "f") (call $i2))
)
Wasm ではロードできて実行可能なひとつの単位を module と読んでいる
demo.wat は Wasm のテキストフォーマットで demo.wasm にエンコードされる
module がインスタンス化されたときに main が呼ばれて import1 が call される
関数として f を export しており、呼ばれたら import2 を call
code:demo.js
var importObj = {js: {
import1: () => console.log("hello,"),
import2: () => console.log("world!")
}};
fetch('demo.wasm').then(response =>
response.arrayBuffer()
).then(buffer =>
WebAssembly.instantiate(buffer, importObj)
).then(({module, instance}) =>
instance.exports.f()
);
demo.wasm を ArrayBuffer にしてから WebAssembly.instantiate(buffer, importObj) でインスタンス化
instance.exports.f() で export された f を呼び出している
インスタンス化時に hello, , f 呼び出し後に world! が表示される
eitaro のコードをおさらい
main.go
syscall/js を使って body タグの style 属性にアクセス
変な背景をセットする
main.go により作られた example.wasm をwasm_exec.html でロード
ほぼチュートリアルのまま
wasm_exec.js はコピー
チュートリアルをやったときはこれが何しているのか分かっていなかった
wasm_exec.html
https://github.com/jewel12/eitaro/blob/master/static/wasm_exec.html#L30-L36
code:js
const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("/static/wasm/example.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
go.run(inst);
});
module をインスタンス化するときに wasm_exec.js で定義された go.importObject を渡している
go.run(inst) というのをやっている
wasm_exec.js
WebAssembly.instantiateStreaming の第二引数は Wasm 側で import するものを定義している
ということは Go で Wasm バイナリフォーマットをビルドしたときに import することが前提になっているものになる
本当に import すべきなのか example.wasm の中身を見てみる
wasm-dump というツールを go get して利用すると Wasm バイナリフォーマットを解析できる
-x オプションで Section ごとの情報が取れるので impot section を見る
import されているもの
code:txt
import:
- function0 sig=1 <- go.debug
- function1 sig=1 <- go.runtime.wasmExit
- function2 sig=1 <- go.runtime.wasmWrite
- function3 sig=1 <- go.runtime.nanotime
- function4 sig=1 <- go.runtime.walltime
- function5 sig=1 <- go.runtime.scheduleCallback
- function6 sig=1 <- go.runtime.clearScheduledCallback
- function7 sig=1 <- go.runtime.getRandomData
- function8 sig=1 <- go.syscall/js.boolVal
- function9 sig=1 <- go.syscall/js.intVal
- function10 sig=1 <- go.syscall/js.floatVal
- function11 sig=1 <- go.syscall/js.stringVal
- function12 sig=1 <- go.syscall/js.valueGet
- function13 sig=1 <- go.syscall/js.valueSet
- function14 sig=1 <- go.syscall/js.valueIndex
- function15 sig=1 <- go.syscall/js.valueCall
- function16 sig=1 <- go.syscall/js.valueNew
- function17 sig=1 <- go.syscall/js.valueInt
- function18 sig=1 <- go.syscall/js.valueBool
- function19 sig=1 <- go.syscall/js.valueLength
- function20 sig=1 <- go.syscall/js.valuePrepareString
- function21 sig=1 <- go.syscall/js.valueLoadString
runtime と syscall/js といった prefix で関数が定義されている
fmt.Print() の出力先など実行環境に依存している部分は書いてあげないといけない
export されているもの
run
エントリーポイント(main())が実行される
mem
線形メモリ にアクセスできる
他に export はできるの?
関数で export できる(されている)ものは今のところ run しかない
syscall/js の NewCallback で callback を作り、JavaScript で定義された関数に引数として渡してから callback を関数として生成するパターンがある
例: https://matthewphillips.info/programming/wasm-golang-ce.html
wasm_exec.js まとめ
メッセージ出力のハンドリングやランダム値の取得など実行環境に任せる部分で、 Go によりビルドされた Wasm フォーマットが import しているものを実装している
Go から JavaScript の世界にアクセスするために用意された syscall/js で必要なものも実装されている
export している関数は run のみ
Go 側で定義された関数を使うにはちょっと手間がいるし、Wasm exported function ではない
まとめ
Wasm についてざっくり理解してもらえたと思う
もし Go で Wasm バイナリフォーマットをビルドするときは参考にしてもらいたい
Go 1.11 Beta の情報なのであっという間に古い情報になるかもだけど
go wasm javascript