Skip to content

Commit cabdf4a

Browse files
committed
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`. - If we turn model generation on, we performs casesplits even if the flag `--enable-adts-cs` isn't present in the command line. - The termination of the model generation is a bit tricky in the case of mutually recursive ADT. Please see the tests added for some complicated examples. I hope that I caught all the corner cases. To ensure the termination, the basic idea is to sort ADT's constructors in the module `Ty` during the parsing and to use the fact that the SMT-LIB standard only accepts well-founded ADT. We choose constructors in domains with the following order: - Constructor with the less destructors using the same nest; - Constructor with the less destructors using another nest of the same mutually recursive declaration;
1 parent 107122a commit cabdf4a

19 files changed

Lines changed: 364 additions & 60 deletions

src/bin/common/solving_loop.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ let main () =
579579
st
580580
| ":produce-models", Symbol { name = Simple "true"; _ } ->
581581
Options.set_interpretation ILast;
582+
Options.set_enable_adts_cs true;
582583
st
583584
| ":produce-models", Symbol { name = Simple "false"; _ } ->
584585
Options.set_interpretation INone;

src/lib/reasoners/adt.ml

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

406406

407407
let assign_value _ _ _ =
408-
Printer.print_err
409-
"[ADTs.models] assign_value currently not implemented";
410-
raise (Util.Not_implemented "Models for ADTs")
411-
412-
let to_model_term _r =
413-
Printer.print_err
414-
"[ADTs.models] to_model_term currently not implemented";
415-
raise (Util.Not_implemented "Models for ADTs")
408+
(* Model generation is performed by the casesplit mechanism
409+
in [Adt_rel]. *)
410+
None
411+
412+
let to_model_term r =
413+
match embed r with
414+
| Constr { c_name; c_ty; c_args } ->
415+
let args = Lists.try_map (fun (_, arg) -> X.to_model_term arg) c_args in
416+
Option.bind args @@ fun args ->
417+
Some (E.mk_term Sy.(Op (Constr c_name)) args c_ty)
418+
419+
| Select _ -> None
420+
| Alien a -> X.to_model_term a
416421
end

src/lib/reasoners/adt_rel.ml

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ module Domain = struct
5252

5353
exception Inconsistent of Ex.t
5454

55+
let[@inline always] cardinal { constrs; _ } = HSS.cardinal constrs
56+
57+
let[@inline always] choose { constrs; _ } = HSS.choose constrs
58+
5559
let[@inline always] as_singleton { constrs; ex } =
5660
if HSS.cardinal constrs = 1 then
5761
Some (HSS.choose constrs, ex)
@@ -202,6 +206,8 @@ module Domains = struct
202206
) t.changed acc
203207
in
204208
acc, { t with changed = SX.empty }
209+
210+
let iter f t = MX.iter f t.domains
205211
end
206212

207213
let calc_destructor d e uf =
@@ -603,8 +609,12 @@ let pick_delayed_destructor env =
603609
Rel_utils.Delayed.iter_delayed
604610
(fun r sy _e ->
605611
match sy with
606-
| Sy.Destruct d ->
607-
raise_notrace @@ Found (r, d)
612+
| Sy.Destruct destr ->
613+
let d = Domains.get r env.domains in
614+
if Domain.cardinal d > 1 then
615+
raise_notrace @@ Found (r, destr)
616+
else
617+
()
608618
| _ ->
609619
()
610620
) env.delayed;
@@ -615,11 +625,31 @@ let pick_delayed_destructor env =
615625
for which there are delayed destructor applications and propagate the
616626
literal [(not (_ is c) r)]. *)
617627
let case_split env _uf ~for_model =
618-
if Options.get_disable_adts () || not (Options.get_enable_adts_cs())
628+
if Options.get_disable_adts ()
629+
|| not (Options.get_enable_adts_cs () || for_model)
619630
then
620631
[]
632+
else if for_model then
633+
try
634+
Domains.iter
635+
(fun r d ->
636+
if Domain.cardinal d > 1 then
637+
let c = Domain.choose d in
638+
raise_notrace @@ Found (r, c)
639+
) env.domains;
640+
[]
641+
with Found (r, c) ->
642+
match build_constr_eq r c with
643+
| Some (_, cons) ->
644+
let nr, _ = X.make cons in
645+
let cs = LR.mkv_eq r nr in
646+
if Options.get_debug_adt () then
647+
Printer.print_dbg ~flushed:false
648+
~module_name:"Adt_rel" ~function_name:"case_split"
649+
"Assume %a = %a" X.print r Hstring.print c;
650+
[ cs, true, Th_util.CS (Th_util.Th_adt, two) ]
651+
| None -> assert false
621652
else begin
622-
assert (not for_model);
623653
if Options.get_debug_adt () then Debug.pp_env "before cs" env;
624654
match pick_delayed_destructor env with
625655
| Some (r, d) ->

src/lib/reasoners/shostak.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,8 @@ struct
443443
not (Options.get_restricted ()) &&
444444
(RECORDS.is_mine_symb sb ty ||
445445
BITV.is_mine_symb sb ty ||
446-
ENUM.is_mine_symb sb ty)
446+
ENUM.is_mine_symb sb ty ||
447+
ADT.is_mine_symb sb ty)
447448

448449
let is_a_leaf r = match r.v with
449450
| Term _ | Ac _ -> true

src/lib/reasoners/uf.ml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,10 @@ type cache = {
10681068
to ensure we don't generate twice an abstract value for a given symbol. *)
10691069
}
10701070

1071+
let is_destructor = function
1072+
| Sy.Op (Destruct _) -> true
1073+
| _ -> false
1074+
10711075
(* The environment of the union-find contains almost a first-order model.
10721076
There are two situations that require some computations to retrieve an
10731077
appropriate model value:
@@ -1084,9 +1088,9 @@ let compute_concrete_model_of_val cache =
10841088
and get_abstract_for = Cache.get_abstract_for cache.abstracts
10851089
in fun env t ((mdl, mrepr) as acc) ->
10861090
let { E.f; xs; ty; _ } = E.term_view t in
1087-
if X.is_solvable_theory_symbol f ty
1088-
|| Sy.is_internal f || E.is_internal_name t || E.is_internal_skolem t
1089-
|| E.equal t E.vrai || E.equal t E.faux
1091+
if X.is_solvable_theory_symbol f ty || is_destructor f
1092+
|| Sy.is_internal f || E.is_internal_name t || E.is_internal_skolem t
1093+
|| E.equal t E.vrai || E.equal t E.faux
10901094
then
10911095
(* These terms are built-in interpreted ones and we don't have
10921096
to produce a definition for them. *)

src/lib/structures/ty.ml

Lines changed: 96 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -463,54 +463,55 @@ module Decls = struct
463463
MH.add name {decl = (params, body); instances = MTY.empty} !decls
464464

465465
let body name args =
466+
let {decl = (params, body); instances} = MH.find name !decls in
466467
try
467-
let {decl = (params, body); instances} = MH.find name !decls in
468-
try
469-
if compare_list params args = 0 then body
470-
else MTY.find args instances
471-
(* should I instantiate if not found ?? *)
472-
with Not_found ->
473-
let params, body = fresh_type params body in
474-
(*if true || get_debug_adt () then*)
475-
let sbt =
476-
try
477-
List.fold_left2
478-
(fun sbt vty ty ->
479-
let vty = shorten vty in
480-
match vty with
481-
| Tvar { value = Some _ ; _ } -> assert false
482-
| Tvar {v ; value = None} ->
483-
if equal vty ty then sbt else M.add v ty sbt
484-
| _ ->
485-
Printer.print_err "vty = %a and ty = %a"
486-
print vty print ty;
487-
assert false
488-
)M.empty params args
489-
with Invalid_argument _ -> assert false
490-
in
491-
let body = match body with
492-
| Adt cases ->
493-
Adt(
494-
List.map
495-
(fun {constr; destrs} ->
496-
{constr;
497-
destrs =
498-
List.map (fun (d, ty) -> d, apply_subst sbt ty) destrs }
499-
) cases
500-
)
501-
in
502-
let params = List.map (fun ty -> apply_subst sbt ty) params in
503-
add name params body;
504-
body
468+
if compare_list params args = 0 then body
469+
else MTY.find args instances
470+
(* should I instantiate if not found ?? *)
505471
with Not_found ->
506-
Printer.print_err "%a not found" Hstring.print name;
507-
assert false
472+
let params, body = fresh_type params body in
473+
(*if true || get_debug_adt () then*)
474+
let sbt =
475+
try
476+
List.fold_left2
477+
(fun sbt vty ty ->
478+
let vty = shorten vty in
479+
match vty with
480+
| Tvar { value = Some _ ; _ } -> assert false
481+
| Tvar {v ; value = None} ->
482+
if equal vty ty then sbt else M.add v ty sbt
483+
| _ ->
484+
Printer.print_err "vty = %a and ty = %a"
485+
print vty print ty;
486+
assert false
487+
)M.empty params args
488+
with Invalid_argument _ -> assert false
489+
in
490+
let body = match body with
491+
| Adt cases ->
492+
Adt(
493+
List.map
494+
(fun {constr; destrs} ->
495+
{constr;
496+
destrs =
497+
List.map (fun (d, ty) -> d, apply_subst sbt ty) destrs }
498+
) cases
499+
)
500+
in
501+
let params = List.map (fun ty -> apply_subst sbt ty) params in
502+
add name params body;
503+
body
508504

509505
let reinit () = decls := MH.empty
510506

511507
end
512508

513-
let type_body name args = Decls.body name args
509+
let type_body name args =
510+
try
511+
Decls.body name args
512+
with Not_found ->
513+
Printer.print_err "%a not found" Hstring.print name;
514+
assert false
514515

515516

516517
(* smart constructors *)
@@ -524,6 +525,56 @@ let fresh_empty_text =
524525

525526
let tsum s lc = Tsum (Hstring.make s, List.map Hstring.make lc)
526527

528+
(* Count the number of occurences of the nest of [name] in the signature
529+
of payload [l]. *)
530+
let count_same_nest name l =
531+
Lists.sum
532+
(fun (_, ty) ->
533+
match ty with
534+
| Tadt (name', _) when Hstring.equal name name' -> 1
535+
| _ -> 0
536+
) l
537+
538+
(* Count the number of occurences of the (mutually recursive) ADT type of
539+
[name] in the signature of payload [l]. *)
540+
let count_same_adt name l =
541+
Lists.sum
542+
(fun (_, ty) ->
543+
match ty with
544+
| Tadt (name', params) ->
545+
(* TODO: this is a hackish way to check that `name'` and `name` are
546+
two nests of the same mutually recursive ADT. We should store
547+
ADT's nests in a data structure as it's done in Dolmen. *)
548+
begin try
549+
let Adt cases = Decls.body name' params in
550+
List.exists
551+
(fun { destrs; _ } ->
552+
List.exists
553+
(fun (_, ty') ->
554+
match ty' with
555+
| Tadt (name'', _) -> Hstring.equal name name''
556+
| _ -> false
557+
) destrs
558+
) cases
559+
|> Bool.to_int
560+
with Not_found ->
561+
(* If we haven't already register the nest [name'], it means that
562+
[name] and [name'] are two nests of the same ADT. *)
563+
1
564+
end
565+
| _ -> 0
566+
) l
567+
568+
(* Comparison function used to ensure the termination of the model
569+
generation for recursive ADT values. *)
570+
let cons_weight name (_, l1) (_, l2) =
571+
let c = count_same_nest name l1 - count_same_nest name l2 in
572+
if c <> 0 then c
573+
else
574+
let c = count_same_adt name l1 - count_same_adt name l2 in
575+
if c <> 0 then c
576+
else List.compare_lengths l1 l2
577+
527578
let t_adt ?(body=None) s ty_vars =
528579
let hs = Hstring.make s in
529580
let ty = Tadt (hs, ty_vars) in
@@ -545,12 +596,13 @@ let t_adt ?(body=None) s ty_vars =
545596
Decls.add hs ty_vars (Adt cases)
546597
| Some cases ->
547598
let cases =
548-
List.map (fun (s, l) ->
599+
List.stable_sort (cons_weight hs) cases |>
600+
List.map (fun (c, l) ->
549601
let l =
550602
List.map (fun (d, e) -> Hstring.make d, e) l
551603
in
552-
{constr = Hstring.make s; destrs = l}
553-
) cases
604+
{constr = Hstring.make c; destrs = l}
605+
)
554606
in
555607
Decls.add hs ty_vars (Adt cases)
556608
end;

src/lib/util/lists.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,6 @@ let rec is_sorted cmp l =
6464
match l with
6565
| x :: y :: xs -> cmp x y <= 0 && is_sorted cmp (y :: xs)
6666
| [_] | [] -> true
67+
68+
let sum f l =
69+
List.fold_left (fun sum i -> sum + f i) 0 l

src/lib/util/lists.mli

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ val try_map : ('a -> 'b option) -> 'a list -> 'b list option
5555
val is_sorted : ('a -> 'a -> int) -> 'a list -> bool
5656
(** [is_sorted cmp l] checks that [l] is sorted for the comparison function
5757
[cmp]. *)
58+
59+
val sum : ('a -> int) -> 'a list -> int
60+
(** [sum f l] computes the sum [f a1 + f a2 + ...] where
61+
[l = [a1; a2; ...]]. *)

0 commit comments

Comments
 (0)