LLVMのプロファイル情報を紐解いてみた〜カバレッジ編〜


コードカバレッジ結果を保存するフォーマットについて調べてみました。主な情報源はLLVMのドキュメントとソースコードです。

* [LLVM Code Coverage Mapping Format](https://llvm.org/docs/CoverageMappingFormat.html)
* [LLVM](https://github.com/llvm-mirror/llvm)
* [LLVM Compiler-rt](https://github.com/llvm-mirror/compiler-rt)

# データ

## プロファイル情報取得

プロファイル情報は以下のようにして取得しました。```default.profraw```にカバレッジ結果が詰まっているようなのですが、バイナリデータのため、すぐには取り出せません。このフォーマットを調べてみます。

```bash
$ cd /tmp
$ cat a.c
#include <stdio.h>

int main(void) {
  printf("Hello, World\n");
  return 0;
}
$ xcrun clang -g -Wall -Werror -O0 -fprofile-instr-generate -fcoverage-mapping a.c
$ ./a.out
$ xcrun llvm-profdata merge -o default.profdata default.profraw
$ xcrun llvm-cov show ./a.out -instr-profile=default.profdata a.c
    1|       |#include <stdio.h>
    2|       |
    3|      1|int main(void) {
    4|      1|    printf("Hello, World\n");
    5|      1|    return 0;
    6|      1|}
```

## データサンプル

[raw-two-profiles.test](https://github.com/llvm-mirror/llvm/blob/93e6e5414ded14bcbb233baaaa5567132fee9a0c/test/tools/llvm-profdata/raw-two-profiles.test)にあるrawデータのテストファイルを参考にします。

```
RUN: printf '\201rforpl\377' > %t-foo.profraw
RUN: printf '\4\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\10\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\0\0\4\0\1\0\0\0' >> %t-foo.profraw
RUN: printf '\0\0\4\0\2\0\0\0' >> %t-foo.profraw
RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw

RUN: printf '\254\275\030\333\114\302\370\134' >> %t-foo.profraw
RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\0\0\4\0\1\0\0\0' >> %t-foo.profraw
RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw

RUN: printf '\023\0\0\0\0\0\0\0' >> %t-foo.profraw
RUN: printf '\3\0foo\0\0\0' >> %t-foo.profraw

RUN: printf '\201rforpl\377' > %t-bar.profraw
RUN: printf '\4\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\1\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\2\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\10\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\0\0\6\0\1\0\0\0' >> %t-bar.profraw
RUN: printf '\0\0\6\0\2\0\0\0' >> %t-bar.profraw
RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw

RUN: printf '\067\265\035\031\112\165\023\344' >> %t-bar.profraw
RUN: printf '\02\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\0\0\6\0\1\0\0\0' >> %t-bar.profraw
RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\02\0\0\0\0\0\0\0' >> %t-bar.profraw

RUN: printf '\067\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\101\0\0\0\0\0\0\0' >> %t-bar.profraw
RUN: printf '\3\0bar\0\0\0' >> %t-bar.profraw

RUN: cat %t-foo.profraw %t-bar.profraw > %t-pad.profraw
RUN: llvm-profdata show %t-pad.profraw -all-functions -counts | FileCheck %s

CHECK: Counters:
CHECK:   foo:
CHECK:     Hash: 0x0000000000000001
CHECK:     Counters: 1
CHECK:     Function count: 19
CHECK:     Block counts: []
CHECK:   bar:
CHECK:     Hash: 0x0000000000000002
CHECK:     Counters: 2
CHECK:     Function count: 55
CHECK:     Block counts: [65]
CHECK: Functions shown: 2
CHECK: Total functions: 2
CHECK: Maximum function count: 55
CHECK: Maximum internal block count: 65
```

## データの出力元

[InstrProfilingWriter.c](https://github.com/llvm-mirror/compiler-rt/blob/master/lib/profile/InstrProfilingWriter.c)に出力しているであろう箇所が読み取れます。

```c:InstrProfilingWriter.c
COMPILER_RT_VISIBILITY int
lprofWriteDataImpl(ProfDataWriter *Writer, const __llvm_profile_data *DataBegin,
                   const __llvm_profile_data *DataEnd,
                   const uint64_t *CountersBegin, const uint64_t *CountersEnd,
                   VPDataReaderType *VPDataReader, const char *NamesBegin,
                   const char *NamesEnd, int SkipNameDataWrite) {

  /* Calculate size of sections. */
  const uint64_t DataSize = __llvm_profile_get_data_size(DataBegin, DataEnd);
  const uint64_t CountersSize = CountersEnd - CountersBegin;
  const uint64_t NamesSize = NamesEnd - NamesBegin;
  const uint64_t Padding = __llvm_profile_get_num_padding_bytes(NamesSize);

  /* Enough zeroes for padding. */
  const char Zeroes[sizeof(uint64_t)] = {0};

  /* Create the header. */
  __llvm_profile_header Header;

  if (!DataSize)
    return 0;

/* Initialize header structure.  */
#define INSTR_PROF_RAW_HEADER(Type, Name, Init) Header.Name = Init;
#include "InstrProfData.inc"

  /* Write the data. */
  ProfDataIOVec IOVec[] = {
      {&Header, sizeof(__llvm_profile_header), 1},
      {DataBegin, sizeof(__llvm_profile_data), DataSize},
      {CountersBegin, sizeof(uint64_t), CountersSize},
      {SkipNameDataWrite ? NULL : NamesBegin, sizeof(uint8_t), NamesSize},
      {Zeroes, sizeof(uint8_t), Padding}};
  if (Writer->Write(Writer, IOVec, sizeof(IOVec) / sizeof(*IOVec)))
    return -1;

  return writeValueProfData(Writer, VPDataReader, DataBegin, DataEnd);
}
```

データ・フォーマットは```IOVec```の部分にまとまっています。

| データ | サイズ |
|:--|:--|
|Header            |sizeof(__llvm_profile_header) |
|DataBegin         |sizeof(__llvm_profile_data)   |
|CounterBegin      |sizeof(uint64_t)              |
|NULL or NamesBegin|sizeof(uint8_t)               |
|Zero (0)          |sizeof(uint8_t)               |


### __llvm_profile_header

[InstrProfiling.h](https://github.com/llvm-mirror/compiler-rt/blob/907e59854ac144ef0760d4c40ca3359481d511a1/lib/profile/InstrProfiling.h)のデータ構造では次のようになっています。

```c:InstrProfiling.h
typedef struct __llvm_profile_header {
#define INSTR_PROF_RAW_HEADER(Type, Name, Initializer) Type Name;
#include "InstrProfData.inc"
} __llvm_profile_header;
```

[InstrProfData.inc](https://github.com/llvm-mirror/llvm/blob/master/include/llvm/ProfileData/InstrProfData.inc)の```INSTR_PROF_RAW_HEADER```は以下のようになっています。

```cpp:InstrProfData.inc
/* INSTR_PROF_RAW_HEADER  start */
/* Definition of member fields of the raw profile header data structure. */
#ifndef INSTR_PROF_RAW_HEADER
#define INSTR_PROF_RAW_HEADER(Type, Name, Initializer)
#else
#define INSTR_PROF_DATA_DEFINED
#endif
INSTR_PROF_RAW_HEADER(uint64_t, Magic, __llvm_profile_get_magic())
INSTR_PROF_RAW_HEADER(uint64_t, Version, __llvm_profile_get_version())
INSTR_PROF_RAW_HEADER(uint64_t, DataSize, DataSize)
INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize)
INSTR_PROF_RAW_HEADER(uint64_t, NamesSize,  NamesSize)
INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin)
INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin)
INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last)
#undef INSTR_PROF_RAW_HEADER
/* INSTR_PROF_RAW_HEADER  end */
```
とりあえず、```uint64_t```が8個連なっていることがわかります。

### __llvm_profile_data

```cpp:InstrProfiling.h
typedef void *IntPtrT;
typedef struct COMPILER_RT_ALIGNAS(INSTR_PROF_DATA_ALIGNMENT)
    __llvm_profile_data {
#define INSTR_PROF_DATA(Type, LLVMType, Name, Initializer) Type Name;
#include "InstrProfData.inc"
} __llvm_profile_data;
```

```cpp:InstrProfData.inc
/* INSTR_PROF_DATA start. */
/* Definition of member fields of the per-function control structure. */
#ifndef INSTR_PROF_DATA
#define INSTR_PROF_DATA(Type, LLVMType, Name, Initializer)
#else
#define INSTR_PROF_DATA_DEFINED
#endif
INSTR_PROF_DATA(const uint64_t, llvm::Type::getInt64Ty(Ctx), NameRef, \
                ConstantInt::get(llvm::Type::getInt64Ty(Ctx), \
                IndexedInstrProf::ComputeHash(getPGOFuncNameVarInitializer(Inc->getName()))))
INSTR_PROF_DATA(const uint64_t, llvm::Type::getInt64Ty(Ctx), FuncHash, \
                ConstantInt::get(llvm::Type::getInt64Ty(Ctx), \
                Inc->getHash()->getZExtValue()))
INSTR_PROF_DATA(const IntPtrT, llvm::Type::getInt64PtrTy(Ctx), CounterPtr, \
                ConstantExpr::getBitCast(CounterPtr, \
                llvm::Type::getInt64PtrTy(Ctx)))
/* This is used to map function pointers for the indirect call targets to
 * function name hashes during the conversion from raw to merged profile
 * data.
 */
INSTR_PROF_DATA(const IntPtrT, llvm::Type::getInt8PtrTy(Ctx), FunctionPointer, \
                FunctionAddr)
INSTR_PROF_DATA(IntPtrT, llvm::Type::getInt8PtrTy(Ctx), Values, \
                ValuesPtrExpr)
INSTR_PROF_DATA(const uint32_t, llvm::Type::getInt32Ty(Ctx), NumCounters, \
                ConstantInt::get(llvm::Type::getInt32Ty(Ctx), NumCounters))
INSTR_PROF_DATA(const uint16_t, Int16ArrayTy, NumValueSites[IPVK_Last+1], \
                ConstantArray::get(Int16ArrayTy, Int16ArrayVals))
#undef INSTR_PROF_DATA
/* INSTR_PROF_DATA end. */
```

ん？サンプルデータと、サイズが合っていない・・？
**要調査**

# おまけ

ちなみに```llvm-cov export```を使うと、JSON形式に変換できます。

```bash
$ xcrun llvm-cov export ./a.out -instr-profile=default.profdata a.c|jq                                                                                                                                                       [/tmp]
{
  "version": "2.0.0",
  "type": "llvm.coverage.json.export",
  "data": [
    {
      "files": [
        {
          "filename": "/tmp/a.c",
          "segments": [
            [
              3,
              16,
              1,
              1,
              1
            ],
            [
              6,
              2,
              0,
              0,
              0
            ]
          ],
          "expansions": [],
          "summary": {
            "lines": {
              "count": 4,
              "covered": 4,
              "percent": 100
            },
            "functions": {
              "count": 1,
              "covered": 1,
              "percent": 100
            },
            "instantiations": {
              "count": 1,
              "covered": 1,
              "percent": 100
            },
            "regions": {
              "count": 1,
              "covered": 1,
              "notcovered": 0,
              "percent": 100
            }
          }
        }
      ],
      "functions": [
        {
          "name": "main",
          "count": 1,
          "regions": [
            [
              3,
              16,
              6,
              2,
              1,
              0,
              0,
              0
            ]
          ],
          "filenames": [
            "/tmp/a.c"
          ]
        }
      ],
      "totals": {
        "lines": {
          "count": 4,
          "covered": 4,
          "percent": 100
        },
        "functions": {
          "count": 1,
          "covered": 1,
          "percent": 100
        },
        "instantiations": {
          "count": 1,
          "covered": 1,
          "percent": 100
        },
        "regions": {
          "count": 1,
          "covered": 1,
          "notcovered": 0,
          "percent": 100
        }
      }
    }
  ]
}
```