@ -10,120 +10,124 @@ module AbstractValue = PulseAbstractValue
open PulseFormula
open SatUnsatMonad
let % test_module _ =
( module struct
(* * {2 Utilities for defining formulas easily}
(* * {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 rest of
Pulse interacts with the arithmetic layer , so it's good that we follow this convention 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 ) ] .
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 ) .
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 . * )
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 ) ] .
(* * a literal integer leaves the formula unchanged and returns a [LiteralOperand] *)
let i i phi = Sat ( phi , LiteralOperand ( IntLit . of_int i ) )
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 . * )
(* * 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 )
(* * a literal integer leaves the formula unchanged and returns a [LiteralOperand] *)
let i i phi = Sat ( phi , LiteralOperand ( IntLit . of_int i ) )
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 )
(* * 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 = of_binop ( PlusA None ) f1 f2 phi
let ( * ) f1 f2 phi = of_binop ( Mult None ) f1 f2 phi
let ( - ) f1 f2 phi = of_binop ( MinusA None ) f1 f2 phi
let ( / ) f1 f2 phi = of_binop Div f1 f2 phi
let ( * ) f1 f2 phi = of_binop ( Mult None ) f1 f2 phi
let ( & ) f1 f2 phi = of_binop BAnd f1 f2 phi
let ( / ) f1 f2 phi = of_binop Div f1 f2 phi
let ( mod ) f1 f2 phi = of_binop Mod 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_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 =
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
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
(* 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 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_var = mk_var " x "
let x = op_of_var x_var
let x = op_of_var x_var
let y_var = mk_var " y "
let y_var = mk_var " y "
let y = op_of_var y_var
let y = op_of_var y_var
let z_var = mk_var " z "
let z_var = mk_var " z "
let z = op_of_var z_var
let z = op_of_var z_var
let w = op_of_var ( mk_var " w " )
let w_var = mk_var " w "
let v = op_of_var ( mk_var " v " )
let w = op_of_var w_var
(* * 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 v = op_of_var ( mk_var " v " )
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
(* * 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 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 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
let normalize phi = test ~ f : normalize phi
(* * the actual tests *)
let simplify ~ keep phi = test ~ f : ( simplify ~ keep : ( AbstractValue . Set . of_list keep ) ) phi
let % test_module " normalization " =
( module struct
let % expect_test _ =
normalize ( x < y ) ;
[ % expect { | true ( no var = var ) && true ( no linear ) && { x < y } | } ]
@ -194,7 +198,10 @@ let%test_module _ =
∧ v8 = x + v6 + 1 ∧ v9 = 0 ∧ v10 = 0
&&
true ( no atoms ) | } ]
end )
let % test_module " variable elimination " =
( module struct
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 ) | } ]
@ -226,3 +233,14 @@ let%test_module _ =
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 )
let % test_module " non-linear simplifications " =
( module struct
let % expect_test " zero propagation " =
simplify ~ keep : [ w_var ] ( ( ( i 0 / ( x * z ) ) & v ) * v mod y = w ) ;
[ % expect { | w = v10 && true ( no linear ) && { w = v9 mod y } ∧ { v8 = 0 & v } ∧ { v9 = v8 × v } | } ]
let % expect_test " constant propagation: bitshift " =
simplify ~ keep : [ x_var ] ( of_binop Shiftlt ( of_binop Shiftrt ( i 0b111 ) ( i 2 ) ) ( i 2 ) = x ) ;
[ % expect { | x = v7 && true ( no linear ) && { x = v6 < < 2 } ∧ { v6 = 7 > > 2 } | } ]
end )