Skip to content

Commit 5b1d0fb

Browse files
authored
feat(ADT): model generation (#1093)
* Model generation for ADT theory This PR implements the model generation for ADT. The model generation is done by the casesplit mechanism in `Adt_rel`. To ensure the termination of the model generation, we sorts the constructors of nests during the parsing pass.
1 parent 89f7072 commit 5b1d0fb

30 files changed

Lines changed: 703 additions & 124 deletions

src/lib/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
Emap Gc_debug Hconsing Hstring Heap Lists Loc
6262
MyUnix Numbers Uqueue
6363
Options Timers Util Vec Version Steps Printer My_zip
64-
Theories
64+
Theories Nest
6565
)
6666

6767
(js_of_ocaml

src/lib/frontend/d_cnf.ml

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -586,10 +586,11 @@ and handle_ty_app ?(update = false) ty_c l =
586586
let mk_ty_decl (ty_c: DE.ty_cst) =
587587
match DT.definition ty_c with
588588
| Some (
589-
Adt { cases = [| { cstr = { id_ty; _ } as cstr; dstrs; _ } |]; _ }
589+
Adt { cases = [| { cstr = { id_ty; _ } as cstr; dstrs; _ } |]; _ } as adt
590590
) ->
591591
(* Records and adts that only have one case are treated in the same way,
592592
and considered as records. *)
593+
Nest.add_nest [adt];
593594
let tyvl = Cache.store_ty_vars_ret id_ty in
594595
let rev_lbs =
595596
Array.fold_left (
@@ -610,9 +611,8 @@ let mk_ty_decl (ty_c: DE.ty_cst) =
610611
let ty = Ty.trecord ~record_constr tyvl (Uid.of_dolmen ty_c) lbs in
611612
Cache.store_ty ty_c ty
612613

613-
| Some (
614-
(Adt { cases; _ } as _adt)
615-
) ->
614+
| Some ((Adt { cases; _ } as adt)) ->
615+
Nest.add_nest [adt];
616616
let uid = Uid.of_dolmen ty_c in
617617
let tyvl = Cache.store_ty_vars_ret cases.(0).cstr.id_ty in
618618
let rev_cs, is_enum =
@@ -624,7 +624,7 @@ let mk_ty_decl (ty_c: DE.ty_cst) =
624624
if Array.length dstrs = 0
625625
then true
626626
else (
627-
let ty = Ty.t_adt (Uid.of_dolmen ty_c) tyvl in
627+
let ty = Ty.t_adt uid tyvl in
628628
Cache.store_ty ty_c ty;
629629
false
630630
)
@@ -711,10 +711,7 @@ let mk_mr_ty_decls (tdl: DE.ty_cst list) =
711711
in
712712
Cache.store_ty ty_c ty
713713

714-
| Tadt (hs, tyl),
715-
Some (
716-
Adt { cases; ty = ty_c; _ }
717-
) ->
714+
| Tadt (hs, tyl), Some (Adt { cases; ty = ty_c; _ }) ->
718715
let rev_cs =
719716
Array.fold_left (
720717
fun accl DE.{ cstr; dstrs; _ } ->
@@ -744,19 +741,21 @@ let mk_mr_ty_decls (tdl: DE.ty_cst list) =
744741
List.fold_left (
745742
fun (acc, ca) ty_c ->
746743
match DT.definition ty_c with
747-
| Some (Adt { record; cases; _ }) as df
744+
| Some (Adt { record; cases; _ } as df)
748745
when not record && Array.length cases > 1 ->
749746
df :: acc, true
750-
| df -> df :: acc, ca
747+
| Some (Adt _ as df) ->
748+
df :: acc, ca
749+
| Some Abstract | None ->
750+
assert false
751751
) ([], false) tdl
752752
in
753+
Nest.add_nest rev_tdefs;
753754
let rev_l =
754755
List.fold_left (
755756
fun acc tdef ->
756757
match tdef with
757-
| Some (
758-
(DE.Adt { cases; record; ty = ty_c; }) as adt
759-
) ->
758+
| DE.Adt { cases; record; ty = ty_c; } as adt ->
760759
let tyvl = Cache.store_ty_vars_ret cases.(0).cstr.id_ty in
761760

762761
let cns, is_enum =
@@ -784,8 +783,7 @@ let mk_mr_ty_decls (tdl: DE.ty_cst list) =
784783
Cache.store_ty ty_c ty;
785784
(ty, Some adt) :: acc
786785
)
787-
| None
788-
| Some Abstract ->
786+
| Abstract ->
789787
assert false (* unreachable in the second iteration *)
790788
) [] (List.rev rev_tdefs)
791789
in
@@ -1009,8 +1007,7 @@ let rec mk_expr
10091007

10101008
| B.Constructor _ ->
10111009
let ty = dty_to_ty term_ty in
1012-
let sy = Sy.Op (Sy.Constr (Uid.of_dolmen tcst)) in
1013-
E.mk_term sy [] ty
1010+
E.mk_constr (Uid.of_dolmen tcst) [] ty
10141011

10151012
| _ -> unsupported "Constant term %a" DE.Term.print term
10161013
end
@@ -1379,7 +1376,7 @@ let rec mk_expr
13791376
let ty = dty_to_ty term_ty in
13801377
begin match ty with
13811378
| Ty.Tadt (_, _) ->
1382-
let sy = Sy.Op (Sy.Constr (Uid.of_dolmen tcst)) in
1379+
let sy = Sy.constr @@ Uid.of_dolmen tcst in
13831380
let l = List.map (fun t -> aux_mk_expr t) args in
13841381
E.mk_term sy l ty
13851382
| Ty.Trecord _ ->

src/lib/reasoners/adt.ml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,17 @@ module Shostak (X : ALIEN) = struct
401401

402402

403403
let assign_value _ _ _ =
404-
Printer.print_err
405-
"[ADTs.models] assign_value currently not implemented";
406-
raise (Util.Not_implemented "Models for ADTs")
407-
408-
let to_model_term _r =
409-
Printer.print_err
410-
"[ADTs.models] to_model_term currently not implemented";
411-
raise (Util.Not_implemented "Models for ADTs")
404+
(* Model generation is performed by the case-split mechanism
405+
in [Adt_rel]. *)
406+
None
407+
408+
let to_model_term r =
409+
match embed r with
410+
| Constr { c_name; c_ty; c_args } ->
411+
let args = Lists.try_map (fun (_, arg) -> X.to_model_term arg) c_args in
412+
Option.bind args @@ fun args ->
413+
Some (E.mk_constr c_name args c_ty)
414+
415+
| Select _ -> None
416+
| Alien a -> X.to_model_term a
412417
end

src/lib/reasoners/adt_rel.ml

Lines changed: 98 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,50 @@ module LR = Uf.LX
3636
module Th = Shostak.Adt
3737
module SLR = Set.Make(LR)
3838

39+
module TSet = Set.Make
40+
(struct
41+
type t = Uid.t
42+
43+
(* We use a dedicated total order on the constructors to ensure
44+
the termination of the model generation. *)
45+
let compare = Nest.compare
46+
end)
47+
3948
let timer = Timers.M_Adt
4049

4150
module Domain = struct
4251
(* Set of possible constructors. The explanation justifies that any value
4352
assigned to the semantic value has to use a constructor lying in the
4453
domain. *)
4554
type t = {
46-
constrs : Uid.Set.t;
55+
constrs : TSet.t;
4756
ex : Ex.t;
4857
}
4958

5059
exception Inconsistent of Ex.t
5160

61+
let[@inline always] cardinal { constrs; _ } = TSet.cardinal constrs
62+
63+
let[@inline always] choose { constrs; _ } =
64+
(* We choose the minimal element to ensure the termination of
65+
model generation. *)
66+
TSet.min_elt constrs
67+
5268
let[@inline always] as_singleton { constrs; ex } =
53-
if Uid.Set.cardinal constrs = 1 then
54-
Some (Uid.Set.choose constrs, ex)
69+
if TSet.cardinal constrs = 1 then
70+
Some (TSet.choose constrs, ex)
5571
else
5672
None
5773

5874
let domain ~constrs ex =
59-
if Uid.Set.is_empty constrs then
75+
if TSet.is_empty constrs then
6076
raise @@ Inconsistent ex
6177
else
6278
{ constrs; ex }
6379

64-
let[@inline always] singleton ~ex c = { constrs = Uid.Set.singleton c; ex }
80+
let[@inline always] singleton ~ex c = { constrs = TSet.singleton c; ex }
6581

66-
let[@inline always] subset d1 d2 = Uid.Set.subset d1.constrs d2.constrs
82+
let[@inline always] subset d1 d2 = TSet.subset d1.constrs d2.constrs
6783

6884
let unknown ty =
6985
match ty with
@@ -73,31 +89,31 @@ module Domain = struct
7389
let constrs =
7490
List.fold_left
7591
(fun acc Ty.{ constr; _ } ->
76-
Uid.Set.add constr acc
77-
) Uid.Set.empty body
92+
TSet.add constr acc
93+
) TSet.empty body
7894
in
79-
assert (not @@ Uid.Set.is_empty constrs);
95+
assert (not @@ TSet.is_empty constrs);
8096
{ constrs; ex = Ex.empty }
8197
| _ ->
8298
(* Only ADT values can have a domain. This case shouldn't happen since
8399
we check the type of semantic values in both [add] and [assume]. *)
84100
assert false
85101

86-
let equal d1 d2 = Uid.Set.equal d1.constrs d2.constrs
102+
let equal d1 d2 = TSet.equal d1.constrs d2.constrs
87103

88104
let pp ppf d =
89105
Fmt.(braces @@
90-
iter ~sep:comma Uid.Set.iter Uid.pp) ppf d.constrs;
106+
iter ~sep:comma TSet.iter Uid.pp) ppf d.constrs;
91107
if Options.(get_verbose () || get_unsat_core ()) then
92108
Fmt.pf ppf " %a" (Fmt.box Ex.print) d.ex
93109

