デバッグ情報代入追跡

代入追跡は、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にエンコードしているため、実装上の癖として、VariableFragmentInfoValueExpression内のみに含まれます。

ストアのような命令

リンクされた#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_assignUndef 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_declareDIExpression(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」レコード)と、固定オフセットとサイズを持つ代入のみを追跡できるという事実を考慮すると、アドレスとアドレス式部分は、常に私たちが持っている情報で計算できるため、おそらく削除できると思います。