HIR
HIR –「High-Level Intermediate Representation(高レベル中間表現)」– は、rustc の大部分で使用される主要な IR です。
これは、パース、マクロ展開、名前解決の後に生成される抽象構文木(AST)の、コンパイラにとって扱いやすい表現です(HIR がどのように作成されるかについては Lowering を参照してください)。
HIR の多くの部分は Rust の表層構文に非常によく似ていますが、Rust の式形式の一部が脱糖されて取り除かれている点は例外です。
たとえば、for ループは loop に変換され、HIR には現れません。
これにより、HIR は通常の AST よりも解析しやすくなっています。
この章では、HIR の主要な概念について説明します。
rustc に -Z unpretty=hir-tree フラグを渡すことで、コードの HIR 表現を確認できます。
cargo rustc -- -Z unpretty=hir-tree
また、-Z unpretty=hir オプションを使用して、元のソースコードの式により近い HIR を生成することもできます。
cargo rustc -- -Z unpretty=hir
アウトオブバンドストレージと Crate 型
HIR におけるトップレベルのデータ構造は Crate であり、現在コンパイル中のクレートの内容を格納します(HIR は現在のクレートに対してのみ構築します)。
AST ではクレートのデータ構造は基本的にルートモジュールだけを含みますが、HIR の Crate 構造体には、クレートの内容をより簡単にアクセスできるよう整理するための多数のマップやその他のものが含まれます。
たとえば、HIR における個々のアイテム(モジュール、関数、トレイト、impl など)の内容は、親から直接アクセスできるわけではありません。
したがって、たとえば関数 bar() を含むモジュールアイテム foo があるとします。
#![allow(unused)]
fn main() {
mod foo {
fn bar() { }
}
}
この場合、HIR ではモジュール foo の表現(Mod 構造体)は、bar() の ItemId I だけを持ちます。
関数 bar() の詳細を取得するには、items マップで I を検索します。
この表現から得られる良い結果の 1 つは、これらのマップ内のキーと値のペアを反復処理することで、クレート内のすべてのアイテムを反復処理できることです(HIR 全体をくまなく探索する必要はありません)。 トレイトアイテムや impl アイテムのようなもの、および「本体」(後述)についても同様のマップがあります。
このように表現を構成するもう 1 つの理由は、インクリメンタルコンパイルとの統合をより良くするためです。
この方法では、(たとえば mod foo について)&rustc_hir::Item へのアクセスを得たとしても、関数 bar() の内容に直ちにアクセスできるわけではありません。
代わりに、bar() の id だけにアクセスでき、その id を指定して bar() の内容を検索する何らかの関数を呼び出す必要があります。これにより、コンパイラはあなたが bar() のデータにアクセスしたことを観測し、その依存関係を記録する機会を得ます。
HIR における識別子
HIR では、共存しながら異なる目的を果たすさまざまな識別子を使用します。
-
DefIdは、その名前が示すように、特定のクレート内の特定の定義、またはトップレベルアイテムを識別します。 これは 2 つの部分から構成されます。定義の由来となるクレートを識別するCrateNumと、クレート内の定義を識別するDefIndexです。HirIdとは異なり、すべての式にDefIdがあるわけではないため、コンパイル間でより安定しています。 -
LocalDefIdは基本的に、現在のクレートに由来することがわかっているDefIdです。 これにより、CrateNum部分を省略でき、ローカル定義を期待する関数にローカル定義だけが渡されることを型システムで保証できます。 -
HirIdは、現在のクレートの HIR 内のノードを一意に識別します。 これは 2 つの部分から構成されます。ownerと、owner内で一意なlocal_idです。 この組み合わせにより、インクリメンタルコンパイルに役立つ、より安定した値になります。DefIdとは異なり、HirIdは式のような細粒度のエンティティを参照できますが、現在のクレート内にローカルなままです。 -
BodyIdは、現在のクレート内の HIRBodyを識別します。 現在のところ、これはHirIdの単なるラッパーです。 HIR の本体に関する詳細については、HIR の章を参照してください。
これらの識別子は TyCtxt を通じて相互に変換できます。
HIR の操作
HIR を扱うときは、ほとんどの場合 TyCtxt を介して行います。
これには、さまざまな種類の ID 間の変換や、HIR ノードに関連付けられたデータの検索を行うためのメソッドが多数含まれています。これらは hir::map モジュールで定義されており、主に hir_ というプレフィックスが付いています。
たとえば、LocalDefId を持っていて、それを HirId に変換したい場合は、tcx.local_def_id_to_hir_id(def_id) を使用できます。
HIR ノードを持つのはローカルアイテムだけであるため、DefId ではなく LocalDefId が必要です。
同様に、tcx.hir_node(n) を使用して、HirId に対応するノードを検索できます。
これは Option<Node<'hir>> を返します。ここで Node はマップ内で定義されている enum です。
これに対してマッチすることで、HirId がどの種類のノードを参照していたのかを調べることができ、データ自体へのポインタも取得できます。多くの場合、n がどの種類のノードであるかはわかっています。たとえば、n が何らかの HIR 式でなければならないことがわかっている場合は、tcx.hir_expect_expr(n) を実行できます。これは &hir::Expr を抽出して返し、n が実際には式でない場合はパニックします。
最後に、[tcx.parent_hir_node(n)][parent_hir_node] のような呼び出しを介して、ノードの親を見つけることができます。
[parent_hir_node]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.parent_hir_node
HIRボディ
rustc_hir::Body は、関数/クロージャの本体や
定数の定義など、何らかの実行可能コードを表します。
ボディは 所有者 に関連付けられており、これは通常、何らかのアイテム
(例: fn() や const)ですが、クロージャ式である場合もあります
(例: |x, y| x + y)。TyCtxt を使用して、指定したdef-idに
関連付けられたボディ(hir_maybe_body_owned_by)を検索したり、ボディの
所有者(hir_body_owner_def_id)を検索したりできます。