デバッグ情報のための命令参照¶
このドキュメントでは、LLVMがコンパイルのコード生成段階で、値の追跡、つまり命令参照を使用して、デバッグ情報の変数の位置をどのように決定するかについて説明します。この内容は、コード生成ターゲットと最適化パスに取り組んでいる方を対象としています。また、低レベルのデバッグ情報の処理に興味がある方にも役立つかもしれません。
問題の記述¶
コンパイルの最後に、LLVMは、変数の字句スコープ内の各命令について、変数がどのレジスタまたはスタック位置にあるかを記述するDWARF位置リスト(または同様のもの)を生成する必要があります。コンパイルを通して変数が存在する仮想レジスタを追跡することもできますが、これはレジスタ割り当て中のレジスタの最適化や命令の移動の影響を受けやすいです。
解決策:命令参照¶
変数値が存在する仮想レジスタを識別するのではなく、命令参照モードでは、LLVMは値が定義されているマシン命令とオペランドの位置を参照します。命令値を参照するLLVM IRの方法を考えてみましょう。
%2 = add i32 %0, %1
#dbg_value(metadata i32 %2,
LLVM IRでは、IR値は値を計算する命令と同義であり、メモリ内では値は計算命令へのポインタです。命令参照は、命令選択後、LLVMのコード生成バックエンドでこの関係を実装します。以下のX86アセンブリと、前のLLVM IRに対応する命令参照デバッグ情報を考えてみましょう。
%2:gr32 = ADD32rr %0, %1, implicit-def $eflags, debug-instr-number 1
DBG_INSTR_REF 1, 0, !123, !456, debug-location !789
関数がSSA形式のままである間、仮想レジスタ %2
は命令によって計算された値を識別するのに十分です。しかし、関数は最終的にSSA形式ではなくなり、レジスタの最適化によって目的の値がどのレジスタにあるかがわかりにくくなります。代わりに、命令の値をより一貫して識別する方法は、MachineOperand
がどのレジスタによって定義されているかに関係なく、値が定義されている MachineOperand
を参照することです。上記のコードでは、DBG_INSTR_REF
命令は命令番号1、オペランド0を参照し、ADD32rr
にはそれが命令番号1であることを示す debug-instr-number
属性が付いています。
変数の位置をレジスタから切り離すことで、レジスタ割り当てと最適化に関する問題を回避できますが、代わりに命令が最適化されるときに追加のインストルメンテーションが必要になります。同じ値を計算する最適化されたバージョンで命令を置き換える最適化は、命令番号を保持するか、古い命令/オペランド番号のペアから新しい命令/オペランド番号のペアへの置換を記録する必要があります。 MachineFunction::substituteDebugValuesForInst
を参照してください。デバッグ情報の保守が行われなかった場合、または命令がデッドコードとして削除された場合、変数の位置は安全に削除され、「最適化済み」とマークされます。例外は、置き換えられるのではなく変更される命令で、常にデバッグ情報の保守が必要です。
レジスタアロケータの考慮事項¶
レジスタアロケータが実行されると、デバッグ命令は仮想レジスタを直接参照しないため、レジスタ割り当て中に高価な位置の保守(つまり、LiveDebugVariables
)を行う必要はありません。デバッグ命令は関数からリンク解除され、レジスタ割り当ての完了後に再びリンクされます。
例外は PHI
命令です。レジスタ割り当てが終了すると、これらは制御フローのマージで暗黙的な定義になり、PHI
命令に添付されたデバッグ番号は失われます。これを回避するために、レジスタ割り当ての開始時(phi-node-elimination
)に PHI
のデバッグ番号が記録され、レジスタ割り当ての完了後に DBG_PHI
命令が挿入されます。これには、レジスタ割り当て中に変数がどのレジスタにあるかについての保守が必要ですが、命令の範囲ではなく、単一の位置(ブロックエントリポイント)で行われます。
レジスタ割り当て前の例
bb.2:
%2 = PHI %1, %bb.0, %2, %bb.1, debug-instr-number 1
レジスタ割り当て後
bb.2:
DBG_PHI $rax, 1
LiveDebugValues
¶
最適化とコードレイアウトが完了した後、変数値に関する情報は変数の位置、つまりレジスタとスタックスロットに変換する必要があります。これは[LiveDebugValues
パス][LiveDebugValues]で実行され、デバッグ命令とマシンコードは2つの独立した関数に分割されます。
変数名に値を割り当てる関数
マシンレジスタとスタックスロットに値を割り当てる関数
LLVMの既存のSSAツールを使用して、変数値とマシンロケーションに含まれる値の間に、各関数の PHI
を配置し、値の伝播によって不要な PHI
を排除します。その後、2つを結合して、関数の各命令について、変数を値に、値をロケーションにマッピングできます。
このプロセスの鍵は、レジスタとスタックロケーション間の値の移動を識別できることです。そのため、値がマシンに存在する間、値のロケーションを完全に保持できます。
必要なターゲットサポートと移行ガイド¶
命令参照はどのターゲットでも機能しますが、カバレッジは低い可能性があります。命令参照を適切にサポートするには、以下が必要です。
LiveDebugValues
がマシンを通して値を追跡できるようにするためのターゲットフックを実装する命令番号を保持するために、ターゲット固有の最適化をインストルメント化する
ターゲットフック¶
TargetInstrInfo::isCopyInstrImpl
は、コピーのような命令を認識するために実装する必要があります。 LiveDebugValues
はこれを使用して、値がレジスタ間を移動するタイミングを識別します。
TargetInstrInfo::isLoadFromStackSlotPostFE
と TargetInstrInfo::isStoreToStackSlotPostFE
は、スピル命令とリストア命令を識別するために必要です。それぞれ、デスティネーションレジスタまたはソースレジスタを返します。 LiveDebugValues
は、スタックスロットとの間の値の移動を追跡します。さらに、スタックスピルに書き込む命令には、MachineMemoryOperand
が添付されている必要があります。そのため、LiveDebugValues
はスロットが上書きされたことを認識できます。
ターゲット固有の最適化インストルメンテーション¶
最適化には2種類あります。 MachineInstr
を変更して異なる動作をさせるものと、古い操作を置き換える新しい命令を作成するものとです。
前者は必ずインストルメント化する必要があります。関連する質問は、変更の結果として、オペランドのレジスタ定義で異なる値が生成されるかどうかです。答えが「はい」の場合、そのオペランドを参照する DBG_INSTR_REF
命令が異なる値を変数に割り当て、デバッグ開発者に予期しない変数値を提示するリスクがあります。このようなシナリオでは、変更された命令で MachineInstr::dropDebugNumber()
を呼び出して、命令番号を消去します。それを参照する DBG_INSTR_REF
は、代わりに空の変数ロケーションを生成し、デバッガでは「最適化済み」と表示されます。
後者の最適化では、カバレッジを高めるために、命令番号の置換、つまり古い命令番号/オペランドペアから新しい命令番号/オペランドペアへのマッピングを記録する必要があります。3アドレス加算命令を2アドレス加算命令に置き換える場合を考えてみましょう。
%2:gr32 = ADD32rr %0, %1, debug-instr-number 1
が
%2:gr32 = ADD32rr %0(tied-def 0), %1, debug-instr-number 2
になり、「命令番号1オペランド0」から「命令番号2オペランド0」への置換が MachineFunction
に記録されます。 LiveDebugValues
では、DBG_INSTR_REF
は置換テーブルを通してマッピングされ、参照する値の最新の命令番号/オペランド番号が検索されます。
MachineFunction::substituteDebugValuesForInst
を使用して、古い命令と新しい命令の間の置換を自動的に生成します。古い命令で定義されているオペランドは、同じオペランド位置にある新しい命令で定義されていると想定しています。これはほとんどの場合、例えば上記の例で機能します。
古い命令と新しい命令の間でオペランド番号が一致しない場合は、MachineInstr::getDebugInstrNum
を使用して新しい命令の命令番号を取得し、MachineFunction::makeDebugValueSubstitution
を使用して、古い命令と新しい命令のレジスタ定義間のマッピングを記録します。古い命令によって計算された値の一部が新しい命令によって計算されなくなった場合は、置換を記録しないでください。 LiveDebugValues
は、使用できなくなった変数値を安全に削除します。
ターゲットクローンの手順は、TailDuplicator
最適化パスと同様に、命令番号を保持したり、置換を記録したりしようとしないでください。MachineFunction::CloneMachineInstr
は、複製された命令の命令番号を削除して、LiveDebugValues
に重複番号が表示されないようにする必要があります。重複した命令の処理は、現在実装されていない命令参照の自然な拡張です。
[LiveDebugValues]: project:SourceLevelDebugging.rst#LiveDebugValues 変数位置の展開