Skip to content
Snippets Groups Projects
quantif.ml 8.19 KiB
(**************************************************************************)
(*                                                                        *)
(*  This file is part of the E-ACSL plug-in of Frama-C.                   *)
(*                                                                        *)
(*  Copyright (C) 2011                                                    *)
(*    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 Cil_types
open Cil
open Cil_datatype

let named_predicate_to_exp_ref
    : (Env.t -> predicate named -> exp * Env.t) ref
    = Extlib.mk_fun "named_predicate_to_exp_ref"

let term_to_exp_ref
    : (Env.t -> logic_type -> term -> exp * Env.t) ref
    = Extlib.mk_fun "term_to_exp_ref"

let compute_quantif_guards quantif bounded_vars hyps = 
  let error msg pp x =
    let msg1 = Pretty_utils.sfprintf msg pp x in
    let msg2 = 
      Pretty_utils.sfprintf "@[ in quantification@ %a@]"
	d_predicate_named quantif
    in
    Error.untypable (msg1 ^ msg2)
  in
  let rec aux acc vars p = 
    match p.content with
    | Pand({ content = Prel((Rlt | Rle) as r1, t11, t12) },
	   { content = Prel((Rlt | Rle) as r2, t21, t22) }) ->
      (match t12.term_node, t21.term_node with
      | TLval(TVar x1, TNoOffset), TLval(TVar x2, TNoOffset) -> 
	let v, vars = match vars with
	  | [] -> error "@[too much constraint(s)%a@]" (fun _ () -> ()) ()
	  | v :: tl -> 
	    match v.lv_type with
	    | Ctype ty when isIntegralType ty -> v, tl
	    | Linteger -> v, tl
	    | Ctype _ | Ltype _ | Lvar _ | Lreal | Larrow _ -> 
	      error "@[non integer variable %a@]" d_logic_var v
	in
	if Logic_var.equal x1 x2 && Logic_var.equal x1 v then
	  (t11, r1, x1, r2, t22) :: acc, vars
	else
	  error "@[invalid binder %a@]" d_term t21
      | TLval _, _ -> error "@[invalid binder %a@]" d_term t21
      | _, _ -> error "@[invalid binder %a@]" d_term t12)
    | Pand(p1, p2) -> 
      let acc, vars = aux acc vars p1 in
      aux acc vars p2
    | _ -> error "@[invalid guard %a@]" d_predicate_named p
  in 
  let acc, vars = aux [] bounded_vars hyps in
  (match vars with
  | [] -> ()
  | _ :: _ ->
    let msg = 
      Pretty_utils.sfprintf
	"@[unguarded variable%s %tin quantification@ %a@]" 
	(if List.length vars = 1 then "" else "s") 
	(fun fmt -> 
	  List.iter (fun v -> Format.fprintf fmt "@[%a @]" d_logic_var v) vars)
	d_predicate_named quantif
    in
    Error.untypable msg);
  List.rev acc

module Label_ids = 
  State_builder.Counter(struct let name = "E_ACSL.Label_ids" end)

let convert env loc p bounded_vars hyps goal =
  let named_predicate_to_exp = !named_predicate_to_exp_ref in
  let term_to_exp = !term_to_exp_ref in
  (* universal quantification over integers (or a subtype of integer) *)
  let guards = compute_quantif_guards p bounded_vars hyps in
  let env = List.fold_left Env.Logic_binding.add env bounded_vars in
  let var_res = ref Varinfo.dummy in
  let res, env =
    (* variable storing the result of the \forall *)
    Env.new_var env None intType
      (fun v _ ->
	var_res := v;
	let lv = var v in
	[ mkStmtOneInstr ~valid_sid:true (Set(lv, one ~loc, loc)) ])
  in
  let end_loop_ref = ref dummyStmt in
  let rec mk_for_loop env = function
    | [] -> 
      (* innermost loop body: store the result in [res] and go out according
	 to evaluation of the goal *)
      let test, env = named_predicate_to_exp (Env.push env) goal in
      let then_block = mkBlock [ mkEmptyStmt ~loc () ] in
      let else_block = 
	(* use a 'goto', not a simple 'break' in order to handle 'forall' with
	   multiple binders (leading to imbricated loops) *)
	mkBlock
	  [ mkStmtOneInstr
	      ~valid_sid:true (Set(var !var_res, zero ~loc, loc));
	    mkStmt ~valid_sid:true (Goto(end_loop_ref, loc)) ]
      in
      let blk, env = 
	Env.pop_and_get
	  env
	  (mkStmt ~valid_sid:true (If(test, then_block, else_block, loc)))
	  ~global_clear:false
	  Env.After
      in
      (* TODO: could be optimised if [pop_and_get] would return a list of
	 stmts *)
      [ mkStmt ~valid_sid:true (Block blk) ], env
    | (t1, rel1, logic_x, rel2, t2) :: tl ->
      let body, env = mk_for_loop env tl in
      let t_plus_one t =
	Logic_const.term ~loc
	  (TBinOp(PlusA, t, Logic_const.tinteger ~loc ~ikind:IChar 1))
	  Linteger
      in
      let t1 = match rel1 with
	| Rlt -> t_plus_one t1
	| Rle -> t1
	| Rgt | Rge | Req | Rneq -> assert false
      in
      let bop2 = match rel2 with
	| Rlt -> Lt
	| Rle -> Le
	| Rgt | Rge | Req | Rneq -> assert false
      in
      (* we increment the loop counter one more time (at the end of the
	 loop). Thus to prevent  overflow, check the type of [t2 + 1] instead
	 of [t2]. *) 
      let ty = Typing.principal_type_from_term t1 (t_plus_one t2) in
      let ty = Typing.principal_type ty logic_x.lv_type in
      (* loop counter corresponding to the quantified variable *)
      let var_x = Env.Logic_binding.get env logic_x in
      let lv_x = var var_x in
      let x = new_exp ~loc (Lval lv_x) in
      let env = match ty with
	| Ctype ty when isIntegralType ty -> 
	  var_x.vtype <- ty;
	  env
	| Linteger -> 
	  var_x.vtype <- Mpz.t;
	  Env.add_stmt env (Mpz.init x)
	| Ctype _ | Ltype _ | Lvar _ | Lreal | Larrow _ -> assert false
      in
      (* initialize the loop counter to [t1] *)
      let e1, env = term_to_exp (Env.push env) ty t1 in
      let init_blk, env = 
	Env.pop_and_get 
	  env
	  (Mpz.affect lv_x x e1)
	  ~global_clear:false
	  Env.Middle
      in
      (* generate the guard [x bop t2] *)
      let stmts_block b = [ mkStmt ~valid_sid:true (Block b) ] in
      let tlv = Logic_const.tvar ~loc (cvar_to_lvar var_x) in
      let guard_exp, env = 
	term_to_exp
	  (Env.push env)
	  (Ctype intType)
	  (Logic_const.term (TBinOp(bop2, tlv, t2)) ty)
      in
      let break_stmt = mkStmt ~valid_sid:true (Break guard_exp.eloc) in
      let guard_blk, env =
	Env.pop_and_get
	  env
	  (mkStmt ~valid_sid:true
	     (If(guard_exp,
		 mkBlock [ mkEmptyStmt () ],
		 mkBlock [ break_stmt ], 
		 guard_exp.eloc)))
	  ~global_clear:false
	  Env.Middle
      in
      let guard = stmts_block guard_blk in
      (* increment the loop counter [x++] *)
      let incr, env = term_to_exp (Env.push env) ty (t_plus_one tlv) in
      let next_blk, env = 
	Env.pop_and_get
	  env
	  (Mpz.affect lv_x x incr)
	  ~global_clear:false
	  Env.Middle
      in
      (* generate the whole loop *)
      let start = stmts_block init_blk in
      let next = stmts_block next_blk in
      start @
	[ mkStmt ~valid_sid:true
	    (Loop ([],
		   mkBlock (guard @ body @ next),
		   loc, 
		   None, 
		   Some break_stmt)) ], 
      env
  in
  let stmts, env = mk_for_loop env guards in
  let env = 
    Env.add_stmt env (mkStmt ~valid_sid:true (Block (mkBlock stmts))) 
  in
  (* where to jump to go out of the loop *)
  let end_loop = mkEmptyStmt ~loc () in
  let label_name = "e_acsl_end_loop" ^ string_of_int (Label_ids.next ()) in
  let label = Label(label_name, loc, false) in
  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 Env.Logic_binding.remove env bounded_vars in
  res, env

(*
Local Variables:
compile-command: "make"
End:
*)