0fbe82511d
After these changes AutoCorres type checks and compiles, and the proofs work, but for most test cases we still get runtime exceptions. Signed-off-by: Gerwin Klein <gerwin.klein@proofcraft.systems>
418 lines
16 KiB
Standard ML
418 lines
16 KiB
Standard ML
(*
|
|
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*)
|
|
|
|
(*
|
|
* Information about functions in the program we are translating,
|
|
* and the call-graph between them.
|
|
* To support incremental translation, we store the function information
|
|
* for every intermediate phase as well.
|
|
*)
|
|
signature FUNCTION_INFO2 =
|
|
sig
|
|
(*** Basic data types ***)
|
|
|
|
(* List of AutoCorres phases. *)
|
|
datatype phase = CP (* Initial definition we get from the C parser *)
|
|
| L1 (* SimplConv *)
|
|
| L2 (* LocalVarExtract *)
|
|
| HL (* HeapLift *) (* TODO: rename to HeapAbstract *)
|
|
| WA (* WordAbstract *)
|
|
| TS (* TypeStrengthen *);
|
|
val string_of_phase : phase -> string;
|
|
val phase_ord : phase * phase -> order;
|
|
structure Phasetab : TABLE; (* currently unused *)
|
|
|
|
(* Function info for a single phase. *)
|
|
type function_info = {
|
|
(* Name of the function. *)
|
|
name : string,
|
|
|
|
(* The translation phase for this definition. *)
|
|
phase : phase,
|
|
|
|
(* Constant for the function, which can be inserted as a call to the
|
|
* function. Unlike "raw_const", this includes any locale parameters
|
|
* required by the function. *)
|
|
const: term,
|
|
|
|
(* Raw constant for the function. Existence of this constant in another
|
|
* function's body indicates that that function calls this one. *)
|
|
raw_const: term,
|
|
|
|
(* Arguments of the function, in order, excluding measure variables. *)
|
|
args : (string * typ) list,
|
|
|
|
(* Return type of the function ("unit" is used for void). *)
|
|
return_type : typ,
|
|
|
|
(* Function calls. Mutually recursive calls go in rec_callees. *)
|
|
callees : symset,
|
|
rec_callees : symset,
|
|
|
|
(* Definition of the function. *)
|
|
definition : thm,
|
|
|
|
(* corres theorem for the function. (TrueI when phase = CP.) *)
|
|
corres_thm : thm,
|
|
|
|
(* monad_mono theorem for the function, if it is recursive. *)
|
|
mono_thm : thm option,
|
|
|
|
(* Is this function actually being translated, or are we just
|
|
* wrapping the SIMPL code? *)
|
|
is_simpl_wrapper : bool,
|
|
|
|
(* Is this function generated by AutoCorres as a placeholder for
|
|
* a function we didn't have the source code to? *)
|
|
invented_body : bool
|
|
};
|
|
|
|
(* Standard result sequence that is passed between stages *)
|
|
type phase_results = (local_theory * function_info Symtab.table) FSeq.fseq;
|
|
|
|
val function_info_upd_name : string -> function_info -> function_info;
|
|
val function_info_upd_phase : phase -> function_info -> function_info;
|
|
(* Also updates raw_const. *)
|
|
val function_info_upd_const : term -> function_info -> function_info;
|
|
val function_info_upd_args : (string * typ) list -> function_info -> function_info;
|
|
val function_info_upd_return_type : typ -> function_info -> function_info;
|
|
val function_info_upd_callees : symset -> function_info -> function_info;
|
|
val function_info_upd_rec_callees : symset -> function_info -> function_info;
|
|
val function_info_upd_definition : thm -> function_info -> function_info;
|
|
val function_info_upd_mono_thm : thm option -> function_info -> function_info;
|
|
val function_info_upd_corres_thm : thm -> function_info -> function_info;
|
|
val function_info_upd_invented_body : bool -> function_info -> function_info;
|
|
val function_info_upd_is_simpl_wrapper : bool -> function_info -> function_info;
|
|
|
|
(* Convenience getters. *)
|
|
val is_function_recursive : function_info -> bool;
|
|
val all_callees : function_info -> symset;
|
|
|
|
(* Generate initial function_info from the C Parser's output. *)
|
|
val init_function_info : Proof.context -> string -> function_info Symtab.table;
|
|
|
|
type call_graph_info = {
|
|
(* Topologically sorted function calls, in dependency order.
|
|
* Each sub-list represents one function or recursive function group. *)
|
|
topo_sorted_functions : symset list,
|
|
|
|
(* Table mapping raw_consts to functions. *)
|
|
const_to_function : string Termtab.table,
|
|
|
|
(* Table mapping each recursive function to its recursive function group.
|
|
* Non-recursive functions do not appear in the table. *)
|
|
recursive_group_of : symset Symtab.table
|
|
};
|
|
|
|
(* Calculate call-graph information.
|
|
* Also updates the callees and rec_callees entries of its inputs,
|
|
* which are assumed to have outdated callee info.
|
|
*
|
|
* Ideally, we'd also have a pre_function_info type that doesn't have
|
|
* outdated callees, but dealing with ML records is annoying. *)
|
|
val calc_call_graph : function_info Symtab.table -> call_graph_info * function_info Symtab.table;
|
|
|
|
(* Update callees and rec_callees for the given recursive function group,
|
|
* relative to a set of background functions (that must not contain the
|
|
* given group).
|
|
*
|
|
* Returns a sequence of function groups in a valid topological order.
|
|
* We return multiple groups because function calls can be removed by
|
|
* dead code elimination and other transformations, which could cause
|
|
* the original group to split.*)
|
|
val recalc_callees :
|
|
function_info Symtab.table -> (* background *)
|
|
function_info Symtab.table -> (* group *)
|
|
function_info Symtab.table list;
|
|
end;
|
|
|
|
structure FunctionInfo : FUNCTION_INFO2 =
|
|
struct
|
|
|
|
datatype phase = CP | L1 | L2 | HL | WA | TS;
|
|
fun string_of_phase CP = "CP"
|
|
| string_of_phase L1 = "L1"
|
|
| string_of_phase L2 = "L2"
|
|
| string_of_phase HL = "HL"
|
|
| string_of_phase WA = "WA"
|
|
| string_of_phase TS = "TS";
|
|
fun encode_phase CP = 0
|
|
| encode_phase L1 = 1
|
|
| encode_phase L2 = 2
|
|
| encode_phase HL = 3
|
|
| encode_phase WA = 4
|
|
| encode_phase TS = 5;
|
|
val phase_ord = int_ord o apply2 encode_phase;
|
|
structure Phasetab = Table(
|
|
type key = phase
|
|
val ord = phase_ord);
|
|
|
|
type function_info = {
|
|
name : string,
|
|
phase : phase,
|
|
const : term,
|
|
raw_const : term,
|
|
args : (string * typ) list,
|
|
return_type : typ,
|
|
callees : symset,
|
|
rec_callees : symset,
|
|
definition : thm,
|
|
mono_thm : thm option,
|
|
corres_thm : thm,
|
|
invented_body : bool,
|
|
is_simpl_wrapper : bool
|
|
};
|
|
|
|
type phase_results = (local_theory * function_info Symtab.table) FSeq.fseq;
|
|
|
|
(* We use FunctionalRecordUpdate internally to define the setters *)
|
|
open FunctionalRecordUpdate;
|
|
local
|
|
fun from name phase const raw_const args return_type callees rec_callees
|
|
definition mono_thm corres_thm is_simpl_wrapper invented_body =
|
|
{ name = name,
|
|
phase = phase,
|
|
const = const,
|
|
raw_const = raw_const,
|
|
args = args,
|
|
return_type = return_type,
|
|
callees = callees,
|
|
rec_callees = rec_callees,
|
|
definition = definition,
|
|
mono_thm = mono_thm,
|
|
corres_thm = corres_thm,
|
|
is_simpl_wrapper = is_simpl_wrapper,
|
|
invented_body = invented_body };
|
|
fun from' invented_body is_simpl_wrapper corres_thm mono_thm definition
|
|
rec_callees callees return_type args raw_const const phase name =
|
|
{ name = name,
|
|
phase = phase,
|
|
const = const,
|
|
raw_const = raw_const,
|
|
args = args,
|
|
return_type = return_type,
|
|
callees = callees,
|
|
rec_callees = rec_callees,
|
|
definition = definition,
|
|
mono_thm = mono_thm,
|
|
corres_thm = corres_thm,
|
|
is_simpl_wrapper = is_simpl_wrapper,
|
|
invented_body = invented_body };
|
|
fun to f { name,
|
|
phase,
|
|
const,
|
|
raw_const,
|
|
args,
|
|
return_type,
|
|
callees,
|
|
rec_callees,
|
|
definition,
|
|
mono_thm,
|
|
corres_thm,
|
|
is_simpl_wrapper,
|
|
invented_body } =
|
|
f name phase const raw_const args return_type callees rec_callees
|
|
definition mono_thm corres_thm is_simpl_wrapper invented_body;
|
|
fun update x = makeUpdate13 (from, from', to) x;
|
|
in
|
|
fun function_info_upd_name name pinfo = update pinfo (U#name name) $$;
|
|
fun function_info_upd_phase phase pinfo = update pinfo (U#phase phase) $$;
|
|
fun function_info_upd_const_ const pinfo = update pinfo (U#const const) $$;
|
|
fun function_info_upd_raw_const_ raw_const pinfo = update pinfo (U#raw_const raw_const) $$;
|
|
fun function_info_upd_args args pinfo = update pinfo (U#args args) $$;
|
|
fun function_info_upd_return_type return_type pinfo = update pinfo (U#return_type return_type) $$;
|
|
fun function_info_upd_callees callees pinfo = update pinfo (U#callees callees) $$;
|
|
fun function_info_upd_rec_callees rec_callees pinfo = update pinfo (U#rec_callees rec_callees) $$;
|
|
fun function_info_upd_definition definition pinfo = update pinfo (U#definition definition) $$;
|
|
fun function_info_upd_mono_thm mono_thm pinfo = update pinfo (U#mono_thm mono_thm) $$;
|
|
fun function_info_upd_corres_thm corres_thm pinfo = update pinfo (U#corres_thm corres_thm) $$;
|
|
fun function_info_upd_is_simpl_wrapper is_simpl_wrapper pinfo = update pinfo (U#is_simpl_wrapper is_simpl_wrapper) $$;
|
|
fun function_info_upd_invented_body invented_body pinfo = update pinfo (U#invented_body invented_body) $$;
|
|
|
|
fun function_info_upd_const t = function_info_upd_const_ t o function_info_upd_raw_const_ (head_of t);
|
|
end;
|
|
|
|
fun is_function_recursive { rec_callees, ... } = not (Symset.is_empty rec_callees);
|
|
|
|
fun all_callees { rec_callees, callees, ... } = Symset.union rec_callees callees;
|
|
|
|
type call_graph_info = {
|
|
topo_sorted_functions : symset list,
|
|
const_to_function : string Termtab.table,
|
|
recursive_group_of : symset Symtab.table
|
|
};
|
|
|
|
fun calc_call_graph fn_infos = let
|
|
val const_to_function =
|
|
Symtab.dest fn_infos
|
|
|> map (fn (name, info) => (#raw_const info, name))
|
|
|> Termtab.make;
|
|
|
|
(* Get a function's direct callees, based on the list of constants that appear
|
|
* in its definition. *)
|
|
fun get_direct_callees fn_info = let
|
|
val body =
|
|
#definition fn_info
|
|
|> Thm.concl_of
|
|
|> Utils.rhs_of_eq;
|
|
in
|
|
(* Ignore function bodies if we are using SIMPL wrappers. *)
|
|
if #is_simpl_wrapper fn_info then [] else
|
|
Term.fold_aterms (fn t => fn a =>
|
|
(Termtab.lookup const_to_function t
|
|
|> Option.map single
|
|
|> the_default []) @ a) body []
|
|
|> distinct (op =)
|
|
end;
|
|
|
|
(* Call graph of all functions. *)
|
|
val fn_callees_lists = fn_infos |> Symtab.map (K get_direct_callees);
|
|
(* Add each function to its own callees to get a complete inverse *)
|
|
val fn_callers_lists = flip_symtab (Symtab.map cons fn_callees_lists);
|
|
|
|
val topo_sorted_functions =
|
|
Topo_Sort.topo_sort {
|
|
cmp = String.compare,
|
|
graph = Symtab.lookup fn_callees_lists #> the,
|
|
converse = Symtab.lookup fn_callers_lists #> the
|
|
} (Symtab.keys fn_callees_lists |> sort String.compare)
|
|
|> map Symset.make;
|
|
|
|
val fn_callees = Symtab.map (K Symset.make) fn_callees_lists;
|
|
fun is_recursive_singleton f =
|
|
Symset.contains (Utils.the' ("is_recursive_singleton: " ^ f)
|
|
(Symtab.lookup fn_callees f)) f;
|
|
val recursive_group_of =
|
|
topo_sorted_functions
|
|
|> maps (fn f_group =>
|
|
(* Exclude non-recursive functions *)
|
|
if Symset.card f_group = 1 andalso not (is_recursive_singleton (hd (Symset.dest f_group)))
|
|
then []
|
|
else Symset.dest f_group ~~ replicate (Symset.card f_group) f_group)
|
|
|> Symtab.make;
|
|
|
|
(* Now update callee info. *)
|
|
fun maybe_symset NONE = Symset.empty
|
|
| maybe_symset (SOME x) = x;
|
|
val fn_infos' =
|
|
fn_infos |> Symtab.map (fn f => let
|
|
val (rec_callees, callees) =
|
|
Symset.dest (Utils.the' ("not in fn_callees: " ^ f) (Symtab.lookup fn_callees f))
|
|
|> List.partition (Symset.contains (maybe_symset (Symtab.lookup recursive_group_of f)));
|
|
in function_info_upd_callees (Symset.make callees) o
|
|
function_info_upd_rec_callees (Symset.make rec_callees) end);
|
|
in ({ topo_sorted_functions = topo_sorted_functions,
|
|
const_to_function = const_to_function,
|
|
recursive_group_of = recursive_group_of
|
|
}, fn_infos')
|
|
end;
|
|
|
|
fun recalc_callees base_infos fn_infos = let
|
|
val base_consts =
|
|
Symtab.dest base_infos
|
|
|> map (fn (f, info) => (#raw_const info, f))
|
|
|> Termtab.make;
|
|
(* restrict_fn_infos has the correct call graph,
|
|
* but omits functions outside fn_infos *)
|
|
val (call_graph, restrict_fn_infos) = calc_call_graph fn_infos;
|
|
|
|
fun update_info f info = let
|
|
val restrict_info = the (Symtab.lookup restrict_fn_infos f);
|
|
(* Update calls into base_info *)
|
|
val f_body = Thm.prop_of (#definition info) |> Utils.rhs_of_eq;
|
|
val base_callees' =
|
|
if #is_simpl_wrapper info then Symset.empty else
|
|
Term.fold_aterms (fn t => fn a =>
|
|
(Termtab.lookup base_consts t
|
|
|> Option.map single
|
|
|> the_default []) @ a)
|
|
f_body []
|
|
|> Symset.make;
|
|
(* base_infos should not include fn_infos, otherwise this weird call would exist *)
|
|
val () = assert (not (Symset.contains base_callees' f))
|
|
"FunctionInfo.recalc_base_callees";
|
|
(* rec_callees has been recalculated *)
|
|
val rec_callees' = #rec_callees restrict_info;
|
|
(* Some rec_callees may have become callees due to breaking recursive loops *)
|
|
val callees' = Symset.union base_callees' (#callees restrict_info);
|
|
in info
|
|
|> function_info_upd_rec_callees rec_callees'
|
|
|> function_info_upd_callees callees'
|
|
end;
|
|
|
|
in #topo_sorted_functions call_graph
|
|
|> map (fn group =>
|
|
Symset.dest group
|
|
|> map (fn f => (f, update_info f (the (Symtab.lookup fn_infos f))))
|
|
|> Symtab.make)
|
|
end;
|
|
|
|
fun init_function_info ctxt filename = let
|
|
val thy = Proof_Context.theory_of ctxt;
|
|
val prog_info = ProgramInfo.get_prog_info ctxt filename;
|
|
val csenv = #csenv prog_info;
|
|
|
|
(* Get information about a single function. *)
|
|
fun gen_fn_info name (return_ctype, _, carg_list) = let
|
|
(* Convert C Parser return type into a HOL return type. *)
|
|
val return_type =
|
|
if return_ctype = Absyn.Void then
|
|
@{typ unit}
|
|
else
|
|
CalculateState.ctype_to_typ (thy, return_ctype);
|
|
|
|
(* Convert arguments into a list of (name, HOL type) pairs. *)
|
|
val arg_list = map (fn v =>
|
|
(ProgramAnalysis.get_mname v |> MString.dest,
|
|
CalculateState.ctype_to_typ (thy, ProgramAnalysis.get_vi_type v))
|
|
) carg_list;
|
|
|
|
(*
|
|
* Get constant, type signature and definition of the function.
|
|
*
|
|
* The definition may not exist if the function is declared "extern", but
|
|
* never defined. In this case, we replace the body of the function with
|
|
* what amounts to a "fail" command. Any C body is a valid refinement of
|
|
* this, allowing our abstraction to succeed.
|
|
*)
|
|
val const = Utils.get_term ctxt (name ^ "_'proc");
|
|
val myvars_typ = #state_type prog_info;
|
|
val (definition, invented) =
|
|
(Proof_Context.get_thm ctxt (name ^ "_body_def"), false)
|
|
handle ERROR _ =>
|
|
(Thm.instantiate (TVars.make [((("'a", 0), ["HOL.type"]), Thm.ctyp_of ctxt myvars_typ)], Vars.empty)
|
|
@{thm undefined_function_body_def}, true);
|
|
in {
|
|
name = name,
|
|
phase = CP,
|
|
args = arg_list,
|
|
return_type = return_type,
|
|
const = const,
|
|
raw_const = const,
|
|
callees = Symset.empty, (* filled in later *)
|
|
rec_callees = Symset.empty,
|
|
definition = definition,
|
|
mono_thm = NONE,
|
|
corres_thm = @{thm TrueI},
|
|
is_simpl_wrapper = false,
|
|
invented_body = invented
|
|
}
|
|
end
|
|
|
|
val raw_infos = ProgramAnalysis.get_fninfo csenv
|
|
|> Symtab.dest
|
|
|> map (uncurry gen_fn_info);
|
|
(* We discard the call graph info here.
|
|
* After calling init_function_info, we often want to change some of the entries,
|
|
* which usually requires recalculating it anyway. *)
|
|
val (_, fn_infos) =
|
|
calc_call_graph (Symtab.make (map (fn info => (#name info, info)) raw_infos));
|
|
in
|
|
fn_infos
|
|
end;
|
|
|
|
end; (* structure FunctionInfo *)
|