LoginSignup
9
3

More than 3 years have passed since last update.

本家Rustコンパイラのソースを読もうとしてみる(4)

Last updated at Posted at 2020-09-09

前回までのあらすじ

Guide to Rustc Developmentという便利なガイドに沿って、Rustの本家コンパイラであるrust-lang/rustのソースコードを読んでみようという試み。なかなか終わらない「Source Code Representations」の、MIRのあたりから。

目次(初回)

Source Code Representations

Token列、AST、HIRときたので、今回はMIR生成を見ていく。このあたりからQuery systemがガンガン使われるので、シーケンシャルな感覚でいると流れが追いづらい。内容的にもこれまでより難しいので、前提となる知識から整理していこう。

CFG

MIRの構造はControl-flow graph(CFG)をベースにしている。私は詳しくないが、最適化コンパイラではそれなりに使われるデータ構造と思われる。MIRにおいてCFGがどのように表されるのかの具体例は、ガイドの説明が結構丁寧なのでそちらを参照するのが分かりやすい。基本的には基本ブロック(Basic Block)が並び、典型的には基本ブロックの中には複数のstatementと単独のterminatorが配置される。terminatorは次に進む基本ブロックを指し示しており、場合によっては条件で決まる複数の行き先をもつ。

(<n>、<m>には番号が入る)
bb<n>: {
    statement
    statement
    ...
    terminater -> bb<m>;
}

データ構造がそれなりに複雑なので、まずはこの構造とソースの対応を確認しておこう。

MIRの生成はrustc_mir_buildで定義されているmir_builtのQueryから開始される。その実体となる関数mir_build()のシグネチャで出現するデータ型を調べてみる。

rustc_mir_build/src/build/mod.rs
crate fn mir_built<'tcx>(
    tcx: TyCtxt<'tcx>,
    def: ty::WithOptConstParam<LocalDefId>,
) -> &'tcx ty::steal::Steal<Body<'tcx>> {
    if let Some(def) = def.try_upgrade(tcx) {
        return tcx.mir_built(def);
    }

    tcx.alloc_steal_mir(mir_build(tcx, def))
}

/// Construct the MIR for a given `DefId`.
fn mir_build(tcx: TyCtxt<'_>, def: ty::WithOptConstParam<LocalDefId>) -> Body<'_> {
...
}

defとしてDefIDを受け取り、Bodyという型を返している。

ガイドにもある通り、MIRの生成は関数の本体ブロックや、staticconstの初期化ブロックなどに対して行われる。考えてみれば、プログラムにおいて実行される記述はそのような部分に絞られるのだろう。そのため、ASTやHIRのようにクレート全体を渡すのではなく、DefIDを渡すことでソースのブロック単位で変換を行うような作りになっている。(Query戦略のほうが理由としては大きいかもしれないが、、)

関数の結果としては、Bodyという型を返しており、この型名からもブロック単位で処理が行われていることが読み取れる。では、Bodyの構造について掘り下げていこう。

Bodyは様々な情報をもっているが、その中にbasic_blocksというベクタがある。これは、MIRが複数の基本ブロックで構成されているという構造と一致しそうだ。ベクタの要素としてはBasicBlockBasicBlockDataをもっている。BasicBlockDataにはStatementのベクタとTerminaterがあり、これも基本ブロックの内部構造と一致していることが分かる。BasicBlockは、BasicBlockDataに紐づくインデックスを保持しているようで、見る限りBasicBlockを使ったアクセスのほうが多用されている。(ちなみに執筆時点のガイドには、MIRはMirという型で表されると記載されているが、そのような型は見当たらなかった)

rustc_middle/src/mir/mod.rs
/// The lowered representation of a single function.
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable, TypeFoldable)]
pub struct Body<'tcx> {
    /// A list of basic blocks. References to basic block use a newtyped index type `BasicBlock`
    /// that indexes into this vector.
    basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
...
}
...
rustc_index::newtype_index! {
    pub struct BasicBlock {
        derive [HashStable]
        DEBUG_FORMAT = "bb{}",
        const START_BLOCK = 0,
    }
}
...
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
pub struct BasicBlockData<'tcx> {
    /// List of statements in this block.
    pub statements: Vec<Statement<'tcx>>,

    /// Terminator for this block.
    ///
    /// N.B., this should generally ONLY be `None` during construction.
    /// Therefore, you should generally access it via the
    /// `terminator()` or `terminator_mut()` methods. The only
    /// exception is that certain passes, such as `simplify_cfg`, swap
    /// out the terminator temporarily with `None` while they continue
    /// to recurse over the set of basic blocks.
    pub terminator: Option<Terminator<'tcx>>,
