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: トレイト解決のため • 宣言マクロインタプリタ: マクロのため • つまり、メタプログラミングをすることができる。