このサイトの記事更新は2019年11月に終了されました。過去記事アーカイブを公開しています。

投稿されたすべてのトピック

Tokyo WebExtensions Meetup #1 が開催されました!

2018 年 3 月 2 日に Tokyo WebExtensions Meetup #1 がデジタルハリウッド大学大学院にて開催されました。
(デジハリさん、会場の提供ありがとうございました!)
20 名 + リモート参加 4 名の拡張機能開発者が集まり、拡張機能に関する問題点や要望などを題材とし、さまざまな観点から熱い議論が繰り広げられました!わお。

当日のスライドや議事録、twitter などは以下になります。

今回のミートアップでは、会の趣旨、Mozilla のスタンス、拡張機能をとりまく現状の説明を経て議論を開始。議題はミートアップの事前にみなで書き出して、それに沿って進んでいきました。拡張機能開発と一言で言っても、新規作成から開発、デバッグ、テスト、そしてリリースさらにはマーケティングなどのプロセスを踏みます。それぞれのプロセスや開発者のコンテクストによって、多様な観点から問題点や要望が浮かびましたが、今回は特に開発・デバッグ、翻訳に注目して議論が展開されました。(議事録を参照くださいませ)
そのあとは懇親会、ピザとお神酒が入り、議論がさらに活発化したのは言うまでもありません。

999FC5DC-02C5-4C31-9B8B-854CAA9F90ED

DSCN5065

Tokyo WebExtensions Meetup はコミュニティベースのイベントです。
これから本ミートアップに関することは Mozilla Japan コミュニティ Slack の #extdev で決めていきます。
みなさんも是非ご参加くださいませ。もちろん拡張機能に関する質問なども自由に投稿ください。参加はこちらから。

次回は 5 月中旬をなんとなく予定しています。それでは!

WebAssembly は今どこにあり、次に何があるのか

[この記事は”Where is WebAssembly now and what’s next?“の翻訳です]この記事は WebAssembly と何が速くしたのかのシリーズの6部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。

2月28日、4つのメジャーなブラウザが WebAssembly の必要最低限の機能のみを持つ製品 (MVP) が完了したという コンセンサスを発表しました

これにより、ブラウザが出荷できる安定したコアが提供されます。このコアには、コミュニティグループが計画している機能のすべてが含まれているわけではありませんが、WebAssembly を高速かつ使いやすくするために十分な機能を備えています。

これにより、開発者は WebAssembly コードの提供を開始できます。以前のバージョンのブラウザでは、開発者は asm.js バージョンのコードを提供できます。asm.js は JavaScript のサブセットであるため、どの JS エンジンでも実行できます。Emscripten を使用すると、同じアプリケーションを WebAssembly と asm.js の両方にコンパイルできます。

初回リリースであっても WebAssmbly は高速に動くでしょう。しかし修正と新規機能を導入により将来的にさらに高速になるでしょう。

ブラウザー上の WebAssembly のパフォーマンス向上

ブラウザーエンジンが WebAssembly のサポートを向上させるとスピードが向上します。ブラウザベンダーはこれらの問題を個々に取り組んでいます。

JS と WebAssembly 間の関数呼び出しの高速化

現在 JS からの WebAssembly 関数呼び出しは求められるスピードに達していません。その理由は「トランポリン」と呼ばれることが原因です。JIT は WebAssembly をどのように扱えばよいのか知らないため、WebAssemblyを何かにルーティングする必要があります。これは最適化された WebAssembly コードを実行するためのセットアップ時のエンジン自体のコードが遅い箇所です。

Person jumping from JS on to a trampoline setup function to get to WebAssembly

これは JIT が直接処理する方法を知っていた場合よりも、最大100倍遅くなることがあります。

単一の大きなタスクを WebAssembly モジュールに渡している場合は、このオーバーヘッドに気付かないでしょう。しかし WebAssembly と JS の間で多くのやりとりがある場合 (小規模な作業と同じように)、このオーバーヘッドが目立ちます。

より速いロード時間

JIT はより速いロード時間とより速い実行時間の間のトレードオフを管理する必要があります。コンパイルや最適化に時間を費やすと実行速度が向上しますが、起動が遅くなります。

先進的なコンパイル (コードが実行されるとジャンクが発生しないようにする) と、コードのほとんどの部分がそれに見合った最適化を行うために十分な時間実行されないという基本的な事実とのバランスを取るために、多くの進行中の作業があります。

WebAssembly はどの型が使用されるかを特定する必要が無いため、エンジンは実行時に型をモニターする必要がありません。これは実行時の平行コンパイルといった例のような様々なオプションをもたらします。

さらに JavaScript API への最近の追加では WebAssembly のストリーミング・コンパイルが可能です。これは WebAssembly のデータをダウンロード中にエンジンがコンパイルを開始できることを意味します。

Firefox では二つのコンパイラーを使用するシステムになっています。一つ目のコンパイラは事前に実行し、コードを最適化を行います。コードの実行中は別のコンパイラがバックグラウンドで完全な最適化を行います。完全に最適化されたバージョンのコードへ準備が整うと切り替わります。

MVP リリース以降の機能導入

WebAssembly の目標の 1 つは、すべての機能を設計するのではなく、小さなチャンクで指定し、途中でテストを行うことです。

期待されている機能はたくさんあるが、100% の考え方はまだ存在しないことを意味しています。これらはブラウザベンダが参加している使用策定プロセスを経る必要があります。

これらの機能は将来の機能と呼ばれています。少し紹介します。

DOM を直接扱う

現在 DOM を扱う手段はありません。これは WebAssembly から element.innerHTML のようにノードを更新できないこと意味します。

代わりに、値を設定するために JS を経由する必要があります。これは値を JavaScript 呼び出し側に渡すことを意味します。一方、WebAssembly 内から JavaScript 関数を呼び出すことは、JavaScript と WebAssembly の両方の関数を WebAssembly モジュールをインポートして使用できることを意味します。

Person reaching around from WebAssembly through JS to get to the DOM

いずれにしても、JavaScript を使用することは直接アクセスよりも遅くなる可能性があります。WebAssembly の一部のアプリケーションはこれが解決されるまでが続くかもしれません。

共有メモリーの同時実行性

コードを高速化する方法の 1 つは、コードの異なる部分を同時に並列で実行できるようにすることです。しかし、スレッド間の通信のオーバーヘッドは、タスクを分けずに実行するよりも多くの時間を要する可能性があるため、時には逆効果になる場合があります。

しかしスレッド間のシェアードメモリを使用すればこのオーバーヘッドを削減できます。これを実施するため WebAssembly は JavaScript の新しい SharedArrayBuffer を使用すべきです。これがブラウザに組み込まれると、ワーキンググループは WebAssembly の動作方法を指定することができます。

SIMD

WebAssembly に関する他の投稿を読んだり、話を聞いたりすると、SIMD のサポートについて聞かれることがあります。頭字語は、単一命令、複数のデータを表します。物事を並行して運用する別の方法です。

SIMD を使用すると、異なる数のベクトルのような大きなデータ構造を取り、同じ命令を異なるパーツに同時に適用することができます。このようにして、ゲームや VR に必要な複雑な計算を大幅に高速化できます。

これらは平均的な Web 開発者にとってさほど重要ではありません。しかしゲーム開発者のようなマルチメディアを扱う開発者にとって非常に重要です。

例外ハンドリング

C++ のような言語の多くのコードは例外を使用しています。しかし例外は WebAssembly の一部としてまだ定義されていません。

