From 574fc624a3203a0b5791d8527b1fb30896734eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bobot?= <francois.bobot@cea.fr> Date: Wed, 21 Sep 2022 15:57:19 +0200 Subject: [PATCH] [interpretation] Copy module reduction_engine from Why3. --- src/interpretation.ml | 49 ++ src/interpretation.mli | 30 + src/main.ml | 51 +- src/reduction_engine.ml | 1495 ++++++++++++++++++++++++++++++++++++++ src/reduction_engine.mli | 133 ++++ src/verification.ml | 30 +- src/verification.mli | 17 +- tests/interpretation.t | 13 + 8 files changed, 1790 insertions(+), 28 deletions(-) create mode 100644 src/interpretation.ml create mode 100644 src/interpretation.mli create mode 100644 src/reduction_engine.ml create mode 100644 src/reduction_engine.mli create mode 100644 tests/interpretation.t diff --git a/src/interpretation.ml b/src/interpretation.ml new file mode 100644 index 0000000..bcfb697 --- /dev/null +++ b/src/interpretation.ml @@ -0,0 +1,49 @@ +(**************************************************************************) +(* *) +(* This file is part of CAISAR. *) +(* *) +(* Copyright (C) 2022 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* You can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Why3 +open Base + +let interpret_task env task = + let f = Task.task_goal_fmla task in + let engine = + Reduction_engine.create + { + compute_defs = false; + compute_builtin = true; + compute_def_set = Term.Sls.empty; + compute_max_quantifier_domain = 0; + } + env Ident.Mid.empty + in + let f = Reduction_engine.normalize ~limit:1000 engine Term.Mvs.empty f in + Fmt.pr "%a : %a@." Pretty.print_pr (Task.task_goal task) Pretty.print_term f + +let interpret ?debug ?format ~loadpath s = + let env, _config, mstr_theory = + Verification.open_file ?debug ?format ~loadpath s + in + Wstdlib.Mstr.iter + (fun _ theory -> + List.iter (Task.split_theory theory None None) ~f:(fun task -> + interpret_task env task)) + mstr_theory diff --git a/src/interpretation.mli b/src/interpretation.mli new file mode 100644 index 0000000..68649f2 --- /dev/null +++ b/src/interpretation.mli @@ -0,0 +1,30 @@ +(**************************************************************************) +(* *) +(* This file is part of CAISAR. *) +(* *) +(* Copyright (C) 2022 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* You can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Base + +val interpret : + ?debug:bool -> + ?format:string -> + loadpath:string list -> + Verification.File.t -> + unit diff --git a/src/main.ml b/src/main.ml index 00b42e0..c0034ca 100644 --- a/src/main.ml +++ b/src/main.ml @@ -204,6 +204,10 @@ let verify_json ?memlimit ?timelimit ?outfile json = ~f:(record_verification_result jin.id verification_result) | _ -> failwith "Unexpected more than one theory from a JSON file" +let interpret format loadpath files = + let debug = log_level_is_debug () in + List.iter ~f:(Interpretation.interpret ~debug ?format ~loadpath) files + let exec_cmd cmdname cmd = Logs.debug (fun m -> m "Execution of command '%s'" cmdname); cmd () @@ -257,6 +261,20 @@ let timelimit = Arg.( value & opt (some string) None & info [ "t"; "timelimit" ] ~doc ~docv:"TIME") + let loadpath = + let doc = "Additional loadpath." in + Arg.(value & opt_all string [ "." ] & info [ "L"; "loadpath" ] ~doc) + + let format = + let doc = "File format." in + Arg.(value & opt (some string) None & info [ "format" ] ~doc) + + let files = + let doc = "File to verify." in + let file_or_stdin = Arg.conv' Verification.File.(of_string, pretty) in + Arg.(non_empty & pos_all file_or_stdin [] & info [] ~doc ~docv:"FILE") + + let verify_cmd = let cmdname = "verify" in let info = @@ -265,14 +283,6 @@ let verify_cmd = ~doc ~man:[ `S Manpage.s_description; `P doc ] in - let loadpath = - let doc = "Additional loadpath." in - Arg.(value & opt_all string [ "." ] & info [ "L"; "loadpath" ] ~doc) - in - let format = - let doc = "File format." in - Arg.(value & opt (some string) None & info [ "format" ] ~doc) - in let prover = let all_provers = Prover.list_available () in let doc = @@ -297,11 +307,6 @@ let verify_cmd = let doc = "Dataset $(docv) (CSV format only)." in Arg.(value & opt (some file) None & info [ "dataset" ] ~doc ~docv:"FILE") in - let files = - let doc = "File to verify." in - let file_or_stdin = Arg.conv' Verification.File.(of_string, pretty) in - Arg.(non_empty & pos_all file_or_stdin [] & info [] ~doc ~docv:"FILE") - in let term = Term.( const @@ -355,6 +360,24 @@ let verify_json_cmd = in Cmd.v info term +let interpret_cmd = + let cmdname = "interpret" in + let doc = + "Interpret the goal and print the strategy that will be executed." + in + let info = + Cmd.info cmdname ~sdocs:Manpage.s_common_options ~exits:Cmd.Exit.defaults + ~doc + ~man:[ `S Manpage.s_description; `P doc ] + in + let term = + Term.( + const (fun format loadpath files _ -> + exec_cmd cmdname (fun () -> interpret format loadpath files)) + $ format $ loadpath $ files $ setup_logs) + in + Cmd.v info term + let default_info = let doc = "A platform for characterizing the safety and robustness of artificial \ @@ -389,7 +412,7 @@ let () = let () = try Cmd.group ~default:default_cmd default_info - [ config_cmd; verify_cmd; verify_json_cmd ] + [ config_cmd; verify_cmd; verify_json_cmd; interpret_cmd ] |> Cmd.eval ~catch:false |> Caml.exit with exn when not (log_level_is_debug ()) -> Logs.err (fun m -> m "@[%a@]" Why3.Exn_printer.exn_printer exn) diff --git a/src/reduction_engine.ml b/src/reduction_engine.ml new file mode 100644 index 0000000..e2f8783 --- /dev/null +++ b/src/reduction_engine.ml @@ -0,0 +1,1495 @@ +(********************************************************************) +(* *) +(* The Why3 Verification Platform / The Why3 Development Team *) +(* Copyright 2010-2022 -- Inria - CNRS - Paris-Saclay University *) +(* *) +(* This software is distributed under the terms of the GNU Lesser *) +(* General Public License version 2.1, with the special exception *) +(* on linking described in file LICENSE. *) +(* *) +(********************************************************************) + +open Why3 +open Term + +[@@@ ocaml.warning "-9"] + +(* {2 Values} *) + +type value = + | Term of term (* invariant: is in normal form *) + | Int of BigInt.t + | Real of Number.real_value + +let v_attr_copy orig v = + match v with + | Int _ -> v + | Real _ -> v + | Term t -> Term (t_attr_copy orig t) + +let term_of_value v = + let open Number in + match v with + | Term t -> t + | Int n -> t_int_const n + | Real { rv_sig = s; rv_pow2 = pow2; rv_pow5 = pow5 } -> t_real_const ~pow2 ~pow5 s + +exception NotNum + +let big_int_of_const c = + match c with + | Constant.ConstInt i -> i.Number.il_int + | Constant.ConstReal _ -> assert false + | Constant.ConstStr _ -> assert false + +let big_int_of_value v = + match v with + | Int n -> n + | Real _ -> assert false + | Term { t_node = Tconst c } -> big_int_of_const c + | Term _ -> raise NotNum + +let real_of_const c = + match c with + | Constant.ConstReal r -> r.Number.rl_real + | Constant.ConstInt _ -> assert false + | Constant.ConstStr _ -> assert false + +let real_of_value v = + match v with + | Real r -> r + | Int _ -> assert false + | Term { t_node = Tconst c } -> real_of_const c + | Term _ -> raise NotNum + +(* {2 Builtin symbols} *) + +let builtins = Hls.create 17 + +(* all builtin functions *) + +exception Undetermined + +let to_bool b = if b then t_true else t_false + +let _t_app_value ls l ty = + Term (t_app ls (List.map term_of_value l) ty) + +let is_zero v = + try BigInt.eq (big_int_of_value v) BigInt.zero + with NotNum -> false + +let is_one v = + try BigInt.eq (big_int_of_value v) BigInt.one + with NotNum -> false + +let eval_int_op op simpl ls l ty = + match l with + | [t1 ; t2] -> + begin + try + let n1 = big_int_of_value t1 in + let n2 = big_int_of_value t2 in + Int (op n1 n2) + with NotNum | Division_by_zero -> + simpl ls t1 t2 ty + end + | _ -> assert false + +let simpl_add _ls t1 t2 _ty = + if is_zero t1 then t2 else + if is_zero t2 then t1 else + raise Undetermined + +let simpl_sub _ls t1 t2 _ty = + if is_zero t2 then t1 else + raise Undetermined + +let simpl_mul _ls t1 t2 _ty = + if is_zero t1 then t1 else + if is_zero t2 then t2 else + if is_one t1 then t2 else + if is_one t2 then t1 else + raise Undetermined + +let simpl_div _ls t1 t2 _ty = + if is_zero t2 then raise Undetermined; + if is_zero t1 then t1 else + if is_one t2 then t1 else + raise Undetermined + +let simpl_mod _ls t1 t2 _ty = + if is_zero t2 then raise Undetermined; + if is_zero t1 then t1 else + if is_one t2 then Int (BigInt.zero) else + raise Undetermined + +let simpl_minmax _ls v1 v2 _ty = + match v1,v2 with + | Term t1, Term t2 -> + if t_equal t1 t2 then v1 else + raise Undetermined + | _ -> + raise Undetermined + +let eval_int_rel op _ls l _ty = + match l with + | [t1 ; t2] -> + begin + try + let n1 = big_int_of_value t1 in + let n2 = big_int_of_value t2 in + Term (to_bool (op n1 n2)) + with NotNum | Division_by_zero -> + raise Undetermined + (* t_app_value ls l ty *) + end + | _ -> assert false + +let eval_int_uop op _ls l _ty = + match l with + | [t1] -> + begin + try + let n1 = big_int_of_value t1 in Int (op n1) + with NotNum | Division_by_zero -> + raise Undetermined + (* t_app_value ls l ty *) + end + | _ -> assert false + +let real_align ~pow2 ~pow5 r = + let open Number in + let { rv_sig = s; rv_pow2 = p2; rv_pow5 = p5 } = r in + let s = BigInt.mul s (BigInt.pow_int_pos_bigint 2 (BigInt.sub p2 pow2)) in + let s = BigInt.mul s (BigInt.pow_int_pos_bigint 5 (BigInt.sub p5 pow5)) in + s + +let eval_real_op op simpl ls l ty = + match l with + | [t1; t2] -> + begin + try + let n1 = real_of_value t1 in + let n2 = real_of_value t2 in + Real (op n1 n2) + with NotNum -> + simpl ls t1 t2 ty + end + | _ -> assert false + +let eval_real_uop op _ls l _ty = + match l with + | [t1] -> + begin + try + let n1 = real_of_value t1 in + Real (op n1) + with NotNum -> + raise Undetermined + end + | _ -> assert false + +let eval_real_rel op _ls l _ty = + let open Number in + match l with + | [t1 ; t2] -> + begin + try + let n1 = real_of_value t1 in + let n2 = real_of_value t2 in + let s1, s2 = + let { rv_sig = s1; rv_pow2 = p21; rv_pow5 = p51 } = n1 in + let { rv_sig = s2; rv_pow2 = p22; rv_pow5 = p52 } = n2 in + if BigInt.sign s1 <> BigInt.sign s2 then s1,s2 + else + let pow2 = BigInt.min p21 p22 in + let pow5 = BigInt.min p51 p52 in + let s1 = real_align ~pow2 ~pow5 n1 in + let s2 = real_align ~pow2 ~pow5 n2 in + s1, s2 in + Term (to_bool (op s1 s2)) + with NotNum -> + raise Undetermined + (* t_app_value ls l ty *) + end + | _ -> assert false + +let real_0 = Number.real_value BigInt.zero +let real_1 = Number.real_value BigInt.one + +let is_real t r = + try Number.compare_real (real_of_value t) r = 0 + with NotNum -> false + +let real_opp r = + let open Number in + let { rv_sig = s; rv_pow2 = pow2; rv_pow5 = pow5 } = r in + real_value ~pow2 ~pow5 (BigInt.minus s) + +let real_add r1 r2 = + let open Number in + let { rv_sig = s1; rv_pow2 = p21; rv_pow5 = p51 } = r1 in + let { rv_sig = s2; rv_pow2 = p22; rv_pow5 = p52 } = r2 in + if BigInt.eq s1 BigInt.zero then r2 else + if BigInt.eq s2 BigInt.zero then r1 else + let pow2 = BigInt.min p21 p22 in + let pow5 = BigInt.min p51 p52 in + let s1 = real_align ~pow2 ~pow5 r1 in + let s2 = real_align ~pow2 ~pow5 r2 in + real_value ~pow2 ~pow5 (BigInt.add s1 s2) + +let real_sub r1 r2 = + let open Number in + let { rv_sig = s1; rv_pow2 = p21; rv_pow5 = p51 } = r1 in + let { rv_sig = s2; rv_pow2 = p22; rv_pow5 = p52 } = r2 in + if BigInt.eq s2 BigInt.zero then r1 else + if BigInt.eq s1 BigInt.zero then real_value ~pow2:p22 ~pow5:p52 (BigInt.minus s2) else + let pow2 = BigInt.min p21 p22 in + let pow5 = BigInt.min p51 p52 in + let s1 = real_align ~pow2 ~pow5 r1 in + let s2 = real_align ~pow2 ~pow5 r2 in + real_value ~pow2 ~pow5 (BigInt.sub s1 s2) + +let real_mul r1 r2 = + let open Number in + let { rv_sig = s1; rv_pow2 = p21; rv_pow5 = p51 } = r1 in + let { rv_sig = s2; rv_pow2 = p22; rv_pow5 = p52 } = r2 in + let s = BigInt.mul s1 s2 in + let pow2 = BigInt.add p21 p22 in + let pow5 = BigInt.add p51 p52 in + real_value ~pow2 ~pow5 s + +let simpl_real_add _ls t1 t2 _ty = + if is_real t1 real_0 then t2 else + if is_real t2 real_0 then t1 else + raise Undetermined + +let simpl_real_sub _ls t1 t2 _ty = + if is_real t2 real_0 then t1 else + raise Undetermined + +let simpl_real_mul _ls t1 t2 _ty = + if is_real t1 real_0 then t1 else + if is_real t2 real_0 then t2 else + if is_real t1 real_1 then t2 else + if is_real t2 real_1 then t1 else + raise Undetermined + +let real_pow r1 r2 = + let open Number in + let { rv_sig = s1; rv_pow2 = p21; rv_pow5 = p51 } = r1 in + let { rv_sig = s2; rv_pow2 = p22; rv_pow5 = p52 } = r2 in + if BigInt.le s1 BigInt.zero then + raise Undetermined + else if BigInt.lt p22 BigInt.zero || BigInt.lt p52 BigInt.zero then + raise NotNum + else + let s1 = try BigInt.to_int s1 with Failure _ -> raise NotNum in + let s2 = BigInt.mul s2 (BigInt.pow_int_pos_bigint 2 p22) in + let s2 = BigInt.mul s2 (BigInt.pow_int_pos_bigint 5 p52) in + let s = + if s1 = 1 then BigInt.one + else if BigInt.lt s2 BigInt.zero then raise NotNum + else BigInt.pow_int_pos_bigint s1 s2 in + let pow2 = BigInt.mul p21 s2 in + let pow5 = BigInt.mul p51 s2 in + Number.real_value ~pow2 ~pow5 s + +let simpl_real_pow _ls t1 _t2 _ty = + if is_real t1 real_1 then t1 else + raise Undetermined + +let real_from_int _ls l _ty = + match l with + | [t] -> + begin + try + let n = big_int_of_value t in + Real (Number.real_value n) + with NotNum -> + raise Undetermined + end + | _ -> assert false + +let built_in_theories = + [ +(* + ["bool"],"Bool", [], + [ "True", None, eval_true ; + "False", None, eval_false ; + ] ; +*) + ["int"],"Int", [], + [ Ident.op_infix "+", None, eval_int_op BigInt.add simpl_add; + Ident.op_infix "-", None, eval_int_op BigInt.sub simpl_sub; + Ident.op_infix "*", None, eval_int_op BigInt.mul simpl_mul; + Ident.op_prefix "-", None, eval_int_uop BigInt.minus; + Ident.op_infix "<", None, eval_int_rel BigInt.lt; + Ident.op_infix "<=", None, eval_int_rel BigInt.le; + Ident.op_infix ">", None, eval_int_rel BigInt.gt; + Ident.op_infix ">=", None, eval_int_rel BigInt.ge; + ] ; + ["int"],"MinMax", [], + [ "min", None, eval_int_op BigInt.min simpl_minmax; + "max", None, eval_int_op BigInt.max simpl_minmax; + ] ; + ["int"],"ComputerDivision", [], + [ "div", None, eval_int_op BigInt.computer_div simpl_div; + "mod", None, eval_int_op BigInt.computer_mod simpl_mod; + ] ; + ["int"],"EuclideanDivision", [], + [ "div", None, eval_int_op BigInt.euclidean_div simpl_div; + "mod", None, eval_int_op BigInt.euclidean_mod simpl_mod; + ] ; + ["real"], "Real", [], + [ Ident.op_prefix "-", None, eval_real_uop real_opp; + Ident.op_infix "+", None, eval_real_op real_add simpl_real_add; + Ident.op_infix "-", None, eval_real_op real_sub simpl_real_sub; + Ident.op_infix "*", None, eval_real_op real_mul simpl_real_mul; + Ident.op_infix "<", None, eval_real_rel BigInt.lt; + Ident.op_infix "<=", None, eval_real_rel BigInt.le; + Ident.op_infix ">", None, eval_real_rel BigInt.gt; + Ident.op_infix ">=", None, eval_real_rel BigInt.ge ] ; + ["real"], "FromInt", [], + [ "from_int", None, real_from_int ] ; + ["real"], "PowerReal", [], + [ "pow", None, eval_real_op real_pow simpl_real_pow ] ; +(* + ["map"],"Map", ["map", builtin_map_type], + [ "const", Some ls_map_const, eval_map_const; + "get", Some ls_map_get, eval_map_get; + "set", Some ls_map_set, eval_map_set; + ] ; +*) + ] + +let add_builtin_th env (l,n,t,d) = + let th = Env.read_theory env l n in + List.iter + (fun (id,r) -> + let ts = Theory.ns_find_ts th.Theory.th_export [id] in + r ts) + t; + List.iter + (fun (id,r,f) -> + let ls = Theory.ns_find_ls th.Theory.th_export [id] in + Hls.add builtins ls f; + match r with + | None -> () + | Some r -> r := ls) + d + +let get_builtins env = + Hls.clear builtins; + List.iter (add_builtin_th env) built_in_theories + + + +(* {2 the reduction machine} *) + + +type rule = Svs.t * term list * term + +type params = + { compute_defs : bool; + compute_builtin : bool; + compute_def_set : Term.Sls.t; + compute_max_quantifier_domain : int; + } + +type engine = + { known_map : Decl.decl Ident.Mid.t; + rules : rule list Mls.t; + params : params; + ls_lt : lsymbol; (* The lsymbol for [int.Int.(<)] *) + } + + +(* OBSOLETE COMMENT + + A configuration is a pair (t,s) where t is a stack of terms and s is a + stack of function symbols. + + A configuration ([t1;..;tn],[f1;..;fk]) represents a whole term, its + model, as defined recursively by + + model([t],[]) = t + + model(t1::..::tn::t,f::s) = model(f(t1,..,tn)::t,s) + where f is of arity n + + A given term can be "exploded" into a configuration by reversing the + rules above + + During reduction, the terms in the first stack are kept in normal + form (value). The normalization process can be defined as the repeated + application of the following rules. + + ([t],[]) --> t // t is in normal form + + if f(t1,..,tn) is not a redex then + (t1::..::tn::t,f::s) --> (f(t1,..,tn)::t,s) + + if f(t1,..,tn) is a redex l sigma for a rule l -> r then + (t1::..::tn::t,f::s) --> (subst(sigma) @ t,explode(r) @ s) + + + + +*) + +type substitution = term Mvs.t + +type cont = +| Kapp of lsymbol * Ty.ty option +| Kif of term * term * substitution +| Klet of vsymbol * term * substitution +| Kcase of term_branch list * substitution +| Keps of vsymbol +| Kquant of quant * vsymbol list * trigger +| Kbinop of binop +| Knot +| Keval of term * substitution + +type config = { + value_stack : value list; + cont_stack : (cont * term) list; + (* second term is the original term, for attribute and loc copy *) +} + + +(* This global variable is used to approximate a count of the elementary + simplifications that are done during normalization. This is used for + transformation step. *) +let rec_step_limit = ref 0 + +exception NoMatch of (term * term * term option) option +exception NoMatchpat of (pattern * pattern) option + +let rec pattern_renaming (bound_vars, mt) p1 p2 = + match p1.pat_node, p2.pat_node with + | Pwild, Pwild -> + (bound_vars, mt) + | Pvar v1, Pvar v2 -> + begin + try + let mt = Ty.ty_match mt v1.vs_ty v2.vs_ty in + let bound_vars = Mvs.add v2 v1 bound_vars in + (bound_vars, mt) + with + | Ty.TypeMismatch _ -> + raise (NoMatchpat (Some (p1, p2))) + end + | Papp (ls1, tl1), Papp (ls2, tl2) when ls_equal ls1 ls2 -> + List.fold_left2 pattern_renaming (bound_vars, mt) tl1 tl2 + | Por (p1a, p1b), Por (p2a, p2b) -> + let (bound_vars, mt) = + pattern_renaming (bound_vars, mt) p1a p2a in + pattern_renaming (bound_vars, mt) p1b p2b + | Pas (p1, v1), Pas (p2, v2) -> + begin + try + let mt = Ty.ty_match mt v1.vs_ty v2.vs_ty in + let bound_vars = Mvs.add v2 v1 bound_vars in + pattern_renaming (bound_vars, mt) p1 p2 + with + | Ty.TypeMismatch _ -> + raise (NoMatchpat (Some (p1, p2))) + end + | _ -> raise (NoMatchpat (Some (p1, p2))) + +let first_order_matching (vars : Svs.t) (largs : term list) + (args : term list) : Ty.ty Ty.Mtv.t * substitution = + let rec loop bound_vars ((mt,mv) as sigma) largs args = + match largs,args with + | [],[] -> sigma + | t1::r1, t2::r2 -> + begin +(* + Format.eprintf "matching terms %a and %a...@." + Pretty.print_term t1 Pretty.print_term t2; +*) + match t1.t_node with + | Tvar vs when Svs.mem vs vars -> + begin + try let t = Mvs.find vs mv in + if t_equal t t2 then + loop bound_vars sigma r1 r2 + else + raise (NoMatch (Some (t1, t2, Some t))) + with Not_found -> + try + let ts = Ty.ty_match mt vs.vs_ty (t_type t2) in + let fv2 = t_vars t2 in + if Mvs.is_empty (Mvs.set_inter bound_vars fv2) then + loop bound_vars (ts,Mvs.add vs t2 mv) r1 r2 + else + raise (NoMatch (Some (t1, t2, None))) + with Ty.TypeMismatch _ -> + raise (NoMatch (Some (t1, t2, None))) + end + | Tapp(ls1,args1) -> + begin + match t2.t_node with + | Tapp(ls2,args2) when ls_equal ls1 ls2 -> + let mt, mv = loop bound_vars sigma + (List.rev_append args1 r1) + (List.rev_append args2 r2) in + begin + try Ty.oty_match mt t1.t_ty t2.t_ty, mv + with Ty.TypeMismatch _ -> + raise (NoMatch (Some (t1, t2, None))) + end + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + | Tquant (q1, bv1) -> + begin + match t2.t_node with + | Tquant (q2, bv2) when q1 = q2 -> + let (vl1, _tl1, term1) = t_open_quant bv1 in + let (vl2, _tl2, term2) = t_open_quant bv2 in + let (bound_vars, term1, mt) = + try List.fold_left2 (fun (bound_vars, term1, mt) e1 e2 -> + let mt = Ty.ty_match mt e1.vs_ty e2.vs_ty in + let bound_vars = Mvs.add e2 e1 bound_vars in + (bound_vars,term1, mt)) (bound_vars,term1, mt) vl1 vl2 + with Invalid_argument _ | Ty.TypeMismatch _ -> + raise (NoMatch (Some (t1,t2,None))) + in + loop bound_vars (mt, mv) (term1 :: r1) (term2 :: r2) + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + | Tbinop (b1, t1_l, t1_r) -> + begin + match t2.t_node with + | Tbinop (b2, t2_l, t2_r) when b1 = b2 -> + loop bound_vars (mt, mv) (t1_l :: t1_r :: r1) (t2_l :: t2_r :: r2) + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + | Tif (t11, t12, t13) -> + begin + match t2.t_node with + | Tif (t21, t22, t23) -> + loop bound_vars (mt, mv) (t11 :: t12 :: t13 :: r1) + (t21 :: t22 :: t23 :: r2) + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + | Tlet (td1, tb1) -> + begin + match t2.t_node with + | Tlet (td2, tb2) -> + let (v1, tl1) = t_open_bound tb1 in + let (v2, tl2) = t_open_bound tb2 in + let mt = try Ty.ty_match mt v1.vs_ty v2.vs_ty + with Ty.TypeMismatch _ -> + raise (NoMatch (Some (t1,t2, None))) in + let bound_vars = Mvs.add v2 v1 bound_vars in + loop bound_vars (mt, mv) (td1 :: tl1 :: r1) (td2 :: tl2 :: r2) + | _ -> + raise (NoMatch (Some (t1, t2, None))) + end + | Tcase (ts1, tbl1) -> + begin + match t2.t_node with + | Tcase (ts2, tbl2) -> + begin try + let (bound_vars, mt, l1, l2) = + List.fold_left2 (fun (bound_vars, mt, l1, l2) tb1 tb2 -> + let (p1, tb1) = t_open_branch tb1 in + let (p2, tb2) = t_open_branch tb2 in + let bound_vars, mt = pattern_renaming (bound_vars, mt) p1 p2 in + (bound_vars,mt, tb1 :: l1, tb2 :: l2) + ) (bound_vars,mt, ts1 :: r1, ts2 :: r2) tbl1 tbl2 in + loop bound_vars (mt,mv) l1 l2 + with Invalid_argument _ -> + raise (NoMatch (Some (t1, t2, None))) + end + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + | Teps tb1 -> + begin + match t2.t_node with + | Teps tb2 -> + let (v1, td1) = t_open_bound tb1 in + let (v2, td2) = t_open_bound tb2 in + let mt = try Ty.ty_match mt v1.vs_ty v2.vs_ty + with Ty.TypeMismatch _ -> + raise (NoMatch (Some (t1,t2,None))) in + let bound_vars = Mvs.add v2 v1 bound_vars in + loop bound_vars (mt, mv) (td1 :: r1) (td2 :: r2) + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + + | Tnot t1 -> + begin + match t2.t_node with + | Tnot t2 -> + loop bound_vars sigma (t1 :: r1) (t2 :: r2) + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + | Tvar v1 -> + begin match t2.t_node with + | Tvar v2 -> + begin try + if vs_equal v1 (Mvs.find v2 bound_vars) then + loop bound_vars sigma r1 r2 + else + raise (NoMatch (Some (t1, t2, None))) + with + Not_found -> assert false + end + | _ -> raise (NoMatch (Some (t1, t2, None))) + end + | (Tconst _ | Ttrue | Tfalse) when t_equal t1 t2 -> + loop bound_vars sigma r1 r2 + | Tconst _ | Ttrue | Tfalse -> raise (NoMatch (Some (t1, t2, None))) + end + | _ -> raise (NoMatch None) + in + loop Mvs.empty (Ty.Mtv.empty, Mvs.empty) largs args + +exception Irreducible + +let one_step_reduce engine ls args = + try + let rules = Mls.find ls engine.rules in + let rec loop rules = + match rules with + | [] -> raise Irreducible + | (vars,largs,rhs)::rem -> + begin + try + let sigma = first_order_matching vars largs args in + sigma,rhs + with NoMatch _ -> + loop rem + end + in loop rules + with Not_found -> + raise Irreducible + +let rec matching ((mt,mv) as sigma) t p = + match p.pat_node with + | Pwild -> sigma + | Pvar v -> (mt,Mvs.add v t mv) + | Por(p1,p2) -> + begin + try matching sigma t p1 + with NoMatch _ -> matching sigma t p2 + end + | Pas(p,v) -> matching (mt,Mvs.add v t mv) t p + | Papp(ls1,pl) -> + match t.t_node with + | Tapp(ls2,tl) -> + if ls_equal ls1 ls2 then + List.fold_left2 matching sigma tl pl + else + if ls2.ls_constr > 0 then raise (NoMatch None) + else raise Undetermined + | _ -> raise Undetermined + + +let rec extract_first n acc l = + if n = 0 then acc,l else + match l with + | x :: r -> + extract_first (n-1) (x::acc) r + | [] -> assert false + +(** If [t1] matches [i1 < vs] or [i1 <= vs] and [t2] matches [vs < i2] or + [vs <= i2], then return the bounds [i1(+1), vs, i2(-1)]. The +1/-1 shift the + bounds for strict unequalities operators to obtain inclusive bounds. *) +let bounds ls_lt t1 t2 = + let lt order = function (* match [i < vs] or [vs < i], return [i, vs] *) + | {t_node= Tapp (ls, [t1; t2])} when ls_equal ls ls_lt -> ( + assert Ty.(ty_equal (t_type t1) ty_int && ty_equal (t_type t2) ty_int); + match order t1 t2 with + | {t_node= Tconst (Constant.ConstInt {Number.il_int= i})}, + {t_node= Tvar vs} -> + i, vs + | _ -> raise Exit ) + | _ -> raise Exit in + let eq order = function (* match [i = vs] or [vs = i], return [i, vs] *) + | {t_node= Tapp (ls, [t1; t2])} + when ls_equal ls Term.ps_equ + && Ty.ty_equal (t_type t1) Ty.ty_int + && Ty.ty_equal (t_type t2) Ty.ty_int -> ( + match order t1 t2 with + | {t_node= Tconst (Constant.ConstInt {Number.il_int= i})}, + {t_node= Tvar vs} when Ty.ty_equal vs.vs_ty Ty.ty_int -> + i, vs + | _ -> raise Exit ) + | _ -> raise Exit in + let le order = function (* match [i <= vs] or [vs <= i], return [i, vs] *) + | {t_node= Tbinop (Tor, t1, t2)} -> + let i, vs = lt order t1 in + let i', vs' = eq order t2 in + if not (BigInt.eq i i' && vs_equal vs vs') then raise Exit; + i, vs + | _ -> raise Exit in + let bound order shift t = (* match [i op vs] or [vs op i], op is [<] or [<=] *) + try le order t with Exit -> + let i, vs = lt order t in + shift i, vs in + let i1, vs = bound (fun t1 t2 -> t1, t2) BigInt.succ t1 in + let i2, vs' = bound (fun t1 t2 -> t2, t1) BigInt.pred t2 in + if not (vs_equal vs vs') then raise Exit; + i1, vs, i2 + +(** [reduce_bounded_quant ls_lt limit t sigma st rem] detects a bounded + quantification and reduces it to conjunction: + + forall v. a < v < b. t(v) + --> f(a+1) /\ ... /\ f(b-1) + + @param ls_lt lsymbol for [int.Int.(<)] + + @param limit Reduce if the difference between upper and lower bound is at + most [limit]. + + TODO: + - expand to bounded quantifications on range values + - compatiblity with reverse direction (forall i. b > i > a -> t) + - detect SPARK-style [forall i. if a < i /\ i < b then t else true] *) +let reduce_bounded_quant ls_lt limit t sigma st rem = + match st, rem with (* st = a < vs < b :: _; rem = -> :: forall vs :: _ *) + | Term {t_node= Tbinop (Tand, t1, t2)} :: st, + ((Kbinop Timplies, _) :: (Kquant (Tforall as quant, [vs], _), _) :: rem | + (Kbinop Tand, _) :: (Kquant (Texists as quant, [vs], _), _) :: rem) + when Ty.ty_equal vs.vs_ty Ty.ty_int -> + let t_empty, binop = match quant with + | Tforall -> t_true, Tand + | Texists -> t_false, Tor in + let a, vs', b = bounds ls_lt t1 t2 in + if not (vs_equal vs vs') then raise Exit; + if BigInt.(gt (sub b a) (of_int limit)) then raise Exit; + if BigInt.gt a b then (* empty range *) + {value_stack= Term t_empty :: st; cont_stack= rem} + else + let rec loop rem i = + if BigInt.lt i a then + rem + else + let t_i = t_const (Constant.int_const i) Ty.ty_int in + let rem = (* conjunction for all i > a *) + if BigInt.gt i a then (Kbinop binop, t_true) :: rem else rem in + let rem = (Keval (t, Mvs.add vs t_i sigma), t_true) :: rem in + loop rem (BigInt.pred i) in + {value_stack= st; cont_stack= loop rem b} + | _ -> raise Exit + +let rec reduce engine c = + match c.value_stack, c.cont_stack with + | _, [] -> assert false + | st, (Keval (t,sigma),orig) :: rem -> ( + let limit = engine.params.compute_max_quantifier_domain in + try reduce_bounded_quant engine.ls_lt limit t sigma st rem with Exit -> + reduce_eval engine st t ~orig sigma rem ) + | [], (Kif _, _) :: _ -> assert false + | v :: st, (Kif(t2,t3,sigma), orig) :: rem -> + begin + match v with + | Term { t_node = Ttrue } -> + incr(rec_step_limit); + { value_stack = st ; + cont_stack = (Keval(t2,sigma),t_attr_copy orig t2) :: rem } + | Term { t_node = Tfalse } -> + incr(rec_step_limit); + { value_stack = st ; + cont_stack = (Keval(t3,sigma),t_attr_copy orig t3) :: rem } + | Term t1 -> begin + match t1.t_node , t2.t_node , t3.t_node with + | Tapp (ls,[b0;{ t_node = Tapp (ls1,_) }]) , Tapp(ls2,_) , Tapp(ls3,_) + when ls_equal ls ps_equ && ls_equal ls1 fs_bool_true && + ls_equal ls2 fs_bool_true && ls_equal ls3 fs_bool_false -> + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig b0) :: st; + cont_stack = rem } + | _ -> + { value_stack = + Term + (t_attr_copy orig + (t_if t1 (t_subst sigma t2) (t_subst sigma t3))) :: st; + cont_stack = rem; + } + end + | (Int _ | Real _) -> assert false (* would be ill-typed *) + end + | [], (Klet _, _) :: _ -> assert false + | t1 :: st, (Klet(v,t2,sigma), orig) :: rem -> + incr(rec_step_limit); + let t1 = term_of_value t1 in + { value_stack = st; + cont_stack = + (Keval(t2, Mvs.add v t1 sigma), t_attr_copy orig t2) :: rem; + } + | [], (Kcase _, _) :: _ -> assert false + | (Int _ | Real _) :: _, (Kcase _, _) :: _ -> assert false + | (Term t1) :: st, (Kcase(tbl,sigma), orig) :: rem -> + reduce_match st t1 ~orig tbl sigma rem + | ([] | [_] | (Int _ | Real _) :: _ | Term _ :: (Int _ | Real _) :: _), + (Kbinop _, _) :: _ -> assert false + | (Term t1) :: (Term t2) :: st, (Kbinop op, orig) :: rem -> + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig (t_binary_simp op t2 t1)) :: st; + cont_stack = rem; + } + | [], (Knot,_) :: _ -> assert false + | (Int _ | Real _) :: _ , (Knot,_) :: _ -> assert false + | (Term t) :: st, (Knot, orig) :: rem -> + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig (t_not_simp t)) :: st; + cont_stack = rem; + } + | st, (Kapp(ls,ty), orig) :: rem -> + reduce_app engine st ~orig ls ty rem + | [], (Keps _, _) :: _ -> assert false + | (Int _ | Real _) :: _ , (Keps _, _) :: _ -> assert false + | Term t :: st, (Keps v, orig) :: rem -> + { value_stack = Term (t_attr_copy orig (t_eps_close v t)) :: st; + cont_stack = rem; + } + | [], (Kquant _, _) :: _ -> assert false + | (Int _ | Real _) :: _, (Kquant _, _) :: _ -> assert false + | Term t :: st, (Kquant(q,vl,tr), orig) :: rem -> + { value_stack = Term (t_attr_copy orig (t_quant_close_simp q vl tr t)) :: st; + cont_stack = rem; + } + +and reduce_match st u ~orig tbl sigma cont = + let rec iter tbl = + match tbl with + | [] -> assert false (* pattern matching not exhaustive *) + | b::rem -> + let p,t = t_open_branch b in + try + let (mt',mv') = matching (Ty.Mtv.empty,sigma) u p in +(* + Format.eprintf "Pattern-matching succeeded:@\nmt' = @["; + Ty.Mtv.iter + (fun tv ty -> Format.eprintf "%a -> %a," + Pretty.print_tv tv Pretty.print_ty ty) + mt'; + Format.eprintf "@]@\n"; + Format.eprintf "mv' = @["; + Mvs.iter + (fun v t -> Format.eprintf "%a -> %a," + Pretty.print_vs v Pretty.print_term t) + mv'; + Format.eprintf "@]@."; + Format.eprintf "branch before inst: %a@." Pretty.print_term t; +*) + let mv'',t = t_subst_types mt' mv' t in +(* + Format.eprintf "branch after types inst: %a@." Pretty.print_term t; + Format.eprintf "mv'' = @["; + Mvs.iter + (fun v t -> Format.eprintf "%a -> %a," + Pretty.print_vs v Pretty.print_term t) + mv''; + Format.eprintf "@]@."; +*) + incr(rec_step_limit); + { value_stack = st; + cont_stack = (Keval(t,mv''), t_attr_copy orig t) :: cont; + } + with NoMatch _ -> iter rem + in + try iter tbl with Undetermined -> + let dmy = t_var (create_vsymbol (Ident.id_fresh "__dmy") (t_type u)) in + let tbls = match t_subst sigma (t_case dmy tbl) with + | { t_node = Tcase (_,tbls) } -> tbls + | _ -> assert false + in + { value_stack = + Term (t_attr_copy orig (t_case u tbls)) :: st; + cont_stack = cont; + } + + +and reduce_eval eng st t ~orig sigma rem = + let orig = t_attr_copy orig t in + match t.t_node with + | Tvar v -> + begin + match Ident.Mid.find v.vs_name eng.known_map with + | Decl.{d_node = Dlogic dl} -> + incr rec_step_limit ; + let _, ls_defn = List.find (fun (ls, _) -> String.equal ls.ls_name.Ident.id_string v.vs_name.Ident.id_string) dl in + let vs, t = Decl.open_ls_defn ls_defn in + let aux vs t = + (* Create ε fc. λ vs. fc @ vs = t to make the value from + known_map compatible to reduce_func_app *) + let ty = Opt.get t.t_ty in + let app_ty = Ty.ty_func vs.vs_ty ty in + let fc = create_vsymbol (Ident.id_fresh "fc") app_ty in + t_eps + (t_close_bound fc + (t_quant Tforall + (t_close_quant [vs] [] + (t_app ps_equ + [t_app fs_func_app [t_var fc; t_var vs] (Some ty); t] + None)))) in + let t = List.fold_right aux vs t in + {value_stack = Term t :: st; cont_stack = rem} + | _ -> assert false + | exception Not_found -> + match Mvs.find v sigma with + | t -> + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig t) :: st ; + cont_stack = rem; + } + | exception Not_found -> + (* this may happen, e.g when computing below a quantified formula *) + (* Format.eprintf "Tvar not found: %a@." Pretty.print_vs v; + assert false *) + { value_stack = Term orig :: st ; + cont_stack = rem; + } + end + | Tif(t1,t2,t3) -> + { value_stack = st; + cont_stack = (Keval(t1,sigma),t1) :: (Kif(t2,t3,sigma),orig) :: rem; + } + | Tlet(t1,tb) -> + let v,t2 = t_open_bound tb in + { value_stack = st ; + cont_stack = (Keval(t1,sigma),t1) :: (Klet(v,t2,sigma),orig) :: rem } + | Tcase(t1,tbl) -> + { value_stack = st; + cont_stack = (Keval(t1,sigma),t1) :: (Kcase(tbl,sigma),orig) :: rem } + | Tbinop(op,t1,t2) -> + { value_stack = st; + cont_stack = + (Keval(t1,sigma),t1) :: + (Keval(t2,sigma),t2) :: (Kbinop op, orig) :: rem; + } + | Tnot t1 -> + { value_stack = st; + cont_stack = (Keval(t1,sigma),t1) :: (Knot,orig) :: rem; + } + | Teps tb -> + let v,t1 = t_open_bound tb in + { value_stack = st ; + cont_stack = (Keval(t1,sigma),t1) :: (Keps v,orig) :: rem; + } + | Tquant(q,tq) -> + let vl,tr,t1 = t_open_quant tq in + { value_stack = st; + cont_stack = (Keval(t1,sigma),t1) :: (Kquant(q,vl,tr),orig) :: rem; + } + | Tapp(ls,tl) -> + let args = List.rev_map (fun t -> (Keval(t,sigma),t)) tl in + { value_stack = st; + cont_stack = List.rev_append args ((Kapp(ls,t.t_ty),orig) :: rem); + } + | Ttrue | Tfalse | Tconst _ -> + { value_stack = Term orig :: st; + cont_stack = rem; + } + +and reduce_app engine st ls ~orig ty rem_cont = + if ls_equal ls ps_equ then + match st with + | t2 :: t1 :: rem_st -> + begin + try + reduce_equ ~orig rem_st t1 t2 rem_cont + with Undetermined -> + reduce_app_no_equ engine st ls ~orig ty rem_cont + end + | _ -> assert false + else + if ls_equal ls fs_func_app then + match st with + | t2 :: t1 :: rem_st -> + begin + try + reduce_func_app ~orig ty rem_st t1 t2 rem_cont + with Undetermined -> + reduce_app_no_equ engine st ls ~orig ty rem_cont + end + | _ -> assert false + else + reduce_app_no_equ engine st ls ~orig ty rem_cont + +and reduce_func_app ~orig _ty rem_st t1 t2 rem_cont = + (* attempt to decompile t1 under the form + (epsilon fc. forall x. fc @ x = body) + that is equivalent to \x.body *) + match t1 with + | Term { t_node = Teps tb } -> + let fc,t = Term.t_open_bound tb in + begin match t.t_node with + | Tquant(Tforall,tq) -> + let vl,trig,t = t_open_quant tq in + let process lhs body equ elim = + let rvl = List.rev vl in + let rec remove_var lhs rvh rvt = match lhs.t_node with + | Tapp (ls2,[lhs1;{t_node = Tvar v1} as arg]) + when ls_equal ls2 fs_func_app && vs_equal v1 rvh -> + begin + match rvt , lhs1 with + | rvh::rvt , _ -> + let lhs1 , fc2 = remove_var lhs1 rvh rvt in + let lhs2 = t_app ls2 [lhs1;arg] lhs.t_ty in + t_attr_copy lhs lhs2 , fc2 + | [] , { t_node = Tvar fc1 } when vs_equal fc1 fc -> + let fcn = fc.vs_name in + let fc2 = Ident.id_derive fcn.Ident.id_string fcn in + let fc2 = create_vsymbol fc2 (t_type lhs) in + t_attr_copy lhs (t_var fc2) , fc2 + | _ -> raise Undetermined + end + | _ -> raise Undetermined + in + begin + match rvl with + | rvh :: rvt -> let lhs , fc2 = remove_var lhs rvh rvt in + let (vh,vl) = match vl with + | [] -> assert false + | vh::vl -> (vh,vl) + in + let t2 = term_of_value t2 in + begin + match vl with + | [] -> elim body vh t2 + | _ -> + let eq = equ lhs body in + let tq = t_quant Tforall (t_close_quant vl trig eq) in + let body = t_attr_copy t (t_eps_close fc2 tq) in + { value_stack = rem_st; + cont_stack = + (Keval(body,Mvs.add vh t2 Mvs.empty), + t_attr_copy orig body) :: rem_cont; + } + end + | _ -> raise Undetermined + end + in + begin + match t.t_node with + | Tapp (ls1,[lhs;body]) when ls_equal ls1 ps_equ -> + let equ lhs body = t_attr_copy t (t_app ps_equ [lhs;body] None) in + let elim body vh t2 = { + value_stack = rem_st; + cont_stack = + (Keval(body,Mvs.add vh t2 Mvs.empty), + t_attr_copy orig body) :: rem_cont; + } in + process lhs body equ elim + | Tbinop (Tiff, + ({t_node=Tapp (ls1,[lhs;tr])} as teq), + body) + when ls_equal ls1 ps_equ && t_equal tr t_bool_true -> + let equ lhs body = + let lhs = t_attr_copy teq (t_app ps_equ [lhs;tr] None) in + t_attr_copy t (t_binary Tiff lhs body) in + let elim body vh t2 = + let body = t_if body t_bool_true t_bool_false in + { value_stack = rem_st; + cont_stack = + (Keval(body,Mvs.add vh t2 Mvs.empty), + t_attr_copy orig body) :: rem_cont } in + process lhs body equ elim + | _ -> raise Undetermined + end + | _ -> raise Undetermined + end + | _ -> raise Undetermined + +and reduce_app_no_equ engine st ls ~orig ty rem_cont = + let arity = List.length ls.ls_args in + let args,rem_st = extract_first arity [] st in + try + let f = Hls.find builtins ls in + let v = f ls args ty in + { value_stack = (v_attr_copy orig v) :: rem_st; + cont_stack = rem_cont; + } + with Not_found | Undetermined -> + let args = List.map term_of_value args in + match Ident.Mid.find ls.ls_name engine.known_map with + | exception Not_found -> + Format.eprintf "Reduction engine, ident not found: %s@." + ls.ls_name.Ident.id_string ; + { value_stack = Term (t_attr_copy orig (t_app ls args ty)) :: rem_st; + cont_stack = rem_cont; + } + | d -> + let rewrite () = + (* try a rewrite rule *) + try +(* + Format.eprintf "try a rewrite rule on %a@." Pretty.print_ls ls; +*) + let (mt,mv),rhs = one_step_reduce engine ls args in +(* + Format.eprintf "rhs = %a@." Pretty.print_term rhs; + Format.eprintf "sigma = "; + Mvs.iter + (fun v t -> Format.eprintf "%a -> %a," + Pretty.print_vs v Pretty.print_term t) + (snd sigma); + Format.eprintf "@."; + Format.eprintf "try a type match: %a and %a@." + (Pp.print_option Pretty.print_ty) ty + (Pp.print_option Pretty.print_ty) rhs.t_ty; +*) +(* + let type_subst = Ty.oty_match Ty.Mtv.empty rhs.t_ty ty in + Format.eprintf "subst of rhs: "; + Ty.Mtv.iter + (fun tv ty -> Format.eprintf "%a -> %a," + Pretty.print_tv tv Pretty.print_ty ty) + type_subst; + Format.eprintf "@."; + let rhs = t_ty_subst type_subst Mvs.empty rhs in + let sigma = + Mvs.map (t_ty_subst type_subst Mvs.empty) sigma + in + Format.eprintf "rhs = %a@." Pretty.print_term rhs; + Format.eprintf "sigma = "; + Mvs.iter + (fun v t -> Format.eprintf "%a -> %a," + Pretty.print_vs v Pretty.print_term t) + sigma; + Format.eprintf "@."; +*) + let mv,rhs = t_subst_types mt mv rhs in + incr(rec_step_limit); + { value_stack = rem_st; + cont_stack = (Keval(rhs,mv),orig) :: rem_cont; + } + with Irreducible -> + { value_stack = Term (t_attr_copy orig (t_app ls args ty)) :: rem_st; + cont_stack = rem_cont; + } + in + match d.Decl.d_node with + | Decl.Dtype _ | Decl.Dprop _ -> assert false + | Decl.Dlogic dl -> + (* regular definition *) + let d = List.assq ls dl in + if engine.params.compute_defs || + Term.Sls.mem ls engine.params.compute_def_set + then begin + let vl,e = Decl.open_ls_defn d in + let add (mt,mv) x y = + Ty.ty_match mt x.vs_ty (t_type y), Mvs.add x y mv + in + let (mt,mv) = List.fold_left2 add (Ty.Mtv.empty, Mvs.empty) vl args in + let mt = Ty.oty_match mt e.t_ty ty in + let mv,e = t_subst_types mt mv e in + { value_stack = rem_st; + cont_stack = (Keval(e,mv),orig) :: rem_cont; + } + end else rewrite () + | Decl.Dparam _ | Decl.Dind _ -> + rewrite () + | Decl.Ddata dl -> + (* constructor or projection *) + begin try match args with + | [ { t_node = Tapp(ls1,tl1) } ] -> + (* if ls is a projection and ls1 is a constructor, + we should compute that projection *) + let rec iter dl = + match dl with + | [] -> raise Exit + | (_,csl) :: rem -> + let rec iter2 csl = + match csl with + | [] -> iter rem + | (cs,prs) :: rem2 -> + if ls_equal cs ls1 + then + (* we found the right constructor *) + let rec iter3 prs tl1 = + match prs,tl1 with + | (Some pr)::prs, t::tl1 -> + if ls_equal ls pr + then (* projection found! *) + { value_stack = + (Term (t_attr_copy orig t)) :: rem_st; + cont_stack = rem_cont; + } + else + iter3 prs tl1 + | None::prs, _::tl1 -> + iter3 prs tl1 + | _ -> raise Exit + in iter3 prs tl1 + else iter2 rem2 + in iter2 csl + in iter dl + | _ -> raise Exit + with Exit -> rewrite () end + + +and reduce_equ (* engine *) ~orig st v1 v2 cont = +(* + try +*) + match v1,v2 with + | Int n1, Int n2 -> + let b = to_bool (BigInt.eq n1 n2) in + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig b) :: st; + cont_stack = cont; + } + | Real r1, Real r2 -> + let b = to_bool (Number.compare_real r1 r2 = 0) in + incr rec_step_limit; + { value_stack = Term (t_attr_copy orig b) :: st; + cont_stack = cont; + } + | Int n, Term {t_node = Tconst c} | Term {t_node = Tconst c}, Int n -> + begin + try + let n' = big_int_of_const c in + let b = to_bool (BigInt.eq n n') in + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig b) :: st; + cont_stack = cont; + } + with NotNum -> raise Undetermined + end + | Real r, Term {t_node = Tconst c} | Term {t_node = Tconst c}, Real r -> + begin + try + let r' = real_of_const c in + let b = to_bool (Number.compare_real r r' = 0) in + incr rec_step_limit; + { value_stack = Term (t_attr_copy orig b) :: st; + cont_stack = cont; + } + with NotNum -> raise Undetermined + end + | Real _, Term _ | Term _, Real _ -> raise Undetermined + | Int _, Term _ | Term _, Int _ -> raise Undetermined + | Int _, Real _ | Real _, Int _ -> assert false + | Term t1, Term t2 -> reduce_term_equ ~orig st t1 t2 cont +(* + with Undetermined -> + { value_stack = Term (t_equ (term_of_value v1) (term_of_value v2)) :: st; + cont_stack = cont; + } +*) + +and reduce_term_equ ~orig st t1 t2 cont = + if t_equal t1 t2 then + let () = incr(rec_step_limit) in + { value_stack = Term (t_attr_copy orig t_true) :: st; + cont_stack = cont; + } + else + match (t1.t_node,t2.t_node) with + | Tconst c1, Tconst c2 -> + begin + match c1,c2 with + | Constant.ConstInt i1, Constant.ConstInt i2 -> + let b = + BigInt.eq i1.Number.il_int i2.Number.il_int + in + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig (to_bool b)) :: st; + cont_stack = cont; + } + | _ -> raise Undetermined + end + | Tapp(ls1,tl1), Tapp(ls2,tl2) when ls1.ls_constr > 0 && ls2.ls_constr > 0 -> + if ls_equal ls1 ls2 then + let rec aux sigma t tyl l1 l2 = + match tyl,l1,l2 with + | [],[],[] -> sigma,t + | ty::tyl, t1::tl1, t2::tl2 -> + let v1 = create_vsymbol (Ident.id_fresh "") ty in + let v2 = create_vsymbol (Ident.id_fresh "") ty in + aux + (Mvs.add v1 t1 (Mvs.add v2 t2 sigma)) + (t_and_simp (t_equ (t_var v1) (t_var v2)) t) + tyl tl1 tl2 + | _ -> assert false + in + let sigma,t = + aux Mvs.empty t_true ls1.ls_args tl1 tl2 + in + let () = incr(rec_step_limit) in + { value_stack = st; + cont_stack = (Keval(t,sigma),orig) :: cont; + } + else + { value_stack = Term (t_attr_copy orig t_false) :: st; + cont_stack = cont; + } + | Tif (b,{ t_node = Tapp(ls1,_) },{ t_node = Tapp(ls2,_) }) , Tapp(ls3,_) + when ls_equal ls3 fs_bool_true && ls_equal ls1 fs_bool_true && + ls_equal ls2 fs_bool_false -> + incr(rec_step_limit); + { value_stack = Term (t_attr_copy orig b) :: st; + cont_stack = cont } + | _ -> raise Undetermined + + + +let rec reconstruct c = + match c.value_stack, c.cont_stack with + | [Term t], [] -> t + | _, [] -> assert false + | _, (k,orig) :: rem -> + let t, st = + match c.value_stack, k with + | st, Keval (t,sigma) -> (t_subst sigma t), st + | [], Kif _ -> assert false + | v :: st, Kif(t2,t3,sigma) -> + (t_if (term_of_value v) (t_subst sigma t2) (t_subst sigma t3)), st + | [], Klet _ -> assert false + | t1 :: st, Klet(v,t2,sigma) -> + (t_let_close v (term_of_value t1) (t_subst sigma t2)), st + | [], Kcase _ -> assert false + | v :: st, Kcase(tbl,sigma) -> + (t_subst sigma (t_case (term_of_value v) tbl)), st + | ([] | [_]), Kbinop _ -> assert false + | t1 :: t2 :: st, Kbinop op -> + (t_binary_simp op (term_of_value t2) (term_of_value t1)), st + | [], Knot -> assert false + | t :: st, Knot -> (t_not (term_of_value t)), st + | st, Kapp(ls,ty) -> + let args,rem_st = extract_first (List.length ls.ls_args) [] st in + let args = List.map term_of_value args in + (t_app ls args ty), rem_st + | [], Keps _ -> assert false + | t :: st, Keps v -> (t_eps_close v (term_of_value t)), st + | [], Kquant _ -> assert false + | t :: st, Kquant(q,vl,tr) -> + (t_quant_close_simp q vl tr (term_of_value t)), st + in + reconstruct { + value_stack = (Term (t_attr_copy orig t)) :: st; + cont_stack = rem; + } + + +(** iterated reductions *) + +let normalize ?step_limit ~limit engine sigma t0 = + rec_step_limit := 0; + let rec many_steps c n = + match c.value_stack, c.cont_stack with + | [Term t], [] -> t + | _, [] -> assert false + | _ -> + if n = limit then + begin + let t1 = reconstruct c in + (* Loc.warning "reduction of term %a takes more than %d steps, aborted at %a.@." + * Pretty.print_term t0 limit Pretty.print_term t1; *) + t1 + end + else begin + match step_limit with + | None -> + let c = reduce engine c in + many_steps c (n+1) + | Some step_limit -> + if !rec_step_limit >= step_limit then + reconstruct c + else + let c = reduce engine c in + many_steps c (n+1) + end + in + let c = { value_stack = []; + cont_stack = [Keval(t0,sigma),t0] ; + } + in + many_steps c 0 + +(* the rewrite engine *) + +let create p env km = + if p.compute_builtin + then get_builtins env + else Hls.clear builtins; + let th = Env.read_theory env ["int"] "Int" in + let ls_lt = Theory.ns_find_ls th.Theory.th_export [Ident.op_infix "<"] in + { known_map = km ; + rules = Mls.empty; + params = p; + ls_lt; + } + +exception NotARewriteRule of string + +let extract_rule _km t = +(* + let check_ls ls = + try let _ = Hls.find builtins ls in + raise (NotARewriteRule "root of lhs of rule must not be a built-in symbol") + with Not_found -> + let d = Ident.Mid.find ls.ls_name km in + match d.Decl.d_node with + | Decl.Dtype _ | Decl.Dprop _ -> assert false + | Decl.Dlogic _ -> + raise (NotARewriteRule "root of lhs of rule must not be defined symbol") + | Decl.Ddata _ -> + raise (NotARewriteRule "root of lhs of rule must not be a constructor nor a projection") + | Decl.Dparam _ | Decl.Dind _ -> () + in +*) + + let check_vars acc t1 t2 = + (* check that quantified variables all appear in the lefthand side + (quantified variables not appearing could be removed and those appearing + on right hand side cannot be guessed during rewriting). *) + let vars_lhs = t_vars t1 in + if Svs.exists (fun vs -> not (Mvs.mem vs vars_lhs)) acc + then raise (NotARewriteRule "lhs should contain all variables"); + (* check the same with type variables *) + if not + (Ty.Stv.subset + (t_ty_freevars Ty.Stv.empty t2) (t_ty_freevars Ty.Stv.empty t2)) + then raise (NotARewriteRule "lhs should contain all type variables") + + in + let rec aux acc t = + match t.t_node with + | Tquant(Tforall,q) -> + let vs,_,t = t_open_quant q in + aux (List.fold_left (fun acc v -> Svs.add v acc) acc vs) t + | Tbinop(Tiff,t1,t2) -> + begin + match t1.t_node with + | Tapp(ls,args) -> + (* check_ls ls; *) + check_vars acc t1 t2; + acc,ls,args,t2 + | _ -> raise + (NotARewriteRule "lhs of <-> should be a predicate symbol") + end + | Tapp(ls,[t1;t2]) when ls == ps_equ -> + begin + match t1.t_node with + | Tapp(ls,args) -> + (* check_ls ls; *) + check_vars acc t1 t2; + acc,ls,args,t2 + | _ -> raise + (NotARewriteRule "lhs of = should be a function symbol") + end + | _ -> raise + (NotARewriteRule "rule should be of the form forall ... t1 = t2 or f1 <-> f2") + in + aux Svs.empty t + + +let add_rule t e = + let vars,ls,args,r = extract_rule e.known_map t in + let rules = + try Mls.find ls e.rules + with Not_found -> [] + in + {e with rules = + Mls.add ls ((vars,args,r)::rules) e.rules} diff --git a/src/reduction_engine.mli b/src/reduction_engine.mli new file mode 100644 index 0000000..282ee55 --- /dev/null +++ b/src/reduction_engine.mli @@ -0,0 +1,133 @@ +(********************************************************************) +(* *) +(* The Why3 Verification Platform / The Why3 Development Team *) +(* Copyright 2010-2022 -- Inria - CNRS - Paris-Saclay University *) +(* *) +(* This software is distributed under the terms of the GNU Lesser *) +(* General Public License version 2.1, with the special exception *) +(* on linking described in file LICENSE. *) +(* *) +(********************************************************************) + + +(** A reduction engine for Why3 terms *) + +(* +terms are normalized with respect to + +1) built-in computation rules + + a) on propositional connectives, e.g. + + f /\ true -> f + + b) on integers, e.g. + + 35 + 7 -> 42 + + c) on projections of pairs and of other ADTs, e.g + + fst (x,y) -> x + + cdr (Cons x y) -> y + + d) on defined function symbols, e.g. + + function sqr (x:int) = x * x + + sqr 4 -> 4 * 4 -> 16 + + sqr x -> x * x + + e) (TODO?) on booleans, e.g. + + True xor False -> True + + f) (TODO?) on reals, e.g. + + 1.0 + 2.5 -> 3.5 + +2) axioms declared as rewrite rules, thanks to the "rewrite" metas, e.g. if + + function dot : t -> t -> t + axiom assoc: forall x y z, dot (dot x y) z = dot x (dot y z) + meta "rewrite" assoc + + then + + dot (dot a b) (dot c d) -> dot a (dot b (dot c d)) + + axioms used as rewrite rules must be either of the form + + forall ... t1 = t2 + + or + + forall ... f1 <-> f2 + + where the root symbol of t1 (resp. f1) is a non-interpreted function + symbol (resp. non-interpreted predicate symbol) + + rewriting is done from left to right + + +*) +open Why3 + +type engine +(** abstract type for reduction engines *) + +type params = { + compute_defs : bool; + compute_builtin : bool; + compute_def_set : Term.Sls.t; + compute_max_quantifier_domain : int; +} +(** Configuration of the engine. + . [compute_defs]: if set to true, automatically compute symbols using + known definitions. Otherwise, only symbols in [compute_def_set] + will be computed. + . [compute_builtin]: if set to true, compute builtin functions. + . [compute_max_quantifier_domain]: maximum domain size for the reduction of + bounded quantifications *) + +val create : params -> Env.env -> Decl.decl Ident.Mid.t -> engine +(** [create env known_map] creates a reduction engine with + . builtins theories (int.Int, etc.) extracted from [env] + . known declarations from [known_map] + . empty set of rewrite rules +*) + +exception NotARewriteRule of string + +val add_rule : Term.term -> engine -> engine +(** [add_rule t e] turns [t] into a new rewrite rule and returns the + new engine. + + raise NotARewriteRule if [t] cannot be seen as a rewrite rule + according to the general rules given above. +*) + + +val normalize : ?step_limit:int -> limit:int -> engine -> Term.term Term.Mvs.t -> Term.term -> Term.term +(** [normalize e sigma t] normalizes the term [t] with respect to the engine + [e] with an initial variable environment [sigma]. + + parameter [limit] provides a maximum number of steps for execution. + When limit is reached, the partially reduced term is returned. + parameter [step_limit] provides a maximum number of steps on reductions + that would change the term even after reconstruction. +*) + + +open Term + +exception NoMatch of (term * term * term option) option +(** [NoMatch (t1, t2, t3)] Cannot match [t1] with [t2]. If [t3] exists then [t1] + is already matched with [t3]. *) + +exception NoMatchpat of (pattern * pattern) option + +type substitution = term Mvs.t + +val first_order_matching: Svs.t -> term list -> term list -> Ty.ty Ty.Mtv.t * substitution diff --git a/src/verification.ml b/src/verification.ml index 8fe43e4..1a7162e 100644 --- a/src/verification.ml +++ b/src/verification.ml @@ -274,10 +274,25 @@ let call_prover ?dataset ~limit config env prover config_prover driver task = let id = Task.task_goal task in { id; prover_answer; additional_info } -let verify ?(debug = false) ?format ~loadpath ?memlimit ?timelimit ?dataset - prover ?prover_altern file = +let open_file ?(debug = false) ?format ~loadpath file = if debug then Debug.set_flag (Debug.lookup_flag "call_prover"); let env, config = create_env ~debug loadpath in + let mstr_theory = + match file with + | File.Stdin -> + Env.(read_channel ?format base_language env "stdin" Caml.stdin) + | File file -> + let mlw_files, _ = Env.(read_file ?format base_language env file) in + mlw_files + | JSON jin -> + let th = JSON.theory_of_input env jin in + Wstdlib.Mstr.singleton th.th_name.id_string th + in + (env, config, mstr_theory) + +let verify ?debug ?format ~loadpath ?memlimit ?timelimit ?dataset prover + ?prover_altern file = + let env, config, mstr_theory = open_file ?debug ?format ~loadpath file in let main = Whyconf.get_main config in let limit = let memlimit = Option.value memlimit ~default:(Whyconf.memlimit main) in @@ -292,17 +307,6 @@ let verify ?(debug = false) ?format ~loadpath ?memlimit ?timelimit ?dataset Call_provers.limit_mem = memlimit; } in - let mstr_theory = - match file with - | File.Stdin -> - Env.(read_channel ?format base_language env "stdin" Caml.stdin) - | File file -> - let mlw_files, _ = Env.(read_file ?format base_language env file) in - mlw_files - | JSON jin -> - let th = JSON.theory_of_input env jin in - Wstdlib.Mstr.singleton th.th_name.id_string th - in let config_prover = let altern = let default = diff --git a/src/verification.mli b/src/verification.mli index 8dc3939..0dabf96 100644 --- a/src/verification.mli +++ b/src/verification.mli @@ -23,7 +23,10 @@ open Why3 module File : sig - type t + type t = private + | Stdin + | File of string + | JSON of JSON.input val of_json_input : JSON.input -> (t, string) result val of_string : string -> (t, string) result @@ -71,3 +74,15 @@ val verify : @return for each theory, an [answer] for each goal of the theory, respecting the order of appearance. *) + +val open_file : + ?debug:bool -> + ?format:string -> + loadpath:string list -> + File.t -> + Env.env * Whyconf.config * Theory.theory Wstdlib.Mstr.t +(** [open_file ?debug ?format ~loadpath file] just opens the given file. + + @param debug when set, enables debug information. + @param format is the [file] format. + @param loadpath is the additional loadpath. *) diff --git a/tests/interpretation.t b/tests/interpretation.t new file mode 100644 index 0000000..0b75c82 --- /dev/null +++ b/tests/interpretation.t @@ -0,0 +1,13 @@ +Test verify + $ caisar interpret -L . --format whyml - 2>&1 <<EOF | ./filter_tmpdir.sh + > theory T + > use int.Int + > + > goal G1: 1+1=2 + > + > goal G2: 1+1=3 + > + > end + > EOF + G1 : true + G2 : false -- GitLab