blob: 36f7aec384fb2929000127c00cf04c70dc3746ab [file]
(*
* Copyright 2018, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*)
theory Corres_Tac
imports
"Cogent_Corres"
"Cogent.ProofTrace"
"Cogent.CogentHelper"
"Cogent.ML_Old"
Value_Relation_Generation
begin
(*
* Fix Cogent/C mismatch caused by unused return values of C blocks.
* Cogent code can bind a value without using it. This commonly appears in code like
*
* -- first, a message from our sponsors
* let debug_output = stuff
* and _ = _debug_print debug_output
* -- now back to our regularly scheduled programming
* ...
*
* where debug_output becomes unused because the call to _debug_print is stripped for verification.
*
* When AutoCorres comes across this code, it figures that debug_output is unused,
* so its generated code for "stuff" appends "gets (\<lambda>_. ())".
* This breaks Corres_Tac's expectation that the C code for "stuff" returns a value that
* corresponds to the Cogent code.
*
* We fix that up by walking the AutoCorres output and removing the extra "gets (\<lambda>_. ())",
* allowing the original return value of "stuff" to be propagated.
* The most annoying bit is that the last statement of a monad block is the most deeply nested.
* So our rules need to iterate through pairs of consecutive statements in the same manner
* as @{term simp_last_bind}. The other annoying bit is that we need to change the return type
* from unit to the corresponding type (e.g. of debug_output).
* We use a stack of schematic variables to remember where to apply the type changes.
*
* This could also be fixed directly in AutoCorres, assuming someone remembers how l2 works...
*)
definition "cogent_C_unused_return__internal X Y \<equiv> X = (do Y; return () od)"
lemma cogent_C_unused_return_step:
"\<And>A A' B B'. A = A' \<Longrightarrow> B = B' \<Longrightarrow> A >>= K_bind B = A' >>= K_bind B'"
"\<And>A A' B B'. A = A' \<Longrightarrow> (\<And>v. B v = B' v) \<Longrightarrow> A >>= B = A' >>= B'"
by meson+
(* This should be the only interesting statement block.
* For Let and LetBang codegen, C always returns true and R is unused,
* so we don't care what the rules do to it. *)
lemma cogent_C_unused_return_L:
"\<lbrakk> L = L';
cogent_C_unused_return__internal (L' :: ('s, unit) nondet_monad) (L'' :: ('s, 'a) nondet_monad);
X = X' \<rbrakk> \<Longrightarrow>
(do (_ :: unit) \<leftarrow> condition C L R; X od)
=
(do (_ :: 'a) \<leftarrow> condition C L'' (do R; return undefined od); X' od)"
apply (simp add: cogent_C_unused_return__internal_def)
(* goal assumptions cause normal monad_eq to loop *)
apply (tactic \<open> simp_tac (@{context} addsimps (MonadEqThms.get @{context})) 1 \<close>)
apply blast
done
lemma cogent_C_unused_return__internal:
"\<And>A. cogent_C_unused_return__internal (do v \<leftarrow> A; gets (\<lambda>_. ()) od) A"
"\<And>A A' B B'. \<lbrakk> A = A'; (\<And>v. cogent_C_unused_return__internal (B v) (B' v)) \<rbrakk> \<Longrightarrow>
cogent_C_unused_return__internal (A >>= B) (A' >>= B')"
"\<And>A A'. A = A' \<Longrightarrow> cogent_C_unused_return__internal A A'"
by (monad_eq simp: cogent_C_unused_return__internal_def | blast)+
(* Test: *)
schematic_goal
(* input *)
"(do stuff1;
stuff2;
_ \<leftarrow>
condition C1
(do _ \<leftarrow>
condition C2
(do _ \<leftarrow> gets (\<lambda>_. r1);
gets (\<lambda>_. ()) od) \<comment> \<open> <-- \<close>
bla1;
gets (\<lambda>_. ()) od) \<comment> \<open> <-- \<close>
bla2;
_ \<leftarrow>
condition C3 stuff3 stuff4; \<comment> \<open> no change \<close>
stuff5 od)
= ?A"
(* expected output *)
"?A =
(do stuff1;
stuff2;
_ \<leftarrow>
condition C1
(condition C2
(gets (\<lambda>_. r1))
(do bla1; return undefined od))
(do bla2; return undefined od);
_ \<leftarrow>
condition C3 stuff3 stuff4;
stuff5 od)"
(* do it *)
apply ((rule cogent_C_unused_return_L cogent_C_unused_return_step cogent_C_unused_return__internal refl)+)[1]
(* check *)
by (rule refl)
(* Apply the rewrite to a corres proof state.
* In corres_tac, we apply this tactic blindly and it rewrites all unit-returning blocks
* in the C code. This should be safe because no genuine Cogent code ever returns unit
* (the unit type in Cogent is currently compiled to unit_t in C, instead of void). *)
(* FIXME: maybe make this part of Tidy *)
context update_sem_init begin
lemma cogent_corres_unused_return:
"m = m' \<Longrightarrow>
corres srel c m' \<xi> \<gamma> \<Xi> \<Gamma> \<sigma> s \<Longrightarrow>
corres srel c m \<xi> \<gamma> \<Xi> \<Gamma> \<sigma> s"
by simp
ML \<open>
fun cogent_C_unused_return_tac ctxt = let
(* import into ctxt's locale *)
val corres_rule = Proof_Context.get_thm ctxt "cogent_corres_unused_return"
in fn n =>
rtac corres_rule n
THEN SOLVES (REPEAT_DETERM
(resolve_tac ctxt @{thms cogent_C_unused_return_L cogent_C_unused_return_step
cogent_C_unused_return__internal refl} n)) end
\<close>
end
ML \<open>
(* Create derivative equations that only apply in a given context.
*
* make_contextual_eq_thms "f" ["a = b", "c = d"] ctxt
* = ["f a = f b", "f c = f d"]
*)
fun make_contextual_eq_thms (context : term) (eq_thms : thm list) ctxt : thm list =
let fun dest_eq (Const (@{const_name "Trueprop"}, _) $ eq) = dest_eq eq
| dest_eq (Const (@{const_name "Pure.eq"}, _) $ l $ r) = (l, r)
| dest_eq (Const (@{const_name "HOL.eq"}, _) $ l $ r) = (l, r)
| dest_eq t = raise (TERM ("ContextThms.dest_eq", [t]))
fun make_eq_thm thm0 =
let val ((_, [thm]), _) = Variable.import true [thm0] (Variable.set_body false ctxt);
val (lhs, rhs) = dest_eq (Thm.prop_of thm)
handle TERM _ => raise THM ("make_contextual_eq_thms: not an equation", 0, [thm0])
val prop = @{term Trueprop} $ (@{term "(=)"} $ (context $ lhs) $ (context $ rhs))
val prop' = map_types (K dummyT) prop |> Syntax.check_term ctxt
handle ERROR _ => raise TERM ("make_contextual_eq_thms: equality term is invalid", [prop])
fun free_var v = case Syntax.check_term ctxt (Free (v, dummyT)) of
Free (_, TFree _) => true
| _ => false
in Goal.prove ctxt (filter free_var (Term.add_free_names prop' [])) [] prop'
(K (simp_tac (ctxt addsimps [thm]) 1))
handle ERROR msg => raise TERM ("make_contextual_eq_thms proof failed:\n" ^ msg, [prop']) end
in map make_eq_thm eq_thms end
\<close>
lemma simp_trivial_gets:
"do x \<leftarrow> gets (\<lambda>_. v); B x od = B v"
by simp
(* Limit simp_trivial_gets to the top level *)
local_setup \<open>
fn ctxt =>
Local_Theory.note
((Binding.name "corres_simp_gets", []),
(make_contextual_eq_thms @{term "\<lambda>m. update_sem_init.corres abs_typing abs_repr srel c m \<xi>' \<gamma> \<Xi>' \<Gamma>' \<sigma> s"} @{thms simp_trivial_gets} ctxt))
ctxt |> snd
\<close>
lemma simp_condition_bind:
"do retval \<leftarrow> condition b x y; gets (\<lambda>s. retval) od = condition b x y" by simp
local_setup \<open>
fn ctxt =>
Local_Theory.note
((Binding.name "corres_simp_cond_gets", []),
(make_contextual_eq_thms @{term "\<lambda>m. update_sem_init.corres abs_typing abs_repr srel c m \<xi>' \<gamma> \<Xi>' \<Gamma>' \<sigma> s"} @{thms simp_condition_bind} ctxt))
ctxt |> snd
\<close>
lemma ucast_up_lesseq[OF refl]:
"upcast = ucast
\<Longrightarrow> is_up (upcast :: ('a :: len) word \<Rightarrow> ('b :: len) word)
\<Longrightarrow> (upcast x \<le> upcast y) = (x \<le> y)"
by (simp add: word_le_nat_alt unat_ucast_upcast)
lemma ucast_up_less[OF refl]:
"upcast = ucast
\<Longrightarrow> is_up (upcast :: ('a :: len) word \<Rightarrow> ('b :: len) word)
\<Longrightarrow> (upcast x < upcast y) = (x < y)"
by (simp add: word_less_nat_alt unat_ucast_upcast)
lemma ucast_up_mod[OF refl]:
"upcast = ucast
\<Longrightarrow> is_up (upcast :: ('c :: len) word \<Rightarrow> ('d :: len) word)
\<Longrightarrow> (upcast x mod upcast y) = upcast (x mod y)"
apply (rule word_unat.Rep_eqD)
apply (simp only: unat_mod unat_ucast_upcast)
done
lemma ucast_up_div[OF refl]:
"upcast = ucast
\<Longrightarrow> is_up (upcast :: ('c :: len) word \<Rightarrow> ('d :: len) word)
\<Longrightarrow> (upcast x div upcast y) = upcast (x div y)"
apply (rule word_unat.Rep_eqD)
apply (simp only: unat_div unat_ucast_upcast)
done
lemma ucast_up_eq_0[OF refl]:
"upcast = ucast
\<Longrightarrow> is_up (upcast :: ('c :: len) word \<Rightarrow> ('d :: len) word)
\<Longrightarrow> (upcast x = 0) = (x = 0)"
by (metis word_unat.Rep_inject unat_ucast_upcast ucast_0)
lemma ucast_down_bitwise[OF refl]:
"dcast = ucast
\<Longrightarrow> (bitOR (dcast x) (dcast y)) = dcast (bitOR x y)"
"dcast = ucast
\<Longrightarrow> (bitAND (dcast x) (dcast y)) = dcast (bitAND x y)"
"dcast = ucast
\<Longrightarrow> (bitXOR (dcast x) (dcast y)) = dcast (bitXOR x y)"
"dcast = ucast
\<Longrightarrow> is_down (dcast :: ('a :: len) word \<Rightarrow> ('b :: len) word)
\<Longrightarrow> (bitNOT (dcast x)) = dcast (bitNOT x)"
by (auto intro!: word_eqI simp add: word_size nth_ucast word_ops_nth_size
is_down_def target_size_def source_size_def)
lemma ucast_down_shiftl[OF refl]:
"dcast = ucast
\<Longrightarrow> is_down (dcast :: ('a :: len) word \<Rightarrow> ('b :: len) word)
\<Longrightarrow> dcast (x << n) = dcast x << n"
apply clarsimp
apply (rule word_eqI)
apply (simp add: word_size nth_shiftl nth_ucast)
apply (simp add: is_down_def source_size_def target_size_def word_size)
apply auto
done
lemma ucast_up_down_shiftr[OF refl]:
"dcast = ucast
\<Longrightarrow> is_down (dcast :: ('a :: len) word \<Rightarrow> ('b :: len) word)
\<Longrightarrow> dcast (ucast x >> n) = x >> n"
apply clarsimp
apply (rule word_eqI)
apply (simp add: word_size nth_shiftr nth_ucast)
apply (simp add: is_down_def source_size_def target_size_def word_size)
apply (auto dest: test_bit_size simp: word_size)
done
lemma ucast_up_sless_disgusting[OF refl]:
"(upcast :: ('c :: len) word \<Rightarrow> ('d :: len) word) = ucast
\<Longrightarrow> len_of TYPE('c) < len_of TYPE('d)
\<Longrightarrow> (upcast x <s upcast y) = (x < y)"
apply (clarsimp simp: word_sless_msb_less msb_nth nth_ucast
word_less_nat_alt unat_ucast_upcast
is_up_def source_size_def target_size_def word_size)
apply (auto dest: test_bit_size simp: word_size)
done
lemma ucast_up_sle_disgusting[OF refl]:
"(upcast :: ('c :: len) word \<Rightarrow> ('d :: len) word) = ucast
\<Longrightarrow> len_of TYPE('c) < len_of TYPE('d)
\<Longrightarrow> (upcast x <=s upcast y) = (x \<le> y)"
apply (clarsimp simp: word_sle_msb_le msb_nth nth_ucast
word_le_nat_alt unat_ucast_upcast
is_up_def source_size_def target_size_def word_size)
apply (auto dest: test_bit_size simp: word_size)
done
(* Corres tactic *)
ML \<open>
(* Used to decode Cogent Var indices *)
fun decode_isa_nat @{term "0 :: nat"} = 0
| decode_isa_nat (@{term Suc} $ n) = decode_isa_nat n + 1
| decode_isa_nat n = HOLogic.dest_number n |> snd
fun TRY_FST tac1 tac2 st = (tac2 ORELSE ((DETERM tac1) THEN tac2)) st
fun TRY_FST_N tac1 tac2 st = (tac2 ORELSE (tac1 THEN tac2)) st
fun TRY_MORE_FST tac1 tac2 st = (tac2 ORELSE ((DETERM tac1) THEN (TRY_MORE_FST tac1 tac2))) st
(* Determine whether a Cogent type contains a TFun anywhere.
* This is used as a crude heuristic for applying corres_let_gets_propagate. *)
fun Cogent_type_contains_TFun (Const (@{const_name TFun}, _)) = true
| Cogent_type_contains_TFun (Abs (_, _, t)) = Cogent_type_contains_TFun t
| Cogent_type_contains_TFun (f $ x) = Cogent_type_contains_TFun f orelse Cogent_type_contains_TFun x
| Cogent_type_contains_TFun _ = false
(* Matches within a typing judgement. *)
fun Cogent_typing_returns_TFun (@{term Trueprop} $
(Const (@{const_name typing}, _) $ tenv $ kind $ env $ expr $ typ)) =
Cogent_type_contains_TFun typ
| Cogent_typing_returns_TFun t = raise TERM ("Cogent_typing_returns_TFun: not a typing rule", [t])
(* Number of expected nondet_monad statements for each Cogent atom in a Let. *)
fun atom_stmts @{const_name Var} = SOME 1
| atom_stmts @{const_name Prim} = NONE
| atom_stmts @{const_name App} = SOME 2
| atom_stmts @{const_name Con} = SOME 1
| atom_stmts @{const_name Struct} = SOME 1
| atom_stmts @{const_name Unit} = SOME 1
| atom_stmts @{const_name Lit} = SOME 1
| atom_stmts @{const_name Cast} = SOME 1
| atom_stmts @{const_name Tuple} = SOME 1
| atom_stmts @{const_name Esac} = SOME 1
| atom_stmts @{const_name Fun} = SOME 1
| atom_stmts @{const_name AFun} = SOME 1
| atom_stmts @{const_name Promote} = SOME 1
(* Let (Put...) is handled outside of corres_let_tac. *)
| atom_stmts _ = NONE
fun sigil_atom_stmts @{const_name Member} Unboxed = SOME 1
| sigil_atom_stmts @{const_name Member} _ = SOME 2
| sigil_atom_stmts _ _ = NONE
fun rec_sigil (Const (@{const_name TRecord}, _) $ _ $ @{term Unboxed}) = SOME Unboxed
| rec_sigil (Const (@{const_name TRecord}, _) $ _ $ (@{const Boxed} $ @{const ReadOnly} $ _)) = SOME ReadOnly
| rec_sigil (Const (@{const_name TRecord}, _) $ _ $ (@{const Boxed} $ @{const Writable} $ _)) = SOME Writable
| rec_sigil _ = NONE
(* Guess the number of statements for this atom.
* "Member (Var 0) 0" is passed as {head = Member, args = [Var 0, 0]}.
* The type env is used to distinguish unboxed and boxed member accesses. *)
fun atom_stmts' (head : string) (args : term list) (env : term) =
case atom_stmts head of SOME n => SOME n | NONE => (case (head, args)
of (@{const_name Member}, Const (@{const_name Var}, _) $ n :: _) => let
val ty = case nth (HOLogic.dest_list env) (decode_isa_nat n) of
(Const (@{const_name Some}, _) $ ty) => ty | t => raise TERM ("atom_stmts': Gamma none", [t])
val sg = case rec_sigil ty of SOME s => s | _ => raise ERROR ("atom_stmts': cannot parse sigil for record: " ^ @{make_string} ty)
in sigil_atom_stmts head sg end
| (@{const_name Prim}, primop :: _) => let
val is_guarded = case head_of primop of @{const "LShift"} => true
| @{const "RShift"} => true | @{const "Divide"} => true
| @{const "Mod"} => true | _ => false
in if is_guarded then SOME 2 else SOME 1 end
| _ => NONE)
(* Inspect a "corres ..." subgoal. *)
fun dest_corres_prop prop =
case prop of
@{term Trueprop} $ (Const (@{const_name "update_sem_init.corres"}, _) $
_ $ _ $ (* locale parameters *)
srel $ cogent $ m $ xi $ gam $ Xi $ Gam $ si $ s) =>
SOME (srel, cogent, m, xi, gam, Xi, Gam, si, s)
| _ => NONE
fun dest_corres_goal goal_num st =
if Thm.nprems_of st < goal_num then NONE else
Logic.concl_of_goal (Thm.prop_of st) goal_num |> dest_corres_prop
(* Guess the C types mentioned in a rule. This can reduce the amount of
* val_rel and type_rels that we need to unfold.
* Ideally, this should be tracked in the lemma buckets. *)
fun scrape_C_types thm = let
fun filter_Const P (Const (c_name, _)) = if P c_name then [c_name] else []
| filter_Const P (f $ x) = filter_Const P f @ filter_Const P x
| filter_Const P (Abs (_, _, t)) = filter_Const P t
| filter_Const _ _ = []
fun c_type_name str = String.tokens (fn x => x = #".") str
|> filter (String.isSuffix "_C") |> take 1
in Thm.prop_of thm
|> filter_Const (c_type_name #> null #> not)
|> map c_type_name |> List.concat
|> distinct (op =)
end
(* Apply a conversion to the n'th arg in the concl of the chosen subgoal. *)
fun nth_arg_conv_tac n ngoal ctxt conv st = if Thm.nprems_of st < ngoal then no_tac st else
let val subgoal = Logic.get_goal (Thm.prop_of st) ngoal
val all_vars = length (strip_all_vars subgoal)
val imps = length (Logic.strip_imp_prems (strip_all_body subgoal))
val goal_concl = Logic.concl_of_goal (Thm.prop_of st) ngoal
in
Conv.gconv_rule
(Utils.nth_arg_conv n conv
|> (if fst (strip_comb goal_concl) = @{term Trueprop} then Conv.arg_conv else I)
|> Conv.concl_conv imps
|> (fn conv => Conv.params_conv all_vars (K conv) ctxt)) ngoal st
|> Seq.succeed
end
handle CTERM _ => no_tac st
(* Remove "val_rel a a'" assumptions if a' does not appear anywhere else in the subgoal.
Special case for appearance within corres propositions - ignore elements of \<gamma>
that are not given types in \<Gamma>. *)
val val_rel_thin_tac = SUBGOAL (fn (goal, n) => let
val hyps = Logic.strip_assums_hyp goal
val concl = Logic.strip_assums_concl goal
fun match_xi_bvars (Const (@{const_name Cons}, _) $ _ $ xs)
(Const (@{const_name Cons}, _) $ Const (@{const_name None}, _) $ ys) = match_xi_bvars xs ys
| match_xi_bvars (Const (@{const_name Cons}, _) $ x $ xs)
(Const (@{const_name Cons}, _) $ y $ ys) = maps spot_bvars [x, y] @ match_xi_bvars xs ys
| match_xi_bvars xs ys = maps spot_bvars [xs, ys]
and spot_bvars t = case (dest_corres_prop t, t) of
(SOME (srel, cogent, m, xi, gam, Xi, Gam, si, s), _)
=> maps spot_bvars [srel, cogent, m, xi, Xi, si, s] @ match_xi_bvars gam Gam
| (NONE, f $ x) => spot_bvars f @ spot_bvars x
| (NONE, Abs (_, _, bd)) => map (fn x => x - 1) (filter (fn x => x > 0) (spot_bvars bd))
| (NONE, Bound n) => [n]
| _ => []
val used_bvars = (maps spot_bvars (concl :: hyps))
|> map (fn x => (x, ())) |> Inttab.make_list
fun keep (@{term Trueprop} $ (Const (@{const_name val_rel}, _) $ _ $ Bound n))
= (Inttab.lookup used_bvars n <> SOME [()])
| keep _ = true
val drops = filter_out (keep o fst) (hyps ~~ (0 upto length hyps - 1))
|> map snd |> rev
fun thin i = (rotate_tac i n THEN etac thin_rl n THEN rotate_tac (~ i) n)
in EVERY (map thin drops) end)
fun corres_tac ctxt
(typing_tree : thm tree)
(fun_defs : thm list)
(absfun_corres : thm list)
(fun_corres : thm list)
(corres_if: thm)
(corres_esac: thm list)
(val_rel_simps : thm list)
(type_rel_simps : thm list)
(tag_enum_defs : thm list)
(LETBANG_TRUE_def: thm)
(list_to_map_simps: thm list)
(verbose : bool)
: tactic =
let
fun corres_Cogent_head goal_num st =
Option.map (#2 #> strip_comb #> fst #> dest_Const #> fst) (dest_corres_goal goal_num st)
fun get_thm nm = Proof_Context.get_thm ctxt nm;
fun get_thms nm = Proof_Context.get_thms ctxt nm;
(* Basic rules. *)
val corres_let = get_thm "corres_let";
val corres_nested_let = get_thm "corres_nested_let";
val corres_let_propagate = get_thm "corres_let_gets_propagate";
val corres_letbang = get_thm "corres_letbang";
val corres_app_concrete = get_thm "corres_app_concrete";
val corres_var = get_thm "corres_var";
val corres_con = get_thm "corres_con";
val corres_lit = get_thm "corres_lit";
val corres_prim1 = get_thm "corres_prim1";
val corres_prim2 = get_thm "corres_prim2";
val corres_prim2_partial_right = get_thm "corres_prim2_partial_right";
val corres_prim2_partial_left = get_thm "corres_prim2_partial_left";
val eval_prim_simps = @{thms eval_prim_u_def
ucast_down_add ucast_down_mult up_ucast_inj_eq
ucast_down_minus ucast_up_less ucast_up_lesseq
ucast_down_bitwise[symmetric] ucast_down_shiftl unat_ucast_upcast
ucast_up_down_shiftr ucast_id
ucast_up_div ucast_up_mod ucast_up_eq_0 checked_div_def
ucast_up_sle_disgusting ucast_up_sless_disgusting
is_up_def is_down_def source_size_def
target_size_def word_size ucast_down_ucast_id
};
val eval_prim_ineq_guard_simps = @{thms word_less_nat_alt word_le_nat_alt}
val corres_unit = get_thm "corres_unit";
val corres_fun = get_thm "corres_fun";
val corres_afun = get_thm "corres_afun";
val corres_promote = get_thm "corres_promote";
val corres_cast = get_thms "corres_cast";
val corres_struct = get_thm "corres_struct";
val corres_let_put_unboxed = get_thm "corres_let_put_unboxed'";
val corres_no_let_put_unboxed = get_thm "corres_no_let_put_unboxed'";
(* Type-specialised rule buckets. *)
val net_resolve_tac = Tactic.build_net #> resolve_from_net_tac ctxt
val corres_case_rule = Case.get ctxt |> net_resolve_tac;
val corres_member_boxed_rule = MemberReadOnly.get ctxt |> net_resolve_tac;
val corres_take_boxed_rule = TakeBoxed.get ctxt |> net_resolve_tac;
val corres_take_unboxed_rule = TakeUnboxed.get ctxt |> net_resolve_tac;
val corres_put_boxed_rule = PutBoxed.get ctxt |> net_resolve_tac;
val corres_let_put_boxed_rule = LetPutBoxed.get ctxt |> net_resolve_tac;
(* Miscellaneous rules. *)
val bind_assoc_sym = @{thm bind_assoc[symmetric]};
val recguard_true_rule = get_thm "condition_true_pure";
val type_rel_simps = type_rel_simps @ TypeRelSimp.get ctxt;
val val_rel_simps_prim = @{thms val_rel_word}
@ [get_thm "val_rel_bool_t_C_def"]
val val_rel_simps = val_rel_simps @ ValRelSimp.get ctxt;
fun make_thm_index guess thms = let
val nmths = map swap (maps (fn t => map (pair t) (guess t)) thms)
in Symtab.make_list nmths end
fun lookup_thm_index table = List.mapPartial (Symtab.lookup table) #> List.concat #> distinct Thm.eq_thm
val type_rel_index = make_thm_index scrape_C_types type_rel_simps
val val_rel_index = make_thm_index guess_val_rel_type val_rel_simps
(* Basic tactics. *)
fun SOLVES' tac = fn n => SOLVES (tac n);
fun TRY' tac = fn n => TRY (tac n);
val simp = asm_full_simp_tac ctxt;
val subgoal_simp = TRY' (SOLVES' simp);
fun simp_add thms = asm_full_simp_tac (add_simps thms ctxt);
fun subgoal_simp_add thms = TRY' (SOLVES' (simp_add thms));
fun fastforce_add thms = Clasimp.fast_force_tac (add_simps thms ctxt);
fun clarsimp_add thms = Clasimp.clarsimp_tac (add_simps thms ctxt);
fun subst thms = EqSubst.eqsubst_tac ctxt [0] thms;
val rule = rtac;
val rules = resolve_tac ctxt;
(* Common simpsets. *)
val val_rel_simp_ctxt = ctxt addsimps val_rel_simps
val type_rel_simp_ctxt = ctxt addsimps type_rel_simps
fun subgoal_val_rel_simp_add thms = TRY' (val_rel_thin_tac
THEN' SOLVES' (asm_full_simp_tac (val_rel_simp_ctxt addsimps thms)))
fun subgoal_type_rel_simp_add thms = TRY' (SOLVES' (asm_full_simp_tac (type_rel_simp_ctxt addsimps thms)))
fun subgoal_val_rel_clarsimp_add thms = TRY' (val_rel_thin_tac
THEN' SOLVES' (Clasimp.clarsimp_tac (val_rel_simp_ctxt addsimps thms)))
fun real_goal_of (@{term Pure.imp} $ _ $ t) = real_goal_of t
| real_goal_of (@{term Trueprop} $ t) = real_goal_of t
| real_goal_of (Const (@{const_name Pure.all}, _) $ Abs (x, ty, t)) = betapply (real_goal_of t, Free (x, ty))
| real_goal_of t = t
(* Apply an abstract function corres rule.
* These rules may come in all shapes and sizes; try to solve their assumptions by simp. *)
fun apply_absfun absfun_thm st =
if exists_subterm (fn t => is_const @{const_name measure_call} t) (Thm.prop_of st)
then (* resolve AutoCorres' "measure_call" construct *)
st |>
(rule (get_thm "corres_measure_call_subst") 1
THEN
(fn st => (case Logic.concl_of_goal (Thm.prop_of st) 1 |> real_goal_of of
Const (@{const_name "monad_mono"}, _) $ Abs (_ (* measure var *), _, call) =>
case fst (strip_comb call) of
Const (f, _) => rule (get_thm (Long_Name.base_name f ^ "_mono")) 1 st)
handle Match => raise TERM ("Corres_Tac: failed to resolve measure_call",
[Logic.concl_of_goal (Thm.prop_of st) 1]))
THEN
(rule absfun_thm
THEN_ALL_NEW
(SOLVES' (val_rel_thin_tac THEN'
(rule @{thm order_refl} ORELSE'
simp_add (type_rel_simps @ val_rel_simps @ @{thms recguard_dec_def}))))) 1
)
else
st |>
(rule absfun_thm
THEN_ALL_NEW
(SOLVES' (val_rel_thin_tac THEN' simp_add (type_rel_simps @ val_rel_simps @ @{thms recguard_dec_def})))) 1
(* Strip the recursion guard from recursive function bodies.
* We try to simp away the guard condition. *)
fun simp_recguard_tac n st =
case dest_corres_goal n st of
NONE => raise THM ("corres_tac/simp_recguard_tac: expected a corres subgoal here", 0, [st])
| SOME (_, _, c_def, _, _, _, _, _, _) =>
(case strip_comb c_def of
(Const (@{const_name condition}, _), _) =>
(case (if verbose then tracing "Proving: recursion guard\n" else ();
subst [recguard_true_rule] n THEN SOLVES (simp n)) st |> Seq.pull of
SOME (st', _) => Seq.succeed st'
| NONE => raise THM ("corres_tac/simp_recguard_tac: failed to discharge recursion guard\n", 0, [st]))
| _ => all_tac st);
(* Prove corres recursively. *)
fun corres_tac_rec typing_tree depth = let
fun print msg st = ((if verbose then tracing (String.implode (replicate (depth*2) #" ") ^ msg ^ "\n") else ()); Seq.single st);
fun tree_nth nth = List.nth (tree_rest typing_tree, nth);
fun rule_tree nth n st = TRY (TRY_FST (simp n) (rule (tree_hd (tree_nth nth)) n) |> SOLVES) st
handle Subscript => (print "Warning: rule_tree failed" THEN no_tac) st;
fun corres_tac_nth nth st = corres_tac_rec (tree_nth nth) (depth+1) st;
fun tree_nth' tree nth = List.nth (tree_rest tree, nth);
fun rule_tree' tree nth n st = rule (tree_hd (tree_nth' tree nth)) n st
handle Subscript => (print "Warning: rule_tree' failed" THEN no_tac) st;
(* For Let (Fun...) and similar constructs, we need to remember the value of the Fun
* so that we can apply the corres lemma for that function. *)
fun apply_corres_let n st =
(if Cogent_typing_returns_TFun (Thm.prop_of (tree_hd (tree_nth 1))) (* check the type of the bound-expr *)
then (print "Debug: using corres_let_propagate" THEN rule corres_let_propagate n) st
else rule corres_let n st
) handle Subscript => (print "Warning: tree_nth failed in apply_corres_let" THEN no_tac) st
(* Process a program of the form (Let x y...).
* While x is guaranteed to be an atomic expression, atoms may translate
* to more than one statement in the C monad.
* For two-statement atoms, we use bind_assoc to pull out the statement pair
* before continuing.
*)
fun corres_let_tac n st =
(case dest_corres_goal n st of
SOME (_, Const (@{const_name Cogent.Let}, _) $ lhs $ rhs, _, _, _, _, env, _, _) =>
(case strip_comb lhs of
(Const (lhs_head, _), args) =>
(case atom_stmts' lhs_head args env of
SOME 1 => apply_corres_let n
THEN print ("corres_let: " ^ lhs_head)
THEN rule_tree 0 n
THEN rule_tree 1 n
THEN TRY (corres_tac_nth 1)
THEN TRY (corres_tac_nth 2)
| SOME 2 => subst [bind_assoc_sym] n
THEN apply_corres_let n
THEN print ("corres_let: " ^ lhs_head)
THEN rule_tree 0 n
THEN rule_tree 1 n
THEN TRY (corres_tac_nth 1)
THEN TRY (corres_tac_nth 2)
| _ => no_tac)
| _ => no_tac)
| _ => no_tac) st;
(* Check the head of the Cogent program before applying a tactic.
* This is useful when the tactic doesn't fail quickly (eg. for type-specialised rule buckets). *)
fun check_Cogent_head head tac st =
case corres_Cogent_head 1 st of
NONE => tac st (* not sure if we'd expect this to work *)
| SOME head' => if head = head' then tac st else no_tac st;
in
(fn t => case corres_Cogent_head 1 t of SOME h => print ("Proving: " ^ h) t | _ => all_tac t)
THEN
(* Evaluate Cogent environment lookups (ie. list lookups) eagerly *)
((nth_arg_conv_tac 7 (* \<gamma> *) 1 ctxt (Simplifier.rewrite ctxt)
THEN nth_arg_conv_tac 9 (* \<Gamma> *) 1 ctxt (Simplifier.rewrite ctxt))
THEN
(* Prune val_rel assumptions and variables. *)
val_rel_thin_tac 1 THEN prune_params_tac ctxt
THEN
((rule corres_var 1
THEN print "corres_var"
THEN subgoal_simp 1)
ORELSE
(rule corres_unit 1
THEN print "corres_unit"
THEN subgoal_val_rel_simp_add [] 1)
ORELSE
(rule corres_con 1
THEN print "corres_con"
THEN subgoal_val_rel_simp_add [] 1)
ORELSE
(rule corres_lit 1
THEN print "corres_lit"
THEN subgoal_val_rel_simp_add [] 1)
ORELSE
(rule corres_prim1 1
THEN print "corres_prim (unary)"
THEN val_rel_thin_tac 1 THEN fastforce_add (val_rel_simps_prim @ eval_prim_simps) 1)
ORELSE
(rule corres_prim2 1
THEN print "corres_prim (binary)"
THEN val_rel_thin_tac 1 THEN fastforce_add (val_rel_simps_prim @ eval_prim_simps) 1)
ORELSE
(rule corres_prim2_partial_right 1
THEN print "corres_prim (binary, partial, right)"
THEN (val_rel_thin_tac THEN' simp_add (eval_prim_simps @ val_rel_simps)
THEN_ALL_NEW simp_add (eval_prim_simps @ eval_prim_ineq_guard_simps)) 1
THEN subgoal_val_rel_simp_add (eval_prim_simps @ eval_prim_ineq_guard_simps) 1
)
ORELSE
(rule corres_prim2_partial_left 1
THEN print "corres_prim (binary, partial, left)"
THEN (val_rel_thin_tac THEN' simp_add (eval_prim_simps @ val_rel_simps)
THEN_ALL_NEW simp_add (eval_prim_simps @ eval_prim_ineq_guard_simps)) 1
THEN subgoal_val_rel_simp_add (eval_prim_simps @ eval_prim_ineq_guard_simps) 1
)
ORELSE
(rule corres_fun 1
THEN print "corres_fun"
THEN subgoal_val_rel_simp_add [] 1)
ORELSE
(rule corres_afun 1
THEN print "corres_afun"
THEN subgoal_val_rel_simp_add [] 1)
ORELSE
(rule corres_promote 1
THEN print "corres_promote"
THEN subgoal_val_rel_simp_add [] 1)
ORELSE
((simp_tac (put_simpset HOL_basic_ss ctxt addsimps list_to_map_simps) 1)
THEN rule corres_struct 1
THEN print "corres_struct"
THEN subgoal_val_rel_simp_add [] 1)
ORELSE
(rules corres_cast 1
THEN print "corres_cast"
THEN rule_tree 0 1
THEN subgoal_simp 1)
ORELSE check_Cogent_head @{const_name App}
(((fn n => rule corres_app_concrete n
THEN print "corres_app_concrete"
THEN simp n THEN simp n
THEN rules fun_corres n)
THEN_ALL_NEW subgoal_simp_add (@{thm recguard_dec_def} :: fun_corres)) 1)
ORELSE check_Cogent_head @{const_name App}
(APPEND_LIST (map apply_absfun absfun_corres)
THEN print "corres_app_abstract")
ORELSE
(rules corres_esac 1
THEN print "corres_esac"
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN rule_tree 0 1
THEN subgoal_val_rel_simp_add [] 1
ORELSE check_Cogent_head @{const_name Member}
(corres_member_boxed_rule 1
THEN print "corres_member_boxed"
THEN rule_tree 0 1
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN subgoal_type_rel_simp_add [] 1
THEN subgoal_type_rel_simp_add [] 1
THEN rule_tree 0 1
THEN TRY (TRY_FST (simp 1) (rule_tree' (tree_nth 0) 0 1) |> SOLVES))
ORELSE
(rule (Proof_Context.get_thm ctxt "corres_member_unboxed") 1
THEN subgoal_val_rel_clarsimp_add [] 1)
ORELSE check_Cogent_head @{const_name Take}
(corres_take_unboxed_rule 1
THEN print "corres_take_unboxed"
THEN subgoal_simp 1
THEN rule_tree 0 1
THEN subgoal_val_rel_simp_add [] 1
THEN subgoal_type_rel_simp_add [] 1
THEN subgoal_type_rel_simp_add [] 1
THEN TRY (TRY (simp 1) THEN SOLVES (rule (tree_hd typing_tree) 1))
THEN rule_tree 1 1
THEN rule_tree 3 1
THEN rule_tree 2 1
THEN subgoal_simp 1
THEN TRY (corres_tac_nth 3))
ORELSE check_Cogent_head @{const_name Take}
(corres_take_boxed_rule 1
THEN print "corres_take_boxed"
THEN subgoal_simp 1
THEN rule_tree 0 1
THEN subgoal_val_rel_simp_add [] 1
THEN subgoal_type_rel_simp_add [] 1
THEN subgoal_type_rel_simp_add [] 1
THEN TRY (TRY_FST (simp 1) (SOLVES (rule (tree_hd typing_tree) 1)))
THEN rule_tree 1 1
THEN rule_tree 3 1
THEN rule_tree 2 1
THEN subgoal_simp 1
THEN TRY (corres_tac_nth 3))
ORELSE
(rule corres_if 1
THEN print "corres_if"
THEN rule_tree 0 1
THEN rule_tree 1 1
THEN subgoal_val_rel_simp_add [] 1
THEN TRY (corres_tac_nth 2)
THEN TRY (corres_tac_nth 3))
ORELSE check_Cogent_head @{const_name Let}
(rule corres_let_put_unboxed 1
THEN print "corres_put_unboxed"
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN rule_tree 0 1
THEN rule_tree 1 1
THEN subgoal_val_rel_clarsimp_add [] 1
THEN TRY (corres_tac_nth 2))
ORELSE check_Cogent_head @{const_name Put}
(rule corres_no_let_put_unboxed 1
THEN print "corres_put_unboxed (no let)"
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN TRY (TRY (simp 1) THEN SOLVES (rule (tree_hd typing_tree) 1))
THEN subgoal_val_rel_clarsimp_add [] 1)
ORELSE check_Cogent_head @{const_name Let}
(corres_let_put_boxed_rule 1
THEN print "corres_let_put_boxed"
THEN rule_tree 0 1
THEN subgoal_simp 1
THEN subgoal_type_rel_simp_add [] 1
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN TRY (TRY_FST (simp 1) (SOLVES (rule (tree_hd typing_tree) 1)))
THEN rule_tree 1 1
THEN subgoal_simp 1
THEN TRY (corres_tac_nth 2))
ORELSE check_Cogent_head @{const_name Put}
(corres_put_boxed_rule 1
THEN print "corres_put_boxed"
THEN rule_tree' (tree_nth 0 handle Subscript => error "tree_nth failed") 0 1
THEN subgoal_simp 1
THEN subgoal_type_rel_simp_add [] 1
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN TRY (TRY_FST (simp 1) (SOLVES (rule (tree_hd typing_tree) 1)))
THEN subgoal_simp 1)
ORELSE check_Cogent_head @{const_name Case}
(rtac (get_thm "corres_simp_cond_gets" RS @{thm iffD2}) 1 THEN
(corres_case_rule 1
THEN print "corres_case"
THEN subgoal_simp 1
THEN subgoal_simp 1
THEN rule_tree 0 1
THEN subgoal_simp 1
THEN rule_tree 1 1
THEN rule_tree 2 1
THEN rule_tree 3 1
THEN TRY (corres_tac_nth 2)
THEN TRY (corres_tac_nth 3)))
ORELSE check_Cogent_head @{const_name Let}
(rtac corres_nested_let 1
THEN print "corres_let (nested)"
THEN rule_tree 0 1
THEN rule_tree 1 1
THEN TRY (corres_tac_nth 1)
THEN TRY (corres_tac_nth 2))
ORELSE corres_let_tac 1
ORELSE check_Cogent_head @{const_name LetBang}
((simp_tac (put_simpset HOL_basic_ss ctxt addsimps [LETBANG_TRUE_def]) 1)
THEN
rule corres_letbang 1
THEN print "corres_letbang"
THEN rule_tree 0 1
THEN rule_tree 1 1
THEN rule_tree 3 1
THEN subgoal_simp 1
THEN TRY (corres_tac_nth 1)
THEN TRY (corres_tac_nth 2))
ORELSE check_Cogent_head @{const_name Split}
(rtac (get_thm "corres_split") 1
THEN print "corres_split"
THEN rule_tree 0 1
THEN subgoal_val_rel_simp_add [] 1
THEN subgoal_simp 1
THEN rule_tree 1 1
THEN rule_tree 2 1
THEN subgoal_val_rel_simp_add [] 1
THEN subgoal_val_rel_simp_add [] 1
THEN TRY (corres_tac_nth 1))
)))
end
in
(simp_tac (put_simpset HOL_basic_ss ctxt addsimps fun_defs) 1)
THEN simp_recguard_tac 1
THEN (fn st => (if verbose then tracing "Fixing unused variables\n" else ();
cogent_C_unused_return_tac ctxt 1 st))
THEN corres_tac_rec typing_tree 0
end
\<close>
ML\<open>
fun peel_two tree = hd (tree_rest (hd (tree_rest (hd tree))));
\<close>
(* Analyse the program and generate proofs based on its call tree. *)
ML \<open>
fun partition _ [] = ([], [])
| partition P (x::xs) = let val (ps, ns) = partition P xs in
if P x then (x::ps, ns) else (ps, x::ns) end
fun max a b = if a < b then b else a
fun maximum [] = error "maximum: empty list"
| maximum [x] = x
| maximum (x::xs) = max x (maximum xs)
fun get_Cogent_funtype ctxt fname = let
val simps = Proof_Context.get_thms ctxt "abbreviated_type_defs"
in
Proof_Context.get_thm ctxt (fname ^ "_type_def")
|> simplify (ctxt addsimps simps)
end
(* check whether the function argument type contains a TFun *)
fun funtype_is_first_order (funtype:term) =
case funtype of (Const (@{const_name Pair}, _) $ _ $
(Const (@{const_name Pair}, _) $ arg $ _)) =>
not (Cogent_type_contains_TFun arg)
| _ => raise TERM ("Expected a Cogent type signature", [funtype])
(* scrape all direct function calls *)
val get_simple_function_calls = let
fun search (Const (@{const_name App}, _) $
(Const (@{const_name Fun}, _) $ Const (callee, _) $ _) $ _) = [Long_Name.base_name callee]
| search (Const (@{const_name App}, _) $
(Const (@{const_name AFun}, _) $ callee $ _) $ _) = [Utils.decode_isa_string callee]
| search (f $ x) = search f @ search x
| search _ = []
in search end
(*
* Infer a call graph. We assume that the program only has:
* - first-order Cogent functions,
* - first-order abstract functions, and
* - second-order abstract functions with first-order Cogent function arguments.
*)
datatype FunType = AbsFun | CogentFun;
(* Warning: CogentCallTree inlines a full subtree at each call site.
* If this doesn't scale, we will need more indirection. *)
datatype 'a CogentCallOrder = FirstOrderCall of 'a CogentCallTree
| SecondOrderCall of ('a CogentCallTree * (term * 'a CogentCallTree) list)
and 'a CogentCallTree = CogentCallTree of ('a * FunType * string * 'a CogentCallOrder list);
fun CogentCallTree_name (CogentCallTree (_, _, name, _)) = name
fun CogentCallTree_funtype (CogentCallTree (_, typ, _, _)) = typ
fun CogentCallTree_data (CogentCallTree (a, _, _, _)) = a
fun CogentCallTree_calls (CogentCallTree (_, _, _, calls)) = calls
(* We need a stack of environments \<xi> to verify with higher-order absfuns later on.
* Each such function call increments \<xi>.
* Get the stack height and the position of each function in the call tree.
* Note that a function may appear in multiple environments. *)
fun calc_call_depth tr = maximum (0 :: map calc_call_order_depth (CogentCallTree_calls tr))
and calc_call_order_depth (FirstOrderCall f) = calc_call_depth f
| calc_call_order_depth (SecondOrderCall (f, args)) =
maximum (map calc_call_depth (f::map snd args)) + (if CogentCallTree_funtype f = AbsFun then 1 else 0)
(*
* FIXME: this only deals with one function (call tree) at a time.
* If there are multiple entry points, we'd want to handle them simultaneously
* to avoid redundant \<xi>'s and subproofs.
*)
fun annotate_depth tr = let
fun annotate' d (CogentCallTree ((), ty, name, calls)) = let
in CogentCallTree (d, ty, name, map (annotate_call' d) calls) end
and annotate_call' d (FirstOrderCall f) = FirstOrderCall (annotate' d f)
| annotate_call' d (SecondOrderCall (f, args)) = let
val d' = if CogentCallTree_funtype f = AbsFun then d-1 else 0
in SecondOrderCall (annotate' d f, map (apsnd (annotate' d')) args) end
in annotate' (calc_call_depth tr) tr end
fun make_call_tree (Cogent_functions, Cogent_abstract_functions) HO_hints ctxt = let
val Cogent_abstract_functions_HO =
filter_out (get_Cogent_funtype ctxt #> Thm.prop_of #> Utils.rhs_of_eq #> funtype_is_first_order)
Cogent_abstract_functions
val FO_call_graph =
Cogent_functions
|> map (fn n => (n, Proof_Context.get_thm ctxt (n ^ "_def") |> Thm.prop_of |> Utils.rhs_of_eq
|> get_simple_function_calls |> distinct (op =)))
|> map (apsnd (filter (fn f => not (exists (fn af => f = af) Cogent_abstract_functions_HO))))
val absfun_decls = map (fn name => (name, CogentCallTree ((), AbsFun, name, []))) Cogent_abstract_functions
fun func_name (Left f) = f
| func_name (Right f) = f
fun add_fun (name, FO_callees) table = let
fun subtree f = case Symtab.lookup table f of
SOME tr => tr
| (* assume that we get Cogent_functions in topological order *)
NONE => error ("make_call_tree: " ^ quote name ^ " calls " ^ quote f ^
" but we don't know anything about it (yet)")
val FO_callees' = FO_callees
|> map (fn f => FirstOrderCall (subtree f))
val HO_callees = Symtab.lookup_list HO_hints name
|> map (fn (f, args) => SecondOrderCall (subtree (func_name f), map (apsnd (subtree o func_name)) args))
in Symtab.update_new (name, CogentCallTree ((), CogentFun, name, FO_callees' @ HO_callees)) table end
in
fold add_fun FO_call_graph (Symtab.make absfun_decls)
end
(* Obtain the absfuns included in each \<xi>_n, up to the call tree depth. *)
fun make_uabsfuns_defs (tr as CogentCallTree (depth, _, _, _)) = let
fun collect_absfuns d (CogentCallTree (d', ty, name, calls)) callees =
(if d = d' andalso ty = AbsFun then [(AbsFun, name, map CogentCallTree_name callees)] else []) @
maps (fn c => case c of
FirstOrderCall t => collect_absfuns d t []
| SecondOrderCall (f, args) => collect_absfuns d f (map snd args) @
maps (fn arg => collect_absfuns d (snd arg) []) args) calls
in map (fn d => collect_absfuns d tr []) (0 upto depth) end
(* Define each of the \<xi>_n. *)
fun define_uabsfuns (defs : string list list) ctxt : ((term * (string * thm)) list * Proof.context) = let
fun define _ [] ctxt = ([], ctxt)
| define n (absfuns::defs') ctxt = let
val name = "\<xi>_" ^ string_of_int n
val typ = @{typ "(funtyp, abstyp, ptrtyp) uabsfuns"}
val rhs = Const (@{const_name undefined}, typ) (* FIXME *)
val (thm, ctxt) = Specification.definition
NONE [] [] ((Binding.name (name ^ "_def"), []),
@{mk_term "?name \<equiv> ?def" (name, def)} (Free (name, typ), rhs)) ctxt
val (thms, ctxt) = define (n+1) defs' ctxt
in (thm::thms, ctxt) end
in define 0 defs ctxt end
(* Convenience wrapper. *)
fun define_uabsfuns' tr ctxt =
make_uabsfuns_defs tr
|> map (map #2 o filter (fn (funtyp, _, _) => funtyp = AbsFun))
|> (fn defs => define_uabsfuns defs ctxt)
|> snd
fun isAutoCorresFunRec ctxt f =
(Proof_Context.get_thms ctxt (f ^ "'.simps"); true)
handle ERROR _ => false
(* Manufacture fake corres rules for first-order absfuns. *)
fun generate_FO_absfun_corres (xi:term) ctxt (fname:string) = let
val abs_rel = Syntax.read_term ctxt "abs_rel"
val state_rel = Syntax.read_term ctxt "state_rel"
val Xi = Syntax.read_term ctxt "\<Xi> :: string \<Rightarrow> poly_type"
val _ = if isAutoCorresFunRec ctxt fname then
error ("Corres_Tac: expected first-order function call for " ^ quote fname ^ " but it is recursive")
else ()
val cfun = Syntax.read_term ctxt (fname ^ "'")
val prop = @{mk_term "Trueprop (?abs_rel ?Xi ?state_rel ?fname ?xi ?cfun)"
(abs_rel, Xi, state_rel, fname, xi, cfun)}
(abs_rel, Xi, state_rel, Utils.encode_isa_string fname, xi, cfun)
|> strip_type |> Syntax.check_term ctxt
in
Goal.prove ctxt [] [] prop (K (Skip_Proof.cheat_tac ctxt 1)) RS Proof_Context.get_thm ctxt "afun_corres"
end
(* The same, but for (higher-order absfun, callees) pairs. *)
fun generate_HO_absfun_corres (xi:term) ctxt (fname:string) (callees:(term * string) list) min_measure = let
val corres = Syntax.read_term ctxt "corres"
val state_rel = Syntax.read_term ctxt "state_rel"
val Xi = Syntax.read_term ctxt "\<Xi> :: string \<Rightarrow> poly_type"
val cfun = Syntax.read_term ctxt (fname ^ "'")
val prop = if isAutoCorresFunRec ctxt fname
then @{mk_term "\<lbrakk> i < length \<gamma>; val_rel (\<gamma> ! i) v'; \<Gamma> ! i = Some (fst (snd (?Xi ?fname))); m \<ge> ?min_m \<rbrakk> \<Longrightarrow>
?corres ?state_rel (App (AFun ?fname []) (Var i))
(do x \<leftarrow> ?cfun m v'; gets (\<lambda>s. x) od) ?xi \<gamma> ?Xi \<Gamma> \<sigma> s"
(corres, Xi, state_rel, fname, xi, cfun, min_m)}
(corres, Xi, state_rel, Utils.encode_isa_string fname, xi, cfun,
Int.toString min_measure |> Syntax.read_term ctxt)
else @{mk_term "\<lbrakk> i < length \<gamma>; val_rel (\<gamma> ! i) v'; \<Gamma> ! i = Some (fst (snd (?Xi ?fname))) \<rbrakk> \<Longrightarrow>
?corres ?state_rel (App (AFun ?fname []) (Var i))
(do x \<leftarrow> ?cfun v'; gets (\<lambda>s. x) od) ?xi \<gamma> ?Xi \<Gamma> \<sigma> s"
(corres, Xi, state_rel, fname, xi, cfun)}
(corres, Xi, state_rel, Utils.encode_isa_string fname, xi, cfun)
fun callee_assm (getter, callee) = @{mk_term "Trueprop (?getter v' = ?callee_tag)" (getter, callee_tag)}
(getter, Syntax.read_term ctxt ("FUN_ENUM_" ^ callee))
fun give_xi_type (t as Const (nm, T)) = (if nm = fst (dest_Const Xi) then Xi else t)
| give_xi_type t = t
val prop' = map callee_assm callees |> foldr Logic.mk_implies prop
|> strip_type |> map_aterms give_xi_type |> Syntax.check_term ctxt
in
Goal.prove ctxt ["i", "\<gamma>", "\<Gamma>", "v", "v'", "\<sigma>", "s", "m"] [] prop' (K (Skip_Proof.cheat_tac ctxt 1))
end
fun unfold_abbreviatedType_term ctxt (Const (nm, @{typ "Cogent.type"}))
= if String.isPrefix "abbreviatedType" (Long_Name.base_name nm)
then Proof_Context.get_thm ctxt (nm ^ "_def")
|> safe_mk_meta_eq |> Thm.concl_of |> Logic.dest_equals |> snd |> SOME
else NONE
| unfold_abbreviatedType_term _ _ = NONE
(* Generate and prove corres rules for Cogent functions. *)
fun make_FO_fun_corres_prop xi_index ctxt fname min_measure = let
val read = Syntax.read_term ctxt
val Xi = read "\<Xi> :: string \<Rightarrow> poly_type"
fun give_xi_type (t as Const (nm, T)) = (if nm = fst (dest_Const Xi) then Xi else t)
| give_xi_type t = t
val cfun = read (fname ^ "'")
val prop = if isAutoCorresFunRec ctxt fname
then @{mk_term "\<And>a a' \<sigma> s m. val_rel a a' \<Longrightarrow> m \<ge> ?min_m \<Longrightarrow>
?corres ?state_rel ?cogent (?cfun m a') ?\<xi> [a] ?\<Xi> [Some (fst (snd ?cogent_type))] \<sigma> s"
(corres, state_rel, cogent, cfun, \<xi>, \<Xi>, cogent_type, min_m)}
(read "corres", read "state_rel", read fname, cfun,
read ("\<xi>_" ^ Int.toString xi_index), Xi, read (fname ^ "_type"),
Int.toString min_measure |> Syntax.read_term ctxt)
else @{mk_term "\<And>a a' \<sigma> s. val_rel a a' \<Longrightarrow>
?corres ?state_rel ?cogent (?cfun a') ?\<xi> [a] ?\<Xi> [Some (fst (snd ?cogent_type))] \<sigma> s"
(corres, state_rel, cogent, cfun, \<xi>, \<Xi>, cogent_type)}
(read "corres", read "state_rel", read fname, cfun,
read ("\<xi>_" ^ Int.toString xi_index), Xi, read (fname ^ "_type"))
in prop |> strip_type |> map_aterms give_xi_type |> Syntax.check_term ctxt end
(* Unfold types in corres rules. *)
fun unfold_Cogent_simps ctxt =
Proof_Context.get_thms ctxt "fst_conv" @
Proof_Context.get_thms ctxt "snd_conv" @
Proof_Context.get_thms ctxt "abbreviated_type_defs"
fun unfold_Cogent_types ctxt simps fname thm =
Local_Defs.unfold ctxt (Proof_Context.get_thms ctxt (fname ^ "_type_def") @ simps) thm
fun mapAccumL _ [] acc = ([], acc)
| mapAccumL f (x::xs) acc = let val (x', acc') = f x acc
val (xs', acc'') = mapAccumL f xs acc'
in (x'::xs', acc'') end
type obligations = (string * FunType * string list * cterm) Symtab.table
fun corres_tree_obligations trs ctxt : obligations = let
fun descend (CogentCallTree ((xi_index, _), AbsFun, name, [])) tab = let
val thm_name = name ^ "_corres_" ^ string_of_int xi_index in
if Symtab.defined tab thm_name then (thm_name, tab) else let
(* generate a fake corres rule and for interesting reasons throw it away *)
val x = generate_FO_absfun_corres (Syntax.read_term ctxt ("\<xi>_" ^ string_of_int xi_index)) ctxt name
|> forall_intr_vars
in tracing (" adding thm " ^ thm_name);
(thm_name, Symtab.update (thm_name, (name, AbsFun, [], Thm.cprop_of x)) tab) end end
| descend (CogentCallTree ((xi_index, min_measure), CogentFun, name, callees)) tab = let
(* Calls to CogentFuns, which we should prove. *)
val thm_name = name ^ "_corres_" ^ string_of_int xi_index
in if Symtab.defined tab thm_name then (thm_name, tab) else let
val (callee_nms, tab) = mapAccumL (fn c => fn tab => case c of
FirstOrderCall f => descend f tab
| SecondOrderCall (f as CogentCallTree ((fxi_index, fmin_measure), AbsFun, fname, []), args) => let
(* Second-order AbsFun calls are specialised to their callees. *)
val nm = (space_implode "_" (fname::map (CogentCallTree_name o snd) args)
^ "_corres_" ^ string_of_int fxi_index)
in if Symtab.defined tab nm then (nm, tab) else let
val tab = fold (snd oo descend) (map snd args) tab
val f_thm = generate_HO_absfun_corres (Syntax.read_term ctxt ("\<xi>_" ^ string_of_int fxi_index))
ctxt fname (map (apsnd CogentCallTree_name) args) fmin_measure
|> forall_intr_vars
in tracing (" adding thm " ^ nm);
(nm, Symtab.update (nm, (fname, AbsFun, [], Thm.cprop_of f_thm)) tab) end end
| tr' => raise TERM ("descend: callees: tr': " ^ @{make_string} tr', [])
) callees tab
val prop = make_FO_fun_corres_prop xi_index ctxt name min_measure
val _ = tracing (" adding thm " ^ thm_name)
in (thm_name, Symtab.update (thm_name, (name, CogentFun, callee_nms, Thm.cterm_of ctxt prop)) tab) end end
| descend tr' _ = raise TERM ("descend: tr': " ^ @{make_string} tr', [])
val tab = fold (snd oo descend o snd) (Symtab.dest trs) Symtab.empty
in tab end
fun corres_tac_driver corres_tac typing_tree_of ctxt (tab : obligations) thm_name
= case Symtab.lookup tab thm_name of SOME (fname, CogentFun, assums, prop) => let
val lookup_assums = map (Symtab.lookup tab #> the) assums
val (callee_info, callee_abs_info) = lookup_assums
|> partition (fn v => #2 v = CogentFun)
val (callee_names, callee_abs_names) = (callee_info, callee_abs_info) |> apply2 (map #1)
val (callee_thm_props, callee_abs_thm_props) = (callee_info, callee_abs_info) |> apply2 (map #4)
val type_unfold_simps = unfold_Cogent_simps ctxt
val fun_defs = Proof_Context.get_thms ctxt (fname ^ "_def") @
Proof_Context.get_thms ctxt (fname ^ "'_def'") @
Proof_Context.get_thms ctxt (fname ^ "_type_def") @
type_unfold_simps
val _ = @{trace} ("corres_tac_driver: Proving " ^ thm_name,
{ prop = prop, callee_props = callee_thm_props,
callee_abs_props = callee_abs_thm_props })
in Goal.prove ctxt []
(map Thm.term_of (callee_thm_props @ callee_abs_thm_props))
(Thm.term_of prop)
(fn args => let
val callee_thms = take (length callee_thm_props) (#prems args) ~~ callee_names
|> map (fn (assum, name) => unfold_Cogent_types ctxt type_unfold_simps name assum)
val callee_abs_thms = drop (length callee_thm_props) (#prems args) ~~ callee_abs_names
|> map (fn (assum, name) => assum |> simp_xi_fully_applied ctxt |> unfold_Cogent_types ctxt type_unfold_simps name)
val _ = @{trace} ("Assumptions for " ^ thm_name, callee_thms, callee_abs_thms)
in corres_tac (#context args) (peel_two (typing_tree_of fname))
fun_defs callee_abs_thms callee_thms
end)
end
| SOME (_, AbsFun, [], _) => @{thm TrueI}
| v => error ("corres_tac_driver: tab contents: " ^ thm_name ^ ": " ^ @{make_string} v)
fun finalise (tab : obligations) ctxt thm_tab = let
fun to_rsn NONE = Thm.trivial (Thm.global_cterm_of @{theory} @{schematic_term "?P :: prop"})
| to_rsn (SOME thm) = thm
fun cleanup thm = thm
|> (ALLGOALS val_rel_thin_tac
THEN prune_params_tac ctxt
THEN distinct_subgoals_tac)
|> Seq.hd
fun inner nm ftab =
case Symtab.lookup ftab nm of SOME thm => (thm, ftab)
| NONE => let
val _ = tracing ("finalise: " ^ nm)
val assum_nms = Symtab.lookup tab nm |> the |> #3
val (concr_assums, abs_assums) = partition (fn n => CogentFun = (Symtab.lookup tab n |> the |> #2)) assum_nms
val assum_nms = concr_assums @ abs_assums
val thm = Symtab.lookup thm_tab nm |> the
val (assums, ftab) = mapAccumL inner assum_nms ftab
val rthm = case thm of NONE => NONE
| SOME t => if Thm.eq_thm (t, @{thm TrueI}) then NONE
else SOME ((map to_rsn assums MRS gen_all (Variable.maxidx_of ctxt) t) |> cleanup)
in (rthm, Symtab.update (nm, rthm) ftab) end
in fold (snd oo inner) (Symtab.keys tab) Symtab.empty end
fun all_corres_goals corres_tac typing_tree_of time_limit ctxt (tab : obligations) =
let
val tl = Time.fromSeconds time_limit
fun run_tac nm = corres_tac_driver corres_tac typing_tree_of ctxt tab nm
handle ERROR x => (tracing ("Failed: " ^ nm ^ " with error:\n" ^ x); raise ERROR x)
fun driver nm = Timing.timing (try (Timeout.apply tl
run_tac)) nm
|> (fn (dur, res) => (tracing ("Time for " ^ nm ^ ": " ^ Timing.message dur); res))
|> (fn NONE => (tracing ("Failed: " ^ nm); (nm, NONE))
| SOME thm => (tracing ("Succeeded: " ^ nm); (nm, SOME thm)))
val res = Par_List.map driver (Symtab.keys tab)
val thm_tab = Symtab.make res
in thm_tab end
(* Top-level driver that attempts to prove a CogentCallTree.
* For each function in the tree, it proves a corres theorem and assigns a standard name.
* If a theorem by that name already exists, that is used instead.
*
* The naming scheme is: <fun>_[<funarg1>_<funarg2>_...]_corres_<xi_index>
* Eg. for f called with function arguments g and h: f_g_h_corres_1
* These names can be obtained using callee_corres_thms.
*
* Known issues:
* - Does not handle C recursion guards.
* - Does not handle higher-order CogentFuns.
* - Should be parallelised.
*)
fun corres_tree tr typing_tree_of corres_tac run_proofs skip_initial time_limit ctxt = let
fun cache_proof ctxt thm_name (make_thms : unit -> thm list) =
(Proof_Context.get_thms ctxt thm_name, ctxt)
handle ERROR _ =>
Utils.define_lemmas thm_name (make_thms ()) ctxt
fun mapAccumL _ [] acc = ([], acc)
| mapAccumL f (x::xs) acc = let val (x', acc') = f x acc
val (xs', acc'') = mapAccumL f xs acc'
in (x'::xs', acc'') end
val type_unfold_simps = unfold_Cogent_simps ctxt
val skip_ctr = Unsynchronized.ref skip_initial
val failed_proofs = Unsynchronized.ref []
fun descend (CogentCallTree (xi_index, AbsFun, name, [])) ctxt = let
(* Simple AbsFun calls. Higher-order calls are handled elsewhere. *)
val (thm, ctxt) =
cache_proof ctxt (name ^ "_corres_" ^ string_of_int xi_index) (fn () =>
[generate_FO_absfun_corres (Syntax.read_term ctxt ("\<xi>_" ^ string_of_int xi_index)) ctxt name
|> unfold_Cogent_types ctxt type_unfold_simps name])
in (CogentCallTree ((xi_index, thm), AbsFun, name, []), ctxt) end
| descend (CogentCallTree (xi_index, CogentFun, name, callees)) ctxt = let
(* Calls to CogentFuns, which we should prove. *)
val (callees', ctxt) = mapAccumL (fn c => fn ctxt => case c of
FirstOrderCall f => descend f ctxt |> apfst FirstOrderCall
| SecondOrderCall (f as CogentCallTree (fxi_index, AbsFun, fname, []), args) => let
(* Second-order AbsFun calls are specialised to their callees. *)
val (args', ctxt) = mapAccumL descend (map snd args) ctxt
val (f_thm, ctxt) = cache_proof ctxt
(space_implode "_" (fname::map (CogentCallTree_name o snd) args) ^ "_corres_" ^ string_of_int fxi_index)
(fn () => [generate_HO_absfun_corres (Syntax.read_term ctxt ("\<xi>_" ^ string_of_int fxi_index))
ctxt fname (map (apsnd CogentCallTree_name) args) 42
|> unfold_Cogent_types ctxt type_unfold_simps fname])
in (SecondOrderCall (CogentCallTree ((fxi_index, f_thm), AbsFun, fname, []), map fst args ~~ args'), ctxt) end)
callees ctxt
val thm_name = name ^ "_corres_" ^ string_of_int xi_index
val _ = if !skip_ctr > 0 then @{trace} ("skipping " ^ string_of_int (!skip_ctr) ^ " more") else ()
val run_proofs = run_proofs andalso !skip_ctr <= 0
val _ = (skip_ctr := !skip_ctr-1)
val (thm, ctxt) =
cache_proof ctxt thm_name (fn () => let
(* Warning: actual proofs ahead. *)
val corres = Syntax.read_term ctxt "corres"
val fun_term = Syntax.read_term ctxt name
val fun_type = Syntax.read_term ctxt (name ^ "_type")
val fun_c = Syntax.read_term ctxt (name ^ "'")
val state_rel = Syntax.read_term ctxt "state_rel"
val xi = Syntax.read_term ctxt ("\<xi>_" ^ string_of_int xi_index)
val Xi = Syntax.read_term ctxt "\<Xi> :: string \<Rightarrow> poly_type"
val prop = @{mk_term "\<And>a a' \<sigma> s. val_rel a a' \<Longrightarrow> ?corres ?state_rel ?fun_term (?fun_c a') ?xi [a] ?Xi
[Some (fst (snd ?fun_type))] \<sigma> s"
(corres, state_rel, fun_term, fun_c, xi, Xi, fun_type)}
(corres, state_rel, fun_term, fun_c, xi, Xi, fun_type)
|> strip_type |> Syntax.check_term ctxt
val (callee_thms, callee_abs_thms) = callees'
|> map (fn call => case call of FirstOrderCall f => f
| SecondOrderCall (f, _) => f)
|> partition (fn tr => CogentCallTree_funtype tr = CogentFun)
|> apply2 (map (CogentCallTree_data #> snd))
val (callee_thms, callee_abs_thms) = (List.concat callee_thms, List.concat callee_abs_thms)
val fun_defs = Proof_Context.get_thms ctxt (name ^ "_def") @
Proof_Context.get_thms ctxt (name ^ "'_def'") @
Proof_Context.get_thms ctxt (name ^ "_type_def") @
type_unfold_simps
val _ = @{trace} ("cogent_corres_tree: " ^ (if run_proofs then "Proving " else "Skipping ") ^ thm_name,
{ prop = Thm.cterm_of ctxt prop,
callees = callees
|> map (fn call => case call of FirstOrderCall f => f
| SecondOrderCall (f, _) => f)
|> map CogentCallTree_name |> commas })
fun fallback_thm msg = (warning ("Failed to prove " ^ thm_name ^ "; error: " ^ msg);
failed_proofs := thm_name :: !failed_proofs;
Goal.prove ctxt [] [] prop (K (Skip_Proof.cheat_tac ctxt 1)))
val (time, thms) = (fn f => Timing.timing f ()) (fn () =>
[(Timeout.apply (Time.fromSeconds time_limit) (fn () =>
(((((Goal.prove ctxt [] [] prop (fn {context, prems} =>
if not run_proofs then Skip_Proof.cheat_tac ctxt 1 else
(corres_tac context (peel_two (typing_tree_of name)) fun_defs
callee_abs_thms callee_thms))
handle Bind => fallback_thm (@{make_string} Bind))
handle Match => fallback_thm (@{make_string} Match))
handle Option => fallback_thm (@{make_string} Option))
handle THM t => fallback_thm (@{make_string} (THM t)))
handle TERM t => fallback_thm (@{make_string} (TERM t)))
handle ERROR e => fallback_thm (@{make_string} (ERROR e))) ()
handle Timeout.TIMEOUT t => fallback_thm (@{make_string} (Timeout.TIMEOUT t)))
|> unfold_Cogent_types ctxt type_unfold_simps name])
val _ = tracing ("Time for " ^ thm_name ^ ": " ^ Timing.message time)
in thms end)
in (CogentCallTree ((xi_index, thm), CogentFun, name, callees'), ctxt) end
val (tr', ctxt) = descend tr ctxt
val _ = if null (!failed_proofs) then () else warning ("Failed proofs: " ^ commas_quote (!failed_proofs))
in (tr', ctxt) end
(* Convenience function for getting the expected corres thm names. *)
fun callee_corres_thms (CogentCallTree (_, _, _, callees)) = callees
|> map (fn call => case call of
FirstOrderCall f => (f, CogentCallTree_name f ^ "_corres_" ^ string_of_int (CogentCallTree_data f))
| SecondOrderCall (f, args) => (f, space_implode "_" (map CogentCallTree_name (f :: map snd args)) ^
"_corres_" ^ string_of_int (CogentCallTree_data f)))
|> partition (fn (tr, _) => CogentCallTree_funtype tr = CogentFun)
|> apply2 (map snd)
(* Assign AutoCorres recursion measures.
* Each second-order function call involves a trip to the dispatcher,
* meaning that the measure decreases by 2 instead of 1. *)
fun calc_call_measure tr = maximum (1 :: map calc_call_order_measure (CogentCallTree_calls tr))
and calc_call_order_measure (FirstOrderCall f) = 1 + calc_call_measure f
| calc_call_order_measure (SecondOrderCall (f, args)) =
1 + max (maximum (map (calc_call_measure o snd) args) + 2) (calc_call_measure f)
fun annotate_measure tr = let
fun annotate' d (CogentCallTree (x, ty, name, calls)) = let
in CogentCallTree ((x, d), ty, name, map (annotate_call' d) calls) end
and annotate_call' d (FirstOrderCall f) = FirstOrderCall (annotate' (d-1) f)
| annotate_call' d (SecondOrderCall (f, args)) =
SecondOrderCall (annotate' (d-1) f, map (apsnd (annotate' (d-3))) args)
in annotate' (calc_call_measure tr) tr end
fun map_annotations f (CogentCallTree (a, ty, name, calls)) =
CogentCallTree (f a, ty, name, calls |>
map (fn c => case c of FirstOrderCall a => FirstOrderCall (map_annotations f a)
| SecondOrderCall (a, bs) =>
SecondOrderCall (map_annotations f a, map (apsnd (map_annotations f)) bs)))
\<close>
end