MIR の構築
HIR から MIR への lowering は、次の(おそらく不完全な) 項目の一覧に対して行われます。
- 関数とクロージャの本体
static項目とconst項目の初期化子- enum 判別子の初期化子
- あらゆる種類のグルーと shim
- タプル構造体の初期化関数
- Drop コード(
Drop::drop関数は直接呼び出されません) - 明示的な
Drop実装を持たない型の Drop 実装
lowering は mir_built クエリを呼び出すことでトリガーされます。
MIR ビルダーは実際には HIR を使用せず、
代わりに THIR を操作し、
THIR 式を再帰的に処理します。
lowering は、シグネチャで指定されたすべての引数に対してローカル変数を作成します。
次に、指定されたすべての束縛に対してローカル変数を作成します(例: (a, b): (i32, String))。
これは 3 つの束縛を生成し、1 つは引数用、2 つは束縛用です。
次に、
引数からフィールドを読み取り、
その値を束縛変数へ書き込むフィールドアクセスを生成します。
この初期化が完了すると、lowering は本体(Block 式)の MIR を生成する関数への再帰呼び出しをトリガーし、
その結果を RETURN_PLACE に書き込みます。
すべてを unpack! する
MIR を生成する関数は、2 つのパターンのいずれかに分類される傾向があります。 まず、その関数が文だけを生成する場合、その関数は それらの文を追加すべき基本ブロックを引数として受け取ります。 その後、通常どおり結果を返すことができます。
fn generate_some_mir(&mut self, block: BasicBlock) -> ResultType {
...
}
しかし、新しい基本ブロックも生成する可能性のある関数もあります。
たとえば、if foo { 22 } else { 44 } のような式を lowering するには、
小さな「ひし形のグラフ」を生成する必要があります。
この場合、関数はコードが開始する基本ブロックを受け取り、
コード生成が終了する(可能性のある)新しい基本ブロックを返します。
これを表すために BlockAnd 型が使用されます。
fn generate_more_mir(&mut self, block: BasicBlock) -> BlockAnd<ResultType> {
...
}
これらの関数を呼び出すときは、実質的に「カーソル」であるローカル変数 block を持つのが一般的です。
これは、新しい MIR を追加している位置を表します。
generate_more_mir を呼び出すときは、このカーソルを更新したいはずです。
これは手動でも行えますが、面倒です。
let mut block;
let v = match self.generate_more_mir(..) {
BlockAnd { block: new_block, value: v } => {
block = new_block;
v
}
};
このため、let v = unpack!(block = self.generate_more_mir(...)) と書けるマクロを提供しています。
これは新しいブロックを抽出し、
unpack! で指定した変数 block を上書きするだけです。
式を目的の MIR へ lowering する
式について望む表現には、本質的に 4 種類あります。
Placeは既存のメモリ位置(ローカル、static、promoted)の(またはその一部の)参照ですRvalueはPlaceに代入できるものですOperandは、たとえば+演算や関数呼び出しへの引数です- 値のコピーを含む一時変数です
次の画像は、これらの表現間の相互作用の概要を示しています。
まず、関数本体を Rvalue へ lowering することで、
RETURN_PLACE への代入を作成できるようにします。この Rvalue の lowering は、今度はその引数(もしあれば)を
Operand へ lowering することをトリガーします。
Operand の lowering は、const オペランドを生成するか、
Place からムーブまたはコピーするため、Place の lowering をトリガーします。
Place へ lowering される式は、lowering される式に演算が含まれている場合、
一時変数の作成をトリガーすることがあります。
ここで蛇が自分の尾を噛むことになり、
そのローカルへ書き込まれる式に対して Rvalue の lowering をトリガーする必要があります。
演算子の lowering
組み込み型に対する演算子は、関数呼び出しへは lowering されません(そうすると、
トレイト impl が再びその演算自体を含むだけなので、
無限再帰呼び出しになってしまいます)。
代わりに、二項演算子、単項演算子、インデックス演算用の Rvalue があります。
これらの Rvalue は後で LLVM のプリミティブ演算または LLVM intrinsic に codegen されます。
それ以外のすべての型に対する演算子は、
その演算子に対応するトレイトの impl への関数呼び出しへ lowering されます。
lowering の種類に関係なく、演算子への引数は Operand へ lowering されます。
つまり、すべての引数は定数であるか、
ローカルまたは static のどこかにすでに存在する値を参照します。
メソッド呼び出しの lowering
メソッド呼び出しは、関数呼び出しと同じ TerminatorKind へ lowering されます。
MIR では、メソッド呼び出しと関数呼び出しの違いはもはやありません。
条件
if 条件と、フィールドを持たないバリアントを持つ enum の match 文は、
TerminatorKind::SwitchInt へ lowering されます。
取り得る各値(したがって if 条件では 0 と 1)には、
コードが継続する対応する BasicBlock があります。
分岐対象となる引数は、(ここでも)if 条件の値を表す Operand です。
パターンマッチング
フィールドを持つバリアントを持つ enum の match 文も
TerminatorKind::SwitchInt へ lowering されますが、Operand は
値の判別子を見つけられる Place を参照します。
これは多くの場合、判別子を新しい一時変数へ読み込むことを伴います。
集約値の構築
あらゆる種類の集約値(例: 構造体やタプル)は Rvalue::Aggregate を介して構築されます。
すべてのフィールドは Operator へ lowering されます。
これは本質的に、
集約値の各フィールドにつき 1 つの代入文に加え、
enum の場合には判別子への代入を行うのと同等です。