あなたのコードを Emscripten でコンパイルした場合、いくつかのコンパイラ最適化レベルの例外処理をエミュレートします。これは非常に遅いため、DISABLE_EXCEPTION_CATCHING フラグを OFF にできます。

WebAssembly で例外がネイティブに処理できるようになると、このエミュレーションは必要ありません。

開発者にとってより簡単にするその他の改善

いくつかの将来の機能はパフォーマンスは改善しませんが、WebAssembly を使用するにあたり開発者がより簡単に使用できるようになるでしょう。

  • ファーストクラスのソースレベル開発ツール. 現在ブラウザ上で WebAssembly をデバッグすることは、生のアセンブリーをデバッグするようなものです。しかし、ソースコードがアセンブリにどのように変換されるかに関して精通している開発者はほとんどいません。開発者がソースコードをデバッグできるように、ツールサポートを改善する方法を検討しています。
  • ガベージコレクション. 事前に型を定義できる場合は、コードを WebAssembly に変換する必要があります。したがって TypeScript のようなコードを使用するコードは、WebAssembly にコンパイル可能である必要があります。WebAssembly は JS エンジンに組み込まれているような既存のガベージコレクタとやりとりができないというのが現状です。この将来の機能のアイデアは、WebAssembly に低レベルの GC プリミティブタイプとオペレーションのセットを使用して、組み込み GC へのファーストクラスのアクセスを与えることです。
  • ES6 モジュールの統合. ブラウザは現在、script タグを使用して JavaScript モジュールを読み込むためのサポートを追加しています。 この機能を追加すると、url が WebAssembly モジュールを指している場合でも<script src=url type="module">のようなタグが機能します。

結論

WebAssembly は今日でも高速で、そしてブラウザの新しい機能や実装の改良により、さらに高速化するはずです。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly を速くするには?

この記事は WebAssembly と何が速くしたのかのシリーズの5部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。前の記事では WebAssembly や JavaScript を使ったプログラミングは、どちらか一方を選択するものではないことを説明しました。多くの開発者が完全な WebAssembly コードベースを記述しているとは考えていません。開発者はアプリケーション用に WebAssembly と JavaScript のどちらかを選択する必要はありません。 しかし開発者は JavaScript コードの一部を WebAssembly に取り替えようとしています。

例えば React に取り組んでいるチームは、調整コード (別名仮想 DOM) を WebAssembly バージョンに置き換えることができます。React を使う人は WebAssembly を使用するメリットを除いて、これらのアプリはまったく同じように動作し、何もする必要はありません。

React チームのメンバーのような開発者がこの転換を行う理由は、WebAssembly が高速であるためです。しかし何が高速にしているのでしょうか?

JavaScript のパフォーマンスは現在どうなっているのか?

JavaScript と WebAssembly の間のパフォーマンスの差を理解する前に、私たちは JS エンジンがどのように動くかを理解する必要があります。

この図は今日のアプリケーションの起動時のパフォーマンスがどうなっているかの概略図です。

JS エンジンがこれらのタスクを実行する時間は、ページが使用する JavaScript により異なります。 この図は正確なパフォーマンスの数値を表すものではありません。代わりに JS と WebAssembly で同じ機能のパフォーマンスがどのように異なるかについて、高度なモデルを提供することを目的としています。

Diagram showing 5 categories of work in current JS engines

各バーには、特定のタスクを行うのに費やされた時間が表示されます。

  • 構文解析 — ソースコードをインタプリタが実行できるものに処理するまでにかかる時間。
  • コンパイル + 最適化 — ベースラインコンパイラと最適化コンパイラで費やされる時間。最適化コンパイラの作業の幾つかはメインスレッドに含まれていないため、ここでは加えていません。
  • 再最適化 — JITが前提条件が満たされていないときに再調整を行う時間。コードの再最適化と最適化されたコードからのベースラインコードへの復帰。
  • 実行 — コードを実行するのにかかる時間。
  • ガベージコレクション — メモリのクリーンアップに費やされた時間。

注目すべき重要な点:これらのタスクが個別のチャンクまたは特定の順序で実行されないことです。代わりにインターリーブされます。ほんの少しの解析が行われ、次にいくつかの実行が行われ、次にコンパイルされ、さらに解析され、さらに実行されるなどします。

このような内訳によるパフォーマンスのは JavaScript の初期段階からの大きく改善され、以下のようになっています:

Diagram showing 3 categories of work in past JS engines (parse, execute, and garbage collection) with times being much longer than previous diagram

初期の JavaScript 実行時の変換処理だけだった頃は実行速度は非常に遅かったです。JIT が導入されると、実行時間が大幅に短縮されました。

トレードオフはコードの監視とコンパイルです。もし JavaScript の開発者が今までと同じように JavaScript を書き続けた場合、解析とコンパイル時間が非常に短くなります。改善されたパフォーマンスにより開発者はより大きな JavaScript アプリケーションを作成することができるようになりました。

これはまだ改善の余地があることを意味します。

WebAssembly をどのように比較しますか?

典型的な Web アプリケーションにおける WebAssembly の比較方法を概説します。

Diagram showing 3 categories of work in WebAssembly (decode, compile + optimize, and execute) with times being much shorter than either of the previous diagrams

ブラウザがこれらのフェーズをどのように処理するかは、ブラウザにより若干の違いがあります。ここでは SpiderMonkey を使用します。

フェッチ

これは図には表示していませんが、時間がかかるのはサーバーからファイルを取得することだけです。

WebAssembly は JavaScript よりもコンパクトなため、フェッチはより速くなります。コンパクションアルゴリズムは JavaScript バンドルのサイズを大幅に縮小することができますが、WebAssembly の圧縮バイナリ表現をまだまだ小さくすることは可能です。

これはサーバーとクライアント間の転送時間をより短くできることを意味します。これは特に低速ネットワークでは当てはまります。

解析

ブラウザへ届くと JavaScript ソースが抽象構文木に解析されます。

ブラウザはたいていこれを遅延させ、最初は本当に必要なものを解析し、まだ呼び出されていない関数のスタブを作成するだけです。

そこから、AST はその JS エンジンに固有の中間表現 (バイトコードと呼ばれる) に変換されます。

対照的に、WebAssembly はすでに中間表現であるため、変換を行う必要はありません。デコードしてエラーがないか検証するのみです。

Diagram comparing parsing in current JS engine with decoding in WebAssembly, which is shorter

コンパイル + 最適化

JIT についての記事で説明したように、JavaScript はコードの実行時にコンパイルされます。実行時に使用されるランタイムに応じて、同じコードの複数のバージョンをコンパイルする必要があります。

ブラウザが異なると WebAssembly のコンパイル方法も異なります。WebAssembly を実行する前にベースラインのコンパイルを行うブラウザもあれば、JIT を使用するものもあります。

どちらにしても、WebAssembly は機械語に非常に近い状態で始まります。例えば、型はプログラムの一部です。これはいくつかの理由により高速です:

  1. コンパイラは最適化されたコードのコンパイルを開始する前に、コードにどの型を使用されているかを判断するために時間を費やす必要はありません。
  2. コンパイラは異なる種類のコードに基づいて同じコードの異なるバージョンをコンパイルする必要はありません。
  3. LLVM では、より多くの最適化が事前に行われています。それによりコンパイルと最適化に必要な作業が少なくて済みます。

Diagram comparing compiling + optimizing, with WebAssembly being shorter

再最適化

