llvmのツールチェインでコードカバレッジを取得してみます。
ここでのコードカバレッジは、処理の実行数のことです。例えば、以下の左から2番目の列がコードカバレッジです。ライン単位の実行回数がわかります。
1| |#include <iostream>
2| |
3| 1|int main() { // mainは1回
4| 1| int t = 0;
5| 11| while(t < 10) { // 真偽評価は11回
6| 10| t++; // whileが真となるのは10回
7| 10| if(t%3 == 0) {
8| 3| std::cout << t << std::endl; // ifが真となるのは3回
9| 3| }
10| 10| }
11| 1| return 0;
12| 1|}
ツール
- clang/clang++: llvmのc/c++コンパイラ
- llvm-profdata: プロファイルを整形・マージするツール
- llvm-cov: カバレッジデータと実行バイナリを読み込み、コードカバレッジを出力するツール
MacOSではllvmのツールチェインをインストールすることで使えるようになるようです。
$ brew install llvm # LLVMツールチェインをインストール
...
$ export PATH="/opt/homebrew/opt/llvm/bin:$PATH
方法
取得方法は以下の4ステップです。
- プロファイルオプションをつけてコンパイル
- 実行 & ロープロファイルを取得
- ロープロファイルをプロファイルデータに変換
- プロファイルデータの表示
1. プロファイルオプションをつけてコンパイル
clangの場合、-fcoverage-mapping
と-fprofile-instr-generate
をつけてコンパイルする必要があるようです。
$ clang++ -O0 -fcoverage-mapping -fprofile-instr-generate=a.profraw -o a.out a.cc
-fprofile-instr-generate
はプロファイルのファイル名を指定する事ができます。
コンパイルが最適化すると、処理が変わることもあるので-O0
オプションをつけています。
2. 実行 & ロープロファイルを取得
コンパイルしたファイルを実行すると、実行時情報を記録したロープロファイルデータが作られます。
$ ./a.out # a.profrawにプロファイルが記録される
3. ロープロファイルをプロファイルデータに変換
ロープロファイルは、そのままの形式ではllvm-cov
に読み込ませることができないので、llvm-profdata
を使って変換します。
$ llvm-profdata merge -o a.profdata a.profraw
このコマンドはmerge
でロープロファイルを2つ入れた場合はマージして変換することができます。
4. プロファイルデータの表示
llvm-cov
を使ってカバレッジを表示します。
実行ファイルも入力にする必要があります。
$ llvm-cov show -show-line-counts -instr-profile=a.profdata a.out
1| |#include <iostream>
2| |
3| 1|int main() {
4| 1| int t = 0;
5| 11| while(t < 10) {
6| 10| t++;
7| 10| if(t%3 == 0) {
8| 3| std::cout << t << std::endl;
9| 3| }
10| 10| }
11| 1| return 0;
12| 1|}
これでコードカバレッジを確認できました。
因みに、プロファイルオプション-fprofile-instr-generate
はコードの基本ブロック単位にカウンタ(インストルメント)を入れて実行回数を記録できると理解しています。途中に多数のプロファイリング処理が入るので、このオプションでコンパイルした場合は実行時間が長くなります。
実際にIRを見てみると、各ブロックでカウンタの外部関数と思われる@__profc_main
を呼ぶ形になっています。
プロファイルオプションをつけた時のIR
; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable(sync)
define noundef i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i32, align 4
store i32 0, ptr %1, align 4
%3 = load i64, ptr @__profc_main, align 8
%4 = add i64 %3, 1
store i64 %4, ptr @__profc_main, align 8
store i32 0, ptr %2, align 4
br label %5
5: ; preds = %22, %0
%6 = load i32, ptr %2, align 4
%7 = icmp slt i32 %6, 10
br i1 %7, label %8, label %23
8: ; preds = %5
%9 = load i64, ptr getelementptr inbounds ([3 x i64], ptr @__profc_main, i32 0, i32 1), align 8
%10 = add i64 %9, 1
store i64 %10, ptr getelementptr inbounds ([3 x i64], ptr @__profc_main, i32 0, i32 1), align 8
%11 = load i32, ptr %2, align 4
%12 = add nsw i32 %11, 1
store i32 %12, ptr %2, align 4
%13 = load i32, ptr %2, align 4
%14 = srem i32 %13, 3
%15 = icmp eq i32 %14, 0
br i1 %15, label %16, label %22
16: ; preds = %8
%17 = load i64, ptr getelementptr inbounds ([3 x i64], ptr @__profc_main, i32 0, i32 2), align 8
%18 = add i64 %17, 1
store i64 %18, ptr getelementptr inbounds ([3 x i64], ptr @__profc_main, i32 0, i32 2), align 8
%19 = load i32, ptr %2, align 4
%20 = call noundef nonnull align 8 dereferenceable(8) ptr @_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEi(ptr noundef nonnull align 8 dereferenceable(8) @_ZNSt3__14coutE, i32 noundef %19)
%21 = call noundef nonnull align 8 dereferenceable(8) ptr @_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsB8ne180100EPFRS3_S4_E(ptr noundef nonnull align 8 dereferenceable(8) %20, ptr noundef @_ZNSt3__14endlB8ne180100IcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_)
br label %22
22: ; preds = %16, %8
br label %5, !llvm.loop !6
23: ; preds = %5
ret i32 0
}
オリジナルのIR
; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable(sync)
define noundef i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i32, align 4
store i32 0, ptr %1, align 4
store i32 0, ptr %2, align 4
br label %3
3: ; preds = %16, %0
%4 = load i32, ptr %2, align 4
%5 = icmp slt i32 %4, 10
br i1 %5, label %6, label %17
6: ; preds = %3
%7 = load i32, ptr %2, align 4
%8 = add nsw i32 %7, 1
store i32 %8, ptr %2, align 4
%9 = load i32, ptr %2, align 4
%10 = srem i32 %9, 3
%11 = icmp eq i32 %10, 0
br i1 %11, label %12, label %16
12: ; preds = %6
%13 = load i32, ptr %2, align 4
%14 = call noundef nonnull align 8 dereferenceable(8) ptr @_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEi(ptr noundef nonnull align 8 dereferenceable(8) @_ZNSt3__14coutE, i32 noundef %13)
%15 = call noundef nonnull align 8 dereferenceable(8) ptr @_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsB8ne180100EPFRS3_S4_E(ptr noundef nonnull align 8 dereferenceable(8) %14, ptr noundef @_ZNSt3__14endlB8ne180100IcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_)
br label %16
16: ; preds = %12, %6
br label %3, !llvm.loop !5
17: ; preds = %3
ret i32 0
}
MacOS: Sonoma v14.5
llvm v18.1.8
Homebrew clang version 18.1.8
Target: arm64-apple-darwin23.5.0
Thread model: posix
InstalledDir: /opt/homebrew/opt/llvm/bin