Skip to content

Commit 7bb1e28

Browse files
hanno-beckerike-mulder-aws
authored andcommitted
Crush improvements: add wp split rules, automatic eta-expansion, more branch control
Various improvements to crush proof automation: 1. Definitions added to `simp [prems,concls] add:` now work for definitions of the form `some_def \equiv \lambda x. `: these are now automatically eta-expanded 2. Add a `wp split:` parameter to `crush`. This accepts the same arguments as `split:`, but only applies them when the top-level expression in a WP statement get stuck on the split. 3. Guard the crush branches `crush_branch_base_simps_early_unfold` and `crush_branch_base_detect_aentails_contradiction` with a configuration option. This is helpful when debugging/speeding up long-running crush calls. 4. Change SSA normalization from top-down to bottom-up. This avoids having to define new SSA rules for additional datatypes. 5. No longer drop premises marked `ASSUMPTION` in the MePo filter 6. Add a configuration option `crush_stop_at_full_blown_clarsimp` to make crush stop whenever it would otherwise apply a full-blown clarsimp. This makes it easier to figure out why such last-resort full-blown clarsimps happen. Signed-off-by: Ike Mulder <ikemul@amazon.com>
1 parent f1c0dd4 commit 7bb1e28

12 files changed

Lines changed: 240 additions & 297 deletions

File tree

Crush/Examples.thy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,19 @@ lemma
646646
irrelevant premises:\<close>
647647
apply (tactic \<open>MePo_Prem.drop_irrelevant_prems_tac @{context} 2 1\<close>)
648648
oops
649+
650+
\<comment>\<open>The MePo filter never drops premises wrapped as \<^verbatim>\<open>ASSUMPTION\<close>:\<close>
651+
lemma
652+
assumes \<open>x < 42\<close>
653+
and \<open>ASSUMPTION (g (g (f t0)) = s0)\<close>
654+
and \<open>g (f t1) = s1\<close>
655+
and \<open>x + y < 10*z\<close>
656+
shows \<open>10*z < 20\<close>
657+
using assms apply -
658+
\<comment>\<open>Keeps \<^verbatim>\<open>(g (g (f t0)) = s0)\<close> which would otherwise have been thrown out.\<close>
659+
apply (tactic \<open>MePo_Prem.ignore_irrelevant_prems_tac @{context} 2 1\<close>)
660+
oops
661+
649662
end
650663

651664
subsubsection\<open>Conversions\<close>

