こんにちは、編集長の白石です。
この記事は、9月24日に開催されたHTML5 Conference 2017に登壇したエキスパートに、お話されたセッションのトピックを中心に、様々なお話を伺おうというものです。セッションの内容をより深く理解する手助けになるだけでなく、本記事単体でも面白く読んでいただけることを目指しています。
今回お話を伺ったのは、WebAssemblyコミュニティを率いてらっしゃる清水 智公さん(@chikoski)です。清水さんのセッションは「WebAssembly MVPまとめと、今の議論の内容からいくつか」でした。
※スライド資料はこちらで公開されています。
WebAssemblyとは何か?
白石: 今日はよろしくお願いします。まずは自己紹介をお願いできますか?
清水: WebAssemblyのコミュニティを運営している清水です。@ikkoさんと一緒に、「emcscripten night !!」というイベントを四半期に一回くらいのペースで開催しています。
白石: では早速ですが、WebAssembly(WASM)とは何なんでしょうか?
清水: 一言で言うと、Webブラウザ上で実行できる、ポータブルなバイナリ形式です。現在、「MVP(Minimul Viable Product: 最小限有用な機能を備えたプロダクト)」の実装が完了し、ほぼすべてのメジャーブラウザで利用できるという状態です。
白石: ポータブルというと、ブラウザにも、OSにも、デバイスにも依存しないということですね。
清水: その通りです。Javaをご存じの方であれば、classファイル形式を思い浮かべていただくと理解は早いと思います。
白石: なるほど。なぜそうしたバイナリ形式が必要とされたんでしょうか?
清水: これまでWebページで動作するプログラミング言語といえば当然JavaScriptだったわけですが、JavaScriptは遅いという問題がありました。asm.jsでスピードの面は大分改善されたのですが、今度はサイズが大きくなってしまうという問題も生まれました。それで早く動かせて、サイズもasm.jsよりは小さいものとして、WebAssemblyが生まれたんです。
遅さの原因の一つは、JavaScriptは変数の型が動的だということです。例えばJavaScriptで足し算を行うコードがあるとしたら、いろんな可能性を考慮しなくちゃいけないんですよね。
値が整数かもしれないし、浮動小数点かもしれない。文字列かもしれないし、valueOf()
を持つオブジェクトかもしれない。こういう可能性を踏まえると、単なる足し算といえども最適化は容易じゃないわけです。
ですがWebAssemblyの場合は、コンパイル時に変数の型が決まるので、最適化が容易なんです。今では、ネイティブに比べて2/3くらいの速度というところまで改善されています。
白石: なるほど。サイズが小さくなるという点についてはいかがでしょう。
清水: JavaScriptは当然テキスト形式ですが、テキストってやはり冗長なんですよね。例えば、とあるゲームのコードを全部JavaScriptに変換したところ、40メガバイトを超えるサイズになってしまったことがあります。
これでは、JavaScriptのソースコードをダウンロードするだけでかなりの時間がかかってしまうし、実行にかかるコストも半端ではありません。
WebAssemblyは、サイズを小さくすることを最初から念頭に置いたバイナリ形式ですので、確実にサイズは縮まります。元のJavaScriptファイルにもよりますが、ミニファイして圧縮した場合に比べても小さくなるのは確実。場合によっては半分以下のサイズになることもあります。
白石: それほど素晴らしいのであれば、将来的にはJavaScriptを置き換えていくような存在になるのでしょうか?
清水: いえ、WASMはJavaScriptの置き換えは目指しておらず、あくまで補完する存在という位置付けです。例えば、WASMはDOMに直接アクセスすることができませんからね。
WASMへの対応状況
白石: では改めて、ブラウザのWASMへの対応状況を教えていただけますか?
清水: 先ほども申し上げたとおり、ほとんどのメジャーブラウザで使えます(参考: Can I Use)。Safariが11から対応するようになったのは大きいですね。iOSでも使えるようになったということなので。 Edgeも先日のCreator’s updateでデフォルトでWebAssemblyを利用できるようになりました。また、Node.jsでもWebAssemblyは動作します。
白石: Nodeでも動くんですか!知りませんでした。WASMにコンパイル可能な言語には、どのようなものがありますか?
清水: 様々なコンパイラで利用されている、LLVMという中間言語があるんですが、LLVMに変換される言語だったらだいたい大丈夫です。Emscriptenというツールを使うと、LLVMからWASMに変換できるのです。
LLVMにコンパイルできる言語というと、C/C++/Objective-Cなどが挙げられます。また、RustもWebAssemblyに対応しています。
あとUnity限定になってしまうのですが、C#にも対応しています。他にはAssemblyScriptといって、WebAssemblyを出力できるTypeScriptのサブセット言語も存在します。
白石: C#やTypeScriptもあるんですね!WebAssemblyにはガベージコレクタがないと聞いていたので、それらの言語が使えるというのは驚きです。
ちなみに、WASMを出力できる言語や動作する環境がたくさんあるのはわかったのですが、実際どれくらい実用的なんでしょうか?
清水: 今は、ざっくり言ってC言語のコードをコンパイルして動かす能力は備えているというところです。 C++への対応は完全じゃありません。C++の例外をまだうまく扱えないんですよね。
白石: Cと言えば、メモリを直接操作するようなコードを直接書けると思うのですが、それがWebAssembly上だとどのように動作するのでしょうか?
清水: WebAssemblyのコードには専用のメモリ空間が割り当てられるんです。C言語におけるメモリ操作などは、そのメモリ空間に対して行われます。そのメモリ空間は、JavaScriptコードからもArrayBuffer
オブジェクトとして参照できます。
ちなみに、WebAssemblyの仕様上は64ビットのメモリ空間を扱えるのですが、現在の実装上では32ビットに制限されていますね。
WASMを使う
白石: WASMを使うコードはどういうふうになりますか?
清水: まず、WASMのバイナリが必要です。拡張子は.wasm
です。そのファイルをfetch()
でもXMLHttpRequest
でも何でもいいので読み込んで、バイナリコードをArrayBuffer
化し、WebAssembly.instanciate()
というメソッドに渡します。
すると、WebAssemblyコードの実行結果がPromise
の戻り値として返ってくるので、そこにエクスポートされている関数をJavaScript側から呼び出すことが可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// MDNのサンプルを引用 // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate // WASMファイルを読み込み fetch('sample.wasm').then(response => // ArrayBuffer化 response.arrayBuffer() ).then(bytes => // インスタンスを生成 WebAssembly.instantiate(bytes) ).then(result => // 生成されたインスタンスのエキスポートされた関数を呼び出し result.instance.exports.exported_func() ); |
白石: なるほど、使うのはそれほど難しくなさそうですね。JavaScriptからシームレスに関数の呼び出しを行えるのが素晴らしい。
清水: はい、それにWASMのコード内から、外部JavaScriptのコードを呼び出すことも簡単にできます。WebAssembly.instantiate()
の第二引数で、WASMコードがインポートするメンバーを指定できるんです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// MDNのサンプルを引用 // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate // WASMがインポートして使用するオブジェクト var importObject = { imports: { imported_func: function(arg) { console.log(arg); } } }; fetch('simple.wasm').then(response => response.arrayBuffer() ).then(bytes => // インポートするオブジェクトを指定 WebAssembly.instantiate(bytes, importObject) ).then(result => result.instance.exports.exported_func() ); |
白石: なるほど、ダイナミックリンクが可能なんですね。WASMがJavaScriptをどう補完するのか、よくわかりました。
WASMはどう使われていくか
白石: WASMが登場したことで、具体的に何が変わっていくと思いますか?
清水: まずはやはり、C/C++で書かれたコードをWASMに変換して使うのが一番最初に思いつくユースケースです。 コンシューマー向けのサービスでいうと、やはりゲーム系がすぐに思いつきますね。
例えば新たなゲームタイトルをスマホ向けにネイティブで開発しつつ、WASMでコンパイルしてWeb版も同時に開発するということができます。 UnityはWebAssemblyに対応しているので、そういう開発を行うのは比較的簡単です。
白石: なるほど、スマホ版だけじゃなくてWeb版も同時に開発して公開できれば、ユーザーへのリーチが拡大しそうですね。
清水: 実際、とあるベンダーさんが、Android用のゲームをWebブラウザでも動作するようにしたところ、ユーザー数はかなり増えたと聞いています。
白石: では、まずはネイティブとWebのクロスプラットフォーム開発でWASMは活躍しそうだと。 他にはC言語で書かれた資産とかもWeb上で使えそうですよね。例えばgzipのライブラリとか。
清水: そう、圧縮や暗号系、画像処理などの、Cで書かれたライブラリをWeb上に移植するのにも向いていますね。 CPUに依存する処理を高速化するのには、すごく向いています。
Cで書かれたコードの移植といえば、luaもWASMにコンパイルできたそうです。
白石: おお、言語処理系まで!そうか、よく考えたら、現在のWASMにガベージコレクタがなくとも、ガベージコレクタを備えた処理系ごとコンパイルしちゃえばいいんですね。
WASMのこれから
清水: はい、ちょうどガベージコレクタの話が出たので、WASMのこれからという話をしたいと思います。WASMの次のターゲットとして、ガベージコレクタが提案されています。
こちらはおそらく、「ガベージコレクタを使いたい場合は使える」という設計になると予想されています。
白石: ガベージコレクタが使えるようになったら、WASMをターゲットにした言語処理系とかが作りやすくなりそうですね。WASMで提案されている仕様は他にもありますか?
清水: まず、スレッドを使えるようにするという提案があります。
やはり、pthread (POSIXスレッド: C言語で使用できるマルチスレッドAPI)を使いたいという要望が多いんですね。
複数のWeb Workers(Web環境でマルチスレッディングが可能なAPI)で共有できる Shared ArrayBuffer は既に利用可能ですが、複数のスレッドを待ち合わせるwait
やwake
と言った命令も使えるようにすることを見据えています。
他には、先ほども申し上げたように例外を扱えるようにして、C++コードもWASMに正しくコンパイルできるようにすること。WASMのモジュールとECMA Scriptモジュールの整合性を取る…と言った提案も行われています。
白石: WASMの未来、楽しみですねー。最後にお聞きしたいのですが、現時点でWASMを試すのにおすすめの言語はありますか?C/C++で書くのはなかなかしんどいなあ、と…
清水: HTML5 Experts.jpの読者に向けてという話であれば、TypeScript(のサブセット)をWASMにコンパイルできる、AssemblyScriptを使うのはおすすめです。Web技術者が軽く試してみるという用途には最適だと思います。
白石: 本日は、WebAssemblyについて基礎から最新情報まで教えていただき、ありがとうございました。