Reviewed By: mbouaziz Differential Revision: D9846748 fbshipit-source-id: 575c6ee15master
parent
f7a9a0c323
commit
a32890a1e3
@ -0,0 +1,455 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2018-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** Congruence closure with integer offsets *)
|
||||||
|
|
||||||
|
(* For background, see:
|
||||||
|
|
||||||
|
Robert Nieuwenhuis, Albert Oliveras: Fast congruence closure and
|
||||||
|
extensions. Inf. Comput. 205(4): 557-580 (2007)
|
||||||
|
|
||||||
|
and, for a more detailed correctness proof of the case without integer
|
||||||
|
offsets, see section 5 of:
|
||||||
|
|
||||||
|
Aleksandar Nanevski, Viktor Vafeiadis, Josh Berdine: Structuring the
|
||||||
|
verification of heap-manipulating programs. POPL 2010: 261-274 *)
|
||||||
|
|
||||||
|
(** set of exps representing congruence classes *)
|
||||||
|
module Cls = struct
|
||||||
|
type t = Exp.t list [@@deriving compare, sexp]
|
||||||
|
|
||||||
|
let pp fs cls =
|
||||||
|
Format.fprintf fs "@[<hov 1>{@[%a@]}@]" (List.pp ",@ " Exp.pp) cls
|
||||||
|
|
||||||
|
let singleton e = [e]
|
||||||
|
let add cls exp = exp :: cls
|
||||||
|
let remove_exn = List.remove_exn
|
||||||
|
let union = List.rev_append
|
||||||
|
let fold_map = List.fold_map
|
||||||
|
let is_empty = List.is_empty
|
||||||
|
let length = List.length
|
||||||
|
let mem = List.mem ~equal:Exp.equal
|
||||||
|
end
|
||||||
|
|
||||||
|
(** set of exps representing "use lists" encoding super-expression relation *)
|
||||||
|
module Use = struct
|
||||||
|
type t = Exp.t list [@@deriving compare, sexp]
|
||||||
|
|
||||||
|
let pp fs use =
|
||||||
|
Format.fprintf fs "@[<hov 1>{@[%a@]}@]" (List.pp ",@ " Exp.pp) use
|
||||||
|
|
||||||
|
let empty = []
|
||||||
|
let singleton exp = [exp]
|
||||||
|
let add use exp = exp :: use
|
||||||
|
let union = List.rev_append
|
||||||
|
let fold = List.fold
|
||||||
|
let is_empty = List.is_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
type 'a exp_map = 'a Map.M(Exp).t [@@deriving compare, sexp]
|
||||||
|
|
||||||
|
let empty_map = Map.empty (module Exp)
|
||||||
|
|
||||||
|
type t =
|
||||||
|
{ sat: bool (** [false] if constraints are inconsistent *)
|
||||||
|
; rep: Exp.t exp_map
|
||||||
|
(** map [a] to [a'+k], indicating that [a=a'+k] holds, and that [a']
|
||||||
|
(without the offset [k]) is the 'rep(resentative)' of [a] *)
|
||||||
|
; lkp: Exp.t exp_map
|
||||||
|
(** inverse of mapping rep over sub-expressions: map [f'(a'+i)] to
|
||||||
|
[f(a+j)+k], an (offsetted) app(lication expression) in the
|
||||||
|
relation which normalizes to one in the 'equivalence modulo
|
||||||
|
offset' class of [f'(a'+i)], indicating that [f'(a'+i) =
|
||||||
|
f(a+j)+k] holds, for some [k] where [rep f = f'] and [rep a =
|
||||||
|
a'+(i-j)] *)
|
||||||
|
; cls: Cls.t exp_map
|
||||||
|
(** inverse rep: map each rep [a'] to all the [a+k] in its class,
|
||||||
|
i.e., [cls a' = {a+k | rep a = a'+(-k)}] *)
|
||||||
|
; use: Use.t exp_map
|
||||||
|
(** super-expression relation for representatives: map each
|
||||||
|
representative [a'] of [a] to the application expressions in the
|
||||||
|
relation where [a] (possibly + an offset) appears as an
|
||||||
|
immediate sub-expression *)
|
||||||
|
; pnd: (Exp.t * Exp.t) list
|
||||||
|
(** equations to be added to the relation, to enable delaying adding
|
||||||
|
equations discovered while invariants are temporarily broken *)
|
||||||
|
}
|
||||||
|
[@@deriving compare, sexp]
|
||||||
|
|
||||||
|
(** The expressions in the range of [lkp] and [use], as well as those in
|
||||||
|
[pnd], are 'in the relation' in the sense that there is some constraint
|
||||||
|
involving them, and in practice are expressions which have been passed
|
||||||
|
to [merge] as opposed to having been constructed internally. *)
|
||||||
|
|
||||||
|
let pp fs {sat; rep; lkp; cls; use; pnd} =
|
||||||
|
let pp_alist pp_k pp_v fs alist =
|
||||||
|
let pp_assoc fs (k, v) =
|
||||||
|
Format.fprintf fs "[@[%a@ @<2>↦ %a@]]" pp_k k pp_v v
|
||||||
|
in
|
||||||
|
Format.fprintf fs "[@[<hv>%a@]]" (List.pp ";@ " pp_assoc) alist
|
||||||
|
in
|
||||||
|
let pp_pnd fs pend =
|
||||||
|
let pp_eq fs (e, f) =
|
||||||
|
Format.fprintf fs "@[%a = %a@]" Exp.pp e Exp.pp f
|
||||||
|
in
|
||||||
|
if not (List.is_empty pend) then
|
||||||
|
Format.fprintf fs ";@ pend= @[%a@];" (List.pp ";@ " pp_eq) pend
|
||||||
|
in
|
||||||
|
Format.fprintf fs
|
||||||
|
"@[{@[<hv>sat= %b;@ rep= %a;@ lkp= %a;@ cls= %a;@ use= %a%a@]}@]" sat
|
||||||
|
(pp_alist Exp.pp Exp.pp) (Map.to_alist rep) (pp_alist Exp.pp Exp.pp)
|
||||||
|
(Map.to_alist lkp) (pp_alist Exp.pp Cls.pp) (Map.to_alist cls)
|
||||||
|
(pp_alist Exp.pp Use.pp) (Map.to_alist use) pp_pnd pnd
|
||||||
|
|
||||||
|
let pp_classes fs {cls} =
|
||||||
|
List.pp "@ @<2>∧ "
|
||||||
|
(fun fs (key, data) ->
|
||||||
|
let es = Cls.remove_exn ~equal:Exp.equal data key in
|
||||||
|
if not (Cls.is_empty es) then
|
||||||
|
Format.fprintf fs "@[%a@ = %a@]" Exp.pp key (List.pp "@ = " Exp.pp)
|
||||||
|
es )
|
||||||
|
fs (Map.to_alist cls)
|
||||||
|
|
||||||
|
let invariant r =
|
||||||
|
Invariant.invariant [%here] r [%sexp_of: t]
|
||||||
|
@@ fun () ->
|
||||||
|
Map.iteri r.rep ~f:(fun ~key:e ~data:e' -> assert (not (Exp.equal e e'))) ;
|
||||||
|
Map.iteri r.cls ~f:(fun ~key:e' ~data:cls -> assert (Cls.mem cls e')) ;
|
||||||
|
Map.iteri r.use ~f:(fun ~key:_ ~data:use -> assert (not (Use.is_empty use))
|
||||||
|
)
|
||||||
|
|
||||||
|
(* Auxiliary functions for manipulating "base plus offset" expressions *)
|
||||||
|
|
||||||
|
let map_sum e ~f =
|
||||||
|
match e with
|
||||||
|
| Exp.App {op= App {op= Add; arg= a}; arg= Integer _ as i} ->
|
||||||
|
let a' = f a in
|
||||||
|
if a' == a then e else Exp.add a' i
|
||||||
|
| a -> f a
|
||||||
|
|
||||||
|
let fold_sum e ~init ~f =
|
||||||
|
match e with
|
||||||
|
| Exp.App {op= App {op= Add; arg= a}; arg= Integer _} -> f init a
|
||||||
|
| a -> f init a
|
||||||
|
|
||||||
|
let base_of = function
|
||||||
|
| Exp.App {op= App {op= Add; arg= a}; arg= Integer _} -> a
|
||||||
|
| a -> a
|
||||||
|
|
||||||
|
(** solve a+i = b for a, yielding a = b-i *)
|
||||||
|
let solve_for_base ai b =
|
||||||
|
match ai with
|
||||||
|
| Exp.App {op= App {op= Add; arg= a}; arg= Integer _ as i} ->
|
||||||
|
(a, Exp.sub b i)
|
||||||
|
| _ -> (ai, b)
|
||||||
|
|
||||||
|
(** [norm r a+i] = [a'+k] where [r] implies [a+i=a'+k] and [a'] is a rep *)
|
||||||
|
let norm r e =
|
||||||
|
map_sum e ~f:(fun a -> try Map.find_exn r.rep a with Caml.Not_found -> a)
|
||||||
|
|
||||||
|
(* Core closure operations *)
|
||||||
|
|
||||||
|
type prefer = Exp.t -> over:Exp.t -> int
|
||||||
|
|
||||||
|
let true_ =
|
||||||
|
{ sat= true
|
||||||
|
; rep= empty_map
|
||||||
|
; lkp= empty_map
|
||||||
|
; cls= empty_map
|
||||||
|
; use= empty_map
|
||||||
|
; pnd= [] }
|
||||||
|
|
||||||
|
let false_ = {true_ with sat= false}
|
||||||
|
|
||||||
|
(** Add app exps (and sub-exps) to the relation. This populates the [lkp]
|
||||||
|
and [use] maps, treating an exp [e] of form [f(a)] as an equation
|
||||||
|
between the app [f(a)] and the 'symbol' [e]. This has the effect of
|
||||||
|
using [e] as a 'name' of the app [f(a)], rather than using an explicit
|
||||||
|
'flattening' transformation introducing new symbols for each
|
||||||
|
application. *)
|
||||||
|
let rec extend r e =
|
||||||
|
fold_sum e ~init:r ~f:(fun r -> function
|
||||||
|
| App _ as fa ->
|
||||||
|
let r, fa' =
|
||||||
|
Exp.fold_map fa ~init:r ~f:(fun r b ->
|
||||||
|
let r, c = extend r b in
|
||||||
|
(r, norm r c) )
|
||||||
|
in
|
||||||
|
Map.find_or_add r.lkp fa'
|
||||||
|
~if_found:(fun d ->
|
||||||
|
let r = {r with pnd= (e, d) :: r.pnd} in
|
||||||
|
(r, d) )
|
||||||
|
~default:e
|
||||||
|
~if_added:(fun lkp ->
|
||||||
|
let use =
|
||||||
|
Exp.fold fa' ~init:r.use ~f:(fun use b' ->
|
||||||
|
if Exp.is_constant b' then use
|
||||||
|
else
|
||||||
|
Map.update use b' ~f:(function
|
||||||
|
| Some b_use -> Use.add b_use fa
|
||||||
|
| None -> Use.singleton fa ) )
|
||||||
|
in
|
||||||
|
let r = {r with lkp; use} in
|
||||||
|
(r, e) )
|
||||||
|
| _ -> (r, e) )
|
||||||
|
|
||||||
|
exception Unsat
|
||||||
|
|
||||||
|
(** Add an equation [b+j] = [a+i] using [a] as the new rep. This removes [b]
|
||||||
|
from the [cls] and [use] maps, as it is no longer a rep. The [rep] map
|
||||||
|
is updated to map each element of [b]'s [cls] (including [b]) to [a].
|
||||||
|
This also adjusts the offsets in [b]'s [cls] and merges it into [a]'s.
|
||||||
|
Likewise [b]'s [use] exps are merged into [a]'s. Finally, [b]'s [use]
|
||||||
|
exps are used to traverse one step up the expression dag, looking for
|
||||||
|
new equations involving a super-expression of [b], whose rep changed. *)
|
||||||
|
let add_directed_equation r ~exp:bj ~rep:ai =
|
||||||
|
let a = base_of ai in
|
||||||
|
(* b+j = a+i so b = a+i-j *)
|
||||||
|
let b, ai_j = solve_for_base bj ai in
|
||||||
|
let b_cls, cls =
|
||||||
|
try Map.find_and_remove_exn r.cls b with Caml.Not_found ->
|
||||||
|
(Cls.singleton b, r.cls)
|
||||||
|
in
|
||||||
|
let b_use, use =
|
||||||
|
try Map.find_and_remove_exn r.use b with Caml.Not_found ->
|
||||||
|
(Use.empty, r.use)
|
||||||
|
in
|
||||||
|
let rep, a_cls_delta =
|
||||||
|
Cls.fold_map b_cls ~init:r.rep ~f:(fun rep ck ->
|
||||||
|
(* c+k = b = a+i-j so c = a+i-j-k *)
|
||||||
|
let c, ai_j_k = solve_for_base ck ai_j in
|
||||||
|
if Exp.is_false (Exp.eq c ai_j_k) then raise Unsat ;
|
||||||
|
let rep = Map.set rep ~key:c ~data:ai_j_k in
|
||||||
|
(* a+i-j = c+k so a = c+k+j-i *)
|
||||||
|
let _, ckj_i = solve_for_base ai_j ck in
|
||||||
|
(rep, ckj_i) )
|
||||||
|
in
|
||||||
|
let cls =
|
||||||
|
Map.update cls a ~f:(function
|
||||||
|
| Some a_cls -> Cls.union a_cls_delta a_cls
|
||||||
|
| None -> Cls.add a_cls_delta a )
|
||||||
|
in
|
||||||
|
let r = {r with rep; cls; use} in
|
||||||
|
let r, a_use_delta =
|
||||||
|
Use.fold b_use ~init:(r, Use.empty) ~f:(fun (r, a_use_delta) u ->
|
||||||
|
let u' = Exp.map ~f:(norm r) u in
|
||||||
|
Map.find_or_add r.lkp u'
|
||||||
|
~if_found:(fun v ->
|
||||||
|
let r = {r with pnd= (u, v) :: r.pnd} in
|
||||||
|
(* no need to add u to use a since u is an app already in r
|
||||||
|
(since u' found in r.lkp) that is equal to v, so will be in
|
||||||
|
use of u's subexps, and u = v is added to pnd so propagate
|
||||||
|
will combine them later *)
|
||||||
|
(r, a_use_delta) )
|
||||||
|
~default:u
|
||||||
|
~if_added:(fun lkp ->
|
||||||
|
let r = {r with lkp} in
|
||||||
|
let a_use_delta = Use.add a_use_delta u in
|
||||||
|
(r, a_use_delta) ) )
|
||||||
|
in
|
||||||
|
let use =
|
||||||
|
if Use.is_empty a_use_delta then use
|
||||||
|
else
|
||||||
|
Map.update use a ~f:(function
|
||||||
|
| Some a_use -> Use.union a_use_delta a_use
|
||||||
|
| None -> a_use_delta )
|
||||||
|
in
|
||||||
|
let r = {r with use} in
|
||||||
|
r |> check invariant
|
||||||
|
|
||||||
|
(** Close the relation with the pending equations. *)
|
||||||
|
let rec propagate_ ?prefer r =
|
||||||
|
match r.pnd with
|
||||||
|
| [] -> r
|
||||||
|
| (d, e) :: pnd ->
|
||||||
|
let d' = norm r d in
|
||||||
|
let e' = norm r e in
|
||||||
|
let r = {r with pnd} in
|
||||||
|
let r =
|
||||||
|
if Exp.equal (base_of d') (base_of e') then
|
||||||
|
if Exp.equal d' e' then r else {r with sat= false; pnd= []}
|
||||||
|
else
|
||||||
|
let prefer_e_over_d =
|
||||||
|
match (Exp.is_constant d, Exp.is_constant e) with
|
||||||
|
| true, false -> -1
|
||||||
|
| false, true -> 1
|
||||||
|
| _ -> (
|
||||||
|
match prefer with
|
||||||
|
| Some prefer -> prefer e' ~over:d'
|
||||||
|
| None -> 0 )
|
||||||
|
in
|
||||||
|
match prefer_e_over_d with
|
||||||
|
| n when n < 0 -> add_directed_equation r ~exp:e' ~rep:d'
|
||||||
|
| p when p > 0 -> add_directed_equation r ~exp:d' ~rep:e'
|
||||||
|
| _ ->
|
||||||
|
let len_d =
|
||||||
|
try Cls.length (Map.find_exn r.cls d')
|
||||||
|
with Caml.Not_found -> 1
|
||||||
|
in
|
||||||
|
let len_e =
|
||||||
|
try Cls.length (Map.find_exn r.cls e')
|
||||||
|
with Caml.Not_found -> 1
|
||||||
|
in
|
||||||
|
if len_d > len_e then add_directed_equation r ~exp:e' ~rep:d'
|
||||||
|
else add_directed_equation r ~exp:d' ~rep:e'
|
||||||
|
in
|
||||||
|
propagate_ ?prefer r
|
||||||
|
|
||||||
|
let propagate ?prefer r =
|
||||||
|
[%Trace.call fun {pf} -> pf "%a" pp r]
|
||||||
|
;
|
||||||
|
(try propagate_ ?prefer r with Unsat -> false_)
|
||||||
|
|>
|
||||||
|
[%Trace.retn fun {pf} -> pf "%a" pp]
|
||||||
|
|
||||||
|
let merge ?prefer r d e =
|
||||||
|
if not r.sat then r
|
||||||
|
else
|
||||||
|
let r, a = extend r d in
|
||||||
|
let r =
|
||||||
|
if Exp.equal d e then r
|
||||||
|
else
|
||||||
|
let r, b = extend r e in
|
||||||
|
let r = {r with pnd= (a, b) :: r.pnd} in
|
||||||
|
r
|
||||||
|
in
|
||||||
|
propagate ?prefer r
|
||||||
|
|
||||||
|
let rec normalize_ r e =
|
||||||
|
[%Trace.call fun {pf} -> pf "%a" Exp.pp e]
|
||||||
|
;
|
||||||
|
map_sum e ~f:(function
|
||||||
|
| App _ as fa -> (
|
||||||
|
let fa' = Exp.map ~f:(normalize_ r) fa in
|
||||||
|
match Map.find_exn r.lkp fa' with
|
||||||
|
| exception _ -> fa'
|
||||||
|
| c -> norm r (Exp.map ~f:(norm r) c) )
|
||||||
|
| c ->
|
||||||
|
let c' = norm r c in
|
||||||
|
if c' == c then c else normalize_ r c' )
|
||||||
|
|>
|
||||||
|
[%Trace.retn fun {pf} -> pf "%a" Exp.pp]
|
||||||
|
|
||||||
|
let normalize r e =
|
||||||
|
[%Trace.call fun {pf} -> pf "%a" pp r]
|
||||||
|
;
|
||||||
|
normalize_ r e
|
||||||
|
|>
|
||||||
|
[%Trace.retn fun {pf} -> pf "%a" Exp.pp]
|
||||||
|
|
||||||
|
let mem_eq r e f = Exp.equal (normalize r e) (normalize r f)
|
||||||
|
|
||||||
|
(** Exposed interface *)
|
||||||
|
|
||||||
|
let and_eq = merge
|
||||||
|
|
||||||
|
let and_ ?prefer r s =
|
||||||
|
if not r.sat then r
|
||||||
|
else if not s.sat then s
|
||||||
|
else
|
||||||
|
Map.fold s.rep ~init:r ~f:(fun ~key:e ~data:e' r -> merge ?prefer r e e')
|
||||||
|
|
||||||
|
let or_ ?prefer r s =
|
||||||
|
if not s.sat then r
|
||||||
|
else if not r.sat then s
|
||||||
|
else
|
||||||
|
let merge_mems rs r s =
|
||||||
|
Map.fold s.rep ~init:rs ~f:(fun ~key:e ~data:e' rs ->
|
||||||
|
if mem_eq r e e' then merge ?prefer rs e e' else rs )
|
||||||
|
in
|
||||||
|
let rs = true_ in
|
||||||
|
let rs = merge_mems rs r s in
|
||||||
|
let rs = merge_mems rs s r in
|
||||||
|
rs
|
||||||
|
|
||||||
|
(* assumes that f is injective and for any set of exps E, f[E] is disjoint
|
||||||
|
from E *)
|
||||||
|
let map_exps ({sat= _; rep; lkp; cls; use; pnd} as r) ~f =
|
||||||
|
[%Trace.call fun {pf} -> pf "%a@." pp r]
|
||||||
|
;
|
||||||
|
assert (List.is_empty pnd) ;
|
||||||
|
let map ~equal_data ~f_data m =
|
||||||
|
Map.fold m ~init:m ~f:(fun ~key ~data m ->
|
||||||
|
let key' = f key in
|
||||||
|
let data' = f_data data in
|
||||||
|
if Exp.equal key' key then
|
||||||
|
if equal_data data' data then m else Map.set m ~key ~data:data'
|
||||||
|
else
|
||||||
|
Map.remove m key
|
||||||
|
|> Map.add_exn ~key:key'
|
||||||
|
~data:(if data' == data then data else data') )
|
||||||
|
in
|
||||||
|
let exp_map = map ~equal_data:Exp.equal ~f_data:f in
|
||||||
|
let exp_list_map =
|
||||||
|
map ~equal_data:[%compare.equal: Exp.t list]
|
||||||
|
~f_data:(List.map_preserving_phys_equal ~f)
|
||||||
|
in
|
||||||
|
let rep' = exp_map rep in
|
||||||
|
let lkp' = exp_map lkp in
|
||||||
|
let cls' = exp_list_map cls in
|
||||||
|
let use' = exp_list_map use in
|
||||||
|
( if rep' == rep && lkp' == lkp && cls' == cls && use' == use then r
|
||||||
|
else {r with rep= rep'; lkp= lkp'; cls= cls'; use= use'} )
|
||||||
|
|>
|
||||||
|
[%Trace.retn fun {pf} -> pf "%a@." pp]
|
||||||
|
|
||||||
|
let rename r sub = map_exps r ~f:(fun e -> Exp.rename e sub)
|
||||||
|
|
||||||
|
let fold_exps r ~init ~f =
|
||||||
|
Map.fold r.lkp ~f:(fun ~key:_ ~data z -> f z data) ~init
|
||||||
|
|> fun init ->
|
||||||
|
Map.fold r.rep ~f:(fun ~key ~data z -> f (f z data) key) ~init
|
||||||
|
|
||||||
|
let fold_vars r ~init ~f =
|
||||||
|
fold_exps r ~init ~f:(fun init -> Exp.fold_vars ~f ~init)
|
||||||
|
|
||||||
|
let fv e = fold_vars e ~f:Set.add ~init:Var.Set.empty
|
||||||
|
|
||||||
|
let is_true {sat; rep; pnd; _} =
|
||||||
|
sat && Map.is_empty rep && List.is_empty pnd
|
||||||
|
|
||||||
|
let is_false {sat} = not sat
|
||||||
|
let classes {cls} = cls
|
||||||
|
|
||||||
|
let entails r s =
|
||||||
|
Map.for_alli s.rep ~f:(fun ~key:e ~data:e' -> mem_eq r e e')
|
||||||
|
|
||||||
|
(* a - b = k if a = b+k *)
|
||||||
|
let difference r a b =
|
||||||
|
[%Trace.call fun {pf} -> pf "%a@ %a@ %a" Exp.pp a Exp.pp b pp r]
|
||||||
|
;
|
||||||
|
let r, a = extend r a in
|
||||||
|
let r, b = extend r b in
|
||||||
|
let r = propagate r in
|
||||||
|
let ci = normalize r a in
|
||||||
|
let dj = normalize r b in
|
||||||
|
(* a - b = (c+i) - (d+j) *)
|
||||||
|
( match solve_for_base dj ci with
|
||||||
|
(* d = (c+i)-j = c+(i-j) & c = d *)
|
||||||
|
| d, App {op= App {op= Add; arg= c}; arg= Integer {data= i_j}}
|
||||||
|
when Exp.equal d c ->
|
||||||
|
(* a - b = (c+i) - (d+j) = i-j *)
|
||||||
|
Some i_j
|
||||||
|
| Integer {data= j}, Integer {data= i} -> Some (Z.sub i j)
|
||||||
|
| d, ci_j when Exp.equal d ci_j -> Some Z.zero
|
||||||
|
| _ -> None )
|
||||||
|
|>
|
||||||
|
[%Trace.retn fun {pf} ->
|
||||||
|
function
|
||||||
|
| Some d -> pf "%a" Z.pp_print d
|
||||||
|
| None -> pf "c+i: %a@ d+j: %a" Exp.pp ci Exp.pp dj]
|
||||||
|
|
||||||
|
let%test_module _ =
|
||||||
|
( module struct
|
||||||
|
let printf pp = Format.printf "@.%a@." pp
|
||||||
|
let ( ! ) i = Exp.integer (Z.of_int i)
|
||||||
|
|
||||||
|
let%expect_test _ =
|
||||||
|
printf pp {true_ with pnd= [(!0, !1)]} ;
|
||||||
|
[%expect
|
||||||
|
{| {sat= true; rep= []; lkp= []; cls= []; use= []; pend= 0 = 1;} |}]
|
||||||
|
end )
|
@ -0,0 +1,66 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2018-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** Constraints representing congruence relations *)
|
||||||
|
|
||||||
|
type t [@@deriving compare, sexp]
|
||||||
|
|
||||||
|
val pp : t pp
|
||||||
|
val pp_classes : t pp
|
||||||
|
|
||||||
|
include Invariant.S with type t := t
|
||||||
|
|
||||||
|
(** If [prefer a ~over:b] is positive, then [b] will not be used as the
|
||||||
|
representative of a class containing [a] and [b]. Similarly, if [prefer
|
||||||
|
a ~over:b] is negative, then [a] will not be used as the representative
|
||||||
|
of a class containing [a] and [b]. Otherwise the choice of
|
||||||
|
representative is unspecified, and made to be most efficient. *)
|
||||||
|
type prefer = Exp.t -> over:Exp.t -> int
|
||||||
|
|
||||||
|
val true_ : t
|
||||||
|
(** The diagonal relation, which only equates each exp with itself. *)
|
||||||
|
|
||||||
|
val merge : ?prefer:prefer -> t -> Exp.t -> Exp.t -> t
|
||||||
|
(** Merge the equivalence classes of exps together. If [prefer a ~over:b] is
|
||||||
|
positive, then [b] will not be used as the representative of a class
|
||||||
|
containing [a] and [b]. *)
|
||||||
|
|
||||||
|
val and_eq : ?prefer:prefer -> t -> Exp.t -> Exp.t -> t
|
||||||
|
|
||||||
|
val and_ : ?prefer:prefer -> t -> t -> t
|
||||||
|
(** Conjunction. *)
|
||||||
|
|
||||||
|
val or_ : ?prefer:prefer -> t -> t -> t
|
||||||
|
(** Disjunction. *)
|
||||||
|
|
||||||
|
val rename : t -> Var.Subst.t -> t
|
||||||
|
(** Apply a renaming substitution to the relation. *)
|
||||||
|
|
||||||
|
val fv : t -> Var.Set.t
|
||||||
|
(** The variables occurring in the exps of the relation. *)
|
||||||
|
|
||||||
|
val is_true : t -> bool
|
||||||
|
(** Test if the relation is diagonal. *)
|
||||||
|
|
||||||
|
val is_false : t -> bool
|
||||||
|
(** Test if the relation is empty / inconsistent. *)
|
||||||
|
|
||||||
|
val classes : t -> (Exp.t, Exp.t list, Exp.comparator_witness) Map.t
|
||||||
|
val entails : t -> t -> bool
|
||||||
|
|
||||||
|
val normalize : t -> Exp.t -> Exp.t
|
||||||
|
(** Normalize an exp [e] to [e'] such that [e = e'] is implied by the
|
||||||
|
relation, where [e'] and its sub-exps are expressed in terms of the
|
||||||
|
relation's canonical representatives of each equivalence-modulo-offset
|
||||||
|
class. *)
|
||||||
|
|
||||||
|
val difference : t -> Exp.t -> Exp.t -> Z.t option
|
||||||
|
(** The difference as an offset. [difference r a b = Some k] if [r] implies
|
||||||
|
[a = b+k], or [None] if [a] and [b] are not equal up to an integer
|
||||||
|
offset. *)
|
||||||
|
|
||||||
|
val fold_exps : t -> init:'a -> f:('a -> Exp.t -> 'a) -> 'a
|
@ -0,0 +1,184 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2018-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
let%test_module _ =
|
||||||
|
( module struct
|
||||||
|
open Congruence
|
||||||
|
|
||||||
|
let printf pp = Format.printf "@.%a@." pp
|
||||||
|
let of_eqs = List.fold ~init:true_ ~f:(fun r (a, b) -> and_eq r a b)
|
||||||
|
let mem_eq x y r = entails r (and_eq true_ x y)
|
||||||
|
let i8 = Typ.integer ~bits:8
|
||||||
|
let i64 = Typ.integer ~bits:64
|
||||||
|
let ( ! ) i = Exp.integer (Z.of_int i)
|
||||||
|
let ( + ) = Exp.add
|
||||||
|
let ( - ) = Exp.sub
|
||||||
|
let f = Exp.convert ~dst:i64 ~src:i8
|
||||||
|
let g = Exp.xor
|
||||||
|
let wrt = Var.Set.empty
|
||||||
|
let t_, wrt = Var.fresh "t" ~wrt
|
||||||
|
let u_, wrt = Var.fresh "u" ~wrt
|
||||||
|
let v_, wrt = Var.fresh "v" ~wrt
|
||||||
|
let w_, wrt = Var.fresh "w" ~wrt
|
||||||
|
let x_, wrt = Var.fresh "x" ~wrt
|
||||||
|
let y_, wrt = Var.fresh "y" ~wrt
|
||||||
|
let z_, _ = Var.fresh "z" ~wrt
|
||||||
|
let t = Exp.var t_
|
||||||
|
let u = Exp.var u_
|
||||||
|
let v = Exp.var v_
|
||||||
|
let w = Exp.var w_
|
||||||
|
let x = Exp.var x_
|
||||||
|
let y = Exp.var y_
|
||||||
|
let z = Exp.var z_
|
||||||
|
let f1 = of_eqs [(!0, !1)]
|
||||||
|
|
||||||
|
let%test _ = is_false f1
|
||||||
|
let%test _ = Map.is_empty (classes f1)
|
||||||
|
let%test _ = is_false (merge f1 !1 !1)
|
||||||
|
|
||||||
|
let f2 = of_eqs [(x, x + !1)]
|
||||||
|
|
||||||
|
let%test _ = is_false f2
|
||||||
|
|
||||||
|
let f3 = of_eqs [(x + !0, x + !1)]
|
||||||
|
|
||||||
|
let%test _ = is_false f3
|
||||||
|
|
||||||
|
let f4 = of_eqs [(x, y); (x + !0, y + !1)]
|
||||||
|
|
||||||
|
let%test _ = is_false f4
|
||||||
|
|
||||||
|
let t1 = of_eqs [(!1, !1)]
|
||||||
|
|
||||||
|
let%test _ = is_true t1
|
||||||
|
|
||||||
|
let t2 = of_eqs [(x, x)]
|
||||||
|
|
||||||
|
let%test _ = is_true t2
|
||||||
|
let%test _ = is_false (and_ f3 t2)
|
||||||
|
let%test _ = is_false (and_ t2 f3)
|
||||||
|
|
||||||
|
let r0 = true_
|
||||||
|
|
||||||
|
let%expect_test _ =
|
||||||
|
printf pp r0 ;
|
||||||
|
[%expect {| {sat= true; rep= []; lkp= []; cls= []; use= []} |}]
|
||||||
|
|
||||||
|
let%expect_test _ = printf pp_classes r0 ; [%expect {| |}]
|
||||||
|
let%test _ = difference r0 (f x) (f x) |> Poly.equal (Some (Z.of_int 0))
|
||||||
|
let%test _ = difference r0 !4 !3 |> Poly.equal (Some (Z.of_int 1))
|
||||||
|
|
||||||
|
let r1 = of_eqs [(x, y)]
|
||||||
|
|
||||||
|
let%test _ = not (Map.is_empty (classes r1))
|
||||||
|
|
||||||
|
let%expect_test _ =
|
||||||
|
printf pp r1 ;
|
||||||
|
[%expect
|
||||||
|
{|
|
||||||
|
{sat= true;
|
||||||
|
rep= [[%x_5 ↦ %y_6]];
|
||||||
|
lkp= [];
|
||||||
|
cls= [[%y_6 ↦ {%y_6, %x_5}]];
|
||||||
|
use= []} |}]
|
||||||
|
|
||||||
|
let%expect_test _ = printf pp_classes r1 ; [%expect {| %y_6 = %x_5 |}]
|
||||||
|
let%test _ = mem_eq x y r1
|
||||||
|
|
||||||
|
let r2 = of_eqs [(x, y); (f x, y); (f y, z)]
|
||||||
|
|
||||||
|
let%expect_test _ =
|
||||||
|
printf pp r2 ;
|
||||||
|
[%expect
|
||||||
|
{|
|
||||||
|
{sat= true;
|
||||||
|
rep= [[%x_5 ↦ %y_6]; [%z_7 ↦ %y_6]];
|
||||||
|
lkp= [];
|
||||||
|
cls= [[%y_6 ↦ {%z_7, %y_6, %x_5}]];
|
||||||
|
use= []} |}]
|
||||||
|
|
||||||
|
let%expect_test _ =
|
||||||
|
printf pp_classes r2 ; [%expect {| %y_6 = %z_7 = %x_5 |}]
|
||||||
|
|
||||||
|
let%test _ = mem_eq x z r2
|
||||||
|
let%test _ = mem_eq x y (or_ r1 r2)
|
||||||
|
let%test _ = entails (or_ r1 r2) r1
|
||||||
|
let%test _ = not (mem_eq x z (or_ r1 r2))
|
||||||
|
let%test _ = mem_eq x z (or_ f1 r2)
|
||||||
|
let%test _ = mem_eq x z (or_ r2 f3)
|
||||||
|
let%test _ = mem_eq (f x) (f z) r2
|
||||||
|
let%test _ = mem_eq (g x y) (g z y) r2
|
||||||
|
|
||||||
|
let%test _ =
|
||||||
|
mem_eq w z (rename r2 Var.Subst.(extend empty ~replace:x_ ~with_:w_))
|
||||||
|
|
||||||
|
let%test _ =
|
||||||
|
r2 == rename r2 Var.Subst.(extend empty ~replace:w_ ~with_:x_)
|
||||||
|
|
||||||
|
let%test _ =
|
||||||
|
mem_eq v w
|
||||||
|
(rename r2
|
||||||
|
Var.Subst.(
|
||||||
|
empty
|
||||||
|
|> extend ~replace:x_ ~with_:v_
|
||||||
|
|> extend ~replace:y_ ~with_:w_))
|
||||||
|
|
||||||
|
let%test _ = difference (or_ r1 r2) x z |> Poly.equal None
|
||||||
|
|
||||||
|
let r3 = of_eqs [(g y z, w); (v, w); (g y w, t); (x, v); (x, u); (u, z)]
|
||||||
|
|
||||||
|
let%expect_test _ =
|
||||||
|
printf pp r3 ;
|
||||||
|
[%expect
|
||||||
|
{|
|
||||||
|
{sat= true;
|
||||||
|
rep= [[%t_1 ↦ %w_4];
|
||||||
|
[%u_2 ↦ %w_4];
|
||||||
|
[%v_3 ↦ %w_4];
|
||||||
|
[%x_5 ↦ %w_4];
|
||||||
|
[%z_7 ↦ %w_4];
|
||||||
|
[(%y_6 xor %w_4) ↦ %w_4];
|
||||||
|
[(%y_6 xor %z_7) ↦ %w_4]];
|
||||||
|
lkp= [[(%y_6 xor %w_4) ↦ (%y_6 xor %w_4)];
|
||||||
|
[(%y_6 xor %z_7) ↦ (%y_6 xor %z_7)];
|
||||||
|
[(xor %y_6) ↦ (xor %y_6)]];
|
||||||
|
cls= [[%w_4
|
||||||
|
↦ {(%y_6 xor %w_4), %t_1, %z_7, %u_2, %x_5, %v_3, %w_4,
|
||||||
|
(%y_6 xor %z_7)}]];
|
||||||
|
use= [[%w_4 ↦ {(%y_6 xor %w_4)}];
|
||||||
|
[%y_6 ↦ {(xor %y_6)}];
|
||||||
|
[(xor %y_6) ↦ {(%y_6 xor %w_4), (%y_6 xor %z_7)}]]} |}]
|
||||||
|
|
||||||
|
let%test _ = mem_eq t z r3
|
||||||
|
let%test _ = mem_eq x z r3
|
||||||
|
let%test _ = mem_eq x z (and_ r2 r3)
|
||||||
|
|
||||||
|
let r4 = of_eqs [(w + !2, x - !3); (x - !5, y + !7); (y, z - !4)]
|
||||||
|
|
||||||
|
let%test _ = mem_eq x (w + !5) r4
|
||||||
|
let%test _ = difference r4 x w |> Poly.equal (Some (Z.of_int 5))
|
||||||
|
|
||||||
|
let r5 = of_eqs [(x, y); (g w x, y); (g w y, f z)]
|
||||||
|
|
||||||
|
let%test _ =
|
||||||
|
Set.equal (fv r5) (Set.of_list (module Var) [w_; x_; y_; z_])
|
||||||
|
|
||||||
|
let r6 = of_eqs [(x, !1); (!1, y)]
|
||||||
|
|
||||||
|
let%test _ = mem_eq x y r6
|
||||||
|
|
||||||
|
let r7 = of_eqs [(v, x); (w, z); (y, z)]
|
||||||
|
|
||||||
|
let%test _ = normalize (merge r7 x z) w |> Exp.equal z
|
||||||
|
|
||||||
|
let%test _ =
|
||||||
|
normalize (merge ~prefer:(fun e ~over:f -> Exp.compare f e) r7 x z) w
|
||||||
|
|> Exp.equal x
|
||||||
|
|
||||||
|
let%test _ = mem_eq (g w x) (g w z) (of_eqs [(g w x, g y z); (x, z)])
|
||||||
|
let%test _ = mem_eq (g w x) (g w z) (of_eqs [(g w x, g y w); (x, z)])
|
||||||
|
end )
|
Loading…
Reference in new issue