...
}

Terminatorの中身についても一応確認しておこう。Terminatorの構造体はTerminatorKindをもっており、このenumの要素の多くは内部でBasicBlockをもっている。これは次に向かう基本ブロックを意味していると思われる。条件によって分岐するような場合はBasicBlockをベクタとして保持している。

#[derive(Clone, TyEncodable, TyDecodable, HashStable, PartialEq)]
pub enum TerminatorKind<'tcx> {
    /// Block should have one successor in the graph; we jump there.
    Goto { target: BasicBlock },

    /// Operand evaluates to an integer; jump depending on its value
    /// to one of the targets, and otherwise fallback to `otherwise`.
    SwitchInt {
...
        /// Possible branch sites. The last element of this vector is used
        /// for the otherwise branch, so targets.len() == values.len() + 1
        /// should hold.
        //
        // This invariant is quite non-obvious and also could be improved.
        // One way to make this invariant is to have something like this instead:
        //
        // branches: Vec<(ConstInt, BasicBlock)>,
        // otherwise: Option<BasicBlock> // exhaustive if None
        //
        // However we’ve decided to keep this as-is until we figure a case
        // where some other approach seems to be strictly better than other.
        targets: Vec<BasicBlock>,
    },
...
}
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable)]
pub struct Terminator<'tcx> {
    pub source_info: SourceInfo,
    pub kind: TerminatorKind<'tcx>,
}

これで、CFGを構成する基本ブロックの構造と、その構造のソース内での表現を確認することができた。

THIR

まだMIRには行けない。HIRからMIRに変換する前に、Typed HIR(THIR)へ変換するという処理が入っている。THIRはHigh-level Abstract IR (HAIR)と呼ばれることも多く、また、全体構成だと省略されていることもあったりして、ちょっと混乱する。この変換では、暗黙的な型強制を明示したり、メソッドの呼び出し形式を変更したりするようだ。THIRも、ソース全体をまとめて変換するのではなく、式に対して必要に応じて変換されているようだ。

THIRへの変換処理は主にrustc_mir_build::thirで実施されており、式に対する処理はhir::Exprに対するmake_mirror()メソッドから始まる。

rustc_mir_build/src/thir/cx/expr.rs
impl<'tcx> Mirror<'tcx> for &'tcx hir::Expr<'tcx> {
    type Output = Expr<'tcx>;

    fn make_mirror(self, cx: &mut Cx<'_, 'tcx>) -> Expr<'tcx> {
        let temp_lifetime = cx.region_scope_tree.temporary_scope(self.hir_id.local_id);
        let expr_scope = region::Scope { id: self.hir_id.local_id, data: region::ScopeData::Node };

        debug!("Expr::make_mirror(): id={}, span={:?}", self.hir_id, self.span);

        let mut expr = make_mirror_unadjusted(cx, self);

        // Now apply adjustments, if any.
        for adjustment in cx.typeck_results().expr_adjustments(self) {
            debug!("make_mirror: expr={:?} applying adjustment={:?}", expr, adjustment);
            expr = apply_adjustment(cx, self, expr, adjustment);
        }

        // Next, wrap this up in the expr's scope.
        expr = Expr {
            temp_lifetime,
            ty: expr.ty,
            span: self.span,
            kind: ExprKind::Scope {
                region_scope: expr_scope,
                value: expr.to_ref(),
                lint_level: LintLevel::Explicit(self.hir_id),
            },
        };
...
        // OK, all done!
        expr
    }
}

処理はmake_mirror_unadjusted()へと続いていき、ここで式の種別ごとの変換に実施される。一例としてメソッド呼び出しの変換処理を見てみると、ソース内のコメントにもあるようにa.b(c)というメソッドの呼び出しをTrait::b(a, c)という形式に変換している。変換後の形式はUFCS(Unified Function Call Syntax)と呼ばれるらしい。知らなかった。

