業界トップクラスの求人数を誇る転職エージェントPR

リクルートグループのコネクションを活かした非公開求人も充実、他にはない好条件の求人と出会える

1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

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++: llvmのc/c++コンパイラ
  • llvm-profdata: プロファイルを整形・マージするツール
  • llvm-cov: カバレッジデータと実行バイナリを読み込み、コードカバレッジを出力するツール

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

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

方法

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

  1. プロファイルオプションをつけてコンパイル
  2. 実行 & ロープロファイルを取得
  3. ロープロファイルをプロファイルデータに変換
  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
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
}
オリジナルのIR
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
}

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Qiita Conference 2025 will be held!: 4/23(wed) - 4/25(Fri)

Qiita Conference is the largest tech conference in Qiita!

Keynote Speaker

ymrl、Masanobu Naruse, Takeshi Kano, Junichi Ito, uhyo, Hiroshi Tokumaru, MinoDriven, Minorun, Hiroyuki Sakuraba, tenntenn, drken, konifar

View event details
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?