Mercurial > hg > Papers > 2023 > moririn-sigos
view presen.ind @ 7:cedf88ea18b9
...
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Sat, 13 May 2023 16:14:11 +0900 |
parents | 9e85fa1cc6a8 |
children | 2514493ae067 |
line wrap: on
line source
-title: GearsAgdaによる Red Black Tree の検証 -author: 森逸汰 河野真治 --証明を用いたOSのアプリケーションの信頼性の保証 信頼性は全体的な問題 テストでは限界がある 記述単位を工夫して、証明しやすくする Gears OS と GearsAgda を使う --とりあえずの例題 簡単な while program Binary Tree Red Black Tree Red Black Tree があると、DBやファイルシステムを記述できる --Gears OS Continuation based C を使ったOS codeGear inputからoutputを得る有限の計算単位 dataGear input/outputに使うデータ単位 metaGear メタ計算(計算の実装、メモリ、CPU、IO)などの記述 context プロセスに相当する codeGear を実行する環境 CbC Continuation based C 以上を実装する言語 --GearsAgda Agdaによる codeGear/dataGearの実装記述 Agda Pure Functional Language Curry Howard Isomorphism dependent types ZF Set theory も使える (Fairness とか、実数とか) --Agdaによる証明の紹介 Agda は単なる pure functional language ほぼ Haskell 高階直観論理 関数を値として使える dependent type 型を値として使える --Agdaによる証明 証明との関係 Curry Howard Isomorphism 命題は型 id : A → A 推論はλ項 id = λ x → x 基本的に → ∧ ∨ ≡ があれば全部命題書ける → 関数型 ∧ record 型 ∨ data 型 ≡ 等号 -- → 関数型 A → B は、型Aを入力として、型Bを返す関数の型 この型の関数があれば、 A → B の証明になる 証明を表す項はプログラムとしては意味がないことが多い 入力は実際の値、あるいはデータ構造ではなく、型 なので計算的には意味がない -- ∧ record 型 Cの構造体に相当する。値なのでアドレスに相当する概念はない record _∧_ {n m : Level} (A : Set n) ( B : Set m ) : Set (n ⊔ m) where constructor ⟪_,_⟫ field proj1 : A proj2 : B オブジェクトと考えても良い A には複雑な命題を書いても良い。すると数学的概念になる。定理とか公理系。群とか。 -- ∨ data 型 data _∨_ {n m : Level} (A : Set n) ( B : Set m ) : Set (n ⊔ m) where case1 : A → A ∨ B case2 : B → A ∨ B 場合分けを表す。 data List (A : Set ) : Set where [] : List A _::_ : A → List A → List A List は場合分けを含むので data になる。 Haskell では、record / data はごっちゃになっている(recordはない) -- ≡ 等号 dataの特別な場合 data _≡_ {A : Set } : A → A → Set where refl : {x : A} → x ≡ x x ≡ y は二つの項が正規化して等しいことを表す 変数が含まれている場合は単一化という操作を行う この単一化は極めて複雑になる場合がある 証明のチェックに時間がかかったり、数ギガのメモリを食ったりする -- data はいろいろ使われる 自然数 順序 < 制約のあるデータ構造 普通にデータ構造として使える data bt {n : Level} (A : Set n) : Set n where leaf : bt A node : (key : ℕ) → (value : A) → (left : bt A ) → (right : bt A ) → bt A --codeGear description in Agda codeGear は関数呼び出しとは違うので普通のλ項にならない find : {n m : Level} {A : Set n} {t : Set m} → (key : ℕ) → (tree tree0 : bt A ) → (stack : List (bt A)) → (next : (tree1 : bt A) → (stack : List (bt A)) → t ) → (exit : (tree1 : bt A) → (stack : List (bt A)) → t ) → t 不定の t を返すことにより、継続を呼び出して「抜ける」つまり tail call する dataGear は Agda のrecord で良い --Hoare Logicと invariant プログラムの証明に良く使われるのは Hoare logic Pre-condition { Command } Post-condition Loop では Pre-condition と Post-conditionが一致するので invariant と呼ぶ 欠点 Command が限定される (アセンブラじゃあるいまいし Sound and complete が Command 依存 証明方法が限定的 --GearsAgda での記述 Command ではなく codeGear をそのまま使える 証明は metaCodeGear / metaDataGear になる invariant は、主に data 型で記述される 証明は meta 部分で停止性を含め自由に記述できる Sound and complete は限定されたcommandではなく個々のプログラム --GearsAgdaの例 findP : {n m : Level} {A : Set n} {t : Set m} → (key : ℕ) → (tree tree0 : bt A ) → (stack : List (bt A)) → treeInvariant tree ∧ stackInvariant key tree tree0 stack -- Pre condition → (next : (tree1 : bt A) → (stack : List (bt A)) → treeInvariant tree1 ∧ stackInvariant key tree1 tree0 stack -- Post condition → bt-depth tree1 < bt-depth tree → t ) -- Halt condition → (exit : (tree1 : bt A) → (stack : List (bt A)) → treeInvariant tree1 ∧ stackInvariant key tree1 tree0 stack -- Post condition → (tree1 ≡ leaf ) ∨ ( node-key tree1 ≡ just key ) → t ) -- Post condition of exit → t codeGear 中に自然に condition を記述できる 証明は、findP の実装と同時に記述する ? でさぼっても良い (証明をさぼっても、プログラムは動作する --Haskell でやれば? Haskell の方が証明との相性は良い Haskell は遅延評価 実行タイミングが予測できない メモリ使用量がGCを含め予測不可能 実行コードがシステム記述向けでない 並行実行は記述と関係なく行われてしまう 非決定性は Monad codeGear なら codeGear内の実行は有限時間かつ有限メモリ codeGearの実装は Continuation based C (LLVM / GCC) 並列実行単位は codeGear 非決定性は Monad だが、定義された meta levelのスケジューラで決まる --Invariant data treeInvariant {n : Level} {A : Set n} : (tree : bt A) → Set n where t-leaf : treeInvariant leaf t-single : (key : ℕ) → (value : A) → treeInvariant (node key value leaf leaf) t-right : {key key₁ : ℕ} → {value value₁ : A} → {t₁ t₂ : bt A} → key < key₁ → treeInvariant (node key₁ value₁ t₁ t₂) → treeInvariant (node key value leaf (node key₁ value₁ t₁ t₂)) t-left : {key key₁ : ℕ} → {value value₁ : A} → {t₁ t₂ : bt A} → key < key₁ → treeInvariant (node key value t₁ t₂) → treeInvariant (node key₁ value₁ (node key value t₁ t₂) leaf ) t-node : {key key₁ key₂ : ℕ} → {value value₁ value₂ : A} → {t₁ t₂ t₃ t₄ : bt A} → key < key₁ → key₁ < key₂ → treeInvariant (node key value t₁ t₂) → treeInvariant (node key₂ value₂ t₃ t₄) → treeInvariant (node key₁ value₁ (node key value t₁ t₂) (node key₂ value₂ t₃ t₄)) 割と当たり前。Invariant だが 可能な順序のある二分木全部の集合 に相当する。つまり、Invariant というよりは表示的意味論になってる。 --停止条件 証明は(項として)有限である必要があるので、停止条件が必要になる TerminatingLoopS : {l m : Level} {t : Set l} (Index : Set m ) → {Invraiant : Index → Set m } → ( reduce : Index → ℕ) → (r : Index) → (p : Invraiant r) → (loop : (r : Index) → Invraiant r → (next : (r1 : Index) → Invraiant r1 → reduce r1 < reduce r → t ) → t) → t これは Indexが自然数で減少するならループが停止する命題である。これは Agda で証明されている (それほどながくない) これは、codeGear の loop connector になっている リンカは単純にリンクするのではなく、停止条件も接続する必要がある 呼び出し方の例 insertTreeP {n} {m} {A} {t} tree key value P0 exit = TerminatingLoopS (bt A ∧ List (bt A) ) {λ p → treeInvariant (proj1 p) ∧ stackInvariant key (proj1 p) tree (proj2 p) } (λ p → bt-depth (proj1 p)) ⟪ tree , tree ∷ [] ⟫ ⟪ P0 , s-nil ⟫ $ λ p P loop → findP key (proj1 p) tree (proj2 p) P (λ t s P1 lt → loop ⟪ t , s ⟫ P1 lt ) $ λ t s P C → replaceNodeP key value t C (proj1 P) ... loop で接続されているのがわかる。⟪ P0 , s-nil ⟫などで実際に証明を渡している。 --何を証明しているのか? insertTreeP なら、exit で抜けた時に、treeinvariant と、元の木の値が変わっているinvariantが得られる ここから必要な性質は全部証明できる --Red Black Tree の invariant (leaf / single ) data RBtreeInvariant {n : Level} {A : Set n} : (tree : bt (Color ∧ A)) → (bd : ℕ) → Set n where rb-leaf : RBtreeInvariant leaf 0 rb-single : (key : ℕ) → (value : A) → RBtreeInvariant (node key ⟪ Black , value ⟫ leaf leaf) 1 --Red Black Tree の invariant (leaf case ) rb-right-red : {key key₁ : ℕ} → {value value₁ : A} → {t t₁ : bt (Color ∧ A)} → RBtreeInvariant (node key₁ ⟪ Black , value₁ ⟫ t t₁) 1 → RBtreeInvariant (node key ⟪ Red , value ⟫ leaf (node key₁ ⟪ Black , value₁ ⟫ t t₁)) 1 rb-right-black : {key key₁ : ℕ} → {value value₁ : A} → {t t₁ : bt (Color ∧ A)} → {c : Color} → RBtreeInvariant (node key₁ ⟪ c , value₁ ⟫ t t₁) 1 → RBtreeInvariant (node key ⟪ Black , value ⟫ leaf (node key₁ ⟪ c , value₁ ⟫ t t₁)) 1 rb-left-red : {key key₁ : ℕ} → {value value₁ : A} → {t t₁ : bt (Color ∧ A)} → RBtreeInvariant (node key₁ ⟪ Black , value₁ ⟫ t t₁) 1 → RBtreeInvariant (node key₁ ⟪ Red , value ⟫ (node key ⟪ Black , value₁ ⟫ t t₁) leaf ) 1 rb-left-black : {key key₁ : ℕ} → {value value₁ : A} → {t t₁ : bt (Color ∧ A)} → {c : Color} → RBtreeInvariant (node key₁ ⟪ c , value₁ ⟫ t t₁) 1 → RBtreeInvariant (node key₁ ⟪ Black , value ⟫ (node key ⟪ c , value₁ ⟫ t t₁) leaf) 1 --Red Black Tree の invariant (intermidiate node) rb-node-red : {key key₁ key₂ : ℕ} → {value value₁ value₂ : A} → {t₁ t₂ t₃ t₄ : bt (Color ∧ A)} → (d : ℕ) → RBtreeInvariant (node key ⟪ Black , value ⟫ t₁ t₂) d → RBtreeInvariant (node key₂ ⟪ Black , value₂ ⟫ t₃ t₄) d → RBtreeInvariant (node key₁ ⟪ Red , value₁ ⟫ (node key ⟪ Black , value ⟫ t₁ t₂) (node key₂ ⟪ Black , value₂ ⟫ t₃ t₄)) d rb-node-black : {key key₁ key₂ : ℕ} → {value value₁ value₂ : A} → {t₁ t₂ t₃ t₄ : bt (Color ∧ A)} → (d : ℕ) → {c c₁ : Color} → RBtreeInvariant (node key ⟪ c , value ⟫ t₁ t₂) d → RBtreeInvariant (node key₂ ⟪ c₁ , value₂ ⟫ t₃ t₄) d → RBtreeInvariant (node key₁ ⟪ Black , value₁ ⟫ (node key ⟪ c , value ⟫ t₁ t₂) (node key₂ ⟪ c₁ , value₂ ⟫ t₃ t₄)) (suc d) そこそこ複雑だが、treeInvariant と直交して書ける --invariant red black tree は rotate を含むので結構複雑 record RBI {n : Level} {A : Set n} (key : ℕ) (value : A) (orig repl : bt (Color ∧ A) ) (stack : List (bt (Color ∧ A))) : Set n where field od d rd : ℕ tree rot : bt (Color ∧ A) origti : treeInvariant orig origrb : RBtreeInvariant orig od treerb : RBtreeInvariant tree d replrb : RBtreeInvariant repl rd d=rd : ( d ≡ rd ) ∨ ( (suc d ≡ rd ) ∧ (color tree ≡ Red)) si : stackInvariant key tree orig stack rotated : rotatedTree tree rot ri : replacedRBTree key value (child-replaced key rot ) repl --concurrency (Dining Philosopher) 普通に GearsAgdaでプロセスを記述する pickup_rfork : {n : Level} {t : Set n} → Phil → ( Phil → t ) → t pickup_lfork : {n : Level} {t : Set n} → Phil → ( Phil → t ) → t eating : {n : Level} {t : Set n} → Phil → ( Phil → t ) → t pickup_rfork p next = f_set (Phil.right p) (Phil.ptr p) ( λ f → pickup_lfork record p { right = f } next ) pickup_lfork p next = f_set (Phil.left p) (Phil.ptr p) ( λ f → eating record p { left = f } next ) eating p next = putdown_rfork p next --meta level での記述 Phil_putdown_rfork_sub : {n : Level} {t : Set n} → Context → ( Code → Context → t ) → t Phil_putdown_rfork_sub c next = next C_cas_int record c { c_AtomicNat-API = record { impl = Phil.right phil ; value = 0 ; fail = C_putdown_lfork ; next = C_putdown_lfork } } where phil : Phil phil = Phil-API.impl ( Context.c_Phil-API c ) メタレベルからは、Code と Context しか見えない Phil_putdown_rfork_sub : {n : Level} {t : Set n} → Context → ( Code → Context → t ) → t 実際の接続は stub で行われる --meta level でのscheduler schedulerは Code と Context しか見ない 共有は scheduler level のmetaGearで処理される -- Concurrency で証明したいこと redBlackTree の transaction redBlackTree の 大きさ制限 (メモリ管理) redBlackTree の格納領域と、backup / commit --証明は実用的なの? Toy OS level での証明は可能になっている GearsAgda では、まだ、いろいろできてない invariant が決まれば、証明は割と機械的 (AI support?)