除錯資訊賦值追蹤

賦值追蹤是一種替代技術,用於在 LLVM 的最佳化過程中追蹤變數位置除錯資訊。它為本機變數(或其欄位)為 LHS 的賦值提供準確的變數位置。在罕見且複雜的情況下,間接賦值可能會在未被追蹤的情況下被最佳化掉,但在其他情況下,我們會盡力追蹤所有變數位置。

核心概念是追蹤更多關於原始碼賦值的資訊,並保留足夠的資訊,以便能夠延遲決定是否使用非記憶體位置(暫存器、常數)或記憶體位置,直到中端最佳化執行完成後。這與使用 #dbg_declare#dbg_value 相反,後者是盡早為大多數變數做出決定,這可能會導致次佳的變數位置,這些位置可能不正確或不完整。

賦值追蹤的次要目標是盡可能減少 LLVM Pass 開發人員的額外工作,並盡可能減少對 LLVM 整體的干擾。

狀態與使用方式

狀態:預設在 Clang 中啟用,但在某些情況下停用(可以使用 forced 選項覆蓋,請參閱下文)。除非被要求 (-passes=declare-to-assign),否則 opt 不會執行此 Pass。

標誌-Xclang -fexperimental-assignment-tracking=<disabled|enabled|forced>

啟用後,Clang 會讓 LLVM 執行 declare-to-assign Pass。此 Pass 會將傳統的除錯記錄轉換為賦值追蹤元資料,並將模組標誌 debug-info-assignment-tracking 設定為值 i1 true。要檢查是否為模組啟用了賦值追蹤,請呼叫 isAssignmentTrackingEnabled(const Module &M) (來自 llvm/IR/DebugInfo.h)。

設計與實作

賦值標記:#dbg_assign

#dbg_value 是一種傳統的除錯記錄,標記了 IR 中變數取得特定值的位置。類似地,賦值追蹤使用名為 #dbg_assign 的記錄標記賦值的位置。

為了知道在 IR 中的哪個位置適合為變數使用記憶體位置,每個賦值標記都必須以某種方式參考執行賦值的 store 指令(如果有的話,或多個!)。這樣,在做出選擇時可以一起考慮 store 指令和標記的位置。參考 store 指令的另一個重要好處是,我們可以建立 store 指令 <-> 標記的雙向映射,可用於查找在 store 指令被修改時需要更新的標記。

未連結到任何指令的 #dbg_assign 標記表示執行賦值的 store 指令已被最佳化掉,因此記憶體位置至少在程式的某些部分將無效。

以下是 #dbg_assign 的簽章。Value * 類型參數首先被包裹在 ValueAsMetadata

  #dbg_assign(Value *Value,
              DIExpression *ValueExpression,
              DILocalVariable *Variable,
              DIAssignID *ID,
              Value *Address,
              DIExpression *AddressExpression)

前三個參數看起來和行為都像 #dbg_valueID 是對 store 指令的參考(請參閱下一節)。Address 是 store 指令的目的地地址,它由 AddressExpression 修改。空/未定義/poison 地址表示地址組件已被移除(記憶體地址不再是有效位置)。LLVM 目前在 DIExpression 中編碼變數片段資訊,因此作為實作上的怪癖,VariableFragmentInfo 僅包含在 ValueExpression 中。

類 store 指令

在沒有連結的 #dbg_assign 的情況下,對已知是變數後備儲存體的地址進行 store 指令,被認為表示對該變數的賦值。

這為我們提供了一個安全的回退方案,以應對 #dbg_assign 記錄已被刪除、store 指令上的 DIAssignID 附件已被移除,或最佳化器已將曾經是間接的 store 指令(未透過賦值追蹤追蹤)變為直接 store 指令的情況。

中端:Pass 開發人員的注意事項

非除錯指令更新

複製指令:沒有新的事項需要處理。複製會自動複製 DIAssignID 附件。多個指令可能具有相同的 DIAssignID 指令。在這種情況下,賦值被認為發生在程式中的多個位置。

移動非除錯指令:沒有新的事項需要處理。連結到 #dbg_assign 的指令的初始 IR 位置由 #dbg_assign 的位置標記。

刪除非除錯指令:沒有新的事項需要處理。簡單的 DSE 不需要任何更改;刪除具有 DIAssignID 附件的指令是安全的。#dbg_assign 使用未附加到任何指令的 DIAssignID 表示記憶體位置無效。

