llvm-covのカバレッジ取得方法（備忘録）

llvmのツールチェインでコードカバレッジを取得してみます。
ここでのコードカバレッジは、処理の実行数のことです。例えば、以下の左から2番目の列がコードカバレッジです。ライン単位の実行回数がわかります。

```c++: llvm-cov show -show-line-counts
    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++](https://clang.llvm.org/docs/ClangCommandLineReference.html): llvmのc/c++コンパイラ
- [llvm-profdata](https://llvm.org/docs/CommandGuide/llvm-profdata.html): プロファイルを整形・マージするツール
- [llvm-cov](https://llvm.org/docs/CommandGuide/llvm-cov.html): カバレッジデータと実行バイナリを読み込み、コードカバレッジを出力するツール

MacOSではllvmのツールチェインをインストールすることで使えるようになるようです。

```console
$ brew install llvm # LLVMツールチェインをインストール
...
$ export PATH="/opt/homebrew/opt/llvm/bin:$PATH
```

## 方法

取得方法は以下の4ステップです。

1. プロファイルオプションをつけてコンパイル
2. 実行 & ロープロファイルを取得
3. ロープロファイルをプロファイルデータに変換
4. プロファイルデータの表示

#### 1. プロファイルオプションをつけてコンパイル

clangの場合、`-fcoverage-mapping`と`-fprofile-instr-generate`をつけてコンパイルする必要があるようです。

```console
$ clang++ -O0 -fcoverage-mapping -fprofile-instr-generate=a.profraw -o a.out a.cc
```
`-fprofile-instr-generate`はプロファイルのファイル名を指定する事ができます。
コンパイルが最適化すると、処理が変わることもあるので`-O0`オプションをつけています。

#### 2. 実行 & ロープロファイルを取得

コンパイルしたファイルを実行すると、実行時情報を記録したロープロファイルデータが作られます。

```console
$ ./a.out # a.profrawにプロファイルが記録される
```

#### 3. ロープロファイルをプロファイルデータに変換

ロープロファイルは、そのままの形式では`llvm-cov`に読み込ませることができないので、`llvm-profdata`を使って変換します。

```console
$ llvm-profdata merge -o a.profdata a.profraw
```

このコマンドは`merge`でロープロファイルを2つ入れた場合はマージして変換することができます。

#### 4. プロファイルデータの表示

`llvm-cov`を使ってカバレッジを表示します。
実行ファイルも入力にする必要があります。

```txt
$ 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`を呼ぶ形になっています。

<details><summary>プロファイルオプションをつけた時のIR </summary>

```txt:a.ll(coverage)
; 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
}
```

</details>

<details><summary>オリジナルのIR </summary>

```txt: a.ll(original)
; 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
}
```

</details>

---

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