open Ast open Ast_util open Formula open Typing open Cmdline open Util open Num_domain open Enum_domain module I (ED : ENUM_ENVIRONMENT_DOMAIN) (ND : NUMERICAL_ENVIRONMENT_DOMAIN) : sig type st val do_prog : cmdline_opt -> rooted_prog -> unit end = struct (* ************************** Disjunction domain ************************** *) type case_v = ED.t * ND.t type case = ED.item list type dd_v = { d_vars : id list; data : (case, case_v) Hashtbl.t; } (* print_dd : Formatter.t -> dd_v -> unit *) let print_aux fmt d_vars env = let print_it q (enum, num) = Format.fprintf fmt "@["; List.iter2 (fun id v -> Format.fprintf fmt "%a ≡ %s,@ " Formula_printer.print_id id v) d_vars q; Format.fprintf fmt "@ %a,@ %a@]@." ED.print enum ND.print num; in Hashtbl.iter print_it env let print_dd fmt dd = print_aux fmt dd.d_vars dd.data (* dd_bot : id list -> dd_v *) let dd_bot d_vars = { d_vars; data = Hashtbl.create 12 } (* dd_add_case : dd_v -> case_v -> unit *) let dd_add_case env (enum, num) = let rec aux enum vals = function | [] -> let case = List.rev vals in begin try let e0, n0 = Hashtbl.find env.data case in Hashtbl.replace env.data case (ED.join enum e0, ND.join num n0); with Not_found -> Hashtbl.add env.data case (enum, num); end | p::q -> List.iter (fun v -> match ED.apply_cons enum (E_EQ, p, EItem v) with | [en] -> aux en (v::vals) q | _ -> assert false) (ED.project enum p) in aux enum [] env.d_vars (* dd_singleton : id list -> case_v -> dd_v *) let dd_singleton d_vars d = let v = { d_vars; data = Hashtbl.create 1 } in dd_add_case v d; v (* dd_extract_case : dd_v -> case -> dd_v *) let dd_extract_case v case = try dd_singleton v.d_vars (Hashtbl.find v.data case) with Not_found -> dd_bot v.d_vars (* dd_apply_ncons : dd_v -> num_cons list -> dd_v *) let dd_apply_ncl v ncl = let vv = dd_bot v.d_vars in Hashtbl.iter (fun case (enum, num) -> let num = ND.apply_cl num ncl in if not (ND.is_bot num) then Hashtbl.add vv.data case (enum, num)) v.data; vv (* dd_apply_econs : dd_v -> enum_cons -> dd_v *) let dd_apply_econs v cons = let vv = dd_bot v.d_vars in Hashtbl.iter (fun case (enum, num) -> List.iter (fun enum -> dd_add_case vv (enum, num)) (ED.apply_cons enum cons)) v.data; vv (* dd_join : dd_v -> dd_v -> dd_v *) let dd_join a b = let vv = { d_vars = a.d_vars; data = Hashtbl.copy a.data } in Hashtbl.iter (fun case (enum, num) -> try let e2, n2 = Hashtbl.find vv.data case in Hashtbl.replace vv.data case (ED.join e2 enum, ND.join n2 num) with Not_found -> Hashtbl.replace vv.data case (enum, num)) b.data; vv (* dd_meet : dd_v -> dd_v -> dd_v *) let dd_meet a b = let vv = dd_bot a.d_vars in Hashtbl.iter (fun case (enum, num) -> try let e2, n2 = Hashtbl.find b.data case in let en, nn = ED.meet enum e2, ND.meet n2 num in if not (ND.is_bot nn) then Hashtbl.add vv.data case (en, nn) with _ -> ()) a.data; vv (* dd_apply_cl : dd_v -> conslist -> dd_v *) let rec apply_cl env (ec, nc, r) = match r with | CLTrue -> let env_ec = List.fold_left dd_apply_econs env ec in let env_nc = dd_apply_ncl env_ec nc in (* Format.printf "[[ %a -> %a ]]@." print_dd env print_dd env_nc; *) env_nc | CLFalse -> dd_bot env.d_vars | CLAnd(a, b) -> let env = apply_cl env (ec, nc, a) in apply_cl env (ec, nc, b) | CLOr((eca, nca, ra), (ecb, ncb, rb)) -> dd_join (apply_cl env (ec@eca, nc@nca, ra)) (apply_cl env (ec@ecb, nc@ncb, rb)) (* *************************** Abstract interpretation *************************** *) type st = { rp : rooted_prog; opt : cmdline_opt; all_vars : (bool * id * typ) list; enum_vars : (id * ED.item list) list; num_vars : (id * bool) list; (* program expressions *) cl : conslist; guarantees : (id * bool_expr) list; (* abstract interpretation *) cycle : (id * id * typ) list; (* s'(x) = s(y) *) forget : (id * typ) list; (* s'(x) not specified *) d_vars : id list; (* disjunction variables when calculating *) acc_d_vars : id list; (* disjunction variables in accumulator *) top : case_v; env : (case, case_v) Hashtbl.t; mutable delta : case list; widen : (case, int) Hashtbl.t; } (* print_st : Formatter.t -> st -> unit *) let print_st fmt st = print_aux fmt st.acc_d_vars st.env (* add_case : st -> case_v -> unit *) let add_case st (enum, num) = let rec aux enum vals = function | [] -> let case = List.rev vals in begin try let e0, n0 = Hashtbl.find st.env case in if (not (ED.subset enum e0)) || (not (ND.subset num n0)) then begin begin try let n = Hashtbl.find st.widen case in let jw = if n < st.opt.widen_delay then ND.join else ND.widen in Hashtbl.replace st.env case (ED.join e0 enum, jw n0 num); Hashtbl.replace st.widen case (n+1); with Not_found -> Hashtbl.replace st.env case (ED.join enum e0, ND.join num n0); Hashtbl.add st.widen case 0; end; if not (List.mem case st.delta) then st.delta <- case::st.delta end with Not_found -> Hashtbl.add st.env case (enum, num); if not (List.mem case st.delta) then st.delta <- case::st.delta end | p::q -> List.iter (fun v -> match ED.apply_cons enum (E_EQ, p, EItem v) with | [en] -> aux en (v::vals) q | _ -> assert false) (ED.project enum p) in aux enum [] st.acc_d_vars (* dd_apply_cl : dd_v -> conslist -> dd_v *) let rec apply_cl_get_all_cases (enum, num) (ec, nc, r) = match r with | CLTrue -> let enums = List.fold_left (fun enums ec -> List.flatten (List.map (fun e -> ED.apply_cons e ec) enums)) [enum] ec in let num = ND.apply_cl num nc in List.map (fun ec -> ec, num) enums | CLFalse -> [] | CLAnd(a, b) -> let envs = apply_cl_get_all_cases (enum, num) (ec, nc, a) in List.flatten (List.map (fun e -> apply_cl_get_all_cases e (ec, nc, b)) envs) | CLOr((eca, nca, ra), (ecb, ncb, rb)) -> apply_cl_get_all_cases (enum, num) (ec@eca, nc@nca, ra) @ apply_cl_get_all_cases (enum, num) (ec@ecb, nc@ncb, rb) (* pass_cycle : st -> case_v -> case_v *) let pass_cycle st (enum, num) = let assign_e, assign_n = List.fold_left (fun (ae, an) (a, b, t) -> match t with | TEnum _ -> (a, b)::ae, an | TInt | TReal -> ae, (a, NIdent b)::an) ([], []) st.cycle in let enum = ED.assign enum assign_e in let num = ND.assign num assign_n in List.fold_left (fun (enum, num) (var, t) -> let e, n = match t with | TEnum _ -> ED.forgetvar enum var, num | TReal | TInt -> enum, ND.forgetvar num var in e, n) (enum, num) st.forget let dd_pass_cycle st (v : dd_v) = let vv = dd_bot st.acc_d_vars in Hashtbl.iter (fun _ x -> dd_add_case vv (pass_cycle st x)) v.data; vv (* init_state : int -> rooted_prog -> st *) let init_state opt rp = let init_f, f = Transform.f_of_prog rp false in Format.printf "Init formula: %a@.@." Formula_printer.print_expr init_f; Format.printf "Cycle formula:@.%a@.@." Formula_printer.print_expr f; let init_cl = conslist_of_f init_f in let cl = Formula.conslist_of_f f in Format.printf "Cycle conslist:@.%a@.@." Formula_printer.print_conslist cl; let guarantees = Transform.guarantees_of_prog rp in Format.printf "Guarantees:@."; List.iter (fun (id, f) -> Format.printf " %s: %a@." id Formula_printer.print_expr f) guarantees; Format.printf "@."; (* add variables from LASTs *) let last_vars = uniq_sorted (List.sort compare (Transform.extract_last_vars f)) in let last_vars = List.map (fun id -> let (_, _, ty) = List.find (fun (_, u, _) -> id = "L"^u) rp.all_vars in false, id, ty) last_vars in let all_vars = last_vars @ rp.all_vars in Format.printf "Vars: @[%a@]@.@." (print_list Ast_printer.print_typed_var ", ") all_vars; let num_vars, enum_vars = List.fold_left (fun (nv, ev) (_, id, t) -> match t with | TEnum ch -> nv, (id, ch)::ev | TInt -> (id, false)::nv, ev | TReal -> (id, true)::nv, ev) ([], []) all_vars in (* calculate cycle variables and forget variables *) let cycle = List.fold_left (fun q (_, id, ty) -> if id.[0] = 'L' then (id, String.sub id 1 (String.length id - 1), ty)::q else q) [] all_vars in let forget = List.map (fun (_, id, ty) -> (id, ty)) rp.all_vars in (* calculate disjunction variables *) (* actually, don't calculate them, rely on user input *) let d_vars = List.filter (fun k -> k <> "/init" && opt.disjunct k) (List.map (fun (id, _) -> id) enum_vars) in let acc_d_vars = List.filter (fun i -> i.[0] = 'L') d_vars in (* setup abstract data *) let top = ED.top enum_vars, ND.top num_vars in let delta = [] in let widen = Hashtbl.create 12 in let st = { rp; opt; enum_vars; num_vars; all_vars; cl; guarantees; cycle; forget; acc_d_vars; d_vars; top; env = Hashtbl.create 12; delta; widen } in (* calculate initial state and environment *) List.iter (fun ed -> add_case st (pass_cycle st ed)) (apply_cl_get_all_cases top init_cl); Hashtbl.iter (fun case _ -> st.delta <- case::st.delta) st.env; st (* cycle : st -> cl -> dd_v -> dd_v *) let cycle st cl env = let env_f = apply_cl env cl in if st.opt.vverbose_ci then Format.printf "{{ %a ->@.%a }}@." print_dd env print_dd env_f; dd_pass_cycle st env_f (* set_target_case : st -> dd_v -> case -> dd_v *) let set_target_case st env case = List.fold_left2 (fun env id v -> if List.exists (fun (id2, _) -> id2 = "L"^id) st.enum_vars then dd_apply_econs env (E_EQ, id, EItem v) else env) env st.acc_d_vars case (* do_prog : rooted_prog -> unit *) let do_prog opt rp = let st = init_state opt rp in Format.printf "Init: %a@." print_st st; let n_ch_iter = ref 0 in (* chaotic iteration loop *) while st.delta <> [] do let case = List.hd st.delta in incr n_ch_iter; Format.printf "Chaotic iteration %d: @[[%a]@]@." !n_ch_iter (print_list Formula_printer.print_id ", ") case; let start = try Hashtbl.find st.env case with Not_found -> assert false in let start_dd = dd_singleton st.acc_d_vars start in let f i = let i = dd_singleton st.d_vars i in let i' = set_target_case st i case in let q = dd_join start_dd (cycle st st.cl i') in try Hashtbl.find q.data case with Not_found -> assert false in let rec iter n (i_enum, i_num) = let fi_enum, fi_num = f (i_enum, i_num) in let j_enum, j_num = if n < st.opt.widen_delay then ED.join i_enum fi_enum, ND.join i_num fi_num else ED.join i_enum fi_enum, ND.widen i_num fi_num in if ED.eq j_enum i_enum && ND.eq j_num i_num then (i_enum, i_num) else iter (n+1) (j_enum, j_num) in let y = iter 0 start in let z = fix (fun (a, b) (c, d) -> ED.eq a c && ND.eq b d) f y in let i = dd_singleton st.d_vars z in let j = cycle st st.cl i in let cases = ref [] in Hashtbl.iter (fun case _ -> cases := case::(!cases)) j.data; List.iter (fun case -> let i' = set_target_case st i case in let j = cycle st st.cl i' in Hashtbl.iter (fun _ q -> add_case st q) j.data) !cases; if st.opt.verbose_ci then Format.printf "-> @[%a@]@." print_st st; st.delta <- List.filter ((<>) case) st.delta; done; let final = apply_cl { d_vars = st.d_vars; data = st.env } st.cl in Format.printf "Accessible: %a@." print_dd final; let check_guarantee (id, f) = let cl = Formula.conslist_of_f f in Format.printf "@[%s:@ %a ⇒ ⊥ @ " id Formula_printer.print_conslist cl; let z = apply_cl final cl in if Hashtbl.length z.data = 0 then Format.printf "OK@]@ " else Format.printf "FAIL@]@ " in if st.guarantees <> [] then begin Format.printf "Guarantee @["; List.iter check_guarantee st.guarantees; Format.printf "@]@." end; if List.exists (fun (p, _, _) -> p) st.rp.all_vars then begin Format.printf "Probes: @["; List.iter (fun (p, id, ty) -> if p then begin Format.printf "%a ∊ @[" Formula_printer.print_id id; Hashtbl.iter (fun case (enum, num) -> begin match ty with | TInt | TReal -> Format.printf "%a" ND.print_itv (ND.project num id) | TEnum _ -> Format.printf "@[{ %a }@]" (print_list Formula_printer.print_id ", ") (ED.project enum id) end; Format.printf " when @[[%a]@]@ " (print_list Formula_printer.print_id ", ") case) final.data; Format.printf "@]@ " end) st.rp.all_vars; Format.printf "@]@." end end