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

Interpreters in Rust compiler

Interpreters in Rust compiler

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

Masaki Hara

August 01, 2018
Tweet

More Decks by Masaki Hara

Other Decks in Programming

Transcript

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

    miriをビルド 3. MIRつきのstdを作成 4. miriでお好きなRustコードが動かせます!
  2. miri • MIR Interpreter • MIRを “Rust abstract machine” の上で実行する

    • unsafeを含め、ほとんどのRustコードを実行できる
  3. Rust Abstract Machine • メモリ = バイト列 • ターゲットアーキテクチャに依存する •

    アラインメント • エンディアン • usizeの大きさ • 完全な機械ではないが、典型的なunsafeコードを実行できる
  4. 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)
  5. miriとCTFE • miriになったことで参照が使えるようになった • 配列のインデックス(Index trait)ですら参照を使っている • miriには可能でもCTFEでは禁止される操作がある • 分岐とループ

    • dropの呼び出し • 参照のアドレス取り出し etc. • 非決定的な操作を許可すると未定義動作になりうる • 配列サイズが場所によって異なる値に評価される、など
  6. chalkを使う • https://github.com/rust-lang-nursery/chalk • 手順 1. cargo run (要nightly) 2.

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

    バックトラック • chalk = Prolog – 副作用 + 量化 + 様相
  8. Prologの復習: 述語と節 • 述語 (predicate) は項と似ているが、 真偽を表わす • Prologプログラムは節 (clause)

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

    複数の節にマッチするときは、バッ クトラックにより各選択肢を順番に 試す。 p(a, f(X)) :- true P(g(X), Y) :- p(X, Y) eq(X, X) :- true
  10. Rustのトレイトシステムとの対応 • Rustの型→Prologの項 • Rustのトレイト→Prologの述語 • Rustのtrait impl→Prologの確定節 • Rustのtrait制約→Prologのゴール節

    • 多相性→??? • サブタイピング→??? • 射影型→??? • クレート際モード→??? • 否定的な推論→???
  11. 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 []
  12. 曖昧性 • 解が複数ある場合は失敗する ?- load libstd.chalk ?- exists<T> { T:

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

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

    (T: Sized, T: AsRef<Slice<i32>>) } Unique 練習: 上のようになるようにlibstd.chalkを修正せよ
  15. クレート際モード • 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が出力される
  16. まとめ • Rustコンパイラ内にはインタプリタが棲んでいる • miri: constのコンパイル時計算のため • chalk: トレイト解決のため •

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