合併 store 指令:在許多情況下,不需要更改,因為如果呼叫 combineMetadataDIAssignID 附件會自動合併。無論如何,必須合併 DIAssignID 附件,以便新的 store 指令連結到合併的 store 指令所連結的所有 #dbg_assign 記錄。這可以通過簡單地呼叫輔助函式 Instruction::mergeDIAssignID 來實現。

內聯 store 指令:當 store 指令被內聯時,我們生成 #dbg_assign 記錄和 DIAssignID 附件,就像 store 指令表示原始碼賦值一樣,就像在前端一樣。這並不完美,因為 store 指令可能在內聯之前已被移動、修改或刪除,但它至少保持了非內聯範圍內變數資訊的正確性。

分割 store 指令:SROA 和分割 store 指令的 Pass 以類似於 #dbg_declare 記錄的方式處理 #dbg_assign 記錄。複製連結到 store 指令的 #dbg_assign 記錄,更新 ValueExpression 中的 FragmentInfo,並為分割的 store 指令(和複製的記錄)分別提供新的 DIAssignID 附件。換句話說,將分割的 store 指令視為單獨的賦值。對於部分 DSE(例如,縮短 memset),我們執行相同的操作,除了死片段的 #dbg_assign 獲得 Undef Address

提升 allocas 和 store/loads:#dbg_assign 記錄隱式地描述了 CFG 連接點處記憶體位置中連接的值,但在提升(或部分提升)變數後,情況不一定如此。提升變數的 Pass 負責在提升期間產生的結果 PHI 之後插入 #dbg_assign 記錄。mem2reg 已經必須為 #dbg_declares 執行此操作(使用 #dbg_value)。如果 store 指令沒有連結的記錄,則 store 指令被假定為表示對儲存在目的地地址的變數的賦值。

除錯記錄更新

移動除錯記錄:盡可能避免移動 #dbg_assign 記錄,因為它們表示原始碼層級的賦值,其在程式中的位置不應受到最佳化 Pass 的影響。

刪除除錯記錄:沒有新的事項需要處理。與傳統的除錯記錄一樣,除非它是無法訪問的,否則刪除 #dbg_assign 記錄幾乎總是不正確的。

#dbg_assign 降低到 MIR

首先,僅支援 SelectionDAG ISel。#dbg_assign 記錄被降低為 MIR DBG_INSTR_REF 指令。在此之前,我們需要決定在何處適合使用記憶體位置,以及在何處必須為每個變數使用非記憶體位置(或沒有位置)。為了做出這些決定,我們執行標準的定點資料流分析,在每個指令處做出選擇,迭代地合併每個區塊的結果。

TODO 列表

未完成的改進

  • 如 test llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll 中所述,分析應將 escape 呼叫視為未標記的 store 指令。

  • 系統預期本機變數由本機 alloca 支援。情況並非總是如此 - 有時指向儲存體的指標會傳遞到函式中(例如 sret、byval)。我們需要能夠處理這些情況。有關範例,請參閱 llvm/test/DebugInfo/Generic/assignment-tracking/track-assignments.ll 和 clang/test/CodeGen/assignment-tracking/assignment-tracking.cpp。

  • trackAssignments 尚不適用於其 #dbg_declare 位置被 DIExpression 修改的變數,例如,當變數的地址本身儲存在 alloca 中時,#dbg_declare 使用 DIExpression(DW_OP_deref)。有關範例,請參閱 llvm/test/DebugInfo/Generic/assignment-tracking/track-assignments.ll 中的 indirectReturn 和 clang/test/CodeGen/assignment-tracking/assignment-tracking.cpp。

  • 為了解決第一個要點,我們需要能夠在不使用 DIAssignID 的情況下指定記憶體位置可用。這是因為儲存體地址不是由指令計算的(它是一個引數值),因此我們無處放置元資料附件。為了解決這個問題,我們可能需要另一個標記記錄來表示「變數的堆疊家目錄是 X 地址」 - 類似於 #dbg_declare,只是它需要與 #dbg_assign 記錄組合,以便僅當 #dbg_assign 記錄同意應該使用堆疊家目錄地址作為變數的位置時才使用。

  • 鑑於上述情況(特殊的「堆疊家目錄是 X」記錄),以及我們只能追蹤具有固定偏移量和大小的賦值,我認為我們可能可以擺脫地址和地址表示式部分,因為它始終可以使用我們擁有的資訊計算出來。

  • 預設情況下,對於 LTO 和 thinLTO 建置,以及如果已指定 LLDB 除錯器調整,則會停用賦值追蹤。我們應該刪除這些限制。請參閱 clang/lib/CodeGen/BackendUtil.cpp 中的 EmitAssemblyHelper::RunOptimizationPipeline。