blob: 5183bd6ca68f3d9bca9b48c06b87fb5e12210c20 [file] [edit]
(*
* 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 CogentHigherOrder imports
"Cogent.TypeProofGen"
"AutoCorres.AutoCorres"
begin
(*
* Abstract semantics for guessing function arguments to higher-order function calls.
* These semantics only work when the hofun and funarg are hardcoded in the function
* that contains the hofun call site.
*
* Example:
* -- previously defined
* funarg x = ...
* hofun (f, x, n) = ...
*
* -- hofun-funarg call can be inferred correctly
* f x = let args = (funarg, x, 42)
* in hofun args
*
* -- multiple (all hardcoded) funargs can be detected inside compound values
* f = hofun2 ((funarg1, x), (y, z), Some funarg2)
*
* -- fails: funarg isn't statically known
* f funarg = hofun (funarg, 0, 42)
*
* -- also fails: farg's value isn't (syntactically) hardcoded
* f x = let farg = if true then funarg else funarg
* in hofun (farg, x, 42)
*)
type_synonym 'f FunCall = "('f, 'f expr) sum"
(*
* Function value annotations for Cogent values.
* An InValue lists known function pointers inside a Cogent value and their locations.
* Each "InData list" contains information about one part of the value, ending in
* an IsFunction that specifies the function name.
* (Eg. if nothing is known about a Cogent value, its Invalue is [].)
*)
datatype 'f InData = InProduct nat
| InRecord nat
| InSum string (* tag name; used by make_HO_call_hints *)
| IsFunction "'f FunCall"
type_synonym 'f InValue = "'f InData list list"
primrec InValue_of where
"InValue_of None = []"
| "InValue_of (Some tv) = snd tv"
fun unzip :: "('a \<times> 'b) list \<Rightarrow> 'a list \<times> 'b list" where
"unzip [] = ([], [])"
| "unzip ((a,b)#xs) = (let (as, bs) = unzip xs in (a#as, b#bs))"
definition "BadType = undefined" (* type mismatch detected *)
(*
* FIXME: port this to ML for performance.
*
* Interpret an expression, returning a list of call sites and
* statically known funargs. Return value is (calls \<times> ptrs) where:
* \<bullet> calls is a list of (fun, fptrs) pairs describing function calls and their funargs.
* Note that first-order calls are included (fptrs will be null).
* \<bullet> ptrs points to known function values in the return value of the expression.
*)
fun funcall_sem :: "'f InValue env \<Rightarrow> 'f expr \<Rightarrow> ('f FunCall \<times> 'f InValue) list \<times> 'f InValue" where
"funcall_sem \<gamma> (Var v) = ([], \<gamma> ! v)"
| "funcall_sem \<gamma> (AFun absfun _) = ([], [[IsFunction (Inl absfun)]])"
| "funcall_sem \<gamma> (Fun f _) = ([], [[IsFunction (Inr f)]])"
| "funcall_sem \<gamma> (App (AFun absfun _) x) =
(let (calls, ptrs) = funcall_sem \<gamma> x
in (calls @ [(Inl absfun, ptrs)], []))"
| "funcall_sem \<gamma> (App (Fun f _) x) =
(let (calls, ptrs) = funcall_sem \<gamma> x
in (calls @ [(Inr f, ptrs)], []))"
| "funcall_sem \<gamma> (App f x) = (fst (funcall_sem \<gamma> f) @ fst (funcall_sem \<gamma> x), [])"
| "funcall_sem \<gamma> (Con _ tag x) =
(let (calls, ptrs) = funcall_sem \<gamma> x
in (calls, map ((#) (InSum tag)) ptrs))"
| "funcall_sem \<gamma> (Struct tys xs) =
(let (callss, ptrss) = unzip (map (funcall_sem \<gamma>) xs)
in (concat callss, concat (map (\<lambda>(n, v). map ((#) (InRecord n)) v) (enumerate 0 ptrss))))"
| "funcall_sem \<gamma> (Member x f) =
(case funcall_sem \<gamma> x of (calls, ptrs) \<Rightarrow>
(calls, map tl (filter (\<lambda>ptr. case ptr of InRecord f' # _ \<Rightarrow> f = f' | _ \<Rightarrow> BadType) ptrs)))"
| "funcall_sem \<gamma> (Cast _ x) = funcall_sem \<gamma> x"
| "funcall_sem \<gamma> (Tuple x y) = (let (calls, ptrs) = funcall_sem \<gamma> x;
(calls', ptrs') = funcall_sem \<gamma> y
in (calls @ calls', map ((#) (InProduct 0)) ptrs @ map ((#) (InProduct 1)) ptrs'))"
| "funcall_sem \<gamma> (Put x f y) =
(let (calls, ptrs) = funcall_sem \<gamma> x;
(calls', ptrs') = funcall_sem \<gamma> y
in (calls @ calls',
filter (\<lambda>ptr. case ptr of InRecord f' # _ \<Rightarrow> f \<noteq> f' | _ \<Rightarrow> BadType) ptrs @
map ((#) (InRecord f)) ptrs'))"
| "funcall_sem \<gamma> (Let x y) =
(let (calls, ptrs) = funcall_sem \<gamma> x;
(calls', ptrs') = funcall_sem (ptrs # \<gamma>) y
in (calls @ calls', ptrs'))"
| "funcall_sem \<gamma> (LetBang _ x y) =
(let (calls, ptrs) = funcall_sem \<gamma> x;
(calls', ptrs') = funcall_sem (ptrs # \<gamma>) y
in (calls @ calls', ptrs'))"
| "funcall_sem \<gamma> (Case x c y z) =
(let (calls, ptrs) = funcall_sem \<gamma> x;
(ycalls, yptrs) = funcall_sem (map tl (filter (\<lambda>ptr. case ptr of InSum c' # _ \<Rightarrow> c = c' | _ \<Rightarrow> BadType) ptrs) # \<gamma>) y;
(zcalls, zptrs) = funcall_sem (map tl (filter (\<lambda>ptr. case ptr of InSum c' # _ \<Rightarrow> c \<noteq> c' | _ \<Rightarrow> BadType) ptrs) # \<gamma>) z
in (calls @ ycalls @ zcalls, []))" (* cannot determine ptrs statically *) (* TODO: warn if [yz]ptrs nonempty *)
| "funcall_sem \<gamma> (Esac x tag) =
(let (calls, ptrs) = funcall_sem \<gamma> x
in (calls, map tl (filter (\<lambda>ptr. case ptr of InSum _ # _ \<Rightarrow> True | _ \<Rightarrow> BadType) ptrs)))"
| "funcall_sem \<gamma> (If x y z) =
(let (calls, ptrs) = funcall_sem \<gamma> x;
(ycalls, yptrs) = funcall_sem \<gamma> y;
(zcalls, zptrs) = funcall_sem \<gamma> z
in (calls @ ycalls @ zcalls, []))" (* cannot determine ptrs statically *)
| "funcall_sem \<gamma> (Take x f y) =
(let (calls, ptrs) = funcall_sem \<gamma> x;
(calls', ptrs') = funcall_sem (map tl (filter (\<lambda>ptr. case ptr of InRecord f' # _ \<Rightarrow> f = f' | _ \<Rightarrow> BadType) ptrs) # ptrs # \<gamma>) y
in (calls @ calls', ptrs'))"
| "funcall_sem \<gamma> (Split x y) =
(let (calls, ptrs) = funcall_sem \<gamma> x;
(calls', ptrs') = funcall_sem
(map tl (filter (\<lambda>ptr. case ptr of InProduct 0 # _ \<Rightarrow> True | _ \<Rightarrow> BadType) ptrs) #
map tl (filter (\<lambda>ptr. case ptr of InProduct (Suc 0) # _ \<Rightarrow> True | _ \<Rightarrow> BadType) ptrs) # \<gamma>) y
in (calls @ calls', ptrs'))"
| "funcall_sem \<gamma> _ = ([], [])"
(* Use the simplifier to extract higher-order function call information. *)
ML \<open>
structure CogentHigherOrder = struct
(* ML representation of InData type *)
datatype InData = InRecord of int
| InProduct of int
| InSum of string
| IsFunction of (string, string) Either
fun dest_funcall (Const (@{const_name Inl}, _) $ absfun) = Left (HOLogic.dest_string absfun)
| dest_funcall (Const (@{const_name Inr}, _) $ Const (f, _)) = Right f
| dest_funcall f = raise TERM ("dest_funcall: unrecognised call", [f])
fun parse_ptr ptr =
HOLogic.dest_list ptr
|> map (fn p =>
(case p of
Const (@{const_name InRecord}, _) $ n => InRecord (HOLogic.dest_nat n)
| Const (@{const_name InProduct}, _) $ n => InProduct (HOLogic.dest_nat n)
| Const (@{const_name InSum}, _) $ s => InSum (HOLogic.dest_string s)
| Const (@{const_name IsFunction}, _) $ f => IsFunction (dest_funcall f)))
fun parse_calls calls =
HOLogic.dest_list calls |> map HOLogic.dest_prod
|> map (fn (f, ptrs) => (dest_funcall f, HOLogic.dest_list ptrs |> map parse_ptr))
fun get_HO_calls ctxt f =
(* Expression that evaluates to the higher-order call lists *)
@{mk_term "filter (\<lambda>call. snd call \<noteq> []) (fst (funcall_sem [[]] ?f))" f}
(Syntax.read_term ctxt f)
|> Thm.cterm_of ctxt
(* Unfold funcall_sem *)
|> Simplifier.rewrite (ctxt addsimps (Proof_Context.get_thms ctxt (f ^ "_def")))
|> Thm.prop_of |> Utils.rhs_of_eq
|> parse_calls
(* Post-processing: translate InData accessors to C datatype accessors, which can be plugged
* into the Corres_Tac code. *)
fun the'' str x =
(the x) handle Option => error (str ())
fun nth'' str xs n =
(nth xs n) handle Subscript => error (str ())
fun nth_field_of struct_info C_typ n =
nth'' (fn _ => raise TYPE ("Type does not have field #" ^ string_of_int n, [C_typ], []))
(Typtab.lookup struct_info C_typ
|> the'' (fn _ => raise TYPE ("No such struct: ", [C_typ], []))
|> #field_info) n
fun ptrs_to_C_getter struct_info C_typ (InRecord n :: ptrs) = let
val field = nth_field_of struct_info C_typ n
val (getter', funarg) = ptrs_to_C_getter struct_info (#field_type field) ptrs
in (#name field :: getter', funarg) end
| ptrs_to_C_getter struct_info C_typ (InProduct n :: ptrs) = let
val field = nth_field_of struct_info C_typ n
val (getter', funarg) = ptrs_to_C_getter struct_info (#field_type field) ptrs
in (#name field :: getter', funarg) end
| ptrs_to_C_getter struct_info C_typ (InSum s :: ptrs) = let
val field_info = Typtab.lookup struct_info C_typ
|> the'' (fn _ => raise TYPE ("No such struct: ", [C_typ], [])) |> #field_info
val field = case filter (fn field => #name field = s ^ "_C") field_info of
[field] => field
| _ => raise TYPE ("Type does not have field " ^ quote s, [C_typ], [])
val (getter', funarg) = ptrs_to_C_getter struct_info (#field_type field) ptrs
in (#name field :: getter', funarg) end
| ptrs_to_C_getter struct_info C_typ [IsFunction f] = ([], f)
| ptrs_to_C_getter _ _ _ = error "ptrs_to_C_getter: failed to parse ptrs"
fun make_HO_call_hints ctxt C_src f = let
val ACfunctions = Symtab.lookup (AutoCorresFunctionInfo.get (Proof_Context.theory_of ctxt)) C_src
|> Utils.the' ("No such C file: " ^ quote C_src)
|> (fn finfo => FunctionInfo.Phasetab.lookup finfo FunctionInfo.TS)
|> Utils.the' ("Failed to get type strengthening function table")
val struct_info = Symtab.lookup (HeapInfo.get (Proof_Context.theory_of ctxt)) C_src
|> Utils.the' ("No such C file: " ^ quote C_src)
|> #heap_info
|> #struct_types
in get_HO_calls ctxt f
|> map (fn (call, ptrs) => let
val hofun = (case call of Left f => f | Right f => f)
val [(_, hoarg_typ)] = Symtab.lookup ACfunctions hofun
|> Utils.the' ("No such C func: " ^ quote hofun) |> #args
in (call, map (ptrs_to_C_getter struct_info hoarg_typ) ptrs) end)
end
end
\<close>
end