open Ast
open Ast_util
open Formula
open Typing
open Util
open Num_domain
open Abs_interp
module I (ND : NUMERICAL_ENVIRONMENT_DOMAIN) : sig
val do_prog : cmdline_opt -> rooted_prog -> unit
end = struct
(* **********************
EDD Domain
********************** *)
type item = string
type evar = id * item list
type nvar = id * bool
type varenv = {
evars : evar list;
nvars : nvar list;
ev_order : (id, int) Hashtbl.t;
}
type edd_node =
| DBot
| DVal of int
| DChoice of id * (item * edd) list
and edd = int * edd_node
type edd_v = {
ve : varenv;
root : edd;
leaves : (int, ND.t) Hashtbl.t;
(* add here eventual annotations *)
}
(*
Utility functions for memoization
memo : (((int * 'a) -> (int * 'b)) -> 'a -> 'b) -> (int * 'a) -> (int * 'b)
memo2 : (((int * 'a) -> (int * 'b) -> (int * 'c)) -> 'a -> 'b -> 'c) -> (int * 'a) -> (int * 'b) -> (int * 'c)
*)
let memo f =
let n = ref 0 in
let memo = Hashtbl.create 12 in
let rec ff (id, v) =
try Hashtbl.find memo id
with Not_found ->
let r = f ff v in
incr n;
Hashtbl.add memo id (n, r);
n, r
in ff
let memo2 f =
let n = ref 0 in
let memo = Hashtbl.create 12 in
let rec ff (id1, v1) (id2, v2) =
try Hashtbl.find memo (id1, id2)
with Not_found ->
let r = f ff v1 v2 in
incr n;
Hashtbl.add memo (id1, id2) (n, r);
n, r
in ff
(*
edd_print : Format.formatter -> edd_v -> unit
*)
let edd_print fmt v =
let c_nodes = Hashtbl.create 12 in
let rec add = function
| n, (DChoice(_, l) as x) ->
if not (Hashtbl.mem c_nodes n) then begin
Hashtbl.add c_nodes n x;
List.iter (fun (_, y) -> add y) l
end
| _ -> ()
in add v.root;
let print_n fmt = function
| (_, DBot) -> Format.fprintf fmt "⊥";
| (_, DVal i) -> Format.fprintf fmt "v%d" i;
| (n, DChoice _) -> Format.fprintf fmt "n%d" n
in
Format.fprintf fmt "Root: %a@." print_n v.root;
Hashtbl.iter
(fun id x -> match x with
| DChoice (var, l) ->
let p fmt (c, l) = Format.fprintf fmt "%s → %a" c print_n l in
Format.fprintf fmt "n%d: %a ? @[<hov>%a@]@."
id Formula_printer.print_id var
(print_list p ", ") l
| _ -> assert false)
c_nodes;
Hashtbl.iter
(fun id v -> Format.fprintf fmt "v%d: %a@." id ND.print v)
v.leaves
(*
edd_bot : varenv -> edd_v
*)
let edd_bot ve = { ve; root = (0, DBot); leaves = Hashtbl.create 1 }
(*
edd_top : evar list -> nvar list -> edd_v
*)
let edd_top ve =
let leaves = Hashtbl.create 1 in
Hashtbl.add leaves 0 (ND.top ve.nvars);
{ ve; root = (1, DVal 0); leaves }
(*
edd_of_cons : varenv -> enum_cons -> edd_v
*)
let edd_of_cons ve (op, vid, r) =
let op = match op with | E_EQ -> (=) | E_NE -> (<>) in
let leaves = Hashtbl.create 1 in
Hashtbl.add leaves 0 (ND.top ve.nvars);
let bot, top = (0, DBot), (1, DVal 0) in
let root = match r with
| EItem x ->
2, DChoice(vid,
List.map (fun v -> if op v x then v, bot else v, top)
(List.assoc vid ve.evars))
| EIdent vid2 ->
let a, b =
if Hashtbl.find ve.ev_order vid < Hashtbl.find ve.ev_order vid2
then vid, vid2
else vid2, vid
in
let n = ref 2 in
let nb x =
incr n;
!n, DChoice(b,
List.map (fun v -> if op v x then v, top else v, bot)
(List.assoc b ve.evars))
in
2, DChoice(a, List.map (fun x -> x, nb x) (List.assoc a ve.evars))
in
{ ve; root; leaves }
(*
edd_join : edd_v -> edd_v -> edd_v
*)
let edd_join a b =
let ve = a.ve in
let leaves = Hashtbl.create 12 in
let n = ref 0 in
let get_leaf x =
let id = ref None in
Hashtbl.iter (fun i v -> if ND.eq v x then id := Some i) leaves;
begin match !id with
| Some i -> DVal i
| None ->
incr n;
Hashtbl.add leaves !n x;
DVal (!n)
end
in
let f f_rec na nb =
let dq v l =
let _, (i, x0) = List.hd l in
if List.exists (fun (_, (j, x)) -> j <> i || x <> x0) l
then DChoice(v, l)
else x0
in
match na, nb with
| DBot, DBot -> DBot
| DBot, DVal i -> get_leaf (Hashtbl.find b.leaves i)
| x, DBot -> x
| DChoice(va, la), DChoice(vb, lb) when va = vb ->
let kl = List.map2
(fun (ta, ba) (tb, bb) -> assert (ta = tb);
ta, f (ba, bb))
la lb
in
dq va kl
| DChoice(va, la), DChoice(vb, lb) ->
let v, kl =
if Hashtbl.find ve.ev_order va < Hashtbl.find ve.ev_order vb then
va, List.map (fun (k, ca) -> k, f (ca, nb)) la
else
vb, List.map (fun (k, cb) -> k, f (na, cb)) lb
in
dq v kl
| DChoice(va, la), _ ->
let kl = List.map (fun (k, ca) -> k, f (ca, nb)) la in
dq va kl
| _, DChoice(vb, lb) ->
let kl = List.map (fun (k, cb) -> k, f (na, cb)) lb in
dq vb kl
| DVal u, DVal v ->
let x = ND.join (Hashtbl.find a.leaves u) (Hashtbl.find b.leaves v) in
get_leaf x
in
{ leaves; ve; root = memo2 f a.root b.root }
(*
edd_meet : edd_v -> edd_v -> edd_v
*)
let edd_meet a b =
let ve = a.ve in
let n = ref 0 in
let leaves = Hashtbl.create 12 in
let get_leaf x =
if ND.is_bot x then DBot else begin
let id = ref None in
Hashtbl.iter (fun i v -> if ND.eq v x then id := Some i) leaves;
match !id with
| Some i -> DVal i
| None ->
incr n;
Hashtbl.add leaves !n x;
DVal (!n)
end
in
let memo = Hashtbl.create 12 in
let rec f (na, nb) =
try Hashtbl.find memo (na, nb)
with Not_found -> let r =
let dq v l =
let _, x0 = List.hd l in
if List.exists (fun (_, x) -> x <> x0) l
then DChoice(v, l)
else x0
in
match na, nb with
| DBot, _ | _, DBot -> DBot
| DChoice(va, la), DChoice(vb, lb) when va = vb ->
let kl = List.map2
(fun (ta, ba) (tb, bb) -> assert (ta = tb);
ta, f (ba, bb))
la lb
in
dq va kl
| DChoice(va, la), DChoice(vb, lb) ->
let v, kl =
if Hashtbl.find ve.ev_order va < Hashtbl.find ve.ev_order vb then
va, List.map (fun (k, ca) -> k, f (ca, nb)) la
else
vb, List.map (fun (k, cb) -> k, f (na, cb)) lb
in
dq v kl
| DChoice(va, la), _ ->
let kl = List.map (fun (k, ca) -> k, f (ca, nb)) la in
dq va kl
| _, DChoice(vb, lb) ->
let kl = List.map (fun (k, cb) -> k, f (na, cb)) lb in
dq vb kl
| DVal u, DVal v ->
let x = ND.meet (Hashtbl.find a.leaves u) (Hashtbl.find b.leaves v) in
get_leaf x
in Hashtbl.add memo (na, nb) r; r
in
{ leaves; ve; root = f (a.root, b.root) }
(*
edd_apply_ncl : edd_v -> num_cons list -> edd_v
*)
let edd_apply_ncl v ncl =
let ve = v.ve in
let n = ref 0 in
let leaves = Hashtbl.create 12 in
let get_leaf x =
if ND.is_bot x then DBot else begin
let id = ref None in
Hashtbl.iter (fun i v -> if ND.eq v x then id := Some i) leaves;
match !id with
| Some i -> DVal i
| None ->
incr n;
Hashtbl.add leaves !n x;
DVal (!n)
end
in
let memo = Hashtbl.create 12 in
let rec f n =
try Hashtbl.find memo n
with Not_found -> let r =
match n with
| DBot -> DBot
| DVal i -> get_leaf (ND.apply_cl (Hashtbl.find v.leaves i) ncl)
| DChoice(var, l) ->
let l = List.map (fun (k, v) -> k, f v) l in
let _, x0 = List.hd l in
if List.exists (fun (_, x) -> x <> x0) l
then DChoice(var, l)
else x0
in Hashtbl.add memo n r; r
in
{ leaves; ve; root = f v.root }
(*
edd_apply_cl : edd_v -> conslist -> edd_v
*)
let rec edd_apply_cl v (ec, nc, r) =
let v = List.fold_left
(fun v cons -> edd_meet v (edd_of_cons v.ve cons))
v ec in
match r with
| CLTrue ->
edd_apply_ncl v nc
| CLFalse -> edd_bot v.ve
| CLAnd (a, b) ->
let v = edd_apply_cl v ([], nc, a) in
edd_apply_cl v ([], nc, b)
| CLOr((eca, nca, ra), (ecb, ncb, rb)) ->
edd_join (edd_apply_cl v (eca, nc@nca, ra))
(edd_apply_cl v (ecb, nc@ncb, rb))
(*
edd_eq : edd_v -> edd_v -> bool
*)
let edd_eq a b =
let memo = Hashtbl.create 12 in
let rec f (na, nb) =
try Hashtbl.find memo (na, nb)
with Not_found -> let r =
match na, nb with
| DBot, DBot -> true
| DVal i, DVal j ->
ND.eq (Hashtbl.find a.leaves i) (Hashtbl.find b.leaves j)
| DChoice(va, la), DChoice(vb, lb) when va = vb ->
List.for_all2 (fun (ca, na) (cb, nb) -> assert (ca = cb); f (na, nb))
la lb
| _ -> false
in Hashtbl.add memo (na, nb) r; r
in f (a.root, b.root)
(*
let test () =
let ve = {
evars = ["x", ["tt"; "ff"]; "y", ["tt"; "ff"]; "z", ["tt"; "ff"]];
nvars = [];
ev_order = Hashtbl.create 2 } in
Hashtbl.add ve.ev_order "x" 0;
Hashtbl.add ve.ev_order "y" 1;
Hashtbl.add ve.ev_order "z" 2;
let u = edd_of_cons ve (E_EQ, "x", EIdent "y") in
Format.printf "x = y : @[%a@]@." edd_print u;
let v = edd_of_cons ve (E_NE, "y", EIdent "z") in
Format.printf "y != z : @[%a@]@." edd_print v;
let w = edd_meet u v in
Format.printf "x = y && y != z : @[%a@]@." edd_print w;
let t = edd_join u v in
Format.printf "x = y || y != z : @[%a@]@." edd_print t
*)
(* ******************************
Abstract interpret
******************************* *)
type env = {
rp : rooted_prog;
opt : cmdline_opt;
ve : varenv;
(* program expressions *)
f : bool_expr;
cl : conslist;
f_g : bool_expr;
cl_g : 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 *)
mutable data : edd_v;
}
(*
extract_linked_evars : conslist -> (id * id) list
Extract all pairs of enum-type variable (x, y) appearing in an
equation like x = y or x != y
A couple may appear several times in the result.
*)
let rec extract_linked_evars_root (ecl, _, r) =
let v_ecl = List.fold_left
(fun c (_, x, v) -> match v with
| EIdent y -> (x, y)::c
| _ -> c)
[] ecl
in
v_ecl
let rec extract_linked_evars(ecl, _, r) =
let v_ecl = List.fold_left
(fun c (_, x, v) -> match v with
| EIdent y -> (x, y)::c
| _ -> c)
[] ecl
in
let v_ecl2 =
let q = List.fold_left
(fun c (_, x, v) -> match v with
| EItem _ -> x::c | _ -> c)
[] ecl
in
match q with
| [x; y] -> [x, y]
| [x; y; z] -> [x, y; y, z; z, x]
| _ -> []
in
let rec aux = function
| CLTrue | CLFalse -> []
| CLAnd(a, b) -> aux a @ aux b
| CLOr(a, b) -> extract_linked_evars a @ extract_linked_evars b
in
v_ecl @ v_ecl2 @ aux r
(*
scope_constrict : id list -> (id * id) list -> id list
Orders the variable in the first argument such as to minimize the
sum of the distance between the position of two variables appearing in
a couple of the second list. (minimisation is approximate, this is
an heuristic so that the EDD will not explode in size when expressing
equations such as x = y && u = v && a != b)
*)
let scope_constrict vars cp_id =
let var_i = Array.of_list vars in
let n = Array.length var_i in
let i_var = Hashtbl.create n in
Array.iteri (fun i v -> Hashtbl.add i_var v i) var_i;
let cp_i = List.map
(fun (x, y) -> Hashtbl.find i_var x, Hashtbl.find i_var y)
cp_id in
let eval i =
let r = Array.make n (-1) in
Array.iteri (fun pos var -> r.(var) <- pos) i;
Array.iteri (fun _ x -> assert (x <> (-1))) r;
List.fold_left
(fun s (x, y) -> s + abs (r.(x) - r.(y)))
0 cp_i
in
let best = Array.make n 0 in
for i = 0 to n-1 do best.(i) <- i done;
let usefull = ref true in
Format.printf "SCA";
while !usefull do
Format.printf ".@?";
usefull := false;
let try_s x =
if eval x < eval best then begin
Array.blit x 0 best 0 n;
usefull := true
end
in
for i = 0 to n-1 do
let tt = Array.copy best in
(* move item i at beginning *)
let temp = tt.(i) in
for j = i downto 1 do tt.(j) <- tt.(j-1) done;
tt.(0) <- temp;
(* try all positions *)
try_s tt;
for j = 1 to n-1 do
let temp = tt.(j-1) in
tt.(j-1) <- tt.(j);
tt.(j) <- temp;
try_s tt
done
done
done;
Format.printf "@.";
Array.to_list (Array.map (Array.get var_i) best)
(*
init_env : cmdline_opt -> rooted_prog -> env
*)
let init_env opt rp =
Format.printf "Vars: @[<hov>%a@]@.@."
(print_list Ast_printer.print_typed_var ", ")
rp.all_vars;
let enum_vars = List.fold_left
(fun v (_, id, t) -> match t with
| TEnum ch -> (id, ch)::v | _ -> v)
[] rp.all_vars in
let num_vars = List.fold_left
(fun v (_, id, t) -> match t with
| TInt -> (id, false)::v | TReal -> (id, true)::v | _ -> v)
[] rp.all_vars in
let init_f = Transform.init_f_of_prog rp in
Format.printf "Init formula: %a@.@." Formula_printer.print_expr init_f;
let init_cl = conslist_of_f init_f in
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 "@.";
let f = Formula.eliminate_not (Transform.f_of_prog rp false) in
let f_g = Formula.eliminate_not (Transform.f_of_prog rp true) in
Format.printf "Cycle formula:@.%a@.@." Formula_printer.print_expr f;
let cl = Formula.conslist_of_f f in
let cl_g = Formula.conslist_of_f f_g in
(* calculate order for enumerated variables *)
let evars = List.map fst enum_vars in
let lv0 = List.map (fun x -> x, "N"^x)
(List.filter (fun x -> List.exists (fun y -> y = "N"^x) evars) evars) in
let lv1 = extract_linked_evars_root init_cl
@ extract_linked_evars_root cl_g in
let lv2 = extract_linked_evars init_cl @ extract_linked_evars cl_g in
let lv1 = uniq_sorted
(List.sort Pervasives.compare (List.map ord_couple lv1)) in
let lv2 = uniq_sorted
(List.sort Pervasives.compare (List.map ord_couple lv2)) in
let evars_ord = scope_constrict evars (lv0 @ lv1 @ lv2) in
let ev_order = Hashtbl.create (List.length evars) in
List.iteri (fun i x -> Hashtbl.add ev_order x i) evars_ord;
let ve = { evars = enum_vars; nvars = num_vars; ev_order } in
Format.printf "Order for variables: @[<hov>[%a]@]@."
(print_list Formula_printer.print_id ", ") evars_ord;
(* calculate cycle variables and forget variables *)
let cycle = List.fold_left
(fun q (_, id, ty) ->
if id.[0] = 'N' then
(String.sub id 1 (String.length id - 1), id, ty)::q
else q)
[] rp.all_vars
in
let forget = List.map (fun (_, id, ty) -> (id, ty))
(List.filter
(fun (_, id, _) ->
not (List.exists (fun (_, id2, _) -> id2 = "N"^id) rp.all_vars))
rp.all_vars)
in
(* calculate initial environment *)
let data = edd_apply_cl (edd_top ve) init_cl in
Format.printf "Init: @[<hov>%a@]@." edd_print data;
{ rp; opt; ve;
f; cl; f_g; cl_g; guarantees;
cycle; forget; data }
let do_prog opt rp =
let e = init_env opt rp in
let d2 = edd_apply_cl e.data e.cl in
Format.printf " -> @[<hov>%a@]@." edd_print d2
end