94110
let intersect ~ex d1 d2 =
95-
let constrs = Uid.Set.inter d1.constrs d2.constrs in
111+
let constrs = TSet.inter d1.constrs d2.constrs in
96112
let ex = ex |> Ex.union d1.ex |> Ex.union d2.ex in
97113
domain ~constrs ex
98114

99115
let remove ~ex c d =
100-
let constrs = Uid.Set.remove c d.constrs in
116+
let constrs = TSet.remove c d.constrs in
101117
let ex = Ex.union ex d.ex in
102118
domain ~constrs ex
103119
end
@@ -209,6 +225,8 @@ module Domains = struct
209225
) t.changed acc
210226
in
211227
acc, { t with changed = SX.empty }
228+
229+
let fold f t = MX.fold f t.domains
212230
end
213231

214232
let calc_destructor d e uf =
@@ -327,7 +345,7 @@ let assume_th_elt t th_elt _ =
327345
This function alone isn't sufficient to produce a complete decision
328346
procedure for the ADT theory. For instance, let's assume we have three
329347
semantic values [r1], [r2] and [r3] whose the domain is `{C1, C2}`. It's
330-
clear that `(distinct r1 r2 r3)` is unsatisfiable but we haven't enough
348+
clear that `(distinct r1 r2 r3)` is unsatisfiable but we have not enough
331349
information to discover this contradiction.
332350
333351
We plan to support model generation for ADT. As a by-product, we will got
@@ -566,45 +584,81 @@ let constr_of_destr ty d =
566584