場合によっては、JIT は最適化されたバージョンのコードをスローして再試行する必要があります。

これは、JIT が実行中のコードに基づいて行う前提が正しくないことが判明した時に発生します。例えばループに入ってくる変数が以前の反復時の変数と異なる場合や、新しい関数がプロトタイプチェーンに挿入された場合に、最適化解除が実施されます。

最適化解除には二つのコストがあります。一つ目は最適化されたコードを取り除いてベースラインのバージョンに戻すのに時間がかかります。二つ目はその関数が依然として多く呼び出されている場合、JIT は最適化コンパイラを介して再度送信する場合があります。そのため2回コンパイルするコストがあります。

WebAssembly では、型のようなものは明白であるため、JIT は実行時に収集するデータに基づいた型の仮定を行う必要はありません。つまり再最適化サイクルを行う必要はありません。

Diagram showing that reoptimization happens in JS, but is not required for WebAssembly

実行

効率よく実行可能な JavaScript を書くことができます。これを行うには JIT が行う最適化について知る必要があります。例えば JIT の記事で説明したように、コンパイラが行う型の特殊化が行う事ができるようなコードを書く方法を知る必要があります。

しかしほとんどの開発者が JIT の内部構造は知りません。このような JIT の内部構造を知っている開発者であってもスイートスポットをヒットすることは難しいでしょう。コードをより読みやすくするために使用する多くのコーディングパターン (一般的なタスクをタイプ間で機能する関数に抽象化するなど) は、コンパイラーがコードを最適化するときに邪魔になります。

さらに、JIT が使用する最適化はブラウザによって異なっているため、あるブラウザの内部構造に合わせてコーディングすると、別のブラウザのでのードのパフォーマンスが低下する可能性があります。

このため、WebAssembly でコードを実行する方が一般的に高速です。WebAssembly では JIT が JavaScript に行う最適化の多く (型の特殊化など) は必要ありません。

また、WebAssembly はコンパイラのターゲットとして設計されています。これはコンパイラーが生成するように設計されており、人間のプログラマーが書くようには設計されていません。

人間のプログラマーは直接 WebAssembly を直接プログラミングする必要が無いため、WebAssembly は機械にとって理想的な一連の命令を提供できます。コードが動作する内容次第で、これらの命令は 10% から 800% 速く実行することができます。
Diagram comparing execution, with WebAssembly being shorter

ガベージコレクション

JavaScript では開発者は必要なくなった古い変数をメモリからクリアすることについて心配する必要はありません。代わりに JS エンジンは自動的にガベージコレクタと呼ばれるものを使用します。

ただし予測可能なパフォーマンスが必要な場合は問題になる可能性があります。ガベージコレクタがいつ動作するのかは制御できないため、都合の悪い時間に実施することがあります。ほとんどのブラウザではスケジューリングをかなりうまく行っていますが、コードの実行の途中でオーバーヘッドになることがあります。

少なくとも今のところ、WebAssembly はガベージコレクションをサポートしていません。メモリは C や C++ のように手動で管理されます。 これは開発者にとってプログラミングを難しくする一方で、パフォーマンスの一貫性も向上させます。

Diagram showing that garbage collection happens in JS, but is not required for WebAssembly

結論

WebAssembly は JavaScript よりも多くのケースで高速です:

  • WebAssembly のフェッチは圧縮された状態であっても JavaScript より時間がかかりません。
  • WebAssembly のデコードは JavaScript を解析するよりも時間がかかりません。
  • WebAssembly は JavaScript よりも機械語に近いため、コンパイルと最適化に要する時間が短縮され、すでにサーバー側で最適化が行われています。
  • WebAssembly には型やその他の情報が組み込まれているため、再最適化を行う必要はありません。そのため JS エンジンは JavaScript を使用方法を最適化するときに推測を行う必要はありません。
  • 開発者が効率良いコードを書くために知っておく必要があるコンパイラのトリックや落とし穴が少ないために、実行時間は短く済み、また WebAssembly の命令セットが機械にとってより理想的です。
  • ガベージコレクションはメモリーを手動で管理するため必要としません。

このため、多くの場合、WebAssembly は同じタスクを実行するときに JavaScript より優れたパフォーマンスを発揮します。

WebAssembly が期待通りの性能を発揮しない場合もありますし、より速くする範囲にもいくつかの変更があります。水平方向にもいくつかの変更があり、高速化する場合もあります。次の記事でこれらをカバーします。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly モジュールの作成と操作

この記事は WebAssembly と何が速くしたのかのシリーズの3部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。WebAssembly は、Web ページ上で JavaScript 以外のプログラミング言語を実行する方法です。これまで Web ページの様々な部分と対話するためにブラウザでコードを実行したいとき、唯一のオプションは JavaScript でした。WebAssembly が高速かどうかの話題の時は比較の対象は JavaScript になります。実際は、WebAssembly を使用しているか JavaScript を使用しているかのどちらか一方になるわけではありません。

実際、開発者は同じアプリケーションで WebAssembly と JavaScript の両方を使用することになります。WebAssembly を自分で作成しないなくても、それを利用可能です。

WebAssembly のモジュールは JavaScript から使用できるように関数を定義することができます。したがって、npm today から lodashの ようなモジュールをダウンロードし、その API の一部である関数を呼び出すように、将来、WebAssembly モジュールをダウンロードすることができるようになるでしょう。

では WebAssembly のモジュールをどのように作成し、JavaScript からどのように呼び出せるのかを見てみましょう。

WebAssembly はどこに適合するのか?

Assembly についての記事の中で、コンパイラが高水準のプログラミング言語をどのようにして機械語に翻訳するのかを話しました。

Diagram showing an intermediate representation between high level languages and assembly languages, with arrows going from high level programming languages to intermediate representation, and then from intermediate representation to assembly language

WebAssembly はこの図のどこに収まるのですか?

ターゲットアセンブリ言語の単なる別のものだと考えるかもしれません。これらの言語 (x86、ARM) のそれぞれが特定のマシンアーキテクチャに対応している点を除いて、それは事実です。

Web 上でユーザーのマシン上で実行されるコードを提供しているときは、コードが実行されるターゲットアーキテクチャはわかりません。

したがって、WebAssembly は他のアセンブリとは少し異なります。実際の物理マシンではなく、仮想マシンの機械語になっています。

このため、WebAssembly の命令は仮想命令と呼ばれることもあります。それらは、JavaScript のソースコードよりもはるかに機械語に直接マッピングされています。ごく一般的なハードウェアで効率的に行うことができることの一種です しかし、特定のハードウェアに対する特定の機械語への直接のマッピングではありません。

Same diagram as above with WebAssembly inserted between the intermediate representation and assembly

ブラウザが WebAssembly をダウンロードします。次に、WebAssembly からターゲットマシンのアセンブリコードまでの短い期間に使用します。

.wasm 形式へコンパイルする

WebAssembly を現在最もサポートしているコンパイラツールチェーンは LLVM です。LLVM に接続できる様々なフロントエンドとバックエンドがあります。

注記: ほとんどの WebAssembly のモジュール開発者は、C や Rust といった言語でコードを作成し、WebAssembly にコンパイルしますが、WebAssembly のモジュールを作成するには他の方法もあります。例えばTypeScript を使用して WebAssembly のモジュールを構築するのに役立つ実験的なツールや、WebAssemblyのテキスト表現を直接コーディングする方法もあります。

