檢測描述檔格式¶
概述¶
Clang 透過檢測 [1] 支援兩種描述檔類型:基於前端和基於 IR,兩者都可以支援各種用例 [2] 。本文檔描述了兩種二進位序列化格式(原始和索引),用於儲存檢測描述檔,特別強調 IRPGO 用例,因為當特定標頭欄位和酬載區段在不同用例中具有不同的解釋方式時,本文檔基於 IRPGO。
注意
前端產生的描述檔與覆蓋率映射一起用於基於原始碼的程式碼覆蓋率。 覆蓋率映射格式與描述檔格式不同。
原始描述檔格式¶
原始描述檔是透過執行檢測後的二進位檔案產生的。來自可執行檔或共享函式庫 [3] 的原始描述檔資料包含標頭和多個區段,每個區段都是記憶體傾印。原始描述檔資料需要合理地緊湊且快速產生。
原始描述檔格式沒有向後或向前版本相容性保證。也就是說,編譯器和工具 要求特定的原始描述檔版本才能解析描述檔。
為了將描述檔回饋到編譯器以進行最佳化建置(例如,透過 IR 檢測使用 -fprofile-use
),原始描述檔必須轉換為索引格式。
一般儲存佈局¶
原始描述檔資料格式的儲存佈局如下所示。基本上,當原始描述檔讀取到記憶體緩衝區時,區段的實際位元組偏移量是從區段在佈局中的順序以及其前面所有區段的大小資訊推斷出來的。
+----+-----------------------+
| | Magic |
| +-----------------------+
| | Version |
| +-----------------------+
H | Size Info for |
E | Section 1 |
A +-----------------------+
D | Size Info for |
E | Section 2 |
R +-----------------------+
| | ... |
| +-----------------------+
| | Size Info for |
| | Section N |
+----+-----------------------+
P | Section 1 |
A +-----------------------+
Y | Section 2 |
L +-----------------------+
O | ... |
A +-----------------------+
D | Section N |
+----+-----------------------+
注意
區段可能會被填充以滿足特定的對齊要求。為了簡化,為了填充目的而設的標頭欄位和資料區段在上面的資料佈局圖以及本文檔的其餘部分中被省略。
標頭¶
Magic
Magic number 編碼描述檔格式(原始、索引或文字)。對於原始格式,magic number 還編碼產生描述檔的平台的位元組序(大端或小端)和 C 指標大小(4 或 8 個位元組)。
工廠方法讀取 magic number 以正確建構讀取器,並在無法識別的格式時傳回錯誤。具體來說,工廠方法和原始描述檔讀取器實作確保原始描述檔檔案可以在具有相反位元組序和/或另一個 C 指標大小的平台上讀取回。
版本
低 32 位元指定實際版本,最高有效 32 位元指定描述檔的變體類型。基於 IR 的檢測 PGO 和上下文相關的基於 IR 的檢測 PGO 是兩種變體類型。
BinaryIdsSize
二進位 ID 區段的位元組大小。
NumData
描述檔元數據的數量。可以透過此欄位計算 描述檔元數據 區段的位元組大小。
NumCounter
描述檔計數器區段中的條目數。可以透過此欄位計算 計數器 區段的位元組大小。
NumBitmapBytes
描述檔 位元地圖 區段中的位元組數。
NamesSize
名稱區段中的位元組數。
CountersDelta
此欄位記錄檢測後的二進位檔案中 描述檔元數據 和計數器區段之間的記憶體位址差異,即
start(__llvm_prf_cnts) - start(__llvm_prf_data)
。它與 CounterPtr 欄位一起使用,以計算相對於
start(__llvm_prf_cnts)
的計數器偏移量。請查看 計數器偏移量計算 以獲得視覺化的解釋。BitmapDelta
此欄位記錄檢測後的二進位檔案中 描述檔元數據 和位元地圖區段之間的記憶體位址差異,即
start(__llvm_prf_bits) - start(__llvm_prf_data)
。它與 BitmapPtr 一起使用,以尋找描述檔資料記錄的位元地圖,方式與 計數器偏移量計算 解釋的計數器參考方式類似。
與 CountersDelta 欄位類似,此欄位可能不會在非 PGO 變體的描述檔中使用。
NamesDelta
記錄名稱區段的記憶體位址。除了原始描述檔讀取器錯誤檢查外,不使用。
NumVTables
記錄二進位檔案中檢測到的虛擬表條目的數量。用於類型描述檔。
VNamesSize
記錄虛擬表名稱區段中的位元組大小。用於類型描述檔。
ValueKindLast
記錄值種類的數量。巨集 VALUE_PROF_KIND 定義了值種類以及種類的描述。
酬載區段¶
二進位 ID¶
儲存檢測後的二進位檔案的二進位 ID,以將二進位檔案與用於原始碼覆蓋率的描述檔關聯起來。請參閱 二進位 ID RFC 以了解設計。
描述檔元數據¶
此區段儲存元數據,以將計數器和值描述檔映射回檢測後的程式碼區域(例如,IRPGO 的 LLVM IR)。
元數據的記憶體中表示形式為 __llvm_profile_data。某些欄位用於參考描述檔中其他區段的資料。欄位的文件記錄如下
NameRef
函式 PGO 名稱的 MD5。PGO 名稱的格式為
[<filepath><delimiter>]<mangled-name>
,其中<filepath>
和<delimiter>
是為本地連結函式提供的,以區分可能相同的函式。
FuncHash
函式 IR 的校驗和,考慮了控制流程圖和檢測到的值站點。有關詳細資訊,請參閱 computeCFGHash 。
CounterPtr
描述檔資料與對應計數器起始位置之間的記憶體位址差異。計數器位置以這種方式儲存(作為連結時間常數),與直接快照符號的位址相比,可以減少檢測後的二進位檔案大小。有關更多資訊,請參閱 commit a1532ed 。
注意
CounterPtr
對於非 IRPGO 用例可能代表不同的值。例如,對於 二進位描述檔關聯,它代表計數器的絕對位址。如有疑問,請檢查原始碼。
BitmapPtr
描述檔資料與對應位元地圖的起始位址之間的記憶體位址差異。
注意
與 CounterPtr 類似,此欄位可能對非 IRPGO 用例表示不同的值。
FunctionPointer
記錄檢測後的二進位檔案執行時的函式位址。這用於在從原始描述檔轉換為索引描述檔期間,將間接呼叫的已描述檔分析的被呼叫者位址映射到
NameRef
。Values
以二維陣列表示值描述檔。第一維中的元素數量是所有種類中檢測到的值站點的數量。第一維中的每個元素都是連結串列的頭,第二維中的每個元素都是連結串列元素,攜帶
<已描述檔分析的值, 計數>
作為酬載。這是編譯器執行階段在寫出值描述檔時使用的。注意
前端和 IR PGO 檢測都支援值描述檔分析,但並非在所有情況下都支援(例如,輕量級檢測)。
NumCounters
檢測函式的計數器數量。
NumValueSites
這是一個計數器陣列,每個計數器代表函式中某種值類型的檢測站點數量。
NumBitmapBytes
函式的位元地圖位元組數。
描述檔計數器¶
對於 PGO [4],特定 FuncHash 的檢測函式中的計數器會連續儲存,並且順序與檢測點選擇一致。
如上所述,記錄的計數器偏移量相對於描述檔元數據。那麼函式計數器如何在原始描述檔資料中定位呢?
基本上,描述檔讀取器迭代描述檔元數據(來自 描述檔元數據 區段),並利用記錄的相對距離,如下圖所示。
+ --> start(__llvm_prf_data) --> +---------------------+ ------------+
| | Data 1 | |
| +---------------------+ =====|| |
| | Data 2 | || |
| +---------------------+ || |
| | ... | || |
Counter| +---------------------+ || |
Delta | | Data N | || |
| +---------------------+ || | CounterPtr1
| || |
| CounterPtr2 || |
| || |
| || |
+ --> start(__llvm_prf_cnts) --> +---------------------+ || |
| ... | || |
+---------------------+ -----||----+
| Counter for | ||
| Data 1 | ||
+---------------------+ ||
| ... | ||
+---------------------+ =====||
| Counter for |
| Data 2 |
+---------------------+
| ... |
+---------------------+
| Counter for |
| Data N |
+---------------------+
在圖表中,
描述檔標頭記錄了
CounterDelta
,其值為start(__llvm_prf_cnts) - start(__llvm_prf_data)
。為方便起見,我們在下面將其稱為CounterDeltaInitVal
。對於每個描述檔資料記錄
ProfileDataN
,CounterPtr
記錄為start(CounterN) - start(ProfileDataN)
,其中ProfileDataN
是__llvm_prf_data
中的第 N 個條目,而CounterN
代表對應的描述檔計數器。
每次讀取器前進到下一個資料記錄時,它都會 更新 CounterDelta
以減去一個 ProfileData
的大小。
對於與第一個資料記錄對應的計數器,相對於計數器區段起始位置的位元組偏移量計算為 CounterPtr1 - CounterDeltaInitVal
。當描述檔讀取器前進到第二個資料記錄時,請注意 CounterDelta
更新為 CounterDeltaInitVal - sizeof(ProfileData)
。因此,相對於計數器區段起始位置的位元組偏移量計算為 CounterPtr2 - (CounterDeltaInitVal - sizeof(ProfileData))
。
位元地圖¶
此區段用於基於原始碼的 修改條件/判斷覆蓋率 程式碼覆蓋率。請查看 Bitmap RFC 以了解設計。
名稱¶
此區段包含函式 PGO 名稱的可能壓縮的串連字串。如果壓縮,則使用 zlib 函式庫。
當原始描述檔轉換為索引描述檔時,函式名稱充當 PGO 資料雜湊表中的鍵。它們對於 llvm-profdata
以人類可讀的方式顯示描述檔也至關重要。
虛擬表描述檔資料¶
此區段用於類型描述檔。每個條目對應一個虛擬表,並由以下 C++ 結構定義
struct VTableProfData {
// The start address of the vtable, collected at runtime.
uint64_t StartAddress;
// The byte size of the vtable. `StartAddress` and `ByteSize` specifies an address range to look up.
uint32_t ByteSize;
// The hash of vtable's (PGO) name
uint64_t MD5HashOfName;
};
在描述檔使用時,編譯器會在排序後的虛擬表位址範圍中查找已描述檔分析的位址,並透過雜湊名稱將位址映射到特定的虛擬表。
虛擬表名稱¶
此區段與上面的 函式名稱 區段類似,不同之處在於它包含已描述檔分析的虛擬表的 PGO 名稱。它是一個獨立的區段,這樣原始描述檔讀取器可以直接透過存取對應的描述檔資料區段來找到每個名稱集。
此區段儲存在原始描述檔中,以便 llvm-profdata 可以以人類可讀的方式顯示描述檔。
值描述檔資料¶
此區段包含值描述檔分析的描述檔資料。
與描述檔元數據對應的值描述檔連續序列化為一個記錄,並且值描述檔記錄以與各自的描述檔資料相同的順序儲存,這樣原始描述檔讀取器 前進 描述檔資料的指標和值描述檔記錄的指標同時 [5] 以尋找每個函式、每個 FuncHash 描述檔資料的值描述檔。
索引描述檔格式¶
索引描述檔是從 llvm-profdata
產生的。在索引描述檔中,函式資料組織為磁碟上的雜湊表,這樣編譯器就可以在 IR 模組中查找函式的描述檔資料。
編譯器和工具必須保持與索引描述檔的向後相容性。也就是說,在新版本的程式碼中建置的工具或編譯器必須理解由舊工具或編譯器產生的描述檔。
一般儲存佈局¶
ASCII art 描繪了索引描述檔的一般儲存佈局。具體來說,索引描述檔標頭描述了個別酬載區段的位元組偏移量。
+-----------------------+---+
| Magic | |
+-----------------------+ |
| Version | |
+-----------------------+ |
| HashType | H
+-----------------------+ E
| Byte Offset | A
+------ | of section A | D
| +-----------------------+ E
| | Byte Of fset | R
+-----------| of section B | |
| | +-----------------------+ |
| | | ... | |
| | +-----------------------+ |
| | | Byte Offset | |
+---------------| of section Z | |
| | | +-----------------------+---+
| | | | Profile Summary | |
| | | +-----------------------+ P
| | +------>| Section A | A
| | +-----------------------+ Y
| +---------->| Section B | L
| +-----------------------+ O
| | ... | A
| +-----------------------+ D
+-------------->| Section Z | |
+-----------------------+---+
注意
描述檔摘要區段位於酬載的開頭。它緊接在標頭之後,因此在讀取標頭後隱式地知道其位置。
標頭¶
Header 結構是事實的來源,結構欄位應說明標頭中的內容。在高層次上,*Offset 欄位記錄區段位元組偏移量,讀取器使用這些偏移量來定位感興趣的區段並跳過不感興趣的區段。
注意
為了保持索引描述檔的向後相容性,不應從結構定義中刪除現有欄位;不應修改欄位順序。應附加新欄位。
酬載區段¶
(CS) 描述檔摘要¶
此區段緊接在描述檔標頭之後。它儲存序列化的描述檔摘要。對於上下文相關的基於 IR 的檢測 PGO,此區段儲存與上下文相關的描述檔對應的額外描述檔摘要。
函式資料¶
此區段將函式及其描述檔分析資料儲存為磁碟上的雜湊表。具有相同名稱的函式的描述檔資料被分組在一起,並共享一個雜湊表條目(函式可能來自不同的共享函式庫,例如)。它們的描述檔資料組織為鍵值對序列,其中鍵是 FuncHash,值是函式的描述檔分析資訊(由 InstrProfRecord 表示)。
MemProf 描述檔資料¶
此區段儲存函式的記憶體描述檔分析資料。請參閱 MemProf 二進位序列化格式 RFC 以了解設計。
二進位 ID¶
此區段用於攜帶來自原始描述檔的 二進位 ID 資訊。
時間描述檔追蹤¶
此區段用於攜帶來自原始描述檔的時間描述檔資訊。請參閱 時間描述檔分析 以了解設計。
虛擬表名稱¶
此區段用於在索引描述檔中儲存來自原始描述檔的虛擬表名稱。
與儲存為 函式資料 雜湊表鍵的函式名稱不同,虛擬表名稱需要儲存在索引描述檔中的獨立區段中。這樣, llvm-profdata 就可以以人類可讀的方式顯示已描述檔分析的虛擬表資訊。
描述檔資料使用方式¶
llvm-profdata
是用於顯示和處理基於檢測的描述檔資料的命令列工具。有關支援的使用方式,請查看 llvm-profdata 文件。