rustc_mir_build/src/thir/cx/expr.rs
fn make_mirror_unadjusted<'a, 'tcx>(
    cx: &mut Cx<'a, 'tcx>,
    expr: &'tcx hir::Expr<'tcx>,
) -> Expr<'tcx> {
    let expr_ty = cx.typeck_results().expr_ty(expr);
    let temp_lifetime = cx.region_scope_tree.temporary_scope(expr.hir_id.local_id);

    let kind = match expr.kind {
        // Here comes the interesting stuff:
        hir::ExprKind::MethodCall(_, method_span, ref args, fn_span) => {
            // Rewrite a.b(c) into UFCS form like Trait::b(a, c)
            let expr = method_callee(cx, expr, method_span, None);
            let args = args.iter().map(|e| e.to_ref()).collect();
            ExprKind::Call { ty: expr.ty, fun: expr.to_ref(), args, from_hir_call: true, fn_span }
        }
...
    };

    Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind }
}

make_mirror()Cxという型のメソッドであるmirror()から呼び出され、このmirror()がMIRへの変換中に多用されることになる。

rustc_mir_build/src/thir/cx/mod.rs
impl<'a, 'tcx> Cx<'a, 'tcx> {
    /// Normalizes `ast` into the appropriate "mirror" type.
    crate fn mirror<M: Mirror<'tcx>>(&mut self, ast: M) -> M::Output {
        ast.make_mirror(self)
    }
...
}

THIRへの変換はMIR生成のところどころで現れるため、意識しておくと読みやすくなるだろう。

MIR

ようやくMIRへの変換処理を読む準備が整った。上でシグネチャだけ確認したmir_build()に戻り、その中身を追っていこう。ここでは、関数定義の本体ブロックを想定して見ていく。

まずはDefIDに基づいてBodyIDを取り出した後、build::construct_fn()に進んでいく。

rustc_mir_build/src/build/mod.rs
/// Construct the MIR for a given `DefId`.
fn mir_build(tcx: TyCtxt<'_>, def: ty::WithOptConstParam<LocalDefId>) -> Body<'_> {
    let id = tcx.hir().local_def_id_to_hir_id(def.did);

    // Figure out what primary body this item has.
    let (body_id, return_ty_span, span_with_body) = match tcx.hir().get(id) {
        Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(_, decl, body_id, _, _), .. }) => {
            (*body_id, decl.output.span(), None)
        }
        Node::Item(hir::Item {
            kind: hir::ItemKind::Fn(hir::FnSig { decl, .. }, _, body_id),
            span,
            ..
        })
        | Node::ImplItem(hir::ImplItem {
            kind: hir::ImplItemKind::Fn(hir::FnSig { decl, .. }, body_id),
            span,
            ..
        })
        | Node::TraitItem(hir::TraitItem {
            kind: hir::TraitItemKind::Fn(hir::FnSig { decl, .. }, hir::TraitFn::Provided(body_id)),
            span,
            ..
        }) => {
            // Use the `Span` of the `Item/ImplItem/TraitItem` as the body span,
            // since the def span of a function does not include the body
            (*body_id, decl.output.span(), Some(*span))
        }
...
    };

    // If we don't have a specialized span for the body, just use the
    // normal def span.
    let span_with_body = span_with_body.unwrap_or_else(|| tcx.hir().span(id));

    tcx.infer_ctxt().enter(|infcx| {
        let cx = Cx::new(&infcx, def, id);
        let body = if let Some(ErrorReported) = cx.typeck_results().tainted_by_errors {
            build::construct_error(cx, body_id)
        } else if cx.body_owner_kind.is_fn_or_closure() {
...
            let body = tcx.hir().body(body_id);
...
            let mut mir = build::construct_fn(
                cx,
                id,
                arguments,
                safety,
                abi,
                return_ty,
                return_ty_span,
                body,
                span_with_body
            );
            mir.yield_ty = yield_ty;
            mir
        } else {
...
        };

        lints::check(tcx, &body, def.did);
...
        body
    })
}

construct_fn()では、まず冒頭部分でBuilder::new()によりBuilderを生成していることに注目しよう。ついでに、Cxとしてhirが渡されていることも意識しておく。この関数は後から他の部分も見るので、全体を載せている。

