LLVMコードのバイセクト

はじめに

git bisectは、バグの原因となったリビジョンを見つけるのに便利なツールです。

このドキュメントでは、git bisectの使い方について説明します。LLVMは主に線形な履歴を持っていますが、いくつかのプロジェクトを追加したマージコミットがあり、これらのプロジェクトの線形履歴をマージしています。その結果、LLVMリポジトリには複数のルートがあります。1つの「通常の」ルートと、ツリー外で開発されて後でマージされた各トップレベルプロジェクトのルートです。2020年初頭現在、このようなマージされたプロジェクトはMLIRのみですが、flangも同様の方法でまもなくマージされる可能性があります。

基本操作

良い概要については、https://git.dokyumento.jp/docs/git-bisectを参照してください。要約すると

git bisect start
git bisect bad main
git bisect good f00ba

gitは、その間のリビジョンをチェックアウトします。そのリビジョンで問題を再現し、git bisect goodまたはgit bisect badを実行します。

現在のコミットで再現できない場合(ビルドが壊れている可能性があります)、git bisect skipを実行すると、gitは近くの代替コミットを選択します。

(バイセクトを中止するには、git bisect resetを実行します。gitがリセットできないと文句を言う場合は、通常のgit checkout -f main; git reset --hard origin/mainを実行して再試行してください)。

git bisect run

1つのバイセクトステップでは、多くの場合、最初にclangをビルドし、次に新しくビルドされたclangで大きなコードベースをコンパイルする必要があります。これは時間がかかるため、完全に自動化できれば理想的です。問題を自動的に再現する実行スクリプトを作成すれば、git bisect runがこれを実行できます。スクリプトの作成には10~20分かかりますが、ほとんどの場合、その価値があります。バイセクトの実行中は(このドキュメントの作成など)他の作業を行うことができます。

実行スクリプトの例を次に示します。これは、llvm-project内にあり、CMakeがNinjaを使用するように構成された兄弟のllvm-build-projectビルドディレクトリがあることを前提としています。現在のディレクトリには、trunkでclangをクラッシュさせるファイルrepro.cがありますが、リビジョンf00baでは正常に動作しました。

# Build clang. If the build fails, `exit 125` causes this
# revision to be skipped
ninja -C ../llvm-build-project clang || exit 125

../llvm-build-project/bin/clang repro.c

実行スクリプトが機能することを確認するために、./run.shを手動で実行し、スクリプトが機能するまで調整してから、スクリプトの結果に基づいてgit bisect goodまたはgit bisect badを手動で1回実行し(スクリプトの実行後にecho $?を確認)、その後にのみgit bisect run ./run.shを実行します。実行スクリプトを実行可能としてマークすることを忘れないでください。 git bisect runはそれを確認しません。実行スクリプトは毎回失敗したと仮定します。

実行スクリプトが機能したら、git bisect run ./run.shを実行すると、数時間後に回帰の原因となったコミットがわかります。

(これは非常に単純な実行スクリプトです。多くの場合、新しくビルドされたclangを使用して別のプロジェクトをビルドし、そのプロジェクトのビルド済みの実行可能ファイルをスクリプトで実行する必要があります。)

複数のルートにまたがるバイセクト

LLVMの履歴の現在の様子を次に示します。

A-o-o-......-o-D-o-o-HEAD
              /
  B-o-...-o-C-

Aは、LLVMで最初のコミット、97724f18c79cです。

Bは、MLIRの最初のコミット、aed0d21a62dbです。

Dは、MLIRをメインのLLVMリポジトリにマージしたマージコミット、0f0d0ed1c78fです。

Cは、マージされる前のMLIRの最後のコミット、0f0d0ed1c78f^2です。(^n修飾子は、マージコミットのn番目の親を選択します。)

git bisectは、すべての親リビジョンを調べます。MLIRのマージ方法のため、C以前のリビジョンでは、mlir/ディレクトリのみが存在し、他のものは存在しません。

2020年初頭現在、到達可能なすべてのコミットに下降しないように指示するgit bisectのフラグはありません。理想的には、Dの最初の親のみをたどるように指示したいでしょう。

最良の回避策は、ディレクトリのリストをgit bisectに渡すことです。バグがllvm、clang、またはcompiler-rtの変更によるものであることがわかっている場合は、次を使用します。

git bisect start -- clang llvm compiler-rt

このようにして、mlir内のコミットは評価されません。

または、git bisect skip aed0d21a6 aed0d21a6..0f0d0ed1c78fはそのブランチのすべてのコミットを明示的にスキップします。高速なマシンでは1.5分かかるだけで、git bisect logの出力が読みにくくなります。(aed0d21a6は2回リストされています。gitの範囲は左側にリストされているリビジョンを除外するため、明示的に無視する必要があります。)

その他の情報源

https://git.dokyumento.jp/book/en/v2/Git-Tools-Revision-Selection