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.

791 lines
24 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

(*
* 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.
*)
(** Symbolic Heap Formulas *)
open Fol
[@@@warning "+9"]
type seg = {loc: Term.t; bas: Term.t; len: Term.t; siz: Term.t; seq: Term.t}
[@@deriving compare, equal, sexp]
type starjunction =
{ us: Var.Set.t
; xs: Var.Set.t
; ctx: Context.t [@ignore]
; pure: Formula.t
; heap: seg list
; djns: disjunction list }
[@@deriving compare, equal, sexp]
and disjunction = starjunction list
type t = starjunction [@@deriving compare, equal, sexp]
(** Basic values *)
let emp =
{ us= Var.Set.empty
; xs= Var.Set.empty
; ctx= Context.empty
; pure= Formula.tt
; heap= []
; djns= [] }
let false_ us = {emp with us; djns= [[]]}
(** Traversals *)
let map_seg ~f h =
let loc = f h.loc in
let bas = f h.bas in
let len = f h.len in
let siz = f h.siz in
let seq = f h.seq in
if
loc == h.loc
&& bas == h.bas
&& len == h.len
&& siz == h.siz
&& seq == h.seq
then h
else {loc; bas; len; siz; seq}
let map ~f_sjn ~f_ctx ~f_trm ~f_fml ({us; xs= _; ctx; pure; heap; djns} as q)
=
let pure = f_fml pure in
if Formula.(equal ff pure) then false_ us
else
let ctx = f_ctx ctx in
let heap = List.map_endo heap ~f:(map_seg ~f:f_trm) in
let djns = List.map_endo djns ~f:(List.map_endo ~f:f_sjn) in
if ctx == q.ctx && pure == q.pure && heap == q.heap && djns == q.djns
then q
else {q with ctx; pure; heap; djns}
let fold_terms_seg {loc; bas; len; siz; seq} ~init ~f =
let f b s = f s b in
f loc (f bas (f len (f siz (f seq init))))
let fold_vars_seg seg ~init ~f =
fold_terms_seg seg ~init ~f:(fun init -> Term.fold_vars ~f ~init)
let fold_vars_stem ?ignore_ctx ?ignore_pure
{us= _; xs= _; ctx; pure; heap; djns= _} ~init ~f =
let unless flag f init = if Option.is_some flag then init else f ~init in
List.fold ~f:(fun init -> fold_vars_seg ~f ~init) heap ~init
|> unless ignore_pure (Formula.fold_vars ~f pure)
|> unless ignore_ctx (Context.fold_vars ~f ctx)
let fold_vars ?ignore_ctx ?ignore_pure fold_vars q ~init ~f =
fold_vars_stem ?ignore_ctx ?ignore_pure ~init ~f q
|> fun init ->
List.fold ~init q.djns ~f:(fun init -> List.fold ~init ~f:fold_vars)
(** Pretty-printing *)
let rec var_strength_ xs m q =
let add m v =
match Var.Map.find m v with
| None -> Var.Map.set m ~key:v ~data:`Anonymous
| Some `Anonymous -> Var.Map.set m ~key:v ~data:`Existential
| Some _ -> m
in
let xs = Var.Set.union xs q.xs in
let m_stem =
fold_vars_stem ~ignore_ctx:() q ~init:m ~f:(fun m var ->
if not (Var.Set.mem xs var) then
Var.Map.set m ~key:var ~data:`Universal
else add m var )
in
let m =
List.fold ~init:m_stem q.djns ~f:(fun m djn ->
let ms = List.map ~f:(fun dj -> snd (var_strength_ xs m dj)) djn in
List.reduce_balanced ms ~f:(fun m1 m2 ->
Var.Map.merge_skewed m1 m2 ~combine:(fun ~key:_ s1 s2 ->
match (s1, s2) with
| `Anonymous, `Anonymous -> `Anonymous
| `Universal, _ | _, `Universal -> `Universal
| `Existential, _ | _, `Existential -> `Existential ) )
|> Option.value ~default:m )
in
(m_stem, m)
let var_strength ?(xs = Var.Set.empty) q =
let m =
Var.Set.fold xs ~init:Var.Map.empty ~f:(fun m x ->
Var.Map.set m ~key:x ~data:`Existential )
in
var_strength_ xs m q
let pp_chunk x fs (siz, seq) = Term.ppx x fs (Term.sized ~siz ~seq)
let pp_seg x fs {loc; bas; len; siz; seq} =
let term_pp = Term.ppx x in
Format.fprintf fs "@[<2>%a@ @[@[-[%a)->@]@ %a@]@]" term_pp loc
(fun fs (bas, len) ->
if (not (Term.equal loc bas)) || not (Term.equal len siz) then
Format.fprintf fs " %a, %a " term_pp bas term_pp len )
(bas, len) (pp_chunk x) (siz, seq)
let pp_seg_norm ctx fs seg =
let x _ = None in
pp_seg x fs (map_seg seg ~f:(Context.normalize ctx))
let pp_block x fs segs =
let is_full_alloc segs =
match segs with
| {loc; bas; len; _} :: _ -> (
Term.equal loc bas
&&
match Term.d_int len with
| Some data -> (
match
List.fold segs ~init:(Some Z.zero) ~f:(fun len seg ->
match (len, Term.d_int seg.siz) with
| Some len, Some data -> Some (Z.add len data)
| _ -> None )
with
| Some blk_len -> Z.equal data blk_len
| _ -> false )
| _ -> false )
| [] -> false
in
let term_pp = Term.ppx x in
let pp_chunks =
List.pp "@,^" (fun fs seg -> pp_chunk x fs (seg.siz, seg.seq))
in
match segs with
| {loc; bas; len; _} :: _ ->
Format.fprintf fs "@[<2>%a@ @[@[-[%t)->@]@ @[%a@]@]@]" term_pp loc
(fun fs ->
if not (is_full_alloc segs) then
Format.fprintf fs " %a, %a " term_pp bas term_pp len )
pp_chunks segs
| [] -> ()
let pp_heap x ?pre ctx fs heap =
let bas_off e =
match Term.const_of e with
| Some const -> (Term.sub e (Term.rational const), const)
| None -> (e, Q.zero)
in
let compare s1 s2 =
[%compare: Term.t * (Term.t * Q.t)]
(Context.normalize ctx s1.bas, bas_off (Context.normalize ctx s1.loc))
(Context.normalize ctx s2.bas, bas_off (Context.normalize ctx s2.loc))
in
let break s1 s2 =
(not (Term.equal s1.bas s2.bas))
|| (not (Term.equal s1.len s2.len))
|| not
(Context.implies ctx (Formula.eq (Term.add s1.loc s1.siz) s2.loc))
in
let heap = List.map heap ~f:(map_seg ~f:(Context.normalize ctx)) in
let blocks = List.group ~break (List.sort ~compare heap) in
List.pp ?pre "@ * " (pp_block x) fs blocks
let pp_us ?(pre = ("" : _ fmt)) ?vs () fs us =
match vs with
| None ->
if not (Var.Set.is_empty us) then
[%Trace.fprintf fs "%( %)@[%a@] .@ " pre Var.Set.pp us]
| Some vs ->
if not (Var.Set.equal vs us) then
[%Trace.fprintf
fs "%( %)@[%a@] .@ " pre (Var.Set.pp_diff Var.pp) (vs, us)]
let rec pp_ ?var_strength vs parent_xs parent_ctx fs
{us; xs; ctx; pure; heap; djns} =
Format.pp_open_hvbox fs 0 ;
let x v = Option.bind ~f:(fun (_, m) -> Var.Map.find m v) var_strength in
pp_us ~vs () fs us ;
let xs_d_vs, xs_i_vs =
Var.Set.diff_inter
(Var.Set.filter xs ~f:(fun v -> Poly.(x v <> Some `Anonymous)))
vs
in
if not (Var.Set.is_empty xs_i_vs) then (
Format.fprintf fs "@<2>∃ @[%a@] ." (Var.Set.ppx x) xs_i_vs ;
if not (Var.Set.is_empty xs_d_vs) then Format.fprintf fs "@ " ) ;
if not (Var.Set.is_empty xs_d_vs) then
Format.fprintf fs "@<2>∃ @[%a@] .@ " (Var.Set.ppx x) xs_d_vs ;
let first =
if Option.is_some var_strength then
Context.ppx_diff x fs parent_ctx pure ctx
else (
Format.fprintf fs "@[ %a@]" Formula.pp pure ;
false )
in
if List.is_empty heap then
Format.fprintf fs
( if first then if List.is_empty djns then " emp" else ""
else "@ @<5>∧ emp" )
else pp_heap x ~pre:(if first then " " else "@ @<2>∧ ") ctx fs heap ;
let first = first && List.is_empty heap in
List.pp
~pre:(if first then " " else "@ * ")
"@ * "
(pp_djn ?var_strength
(Var.Set.union vs (Var.Set.union us xs))
(Var.Set.union parent_xs xs)
ctx)
fs djns ;
Format.pp_close_box fs ()
and pp_djn ?var_strength vs xs ctx fs = function
| [] -> Format.fprintf fs "false"
| djn ->
Format.fprintf fs "@[<hv>( %a@ )@]"
(List.pp "@ @<2> " (fun fs sjn ->
let var_strength =
let+ var_strength_stem, _ = var_strength in
var_strength_ xs var_strength_stem sjn
in
Format.fprintf fs "@[<hv 1>(%a)@]"
(pp_ ?var_strength vs (Var.Set.union xs sjn.xs) ctx)
sjn ))
djn
let pp_diff_eq ?(us = Var.Set.empty) ?(xs = Var.Set.empty) ctx fs q =
pp_ ~var_strength:(var_strength ~xs q) us xs ctx fs q
let pp fs q = pp_diff_eq Context.empty fs q
let pp_djn fs d =
pp_djn ?var_strength:None Var.Set.empty Var.Set.empty Context.empty fs d
let pp_raw fs q =
pp_ ?var_strength:None Var.Set.empty Var.Set.empty Context.empty fs q
let fv_seg seg = fold_vars_seg seg ~f:Var.Set.add ~init:Var.Set.empty
let fv ?ignore_ctx ?ignore_pure q =
let rec fv_union init q =
Var.Set.diff
(fold_vars ?ignore_ctx ?ignore_pure fv_union q ~init ~f:Var.Set.add)
q.xs
in
fv_union Var.Set.empty q
let invariant_pure p = assert (not Formula.(equal ff p))
let invariant_seg _ = ()
let rec invariant q =
let@ () = Invariant.invariant [%here] q [%sexp_of: t] in
let {us; xs; ctx; pure; heap; djns} = q in
try
assert (
Var.Set.disjoint us xs
|| fail "inter: @[%a@]@\nq: @[%a@]" Var.Set.pp (Var.Set.inter us xs)
pp q () ) ;
assert (
Var.Set.is_subset (fv q) ~of_:us
|| fail "unbound but free: %a" Var.Set.pp (Var.Set.diff (fv q) us) ()
) ;
Context.invariant ctx ;
( match djns with
| [[]] ->
assert (Context.is_empty ctx) ;
assert (Formula.(equal tt pure)) ;
assert (List.is_empty heap)
| _ -> assert (not (Context.is_unsat ctx)) ) ;
invariant_pure pure ;
List.iter heap ~f:invariant_seg ;
List.iter djns ~f:(fun djn ->
List.iter djn ~f:(fun sjn ->
assert (Var.Set.is_subset sjn.us ~of_:(Var.Set.union us xs)) ;
invariant sjn ) )
with exc ->
[%Trace.info "%a" pp q] ;
raise exc
(** Quantification and Vocabulary *)
(** primitive application of a substitution, ignores us and xs, may violate
invariant *)
let rec apply_subst sub q =
map q ~f_sjn:(rename sub)
~f_ctx:(fun r -> Context.rename r sub)
~f_trm:(Term.rename sub) ~f_fml:(Formula.rename sub)
|> check (fun q' ->
assert (Var.Set.disjoint (fv q') (Var.Subst.domain sub)) )
and rename_ Var.Subst.{sub; dom; rng} q =
[%Trace.call fun {pf} ->
pf "@[%a@]@ %a" Var.Subst.pp sub pp q ;
assert (Var.Set.is_subset dom ~of_:q.us)]
;
( if Var.Subst.is_empty sub then q
else
let us = Var.Set.union (Var.Set.diff q.us dom) rng in
assert (not (Var.Set.equal us q.us)) ;
let q' = apply_subst sub (freshen_xs q ~wrt:(Var.Set.union dom us)) in
{q' with us} )
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp q' ;
invariant q' ;
assert (Var.Set.disjoint q'.us (Var.Subst.domain sub))]
and rename sub q =
[%Trace.call fun {pf} -> pf "@[%a@]@ %a" Var.Subst.pp sub pp q]
;
rename_ (Var.Subst.restrict sub q.us) q
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp q' ;
invariant q' ;
assert (Var.Set.disjoint q'.us (Var.Subst.domain sub))]
(** freshen existentials, preserving vocabulary *)
and freshen_xs q ~wrt =
[%Trace.call fun {pf} ->
pf "{@[%a@]}@ %a" Var.Set.pp wrt pp q ;
assert (Var.Set.is_subset q.us ~of_:wrt)]
;
let Var.Subst.{sub; dom; rng}, _ = Var.Subst.freshen q.xs ~wrt in
( if Var.Subst.is_empty sub then q
else
let xs = Var.Set.union (Var.Set.diff q.xs dom) rng in
let q' = apply_subst sub q in
if xs == q.xs && q' == q then q else {q' with xs} )
|>
[%Trace.retn fun {pf} q' ->
pf "%a@ %a" Var.Subst.pp sub pp q' ;
assert (Var.Set.equal q'.us q.us) ;
assert (Var.Set.disjoint q'.xs (Var.Subst.domain sub)) ;
assert (Var.Set.disjoint q'.xs (Var.Set.inter q.xs wrt)) ;
invariant q']
let extend_us us q =
let us = Var.Set.union us q.us in
(if us == q.us then q else {(freshen_xs q ~wrt:us) with us})
|> check invariant
let freshen q ~wrt =
[%Trace.call fun {pf} -> pf "{@[%a@]}@ %a" Var.Set.pp wrt pp q]
;
let xsub, _ = Var.Subst.freshen q.us ~wrt:(Var.Set.union wrt q.xs) in
let q' = extend_us wrt (rename_ xsub q) in
(q', xsub.sub)
|>
[%Trace.retn fun {pf} (q', _) ->
pf "%a" pp q' ;
invariant q' ;
assert (Var.Set.is_subset wrt ~of_:q'.us) ;
assert (Var.Set.disjoint wrt (fv q'))]
let bind_exists q ~wrt =
[%Trace.call fun {pf} -> pf "{@[%a@]}@ %a" Var.Set.pp wrt pp q]
;
let q' =
if Var.Set.is_empty wrt then q
else freshen_xs q ~wrt:(Var.Set.union q.us wrt)
in
(q'.xs, {q' with us= Var.Set.union q'.us q'.xs; xs= Var.Set.empty})
|>
[%Trace.retn fun {pf} (_, q') -> pf "%a" pp q']
let exists_fresh xs q =
[%Trace.call fun {pf} ->
pf "{@[%a@]}@ %a" Var.Set.pp xs pp q ;
assert (
Var.Set.disjoint xs q.us
|| fail "Sh.exists_fresh xs ∩ q.us: %a" Var.Set.pp
(Var.Set.inter xs q.us) () )]
;
( if Var.Set.is_empty xs then q
else {q with xs= Var.Set.union q.xs xs} |> check invariant )
|>
[%Trace.retn fun {pf} -> pf "%a" pp]
let exists xs q =
[%Trace.call fun {pf} -> pf "{@[%a@]}@ %a" Var.Set.pp xs pp q]
;
assert (
Var.Set.is_subset xs ~of_:q.us
|| fail "Sh.exists xs - q.us: %a" Var.Set.pp (Var.Set.diff xs q.us) ()
) ;
( if Var.Set.is_empty xs then q
else
{q with us= Var.Set.diff q.us xs; xs= Var.Set.union q.xs xs}
|> check invariant )
|>
[%Trace.retn fun {pf} -> pf "%a" pp]
(** remove quantification on variables disjoint from vocabulary *)
let elim_exists xs q =
assert (Var.Set.disjoint xs q.us) ;
{q with us= Var.Set.union q.us xs; xs= Var.Set.diff q.xs xs}
(** Construct *)
(** conjoin an FOL context assuming vocabulary is compatible *)
let and_ctx_ ctx q =
assert (Var.Set.is_subset (Context.fv ctx) ~of_:q.us) ;
let xs, ctx = Context.union (Var.Set.union q.us q.xs) q.ctx ctx in
if Context.is_unsat ctx then false_ q.us else exists_fresh xs {q with ctx}
let and_ctx ctx q =
[%Trace.call fun {pf} -> pf "%a@ %a" Context.pp ctx pp q]
;
( match q.djns with
| [[]] -> q
| _ -> and_ctx_ ctx (extend_us (Context.fv ctx) q) )
|>
[%Trace.retn fun {pf} q ->
pf "%a" pp q ;
invariant q]
let star q1 q2 =
[%Trace.call fun {pf} -> pf "(%a)@ (%a)" pp q1 pp q2]
;
( match (q1, q2) with
| {djns= [[]]; _}, _ | _, {djns= [[]]; _} ->
false_ (Var.Set.union q1.us q2.us)
| {us= _; xs= _; ctx; pure; heap= []; djns= []}, _
when Context.is_empty ctx && Formula.(equal tt pure) ->
let us = Var.Set.union q1.us q2.us in
if us == q2.us then q2 else {q2 with us}
| _, {us= _; xs= _; ctx; pure; heap= []; djns= []}
when Context.is_empty ctx && Formula.(equal tt pure) ->
let us = Var.Set.union q1.us q2.us in
if us == q1.us then q1 else {q1 with us}
| _ ->
let us = Var.Set.union q1.us q2.us in
let q1 = freshen_xs q1 ~wrt:(Var.Set.union us q2.xs) in
let q2 = freshen_xs q2 ~wrt:(Var.Set.union us q1.xs) in
let {us= us1; xs= xs1; ctx= c1; pure= p1; heap= h1; djns= d1} = q1 in
let {us= us2; xs= xs2; ctx= c2; pure= p2; heap= h2; djns= d2} = q2 in
assert (Var.Set.equal us (Var.Set.union us1 us2)) ;
let xs, ctx =
Context.union (Var.Set.union us (Var.Set.union xs1 xs2)) c1 c2
in
if Context.is_unsat ctx then false_ us
else
exists_fresh xs
{ us
; xs= Var.Set.union xs1 xs2
; ctx
; pure= Formula.and_ p1 p2
; heap= List.append h1 h2
; djns= List.append d1 d2 } )
|>
[%Trace.retn fun {pf} q ->
pf "%a" pp q ;
invariant q ;
assert (Var.Set.equal q.us (Var.Set.union q1.us q2.us))]
let starN = function
| [] -> emp
| [q] -> q
| q :: qs -> List.fold ~f:star ~init:q qs
let or_ q1 q2 =
[%Trace.call fun {pf} -> pf "(%a)@ (%a)" pp_raw q1 pp_raw q2]
;
( match (q1, q2) with
| {djns= [[]]; _}, _ -> extend_us q1.us q2
| _, {djns= [[]]; _} -> extend_us q2.us q1
| ( ({djns= []; _} as q)
, ({us= _; xs; ctx= _; pure; heap= []; djns= [djn]} as d) )
when Var.Set.is_empty xs && Formula.(equal tt pure) ->
{d with us= Var.Set.union q.us d.us; djns= [q :: djn]}
| ( ({us= _; xs; ctx= _; pure; heap= []; djns= [djn]} as d)
, ({djns= []; _} as q) )
when Var.Set.is_empty xs && Formula.(equal tt pure) ->
{d with us= Var.Set.union q.us d.us; djns= [q :: djn]}
| _ ->
{ us= Var.Set.union q1.us q2.us
; xs= Var.Set.empty
; ctx= Context.empty
; pure= Formula.tt
; heap= []
; djns= [[q1; q2]] } )
|>
[%Trace.retn fun {pf} q ->
pf "%a" pp_raw q ;
invariant q ;
assert (Var.Set.equal q.us (Var.Set.union q1.us q2.us))]
let orN = function
| [] -> false_ Var.Set.empty
| [q] -> q
| q :: qs -> List.fold ~f:or_ ~init:q qs
let pure (p : Formula.t) =
[%Trace.call fun {pf} -> pf "%a" Formula.pp p]
;
Iter.fold (Context.dnf p) ~init:(false_ Var.Set.empty)
~f:(fun q (xs, pure, ctx) ->
let us = Formula.fv pure in
if Context.is_unsat ctx then extend_us us q
else or_ q (exists_fresh xs {emp with us; ctx; pure}) )
|>
[%Trace.retn fun {pf} q ->
pf "%a" pp q ;
invariant q]
let and_ e q = star (pure e) q
let and_subst subst q =
[%Trace.call fun {pf} -> pf "%a@ %a" Context.Subst.pp subst pp q]
;
Context.Subst.fold
~f:(fun ~key ~data -> and_ (Formula.eq key data))
subst ~init:q
|>
[%Trace.retn fun {pf} q ->
pf "%a" pp q ;
invariant q]
let subst sub q =
[%Trace.call fun {pf} -> pf "@[%a@]@ %a" Var.Subst.pp sub pp q]
;
let dom, eqs =
Var.Subst.fold sub ~init:(Var.Set.empty, Formula.tt)
~f:(fun var trm (dom, eqs) ->
( Var.Set.add dom var
, Formula.and_ (Formula.eq (Term.var var) (Term.var trm)) eqs ) )
in
exists dom (and_ eqs q)
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp q' ;
invariant q' ;
assert (Var.Set.disjoint q'.us (Var.Subst.domain sub))]
let seg pt =
let us = fv_seg pt in
if Term.equal Term.zero pt.loc then false_ us
else {emp with us; heap= [pt]} |> check invariant
(** Update *)
let rem_seg seg q =
{q with heap= List.remove_exn q.heap seg} |> check invariant
let filter_heap ~f q =
{q with heap= List.filter q.heap ~f} |> check invariant
(** Query *)
let rec is_empty q =
List.is_empty q.heap && List.for_all ~f:(List.for_all ~f:is_empty) q.djns
let rec pure_approx q =
Formula.andN
( [q.pure]
|> fun init ->
List.fold ~init q.heap ~f:(fun p seg -> Formula.dq0 seg.loc :: p)
|> fun init ->
List.fold ~init q.djns ~f:(fun p djn ->
Formula.orN (List.map djn ~f:pure_approx) :: p ) )
let pure_approx q =
[%Trace.call fun {pf} -> pf "%a" pp q]
;
pure_approx q
|>
[%Trace.retn fun {pf} -> pf "%a" Formula.pp]
let is_false q = Context.refutes q.ctx (pure_approx q)
let fold_dnf ~conj ~disj sjn (xs, conjuncts) disjuncts =
let rec add_disjunct pending_splits sjn (xs, conjuncts) disjuncts =
let ys, sjn = bind_exists sjn ~wrt:xs in
let xs = Var.Set.union ys xs in
let djns = sjn.djns in
let sjn = {sjn with djns= []} in
split_case
(Iter.append (Iter.of_list djns) pending_splits)
(xs, conj sjn conjuncts)
disjuncts
and split_case pending_splits (xs, conjuncts) disjuncts =
match Iter.pop pending_splits with
| Some (split, pending_splits) ->
List.fold split ~init:disjuncts ~f:(fun disjuncts sjn ->
add_disjunct pending_splits sjn (xs, conjuncts) disjuncts )
| None -> disj (xs, conjuncts) disjuncts
in
add_disjunct Iter.empty sjn (xs, conjuncts) disjuncts
let dnf q =
[%Trace.call fun {pf} -> pf "%a" pp q]
;
let conj sjn conjuncts = sjn :: conjuncts in
let disj (xs, conjuncts) disjuncts =
exists xs (starN conjuncts) :: disjuncts
in
fold_dnf ~conj ~disj q (Var.Set.empty, []) []
|>
[%Trace.retn fun {pf} -> pf "%a" pp_djn]
(** Simplify *)
let rec norm_ s q =
[%Trace.call fun {pf} -> pf "@[%a@]@ %a" Context.Subst.pp s pp_raw q]
;
let q =
map q ~f_sjn:(norm_ s) ~f_ctx:Fn.id ~f_trm:(Context.Subst.subst s)
~f_fml:(Formula.map_terms ~f:(Context.Subst.subst s))
in
let xs, ctx = Context.apply_subst (Var.Set.union q.us q.xs) s q.ctx in
exists_fresh xs {q with ctx}
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp_raw q' ;
invariant q']
let norm s q =
[%Trace.call fun {pf} -> pf "@[%a@]@ %a" Context.Subst.pp s pp_raw q]
;
(if Context.Subst.is_empty s then q else norm_ s q)
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp_raw q' ;
invariant q']
(** rename existentially quantified variables to avoid shadowing, and reduce
quantifier scopes by sinking them as low as possible into disjunctions *)
let rec freshen_nested_xs q =
[%Trace.call fun {pf} -> pf "%a" pp q]
;
(* trim xs to those that appear in the stem and sink the rest *)
let fv_stem = fv {q with xs= Var.Set.empty; djns= []} in
let xs_sink, xs = Var.Set.diff_inter q.xs fv_stem in
let xs_below, djns =
List.fold_map ~init:Var.Set.empty q.djns ~f:(fun xs_below djn ->
List.fold_map ~init:xs_below djn ~f:(fun xs_below dj ->
(* quantify xs not in stem and freshen disjunct *)
let dj' =
freshen_nested_xs (exists (Var.Set.inter xs_sink dj.us) dj)
in
let xs_below' = Var.Set.union xs_below dj'.xs in
(xs_below', dj') ) )
in
(* rename xs to miss all xs in subformulas *)
freshen_xs {q with xs; djns} ~wrt:(Var.Set.union q.us xs_below)
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp q' ;
invariant q']
let rec propagate_context_ ancestor_vs ancestor_ctx q =
[%Trace.call fun {pf} -> pf "(%a)@ %a" Context.pp ancestor_ctx pp q]
;
(* extend vocabulary with variables in scope above *)
let ancestor_vs = Var.Set.union ancestor_vs (Var.Set.union q.us q.xs) in
(* decompose formula *)
let xs, stem, djns =
(q.xs, {q with us= ancestor_vs; xs= emp.xs; djns= emp.djns}, q.djns)
in
(* strengthen context with that from above *)
let ancestor_stem = and_ctx_ ancestor_ctx stem in
let ancestor_ctx = ancestor_stem.ctx in
exists xs
(List.fold djns ~init:ancestor_stem ~f:(fun q' djn ->
let dj_ctxs, djn =
List.rev_map_unzip djn ~f:(fun dj ->
let dj = propagate_context_ ancestor_vs ancestor_ctx dj in
(dj.ctx, dj) )
in
let new_xs, djn_ctx = Context.interN ancestor_vs dj_ctxs in
(* hoist xs appearing in disjunction's context *)
let djn_xs = Var.Set.diff (Context.fv djn_ctx) q'.us in
let djn = List.map ~f:(elim_exists djn_xs) djn in
let ctx_djn = and_ctx_ djn_ctx (orN djn) in
assert (is_false ctx_djn || Var.Set.is_subset new_xs ~of_:djn_xs) ;
star (exists djn_xs ctx_djn) q' ))
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp q' ;
invariant q']
let propagate_context ancestor_vs ancestor_ctx q =
[%Trace.call fun {pf} -> pf "(%a)@ %a" Context.pp ancestor_ctx pp q]
;
propagate_context_ ancestor_vs ancestor_ctx q
|>
[%Trace.retn fun {pf} q' ->
pf "%a" pp q' ;
invariant q']
let pp_vss fs vss =
Format.fprintf fs "[@[%a@]]"
(List.pp ";@ " (fun fs vs -> Format.fprintf fs "{@[%a@]}" Var.Set.pp vs))
vss
let remove_absent_xs ks q =
let ks = Var.Set.inter ks q.xs in
if Var.Set.is_empty ks then q
else
let xs = Var.Set.diff q.xs ks in
let ctx = Context.elim ks q.ctx in
let djns =
let rec trim_ks ks djns =
List.map djns ~f:(fun djn ->
List.map djn ~f:(fun sjn ->
{ sjn with
us= Var.Set.diff sjn.us ks
; ctx= Context.elim ks sjn.ctx
; djns= trim_ks ks sjn.djns } ) )
in
trim_ks ks q.djns
in
{q with xs; ctx; djns}
let rec simplify_ us rev_xss q =
[%Trace.call fun {pf} -> pf "%a@ %a" pp_vss (List.rev rev_xss) pp_raw q]
;
let rev_xss = q.xs :: rev_xss in
(* recursively simplify subformulas *)
let q =
exists q.xs
(starN
( {q with us= Var.Set.union q.us q.xs; xs= emp.xs; djns= []}
:: List.map q.djns ~f:(fun djn ->
orN (List.map djn ~f:(fun sjn -> simplify_ us rev_xss sjn)) )
))
in
(* try to solve equations in ctx for variables in xss *)
let subst = Context.solve_for_vars (us :: List.rev rev_xss) q.ctx in
(* simplification can reveal inconsistency *)
( if is_false q then false_ q.us
else if Context.Subst.is_empty subst then q
else
(* normalize wrt solutions *)
let q = norm subst q in
(* reconjoin only non-redundant equations *)
let removed =
Var.Set.diff
(Var.Set.union_list rev_xss)
(fv ~ignore_ctx:() (elim_exists q.xs q))
in
let keep, removed, _ = Context.Subst.partition_valid removed subst in
let q = and_subst keep q in
(* remove the eliminated variables from xs and subformulas' us *)
remove_absent_xs removed q )
|>
[%Trace.retn fun {pf} q' ->
pf "%a@ %a" Context.Subst.pp subst pp_raw q' ;
invariant q']
let simplify q =
[%Trace.call fun {pf} -> pf "%a" pp_raw q]
;
let q = freshen_nested_xs q in
let q = propagate_context Var.Set.empty Context.empty q in
let q = simplify_ q.us [] q in
q
|>
[%Trace.retn fun {pf} q' ->
pf "@\n" ;
invariant q']