From 75e0727fc8c4e64558763e286eb0526fa68223f4 Mon Sep 17 00:00:00 2001
From: Thibaut Benjamin <thibaut.benjamin@gmail.com>
Date: Fri, 21 Jan 2022 14:55:17 +0100
Subject: [PATCH] [e-acsl] add interval inference for quantifiers

---
 src/plugins/e-acsl/src/analyses/interval.ml   |  82 ++++++++++-
 src/plugins/e-acsl/src/analyses/typing.ml     | 130 +++++-------------
 src/plugins/e-acsl/src/analyses/typing.mli    |   5 +
 .../e-acsl/src/code_generator/loops.ml        |   6 +-
 .../e-acsl/src/code_generator/quantif.ml      |  34 +++--
 5 files changed, 146 insertions(+), 111 deletions(-)

diff --git a/src/plugins/e-acsl/src/analyses/interval.ml b/src/plugins/e-acsl/src/analyses/interval.ml
index 12a48c77bd9..37c29cecc80 100644
--- a/src/plugins/e-acsl/src/analyses/interval.ml
+++ b/src/plugins/e-acsl/src/analyses/interval.ml
@@ -862,7 +862,65 @@ and infer_term_offset ~logic_env t =
     ignore (infer ~logic_env t);
     infer_term_offset ~logic_env toff
 
-let rec infer_predicate ~logic_env p =
+(* [type_bound_variables] infers an interval associated with each of
+   the provided bounds of a quantified variable, and provides a term
+   accordingly. It could happen that the bounds provided for a quantifier
+   [lv] are bigger than its type. [type_bound_variables] handles such cases
+   and provides smaller bounds whenever possible.
+   Let B be the inferred interval and R the range of [lv.typ]
+   - Case 1: B \subseteq R
+     Example: [\forall unsigned char c; 4 <= c <= 100 ==> 0 <= c <= 255]
+     Return: B
+   - Case 2: B \not\subseteq R and the bounds of B are inferred exactly
+     Example: [\forall unsigned char c; 4 <= c <= 300 ==> 0 <= c <= 255]
+     Return: B \intersect R
+   - Case 3: B \not\subseteq R and the bounds of B are NOT inferred exactly
+     Example: [\let m = n > 0 ? 4 : 341; \forall char u; 1 < u < m ==> u > 0]
+     Return: R with a guard guaranteeing that [lv] does not overflow *)
+let rec infer_bound_variable ~loc ~logic_env (t1, lv, t2) =
+  let get_res = Error.map (fun x -> x) in
+  let i1 = get_res (infer ~logic_env t1) in
+  let i2 = get_res (infer ~logic_env t2) in
+  let i = widen (join i1 i2) in
+  let t1, t2, i =
+    match lv.lv_type with
+    | Ltype _ | Lvar _ | Lreal | Larrow _ ->
+      Error.not_yet "quantification over non-integer type"
+    | Linteger -> t1, t2, i
+    | Ctype ty ->
+      let ity = extended_interv_of_typ ty in
+      if is_included i ity then
+        (* case 1 *)
+        t1, t2, i
+      else if is_singleton_int i1 &&
+              is_singleton_int i2 then
+        begin
+          (* case 2 *)
+          let i = meet i ity in
+          (* We can now update the bounds in the preprocessed form
+             that come from the meet of the two intervals *)
+          let min, max = Misc.finite_min_and_max (extract_ival i) in
+          let t1 = Logic_const.tint ~loc min in
+          let t2 = Logic_const.tint ~loc max in
+          t1, t2, i
+        end else
+        (* case 3 *)
+        let min, max = Misc.finite_min_and_max (extract_ival ity) in
+        let guard_lower = Logic_const.tint ~loc min in
+        let guard_upper = Logic_const.tint ~loc max in
+        let lv_term = Logic_const.tvar ~loc lv in
+        let guard_lower = Logic_const.prel ~loc (Rle, guard_lower, lv_term) in
+        let guard_upper = Logic_const.prel ~loc (Rlt, lv_term, guard_upper) in
+        let guard = Logic_const.pand ~loc (guard_lower, guard_upper) in
+        ignore (infer_predicate ~logic_env guard);
+        Bound_variables.add_guard_for_small_type lv guard;
+        t1, t2, i
+  in
+  ignore (infer ~logic_env t1);
+  ignore (infer ~logic_env t2);
+  Logic_environment.add_let_quantif_binding logic_env lv i, (t1, lv, t2)
+
+and infer_predicate ~logic_env p =
   match Logic_normalizer.get_pred p with
   | PoT_term t -> ignore (infer ~logic_env t)
   | PoT_pred p ->
@@ -896,8 +954,26 @@ let rec infer_predicate ~logic_env p =
       in
       (infer_predicate ~logic_env p)
     | Pforall _
