diff options
| -rw-r--r-- | lib/coverage/Cargo.toml | 11 | ||||
| -rw-r--r-- | lib/coverage/Makefile | 3 | ||||
| -rw-r--r-- | lib/coverage/src/lib.rs | 132 | ||||
| -rw-r--r-- | sailcov/Makefile | 4 | ||||
| -rw-r--r-- | sailcov/dune | 2 | ||||
| -rw-r--r-- | sailcov/dune-project | 1 | ||||
| -rw-r--r-- | sailcov/main.ml | 190 | ||||
| -rw-r--r-- | src/ast_util.ml | 2 | ||||
| -rw-r--r-- | src/ast_util.mli | 2 | ||||
| -rw-r--r-- | src/jib/anf.ml | 13 | ||||
| -rw-r--r-- | src/jib/c_backend.ml | 149 | ||||
| -rw-r--r-- | src/jib/c_backend.mli | 3 | ||||
| -rw-r--r-- | src/jib/c_codegen.ml | 14 | ||||
| -rw-r--r-- | src/jib/jib_compile.ml | 44 | ||||
| -rw-r--r-- | src/jib/jib_compile.mli | 11 | ||||
| -rw-r--r-- | src/jib/jib_smt.ml | 7 | ||||
| -rw-r--r-- | src/lexer.mll | 12 | ||||
| -rw-r--r-- | src/rewrites.ml | 18 | ||||
| -rw-r--r-- | src/sail.ml | 9 | ||||
| -rw-r--r-- | src/type_check.ml | 16 |
20 files changed, 460 insertions, 183 deletions
diff --git a/lib/coverage/Cargo.toml b/lib/coverage/Cargo.toml new file mode 100644 index 00000000..f4fdf1d7 --- /dev/null +++ b/lib/coverage/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sail-coverage" +version = "0.1.0" +authors = ["Alasdair <alasdair.armstrong@cl.cam.ac.uk>"] +edition = "2018" + +[dependencies] +lazy_static = "1.4.0" + +[lib] +crate-type = ["staticlib"] diff --git a/lib/coverage/Makefile b/lib/coverage/Makefile new file mode 100644 index 00000000..55ed9fdc --- /dev/null +++ b/lib/coverage/Makefile @@ -0,0 +1,3 @@ +libsail_coverage.a: src/*.rs Cargo.toml Makefile + cargo build --release + cp target/release/libsail_coverage.a . diff --git a/lib/coverage/src/lib.rs b/lib/coverage/src/lib.rs new file mode 100644 index 00000000..003f8b88 --- /dev/null +++ b/lib/coverage/src/lib.rs @@ -0,0 +1,132 @@ +extern crate lazy_static; +use lazy_static::lazy_static; + +use std::collections::HashSet; +use std::ffi::{CStr, CString}; +use std::fs::{OpenOptions, File}; +use std::io::Write; +use std::os::raw::{c_char, c_int}; +use std::sync::Mutex; + +#[derive(Eq, PartialEq, Hash)] +struct Span { + sail_file: CString, + line1: i32, + char1: i32, + line2: i32, + char2: i32, +} + +lazy_static! { + static ref BRANCHES: Mutex<HashSet<Span>> = Mutex::new(HashSet::new()); + static ref FUNCTIONS: Mutex<HashSet<Span>> = Mutex::new(HashSet::new()); +} + +fn function_entry(_function_name: &CStr, span: Span) { + FUNCTIONS.lock().unwrap().insert(span); +} + +fn branch_taken(_branch_id: i32, span: Span) { + BRANCHES.lock().unwrap().insert(span); +} + +fn branch_reached(_branch_id: i32, _span: Span) { + () +} + +fn write_locations(file: &mut File, kind: char, spans: &Mutex<HashSet<Span>>) -> bool { + for span in spans.lock().unwrap().iter() { + let res = write!( + file, + "{} \"{}\", {}, {}, {}, {}\n", + kind, + span.sail_file.to_string_lossy(), + span.line1, + span.char1, + span.line2, + span.char2, + ); + if res.is_err() { + return false; + } + }; + true +} + +#[no_mangle] +pub extern "C" fn sail_coverage_exit() -> c_int { + if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("sail_coverage") { + if !write_locations(&mut file, 'B', &BRANCHES) { + return 1 + } + if !write_locations(&mut file, 'F', &FUNCTIONS) { + return 1 + } + 0 + } else { + 1 + } +} + +#[no_mangle] +pub unsafe extern "C" fn sail_function_entry( + function_name: *const c_char, + sail_file: *const c_char, + l1: c_int, + c1: c_int, + l2: c_int, + c2: c_int, +) { + function_entry( + CStr::from_ptr(function_name), + Span { + sail_file: CStr::from_ptr(sail_file).into(), + line1: l1 as i32, + char1: c1 as i32, + line2: l2 as i32, + char2: c2 as i32, + }, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn sail_branch_taken( + branch_id: c_int, + sail_file: *const c_char, + l1: c_int, + c1: c_int, + l2: c_int, + c2: c_int, +) { + branch_taken( + branch_id as i32, + Span { + sail_file: CStr::from_ptr(sail_file).into(), + line1: l1 as i32, + char1: c1 as i32, + line2: l2 as i32, + char2: c2 as i32, + }, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn sail_branch_reached( + branch_id: c_int, + sail_file: *const c_char, + l1: c_int, + c1: c_int, + l2: c_int, + c2: c_int, +) { + branch_reached( + branch_id as i32, + Span { + sail_file: CStr::from_ptr(sail_file).into(), + line1: l1 as i32, + char1: c1 as i32, + line2: l2 as i32, + char2: c2 as i32, + }, + ) +} diff --git a/sailcov/Makefile b/sailcov/Makefile new file mode 100644 index 00000000..bc9e39d8 --- /dev/null +++ b/sailcov/Makefile @@ -0,0 +1,4 @@ + +sailcov: *.ml dune + dune build main.exe + cp -f _build/default/main.exe sailcov diff --git a/sailcov/dune b/sailcov/dune new file mode 100644 index 00000000..c69fec07 --- /dev/null +++ b/sailcov/dune @@ -0,0 +1,2 @@ +(executable + (name main)) diff --git a/sailcov/dune-project b/sailcov/dune-project new file mode 100644 index 00000000..4c6dd5ae --- /dev/null +++ b/sailcov/dune-project @@ -0,0 +1 @@ +(lang dune 2.4) diff --git a/sailcov/main.ml b/sailcov/main.ml new file mode 100644 index 00000000..84fbd595 --- /dev/null +++ b/sailcov/main.ml @@ -0,0 +1,190 @@ + +let opt_files = ref ([] : string list) + +let opt_taken = ref "sail_coverage" + +let opt_all = ref "all_branches" + +let options = + Arg.align [ + ( "-t", + Arg.String (fun str -> opt_taken := str), + "<file> coverage information for branches taken while executing the model (default: sail_coverage)"); + ( "-a", + Arg.String (fun str -> opt_all := str), + "<file> information about all possible branches (default: all_branches)") + ] + +type span = { + file : string; + l1 : int; + c1 : int; + l2 : int; + c2 : int; + } + +module Span = struct + type t = span + let compare s1 s2 = compare s1 s2 +end + +module SpanSet = Set.Make(Span) +module SpanMap = Map.Make(Span) + +let mk_span _ file l1 c1 l2 c2 = { file = Filename.basename file; l1 = l1; c1 = c1; l2 = l2; c2 = c2 } + +let read_coverage filename = + let spans = ref SpanSet.empty in + let chan = open_in filename in + try + let rec loop () = + let line = input_line chan in + spans := SpanSet.add (Scanf.sscanf line "%c %S, %d, %d, %d, %d" mk_span) !spans; + loop () + in + loop () + with End_of_file -> + close_in chan; + !spans + +(** We color the source either red (bad) or green (good) if it's + covered vs uncovered. If we have nested uncovered branches, they + will be increasingly bad, whereas nested covered branches will be + increasingly good. *) +type source_char = { + mutable badness : int; + mutable goodness : int; + char : char; + } + +let mark_bad_region source span = + source.(span.l1 - 1).(span.c1).badness <- source.(span.l1 - 1).(span.c1).badness + 1; + source.(span.l2 - 1).(span.c2 - 1).badness <- source.(span.l2 - 1).(span.c2 - 1).badness - 1 + +let mark_good_region source span = + source.(span.l1 - 1).(span.c1).goodness <- source.(span.l1 - 1).(span.c1).goodness + 1; + source.(span.l2 - 1).(span.c2 - 1).goodness <- source.(span.l2 - 1).(span.c2 - 1).goodness - 1 + +let process_line l = Array.init (String.length l) (fun n -> { badness = 0; goodness = 0; char = l.[n] }) + +let read_source filename = + let lines = ref [] in + let chan = open_in filename in + try + let rec loop () = + lines := process_line (input_line chan) :: !lines; + loop () + in + loop () + with End_of_file -> + close_in chan; + Array.of_list (List.rev !lines) + +let output_html_char chan c = + if c == ' ' then + output_string chan " " + else if c == '<' then + output_string chan "<" + else if c == '>' then + output_string chan ">" + else if c == '"' then + output_string chan """ + else + output_char chan c + +let main () = + let all = read_coverage !opt_all in + let taken = read_coverage !opt_taken in + List.iter (fun file -> + let all = SpanSet.filter (fun s -> s.file = Filename.basename file) all in + let taken = SpanSet.filter (fun s -> s.file = Filename.basename file) taken in + let not_taken = SpanSet.diff all taken in + + let source = read_source file in + SpanSet.iter (mark_good_region source) taken; + SpanSet.iter (mark_bad_region source) not_taken; + + let output_file = Filename.remove_extension (Filename.basename file) ^ ".html" in + let chan = open_out output_file in + + let current_goodness = ref 0 in + let current_badness = ref 0 in + + let good_color () = + let darken = 0xE0 - (!current_goodness * 0x20) in + Printf.sprintf "#%xFF%x" darken darken + in + let bad_color () = + let darken = 0xE0 - (!current_badness * 0x20) in + Printf.sprintf "#FF%x%x" darken darken + in + output_string chan "<!DOCTYPE html>\n"; + output_string chan "<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n"; + output_string chan (Printf.sprintf "<title>%s</title>" (Filename.remove_extension (Filename.basename file))); + output_string chan "</head>\n"; + output_string chan "<body>\n"; + output_string chan "<code style=\"display: block\">\n"; + + Array.iter (fun line -> + Array.iter (fun loc -> + if loc.goodness < 0 && loc.badness < 0 then ( + output_html_char chan loc.char; + current_goodness := !current_goodness + loc.goodness; + current_badness := !current_badness + loc.badness; + for _ = 1 to abs loc.goodness + abs loc.badness do + output_string chan (Printf.sprintf "</span>") + done + ) else if loc.goodness < 0 then ( + assert (loc.badness >= 0); + output_html_char chan loc.char; + current_goodness := !current_goodness + loc.goodness; + for _ = 1 to abs loc.goodness do + output_string chan (Printf.sprintf "</span>") + done + ) else if loc.badness < 0 then ( + assert (loc.goodness >= 0); + output_html_char chan loc.char; + current_badness := !current_badness + loc.badness; + for _ = 1 to abs loc.badness do + output_string chan (Printf.sprintf "</span>") + done + ) else if loc.badness > 0 then ( + assert (loc.goodness <= 0); + current_badness := !current_badness + loc.badness; + for _ = 1 to loc.badness do + output_string chan (Printf.sprintf "<span style=\"background-color: %s\">" (bad_color ())); + done; + output_html_char chan loc.char + ) else if loc.goodness > 0 then ( + assert (!current_badness == 0); + current_goodness := !current_goodness + loc.goodness; + for _ = 1 to loc.goodness do + output_string chan (Printf.sprintf "<span style=\"background-color: %s\">" (good_color ())); + done; + output_html_char chan loc.char + ) else ( + output_html_char chan loc.char + ); + assert (!current_goodness >= 0); + assert (!current_badness >= 0) + + ) line; + output_string chan "<br>\n" + ) source; + + output_string chan "</code>\n"; + output_string chan "</body>\n"; + output_string chan "</html>"; + + close_out chan; + Printf.printf "%s (%d/%d)\n" file (SpanSet.cardinal taken) (SpanSet.cardinal all) + ) !opt_files + +let usage_msg = "usage: sail-coverage-viz -c <file> -a <file> <.sail files>\n" + +let _ = + Arg.parse options + (fun s -> opt_files := !opt_files @ [s]) + usage_msg; + try main () with + | Sys_error msg -> prerr_endline msg; exit 1 diff --git a/src/ast_util.ml b/src/ast_util.ml index ac5da907..d0af5da3 100644 --- a/src/ast_util.ml +++ b/src/ast_util.ml @@ -89,7 +89,7 @@ let untyp_pat = function | P_aux (P_typ (typ, pat), _) -> pat, Some typ | pat -> pat, None -let mk_pexp pexp_aux = Pat_aux (pexp_aux, no_annot) +let mk_pexp ?loc:(l=Parse_ast.Unknown) pexp_aux = Pat_aux (pexp_aux, (l, ())) let mk_mpat mpat_aux = MP_aux (mpat_aux, no_annot) let mk_mpexp mpexp_aux = MPat_aux (mpexp_aux, no_annot) diff --git a/src/ast_util.mli b/src/ast_util.mli index 6be8ca34..1449a390 100644 --- a/src/ast_util.mli +++ b/src/ast_util.mli @@ -84,7 +84,7 @@ val mk_nexp : nexp_aux -> nexp val mk_exp : ?loc:l -> unit exp_aux -> unit exp val mk_pat : unit pat_aux -> unit pat val mk_mpat : unit mpat_aux -> unit mpat -val mk_pexp : unit pexp_aux -> unit pexp +val mk_pexp : ?loc:l -> unit pexp_aux -> unit pexp val mk_mpexp : unit mpexp_aux -> unit mpexp val mk_lexp : unit lexp_aux -> unit lexp val mk_lit : lit_aux -> lit diff --git a/src/jib/anf.ml b/src/jib/anf.ml index 7757cd9a..c81f729c 100644 --- a/src/jib/anf.ml +++ b/src/jib/anf.ml @@ -525,13 +525,13 @@ let rec apat_globals (AP_aux (aux, _, _)) = let rec anf (E_aux (e_aux, ((l, _) as exp_annot)) as exp) = let mk_aexp aexp = AE_aux (aexp, env_of_annot exp_annot, l) in - let to_aval (AE_aux (aexp_aux, env, l) as aexp) = - let mk_aexp aexp = AE_aux (aexp, env, l) in + let to_aval (AE_aux (aexp_aux, env, _) as aexp) = + let mk_aexp (AE_aux (_, _, l)) aexp = AE_aux (aexp, env, l) in match aexp_aux with | AE_val v -> (v, fun x -> x) | AE_short_circuit (_, _, _) -> let id = gensym () in - (AV_id (id, Local (Immutable, bool_typ)), fun x -> mk_aexp (AE_let (Immutable, id, bool_typ, aexp, x, typ_of exp))) + (AV_id (id, Local (Immutable, bool_typ)), fun x -> mk_aexp x (AE_let (Immutable, id, bool_typ, aexp, x, typ_of exp))) | AE_app (_, _, typ) | AE_let (_, _, _, _, _, typ) | AE_return (_, typ) @@ -544,10 +544,10 @@ let rec anf (E_aux (e_aux, ((l, _) as exp_annot)) as exp) = | AE_record_update (_, _, typ) | AE_block (_, _, typ) -> let id = gensym () in - (AV_id (id, Local (Immutable, typ)), fun x -> mk_aexp (AE_let (Immutable, id, typ, aexp, x, typ_of exp))) + (AV_id (id, Local (Immutable, typ)), fun x -> mk_aexp x (AE_let (Immutable, id, typ, aexp, x, typ_of exp))) | AE_assign _ | AE_for _ | AE_loop _ | AE_write_ref _ -> let id = gensym () in - (AV_id (id, Local (Immutable, unit_typ)), fun x -> mk_aexp (AE_let (Immutable, id, unit_typ, aexp, x, typ_of exp))) + (AV_id (id, Local (Immutable, unit_typ)), fun x -> mk_aexp x (AE_let (Immutable, id, unit_typ, aexp, x, typ_of exp))) in match e_aux with | E_lit lit -> mk_aexp (ae_lit lit (typ_of exp)) @@ -703,7 +703,8 @@ let rec anf (E_aux (e_aux, ((l, _) as exp_annot)) as exp) = | E_var (LEXP_aux (LEXP_id id, _), binding, body) | E_var (LEXP_aux (LEXP_cast (_, id), _), binding, body) - | E_let (LB_aux (LB_val (P_aux (P_id id, _), binding), _), body) -> + | E_let (LB_aux (LB_val (P_aux (P_id id, _), binding), _), body) + | E_let (LB_aux (LB_val (P_aux (P_typ (_, P_aux (P_id id, _)), _), binding), _), body) -> let env = env_of body in let lvar = Env.lookup_id id env in mk_aexp (AE_let (Mutable, id, lvar_typ lvar, anf binding, anf body, typ_of exp)) diff --git a/src/jib/c_backend.ml b/src/jib/c_backend.ml index 5947b500..fa4fa802 100644 --- a/src/jib/c_backend.ml +++ b/src/jib/c_backend.ml @@ -68,7 +68,8 @@ let opt_no_rts = ref false let opt_prefix = ref "z" let opt_extra_params = ref None let opt_extra_arguments = ref None - +let opt_branch_coverage = ref false + let extra_params () = match !opt_extra_params with | Some str -> str ^ ", " @@ -164,7 +165,7 @@ let literal_to_fragment (L_aux (l_aux, _) as lit) = | L_false -> Some (V_lit (VL_bool false, CT_bool)) | _ -> None -module C_config : Config = struct +module C_config(Opts : sig val branch_coverage : bool end) : Config = struct (** Convert a sail type into a C-type. This function can be quite slow, because it uses ctx.local_env and SMT to analyse the Sail @@ -557,107 +558,13 @@ module C_config : Config = struct analyze_functions ctx analyze_primop (c_literals ctx aexp) - let unroll_loops () = None + let unroll_loops = None let specialize_calls = false let ignore_64 = false let struct_value = false let use_real = false - let branch_coverage = false -end - -(* When compiling to a C library, we want to do things slightly - differently. First, to ensure that functions have a predictable - type and calling convention, we don't use the SMT solver to - optimize types at all. Second we don't apply the analyse primitives - step in optimize_anf for similar reasons. *) -module Clib_config : Config = struct - let rec convert_typ ctx typ = - let Typ_aux (typ_aux, l) as typ = Env.expand_synonyms ctx.tc_env typ in - match typ_aux with - | Typ_id id when string_of_id id = "bit" -> CT_bit - | Typ_id id when string_of_id id = "bool" -> CT_bool - | Typ_id id when string_of_id id = "int" -> CT_lint - | Typ_id id when string_of_id id = "nat" -> CT_lint - | Typ_id id when string_of_id id = "unit" -> CT_unit - | Typ_id id when string_of_id id = "string" -> CT_string - | Typ_id id when string_of_id id = "real" -> CT_real - - | Typ_app (id, _) when string_of_id id = "atom_bool" -> CT_bool - | Typ_app (id, _) when - string_of_id id = "range" || string_of_id id = "atom" || string_of_id id = "implicit" || string_of_id id = "itself" -> - begin match destruct_range Env.empty typ with - | None -> assert false (* Checked if range type in guard *) - | Some (kids, constr, n, m) -> - let ctx = { ctx with local_env = add_existential Parse_ast.Unknown (List.map (mk_kopt K_int) kids) constr ctx.local_env }in - match nexp_simp n, nexp_simp m with - | Nexp_aux (Nexp_constant n, _), Nexp_aux (Nexp_constant m, _) - when Big_int.less_equal (min_int 64) n && Big_int.less_equal m (max_int 64) -> - CT_fint 64 - | _, _ -> - CT_lint - end - - | Typ_app (id, [A_aux (A_typ typ, _)]) when string_of_id id = "list" -> - CT_list (convert_typ ctx typ) - - | Typ_app (id, [A_aux (A_nexp n, _); - A_aux (A_order ord, _)]) - when string_of_id id = "bitvector" -> - let direction = match ord with Ord_aux (Ord_dec, _) -> true | Ord_aux (Ord_inc, _) -> false | _ -> assert false in - begin match nexp_simp n with - | Nexp_aux (Nexp_constant n, _) when Big_int.less_equal n (Big_int.of_int 64) -> CT_fbits (Big_int.to_int n, direction) - | _ -> CT_lbits direction - end - - | Typ_app (id, [A_aux (A_nexp n, _); - A_aux (A_order ord, _); - A_aux (A_typ typ, _)]) - when string_of_id id = "vector" -> - let direction = match ord with Ord_aux (Ord_dec, _) -> true | Ord_aux (Ord_inc, _) -> false | _ -> assert false in - CT_vector (direction, convert_typ ctx typ) - - | Typ_app (id, [A_aux (A_typ typ, _)]) when string_of_id id = "register" -> - CT_ref (convert_typ ctx typ) - - | Typ_id id | Typ_app (id, _) when Bindings.mem id ctx.records -> CT_struct (id, Bindings.find id ctx.records |> UBindings.bindings) - | Typ_id id | Typ_app (id, _) when Bindings.mem id ctx.variants -> CT_variant (id, Bindings.find id ctx.variants |> UBindings.bindings) - | Typ_id id when Bindings.mem id ctx.enums -> CT_enum (id, Bindings.find id ctx.enums |> IdSet.elements) - - | Typ_tup typs -> CT_tup (List.map (convert_typ ctx) typs) - - | Typ_exist _ -> - begin match destruct_exist (Env.expand_synonyms ctx.local_env typ) with - | Some (kids, nc, typ) -> - let env = add_existential l kids nc ctx.local_env in - convert_typ { ctx with local_env = env } typ - | None -> raise (Reporting.err_unreachable l __POS__ "Existential cannot be destructured!") - end - - | Typ_var kid -> CT_poly - - | _ -> c_error ~loc:l ("No C type for type " ^ string_of_typ typ) - - let c_literals ctx = - let rec c_literal env l = function - | AV_lit (lit, typ) as v when is_stack_ctyp (convert_typ { ctx with local_env = env } typ) -> - begin - match literal_to_fragment lit with - | Some cval -> AV_cval (cval, typ) - | None -> v - end - | AV_tuple avals -> AV_tuple (List.map (c_literal env l) avals) - | v -> v - in - map_aval c_literal - - let optimize_anf ctx aexp = c_literals ctx aexp - - let unroll_loops () = None - let specialize_calls = false - let ignore_64 = false - let struct_value = false - let use_real = false - let branch_coverage = false + let branch_coverage = Opts.branch_coverage + let track_throw = true end (** Functions that have heap-allocated return types are implemented by @@ -2320,7 +2227,7 @@ let rec get_recursive_functions (Defs defs) = | [] -> IdSet.empty let jib_of_ast env ast = - let module Jibc = Make(C_config) in + let module Jibc = Make(C_config(struct let branch_coverage = !opt_branch_coverage end)) in let ctx = initial_ctx (add_special_functions env) in Jibc.compile_ast ctx ast @@ -2341,6 +2248,7 @@ let compile_ast env output_chan c_includes ast = @ (if !opt_no_rts then [] else [ string "#include \"rts.h\""; string "#include \"elf.h\"" ]) + @ (if !opt_branch_coverage then [string "#include \"sail_coverage.h\""] else []) @ (List.map (fun h -> string (Printf.sprintf "#include \"%s\"" h)) c_includes)) in @@ -2405,22 +2313,32 @@ let compile_ast env output_chan c_includes ast = @ [ "}" ] )) in - let model_default_main = separate hardline (List.map string - [ "int model_main(int argc, char *argv[])"; - "{"; - " model_init();"; - " if (process_arguments(argc, argv)) exit(EXIT_FAILURE);"; - Printf.sprintf " %s(UNIT);" (sgen_function_id (mk_id "main")); - " model_fini();"; - " return EXIT_SUCCESS;"; - "}" ] ) + let model_default_main = + ([ "int model_main(int argc, char *argv[])"; + "{"; + " model_init();"; + " if (process_arguments(argc, argv)) exit(EXIT_FAILURE);"; + Printf.sprintf " %s(UNIT);" (sgen_function_id (mk_id "main")); + " model_fini();" ] + @ (if !opt_branch_coverage then + [ " if (sail_coverage_exit() != 0) {"; + " fprintf(stderr, \"Could not write coverage information\\n\");"; + " exit(EXIT_FAILURE);"; + " }" ] + else + [] + ) + @ [ " return EXIT_SUCCESS;"; + "}" ]) + |> List.map string + |> separate hardline in let model_main = separate hardline (if (!opt_no_main) then [] else List.map string - [ "int main(int argc, char *argv[])"; - "{"; - " return model_main(argc, argv);"; - "}" ] ) + [ "int main(int argc, char *argv[])"; + "{"; + " return model_main(argc, argv);"; + "}" ] ) in let hlhl = hardline ^^ hardline in @@ -2438,11 +2356,6 @@ let compile_ast env output_chan c_includes ast = | Type_error (_, l, err) -> c_error ~loc:l ("Unexpected type error when compiling to C:\n" ^ Type_error.string_of_type_error err) -let jib_of_ast_clib env ast = - let module Jibc = Make(Clib_config) in - let ctx = initial_ctx (add_special_functions env) in - Jibc.compile_ast ctx ast - let compile_ast_clib env ast codegen = let cdefs, ctx = jib_of_ast env ast in let cdefs', _ = Jib_optimize.remove_tuples cdefs ctx in diff --git a/src/jib/c_backend.mli b/src/jib/c_backend.mli index 2334cc0e..b10e53d3 100644 --- a/src/jib/c_backend.mli +++ b/src/jib/c_backend.mli @@ -88,6 +88,8 @@ val opt_prefix : string ref val opt_extra_params : string option ref val opt_extra_arguments : string option ref +val opt_branch_coverage : bool ref + (** Optimization flags *) val optimize_primops : bool ref @@ -100,5 +102,4 @@ val optimize_fixed_bits : bool ref val jib_of_ast : Env.t -> tannot Ast.defs -> cdef list * Jib_compile.ctx val compile_ast : Env.t -> out_channel -> string list -> tannot Ast.defs -> unit -val jib_of_ast_clib : Env.t -> tannot Ast.defs -> cdef list * Jib_compile.ctx val compile_ast_clib : Env.t -> tannot Ast.defs -> (Jib_compile.ctx -> cdef list -> unit) -> unit diff --git a/src/jib/c_codegen.ml b/src/jib/c_codegen.ml index 05abfe70..d5015318 100644 --- a/src/jib/c_codegen.ml +++ b/src/jib/c_codegen.ml @@ -213,20 +213,20 @@ let options_from_json json cdefs = in opts -module type Config = sig +module type Options = sig val opts : codegen_options end -module Make(C: Config) = struct +module Make(O: Options) = struct let mangle str = let mangled = Util.zencode_string str in - match StringMap.find_opt mangled C.opts.exports_mangled with + match StringMap.find_opt mangled O.opts.exports_mangled with | Some export -> export | None -> mangled let sgen_id id = - match Bindings.find_opt id C.opts.exports with + match Bindings.find_opt id O.opts.exports with | Some export -> export | None -> mangle (string_of_id id) @@ -651,7 +651,7 @@ let rec codegen_instr fid ctx (I_aux (instr, (_, l))) = (sgen_function_uid f, false) in let sail_state_arg = - if is_extern && StringSet.mem fname C.opts.state_primops then + if is_extern && StringSet.mem fname O.opts.state_primops then "sail_state *state, " else "" @@ -1476,7 +1476,7 @@ let codegen_state_struct ctx cdefs = ^^ string " bool have_exception;" ^^ hardline ^^ string " sail_string *throw_location;" ^^ hardline )) - ^^ concat_map (fun str -> string (" " ^ str) ^^ hardline) C.opts.extra_state + ^^ concat_map (fun str -> string (" " ^ str) ^^ hardline) O.opts.extra_state ^^ string "};" let is_cdef_startup = function @@ -1570,7 +1570,7 @@ let codegen ctx output_name cdefs = let header = stdlib_headers ^^ hardline ^^ sail_headers ^^ hardline - ^^ concat_map add_extra_header C.opts.extra_headers ^^ hardline + ^^ concat_map add_extra_header O.opts.extra_headers ^^ hardline ^^ string "struct sail_state;" ^^ hardline ^^ string "typedef struct sail_state sail_state;" ^^ twice hardline diff --git a/src/jib/jib_compile.ml b/src/jib/jib_compile.ml index 11bfa2fc..84705ae2 100644 --- a/src/jib/jib_compile.ml +++ b/src/jib/jib_compile.ml @@ -58,7 +58,6 @@ open Value2 open Anf let opt_memo_cache = ref false -let opt_track_throw = ref true let optimize_aarch64_fast_struct = ref false @@ -174,12 +173,13 @@ let initial_ctx env = module type Config = sig val convert_typ : ctx -> typ -> ctyp val optimize_anf : ctx -> typ aexp -> typ aexp - val unroll_loops : unit -> int option + val unroll_loops : int option val specialize_calls : bool val ignore_64 : bool val struct_value : bool val use_real : bool val branch_coverage : bool + val track_throw : bool end module Make(C: Config) = struct @@ -201,12 +201,10 @@ let coverage_branch_count = ref 0 let coverage_loc_args l = match Reporting.simp_loc l with - | None -> - Reporting.simple_warn "Branch found with no location info when inserting coverage instrumentation"; - None + | None -> None | Some (p1, p2) -> Some (Printf.sprintf "\"%s\", %d, %d, %d, %d" - p1.pos_fname p1.pos_lnum (p1.pos_cnum - p1.pos_bol) p2.pos_lnum (p2.pos_cnum - p2.pos_bol)) + (String.escaped p1.pos_fname) p1.pos_lnum (p1.pos_cnum - p1.pos_bol) p2.pos_lnum (p2.pos_cnum - p2.pos_bol)) let coverage_branch_reached l = let branch_id = !coverage_branch_count in @@ -214,9 +212,7 @@ let coverage_branch_reached l = branch_id, if C.branch_coverage then begin match coverage_loc_args l with - | None -> - Reporting.simple_warn "Branch found with no location info when inserting coverage instrumentation"; - [] + | None -> [] | Some args -> [iraw (Printf.sprintf "sail_branch_reached(%d, %s);" branch_id args)] end @@ -234,8 +230,20 @@ let coverage_branch_taken branch_id (AE_aux (_, _, l)) = else match coverage_loc_args l with | None -> [] - | Some args -> [iraw (Printf.sprintf "sail_branch_taken(%d, %s);" branch_id args)] - + | Some args -> + print_endline ("B " ^ args); + [iraw (Printf.sprintf "sail_branch_taken(%d, %s);" branch_id args)] + +let coverage_function_entry id l = + if not C.branch_coverage then + [] + else + match coverage_loc_args l with + | None -> [] + | Some args -> + print_endline ("F " ^ args); + [iraw (Printf.sprintf "sail_function_entry(\"%s\", %s);" (string_of_id id) args)] + let rec compile_aval l ctx = function | AV_cval (cval, typ) -> let ctyp = cval_ctyp cval in @@ -668,6 +676,9 @@ let rec compile_aexp ctx (AE_aux (aexp_aux, env, l)) = | AE_case (aval, cases, typ) -> let ctyp = ctyp_of_typ ctx typ in let aval_setup, cval, aval_cleanup = compile_aval l ctx aval in + (* Get the number of cases, because we don't want to check branch + coverage for matches with only a single case. *) + let num_cases = List.length cases in let branch_id, on_reached = coverage_branch_reached l in let case_return_id = ngensym () in let finish_match_label = label "finish_match_" in @@ -689,7 +700,7 @@ let rec compile_aexp ctx (AE_aux (aexp_aux, env, l)) = @ [iif l (V_call (Bnot, [V_id (gs, CT_bool)])) (destructure_cleanup @ [igoto case_label]) [] CT_unit] else []) @ body_setup - @ coverage_branch_taken branch_id body + @ (if num_cases > 1 then coverage_branch_taken branch_id body else []) @ [body_call (CL_id (case_return_id, ctyp))] @ body_cleanup @ destructure_cleanup @ [igoto finish_match_label] in @@ -698,7 +709,9 @@ let rec compile_aexp ctx (AE_aux (aexp_aux, env, l)) = else [iblock case_instrs; ilabel case_label] in - aval_setup @ on_reached @ [idecl ctyp case_return_id] + aval_setup + @ (if num_cases > 1 then on_reached else []) + @ [idecl ctyp case_return_id] @ List.concat (List.map compile_case cases) @ [imatch_failure ()] @ [ilabel finish_match_label], @@ -997,7 +1010,7 @@ let rec compile_aexp ctx (AE_aux (aexp_aux, env, l)) = (* We can either generate an actual loop body for C, or unroll the body for SMT *) let actual = loop_body [ilabel loop_start_label] (fun () -> [igoto loop_start_label]) in let rec unroll max n = loop_body [] (fun () -> if n < max then unroll max (n + 1) else [imatch_failure ()]) in - let body = match C.unroll_loops () with Some times -> unroll times 0 | None -> actual in + let body = match C.unroll_loops with Some times -> unroll times 0 | None -> actual in variable_init from_gs from_setup from_call from_cleanup @ variable_init to_gs to_setup to_call to_cleanup @@ -1106,7 +1119,7 @@ let fix_exception_block ?return:(return=None) ctx instrs = before @ [icopy l (CL_id (current_exception, cval_ctyp cval)) cval; icopy l (CL_id (have_exception, CT_bool)) (V_lit (VL_bool true, CT_bool))] - @ (if !opt_track_throw then + @ (if C.track_throw then let loc_string = Reporting.short_loc_to_string l in [icopy l (CL_id (throw_location, CT_string)) (V_lit (VL_string loc_string, CT_string))] else []) @@ -1307,6 +1320,7 @@ let compile_funcl ctx id pat guard exp = let instrs = arg_setup @ destructure @ guard_instrs @ setup @ [call (CL_id (return, ret_ctyp))] @ cleanup @ destructure_cleanup @ arg_cleanup in let instrs = fix_early_return (CL_id (return, ret_ctyp)) instrs in let instrs = fix_exception ~return:(Some ret_ctyp) ctx instrs in + let instrs = coverage_function_entry id (exp_loc exp) @ instrs in [CDEF_fundef (id, None, List.map fst compiled_args, instrs)], orig_ctx diff --git a/src/jib/jib_compile.mli b/src/jib/jib_compile.mli index 98ab44de..47ca6962 100644 --- a/src/jib/jib_compile.mli +++ b/src/jib/jib_compile.mli @@ -61,11 +61,6 @@ open Type_check ARM v8.5 spec. It is unsound in general. *) val optimize_aarch64_fast_struct : bool ref -(** If true (default) track the location of the last exception thrown, - useful for debugging C but we want to turn it off for SMT generation - where we can't use strings *) -val opt_track_throw : bool ref - (** (WIP) [opt_memo_cache] will store the compiled function definitions in file _sbuild/ccacheDIGEST where DIGEST is the md5sum of the original function to be compiled. Enabled using the -memo @@ -105,7 +100,7 @@ module type Config = sig val optimize_anf : ctx -> typ aexp -> typ aexp (** Unroll all for loops a bounded number of times. Used for SMT generation. *) - val unroll_loops : unit -> int option + val unroll_loops : int option (** If false, function arguments must match the function type exactly. If true, they can be more specific. *) val specialize_calls : bool @@ -119,6 +114,10 @@ module type Config = sig val use_real : bool (** Insert branch coverage operations *) val branch_coverage : bool + (** If true track the location of the last exception thrown, useful + for debugging C but we want to turn it off for SMT generation + where we can't use strings *) + val track_throw : bool end module Make(C: Config) : sig diff --git a/src/jib/jib_smt.ml b/src/jib/jib_smt.ml index 734f3fe4..a299d7b9 100644 --- a/src/jib/jib_smt.ml +++ b/src/jib/jib_smt.ml @@ -1397,7 +1397,7 @@ let rec generate_reg_decs ctx inits = function let max_int n = Big_int.pred (Big_int.pow_int_positive 2 (n - 1)) let min_int n = Big_int.negate (Big_int.pow_int_positive 2 (n - 1)) -module SMT_config : Jib_compile.Config = struct +module SMT_config(Opts : sig val unroll_limit : int end) : Jib_compile.Config = struct open Jib_compile (** Convert a sail type into a C-type. This function can be quite @@ -1560,10 +1560,11 @@ let unroll_static_foreach ctx = function let specialize_calls = true let ignore_64 = true - let unroll_loops () = Some !opt_unroll_limit + let unroll_loops = Some Opts.unroll_limit let struct_value = true let use_real = true let branch_coverage = false + let track_throw = false end @@ -2328,7 +2329,7 @@ let rec build_register_map rmap = function let compile env ast = let cdefs, jib_ctx = - let module Jibc = Jib_compile.Make(SMT_config) in + let module Jibc = Jib_compile.Make(SMT_config(struct let unroll_limit = !opt_unroll_limit end)) in let ctx = Jib_compile.(initial_ctx (add_special_functions env)) in let t = Profile.start () in let cdefs, ctx = Jibc.compile_ast ctx ast in diff --git a/src/lexer.mll b/src/lexer.mll index 8000879e..40f4b470 100644 --- a/src/lexer.mll +++ b/src/lexer.mll @@ -288,8 +288,10 @@ rule token = parse | "-" digit+ as i { (Num(Big_int.of_string i)) } | "0b" (binarydigit+ as i) { (Bin(Util.string_of_list "" (fun s -> s) (Util.split_on_char '_' i))) } | "0x" (hexdigit+ as i) { (Hex(Util.string_of_list "" (fun s -> s) (Util.split_on_char '_' i))) } - | '"' { (String( - string (Lexing.lexeme_start_p lexbuf) (Buffer.create 10) lexbuf)) } + | '"' { let startpos = Lexing.lexeme_start_p lexbuf in + let contents = string startpos (Buffer.create 10) lexbuf in + lexbuf.lex_start_p <- startpos; + String(contents) } | eof { Eof } | _ as c { raise (LexError( Printf.sprintf "Unexpected character: %c" c, @@ -331,10 +333,6 @@ and string pos b = parse | '\\' '\n' ws { Lexing.new_line lexbuf; string pos b lexbuf } | '\\' { assert false (*raise (Reporting.Fatal_error (Reporting.Err_syntax (pos, "illegal backslash escape in string"*) } - | '"' { let s = unescaped(Buffer.contents b) in - (*try Ulib.UTF8.validate s; s - with Ulib.UTF8.Malformed_code -> - raise (Reporting.Fatal_error (Reporting.Err_syntax (pos, - "String literal is not valid utf8"))) *) s } + | '"' { unescaped (Buffer.contents b) } | eof { assert false (*raise (Reporting.Fatal_error (Reporting.Err_syntax (pos, "String literal not terminated")))*) } diff --git a/src/rewrites.ml b/src/rewrites.ml index 89f67d87..48ea78ae 100644 --- a/src/rewrites.ml +++ b/src/rewrites.ml @@ -489,6 +489,13 @@ let remove_vector_concat_pat pat = let decls = List.fold_left (fun f g x -> f (g x)) (fun b -> b) decls in + (* Finally we patch up the top location for the expressions wrapped + by decls, otherwise this can cause the coverage instrumentation + to get super confused by the generated locations *) + let decls (E_aux (_, (l, _)) as exp) = + let E_aux (aux, (_, annot)) = decls exp in + E_aux (aux, (gen_loc l, annot)) in + (* at this point shouldn't have P_as patterns in P_vector_concat patterns any more, all P_as and P_id vectors should have their declarations in decls. Now flatten all vector_concat patterns *) @@ -691,7 +698,7 @@ let rec subsumes_pat (P_aux (p1,annot1) as pat1) (P_aux (p2,annot2) as pat2) = if Env.lookup_id id1 (env_of_annot annot1) = Unbound then Some [] else None | P_var (pat1,_), P_var (pat2,_) -> subsumes_pat pat1 pat2 | P_wild, _ -> Some [] - | P_app (Id_aux (id1,l1),args1), P_app (Id_aux (id2,_),args2) -> + | P_app (Id_aux (id1,_),args1), P_app (Id_aux (id2,_),args2) -> if id1 = id2 then subsumes_list subsumes_pat args1 args2 else None | P_vector pats1, P_vector pats2 | P_vector_concat pats1, P_vector_concat pats2 @@ -1374,9 +1381,6 @@ let updates_vars exp = lit vectors in patterns or expressions *) let rewrite_exp_lift_assign_intro rewriters ((E_aux (exp,((l,_) as annot))) as full_exp) = - let rewrap e = E_aux (e,annot) in - let rewrap_effects e eff = - E_aux (e, (l,Some (env_of_annot annot, typ_of_annot annot, eff))) in let rewrite_rec = rewriters.rewrite_exp rewriters in let rewrite_base = rewrite_exp rewriters in match exp with @@ -2562,7 +2566,7 @@ let fold_typed_guards env guards = | g :: gs -> List.fold_left (fun g g' -> annot_exp (E_app (mk_id "and_bool", [g; g'])) Parse_ast.Unknown env bool_typ) g gs -let rewrite_pexp_with_guards rewrite_pat (Pat_aux (pexp_aux, (annot: tannot annot)) as pexp) = +let rewrite_pexp_with_guards rewrite_pat (Pat_aux (pexp_aux, (l, _)) as pexp) = let guards = ref [] in match pexp_aux with @@ -2572,13 +2576,13 @@ let rewrite_pexp_with_guards rewrite_pat (Pat_aux (pexp_aux, (annot: tannot anno match !guards with | [] -> pexp | gs -> - let unchecked_pexp = mk_pexp (Pat_when (strip_pat pat, List.map strip_exp gs |> fold_guards, strip_exp exp)) in + let unchecked_pexp = mk_pexp ~loc:l (Pat_when (strip_pat pat, List.map strip_exp gs |> fold_guards, strip_exp exp)) in check_case (env_of_pat pat) (typ_of_pat pat) unchecked_pexp (typ_of exp) end | Pat_when (pat, guard, exp) -> begin let pat = fold_pat { id_pat_alg with p_aux = rewrite_pat guards } pat in - let unchecked_pexp = mk_pexp (Pat_when (strip_pat pat, List.map strip_exp !guards |> fold_guards, strip_exp exp)) in + let unchecked_pexp = mk_pexp ~loc:l (Pat_when (strip_pat pat, List.map strip_exp !guards |> fold_guards, strip_exp exp)) in check_case (env_of_pat pat) (typ_of_pat pat) unchecked_pexp (typ_of exp) end diff --git a/src/sail.ml b/src/sail.ml index 9dbd1f6c..772d8564 100644 --- a/src/sail.ml +++ b/src/sail.ml @@ -173,10 +173,10 @@ let options = Arg.align ([ set_target "ir", " print intermediate representation"); ( "-smt", - Arg.Tuple [set_target "smt"; Arg.Clear Jib_compile.opt_track_throw], + Arg.Tuple [set_target "smt"], " print SMT translated version of input"); ( "-smt_auto", - Arg.Tuple [set_target "smt"; Arg.Clear Jib_compile.opt_track_throw; Arg.Set Jib_smt.opt_auto], + Arg.Tuple [set_target "smt"; Arg.Set Jib_smt.opt_auto], " generate SMT and automatically call the solver (implies -smt)"); ( "-smt_ignore_overflow", Arg.Set Jib_smt.opt_ignore_overflow, @@ -201,7 +201,7 @@ let options = Arg.align ([ " output a C translated version of the input"); ( "-c2", Arg.Tuple [set_target "c2"; Arg.Set Initial_check.opt_undefined_gen], - " output a C translated version of the input"); + " output a C translated version of the input (experimental code-generation)"); ( "-c_include", Arg.String (fun i -> opt_includes_c := i::!opt_includes_c), "<filename> provide additional include for C output"); @@ -232,6 +232,9 @@ let options = Arg.align ([ ( "-c_fold_unit", Arg.String (fun str -> Constant_fold.opt_fold_to_unit := Util.split_on_char ',' str), " remove comma separated list of functions from C output, replacing them with unit"); + ( "-c_coverage", + Arg.Set C_backend.opt_branch_coverage, + " instrument C code to track branch coverage"); ( "-elf", Arg.String (fun elf -> opt_process_elf := Some elf), " process an ELF file so that it can be executed by compiled C code"); diff --git a/src/type_check.ml b/src/type_check.ml index 82bc92d8..73cb8a57 100644 --- a/src/type_check.ml +++ b/src/type_check.ml @@ -2994,7 +2994,7 @@ let rec check_exp env (E_aux (exp_aux, (l, ())) as exp : unit exp) (Typ_aux (typ let checked_exp = crule check_exp env exp exc_typ in annot_exp_effect (E_throw checked_exp) typ (mk_effect [BE_escape]) | E_var (lexp, bind, exp), _ -> - let lexp, bind, env = match bind_assignment env lexp bind with + let lexp, bind, env = match bind_assignment l env lexp bind with | E_aux (E_assign (lexp, bind), _), env -> lexp, bind, env | _, _ -> assert false in @@ -3059,8 +3059,8 @@ and check_block l env exps ret_typ = match Nl_flow.analyze exps with | [] -> (match ret_typ with Some typ -> typ_equality l env typ unit_typ; [] | None -> []) | [exp] -> [final env exp] - | (E_aux (E_assign (lexp, bind), _) :: exps) -> - let texp, env = bind_assignment env lexp bind in + | (E_aux (E_assign (lexp, bind), (assign_l, _)) :: exps) -> + let texp, env = bind_assignment assign_l env lexp bind in texp :: check_block l env exps ret_typ | ((E_aux (E_assert (constr_exp, msg), _) as exp) :: exps) -> let msg = assert_msg msg in @@ -3548,9 +3548,9 @@ and bind_typ_pat_arg env (TP_aux (typ_pat_aux, l) as typ_pat) (A_aux (typ_arg_au | _, A_order _ -> typ_error env l "Cannot bind type pattern against order" | _, _ -> typ_error env l ("Couldn't bind type argument " ^ string_of_typ_arg typ_arg ^ " with " ^ string_of_typ_pat typ_pat) -and bind_assignment env (LEXP_aux (lexp_aux, _) as lexp) (E_aux (_, (l, ())) as exp) = - let annot_assign lexp exp = E_aux (E_assign (lexp, exp), (l, mk_tannot env (mk_typ (Typ_id (mk_id "unit"))) no_effect)) in - let annot_lexp_effect lexp typ eff = LEXP_aux (lexp, (l, mk_tannot env typ eff)) in +and bind_assignment assign_l env (LEXP_aux (lexp_aux, (lexp_l, ())) as lexp) (E_aux (_, (exp_l, ())) as exp) = + let annot_assign lexp exp = E_aux (E_assign (lexp, exp), (assign_l, mk_tannot env (mk_typ (Typ_id (mk_id "unit"))) no_effect)) in + let annot_lexp_effect lexp typ eff = LEXP_aux (lexp, (lexp_l, mk_tannot env typ eff)) in let annot_lexp lexp typ = annot_lexp_effect lexp typ no_effect in let has_typ v env = match Env.lookup_id v env with @@ -3559,7 +3559,7 @@ and bind_assignment env (LEXP_aux (lexp_aux, _) as lexp) (E_aux (_, (l, ())) as in match lexp_aux with | LEXP_memory (f, xs) -> - check_exp env (E_aux (E_app (f, xs @ [exp]), (l, ()))) unit_typ, env + check_exp env (E_aux (E_app (f, xs @ [exp]), (lexp_l, ()))) unit_typ, env | LEXP_cast (typ_annot, v) -> let checked_exp = crule check_exp env exp typ_annot in let tlexp, env' = bind_lexp env lexp (typ_of checked_exp) in @@ -3817,7 +3817,7 @@ and infer_exp env (E_aux (exp_aux, (l, ())) as exp) = let inferred_exps = List.map (irule infer_exp env) exps in annot_exp (E_tuple inferred_exps) (mk_typ (Typ_tup (List.map typ_of inferred_exps))) | E_assign (lexp, bind) -> - fst (bind_assignment env lexp bind) + fst (bind_assignment l env lexp bind) | E_record_update (exp, fexps) -> let inferred_exp = irule infer_exp env exp in let typ = typ_of inferred_exp in |
