Upgrade to Pro — share decks privately, control downloads, hide ads and more …

カジュアルコントリビュータと学ぶRustコンパイラ

Avatar for Yuki Toyoda Yuki Toyoda
September 10, 2025
200

 カジュアルコントリビュータと学ぶRustコンパイラ

Rustの現場に学ぶ
〜Webアプリの裏側からOS、人工衛星まで〜
https://findy.connpass.com/event/359456/

にて発表した際に使用したスライドです。

Avatar for Yuki Toyoda

Yuki Toyoda

September 10, 2025
Tweet

Transcript

  1. Rustコンパイラの構成と特徴 全体的な構成: HIR HIR (High-level Intermediate Representation) と呼ばれる中間表現がまずは生成さ れる。その後THIR (Typed

    High-level Intermediate Representation)が生成され、次 のフェーズ(MIR)に渡される。 主には脱糖(desugar)を担う。たとえば、Rustの文法上現れる下記の文法機能 は、コンパイラ内部では違う形で扱われる。 for式やwhile式は、すべてmatch + loopに変換される。 asyncやawaitは、コンパイラ内部表現としてコルーチンに変換される。 11
  2. Rustコンパイラの構成と特徴 全体的な構成: HIR HIRは、次のcargoコマンドで確認できる。 cargo rustc -- -Z unpretty=hir-tree #

    AST形式で出力 cargo rustc -- -Z unpretty=hir # Rustのコード形式で出力 cargo rustc -- -Z unpretty=thir-tree 13
  3. Rustコンパイラの構成と特徴 全体的な構成: HIR 試しに下記のようなコードをHIRとして出力させてみる。 fn main() { let nums =

    vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let mut ans = 0; for num in nums { ans += num; } assert_eq!(ans, 55); } 14
  4. Rustコンパイラの構成と特徴 全体的な構成: HIR 本来のHIR自体はこのようにASTに近しい形だが、少し読みにくいのでRustコードに近い形式 で出力させる。 DefId(0:0 ~ for_hir[8e24]) => OwnerNodes

    { node: ParentedNode { parent: 4294967040, node: Crate( Mod { spans: ModSpans { inner_span: src/bin/for_hir.rs:1:1: 8:2 (#0), inject_use_span: no-location (#0), }, 15
  5. すると、for式がmatch, loop, matchの形式で変換されていることを確認できる。 // ... fn main() { let nums

    = <[_]>::into_vec(::alloc::boxed::box_new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); let mut ans = 0; { let _t = match #[lang = "into_iter"](nums) { mut iter => loop { match #[lang = "next"](&mut iter) { #[lang = "None"] {} => break, #[lang = "Some"] { 0: num } => { ans += num; } } }, }; _t }; match (&ans, &55) { (left_val, right_val) => { if !(*left_val == *right_val) { let kind = ::core::panicking::AssertKind::Eq; ::core::panicking::assert_failed(kind, &*left_val, &*right_val, ::core::option::Option::None); } } }; } 16
  6. Rustコンパイラの構成と特徴 全体的な構成: MIR HIRで使用したのと同じコードを使ってMIRを出力させてみると、次のような出力を見ること ができる。 copy などの文字列が見られる。 fn main() ->

    () { let mut _0: (); let _1: std::vec::Vec<i32>; // ... bb1: { _6 = ShallowInitBox(move _5, [i32; 10]); _26 = copy ((_6.0: std::ptr::Unique<[i32; 10]>).0: std::ptr::NonNull<[i32; 10]>) as *const [i32; 10] (Transmute); _27 = copy _26 as *const () (PtrToPtr); _28 = copy _27 as usize (Transmute); _29 = AlignOf([i32; 10]); _30 = Sub(copy _29, const 1_usize); _31 = BitAnd(copy _28, copy _30); _32 = Eq(copy _31, const 0_usize); assert(copy _32, "misaligned pointer dereference: address must be a multiple of {} but is {}", copy _29, copy _28) -> [success: bb15, unwind unreachable]; } 19
  7. Rustコンパイラの構成と特徴 特徴的な設計 > クエリシステム: TyCtxt<'tcx> 各クエリの結果は TyCtxt<'tcx> の構造体に実装されている関数を経由して取り 出せる。 TyCtxt

    が持つ type_of() などの関数を呼び出すと、 i. キャッシュがあるかないかをまずチェックする。 ii. キャッシュがあればその値を返し、なければデフォルト実装用の関数を呼び 出す。 iii. 結果はキャッシュとして保存される。 25
  8. Rustコンパイラの構成と特徴 特徴的な設計 > クエリシステム: クエリの定義 内部的には rustc_queries! というマクロで定義されている。 rustc_queries! {

    /// Records the type of every item. query type_of(key: DefId) -> Ty<'tcx> { cache_on_disk_if { key.is_local() } desc { |tcx| "computing the type of `{}`", tcx.def_path_str(key) } } ... } あとは先ほどの TypCtxt<'tcx> 経由で呼び出せる。 fn foo(tcx: TyCtxt<'_>, def_id: DefId) { let ident = tcx.type_of(def_id).instantiate_identity(); } 26