Rust Codegen
デバッグ情報生成の最初のフェーズでは、Rust がプログラムの MIR を検査し、それを LLVM に伝達する必要があります。これは主に rustc_codegen_llvm/debuginfo で行われますが、一部の型名処理は rustc_codegen_ssa/debuginfo に存在します。Rust は DIBuilder API を介して LLVM と通信します。これは rustc_llvm に存在する、LLVM の内部機構を薄くラップしたものです。
型情報
型情報は通常、型名、サイズ、アラインメントに加え、関連する場合はフィールド、ジェネリックパラメーター、ストレージ修飾子などで構成されます。この作業の多くは rustc_codegen_llvm/src/debuginfo/metadata で行われます。
念頭に置いておくべき重要な点は、目標が必ずしも「Rust で現れるとおりに型を正確に表現する」ことではなく、デバッグ中にデバッガーがデータを可能な限り正確に再構築できる方法で表現することだという点です。この違いは、この層で発生する中核的な作業を理解するうえで不可欠です。ここで行われる多くの変更は、他に有効な選択肢がない場合に、デバッガーの制限を回避するためのものです。
癖
Rust が生成する DI ノードは、CDB と LLDB の両方のために C/C++ であるかのように「装い」ます。これにより、直感的でなく、慣用的でもないデバッグ情報が生成されることがあります。
ポインターと参照
ワイドポインター/参照/Box は、data_ptr と length の 2 つのフィールドを持つ構造体として扱われます。
すべての非ワイドポインター、参照、Box ポインターはポインターノードとして出力され、mut と非 mut の区別は行われません。これを是正する試みは何度か行われてきましたが、残念ながら単純な解決策はありません。それぞれの形式の reference DI ノードを使用することには落とし穴があります。C++ の参照と Rust の参照の間には、調和できない意味論上の違いがあります。
cppreferenceより:
参照はオブジェクトではありません。参照は必ずしもストレージを占有するとは限りませんが、目的のセマンティクスを実装するために必要な場合、コンパイラーがストレージを割り当てることがあります(たとえば、参照型の非静的データメンバーは通常、メモリアドレスを格納するために必要な分だけクラスのサイズを増加させます)。
参照はオブジェクトではないため、参照の配列、参照へのポインター、参照への参照は存在しません
現在提案されている解決策は、単純にポインターノードを typedef することです。
非 mut を示すために const 修飾子を使用すると、LLDB の内部最適化により潜在的な問題が生じます。要するに、LLDB はコードをステップ実行する際に、変数の子値(たとえば構造体フィールドや配列要素)をキャッシュしようとします。どの値が安全にキャッシュ可能かを判断するためにヒューリスティックが使用され、const はそのヒューリスティックの一部です。これが Rust の内部可変性構造のようなものとどのように相互作用するかについては、まだ調査されていません。
DWARF と PDB
型情報の大部分はかなり単純ですが、注目すべき問題の 1 つはターゲットのデバッグ情報形式です。各形式には異なるセマンティクスと制限があるため、場合によってはわずかに異なるデバッグ情報が必要になります。これは cpp_like_debuginfo の呼び出しによって制御されます。
命名
Rust は型名を可能な限り正確に伝達しようとしますが、デバッガーやデバッグ情報形式が常にそれを尊重するとは限りません。
MSVC の式パーサーの制限により、PDB デバッグ情報では次の名前変換が行われます。
| Rust 名 | MSVC 名 |
|---|---|
&str/&mut str | ref$<str$>/ref_mut$<str$> |
&[T]/&mut [T] | ref$<slice$<T> >/ref_mut$<slice$<T> >1 |
[T; N] | array$<T, N> |
RustEnum | enum2$<RustEnum> |
(T1, T2) | tuple$<T1, T2> |
*const T | ptr_const$<T> |
*mut T | ptr_mut$<T> |
usize | size_t2 |
isize | ptrdiff_t2 |
uN | unsigned __intN2 |
iN | __intN2 |
f32 | float2 |
f64 | double2 |
f128 | fp1282 |
ジェネリクス
Rust はジェネリックな型情報(ArrayVec<T, N: usize> の T)を出力しますが、ジェネリックな値情報(ArrayVec<T, N: usize> の N)は出力しません。
CodeView にはジェネリクス/C++ テンプレート用のリーフノードがないため、PDB デバッグ情報を生成するときにすべてのジェネリック情報が失われます。デバッガーが型名を介してジェネリック引数を取得できるようにする回避策はありますが、せいぜい脆弱な解決策です。この欠陥を修正するために Microsoft に連絡する、および/または未使用の CodeView ノード型の 1 つを適切な等価物として使用する取り組みが進められています。
型エイリアス
Rust はデバッガーの制限を補うためにいくつかの場合で typedef ノードを出力しますが、現在のところソースコード内の型エイリアス用のノードは出力しません。
列挙型
列挙型の DI ノードは rustc_codegen_llvm/src/debuginfo/metadata/enums で生成されます
DWARF
DWARF には判別共用体用の専用ノード DW_TAG_variant があります。これは、判別子値を含む場合も含まない場合もある DW_TAG_variant_part ノードを参照するコンテナーです。階層は次のようになります。
DW_TAG_structure_type (top-level type for the coroutine)
DW_TAG_variant_part (variant part)
DW_AT_discr (reference to discriminant DW_TAG_member)
DW_TAG_member (discriminant member)
DW_TAG_variant (variant 1)
DW_TAG_variant (variant 2)
DW_TAG_variant (variant 3)
DW_TAG_structure_type (type of variant 1)
DW_TAG_structure_type (type of variant 2)
DW_TAG_structure_type (type of variant 3)
PDB
PDB には専用ノードがないため、判別共用体の C 相当を生成します。
union enum2$<RUST_ENUM_NAME> {
enum VariantNames {
First,
Second
};
struct Variant0 {
struct First {
// フィールド
};
static const enum2$<RUST_ENUM_NAME>::VariantNames NAME;
static const unsigned long DISCR_EXACT;
enum2$<RUST_ENUM_NAME>::Variant0::First value;
};
struct Variant1 {
struct Second {
// フィールド
};
static enum2$<RUST_ENUM_NAME>::VariantNames NAME;
static unsigned long DISCR_EXACT;
enum2$<RUST_ENUM_NAME>::Variant1::Second value;
};
enum2$<RUST_ENUM_NAME>::Variant0 variant0;
enum2$<RUST_ENUM_NAME>::Variant1 variant1;
unsigned long tag;
}
重要な点として、LLDB の制限により、生成される DISCR_* 値は、その値が #[repr(u64)] でない場合でも常に u64 になります。DISCR_* 値と tag は型に関係なく uint64_t 値として読み込まれるため、これは LLDB にとってはほとんど問題になりません。
ソース情報
TODO