Interpreters in Rust compiler

Interpreters in Rust compiler

「メタプログラミングあるところ、インタプリタあり」——これは私が今考えたいい加減な思いつきですが、少なくともRustに関しては当てはまっています。本講演では、Rustコンパイラの中にある「インタプリタ」を3種類2種類取り上げて説明したいと思います。

Ba655e3712aaabfbca289fe136f85fe4?s=128

Masaki Hara

August 01, 2018
Tweet

Transcript

  1. Rustを支えるインタープリター ~miriとChalkと、宣言マクロ~ 2018/08/01 Rust LT #2 原 将己 (ウォンテッドリー株式会社)

  2. miri: MIRインタプリタ

  3. miriを使う (1) • https://github.com/solson/miri • 手順 1. nightly toolchainとxargoを入手 2.

    miriをビルド 3. MIRつきのstdを作成 4. miriでお好きなRustコードが動かせます!
  4. miriを使う (2) • https://play.rust-lang.org/ • constと書く

  5. miri • MIR Interpreter • MIRを “Rust abstract machine” の上で実行する

    • unsafeを含め、ほとんどのRustコードを実行できる
  6. HIR (High-level Intermediate Representation) • ASTを名前解決して脱糖したもの

  7. MIR (Mid-level Intermediate Representation) • LLVM IRを生成する直前の中間表現 • 多相で、制御フローグラフだがSSAではない

  8. Rust Abstract Machine • メモリ = バイト列 • ターゲットアーキテクチャに依存する •

    アラインメント • エンディアン • usizeの大きさ • 完全な機械ではないが、典型的なunsafeコードを実行できる
  9. Constクイズ • 次のうちnightly compilerでconstに代入できるのはどれ? drop(42) if size_of::<usize>() == 4 {

    "32" } else { "64" } *&42 Vec::<i32>::new()
  10. Constクイズ • 次のうちnightly compilerでconstに代入できるのはどれ? drop(42) if size_of::<usize>() == 4 {

    "32" } else { "64" } *&42 Vec::<i32>::new()
  11. CTFE (Compile-Time Function Evaluation) • C++でいうconstexpr • ~2017: librustc_const_eval (HIRインタプリタ)

    • 2018~: miri (MIRインタプリタ)
  12. CTFEの歴史 • 有史以前: staticの中身はLLVM定数式で実現されていた • 2011年12月 定数パターンのためにeval_const_exprが爆誕。 これはAST(今のHIR)を直接評価していた。 (64ce092) •

    2013年03月 配列の長さに定数式が利用可能になる (#5112) • 2016年03月 rustc_const_evalが切り出される(#32259) • 2016年08月 最初期のstatic生成コードが削除される (#35764) • 2017年12月 miriがコンパイラに統合開始 (#45002) • 2018年3月 rustc_const_evalが削除、miriが正式稼動 (#46882) • 2018年5月 miriがstableで利用開始 (1.26.0)
  13. miriとCTFE • miriになったことで参照が使えるようになった • 配列のインデックス(Index trait)ですら参照を使っている • miriには可能でもCTFEでは禁止される操作がある • 分岐とループ

    • dropの呼び出し • 参照のアドレス取り出し etc. • 非決定的な操作を許可すると未定義動作になりうる • 配列サイズが場所によって異なる値に評価される、など
  14. Chalk: トレイトロジックソルバー

  15. chalkを使う • https://github.com/rust-lang-nursery/chalk • 手順 1. cargo run (要nightly) 2.

    REPLが起動する 3. → $ cargo run ?- load libstd.chalk ?- i32: Clone ?- ^C
  16. chalkとは • トレイト解決のためのProlog風ソルバー • Prolog = 手続き型 + 単一化 +

    バックトラック • chalk = Prolog – 副作用 + 量化 + 様相
  17. Prologの復習: 項 • 右のように「関数のようなもの」を ネストさせ、項 (term) を作る。 • 単射で互いに交わらないので、関数 というよりもRustのバリアントに近

    い。 • 変数を含めることができる(Prolog では大文字して区別する) f(g(a), f(b)) f(a) != f(b) f(a) != g(b) f(X, g(X))
  18. Prologの復習: 述語と節 • 述語 (predicate) は項と似ているが、 真偽を表わす • Prologプログラムは節 (clause)

    の 集まりである。節はパターンマッチ のように動作する。 • ゴール節を書くことで実行開始する p(g(a), f(b)) p(X, f(Y)) :- q(X), q(Y) ?- p(f(a), X)
  19. Prologの復習: 単一化とバックトラック • 通常のパターンマッチと異なり、右 辺の変数は重複してよい。これは単 一化 (unification) によって処理さ れる。 •

    複数の節にマッチするときは、バッ クトラックにより各選択肢を順番に 試す。 p(a, f(X)) :- true P(g(X), Y) :- p(X, Y) eq(X, X) :- true
  20. Prologの復習: 例 • リストを連結する関数→「ZはXとYを連結したリスト」という 述語として表現 append(nil, X, X) :- true

    append(cons(X, Y), Z, cons(X, W)) :- append(Y, Z, W)
  21. Rustのトレイトシステムとの対応 • Rustの型→Prologの項 • Rustのトレイト→Prologの述語 • Rustのtrait impl→Prologの確定節 • Rustのtrait制約→Prologのゴール節

    • 多相性→??? • サブタイピング→??? • 射影型→??? • クレート際モード→??? • 否定的な推論→???
  22. ChalkをPrologのように使う • Prologと異なり、関数記号はあらかじめ宣言する必要がある struct Nil {} struct Cons<H, T> {}

    struct i32 {}
  23. ChalkをPrologのように使う • 述語の第一引数 (Self) の位置が特殊 // 第一引数は書かない trait Append<Y, Z>

    {} // 第一引数がforの後にくる impl<X> Append<X, Nil> for X {}
  24. ChalkをPrologのように使う • 練習: Consの場合に対応させて、以下のクエリを通す $ cargo run ?- load metalist.chalk

    ?- exists<X> { Cons<i64, Nil>: Append<Cons<i32, Nil>, X> } Unique; substitution [?0 := Cons<i64, Cons<i32, Nil>>], lifetime constraints []
  25. 曖昧性 • 解が複数ある場合は失敗する ?- load libstd.chalk ?- exists<T> { T:

    AsRef<i32> } Unique ?- exists<T> { T: AsRef<Slice<i32>> } Ambiguous
  26. 多相性 • forall量化とexists量化をネストする ?- load libstd.chalk ?- forall<T> { if(T:

    Sized) { exists<U> { U: AsRef<T> } } } Unique
  27. 射影型 • 射影型の正規化には制限がある ?- load iter.chalk ?- exists<T> { <IntoIter<i32>

    as Iterator>::Item = T } Ambiguous ?- exists<T> { Normalize(<IntoIter<i32> as Iterator>::Item -> T) } Unique 練習: 上のように動作するiter.chalkを書く
  28. 否定 • 強い否定を記述できる (impl !Trait for T) ?- exists<T> {

    (T: Sized, T: AsRef<Slice<i32>>) } Unique 練習: 上のようになるようにlibstd.chalkを修正せよ
  29. クレート際モード • 以下のような例を考える trait Foo {} trait Bar {} struct

    A {} struct B {} impl Foo for A {} impl Bar for B {}
  30. クレート際モード • compat {} という様相を用いると、全ての可能世界での可能性 になる……なってほしい ?- exists<T> { T:

    Foo } Unique ?- exists<T> { (T: Foo, T: Bar) } No possible solution. ?- compatible { exists<T> { (T: Foo, T: Bar) } } Ambiguous ←現状はNo possible solutionが出力される
  31. Chalkまとめ • Chalkはトレイト解決用のProlog風言語 • コンパイラの責務が分割されることで、高速化・バグ修正・新 機能の追加が容易になることが期待されている • 現在Rustコンパイラへの統合が進められつつある • REPLで試すこともできるが、まだ挙動がところどころ怪しい

  32. まとめ • Rustコンパイラ内にはインタプリタが棲んでいる • miri: constのコンパイル時計算のため • chalk: トレイト解決のため •

    宣言マクロインタプリタ: マクロのため • つまり、メタプログラミングをすることができる。