C から WebAssembly に変換したいとしましょう。clangフロントエンドを使用して、C から LLVM IR (中間コード) に変換することができます。LLVM は LLVM IR を理解します。そのため、LLVM はいくつかの最適化を実行できます。

LLVM IR (中間コード) から WebAssembly に変換するには、バックエンドが必要です。LLVM プロジェクトには現在進行中のものがあります。バックエンドはその大部分であり、すぐに終了するものです。しかし現在はこれを動作させるのはトリッキーかもしれません。

Emscripten と呼ばれる別のツールがありますが、これは現時点では少し使いやすくなっています。それは別のターゲット (asm.js と呼ばれる) にコンパイルし、それを WebAssembly に変換することで WebAssembly を生成できる独自のバックエンドを持っています。これは内部で LLVM を使用するので、Emscripten から 2 つのバックエンドを切り替えることができます。

Diagram of the compiler toolchain

Emscriptenには、C/C++ コードベース全体を移植可能にする多くのツールとライブラリが含まれているため、コンパイラよりもソフトウェア開発キット (SDK) のほうが多くなっています。例えば、システム開発者は、読み書きが可能なファイルシステムに慣れているため、Emscripten は IndexedDB を使用してファイルシステムをシミュレートできます。

使用したツールチェーンにかかわらず、最終的にはファイルは .wasm 形式になります。以下で .wasm ファイルの構造について詳しく説明します。まず、JS でどのように使用できるかを見てみましょう。

JavaScript で .wasm モジュールを読み込む

.wasm ファイルは WebAssembly のファイルであり、JavaScript から読み込むことができます。現在のところ、読み込む処理は少し複雑です。

function fetchAndInstantiate(url, importObject) {
  return fetch(url).then(response =&amp;gt;
    response.arrayBuffer()
  ).then(bytes =&amp;gt;
    WebAssembly.instantiate(bytes, importObject)
  ).then(results =&amp;gt;
    results.instance
  );
}

詳細はこのドキュメントを確認してください。

私たちはこのプロセスをより簡単にしようと取り組んでいます。私たちはツールチェーンを改善し、Webpack のようなモジュールバンドルや SystemJS のようなローダーと統合することを期待しています。WebAssembly モジュールの読み込みは、JavaScript の読み込みと同じくらい簡単です。

WebAssembly モジュールと JS モジュールの間には大きな違いがあります。現在 WebAssembly の関数では、数値 (整数または浮動小数点数) のみをパラメータまたは戻り値として使用できます。

Diagram showing a JS function calling a C function and passing in an integer, which returns an integer in response

文字列のように複雑なデータ型の場合、WebAssembly モジュールのメモリを使用する必要があります。

もし JavaScript を使って作業していたのであれば、メモリに直接アクセスすることはあまり馴染みがありません。 C、C ++、Rust のようなパフォーマンスの高い言語は、手作業によるメモリ管理を行う必要があります。WebAssembly モジュールのメモリは、これらの言語で見られるヒープをシミュレートします。

これを使用するには、ArrayBuffer という JavaScript の型を使用します。配列バッファはバイトの配列です。したがって、配列のインデックスはメモリアドレスとして機能します。

JavaScript と WebAssembly の間に文字列を渡す場合は、文字を文字コードに変換します。次にメモリ配列に書き込みます。インデックスは整数なので、WebAssembly 関数にインデックスを渡すことができます。したがって、文字列の最初の文字のインデックスをポインタとして使用できます。

メモリへのポインタを表す整数を持つ C 関数を呼び出す JS 関数と、次にメモリに書き込むC関数を示す図

WebAssembly モジュールを開発している Web 開発者の場合、そのモジュール周辺にラッパーを作成すべきでしょう。そうすることによってモジュールの使用者はメモリ管理について知る必要はありません。

詳細を知りたい場合は、WebAssembly のメモリの動作を確認してください。

.wasm ファイルの構造

高級言語でコードを記述し、WebAssembly にコンパイルする場合、WebAssembly モジュールの構造を知る必要はありませんが、基本を理解するのに役立ちます。

まだ知らない場合はWebAssembly の記事 (シリーズの第3部) を読むことをお勧めします。

WebAssembly に変換する C 関数を次に示します:

int add42(int num) {
  return num + 42;
}

WASM Explorer を使用してこの機能をコンパイルすることができます。

.wasmファイルを開くと (エディタで表示がサポートされている場合) 次のように表示されます。

00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60
01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80
80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06
81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65
6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69
00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20
00 41 2A 6A 0B

これは “バイナリ” 表現のモジュールです。バイナリは通常 16 進表記で表示されますが、簡単にバイナリ表記や人間が読める形式に変換できるため、バイナリの周りに引用符を入れました

例えば、num + 42 のようになります。

Table showing hexadecimal representation of 3 instructions (20 00 41 2A 6A), their binary representation, and then the text representation (get_local 0, i32.const 42, i32.add)

コードの仕組み:スタックマシン

あなたが疑問に思っている場合、ここではそれらの指示で何をするのでしょうか。

Diagram showing that get_local 0 gets value of first param and pushes it on the stack, i32.const 42 pushes a constant value on the stack, and i32.add adds the top two values from the stack and pushes the result

add 操作でその値がどこから来るのかはわかりませんでした。これは WebAssembly がスタックマシンと呼ばれるものの例であるためです。これは操作が必要とするすべての値が、操作が実行される前にスタックにキューイングされていることを意味します。

add のような操作は、必要な値の数を知っています。add は 2 つ必要なため、スタックの先頭から 2 つの値をとります。これは命令がソースレジスタまたはデスティネーションレジスタを指定する必要がないため、add 命令が短く (1Byte) できることを意味します。これにより、.wasm ファイルのサイズが縮小され、ダウンロードに要する時間が短縮されます。

WebAssembly はスタックマシンの観点から指定されていますが、実際のマシンでは動作しません。ブラウザが実行中のマシンのマシンコードにブラウザが WebAssembly を変換すると、レジスタが使用されます。 WebAssembly コードではレジスタが指定されていないため、ブラウザにはそのマシンに最適なレジスタ割り当てを柔軟に使用できます。

モジュールのセクション

add42 関数自体に加えて、.wasm ファイルには他の部分もあります。これらはセクションと呼ばれます。セクションのいくつかは任意のモジュールに必要であり、いくつかはオプションです。

必須:

  1. Type. モジュールおよびインポートされた関数で定義された関数の関数シグネチャが含まれます。
  2. Function. モジュールで定義された各関数へのインデックスを与えます。
  3. Code. モジュール内の各関数の本体。

オプション:

  1. Export. 関数、メモリ、テーブル、グローバルを他の WebAssembly モジュールや JavaScript で使用できるようにします。これにより、別々にコンパイルされたモジュールを動的にリンクすることができます。これは WebAssembly の .dll バージョンです。
  2. Import. 他の WebAssembly モジュールまたは JavaScript からインポートする関数、メモリ、テーブル、およびグローバルを指定します。
  3. Start. WebAssembly モジュールがロードされたときに自動的に実行される関数 (基本的にメイン関数のような)。
  4. Global. モジュールのグローバル変数を宣言します。
  5. Memory. メモリをモジュールでどのように使うか定義します。
  6. Table. JavaScript オブジェクトなど WebAssembly モジュールの外部にある値にマップすることができます。これは間接的な関数呼び出しを許可する場合に特に便利です。
  7. Data. インポートした、またはローカルのメモリを初期化します。
  8. Element. インポートした、またはローカルのテーブルを初期化します。