-    | Pexists _ -> ()
-    (* TODO:  *)
+    | Pexists _ ->
+      let guards, goal =
+        Error.retrieve_preprocessing
+          "quantified predicate"
+          Bound_variables.get_preprocessed_quantifier
+          p
+          Printer.pp_predicate
+      in
+      let loc = p.pred_loc in
+      let rec do_analysis guards new_guards logic_env = match guards with
+        | [] -> logic_env, new_guards
+        | guard :: guards ->
+          let  logic_env, new_guard =
+            infer_bound_variable ~loc ~logic_env guard
+          in
+          do_analysis guards (new_guard :: new_guards) logic_env
+      in
+      let logic_env, new_guards = do_analysis guards [] logic_env in
+      Bound_variables.replace p new_guards goal;
+      infer_predicate ~logic_env goal
     | Pseparated tlist ->
       List.iter
         (fun t -> ignore (infer ~logic_env t))
diff --git a/src/plugins/e-acsl/src/analyses/typing.ml b/src/plugins/e-acsl/src/analyses/typing.ml
index 5df42089d01..ffffd7fb737 100644
--- a/src/plugins/e-acsl/src/analyses/typing.ml
+++ b/src/plugins/e-acsl/src/analyses/typing.ml
@@ -702,88 +702,33 @@ and type_term_offset ~profile t = match t with
     ignore (type_term ~use_gmp_opt:false ~ctx ~profile t);
     type_term_offset ~profile toff
 
-(* [type_bound_variables] infers an interval associated with each of
-   the provided bounds of a quantified variable, and provides a term
-   accordingly. It could happen that the bounds provided for a quantifier
-   [lv] are bigger than its type. [type_bound_variables] handles such cases
-   and provides smaller bounds whenever possible.
-   Let B be the inferred interval and R the range of [lv.typ]
-   - Case 1: B \subseteq R
-     Example: [\forall unsigned char c; 4 <= c <= 100 ==> 0 <= c <= 255]
-     Return: B
-   - Case 2: B \not\subseteq R and the bounds of B are inferred exactly
-     Example: [\forall unsigned char c; 4 <= c <= 300 ==> 0 <= c <= 255]
-     Return: B \intersect R
-   - Case 3: B \not\subseteq R and the bounds of B are NOT inferred exactly
-     Example: [\let m = n > 0 ? 4 : 341; \forall char u; 1 < u < m ==> u > 0]
-     Return: R with a guard guaranteeing that [lv] does not overflow *)
-and type_bound_variables ~loc ~profile (t1, lv, t2) =
-  let i1 = Interval.get t1 in
-  let i2 = Interval.get t2 in
+and number_ty_bound_variable ~profile (t1, lv, t2) =
+  let i1 = Interval.get_p ~profile t1 in
+  let i2 = Interval.get_p ~profile t2 in
   let i = Interval.(widen (join i1 i2)) in
-  let ctx = match lv.lv_type with
-    | Linteger -> mk_ctx ~use_gmp_opt:true (ty_of_interv ~ctx:Gmpz i)
-    | Ctype ty ->
-      (match Cil.unrollType ty with
-       | TInt(ik, _) | TEnum({ ekind = ik }, _) ->
-         mk_ctx ~use_gmp_opt:true (C_integer ik)
-       | ty ->
-         Options.fatal "unexpected C type %a for quantified variable %a"
-           Printer.pp_typ ty
-           Printer.pp_logic_var lv)
-    | lty ->
-      Options.fatal "unexpected logic type %a for quantified variable %a"
-        Printer.pp_logic_type lty
-        Printer.pp_logic_var lv
-  in
-  let t1, t2, i =
-    match lv.lv_type with
-    | Ltype _ | Lvar _ | Lreal | Larrow _ ->
-      Error.not_yet "quantification over non-integer type"
-    | Linteger -> t1, t2, i
-    | Ctype ty ->
-      let ity = Interval.extended_interv_of_typ ty in
-      if Interval.is_included i ity then
-        (* case 1 *)
-        t1, t2, i
-      else if Interval.is_singleton_int i1 &&
-              Interval.is_singleton_int i2 then
-        begin
-          (* case 2 *)
-          let i = Interval.meet i ity in
-          (* We can now update the bounds in the preprocessed form
-             that come from the meet of the two intervals *)
-          let min, max = Misc.finite_min_and_max (Interval.extract_ival i) in
-          let t1 = Logic_const.tint ~loc min in
-          let t2 = Logic_const.tint ~loc max in
-          t1, t2, i
-        end else
-        (* case 3 *)
-        let min, max = Misc.finite_min_and_max (Interval.extract_ival ity) in
-        let guard_lower = Logic_const.tint ~loc min in
-        let guard_upper = Logic_const.tint ~loc max in
-        let lv_term = Logic_const.tvar ~loc lv in
-        let guard_lower = Logic_const.prel ~loc (Rle, guard_lower, lv_term) in
-        let guard_upper = Logic_const.prel ~loc (Rlt, lv_term, guard_upper) in
-        let guard = Logic_const.pand ~loc (guard_lower, guard_upper) in
-        ignore (type_predicate ~profile guard);
-        Bound_variables.add_guard_for_small_type lv guard;
-        t1, t2, i
-  in
+  match lv.lv_type with
+  | Linteger ->
+    mk_ctx ~use_gmp_opt:true (ty_of_interv ~ctx:Gmpz i)
+  | Ctype ty ->
+    (match Cil.unrollType ty with
+     | TInt(ik, _) | TEnum({ ekind = ik}, _)-> join
+                        (ty_of_interv i)
+                        (mk_ctx ~use_gmp_opt:true (C_integer ik))
+     | ty ->
+       Options.fatal "unexpected C type %a for quantified variable %a"
+         Printer.pp_typ ty
+         Printer.pp_logic_var lv)
+  | lty ->
+    Options.fatal "unexpected logic type %a for quantified variable %a"
+      Printer.pp_logic_type lty
+      Printer.pp_logic_var lv
+
+and type_bound_variables ~profile (t1, lv, t2) =
+  let ctx= number_ty_bound_variable ~profile (t1, lv, t2) in
   (* forcing when typing bounds prevents to generate an extra useless
      GMP variable when --e-acsl-gmp-only *)
