• 状態数の削減 (可算無限→有限) が可能になった 今井 敬吾(株式会社ディー・エヌ・エー) モナディックなリアクティブシステムの仕様記述における Reificationの活用に向けて • 最初の抽象化: t’ ≡ Loc × St × {*} × {*} (にせの) Counterexample が発見される: π err = 〈(L 1 ,Closed,*,*), (L 2 ,Open,*,*), (L 3 ,Open,*,*), (L 4 ,Closed,*,*), (L 5 ,Closed,*,*), (LErr,Closed,*,*)〉 • リアクティブシステムの検証のための OCaml DSL を提案した ◦ Reification:OCaml の Monadic なプログラムの構造を抽出できる ▪ 従来の HOAS に加えパターンマッチを扱えるようになった ▪ CEGAR による状態数削減が可能に • OCaml PPX によるツール支援 あり • 今後の課題 ◦ CEGARの実装 ◦ 抽象実行関係の正しさの定式化と証明 ◦ プロセス代数 CSP の意味論に基づく並行実行+通信モデル • より実践的な例での性能評価 まとめ 8 [Model Checking (2nd Ed.), MIT Press, 2018, Ch. 14] [true] [st=Open] cnt:=cnt+1; st:=Closed L1 L2 L3 L4 L5 LErr L6 [st=Closed] st:=Open [st=Open] old_cnt:=cnt [st=Closed] [st=Closed] [old_cnt≠cnt] [old_cnt=cnt] [st=Open] st:=Closed Open 二重Open 二重Close Close 二重Close エラー Close [st=Closed] st:=Open [st=Open] [st=Open] cnt:=*; st:=Closed L1 L2 L3 L4 L5 LErr L6 [st=Open] old_cnt:=cnt [st=Closed] [st=Closed] [*] [*] [st=Open] st:=Closed [st=Open] [st=Open] b1:=false; st:=Closed L1 L2 L3 L4 L5 LErr L6 [st=Closed] st:=Open [st=Open] b1:=true; [st=Closed] [st=Closed] [¬b1] [b1] [st=Open] st:=Closed 仕様記述言語としての関数型言語とモデル検査 • OCamlで並行システムの動作を記述・検証する事例: 時間を加味したモデリング - DeNA Testing Blog (2022) https://swet.dena.com/entry/2022/04/04/135152 • 問題:状態数爆発 ◦ CEGARなどの状態数低減テクニックを使いたい! • 関数型言語利用の欠点:抽象化しづらい,解析しづらい (CEGARに向かない) 1 OCamlによる仕様記述の既存の枠組み (簡易版) • システム S = (t, init, trans) をユーザ定義の型と関数で記述する module System : sig type t (* システムの状態を表す型 *) val init : t (* 初期状態 *) val trans : t -> t (* システムの遷移 *) val is_error : t -> bool (* エラー状態かどうか *) end • システム実行は無限列 (i 回遷移した状態を s i とする) Run(S) ≡ 〈 transi(init) 〉 i∊ℕ =〈init, trans(init), trans (trans(init)), trans3(init), trans4(init), …〉(= 〈s i 〉 i∊ℕ ) • 安全性検査の例:システムがエラー状態に到達しない Safe(S) = ∀i ∊ ℕ. ¬ is_error(s i ) 2 (Parametric) HOAS: Higher Order Abstract Syntax [Chlipala, ICFP’08など] ⟦HVar v ⟧ = Var v ⟦HApp ( f, a )⟧ = App (⟦ f ⟧, ⟦ a ⟧) ⟦HLam f ⟧ = Lam (nv, ⟦ f nv ⟧) where nv fresh ⟦HBool b ⟧ = Bool b type 't var type _ hoast = | HVar : 'x var -> 'x hoast | HApp : ('x -> 'y) hoast * 'x hoast -> 'y hoast | HLam : ('x var -> 'y hoast) -> ('x -> 'y) hoast | HBool : bool -> bool hoast type 't var = string type ast = | Var : 't var -> ast | App : ast * ast -> ast | Lam : ('t var * ast) -> ast | Bool : bool -> ast Lam ("x0", Lam ("x1", App (Var "x0", Var "x1"))) HLam (fun x -> HLam (fun y -> HApp (HVar x, HVar y))) ↦ 変換 ⟦·⟧ : Hoast → Ast • HOAS→ASTの具体例:(λx. λy. x y) の表現 • 例:λ項のHOAS表現と1階のASTへの変換 👀(フレッシュな)変数を生成して渡す fresh fresh λ抽象で変数束縛を表現 λ抽象で変数束縛を表現 A let is_leaf x = match_ x @@ function | Leaf -> return_bool true | Node (_,_) -> return_bool false # reify_fun1 ~arg:Typ_tree is_leaf;; - : (tree -> bool) reify_fun = """fun (x0 : tree) -> match x0 with | Leaf -> true | Node (x1, x2) -> false""" Reify.DSL: ReifiableなプログラミングのためのOCamlライブラリ Reify.DSL によるプログラミングの例 パターンマッチが 使える 関数の計算構造(プログラム)を実行時に抽出 (Reify) できる type tree = Leaf | Node of tree * tree let is_leaf x = match x with | Leaf -> true | Node _ -> false (元のプログラム) 関数のreification パターンマッチ(変数束縛)を 抽出できた! type tree = Leaf | Node of tree expr * tree expr [@@deriving reify] OCamlの変数束縛 Fun ("x0", Typ_tree, MatchE (VarE ("x0", Typ_tree), [ Branch (Leaf, Typ_tree, ConstE (true, Typ_bool)) ; Branch (Node (VarE ("x1", Typ_tree), VarE ("x2", Typ_tree)), Typ_tree, ConstE (false, Typ_bool))])) 抽出できたAST (抽象構文木) 3 • 状態の型 t ≡ Loc × St × ℕ × ℕ (Loc=状態機械の位置(右図), St = {Closed, Open}) • 二重にソケットをオープン/クローズするとエラー type st = Closed | Open [@@deriving reify] type loc = L1 | L2 | L3 | L4 | L5 | L6 | LErr [@@deriving reify] type t = { loc : loc expr; st : st expr; old_cnt : int expr; cnt : int expr} [@@deriving reify] let init = { loc = L1; st = Closed; old_cnt = 0; cnt = 0 } let trans t = match_ t.loc @@ function | L1 -> open_conn { t with loc = L2 } | L2 -> return_t { t with loc = L3; old_cnt = t.cnt } | L3 -> nondet [ close_conn { t with loc = L4; cnt = t.cnt + 1 }; { t with loc = L4 } ] | L4 -> ifeq t.old_cnt t.cnt ~then:(return_t { t with loc = L1 }) ~else:(return_t { t with loc = L5 }) | L5 -> close_conn { t with loc = L6 } | L6 -> return_t t | LErr -> return_t t let is_error t = match t.loc with LErr -> true | _ -> false 注: open_conn: Close ↦ Open, close_conn : Open ↦ Close (さもなくばエラー位置 LErr に遷移) Reify.DSL と CEGAR によるモデル検査の例:ストリーミング 7 可算無限個の状態 • 詳細化 (Refinement) t’’ ≡ Loc × St × {*} × {*}× {true, false} (loc, st, *, *, b 1 ) ∈ t’’ where b 1 = true iff 元のモデルで cnt = old_cnt 有限状態 match e with { (pat i : τ i ) → e i } i∊ I , ū ⇓ e j θ, ū if v = eval(e) /\ ∃ j. θ = match(v, pat j ) (≠ ⊥) v : τ, ū ⇓ v : τ, ū x : τ, ū ⇓ x : τ, ū nondet [e 1 ; ..; e m ], ū ⇓ v , ū R if ∃ j ∊ {1, …, m}. e j , ū ⇓v, ū R if e 1 =e 2 then e 3 else e 4 , ū ⇓ v, ū R if ∃ū 0 .[SAT(ū; e 1 =e 2 )=ū 0 /\ e 3 ,ū 0 ⇓v, ū R ] \/ [SAT(ū; e 1 ≠e 2 )=ū 0 /\ e 4 ,ū 0 ⇓v, ū R ] op (e 1 , … ,e n ), ū ⇓ op ( v 1 , … ,v n ), ū R if ∃ ū 1 , ... ,ū n . e i ,ū ⇓ v i , ū i /\ SAT(/\ i∊{1,...,n} ū i )= ū R Reify.DSL: CEGARのための抽象実行関係 ⇓ ⊆ 𝓛 R ×Booln × 𝓛 R ×Booln 6 e ::= (v : τ) | match e with { alt 1 ; … ; alt n } | nondet [e 1 ; … ; e m ] | if e 1 =e 2 then e 3 else e 4 | op (e 1 , … ,e n ) | (x : τ) alt ::= (pat : τ) → e v ::= () | true | false | Some(e) | None | c(e 1 , ... ,e n ) pat ::= () | true | false | Some(pat) | None | c(pat 1 , ... ,pat n ) | x c ::= ... Reify.DSL:式言語 𝓛 R パタンマッチ ⟦match_ a f ⟧ = match ⟦ a ⟧ with { (pat : typ) → ⟦ f pat ⟧ | pat ∊ enumerate ( typ ), fresh FV(pat) } where typ = typeof ( a ) ⟦return_bool v ⟧ = (v : bool) ⟦ifeq e 1 e 1 ~then:e 3 ~else:e 4 ⟧ = if e 1 =e 2 then e 3 else e 4 … a が取りうる全てのパターンを (PPXで) 生成 𝓛 R のAST 変換規則 ⟦·⟧:OCaml → 𝓛 R コンストラクタは追加可能 if式 ユーザ定義型 (のコンストラクタ) 👀 (フレッシュな) パターンを渡す MatchE (VarE "x0", [ (Leaf, ConstE true) ; (Node (VarE "x1", VarE "x2")), ConstE false)])) match_ x @@ function | Leaf -> return_bool true | Node _ -> return_bool false 4 ↦ → 😳 どうなってるのか? 注:モナド則は評価関数を経由して成立する:eval [match_ e return] = eval [e] /\ eval [match_ (return x) f ] = eval [ f (x) ] ↕モナドのbindと(制限付き) return [@@deriving reify]: ReificationのためのOCamlプリプロセッサ拡張 (PPX) 5 module System : sig type t [@@deriving reify] val init : t val trans : t -> t expr val is_error : t -> bool end Reification のための 型宣言注釈 type st = Closed | Open [@@deriving reify] プリプロセッサ (PPX) が関数 (とコンストラクタ) を生成: type 'a typ += Typ_st : st typ val return_st : st -> st expr val enumerate_st : unit -> st list val subst_st : st -> subst -> st val gen_match_st : pat:st -> val_:st -> subst list st型の値を全て列挙 st型を言語(EDSL)に導入 代入(インタプリタ実行に必要 ) パターンpatとval_から代入を生成 (インタプリタ実行に必要 ) → 状態数爆発や無限状態の扱いなどが課題 (return_st b = return Typ_st b) OCaml version 5.3.0 Enter #help;; for help. # 1;; - : int = 1 # Leaf;; - : tree = Leaf # is_leaf;; - : tree -> bool = <fun> type tree = Leaf | Node of tree * tree let is_leaf x = match x with | Leaf -> true | Node _ -> false <fun>😤 ホスト言語(OCaml)の「計算の構造」は実行時に解析できない (関数,パターンマッチ, レコード射影, …) ブラックボックス! この関数の振舞いは? • x == VarE "x0" と置いた • enumerate Typ_tree == [Leaf; Node (VarE "x1", VarE "x2")] reify向けに修正 (ラッパー型) 型表現に [@@deriving reify] (下記) type 'a expr = | ConstE : 'a * 'a typ -> 'a expr | MatchE : 'x expr * ('x * 'x typ * 'a expr) list -> 'a expr | NonDetE : 'a expr list -> 'a expr | IfE : 'a expr * 'a expr * 't expr * 't expr -> 't expr | VarE : string * 't typ -> 't expr 式言語の実装 外部ソルバの呼び出し ℕを1点集合に潰した エラーに到達しうるが、 元のモデルでは実現でき ないパス レコード型も使えます • 確かめたいこと: 「エラー状態に到達しない」 • 状態遷移図: パタンマッチ Safe! 述語抽象化 cf. HOAS to AST (上述) 実行時にプログラム自身の構造を解析できること モナドによるプログラミング cf: Svenningsson & Svensson, Simple and compositional reification of monadic embedded languages, ICFP 2013. CEGAR = Counterexample-Guided Abstraction Refinement モデルを過大近似し、反例を元に段階的に詳細化してモデル検査する手法