セクションの詳細については、これらのセクションが詳しく説明しています。

次の記事

WebAssembly モジュールを使用する方法を勉強したので、WebAssembly を速くするには?を見てみましょう。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly の短期集中コース

この記事は WebAssembly と何が速くしたのかのシリーズの 3 部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。どのように WebAssembly が動いているかを理解するために、アセンブリが何でありどのようにアセンブリを生成するのかを理解することが役に立ちます。

JIT の記事では、機械とのやりとりがエイリアンとのコミュニケーションのようなものだと話しました。

A person holding a sign with source code on it, and an alien responding in binary

エイリアンの脳がどのように働いているのか、機械の脳はどのように解析してそこに入ってくるコミュニケーションを理解しているのかを見てみたいと思います。

この脳の一部は、思考に専念しています。つまり、加減算や論理演算などです。短期記憶を提供する脳の部分と、長期記憶を提供するもう一つの部分もあります。

これらの異なる部分には名前があります。

  • 思考を行う部分は算術論理ユニット (ALU) です。
  • 短期記憶はレジスタによって提供されます。
  • 長期記憶はランダムアクセスメモリ (もしくはRAM) です。

A diagram showing the CPU, including ALU and Registers, and RAM

マシン語の文章は命令と呼びます。

これらの命令の 1 つが脳に入ったらどうなりますか?それは異なることを意味する異なる部分に分割されます。

この命令が分割される方法はこの脳の配線に固有のものです。

例えばこのように配線された脳は、常に最初の 6 ビットを取り、それを ALU にパイプすることができます。 ALU は 1 と 0 の位置に基づいて、2 つのものを一緒に追加する必要があることを理解します。

この 塊は ALU にどのような操作を実行するかを指示するため、「オペコード」またはオペレーションコードと呼ばれます。

6-bits being taken from a 16-bit instruction and being piped into the ALU

次に、この脳は次の 2 つのチャンクをそれぞれ 3 ビットずつ取って、どの 2 つの数字を追加するかを決定します。 これらはレジスタのアドレスになります。

Two 3-bit chunks being decoded to determine source registers

ここで機械語の上に注釈があることに注意してください。これは人間が何が起こっているのかを理解しやすくします。これがアセンブリです。シンボリックマシンコードとも呼ばれており、人間が機械語を理解する方法です。

ここには、この機械のためのアセンブリと機械語の間にかなり直接的な関係があることがわかります。このため、さまざまな種類の機械のアーキテクチャー向けにに異なる種類のアセンブリがあります。機械の内部で別のアーキテクチャを使用している場合は、独自の方言のアセンブリが必要になる可能性があります。

だから私たちは翻訳の目標を一つだけ持っているわけではありません。これは、機械語と呼ばれる単なる言語ではありません。これは、さまざまな種類の機械語です。私たちが人としてさまざまな言語を話すように、機械は異なる言語を話します。

人からエイリアンへの翻訳では英語、ロシア語、または北京語からエイリアン語Aまたはエイリアン語Bに移行している可能性があります。プログラミング上、これはC、C ++、Rustからx86またはARMへの変換に似ています。

これらの高レベルのプログラミング言語のいずれかをこれらのアセンブリ言語のいずれかに翻訳したいと考えています (異なるアーキテクチャに対応しています)。これを行うための 1 つの方法は、各言語から各アセンブリに変換することができるさまざまな翻訳者をたくさん作成することです。

Diagram showing programming languages C, C++, and Rust on the left and assembly languages x86 and ARM on the right, with arrows between every combination

それはかなり非効率的になるでしょう。これを解決するため、ほとんどのコンパイラは少なくとも 1 つのレイヤを中間に配置します。コンパイラはこの高水準のプログラミング言語をとり、これを高レベルではないものに変換しますが、機械語のレベルでは動作しません。 それは中間表現 (IR) と呼ばれています。

Diagram showing an intermediate representation between high level languages and assembly languages, with arrows going from high level programming languages to intermediate representation, and then from intermediate representation to assembly language

これはコンパイラがこれらの上位レベルの言語のいずれかを取り、それを 1 つの IR 言語に翻訳できることを意味します。そこから、コンパイラの別の部分がその IR を取得し、ターゲットアーキテクチャに固有のものにコンパイルすることができます。

コンパイラのフロントエンドは、上位レベルのプログラミング言語を IR に変換します。コンパイラのバックエンドは、IR からターゲットアーキテクチャのアセンブリコードに移行します。

Same diagram as above, with labels for front-end and back-end

Conclusion

これはアセンブリの仕組みと、コンパイラがどのように高水準のプログラミング言語をアセンブリに変換するかです。次の記事では、WebAssembly がこれにどのように適合するかを見ていきます。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

ジャスト・イン・タイム (JIT) コンパイラの短期集中コース

この記事は WebAssembly と何が速くしたのかのシリーズの2部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。JavaScript は最初は遅かったですが、JIT と呼ばれるもののお陰で速くなりました。JIT はどのように機能しているのでしょうか?

JavaScript はブラウザ上でどのように動作しているか

開発者として JavaScript を追加すると、ゴールと問題が出てきます。

ゴール : コンピュータへ何かを伝えたいと思います

問題 : あなたとコンピュータは別の言語を話します

あなたは人間の言語を話し、コンピュータは機械語を話します。JavaScript や他の高レベルのプログラミング言語を人間の言語として考えていなくても、実際には人間の言葉です。これらは人間の認知のために開発されたものであり、コンピュータの認知のために考えられたものではありません。

JavaScript のエンジンの仕事は人間の言葉を機械が理解できるように変換することです。

人間とエイリアンがお互いに話そうとしている映画、「メッセージ」のように考えています。

A person holding a sign with source code on it, and an alien responding in binary

この映画では、人間とエイリアンは一語一語の翻訳をするわけではありません。2つのグループは世界に関して別の考え方を持っています。そして人間と機械に関してもそうです (次の記事でこの映画に関してはもっと説明します) 。

では翻訳はどのように行いますか?

プログラミングでは、機械語に翻訳するには一般的に 2 つの方法があります。

インタープリタでは、この翻訳は直ちに一行一行実施します。

A person standing in front of a whiteboard, translating source code to binary as they go

一方コンパイラは直ちに翻訳を行いません。翻訳を作成して、それを書きとめるために、事前に動作します。

A person holding up a page of translated binary

これらの翻訳処理方法にはそれぞれ長所と短所があります。

インタープリタの長所と短所

インタープリタはすぐに起動して実行できます。コードの実行を開始する前に、コンパイルの手順全体を踏む必要はありません。 最初の行を翻訳して実行するだけです。

このため、インタプリタは JavaScript のようなものに自然にフィットするように見えます。Web 開発者がコードをすぐに実行できるようにすることは重要です。

そしてそれが様々なブラウザが最初から JavaScript インタープリタを使用していた理由です。

しかし同じコードを何度も実行するときに毎回翻訳が必要というインタープリタの問題があります。例えば、ループを使用するときがそうです。この時同じ翻訳を何度も何度も実行する必要があります。

コンパイラの長所と短所

コンパイラは対照的なトレードオフを持っています。

開始時にコンパイルを行うステップを経ないといけないため、起動に少し時間がかかります。 しかし、ループ内のコードパスはそのループを通過するたびに翻訳を繰り返す必要がないため、実行速度が速くなります。

もう一つの違いはコンパイラはコードの中身を確認し、より速く動作するように編集を行う時間があることです。これらの編集は最適化と呼ばれています。

