You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
6.2 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module AbstractValue = PulseAbstractValue
open PulseFormula
open SatUnsatMonad
let%test_module _ =
( module struct
(** {2 Utilities for defining formulas easily}
We want to be able to write something close to [x + y - 42 < z], but the API of
{!PulseFormula} doesn't support building arbitrary formulas or even arbitrary terms.
Instead, we have to introduce intermediate variables for each sub-expression (this
corresponds to how the reste of Pulse interacts with the arithmetic layer too, so it's good
that we follow this constraint here too).
The definitions here make this transparent by passing the formula around. For example,
building [x+y] takes in a formula [phi] and returns [(phi ∧ v123 = x+y, v123)], i.e. a
pair of the formula with a new intermediate equality and the resulting intermediate
variable. This allows us to chain operations: [x+y-42] is a function that takes a formula,
passes it to [x+y] returning [(phi',v123)] as we saw with [phi' = phi ∧ v123 = x+y],
passes it to "42", which here is also a function returning [(phi',42)] (note the unchanged
[phi']), then finally returns [(phi ∧ v123 = x+y ∧ v234 = v123-42, v234)].
This is convoluted, especially as each step may also return [Unsat] even during "term"
construction, but as a result the tests themselves should be straightforward to understand. *)
(** a literal integer leaves the formula unchanged and returns a [LiteralOperand] *)
let i i phi = Sat (phi, LiteralOperand (IntLit.of_int i))
(** similarly as for literals; this is not used directly in tests so the name is a bit more
descriptive *)
let op_of_var x phi = Sat (phi, AbstractValueOperand x)
let of_binop bop f1 f2 phi =
let* phi, op1 = f1 phi in
let* phi, op2 = f2 phi in
let v = Var.mk_fresh () in
let+ phi = and_equal_binop v bop op1 op2 phi in
(phi, AbstractValueOperand v)
let ( + ) f1 f2 phi = of_binop (PlusA None) f1 f2 phi
let ( - ) f1 f2 phi = of_binop (MinusA None) f1 f2 phi
let ( = ) f1 f2 phi =
let* phi, op1 = f1 phi in
let* phi, op2 = f2 phi in
and_equal op1 op2 phi
let ( < ) f1 f2 phi =
let* phi, op1 = f1 phi in
let* phi, op2 = f2 phi in
and_less_than op1 op2 phi
let ( && ) f1 f2 phi = f1 phi >>= f2
(* we remember a mapping [Var.t -> string] to print more readable results that mention the
user-defined variables by their readable names instead of [v123] *)
let var_names = Caml.Hashtbl.create 4
let mk_var name =
let v = AbstractValue.mk_fresh () in
Caml.Hashtbl.add var_names v name ;
v
let x_var = mk_var "x"
let x = op_of_var x_var
let y_var = mk_var "y"
let y = op_of_var y_var
let z_var = mk_var "z"
let z = op_of_var z_var
let w = op_of_var (mk_var "w")
let v = op_of_var (mk_var "v")
(** reset to this state before each test so that variable id's remain stable when tests are
added in the future *)
let init_vars_state = AbstractValue.State.get ()
let pp_var fmt v =
match Caml.Hashtbl.find_opt var_names v with
| Some name ->
F.pp_print_string fmt name
| None ->
AbstractValue.pp fmt v
let normalized_pp fmt = function
| Unsat ->
F.pp_print_string fmt "unsat"
| Sat phi ->
pp_with_pp_var pp_var fmt phi
let test ~f phi =
AbstractValue.State.set init_vars_state ;
phi ttrue >>= f |> F.printf "%a" normalized_pp
let normalize phi = test ~f:normalize phi
let simplify ~keep phi = test ~f:(simplify ~keep:(AbstractValue.Set.of_list keep)) phi
(** the actual tests *)
let%expect_test _ =
normalize (x < y) ;
[%expect {|true (no var=var) && true (no linear) && {x < y}|}]
let%expect_test _ =
normalize (x + i 1 - i 1 < x) ;
[%expect {|unsat|}]
let%expect_test _ =
normalize (x + (y - x) < y) ;
[%expect {|unsat|}]
let%expect_test _ =
normalize (x = y && y = z && z = i 0 && x = i 1) ;
[%expect {|unsat|}]
(* should be false (x = w + (y+1) -> 1 = w + z -> 1 = 0) *)
let%expect_test _ =
normalize (x = w + y + i 1 && y + i 1 = z && x = i 1 && w + z = i 0) ;
[%expect {|unsat|}]
(* same as above but atoms are given in the opposite order *)
let%expect_test _ =
normalize (w + z = i 0 && x = i 1 && y + i 1 = z && x = w + y + i 1) ;
[%expect {|unsat|}]
let%expect_test _ =
normalize (of_binop Ne x y = i 0 && x = i 0 && y = i 1) ;
[%expect {|unsat|}]
let%expect_test _ =
normalize (of_binop Eq x y = i 0 && x = i 0 && y = i 1) ;
[%expect {|true (no var=var) && x = 0 y = 1 v6 = 0 && true (no atoms)|}]
let%expect_test _ =
normalize (x = i 0 && x < i 0) ;
[%expect {|unsat|}]
let%expect_test _ =
simplify ~keep:[x_var] (x = i 0 && y = i 1 && z = i 2 && w = i 3) ;
[%expect {|true (no var=var) && x = 0 && true (no atoms)|}]
let%expect_test _ =
simplify ~keep:[x_var] (x = y + i 1 && x = i 0) ;
[%expect {|x=v6 && x = 0 && true (no atoms)|}]
let%expect_test _ =
simplify ~keep:[y_var] (x = y + i 1 && x = i 0) ;
[%expect {|true (no var=var) && y = -1 && true (no atoms)|}]
(* should keep most of this or realize that [w = z] hence this boils down to [z+1 = 0] *)
let%expect_test _ =
simplify ~keep:[y_var; z_var] (x = y + z && w = x - y && v = w + i 1 && v = i 0) ;
[%expect {|x=v6 z=w=v7 && x = y -1 z = -1 && true (no atoms)|}]
let%expect_test _ =
simplify ~keep:[x_var; y_var] (x = y + z && w + x + y = i 0 && v = w + i 1) ;
[%expect
{|
x=v6 v=v9
&&
x = 1/2·z + -1/2·w y = -1/2·z + -1/2·w v = w +1 v7 = 1/2·z + 1/2·w
&&
true (no atoms)|}]
let%expect_test _ =
simplify ~keep:[x_var; y_var] (x = y + i 4 && x = w && y = z) ;
[%expect {|x=w=v6 y=z && x = y +4 && true (no atoms)|}]
end )