rustc_mir_build/src/build/mod.rs
fn construct_fn<'a, 'tcx, A>(
    hir: Cx<'a, 'tcx>,
    fn_id: hir::HirId,
    arguments: A,
    safety: Safety,
    abi: Abi,
    return_ty: Ty<'tcx>,
    return_ty_span: Span,
    body: &'tcx hir::Body<'tcx>,
    span_with_body: Span
) -> Body<'tcx>
where
    A: Iterator<Item = ArgInfo<'tcx>>,
{
    let arguments: Vec<_> = arguments.collect();

    let tcx = hir.tcx();
    let tcx_hir = tcx.hir();
    let span = tcx_hir.span(fn_id);

    let fn_def_id = tcx_hir.local_def_id(fn_id);

    let mut builder = Builder::new(
        hir,
        span_with_body,
        arguments.len(),
        safety,
        return_ty,
        return_ty_span,
        body.generator_kind,
    );

    let call_site_scope =
        region::Scope { id: body.value.hir_id.local_id, data: region::ScopeData::CallSite };
    let arg_scope =
        region::Scope { id: body.value.hir_id.local_id, data: region::ScopeData::Arguments };
    let mut block = START_BLOCK;
    let source_info = builder.source_info(span);
    let call_site_s = (call_site_scope, source_info);
    unpack!(
        block = builder.in_scope(call_site_s, LintLevel::Inherited, |builder| {
            if should_abort_on_panic(tcx, fn_def_id, abi) {
                builder.schedule_abort();
            }

            let arg_scope_s = (arg_scope, source_info);
            // `return_block` is called when we evaluate a `return` expression, so
            // we just use `START_BLOCK` here.
            unpack!(
                block = builder.in_breakable_scope(
                    None,
                    START_BLOCK,
                    Place::return_place(),
                    |builder| {
                        builder.in_scope(arg_scope_s, LintLevel::Inherited, |builder| {
                            builder.args_and_body(
                                block,
                                fn_def_id.to_def_id(),
                                &arguments,
                                arg_scope,
                                &body.value,
                            )
                        })
                    },
                )
            );
            // Attribute epilogue to function's closing brace
            let fn_end = span_with_body.shrink_to_hi();
            let source_info = builder.source_info(fn_end);
            let return_block = builder.return_block();
            builder.cfg.goto(block, source_info, return_block);
            builder.cfg.terminate(return_block, source_info, TerminatorKind::Return);
            // Attribute any unreachable codepaths to the function's closing brace
            if let Some(unreachable_block) = builder.cached_unreachable_block {
                builder.cfg.terminate(unreachable_block, source_info, TerminatorKind::Unreachable);
            }
            return_block.unit()
        })
    );
    assert_eq!(block, builder.return_block());

    let spread_arg = if abi == Abi::RustCall {
        // RustCall pseudo-ABI untuples the last argument.
        Some(Local::new(arguments.len()))
    } else {
        None
    };
    debug!("fn_id {:?} has attrs {:?}", fn_def_id, tcx.get_attrs(fn_def_id.to_def_id()));

    let mut body = builder.finish();
    body.spread_arg = spread_arg;
    body
}

Builderのメンバを確認しよう。いろいろあるが、Cxとしてhirをもつ他、CFGという型のメンバをもっている。そしてこのCFGは、basic_blocksとして基本ブロックのベクタを表していることが分かる。これはBodyに含まれるbasic_blocksと同じ構造だ。

rustc_mir_build/src/build/mod.rs
struct Builder<'a, 'tcx> {
    hir: Cx<'a, 'tcx>,
    cfg: CFG<'tcx>,
...
}
...
struct CFG<'tcx> {
    basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
}

CFGの型には、基本ブロックやその内部データを操作するためのメソッドが定義されている。基本的なところをいくつか示しておくと、新しく基本ブロックを作るstart_new_block()Statementを追加するpush()Terminatorを設定するterminate()などがある。MIRの生成では、これらのメソッドを駆使して、望むような構造を作り上げていくことになる。

rustc_mir_build/src/build/cfg.rs
impl<'tcx> CFG<'tcx> {
    crate fn block_data(&self, blk: BasicBlock) -> &BasicBlockData<'tcx> {
        &self.basic_blocks[blk]
    }

    crate fn block_data_mut(&mut self, blk: BasicBlock) -> &mut BasicBlockData<'tcx> {
        &mut self.basic_blocks[blk]
    }

    // llvm.org/PR32488 makes this function use an excess of stack space. Mark
    // it as #[inline(never)] to keep rustc's stack use in check.
    #[inline(never)]
    crate fn start_new_block(&mut self) -> BasicBlock {
        self.basic_blocks.push(BasicBlockData::new(None))
    }
