デバッグ情報代入追跡¶
代入追跡は、LLVMの最適化を通じて変数ロケーションデバッグ情報を追跡するための代替技術です。ローカル変数(またはそのフィールド)が左辺値となる代入に対して正確な変数ロケーションを提供します。まれで複雑な状況では、間接的な代入が追跡されずに最適化される可能性がありますが、それ以外の場合は、すべての変数ロケーションを追跡するために最善の努力を払います。
中心となる考え方は、ソース代入に関するより多くの情報を順序どおりに追跡し、中間エンドの最適化が実行されるまで、非メモリロケーション(レジスタ、定数)またはメモリロケーションを使用するかどうかの決定を延期できるように十分な情報を保持することです。これは、ほとんどの変数に対して早期に決定を下すために#dbg_declare
および#dbg_value
を使用することとは対照的であり、その結果、不正確または不完全なサブ最適な変数ロケーションになる可能性があります。
代入追跡の2番目の目標は、LLVMパスライターの追加作業を最小限に抑え、一般的にLLVMへの混乱を最小限に抑えることです。
ステータスと使用法¶
ステータス: 実験的な作業中です。開発およびテスト以外の目的で有効にすることは強く推奨されません。
Clang で有効にする: -Xclang -fexperimental-assignment-tracking
これにより、Clang が LLVM に declare-to-assign
パスを実行させます。このパスは、従来のデバッグレコードを代入追跡メタデータに変換し、モジュールフラグ debug-info-assignment-tracking
を値 i1 true
に設定します。モジュールで代入追跡が有効になっているかどうかを確認するには、isAssignmentTrackingEnabled(const Module &M)
を呼び出します(llvm/IR/DebugInfo.h
から)。
設計と実装¶
代入マーカー: #dbg_assign
¶
従来のデバッグレコードである#dbg_value
は、変数が特定の値をとるIR内の位置を示します。同様に、代入追跡は、#dbg_assign
と呼ばれるレコードで代入の位置を示します。
変数にメモリロケーションを使用するのが適切なIR内の位置を知るために、各代入マーカーは、代入を実行するストア(存在する場合、または複数!)を何らかの方法で参照する必要があります。そうすれば、その選択を行う際にストアとマーカーの位置を一緒に考慮できます。ストアを参照することのもう1つの重要な利点は、ストア<->マーカーの双方向マッピングを構築して、ストアが変更されたときに更新する必要のあるマーカーを見つけることができることです。
どの命令にもリンクされていない#dbg_assign
マーカーは、代入を実行したストアが最適化されて削除されたことを示しているため、メモリロケーションは少なくともプログラムの一部では有効ではありません。
以下は#dbg_assign
の署名です。Value *
型パラメータは、最初に ValueAsMetadata
でラップされます。
#dbg_assign(Value *Value,
DIExpression *ValueExpression,
DILocalVariable *Variable,
DIAssignID *ID,
Value *Address,
DIExpression *AddressExpression)
最初の3つのパラメータは、#dbg_value
のように見えて動作します。ID
はストアへの参照です(次のセクションを参照)。Address
はストアの宛先アドレスであり、AddressExpression
によって変更されます。空/未定義/ポイズンアドレスは、アドレスコンポーネントが破棄されたことを意味します(メモリアドレスは有効なロケーションではなくなりました)。LLVMは現在、変数フラグメント情報をDIExpression
にエンコードしているため、実装上の癖として、Variable
のFragmentInfo
はValueExpression
内のみに含まれます。
命令リンク: DIAssignID
¶
DIAssignID
メタデータは、現在、ストア<->マーカーリンクをエンコードするために使用されているメカニズムです。メタデータノードにはオペランドがなく、すべてのインスタンスはdistinct
です。等価性はアドレスを比較することによってチェックされます。
#dbg_assign
レコードは、オペランドとしてDIAssignID
メタデータノードインスタンスを使用します。このようにして、同じDIAssignID
アタッチメントを持つすべてのストアのような命令を参照します。たとえば、このtest.cppの場合、
int fun(int a) {
return a;
}
最適化なしでコンパイルすると
$ clang++ test.cpp -o test.ll -emit-llvm -S -g -O0 -Xclang -fexperimental-assignment-tracking
次のようになります。
define dso_local noundef i32 @_Z3funi(i32 noundef %a) #0 !dbg !8 {
entry:
%a.addr = alloca i32, align 4, !DIAssignID !13
#dbg_assign(i1 undef, !14, !DIExpression(), !13, i32* %a.addr, !DIExpression(), !15)
store i32 %a, i32* %a.addr, align 4, !DIAssignID !16
#dbg_assign(i32 %a, !14, !DIExpression(), !16, i32* %a.addr, !DIExpression(), !15)
%0 = load i32, i32* %a.addr, align 4, !dbg !17
ret i32 %0, !dbg !18
}
...
!13 = distinct !DIAssignID()
!14 = !DILocalVariable(name: "a", ...)
...
!16 = distinct !DIAssignID()
最初の#dbg_assign
は、!DIAssignID !13
を介してalloca
を参照し、2番目の#dbg_assign
は、!DIAssignID !16
を介してstore
を参照します。
ストアのような命令¶
リンクされた#dbg_assign
がない場合、変数のバッキングストレージであることがわかっているアドレスへのストアは、その変数への代入を表すものと見なされます。
これにより、#dbg_assign
レコードが削除された場合、ストアのDIAssignID
アタッチメントがドロップされた場合、またはオプティマイザが以前の間接ストア(代入追跡では追跡されない)を直接にした場合に、安全なフォールバックが提供されます。
中間エンド: パスライターの考慮事項¶
非デバッグ命令の更新¶
命令をクローン作成する: 新しいことは何もする必要はありません。クローン作成は、DIAssignID
アタッチメントを自動的にクローン作成します。複数の命令が同じDIAssignID
命令を持つ場合があります。この場合、代入はプログラム内の複数の位置で行われると見なされます。
非デバッグ命令を移動する: 新しいことは何もする必要はありません。#dbg_assign
にリンクされた命令は、#dbg_assign
の位置によってマークされた初期IR位置を持っています。
非デバッグ命令を削除する: 新しいことは何もする必要はありません。単純なDSEは変更を必要としません。 DIAssignID
アタッチメントを持つ命令を削除しても安全です。どの命令にもアタッチされていないDIAssignID
を使用する#dbg_assign
は、メモリロケーションが有効ではないことを示します。
ストアをマージする: 多くの場合、combineMetadata
が呼び出された場合、DIAssignID
アタッチメントは自動的にマージされるため、変更は必要ありません。どういうわけか、DIAssignID
アタッチメントは、新しいストアがマージされたストアがリンクされていたすべての#dbg_assign
レコードにリンクされるようにマージする必要があります。これは、ヘルパー関数Instruction::mergeDIAssignID
を呼び出すだけで簡単に実現できます。
ストアをインライン化する: ストアがインライン化されると、フロントエンドと同じように、ストアがソース代入を表すかのように#dbg_assign
レコードとDIAssignID
アタッチメントを生成します。ストアがインライン化前に移動、変更、または削除されている可能性があるため、これは完璧ではありませんが、少なくとも非インライン化されたスコープ内で変数の情報を正しく保つことはできます。
ストアを分割する: SROAとストアを分割するパスは、#dbg_declare
レコードと同様に#dbg_assign
レコードを扱います。ストアにリンクされた#dbg_assign
レコードをクローン作成し、ValueExpression
内のFragmentInfoを更新し、分割されたストア(およびクローン作成されたレコード)にそれぞれ新しいDIAssignID
アタッチメントを付与します。つまり、分割されたストアを個別の代入として扱います。部分的なDSE(たとえば、memsetの短縮)の場合、デッドフラグメントの#dbg_assign
がUndef
Address
を取得することを除いて、同じことを行います。
促進 allocas とストア/ロード: #dbg_assign
レコードは、CFG結合点におけるメモリ位置で結合された値を暗黙的に記述しますが、これは変数の昇格(または部分的な昇格)後には必ずしも当てはまりません。変数を昇格させるパスは、昇格中に生成された結果の PHI の後に #dbg_assign
レコードを挿入する責任があります。mem2reg
は、すでに #dbg_declare
に対して ( #dbg_value
を使用して) これを行う必要があります。ストアにリンクされたレコードがない場合、ストアは宛先アドレスに格納された変数の代入を表すと想定されます。
デバッグレコードの更新¶
デバッグレコードの移動: #dbg_assign
レコードは可能な限り移動を避けてください。これは、ソースレベルの代入を表しており、プログラム内の位置は最適化パスの影響を受けるべきではないためです。
デバッグレコードの削除: 新しく行うことはありません。従来のデバッグレコードと同様に、到達不能でない限り、#dbg_assign
レコードを削除するのはほぼ常に間違いです。
#dbg_assign
の MIR への低レベル化¶
最初は SelectionDAG ISel のみがサポートされます。#dbg_assign
レコードは、MIR DBG_INSTR_REF
命令に低レベル化されます。この前に、各変数についてメモリ位置を使用するのに適切な場所と、非メモリ位置(または位置なし)を使用する必要がある場所を決定する必要があります。これらの決定を行うために、各命令で選択を行い、各ブロックの結果を反復的に結合する標準的な固定点データフロー分析を実行します。
TODO リスト¶
これは実験的な作業中であるため、まだ取り組む必要のある項目がいくつかあります。
llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll のテストで述べたように、分析はエスケープする呼び出しをタグなしのストアのように扱う必要があります。
システムは、ローカル変数がローカル alloca によってバックアップされることを想定しています。これは必ずしもそうではありません。ストレージへのポインタが関数に渡される場合もあります(例:sret、byval)。これらのケースを処理できるようにする必要があります。例については、llvm/test/DebugInfo/Generic/assignment-tracking/track-assignments.ll および clang/test/CodeGen/assignment-tracking/assignment-tracking.cpp を参照してください。
trackAssignments
は、#dbg_declare
の場所がDIExpression
によって変更された変数ではまだ機能しません。たとえば、変数のアドレス自体が#dbg_declare
がDIExpression(DW_OP_deref)
を使用してalloca
に格納されている場合などです。例については、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」レコード)と、固定オフセットとサイズを持つ代入のみを追跡できるという事実を考慮すると、アドレスとアドレス式部分は、常に私たちが持っている情報で計算できるため、おそらく削除できると思います。