LoginSignup
7
7

More than 5 years have passed since last update.

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

Posted at

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

データ

プロファイル情報取得

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

$ 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にある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に出力しているであろう箇所が読み取れます。

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のデータ構造では次のようになっています。

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

InstrProfData.incINSTR_PROF_RAW_HEADERは以下のようになっています。

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

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;
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形式に変換できます。

$ 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
        }
      }
    }
  ]
}
7
7
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
7
7