...
    crate fn push(&mut self, block: BasicBlock, statement: Statement<'tcx>) {
        debug!("push({:?}, {:?})", block, statement);
        self.block_data_mut(block).statements.push(statement);
    }
...
    crate fn terminate(
        &mut self,
        block: BasicBlock,
        source_info: SourceInfo,
        kind: TerminatorKind<'tcx>,
    ) {
        debug!("terminating block {:?} <- {:?}", block, kind);
        debug_assert!(
            self.block_data(block).terminator.is_none(),
            "terminate: block {:?}={:?} already has a terminator set",
            block,
            self.block_data(block)
        );
        self.block_data_mut(block).terminator = Some(Terminator { source_info, kind });
    }

    /// In the `origin` block, push a `goto -> target` terminator.
    crate fn goto(&mut self, origin: BasicBlock, source_info: SourceInfo, target: BasicBlock) {
        self.terminate(origin, source_info, TerminatorKind::Goto { target })
    }
}

construct_fn()の最後を見ると、builderに対してfinish()というメソッドを呼んでいる。このfinish()メソッドで、作り上げたcfgを同じ型であるBodybasic_blocksへと移し、MIRを返すという流れになっている。

rustc_mir_build/src/build/mod.rs
impl<'a, 'tcx> Builder<'a, 'tcx> {
...
    fn finish(self) -> Body<'tcx> {
        for (index, block) in self.cfg.basic_blocks.iter().enumerate() {
            if block.terminator.is_none() {
                span_bug!(self.fn_span, "no terminator on block {:?}", index);
            }
        }

        Body::new(
            self.cfg.basic_blocks,
            self.source_scopes,
            self.local_decls,
            self.canonical_user_type_annotations,
            self.arg_count,
            self.var_debug_info,
            self.fn_span,
            self.generator_kind,
        )
    }
...
}

さて、今度はconstruct_fn()の中段あたりに注目し、cfgを作り上げるところを追っていこう。マクロやクロージャが絡み合い複雑になっているが、args_and_body()というメソッドが呼ばれている。大きく省略しているが、このメソッドの最後のあたりを見る。

rustc_mir_build/src/build/mod.rs
impl<'a, 'tcx> Builder<'a, 'tcx> {
...
    fn args_and_body(
        &mut self,
        mut block: BasicBlock,
        fn_def_id: DefId,
        arguments: &[ArgInfo<'tcx>],
        argument_scope: region::Scope,
        ast_body: &'tcx hir::Expr<'tcx>,
    ) -> BlockAnd<()> {
...
        let body = self.hir.mirror(ast_body);
        self.into(Place::return_place(), block, body)
    }
}

上で説明したmirror()メソッドでTHIRに変換したのち、into()というメソッドを呼び出している。このinto()メソッドは、eval_into()を経由して、into_expr()を呼び出すことになる。

rustc_mir_build/src/build/into.rs
impl<'a, 'tcx> Builder<'a, 'tcx> {
    crate fn into<E>(
        &mut self,
        destination: Place<'tcx>,
        block: BasicBlock,
        expr: E,
    ) -> BlockAnd<()>
    where
        E: EvalInto<'tcx>,
    {
        expr.eval_into(self, destination, block)
    }
}

impl<'tcx> EvalInto<'tcx> for ExprRef<'tcx> {
    fn eval_into(
        self,
        builder: &mut Builder<'_, 'tcx>,
        destination: Place<'tcx>,
        block: BasicBlock,
    ) -> BlockAnd<()> {
        let expr = builder.hir.mirror(self);
        builder.into_expr(destination, block, expr)
    }
}

ではinto_expr()を見ていく。ここで式の種別ごとに処理が分けられ、それぞれで先ほどのCFGのメソッドを使い、MIRが生成されていく。いくつか確認してみよう。