インタープリタは実行時に作業を行っているため、これらの最適化を理解するために翻訳フェーズで時間をかけられません。

ジャスト・イン・タイム コンパイラ : 2つの世界の最も優れたもの

インタプリタの非効率性を取り除く方法として、インタプリタがループを通過するたびにコードの再翻訳を続ける必要がある場合に、ブラウザがコンパイラを混在させることを開始しました。

別のブラウザではこの方法はやや異なっていますが、基本的なアイデアは同じです。モニター (別名プロファイラ) と呼ばれる 新しい機能を JavaScript エンジンへ追加しました。そのモニターは実行中のコードを監視し、何回動作したかとどのようなタイプで使われるのかを記録します。

まず最初にモニターはインタープリタを通してすべてを実行します。

Monitor watching code execution and signaling that code should be interpreted

もし同じ行を何度か通った場合、コードのその部分は “warm” と呼ばれます。もし何度も動作する場合、ここは “hot” と呼ばれます。

ベースラインコンパイラ

関数が “warm” な状態になると、JIT はそれを送信しコンパイルします。そしてコンパイル結果を保存します。

Monitor sees function is called multiple times, signals that it should go to the baseline compiler to have a stub created

関数の各行は “スタブ” にコンパイルが行われます。スタブは行番号と変数タイプによって索引付けします (なぜ重要なのかは後で説明します) 。 モニタにより同じ変数の型で同じコードを実行していることが確認した場合、コンパイルしバージョンを取り出します。

それはスピードアップするのに役立ちます。しかし、先程述べたように、コンパイラができることはもっとあります。最適化を行うための最も効率的なやり方を理解するまでには時間がかかることがあります。

ベースラインコンパイラはこれらの最適化の幾つかを行います (以下に例を示します )。長い時間作業を保留にしたくないため、これらに多くの時間をかけたくありません。

しかしコードが本当に “hot” な場合、—もしそれがいっぱい実行されていたら— このような時にさらなる最適化を実施する時間を余分にとる価値があります。

コンパイラの最適化

コードの一部がとても “hot” な時、モニターは最適化コンパイラに対してそれを送信します。これにより、保存される関数の別のより高速なものが作成されます。

Monitor sees function is called even more times, signals that it should be fully optimized

コードのより高速なバージョンを作るために、最適化コンパイラはいくつかの過程を行う必要があります。

例えば、特定のコンストラクタによって作成されたすべてのオブジェクトが同じ形状を持っていると仮定することができます。つまり、それらは常に同じプロパティ名を持ち、それらのプロパティは同じ順序で追加され、それに基づいていくつかのコーナーをカットすることができます。

最適化コンパイラはモニターが監視したコードの実行状態の情報を使用します。ループが通る前のすべてのパスが真だった場合、それらは引き続き真だとみなされます。

もちろん JavaScript を使用した場合、それらの保証はありません。あなたが 99 個の同じ形状のオブジェクトを持ってしたとしても、100 個目のオブジェクトはプロパティが無いかもしれません。

従ってコンパイルされたコードは仮定が正しいかどうかを動作させる前に確認する必要があります。もし正しければコンパイルされたコードを動作させます。もし正しくなければ、JIT は誤った仮定とみなし、最適化したコードを破棄します。

Monitor sees that types don't match expectations, and signals to go back to interpreter. Optimizer throws out optimized code

その後、実行するコードはインタプリタまたはベースラインのコンパイルされたバージョンに戻ります。このプロセスは最適化解除 (または救済) と呼ばれます。

たいていの最適化コンパイラはコードを速く動くようにしますが、時々期待されないパフォーマンスの問題が発生します。最適化した上で最適化解除することを続けるコードがある場合、ベースラインのコンパイル済みバージョンを実行するよりも遅くなります。

ほとんどのブラウザでは最適化/最適化解除のサイクルに問題が発生した時、終了させる制限を加えています。もし JIT が最適化を 10 回以上試行しそれを捨て続けた場合、試行をやめます。

最適化の例 : 型の特殊化

さまざまな種類の最適化がありますが、1 つの型を見て最適化がどのように起こるかを感じることができます。コンパイラの最適化で最も大きな成果を得たのは、型の特殊と呼ばれるものです。

JavaScriptが利用する動的型システムでは、実行時に余分な作業が必要です。例えば、次のコードで考えてみましょう。

function arraySum(arr) {
  var sum = 0;
  for (var i = 0; i &lt; arr.length; i++) {
    sum += arr;
  }
}
ループ内の += ステップは単純に思えるかもしれません。これを 1 ステップで計算できるように思えるかもしれませんが、動的な型付けのために予想以上に多くのステップが必要となります。

arr が 100 個の整数の配列であるとしましょう。コードが “warm up” すると、ベースラインコンパイラは関数内の各演算のスタブを作成します。そのため sum += arr のスタブがあり、これは += 演算を整数加算として扱います。

しかし、sum arr は整数であるとは限りません。 JavaScript では型が動的なので、ループの後の反復で、arr が文字列になる可能性があります。 整数の加算と文字列の連結は非常に異なる2つの操作なので、非常に異なる機械語にコンパイルされます。

JIT がこれを処理する方法は、複数のベースライン・スタブをコンパイルすることです。 コードの一部がモノモーフィックである (つまり、常に同じ型で呼び出される) 場合、それは 1 つのスタブを取得します。 それがポリモーフィックである場合 (あるコードから別のコードへと異なるタイプで呼び出されます) 、その操作を経たタイプの各組み合わせに対してスタブを取得します。

これは JIT はスタブを選ぶ前に多くの質問をする必要が有ることを意味します。

Decision tree showing 4 type checks

コードの各行はベースラインコンパイラに独自のスタブセットを持つため、JIT はコードの各行が実行されるたびに型のチェックを実施し続ける必要があります。そしてループを通るそれぞれのイテレーションのために、同じ質問をする必要があります。

Code looping with JIT asking what types are being used in each loop

JIT がこれらのチェックを繰り返す必要がない場合、コードはより高速に実行されます。 そして、これは最適化コンパイラが行うことの 1 つです。

最適化コンパイラでは、関数全体が一緒にコンパイルされます。 型チェックは、ループの前に実施するように移動されます。

Code looping with questions being asked ahead of time

一部の JIT ではこれをさらに最適化します。 例えば、Firefox には、整数だけを含む配列の特別な分類があります。 arr がこれらの配列のいずれかである場合、JIT は arr が整数かどうかを確認する必要はありません。これは、JIT がループに入る前にすべての型チェックを実行できることを意味します。

結論

以上が JIT の簡潔な説明です。コードを実行しながらコードを監視し、”hot” なコードパスを送信して最適化することで、JavaScript をより速く実行できます。 これにより、ほとんどの JavaScript アプリケーションのパフォーマンスが数倍に向上しました。

しかし、これらの改良によっても、JavaScript のパフォーマンスは予測できません。さらに高速化するために、JIT は実行時にいくつかのオーバーヘッドが増えています :

  • 最適化と最適化解除
  • 救済措置が起こったときのモニタの記録と復旧情報に使用されるメモリ
  • 関数のベースラインバージョンと最適化バージョンを格納するために使用されるメモリ
ここに改善の余地があります : オーバーヘッドが取り除かれ、パフォーマンスが予測可能になります。それが WebAssembly が行うことの 1 つです。

