MIR クエリとパス
MIR を取得したい場合:
- 関数については、
optimized_mirクエリ(通常は codegen によって使用されます)またはmir_for_ctfeクエリ(通常はコンパイル時関数評価、すなわち CTFE によって使用されます)を使用できます。 - promoted については、
promoted_mirクエリを使用できます。
これらは、最終的な最適化済み MIR を返します。外部の def-id については、単に他のクレートのメタデータから MIR を読み取ります。しかしローカルの def-id については、そのクエリは 上流クエリのパイプラインを要求することで、最適化済み MIR を構築します1。 各クエリには一連のパスが含まれます。 このセクションでは、それらのクエリとパスがどのように動作するか、またそれらをどのように拡張できるかを説明します。
与えられた def-id D に対して最適化済み MIR を生成するために、optimized_mir(D) は、
それぞれがクエリごとにグループ化された、複数のパス群を通過します。
各パス群は、lint、解析、変換、または最適化を行うパスで構成されます。
各クエリは、型チェックやその他の目的のために MIR ダイアレクトへアクセスできる、
有用な中間地点を表します。
mir_built(D)– 構築直後の初期 MIR を返します。mir_const(D)– MIR が const qualification の準備を完了するように、 いくつかの単純な変換パスを適用します。mir_promoted(D)- promoted 可能な一時値を別個の MIR 本体へ抽出し、さらに MIR が 借用検査の準備を完了するようにします。mir_drops_elaborated_and_const_checked(D)- 借用検査を実行し、主要な 変換パス(drop elaboration など)を実行して、MIR が最適化の準備を完了するようにします。optimized_mir(D)– 有効化されているすべての最適化を実行し、最終状態に到達します。
パスの実装と登録
MirPass は MIR を処理するコード片であり、通常はその過程で何らかの形で MIR を変換します。
ただし、lint(たとえば CheckPackedRef、
CheckConstItemMutation、FunctionItemReferences。これらは MirLint を実装します)や
最適化(たとえば SimplifyCfg、RemoveUnneededDrops)のような、他の処理を行う場合もあります。ほとんどの MIR パスは
rustc_mir_transform クレートで定義されていますが、MirPass トレイト自体は
rustc_middle クレートにあり、基本的には主要なメソッドが 1 つ、
run_pass だけで構成されています。これは単に &mut Body(および tcx)を受け取ります。
したがって、MIR はインプレースで変更されます(これは効率を保つのに役立ちます)。
MIR パスの基本的な例は RemoveStorageMarkers です。これは
MIR を走査し、codegen 中に出力されない場合はすべての storage mark を削除します。
そのソースからわかるように、MIR パスはまず
ダミー型、つまりフィールドを持たない構造体を定義することで定義されます。
#![allow(unused)]
fn main() {
pub struct RemoveStorageMarkers;
}
これに対して MirPass トレイトを実装します。その後、このパスを
mir_built、optimized_mir などのクエリにある適切なパスのリストへ挿入できます。
(これが最適化である場合は、optimized_mir のリストに入れるべきです。)
単純な MIR パスのもう 1 つの例は CleanupPostBorrowck です。これは
MIR を走査し、コード生成に関連しないすべての文を削除します。
そのソースからわかるように、これはまずダミー型、つまりフィールドを持たない
構造体を定義することで定義されます。
#![allow(unused)]
fn main() {
pub struct CleanupPostBorrowck;
}
これに対して MirPass トレイトを実装します。
#![allow(unused)]
fn main() {
impl<'tcx> MirPass<'tcx> for CleanupPostBorrowck {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
...
}
}
}
このパスは mir_drops_elaborated_and_const_checked クエリ内で登録します。
(これが最適化である場合は、optimized_mir のリストに入れるべきです。)
パスを書いている場合、おそらく MIR visitor を使いたくなるでしょう。MIR visitor は、何かを検索したり小さな 編集を加えたりするために、MIR のすべての部分を走査する便利な方法です。
スティール
中間クエリ mir_const() と mir_promoted() は、
tcx.alloc_steal_mir() を使って割り当てられた &'tcx Steal<Body<'tcx>> を返します。
これは、その結果が後続のクエリによって stolen される可能性があることを示します。これは
MIR のクローンを避けるための最適化です。stolen された結果を使用しようとすると、
コンパイラで panic が発生します。したがって、MIR 処理パイプラインにおける
依存関係を考慮せずに、これらの中間クエリから誤って読み取らないようにすることが重要です。
このスティール機構があるため、処理パイプラインの特定のフェーズにある MIR が stolen される前に、それを読み取りたい可能性があるすべての者が すでに読み取りを完了していることを保証するよう、注意が必要です。
具体的には、mir_promoted(D) の結果にアクセスしたい
クエリ foo(D) がある場合、foo(D) はまず mir_const(D) クエリを呼び出す必要があります。
これにより、その結果を直接必要としていなくても、
それを強制的に実行できます。
この機構は少し危ういものです。より洗練された 代替案についての議論が rust-lang/rust#41710 にあります。
概要
以下は、MIR 処理パイプラインにおけるスティール依存関係の概要です2。
flowchart BT mir_for_ctfe* --borrow--> id40 id5 --steal--> id40 mir_borrowck* --borrow--> id3 id41 --steal part 1--> id3 id40 --steal part 0--> id3 mir_const_qualif* -- borrow --> id2 id3 -- steal --> id2 id2 -- steal --> id1 id1([mir_built]) id2([mir_const]) id3([mir_promoted]) id40([mir_drops_elaborated_and_const_checked]) id41([promoted_mir]) id5([optimized_mir]) style id1 fill:#bbf style id2 fill:#bbf style id3 fill:#bbf style id40 fill:#bbf style id41 fill:#bbf style id5 fill:#bbf
濃い色のスタジアム形クエリ(例: mir_built)は
パイプライン内の主要なクエリであり、一方で薄い色の長方形クエリ(例: mir_const_qualif*3)は
&'tcx Steal<Body<'tcx>> から結果を読み取る必要がある後続のクエリです。
スティール機構では、依存ツリー内で同じまたはより高い位置にあるスタジアム形クエリが
実行される前に、長方形クエリを実行しなければなりません。
例
例として、MIR の const qualification を考えてみましょう。これは、mir_const クエリによって生成された結果を読み取ろうとします。しかし、その結果はパイプラインのどこかの時点で mir_promoted クエリによって奪取されます。mir_promoted が一度でも問い合わせられる前であれば、mir_const_qualif クエリの呼び出しは成功します。なぜなら、mir_const は Steal 結果を(初めて問い合わせられた場合は)生成し、(複数回問い合わせられた場合は)キャッシュし、その結果はまだ奪取されていないからです。mir_promoted が問い合わせられた後は、その結果は奪取されているため、結果を読み取るために mir_const_qualif クエリを呼び出すとパニックが発生します。
したがって、この奪取メカニズムでは、mir_promoted は実際に奪取する前に必ず mir_const_qualif* クエリが呼び出されることを保証すべきです。これにより、読み取りがすでに行われていることが保証されます(クエリはメモ化されますので、同じクエリを 2 回実行すると、2 回目は単にキャッシュから読み込まれます)。
-
mir_promotedクエリはタプル(&'tcx Steal<Body<'tcx>>, &'tcx Steal<IndexVec<Promoted, Body<'tcx>>>)を返し、promoted_mirは part 1(&'tcx Steal<IndexVec<Promoted, Body<'tcx>>>)を steal し、mir_drops_elaborated_and_const_checkedは part 0(&'tcx Steal<Body<'tcx>>)を steal します。そして、それらのスティールは互いに無関係であり、 つまり個別に実行できます。 ↩ -
クエリにおける
*接尾辞は、同じ接頭辞を持つ一連のクエリを表すことに注意してください。 たとえば、mir_borrowck*はmir_borrowck、mir_borrowck_const_arg、およびmir_borrowck_opt_const_argを表します。 ↩