Chrome Dev Summit 2020 で、WebAssembly アプリケーションに対する Chrome のデバッグ サポートを初めてデモンストレーションしました。それ以来、チームは大規模で大規模なアプリケーションのデベロッパー エクスペリエンスを拡張するために、多大なエネルギーを投資してきました。この投稿では、さまざまなツールに追加された(または実際に行われた)ノブとその使用方法について説明します。
スケーラブルなデバッグ
それでは、2020 年の投稿で中断したところから再開しましょう。こちらが当時の例でした。
#include <SDL2/SDL.h>
#include <complex>
int main() {
// Init SDL.
int width = 600, height = 600;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window;
SDL_Renderer* renderer;
SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
&renderer);
// Generate a palette with random colors.
enum { MAX_ITER_COUNT = 256 };
SDL_Color palette[MAX_ITER_COUNT];
srand(time(0));
for (int i = 0; i < MAX_ITER_COUNT; ++i) {
palette[i] = {
.r = (uint8_t)rand(),
.g = (uint8_t)rand(),
.b = (uint8_t)rand(),
.a = 255,
};
}
// Calculate and draw the Mandelbrot set.
std::complex<double> center(0.5, 0.5);
double scale = 4.0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
std::complex<double> point((double)x / width, (double)y / height);
std::complex<double> c = (point - center) * scale;
std::complex<double> z(0, 0);
int i = 0;
for (; i < MAX_ITER_COUNT - 1; i++) {
z = z * z + c;
if (abs(z) > 2.0)
break;
}
SDL_Color color = palette[i];
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_RenderDrawPoint(renderer, x, y);
}
}
// Render everything we've drawn to the canvas.
SDL_RenderPresent(renderer);
// SDL_Quit();
}
まだかなり小さな例であり、大規模なアプリケーションで見られるような実際の問題に気づくことはないでしょうが、新機能について説明することはできます。設定は簡単で、ぜひご自身でお試しください。
前回の投稿では、この例をコンパイルしてデバッグする方法について説明しました。ここでも繰り返しますが、//performance// も少し見てみましょう。
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH
このコマンドは、3MB の Wasm バイナリを生成します。その大部分は、ご想像のとおりデバッグ情報です。これは、llvm-objdump
ツール [1] で確認できます。次に例を示します。
$ llvm-objdump -h mandelbrot.wasm
mandelbrot.wasm: file format wasm
Sections:
Idx Name Size VMA Type
0 TYPE 0000026f 00000000
1 IMPORT 00001f03 00000000
2 FUNCTION 0000043e 00000000
3 TABLE 00000007 00000000
4 MEMORY 00000007 00000000
5 GLOBAL 00000021 00000000
6 EXPORT 0000014a 00000000
7 ELEM 00000457 00000000
8 CODE 0009308a 00000000 TEXT
9 DATA 0000e4cc 00000000 DATA
10 name 00007e58 00000000
11 .debug_info 000bb1c9 00000000
12 .debug_loc 0009b407 00000000
13 .debug_ranges 0000ad90 00000000
14 .debug_abbrev 000136e8 00000000
15 .debug_line 000bb3ab 00000000
16 .debug_str 000209bd 00000000
この出力には、生成された Wasm ファイル内のすべてのセクションが表示されます。そのほとんどは標準の WebAssembly セクションですが、名前が .debug_
で始まるカスタム セクションもいくつかあります。バイナリにはデバッグ情報が含まれています。すべてのサイズを合計すると、デバッグ情報は 3 MB のファイルのうち約 2.3 MB を占めていることがわかります。emcc
コマンドの time
も行うと、マシンでは実行に約 1.5 秒かかったことがわかります。これらの数値は良い基準値になりますが、非常に小さいので、誰も注目しないでしょう。しかし、実際のアプリケーションでは、デバッグ バイナリのサイズがギガバイト単位にまで達し、ビルドに何分もかかる可能性があります。
バイナリのスキップ
Emscripten で Wasm アプリケーションを構築する場合、最後のビルドステップの一つは Binaryen オプティマイザーの実行です。Binaryen は、WebAssembly(類似の)バイナリの最適化と合法化の両方を行うコンパイラ・ツールキットです。ビルドの一環として Binaryen を実行するのはかなりのコストがかかりますが、これが必要になるのは特定の条件下のみです。デバッグビルドでは、Binaryen パスが不要であれば、ビルド時間を大幅に短縮できます。最も一般的な Binaryen パスは、64 ビット整数値を含む関数シグネチャを正当化するためのものです。-sWASM_BIGINT
を使用して WebAssembly BigInt 統合にオプトインすることで、これを回避できます。
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
適切な測定を行うために -sERROR_ON_WASM_CHANGES_AFTER_LINK
フラグをスローしています。Binaryen が実行中であることを検出し、バイナリを予期せず書き換えるのに役立ちます。このようにして、確実に速いペースで進めることができます。
ここで取り上げた例は非常に小さいものの、Binaryen を省略した場合の影響は依然として確認できます。time
によると、このコマンドは 1 秒を少し下回るだけで実行されるため、以前よりも 0.5 秒速くなります。
高度な調整
入力ファイルのスキャンをスキップする
通常、Emscripten プロジェクトをリンクすると、emcc
はすべての入力オブジェクト ファイルとライブラリをスキャンします。これは、プログラム内の JavaScript ライブラリ関数とネイティブ シンボルとの間の正確な依存関係を実装するためです。大規模なプロジェクトでは、(llvm-nm
を使用して)入力ファイルを追加でスキャンすると、リンク時間が大幅に長くなることがあります。
代わりに -sREVERSE_DEPS=all
を指定して、JavaScript 関数の可能なネイティブ依存関係をすべて含めるように emcc
に指示することもできます。コードサイズのオーバーヘッドは小さくなりますが、リンク時間を短縮でき、デバッグビルドに役立ちます。
この例のような小規模なプロジェクトでは、実質的な違いはありませんが、プロジェクトに数百、数千ものオブジェクト ファイルがある場合、リンク時間を大幅に短縮できます。
「name」セクションを削除する
大規模なプロジェクト、特に C++ テンプレートを頻繁に使用するプロジェクトでは、WebAssembly の「name」セクションが非常に大きくなる可能性があります。この例では、ファイルサイズ全体のごく一部に過ぎません(上記の llvm-objdump
の出力を参照)。ただし、非常に大きくなる場合もあります。アプリケーションの「name」セクションが非常に大きく、デバッグ情報が dwarf デバッグ情報で十分である場合は、「name」セクションを削除することをおすすめします。
$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm
これにより、DWARF デバッグ セクションは保持したまま、WebAssembly の「name」セクションが削除されます。
デバッグ分裂
デバッグデータが大量にあるバイナリは、ビルド時間だけでなくデバッグ時間にもプレッシャーがかかります。デバッガは、データを読み込んで、「ローカル変数 x の型は?」のようなクエリにすばやく応答できるようにインデックスを作成する必要があります。
デバッグの分断により、バイナリのデバッグ情報を 2 つの部分に分割できます。1 つはバイナリに残る部分、もう 1 つは別のいわゆる DWARF オブジェクト(.dwo
)ファイルに含まれる部分です。これは、-gsplit-dwarf
フラグを Emscripten に渡すことで有効にできます。
$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
以下では、さまざまなコマンドと、デバッグデータなし、デバッグデータあり、最後にデバッグデータとデバッグ Fission の両方でコンパイルすると、どのようなファイルが生成されるかを示します。
DWARF データを分割する際、デバッグデータの一部はバイナリとともに存在しますが、大部分は mandelbrot.dwo
ファイルに配置されます(上記の図を参照)。
mandelbrot
の場合、ソースファイルは 1 つだけですが、通常はプロジェクトはこれよりも大きく、複数のファイルが含まれます。デバッグ分裂は、それらごとに .dwo
ファイルを生成します。デバッガの現在のベータ版(0.1.6.1615)でこの分割されたデバッグ情報を読み込めるようにするには、次のように、これらすべてをいわゆる DWARF パッケージ(.dwp
)にバンドルする必要があります。
$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp
個々のオブジェクトから DWARF パッケージをビルドすることには、追加のファイルを 1 つ提供するだけでよいという利点があります。現在は、今後のリリースでも個々のオブジェクトをすべて読み込むよう取り組んでいます。
DWARF 5 とは
お気づきかもしれませんが、上記の emcc
コマンドに、-gdwarf-5
という別のフラグが追加されています。現在はデフォルトでなくなっている DWARF シンボルのバージョン 5 を有効にすることも、デバッグをより迅速に開始するための手法です。これにより、デフォルトのバージョン 4 では除外されたメインバイナリに特定の情報が保存されます。具体的には、メインバイナリからソースファイル一式を特定できます。これによりデバッガは、完全なシンボルデータの読み込みや解析を行わずに、完全なソースツリーの表示やブレークポイントの設定といった基本的なアクションを実行できます。これにより、分割シンボルを使用したデバッグが大幅に高速化されるため、常に -gsplit-dwarf
と -gdwarf-5
のコマンドライン フラグを組み合わせて使用します。
DWARF5 デバッグ形式を使用すると、別の便利な機能も利用できます。これにより、-gpubnames
フラグを渡すと生成されるデバッグデータに名前インデックスが追加されます。
$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
デバッグ セッション中は、変数や型を検索する場合などにエンティティを名前で検索すると、シンボル ルックアップがよく行われます。名前インデックスは、名前を定義するコンパイル単位を直接指すことで、この検索を高速化します。名前インデックスがない場合、デバッグデータ全体を徹底的に検索して、探している名前付きエンティティを定義する正しいコンパイル単位を見つける必要があります。
参考情報: デバッグデータを確認する
llvm-dwarfdump
を使用すると、DWARF データを確認できます。試してみましょう。
llvm-dwarfdump mandelbrot.wasm
これにより、デバッグ情報がある「コンパイル ユニット」(大まかに言えばソースファイル)の概要がわかります。この例では、mandelbrot.cc
のデバッグ情報のみがあります。[General] では、スケルトン ユニットがあることがわかります。つまり、このファイルには不完全なデータがあり、残りのデバッグ情報を含む別の .dwo
ファイルがあるということです。
このファイル内の他のテーブルも参照できます。(llvm-dwarfdump -debug-line
を使用してみてください)。
別の .dwo
ファイルに含まれるデバッグ情報を確認することもできます。
llvm-dwarfdump mandelbrot.dwo
要約: デバッグ分裂を使用する利点は何ですか?
大規模なアプリケーションを扱う場合、デバッグ情報を分割すると次のようなメリットがあります。
リンクの高速化: リンカーがデバッグ情報全体を解析する必要がなくなりました。リンカーは通常、バイナリ内の DWARF データ全体を解析する必要があります。デバッグ情報の大部分を別々のファイルに分けることで、リンカーが処理するバイナリが小さくなり、リンク時間が短縮されます(特に大規模なアプリケーションの場合)。
デバッグの高速化: 一部のシンボル ルックアップで、
.dwo
/.dwp
ファイル内の追加シンボルの解析をスキップできます。一部のルックアップ(wasm から C++ ファイルの行マッピングのリクエストなど)では、追加のデバッグデータを調べる必要はありません。これにより、追加のデバッグデータを読み込んで解析する必要がなく、時間を節約できます。
1: システムに最新バージョンの llvm-objdump
がなく、emsdk
を使用している場合は、emsdk/upstream/bin
ディレクトリにあります。
プレビュー チャンネルをダウンロードする
デフォルトの開発ブラウザとして Chrome の Canary、Dev、または Beta を使用することを検討してください。これらのプレビュー チャンネルを使用すると、DevTools の最新機能にアクセスしたり、最先端のウェブ プラットフォーム API をテストしたり、ユーザーに先駆けてサイトの問題を検出したりできます。
Chrome DevTools チームへのお問い合わせ
この投稿で紹介する新機能や変更点、またはその他の DevTools に関連する内容については、以下のオプションを使って議論してください。
- ご提案やフィードバックは、crbug.com からお送りください。
- DevTools の問題を報告するには、その他のオプション() >ヘルプ >DevTools で DevTools の問題を報告します。
- @ChromeDevTools でツイートしてください。
- DevTools の新機能に関する YouTube 動画または DevTools のヒントの YouTube 動画にコメントを残してください。