次の記事では、アセンブリとコンパイラがどのように動作するかについて、さらに詳しく説明します。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly の漫画での紹介

[この記事は”A cartoon into to WebAssembly“の翻訳です]

WebAssembly は高速です。あなたはおそらくこのことは聞いたことがあるでしょう。しかし何が WebAssembly を高速に動作することをできるようにしているのでしょうか?

このシリーズでは、なぜ WebAssembly が高速に動作するのかの説明を行いたいと思います。

WebAssembly とは何ですか?

WebAssembly は JavaScript 以外のプログラミング言語で書かれたコードをブラウザ上で実行する方法です。人々が WebAssembly が高速と言っているときは、JavaScript と比較しています。

これが二者択一の状況-つまり WebAssembly を使うか JavaScript を使うかのどちらか一方-だとは暗示したくありません。実際、開発者が同じアプリケーションで WebAssembly と JavaScript の両方を使用することを期待しています。

しかし、これら2つを比較することは有益であり、WebAssembly が持つであろうインパクトを理解することができるでしょう。

パフォーマンスの歴史を少し

JavaScript は1995年に作成されました。しかし高速に動くように設計されてはおらず、最初の10年は速くありませんでした。

その後ブラウザはより競争的になりました。

2008年、人々がパフォーマンス戦争と呼ぶ期間が始まりました。複数のブラウザに JIT とも呼ばれるジャストインタイムコンパイラが追加されました。JavaScript が実行されている最中、JIT はパターンを見て、そのパターンに基づいてコードをより高速に実行することができます。

これらの JIT の導入により、JavaScript のパフォーマンスに変革がもたらされました。 JavaScript の実行速度は 10 倍になりました。

A graph showing JS execution performance increasing sharply in 2008

パフォーマンスが改良されたことにより、JavaScript は Node.js を使ったサーバーサイドプログラミングのように、これまでに使用されることが予想されなかったものに対して使用され始めました。 パフォーマンスの向上により、全く新しい種類の問題に JavaScript を使用することが可能になりました。

もしかすると我々は今、WebAssembly によって新たな変曲点にいるのかもしれません。

A graph showing another performance spike in 2017 with a question mark next to it

では、WebAssembly を高速化する要素を理解するために詳細を掘り下げてみましょう。

背景:

過去の WebAssembly:

未来の WebAssembly:

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

Firefox 54 アドオン互換性情報

Mozilla では Firefox 57 へ向けたロードマップ を投稿しています。もしまだ読んでいなければ目を通してみてください。

Firefox 54 は 6 月 13 日 [日本時間同日深夜] リリース となります。Firefox 54 の変更点でアドオンの互換性に影響を及ぼす可能性のあるものを以下にまとめました。Firefox 54 for Developers により詳しい情報が載っていますので、こちらも併せてご覧ください。

一般

  • -moz-appearance が削除されました。この変更は chrome:// URL で読み込まれたスタイルシートには適用されませんが、XUL や JavaScript コード内のインライン CSS には影響します。

XPCOM とモジュール

WebExtensions

この一覧に載っていない変更点や間違いを見つけたらコメント欄でお知らせください。もしあなたのアドオンが Firefox 54 で動かなくなった場合は、筆者の方でも調査したいと思います。

AMO に登録されているアドオンの 自動互換性テストと対応バージョンの更新 は数週間以内に行われますので、AMO に Firefox 53 対応のアドオンを登録している方は後日メールをチェックしてみてください。

Firefox 53 アドオン互換性情報

Mozilla では Firefox 57 へ向けたロードマップ を投稿しています。もしまだ読んでいなければ目を通してみてください。Firefox 53 は重要なマイルストーンで、AMO で旧式アドオンの「新規」受付が停止され、マルチプロセス Firefox が初期設定で有効化され、WebExtensions API を経由しないアドオンからのバイナリアクセスが制限されます。

Firefox 53 は 4 月 18 日 [日本時間同日深夜] リリース となります。Firefox 53 の変更点でアドオンの互換性に影響を及ぼす可能性のあるものを以下にまとめました。Firefox 53 for Developers により詳しい情報が載っていますので、こちらも併せてご覧ください。

一般

パスワードマネージャー

以下 3 つの変更は関連しており、アドオンが findSlotByName("") を呼び出してマスターパスワードが設定されているかどうかを確認できなくなったことが主な影響として挙げられます。該当するコードの変更方法は こちら の例を見てください。

XPCOM とモジュール

WebExtensions

この一覧に載っていない変更点や間違いを見つけたらコメント欄でお知らせください。もしあなたのアドオンが Firefox 53 で動かなくなった場合は、筆者の方でも調査したいと思います。

AMO に登録されているアドオンの 自動互換性テストと対応バージョンの更新 は数週間以内に行われますので、AMO に Firefox 52 対応のアドオンを登録している方は後日メールをチェックしてみてください。

JavaScript ゲームにおける操作方法

[この記事は、Control mechanisms in JavaScript games の翻訳です。]

ラップトップやデスクトップをはじめ、スマートフォンやタブレット、TV や冷蔵庫でさえも、共通してブラウザを持つようになりました。ということは、それらの端末上で HTML5 ゲームをプレイできるようになったという風にも思えますが、実際にプレイするためには、ゲーム画面を描画するのと同時に、何らかの方法でゲームを操作できなければなりません。キーボードやマウスはもちろん、タッチ操作、ゲームパッド、TV リモコン、さらにはバナナに至るまで、それぞれのプラットフォームごとに適した選択肢はたくさん存在します(MDN では、control mechanisms に関する一連の記事でそれらのインターフェースを取り上げています)。

captainrogers2-cover

今回は、実際のプロジェクトにおける例として、Phaser を用いて作成された Captain Rogers: Battle at Andromeda のデモを使ってぞれぞれの操作方法の実装方法について紹介します。プラットフォームの違いによってゲームの操作性がどう変わっていくかをご確認ください。また、異なるプラットフォームに対応する際に別々にビルドする必要がないということもお見せします。ウェブのマルチプラットフォームという特性によって、コーディングに多くの労力を費やすことなく複数のデバイスに対応できることがお分かりいただけると思います。

controls-purejsgame

また、GitHub には、小さいですが純粋な JavaScript によるデモがオープンソースとして公開されています。操作方法がどのように実装されているかを実際に確認できるので、ご自身のゲーム開発プロジェクトでもお使いいただけます。以下に、主な項目をハイライトでご紹介しますので、このままお読みいただいても良いですし、今すぐコーディングを開始しても良いでしょう。

モバイル タッチ

HTML5 ゲームではモバイルファースト開発が人気ですので、まずはモバイル タッチへの対応から見ていきましょう。

document.addEventListener("touchstart", touchHandler);
document.addEventListener("touchmove", touchHandler);
function touchHandler(e) {
    if(e.touches) {
        playerX = e.touches[0].pageX - canvas.offsetLeft - playerWidth / 2;
        playerY = e.touches[0].pageY - canvas.offsetTop - playerHeight / 2;
    }
}

たった数行の JavaScript コードですが、これがゲーム内でタッチ操作を実現するために必要な最低限のコードです。これだけでモバイル端末上で機能します。最初の 2 行は、タッチ操作に対するイベントリスナーの設定です。それぞれ、画面をタッチしたとき、および、指で画面をなぞったときに対応します。関数内では、タッチ操作が行われたことを確認し、プレイヤーの座標を設定しています。これにより、ゲームの Canvas 上でプレイヤーの機体が正しい位置に描画されます。

