Interpreters in Rust compiler
by
Masaki Hara
×
Copy
Open
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Slide 1
Slide 1 text
Rustを支えるインタープリター ~miriとChalkと、宣言マクロ~ 2018/08/01 Rust LT #2 原 将己 (ウォンテッドリー株式会社)
Slide 2
Slide 2 text
miri: MIRインタプリタ
Slide 3
Slide 3 text
miriを使う (1) • https://github.com/solson/miri • 手順 1. nightly toolchainとxargoを入手 2. miriをビルド 3. MIRつきのstdを作成 4. miriでお好きなRustコードが動かせます!
Slide 4
Slide 4 text
miriを使う (2) • https://play.rust-lang.org/ • constと書く
Slide 5
Slide 5 text
miri • MIR Interpreter • MIRを “Rust abstract machine” の上で実行する • unsafeを含め、ほとんどのRustコードを実行できる
Slide 6
Slide 6 text
HIR (High-level Intermediate Representation) • ASTを名前解決して脱糖したもの
Slide 7
Slide 7 text
MIR (Mid-level Intermediate Representation) • LLVM IRを生成する直前の中間表現 • 多相で、制御フローグラフだがSSAではない
Slide 8
Slide 8 text
Rust Abstract Machine • メモリ = バイト列 • ターゲットアーキテクチャに依存する • アラインメント • エンディアン • usizeの大きさ • 完全な機械ではないが、典型的なunsafeコードを実行できる
Slide 9
Slide 9 text
Constクイズ • 次のうちnightly compilerでconstに代入できるのはどれ? drop(42) if size_of::() == 4 { "32" } else { "64" } *&42 Vec::::new()
Slide 10
Slide 10 text
Constクイズ • 次のうちnightly compilerでconstに代入できるのはどれ? drop(42) if size_of::() == 4 { "32" } else { "64" } *&42 Vec::::new()
Slide 11
Slide 11 text
CTFE (Compile-Time Function Evaluation) • C++でいうconstexpr • ~2017: librustc_const_eval (HIRインタプリタ) • 2018~: miri (MIRインタプリタ)
Slide 12
Slide 12 text
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)
Slide 13
Slide 13 text
miriとCTFE • miriになったことで参照が使えるようになった • 配列のインデックス(Index trait)ですら参照を使っている • miriには可能でもCTFEでは禁止される操作がある • 分岐とループ • dropの呼び出し • 参照のアドレス取り出し etc. • 非決定的な操作を許可すると未定義動作になりうる • 配列サイズが場所によって異なる値に評価される、など
Slide 14
Slide 14 text
Chalk: トレイトロジックソルバー
Slide 15
Slide 15 text
chalkを使う • https://github.com/rust-lang-nursery/chalk • 手順 1. cargo run (要nightly) 2. REPLが起動する 3. → $ cargo run ?- load libstd.chalk ?- i32: Clone ?- ^C
Slide 16
Slide 16 text
chalkとは • トレイト解決のためのProlog風ソルバー • Prolog = 手続き型 + 単一化 + バックトラック • chalk = Prolog – 副作用 + 量化 + 様相
Slide 17
Slide 17 text
Prologの復習: 項 • 右のように「関数のようなもの」を ネストさせ、項 (term) を作る。 • 単射で互いに交わらないので、関数 というよりもRustのバリアントに近 い。 • 変数を含めることができる(Prolog では大文字して区別する) f(g(a), f(b)) f(a) != f(b) f(a) != g(b) f(X, g(X))
Slide 18
Slide 18 text
Prologの復習: 述語と節 • 述語 (predicate) は項と似ているが、 真偽を表わす • Prologプログラムは節 (clause) の 集まりである。節はパターンマッチ のように動作する。 • ゴール節を書くことで実行開始する p(g(a), f(b)) p(X, f(Y)) :- q(X), q(Y) ?- p(f(a), X)
Slide 19
Slide 19 text
Prologの復習: 単一化とバックトラック • 通常のパターンマッチと異なり、右 辺の変数は重複してよい。これは単 一化 (unification) によって処理さ れる。 • 複数の節にマッチするときは、バッ クトラックにより各選択肢を順番に 試す。 p(a, f(X)) :- true P(g(X), Y) :- p(X, Y) eq(X, X) :- true
Slide 20
Slide 20 text
Prologの復習: 例 • リストを連結する関数→「ZはXとYを連結したリスト」という 述語として表現 append(nil, X, X) :- true append(cons(X, Y), Z, cons(X, W)) :- append(Y, Z, W)
Slide 21
Slide 21 text
Rustのトレイトシステムとの対応 • Rustの型→Prologの項 • Rustのトレイト→Prologの述語 • Rustのtrait impl→Prologの確定節 • Rustのtrait制約→Prologのゴール節 • 多相性→??? • サブタイピング→??? • 射影型→??? • クレート際モード→??? • 否定的な推論→???
Slide 22
Slide 22 text
ChalkをPrologのように使う • Prologと異なり、関数記号はあらかじめ宣言する必要がある struct Nil {} struct Cons {} struct i32 {}
Slide 23
Slide 23 text
ChalkをPrologのように使う • 述語の第一引数 (Self) の位置が特殊 // 第一引数は書かない trait Append {} // 第一引数がforの後にくる impl Append for X {}
Slide 24
Slide 24 text
ChalkをPrologのように使う • 練習: Consの場合に対応させて、以下のクエリを通す $ cargo run ?- load metalist.chalk ?- exists { Cons: Append, X> } Unique; substitution [?0 := Cons>], lifetime constraints []
Slide 25
Slide 25 text
曖昧性 • 解が複数ある場合は失敗する ?- load libstd.chalk ?- exists { T: AsRef } Unique ?- exists { T: AsRef> } Ambiguous
Slide 26
Slide 26 text
多相性 • forall量化とexists量化をネストする ?- load libstd.chalk ?- forall { if(T: Sized) { exists { U: AsRef } } } Unique
Slide 27
Slide 27 text
射影型 • 射影型の正規化には制限がある ?- load iter.chalk ?- exists { as Iterator>::Item = T } Ambiguous ?- exists { Normalize( as Iterator>::Item -> T) } Unique 練習: 上のように動作するiter.chalkを書く
Slide 28
Slide 28 text
否定 • 強い否定を記述できる (impl !Trait for T) ?- exists { (T: Sized, T: AsRef>) } Unique 練習: 上のようになるようにlibstd.chalkを修正せよ
Slide 29
Slide 29 text
クレート際モード • 以下のような例を考える trait Foo {} trait Bar {} struct A {} struct B {} impl Foo for A {} impl Bar for B {}
Slide 30
Slide 30 text
クレート際モード • compat {} という様相を用いると、全ての可能世界での可能性 になる……なってほしい ?- exists { T: Foo } Unique ?- exists { (T: Foo, T: Bar) } No possible solution. ?- compatible { exists { (T: Foo, T: Bar) } } Ambiguous ←現状はNo possible solutionが出力される
Slide 31
Slide 31 text
Chalkまとめ • Chalkはトレイト解決用のProlog風言語 • コンパイラの責務が分割されることで、高速化・バグ修正・新 機能の追加が容易になることが期待されている • 現在Rustコンパイラへの統合が進められつつある • REPLで試すこともできるが、まだ挙動がところどころ怪しい
Slide 32
Slide 32 text
まとめ • Rustコンパイラ内にはインタプリタが棲んでいる • miri: constのコンパイル時計算のため • chalk: トレイト解決のため • 宣言マクロインタプリタ: マクロのため • つまり、メタプログラミングをすることができる。