567585
exception Found of X.r * Uid.t
568586

569-
(* Pick a delayed destructor application in [env.delayed]. Returns [None]
570-
if there is no delayed destructor. *)
571-
let pick_delayed_destructor env =
572-
try
573-
Rel_utils.Delayed.iter_delayed
574-
(fun r sy _e ->
575-
match sy with
576-
| Sy.Destruct d ->
577-
raise_notrace @@ Found (r, d)
578-
| _ ->
579-
()
580-
) env.delayed;
581-
None
582-
with Found (r, d) -> Some (r, d)
587+
let (let*) = Option.bind
583588

584-
(* Do a case-split by choosing a semantic value [r] and constructor [c]
589+
(* Do a cases-plit by choosing a semantic value [r] and constructor [c]
585590
for which there are delayed destructor applications and propagate the
586591
literal [(not (_ is c) r)]. *)
592+
let split_delayed_destructor env =
593+
if not @@ Options.get_enable_adts_cs () then
594+
None
595+
else
596+
try
597+
Rel_utils.Delayed.iter_delayed
598+
(fun r sy _e ->
599+
match sy with
600+
| Sy.Destruct destr -> raise_notrace @@ Found (r, destr)
601+
| _ -> ()
602+
) env.delayed;
603+
None
604+
with Found (r, d) ->
605+
let c = constr_of_destr (X.type_info r) d in
606+
Some (LR.mkv_builtin false (Sy.IsConstr c) [r])
607+
608+
(* Pick a constructor in a tracked domain with minimal cardinal.
609+
Returns [None] if there is no such constructor. *)
610+
let pick_best ds =
611+
let* _, r, c =
612+
Domains.fold
613+
(fun r d best ->
614+
let cd = Domain.cardinal d in
615+
match Th.embed r, best with
616+
| Constr _, _ -> best
617+
| _, Some (n, _, _) when n <= cd -> best
618+
| _ ->
619+
let c = Domain.choose d in
620+
Some (cd, r, c)
621+
) ds None
622+
in
623+
Some (r, c)
624+
625+
let split_best_domain ~for_model uf =
626+
if not for_model then
627+
None
628+
else
629+
let ds = Uf.(GlobalDomains.find (module Domains) @@ domains uf) in
630+
let* r, c = pick_best ds in
631+
let _, cons = Option.get @@ build_constr_eq r c in
632+
(* In the current implementation of `X.make`, we produce
633+
a nonempty context only for interpreted semantic values
634+
of the `Arith` and `Records` theories. The semantic
635+
values `cons` never involves such values. *)
636+
let nr, ctx = X.make cons in
637+
assert (Lists.is_empty ctx);
638+
Some (LR.mkv_eq r nr)
639+
640+
let next_case_split ~for_model env uf =
641+
match split_delayed_destructor env with
642+
| Some _ as r -> r
643+
| None -> split_best_domain ~for_model uf
644+
587645
let case_split env uf ~for_model =
588-
if Options.get_disable_adts () || not (Options.get_enable_adts_cs())
589-
then
646+
if Options.get_disable_adts () then
590647
[]
591648
else begin
592-
assert (not for_model);
593-
if Options.get_debug_adt () then
594-
Debug.pp_domains "before cs"
595-
(Uf.GlobalDomains.find (module Domains) (Uf.domains uf));
596-
match pick_delayed_destructor env with
597-
| Some (r, d) ->
598-
if Options.get_debug_adt () then
649+
match next_case_split ~for_model env uf with
650+
| Some cs ->
651+
if Options.get_debug_adt () then begin
652+
Debug.pp_domains "before cs"
653+
(Uf.GlobalDomains.find (module Domains) (Uf.domains uf));
599654
Printer.print_dbg ~flushed:false
600655
~module_name:"Adt_rel" ~function_name:"case_split"
601-
"found r = %a and d = %a@ " X.print r Uid.pp d;
602-
(* CS on negative version would be better in general. *)
603-
let c = constr_of_destr (X.type_info r) d in
604-
let cs = LR.mkv_builtin false (Sy.IsConstr c) [r] in
605-
[ cs, true, Th_util.CS (Th_util.Th_adt, two) ]
656+
"Assume %a" (Xliteral.print_view X.print) cs;
657+
end;
658+
[ cs, true, Th_util.CS (Th_util.Th_adt, two)]
606659
| None ->
607-
Debug.no_case_split ();
660+
if Options.get_debug_adt () then
661+
Debug.no_case_split ();
608662
[]
609663
end
610664

src/lib/reasoners/enum_rel.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ let tighten_domain rr nd domains =
268268
This function alone isn't sufficient to produce a complete decision
269269
procedure for the Enum theory. For instance, let's assume we have three
270270
semantic values [r1], [r2] and [r3] whose the domain is `{C1, C2}`. It's
271-
clear that `(distinct r1 r2 r3)` is unsatisfiable but we haven't enough
271+
clear that `(distinct r1 r2 r3)` is unsatisfiable but we have not enough
272272
information to discover this contradiction.
273273
274274
Now, if we produce a case-split for one of these semantic values,

src/lib/reasoners/fun_sat.ml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,10 +1275,6 @@ module Make (Th : Theory.S) = struct
12751275
match Options.get_instantiation_heuristic () with
12761276
| INormal ->
12771277
(* TODO: check if this test still produces a wrong model. *)
1278-
(* S: This seems spurious!
1279-
On example UFDT/20170428-Barrett/cdt-cade2015/data/gandl/cotree/
1280-
x2015_09_10_16_49_52_978_1009894.smt_in.smt2,
1281-
this returns a wrong model. *)
12821278
update_model_and_return_unknown
12831279
env (Options.get_last_interpretation ())
12841280
~unknown_reason:Incomplete (* may becomes ModelGen *)
@@ -1885,7 +1881,8 @@ module Make (Th : Theory.S) = struct
18851881
Expr.reinit_cache ();
18861882
Hstring.reinit_cache ();
18871883
Shostak.Combine.reinit_cache ();
1888-
Uf.reinit_cache ()
1884+
Uf.reinit_cache ();
1885+
Nest.reinit ()
18891886

18901887
let () =
18911888
Steps.save_steps ();

0 commit comments

Comments
 (0)