-  ignore (type_term ~use_gmp_opt:false ~ctx ~profile t1);
-  ignore (type_term ~use_gmp_opt:false ~ctx ~profile t2);
-  (* if we must generate GMP code, degrade the interval in order to
-     guarantee that [x] will be a GMP when typing the goal *)
-  let _ = match ctx with
-    | C_integer _ -> i
-    (* [ -\infty; +\infty ] *)
-    | Gmpz -> Interval.Ival (Ival.inject_range None None)
-    | C_float _ | Rational | Real | Nan ->
-      Options.fatal "unexpected quantification over %a" D.pretty ctx
-  in
-  (t1, lv, t2)
+  ignore(type_term ~use_gmp_opt:false ~ctx ~profile t1);
+  ignore(type_term ~use_gmp_opt:false ~ctx ~profile t2)
 
 and type_predicate ~profile p =
   let p = Logic_normalizer.get_pred p in
@@ -796,15 +741,12 @@ and type_predicate ~profile p =
       begin
         match li.l_body with
         | LBpred p ->
-          let typed_args =
-            type_args
-              ~use_gmp_opt:true
-              ~profile
-              li.l_profile
-              args
-              li.l_var_info.lv_name
-          in
-          ignore (type_predicate ~profile:typed_args p);
+          List.iter
+            (fun x -> ignore
+                (type_term ~use_gmp_opt: true ~profile x))
+            args;
+          let new_profile = List.map (Interval.get_p ~profile) args in
+          ignore (type_predicate ~profile:new_profile p);
         | LBnone -> ()
         | LBreads _ -> ()
         | LBinductive _ -> ()
@@ -855,12 +797,10 @@ and type_predicate ~profile p =
             p
             Printer.pp_predicate
         in
-        let guards =
-          List.map
-            (fun (t1, x, t2) ->
-               type_bound_variables ~loc:p.pred_loc ~profile (t1, x, t2))
-            guards
-        in Bound_variables.replace p guards goal;
+        List.iter
+          (fun (t1, x, t2) ->
+             type_bound_variables ~profile (t1, x, t2))
+          guards;
         (type_predicate ~profile goal).ty
       end
     | Pseparated tlist ->
@@ -970,7 +910,7 @@ let typing_visitor profile = object
        those errrors are stored in the table and warnings are raised at
        translation time *)
     ignore
-      (try type_named_predicate ~lenv p
+      (try type_named_predicate ~profile p
        with Error.Not_yet _ | Error.Typing_error _  -> ());
     Cil.SkipChildren
 end
diff --git a/src/plugins/e-acsl/src/analyses/typing.mli b/src/plugins/e-acsl/src/analyses/typing.mli
index 04da55ac107..67dce8431e2 100644
--- a/src/plugins/e-acsl/src/analyses/typing.mli
+++ b/src/plugins/e-acsl/src/analyses/typing.mli
@@ -93,6 +93,11 @@ val join: number_ty -> number_ty -> number_ty
     semi-lattice. If one of the argument is {!Other}, the function assumes that
     the other argument is also {!Other}. In this case, the result is [Other]. *)
 
+val number_ty_bound_variable:
+  profile:Interval.profile ->
+  term * logic_var * term ->
+  number_ty
+
 (******************************************************************************)
 (** {2 Typing} *)
 (******************************************************************************)
