Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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)の(またはその一部の)参照です
  • RvaluePlace に代入できるものです
  • 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 条件と、フィールドを持たないバリアントを持つ enummatch 文は、 TerminatorKind::SwitchInt へ lowering されます。 取り得る各値(したがって if 条件では 01)には、 コードが継続する対応する BasicBlock があります。 分岐対象となる引数は、(ここでも)if 条件の値を表す Operand です。

パターンマッチング

フィールドを持つバリアントを持つ enummatch 文も TerminatorKind::SwitchInt へ lowering されますが、Operand は 値の判別子を見つけられる Place を参照します。 これは多くの場合、判別子を新しい一時変数へ読み込むことを伴います。

集約値の構築

あらゆる種類の集約値(例: 構造体やタプル)は Rvalue::Aggregate を介して構築されます。 すべてのフィールドは Operator へ lowering されます。 これは本質的に、 集約値の各フィールドにつき 1 つの代入文に加え、 enum の場合には判別子への代入を行うのと同等です。