rustc_mir_build/src/build/expr/into.rs
impl<'a, 'tcx> Builder<'a, 'tcx> {
    /// Compile `expr`, storing the result into `destination`, which
    /// is assumed to be uninitialized.
    crate fn into_expr(
        &mut self,
        destination: Place<'tcx>,
        mut block: BasicBlock,
        expr: Expr<'tcx>,
    ) -> BlockAnd<()> {
...
        let block_and = match expr.kind {
...
            ExprKind::Block { body: ast_block } => {
                this.ast_block(destination, block, ast_block, source_info)
            }
            ExprKind::Match { scrutinee, arms } => {
                this.match_expr(destination, expr_span, block, scrutinee, arms)
            }
...
            ExprKind::LogicalOp { op, lhs, rhs } => {
                // And:
                //
                // [block: If(lhs)] -true-> [else_block: If(rhs)] -true-> [true_block]
                //        |                          | (false)
                //        +----------false-----------+------------------> [false_block]
                //
                // Or:
                //
                // [block: If(lhs)] -false-> [else_block: If(rhs)] -true-> [true_block]
                //        | (true)                   | (false)
                //  [true_block]               [false_block]

                let (true_block, false_block, mut else_block, join_block) = (
                    this.cfg.start_new_block(),
                    this.cfg.start_new_block(),
                    this.cfg.start_new_block(),
                    this.cfg.start_new_block(),
                );

                let lhs = unpack!(block = this.as_local_operand(block, lhs));
                let blocks = match op {
                    LogicalOp::And => (else_block, false_block),
                    LogicalOp::Or => (true_block, else_block),
                };
                let term = TerminatorKind::if_(this.hir.tcx(), lhs, blocks.0, blocks.1);
                this.cfg.terminate(block, source_info, term);

                let rhs = unpack!(else_block = this.as_local_operand(else_block, rhs));
                let term = TerminatorKind::if_(this.hir.tcx(), rhs, true_block, false_block);
                this.cfg.terminate(else_block, source_info, term);

                this.cfg.push_assign_constant(
                    true_block,
                    source_info,
                    destination,
                    Constant { span: expr_span, user_ty: None, literal: this.hir.true_literal() },
                );

                this.cfg.push_assign_constant(
                    false_block,
                    source_info,
                    destination,
                    Constant { span: expr_span, user_ty: None, literal: this.hir.false_literal() },
                );

                // Link up both branches:
                this.cfg.goto(true_block, source_info, join_block);
                this.cfg.goto(false_block, source_info, join_block);
                join_block.unit()
            }
...
        };
...
        block_and
    }
}
  • ExprKind::Block:関数の本体などは全体がブロックで囲われているため、最初はここに入ると思われる。詳細なソースは記載しないが、ast_block()ast_block_stmts()と続く。このast_block_stmts()も、Rustにおける束縛の考え方のようなものが反映されていて、深く読み込んだら面白そうな感じだ。ブロック内の各文はstmt_expr()へと渡り、いくつか条件はあるものの、特に引っかからないものはas_temp()expr_as_temp()と進んでいき、再度into()が呼ばれるように見える。つまり、ブロック内の文に対してinto_expr()を呼び出し、再帰的に処理されていく様子が伺える。
  • ExprKind::Match:いわゆるプログラムが分岐していくような処理は、大抵ここに入りmatch_expr()に進んでいくと思われるが、この先は結構処理が大きく複雑なのであまり追えていない。
  • ExprKind::LogicalOp:その代わり、論理演算の処理はMIRにおける分岐の処理がコンパクトにまとまっていて、分かりやすいと思ったので少し説明しておく。ここではA && Bのような処理を考えてみよう。
    • 現在はAにいる。
    • まず、start_new_block()により必要な基本ブロックを作る。全体として真になったときのtrue_block、全体として偽になったときのfalse_blockBの処理が入るelse_block、全てが終わったあとに進んでいくjoin_blockだ。
    • 現在地Aから進む先は条件によりelse_blockfalse_blockになるため、terminate()でそのようにTerminatorを設定する。
    • else_blockに進んだ場合、そこから向かう先はtrue_blockfalse_blockになるため、こちらもterminate()で設定する。
    • true_blockfalse_blockにはpush_assign_constant()によりそれぞれStatementとしてtrueとfalseを設定する。
    • 最後にgoto()によりtrue_blockfalse_blockTerminatorjoin_blockに設定し、作成した基本ブロックをつなぐ流れができあがる。

完成したcfgを上述した通りBodyの要素として返すことで、MIR生成が完了となる。

つづく

これでソースコードからMIRまでの変換を見ていくことができた。ここからLLVM IRの生成に進んでいくかと思いきや、次章「Analysis」ではRustの特徴的な機構をいろいろと見ていく(たぶん)。全部は深掘れないと思うのでいくつかピックアップしていこうと思います。

本家Rustコンパイラのソースを読もうとしてみる(5)←次回

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3