diff --git a/src/plugins/e-acsl/src/code_generator/loops.ml b/src/plugins/e-acsl/src/code_generator/loops.ml
index 5286d9e0dd3..7eb6fa62db3 100644
--- a/src/plugins/e-acsl/src/code_generator/loops.ml
+++ b/src/plugins/e-acsl/src/code_generator/loops.ml
@@ -290,9 +290,9 @@ let rec mk_nested_loops ~loc mk_innermost_block kf env lscope_vars =
     Typing.preprocess_term ~use_gmp_opt:false ~logic_env t1;
     Typing.preprocess_term ~use_gmp_opt:false ~logic_env t2;
     let ctx =
-      let ty1 = Typing.get_number_ty ~logic_env t1 in
-      let ty2 = Typing.get_number_ty ~logic_env t2 in
-      Typing.join ty1 ty2
+      Typing.number_ty_bound_variable
+        ~profile:(Interval.Logic_environment.get_profile (Env.Logic_env.get env))
+        (t1, logic_x, t2)
     in
     let t_plus_one ?ty t =
       (* whenever provided, [ty] is known to be the type of the result *)
diff --git a/src/plugins/e-acsl/src/code_generator/quantif.ml b/src/plugins/e-acsl/src/code_generator/quantif.ml
index 02d65f25a48..2b1f17bb74e 100644
--- a/src/plugins/e-acsl/src/code_generator/quantif.ml
+++ b/src/plugins/e-acsl/src/code_generator/quantif.ml
@@ -43,22 +43,23 @@ let predicate_to_exp_ref
    [has_empty_quantif_with_false_negative] partially detects such cases:
    Case 1: an empty quantification was detected for sure, return true.
    Case 2: we don't know, return false. *)
-let rec has_empty_quantif_with_false_negative = function
+let rec has_empty_quantif_with_false_negative ~logic_env = function
   | [] ->
     (* case 2 *)
     false
   | (t1, _, t2) :: guards ->
-    let iv1 = Interval.(extract_ival (get t1)) in
-    let iv2 = Interval.(extract_ival (get t2)) in
+    let iv1 = Interval.(extract_ival (get ~logic_env t1)) in
+    let iv2 = Interval.(extract_ival (get ~logic_env t2)) in
     let lower_bound, _ = Ival.min_and_max iv1 in
     let _, upper_bound = Ival.min_and_max iv2 in
     begin
       match lower_bound, upper_bound with
       | Some lower_bound, Some upper_bound ->
         let res  = lower_bound >= upper_bound in
-        res (* case 1 *) || has_empty_quantif_with_false_negative guards
+        res (* case 1 *) ||
+        has_empty_quantif_with_false_negative guards ~logic_env
       | None, _ | _, None ->
-        has_empty_quantif_with_false_negative guards
+        has_empty_quantif_with_false_negative guards ~logic_env
     end
 
 let () =
@@ -80,7 +81,11 @@ let convert kf env loc ~is_forall quantif =
       quantif
       Printer.pp_predicate
   in
-  match has_empty_quantif_with_false_negative bound_vars, is_forall with
+  match (has_empty_quantif_with_false_negative
+           ~logic_env:(Env.Logic_env.get env)
+           bound_vars),
+        is_forall
+  with
   | true, true ->
     Cil.one ~loc, env
   | true, false ->
@@ -96,14 +101,17 @@ let convert kf env loc ~is_forall quantif =
         else z, o, fun e -> Smart_exp.lnot ~loc:e.eloc e
       in
       (* transform [bound_vars] into [lscope_var list],
-         and update logic scope in the process *)
-      let lvs_guards, env = List.fold_right
-          (fun (t1, lv, t2) (lvs_guards, env) ->
+         and update logic scope and logic environment in the process *)
+      let lvs_guards, env = List.fold_left
+          (fun (lvs_guards, env) (t1, lv, t2) ->
              let lvs = Lvs_quantif (t1, Rle, lv, Rlt, t2) in
              let env = Env.Logic_scope.extend env lvs in
+             let logic_env = Env.Logic_env.get env in
+             let env = Env.Logic_env.add_let_quantif_binding env lv
+                 Interval.(join (get ~logic_env t1) (get ~logic_env t2)) in
              lvs :: lvs_guards, env)
-          bound_vars
           ([], env)
+          bound_vars
       in
       let var_res, res, env =
         (* variable storing the result of the quantifier *)
@@ -157,6 +165,12 @@ let convert kf env loc ~is_forall quantif =
       end_loop.labels <- label :: end_loop.labels;
       end_loop_ref := end_loop;
       let env = Env.add_stmt env end_loop in
+      let
+        env = List.fold_left
+          (fun env _ -> Env.Logic_env.pop env)
+          env
+          lvs_guards
+      in
       res, env
     end
 
-- 
GitLab