コードカバレッジ結果を保存するフォーマットについて調べてみました。主な情報源は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.incのINSTR_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
}
}
}
]
}