上記はベースとなるコードですので、ここからマルチタッチやジェスチャーなど様々な発展形を実装することができます(むしろ、実装した方がいいでしょう)。ゲームの種類や、何をどのように操作するかということを考慮して、実装する操作方法を決めましょう。また、移動用の矢印や発射ボタンなど、特定のアクションを起こすためのボタンを画面上に設置することもできます。詳しくは、Mobile touch controls をご覧ください。

デスクトップ マウスとキーボード

移動用の矢印について触れましたが、モバイル端末向けに画面上に矢印を表示するのはもちろん、デスクトップ向けにそれらを実装することも可能です。ゲーム内のキャラクターを動かす方法としては、カーソルキーや WASD キーがよく使われます。例えば、以下のコードではカーソルキーを使用しています。

document.addEventListener("keydown", keyDownHandler);
function keyDownHandler(e) {
    if(e.keyCode == 39) {
        rightPressed = true;
    }
    else if(e.keyCode == 37) {
        leftPressed = true;
    }
    if(e.keyCode == 40) {
        downPressed = true;
    }
    else if(e.keyCode == 38) {
        upPressed = true;
    }
}

押されたキーを定常的に検出しその情報を保存するということですので、これは描画ループとして以下のように処理することが可能です。

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if(rightPressed) {
        playerX += 5;
    }
    else if(leftPressed) {
        playerX -= 5;
    }
    if(downPressed) {
        playerY += 5;
    }
    else if(upPressed) {
        playerY -= 5;
    }
    ctx.drawImage(img, playerX, playerY);
    requestAnimationFrame(draw);
}

プレイヤーの位置 x および y を表す変数が調整され、機体画像が新たな位置に表示されます。

controls-howtoplay

一方、デスクトップにおけるマウスは、コーディングにおいてモバイルのタッチと非常に似ています。タッチやクリックが起こった場所に関する情報を取得し、プレイヤーの位置を更新するだけです。

document.addEventListener("mousemove", mouseMoveHandler);
function mouseMoveHandler(e) {
    playerX = e.pageX - canvas.offsetLeft - playerWidth / 2;
    playerY = e.pageY - canvas.offsetTop - playerHeight / 2;
}

マウスポインターの位置が変化するたびに mousemove イベントが検出されるので、プレイヤーの位置がマウスポインターの中央の位置に来るように調整します。こうして、デスクトップのキーボードとマウスの両方で操作可能なゲームを実現することができます。さらに詳しく知りたい方は、Desktop mouse and keyboard controls をご覧ください。

ゲームパッド

個人的に大好きな操作方法です。プレゼンテーションを行う際は、HTML5 ベースの自分のスライドを Gamepad APIを使って操作しているくらいです。ゲームパッドについては、すでに数回お話したこともありますし、ゲームもいくつか実装したこともあります。詳しくは、Gamepad API Content Kit に全て記載しています。 とにかく、コンピューター上でコンソール気分を体験できることは本当に素晴らしいと思いますし、それがウェブ技術によって実現されているということも素晴らしいと思います!ゲームパッドを使用すれば、Captain Rogers のプレイ体験がさらに向上しますし、単純に良い気分になります。

controls-gamepadinfo

Gamepad API への対応はタッチやキーボードよりもやや複雑になりますが、それでも非常に簡単です。

window.addEventListener("gamepadconnected", gamepadHandler);
function gamepadHandler(e) {
    controller = e.gamepad;
}
function gamepadUpdateHandler() {
    buttonsPressed = [];
    if(controller.buttons) {
        for(var b=0; b<controller.buttons.length; b++) {
            if(controller.buttons[i].pressed) {
                buttonsPressed.push(b);
            }
        }
    }
}
function gamepadButtonPressedHandler(button) {
    var press = false;
    for(var i=0; i<buttonsPressed.length; i++) {
        if(buttonsPressed[i] == button) {
            press = true;
        }
    }
    return press;
}

ゲームパッドが接続されるとイベントが発生し、ゲームパッドに関するデータの参照が変数に保存されます。以降は、その変数を使用してゲームパッドに関するデータにアクセスします。更新が起こると、押されたボタンの配列が新たに生成されるため、常に最新の状態が保持されます。また、あるボタンが押されたかどうかを判定するために配列をループ処理する関数もあります。これらはキーボードチェックと同様に、描画ループの中で使用できます。

function draw() {
    // ...
    gamepadUpdateHandler();
    if(gamepadButtonPressedHandler(0)) {
        playerY -= 5;
    }
    else if(gamepadButtonPressedHandler(1)) {
        playerY += 5;
    }
    if(gamepadButtonPressedHandler(2)) {
        playerX -= 5;
    }
    else if(gamepadButtonPressedHandler(3)) {
        playerX += 5;
    }
    if(gamepadButtonPressedHandler(11)) {
        alert('BOOM!');
    }
    // ...
}

こうすることで、プレイヤーの機体を DPad ボタン(十字キー)で操作したり、A ボタンで爆弾を爆発させることができるようになります。このほか、スティック操作を検知することもできますし、独自の小さいライブラリを作ってゲームパッドからの入力を処理することもできます。詳しくは、Desktop gamepad controls をご覧ください。

特殊な操作方法

さらにもっと進んで、リビングにある大型 TV のリモコンを使ったり、ラップトップの前で手を振ったり、食材とPCを線で繋いでその食材を押したりすることで、ゲームをプレイすることも可能です。

controls-tvremote

例えば、Panasonic 製の TV リモコンはキーボードイベントを再利用しているので、対応が非常に簡単です。リモコンの方向矢印はキーボードのカーソルキーと全く同じコード(37383940)を使用しているので、すぐに対応できます。リモコン特有のボタンについては、こちらに一覧がありますので、詳細情報とともにご覧ください。

controls-leapmotion

また、リモコンのボタンを押す代わりに、Leap Motion 機器の機能を使用して自分の手の位置や他のパラメーターを検知することで、何にも触れることなくプレイヤーの機体を操作することもできます。あらかじめ定義されている loop 内で、手に関する情報を取得できるので、

Leap.loop({
    hand: function(hand) {
        horizontalDegree = Math.round(hand.roll() * toDegrees);
        verticalDegree = Math.round(hand.pitch() * toDegrees);
        grabStrength = hand.grabStrength;
    }
});

それを使用してプレイヤーの位置を更新します。

function draw() {
    // ...
    if(horizontalDegree > degreeThreshold) {
        playerX -= 5;
    }
    else if(horizontalDegree < -degreeThreshold) {
        playerX += 5;
    }
    if(verticalDegree > degreeThreshold) {
        playerY += 5;
    }
    else if(verticalDegree < -degreeThreshold) {
        playerY -= 5;
    }
    if(grabStrength == 1) {
        alert('BOOM!');
    }
    // ...
}

その他にも、Unconventional controlsでは、 ドップラー効果や Proximity API、MaKey MaKeyなど興味深い操作方法の実装方法を紹介しています。

まとめ

最近は、HTML5 ゲームをプレイするのに使える機器がますます増えています。ご自身の腕時計も使えるかもしれませんし、はたまた音声認識によるウェブゲームも良いかもしれません。まさに、可能性は無限です。覚えておいて頂きたいのは、多くの操作方法に対応しているほど、ゲームにとっては良いことだということです。なぜなら、幅広い種類の機器を用いて、どんなプラットフォームでもプレイ可能になるからです。ぜひ、ブラウザがもたらす可能性を存分に活用しましょう。