Crush/Separation_Logic_Tactics.thy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ ML\<open>
2929
val _ = Theory.setup (Named_Theorems.declare @{binding "crush_concls_simps"} "" #> snd |> Named_Target.theory_map)
3030
val _ = Theory.setup (Named_Theorems.declare @{binding "crush_aentails_rules"} "" #> snd |> Named_Target.theory_map)
3131
val _ = Theory.setup (Named_Theorems.declare @{binding "crush_aentails_drules"} "" #> snd |> Named_Target.theory_map)
32+
val _ = Theory.setup (Named_Theorems.declare @{binding "crush_wp_split_simps"} "" #> snd |> Named_Target.theory_map)
3233
val _ = Theory.setup (Named_Theorems.declare @{binding "crush_aentails_crules"} "" #> snd |> Named_Target.theory_map)
3334
val _ = Theory.setup (Named_Theorems.declare @{binding "crush_asepconj_simp"} "" #> snd |> Named_Target.theory_map)
3435
val _ = Theory.setup (Named_Theorems.declare @{binding "crush_specs_eager"} "" #> snd |> Named_Target.theory_map)

Crush/base.ML

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ sig
6565
val force_meta_eq : thm -> thm
6666
val force_meta_eqs : thm list -> thm list
6767

68+
\<comment>\<open>Eta expand definitional theorem so there's no toplevel \<^verbatim>\<open>\<lambda>x. _\<close> on the RHS\<close>
69+
val eta_expand: thm -> thm
70+
val eta_expands: thm list -> thm list
71+
6872
val intro_tac : thm list -> Proof.context -> int -> tactic
6973
val elim_tac : thm list -> Proof.context -> int -> tactic
7074

@@ -424,6 +428,20 @@ struct
424428
fun force_meta_eq (t : thm): thm = t RS eq_reflection handle THM _ => t
425429
val force_meta_eqs : thm list -> thm list = List.map force_meta_eq
426430

431+
fun eta_expand thm =
432+
let
433+
val num_abs = thm
434+
|> Thm.prop_of
435+
|> Logic.dest_equals
436+
|> snd
437+
|> Term.strip_abs
438+
|> fst
439+
|> length
440+
in
441+
funpow num_abs (fn t => t RS @{thm meta_fun_cong}) thm
442+
end
443+
val eta_expands : thm list -> thm list = List.map eta_expand
444+
427445
fun filter_to_conv (f : cterm -> bool) : conv = fn t =>
428446
if f t then Conv.all_conv t else Conv.no_conv t
429447

@@ -533,6 +551,9 @@ local
533551
val _ = Theory.setup (Attrib.setup @{binding "meta_all_intro"}
534552
(Thm.rule_attribute [] (K Thm.forall_intr_vars) |> Scan.succeed) "")
535553

554+
val _ = Theory.setup (Attrib.setup @{binding "unabs_def"}
555+
(Thm.rule_attribute [] (K Crush_Base.eta_expand) |> Scan.succeed) "")
556+
536557
fun all_intro ctxt thm =
537558
let val ctxt = Context.proof_of ctxt
538559
val thm' = Thm.forall_intr_vars thm

Crush/config.ML

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,16 @@ sig
5757

5858
val enable_branch_safe : bool Config.T
5959
val enable_branch_clarsimp : bool Config.T
60+
val enable_branch_clarsimp_warn_at_success : bool Config.T
61+
val enable_branch_clarsimp_stop_at_success : bool Config.T
6062
val enable_branch_filtered_clarsimp : bool Config.T
6163
val enable_branch_reduced_clarsimp : bool Config.T
6264
val enable_schematics : bool Config.T
6365
val enable_branch_split : bool Config.T
6466

67+
val enable_base_simps_detect_aentails_contradiction : bool Config.T
68+
val enable_base_simps_early_unfold : bool Config.T
69+
6570
val enable_urust_split_branch : bool Config.T
6671
val enable_urust_determine_branch : bool Config.T
6772

@@ -118,6 +123,8 @@ struct
118123

119124
val enable_branch_safe = Attrib.setup_config_bool @{binding "use_safe"} (K true)
120125
val enable_branch_clarsimp = Attrib.setup_config_bool @{binding "crush_use_clarsimp"} (K true)
126+
val enable_branch_clarsimp_stop_at_success = Attrib.setup_config_bool @{binding "crush_stop_at_full_blown_clarsimp"} (K false)
127+
val enable_branch_clarsimp_warn_at_success = Attrib.setup_config_bool @{binding "crush_warn_at_full_blown_clarsimp"} (K true)
121128
(* TODO: Consider enabling this by default *)
122129
val enable_branch_filtered_clarsimp = Attrib.setup_config_bool @{binding "crush_use_filtered_clarsimp"} (K false)
123130
(* TODO: Consider enabling this by default *)
@@ -143,6 +150,9 @@ struct
143150

144151
val fail_on_bad_schematic = Attrib.setup_config_bool @{binding "crush_fail_on_bad_schematic"} (K true)
145152

153+
val enable_base_simps_early_unfold = Attrib.setup_config_bool @{binding "crush_enable_early_unfold"} (K true)
154+
val enable_base_simps_detect_aentails_contradiction = Attrib.setup_config_bool @{binding "crush_enable_contradiction_detection"} (K true)
155+
146156
val relevance_filter_max_facts = Attrib.setup_config_int @{binding "crush_relevance_filter_max_facts"} (K 20)
147157

148158
val simp_general_implies_prems = Attrib.setup_config_bool @{binding "crush_simp_general_implies_prems"} (K false)

Crush/crush.ML

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,13 @@ struct
150150

151151
val get_urust_contract_simpset = Context.Proof #> URustContractSimps.get
152152

153+
val crush_register_wp_split_attrib = Thm.declaration_attribute (fn t => fn ctxt =>
154+
let val t' = Separation_Logic_Tactics.specialize_split_rule_to_wp_single
155+
(Context.proof_of ctxt) t
156+
in ctxt |> Named_Theorems.add_thm @{named_theorems "crush_wp_split_simps"} t'
157+
end
158+
)
159+
153160
val simp_focus_basic_tac : Proof.context -> int -> tactic = fn ctxt =>
154161
let val focus_intros =
155162
Named_Theorems.get ctxt @{named_theorems focus_intros} @
@@ -173,17 +180,23 @@ struct
173180

174181
val crush_branch_base_simps_early_unfold : Proof.context -> int -> tactic = fn ctxt =>
175182
let val early_simps = Named_Theorems.get ctxt @{named_theorems crush_early_simps}
176-
val congs = Named_Theorems.get ctxt @{named_theorems "crush_cong"}
183+
val congs = Named_Theorems.get ctxt @{named_theorems "crush_cong"}
177184
in
178-
safe_unfold_tac' ctxt congs early_simps
185+
if Config.get ctxt Crush_Config.enable_base_simps_early_unfold then
186+
safe_unfold_tac' ctxt congs early_simps
187+
else
188+
K no_tac
179189
end
180190

181191
val crush_branch_base_detect_aentails_contradiction: Proof.context -> int -> tactic = fn ctxt =>
182-
IF' Separation_Logic_Tactics.is_entailment (
183-
let val congs = Named_Theorems.get ctxt @{named_theorems "crush_cong"} in
184-
CHANGED_PROP o (safe_simp_no_asm_only_tac' ctxt congs @{thms asepconj_bot_zero asepconj_bot_zero2})
185-
THEN' TRY o resolve_tac ctxt @{thms bot_aentails_all} end
186-
)
192+
if Config.get ctxt Crush_Config.enable_base_simps_detect_aentails_contradiction then
193+
IF' Separation_Logic_Tactics.is_entailment (
194+
let val congs = Named_Theorems.get ctxt @{named_theorems "crush_cong"} in
195+
CHANGED_PROP o (safe_simp_no_asm_only_tac' ctxt congs @{thms asepconj_bot_zero asepconj_bot_zero2})
196+
THEN' TRY o resolve_tac ctxt @{thms bot_aentails_all} end
197+
)
198+
else
199+
K no_tac
187200

188201
val crush_branch_base_simps_tac : Proof.context -> int -> tactic = fn ctxt =>
189202
let val crush_intros = Named_Theorems.get ctxt @{named_theorems crush_intros}
@@ -219,9 +232,12 @@ struct
219232

220233
val crush_branch_wp_case_split_tac_core : Proof.context -> int -> tactic = fn ctxt =>
221234
if not (Config.get ctxt Crush_Config.enable_urust_split_branch) then K no_tac else
222-
let val wp_intros = Named_Theorems.get ctxt @{named_theorems micro_rust_wp_case_splits}
235+
let val wp_split_intros = Named_Theorems.get ctxt @{named_theorems micro_rust_wp_case_splits}
236+
val wp_split_simps = Named_Theorems.get ctxt @{named_theorems crush_wp_split_simps}
237+
|> force_meta_eqs
223238
in
224-
intro_tac wp_intros ctxt
239+
intro_tac wp_split_intros ctxt ORELSE'
240+
CONVERSION (Conv.params_conv ~1 (fn _ => (Conv.concl_conv ~1 (prop_conv (Conv.rewrs_conv wp_split_simps)))) ctxt)
225241
end
226242

227243
val crush_branch_wp_case_split_tac : Proof.context -> int -> tactic = fn ctxt =>
@@ -392,9 +408,33 @@ struct
392408
end
393409

394410
val crush_branch_clarsimp_fallback_tac = fn ctxt =>
395-
if Config.get ctxt Crush_Config.enable_branch_clarsimp then
396-
CHANGED_PROP o clarsimp_tac ctxt
397-
else K no_tac
411+
let fun log_before_after old_goal new_goal_opt =
412+
(let val old_txt =
413+
[Pretty.str "Needed to apply full-blown clarsimp to progress the following pure goal:", Pretty.brk 1,
414+
Syntax.pretty_term ctxt old_goal, Pretty.fbrk] in
415+
if Config.get ctxt Crush_Config.enable_branch_clarsimp_stop_at_success then
416+
((old_txt @ [Pretty.fbrk, Pretty.str "Aborting as requested by `crush_stop_at_full_blown_clarsimp` option"]
417+
|> Pretty.block |> Pretty.string_of |> Output.warning);
418+
raise SMART "Stopping after successful clarsimp -- consider debugging why MePo-filtered clarsimp didn't work!")
419+
else if old_goal |> Separation_Logic_Tactics.is_entailment |> not
420+
andalso Config.get ctxt Crush_Config.enable_branch_clarsimp_warn_at_success then
421+
let
422+
val new_txt = case new_goal_opt of
423+
NONE => [Pretty.str "Goal was solved"]
424+
| SOME new_goal => [Pretty.str "New goal: ", Pretty.fbrk,
425+
Syntax.pretty_term ctxt new_goal]
426+
in
427+
old_txt @ new_txt @
428+
[Pretty.fbrk, Pretty.str "Consider debugging why MePo-filtered clarsimp is not successful here.", Pretty.fbrk,
429+
Pretty.str "If you want to stop `crush` at this point, set `crush_stop_at_full_blown_clarsimp=true` and `crush_do_big_steps=false`"]
430+
|> Pretty.block |> Pretty.string_of |> Output.warning
431+
end
432+
else () end)
433+
in
434+
if Config.get ctxt Crush_Config.enable_branch_clarsimp then
435+
DEBUG_BEFORE_AFTER' log_before_after (CHANGED_PROP o clarsimp_tac ctxt)
436+
else K no_tac
437+
end
398438

399439
val crush_branch_misc_tac : Proof.context -> int -> tactic = fn ctxt =>
400440
intro_tac @{thms mset_sub_drop_nth word_unat_lt} ctxt
@@ -731,6 +771,7 @@ struct
731771
make_del_parser "intro" @{named_theorems crush_intros} \<^here>,
732772
make_add_parser "specs" @{named_theorems crush_specs} \<^here>,
733773
make_del_parser "specs" @{named_theorems crush_specs} \<^here>,
774+
make_attrib_parser (Args.$$$ "wp" -- Args.$$$ "split") \<^here> (K crush_register_wp_split_attrib),
734775
make_attrib_parser (Args.$$$ "contracts" -- Args.add) \<^here> (K (crush_register_contract_attrib false)),
735776
make_attrib_parser (Args.$$$ "contracts" -- Args.del) \<^here> (K (crush_register_contract_attrib true)),
736777
make_add_parser "cong" @{named_theorems crush_cong} \<^here>,

Crush/mepo_prem.ML

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ sig
3333
(* reverse the action of ignore_tac_idxs_xxx *)
3434
val unignore_tac : Proof.context -> int -> tactic
3535

36+
val matching_prems : Proof.context -> thm -> (term -> bool) -> int -> int list
37+
3638
val relevant_prems_core : Proof.context -> int ->
3739
relevance_fudge option -> term -> (int * term) list -> (int * term) list
3840

@@ -57,6 +59,22 @@ end;
5759
structure MePo_Prem: MEPO_PREM =
5860
struct
5961

62+
fun enumerate ls =
63+
let
64+
fun aux _ acc [] = List.rev acc
65+
| aux base acc (x :: xs) = aux (base + 1) ((base, x) :: acc) xs
66+
in
67+
aux 0 [] ls
68+
end
69+
70+
fun matching_prems (_: Proof.context) (thm: thm) (prem_filter: term -> bool) i =
71+
let
72+
val subgoal = Thm.cprem_of thm i |> Thm.term_of
73+
val prems = Logic.strip_imp_prems subgoal
74+
in
75+
prems |> enumerate |> filter (snd #> prem_filter) |> map fst
76+
end
77+
6078
val ignore_tac0 : Proof.context -> int -> tactic = fn ctxt =>
6179
DETERM o (eresolve_tac ctxt [@{thm IGNORE_imp_ignoreE}])
6280

@@ -189,11 +207,20 @@ fun add_pconst_to_table (s, p) = Symtab.map_default (s, [p]) (insert ptype_eq p)
189207
more or less as if they were built-in but add their axiomatization at the end. *)
190208
val set_consts = [\<^const_name>\<open>Collect\<close>, \<^const_name>\<open>Set.member\<close>]
191209

210+
val ignore_consts = [\<^const_name>\<open>ASSUMPTION\<close>]
211+
212+
fun mepo_keep_premise_filter t = (case t of
213+
\<^Const_>\<open>Trueprop for t1\<close> => mepo_keep_premise_filter t1
214+
| \<^Const_>\<open>ASSUMPTION for _\<close> => true
215+
| _ => false)
216+
192217
fun add_pconsts_in_term thy =
193218
let
194219
fun do_const const (x as (s, _)) ts =
195220
if member (op =) set_consts s then
196221
fold (do_term false) ts
222+
else if member (op =) ignore_consts s then
223+
I
197224
else
198225
(not (is_irrelevant_const s) ? add_pconst_to_table (rich_pconst thy const x))
199226
#> fold (do_term false) ts
@@ -255,6 +282,7 @@ fun pconsts_in_fact thy t =
255282
[]
256283

257284
fun pair_consts_fact' thy term_of_fact fact =
285+
if mepo_keep_premise_filter (fact |> term_of_fact) then NONE else
258286
(case fact |> term_of_fact |> pconsts_in_fact thy of
259287
[] => NONE
260288
| consts => SOME ((fact, consts), NONE))
@@ -517,14 +545,6 @@ fun relevance_filter_generic ctxt thres0 decay max_facts (term_of_fact: 'a -> te
517545
relevance_filter' ctxt thres0 decay max_facts fudge facts concl_t)
518546
end
519547

520-
fun enumerate ls =
521-
let
522-
fun aux _ acc [] = List.rev acc
523-
| aux base acc (x :: xs) = aux (base + 1) ((base, x) :: acc) xs
524-
in
525-
aux 0 [] ls
526-
end
527-
528548
fun subst_abs_constify v b = subst_bound (v |> apfst wrap_bound_name |> Const, b);
529549

530550
fun dest_abs_constify t =
@@ -582,11 +602,15 @@ fun relevance_filter_generic ctxt thres0 decay max_facts (term_of_fact: 'a -> te
582602
aux a b [] exclude
583603
end
584604

605+
fun sorted_union (a : int list) (b : int list) : int list = a @ b |> sort int_ord
606+
585607
fun irrelevant_prems_in_goal ctxt max_facts fudge i thm =
586608
let
587609
val (nprems, relevant_prems_idxs) = relevant_prems_in_goal ctxt max_facts fudge i thm
610+
val force_relevant_idxs = matching_prems ctxt thm mepo_keep_premise_filter i
611+
val all_relevant_prems = sorted_union relevant_prems_idxs force_relevant_idxs
588612
in
589-
(nprems, upto_without 0 (nprems - 1) relevant_prems_idxs)
613+
(nprems, upto_without 0 (nprems - 1) all_relevant_prems)
590614
end
591615
handle THM _ => (0, [])
592616

0 commit comments

Comments
 (0)