diff options
Diffstat (limited to 'src/c_backend.ml')
| -rw-r--r-- | src/c_backend.ml | 229 |
1 files changed, 201 insertions, 28 deletions
diff --git a/src/c_backend.ml b/src/c_backend.ml index ad9dc2f3..d39680b9 100644 --- a/src/c_backend.ml +++ b/src/c_backend.ml @@ -55,6 +55,7 @@ open PPrint module Big_int = Nat_big_num let c_verbosity = ref 1 +let opt_ddump_flow_graphs = ref false let c_debug str = if !c_verbosity > 0 then prerr_endline str else () @@ -808,7 +809,7 @@ let analyze_primop ctx id args typ = (** We now define a low-level AST that is only slightly abstracted away from C. To be succint in comments we usually refer to this as - LLcode rather than low-level AST repeatedly. + Sail IR or IR rather than low-level AST repeatedly. The general idea is ANF expressions are converted into lists of instructions (type instr) where allocations and deallocations are @@ -903,7 +904,7 @@ let cdef_ctyps = function | CDEF_fundef (_, _, _, instrs) -> List.concat (List.map instr_ctyps instrs) | CDEF_type tdef -> ctype_def_ctyps tdef -(* For debugging we define a pretty printer for LLcode instructions *) +(* For debugging we define a pretty printer for Sail IR instructions *) let pp_ctyp ctyp = string (string_of_ctyp ctyp |> Util.yellow |> Util.clear) @@ -921,15 +922,18 @@ let rec pp_clexp = function | CL_addr clexp -> string "*" ^^ pp_clexp clexp | CL_raw str -> string str -let rec pp_instr = function +let rec pp_instr ?short:(short=false) = function | I_decl (ctyp, id) -> parens (pp_ctyp ctyp) ^^ space ^^ pp_id id | I_if (cval, then_instrs, else_instrs, ctyp) -> - let pp_if_block instrs = surround 2 0 lbrace (separate_map hardline pp_instr instrs) rbrace in + let pp_if_block instrs = lbrace ^^ separate_map semi pp_instr instrs ^^ rbrace in parens (pp_ctyp ctyp) ^^ space ^^ pp_keyword "IF" ^^ pp_cval cval - ^^ pp_keyword "THEN" ^^ pp_if_block then_instrs - ^^ pp_keyword "ELSE" ^^ pp_if_block else_instrs + ^^ if short then + empty + else + pp_keyword "THEN" ^^ pp_if_block then_instrs + ^^ pp_keyword "ELSE" ^^ pp_if_block else_instrs | I_block instrs -> surround 2 0 lbrace (separate_map hardline pp_instr instrs) rbrace | I_try_block instrs -> @@ -1375,7 +1379,7 @@ let rec pat_ids (P_aux (p_aux, (l, _)) as pat) = | P_var (pat, _) -> pat_ids pat | _ -> c_error ~loc:l ("Cannot compile pattern " ^ string_of_pat pat ^ " to C") -(** Compile a sail type definition into a LLcode one. Most of the +(** Compile a sail type definition into a IR one. Most of the actual work of translating the typedefs into C is done by the code generator, as it's easy to keep track of structs, tuples and unions in their sail form at this level, and leave the fiddly details of @@ -1435,15 +1439,15 @@ let generate_cleanup instrs = (** Functions that have heap-allocated return types are implemented by passing a pointer a location where the return value should be - stored. The ANF -> LLcode pass for expressions simply outputs an + stored. The ANF -> Sail IR pass for expressions simply outputs an I_return instruction for any return value, so this function walks - over the LLcode ast for expressions and modifies the return - statements into code that sets that pointer, as well as adds extra - control flow to cleanup heap-allocated variables correctly when a - function terminates early. See the generate_cleanup function for - how this is done. FIXME: could be some memory leaks introduced - here, do we do the right thing with generate_cleanup and multiple - returns in the same block? *) + over the IR ast for expressions and modifies the return statements + into code that sets that pointer, as well as adds extra control + flow to cleanup heap-allocated variables correctly when a function + terminates early. See the generate_cleanup function for how this is + done. FIXME: could be some memory leaks introduced here, do we do + the right thing with generate_cleanup and multiple returns in the + same block? *) let fix_early_return ret ctx instrs = let end_function_label = label "end_function_" in let is_return_recur = function @@ -1487,25 +1491,25 @@ let fix_exception_block ctx instrs = | I_throw _ | I_if _ | I_block _ | I_funcall _ -> true | _ -> false in - let rec rewrite_exception pre_cleanup instrs = + let rec rewrite_exception historic instrs = match instr_split_at is_exception_stop instrs with | instrs, [] -> instrs | before, I_block instrs :: after -> before - @ [I_block (rewrite_exception (pre_cleanup @ generate_cleanup before) instrs)] - @ rewrite_exception (pre_cleanup @ generate_cleanup before) after + @ [I_block (rewrite_exception (generate_cleanup (historic @ before)) instrs)] + @ rewrite_exception (historic @ before) after | before, I_if (cval, then_instrs, else_instrs, ctyp) :: after -> - let cleanup = pre_cleanup @ generate_cleanup before in + let historic = historic @ before in before - @ [I_if (cval, rewrite_exception cleanup then_instrs, rewrite_exception cleanup else_instrs, ctyp)] - @ rewrite_exception (pre_cleanup @ generate_cleanup before) after + @ [I_if (cval, rewrite_exception historic then_instrs, rewrite_exception historic else_instrs, ctyp)] + @ rewrite_exception historic after | before, I_throw cval :: after -> before @ [I_copy (CL_raw "current_exception", cval); I_raw "have_exception = true;"] - @ generate_cleanup before @ pre_cleanup + @ generate_cleanup (historic @ before) @ [I_goto end_block_label] - @ rewrite_exception (pre_cleanup @ generate_cleanup before) after + @ rewrite_exception (historic @ before) after | before, (I_funcall (x, f, args, ctyp) as funcall) :: after -> let effects = match Env.get_val_spec f ctx.tc_env with | _, Typ_aux (Typ_fn (_, _, effects), _) -> effects @@ -1515,10 +1519,10 @@ let fix_exception_block ctx instrs = if has_effect effects BE_escape then before @ [funcall; - I_if (CV_C_fragment (F_lit "have_exception", CT_bool), generate_cleanup before @ [I_goto end_block_label], [], CT_unit)] - @ rewrite_exception (pre_cleanup @ generate_cleanup before) after + I_if (CV_C_fragment (F_lit "have_exception", CT_bool), generate_cleanup (historic @ before) @ [I_goto end_block_label], [], CT_unit)] + @ rewrite_exception (historic @ before) after else - before @ funcall :: rewrite_exception (pre_cleanup @ generate_cleanup before) after + before @ funcall :: rewrite_exception (historic @ before) after | _, _ -> assert false (* unreachable *) in rewrite_exception [] instrs @@ -1538,7 +1542,7 @@ let fix_exception ctx instrs = let instrs = List.map (map_try_block (fix_exception_block ctx)) instrs in fix_exception_block ctx instrs -(** Compile a Sail toplevel definition into an LLcode definition **) +(** Compile a Sail toplevel definition into an IR definition **) let compile_def ctx = function | DEF_reg_dec (DEC_aux (DEC_reg (typ, id), _)) -> [CDEF_reg_dec (ctyp_of_typ ctx typ, id)], ctx @@ -1612,7 +1616,175 @@ let add_local_labels instrs = | _ -> assert false (**************************************************************************) -(* 5. Code generation *) +(* 5. Dependency Graphs *) +(**************************************************************************) + +type graph_node = + | G_id of id + | G_label of string + | G_instr of int * instr + | G_start + +let string_of_node = function + | G_id id -> string_of_id id + | G_label label -> label + | G_instr (n, instr) -> string_of_int n ^ ": " ^ Pretty_print_sail.to_string (pp_instr ~short:true instr) + | G_start -> "START" + +module Node = struct + type t = graph_node + let compare gn1 gn2 = + match gn1, gn2 with + | G_id id1, G_id id2 -> Id.compare id1 id2 + | G_label str1, G_label str2 -> String.compare str1 str2 + | G_instr (n1, _), G_instr (n2, _) -> compare n1 n2 + | G_start , _ -> 1 + | _ , G_start -> -1 + | G_instr _, _ -> 1 + | _ , G_instr _ -> -1 + | _ , G_id _ -> 1 + | G_id _ , _ -> -1 +end + +module NM = Map.Make(Node) +module NS = Set.Make(Node) + +type dep_graph = NS.t NM.t + +let rec fragment_deps = function + | F_id id -> NS.singleton (G_id id) + | F_lit _ -> NS.empty + | F_field (frag, _) | F_unary (_, frag) -> fragment_deps frag + | F_op (frag1, _, frag2) -> NS.union (fragment_deps frag1) (fragment_deps frag2) + +let cval_deps = function + | CV_id (id, _) -> NS.singleton (G_id id) + | CV_C_fragment (frag, _) -> fragment_deps frag + +let rec clexp_deps = function + | CL_id id -> NS.singleton (G_id id) + | CL_field (id, _) -> NS.singleton (G_id id) + | CL_addr clexp -> clexp_deps clexp + | CL_raw _ -> NS.empty + +(** Return the direct, non program-order dependencies of a single + instruction **) +let instr_deps = function + | I_decl (ctyp, id) -> NS.empty, NS.singleton (G_id id) + | I_alloc (ctyp, id) -> NS.singleton (G_id id), NS.singleton (G_id id) + | I_init (ctyp, id, cval) -> NS.add (G_id id) (cval_deps cval), NS.singleton (G_id id) + | I_if (cval, _, _, _) -> cval_deps cval, NS.empty + | I_funcall (clexp, _, cvals, _) -> List.fold_left NS.union NS.empty (List.map cval_deps cvals), clexp_deps clexp + | I_convert (clexp, _, id, _) -> NS.singleton (G_id id), clexp_deps clexp + | I_assign (id, cval) -> cval_deps cval, NS.singleton (G_id id) + | I_copy (clexp, cval) -> cval_deps cval, clexp_deps clexp + | I_clear (_, id) -> NS.singleton (G_id id), NS.singleton (G_id id) + | I_throw cval | I_return cval -> cval_deps cval, NS.empty + | I_block _ | I_try_block _ -> NS.empty, NS.empty + | I_comment _ | I_raw _ -> NS.empty, NS.empty + | I_label label -> NS.singleton (G_label label), NS.empty + | I_goto label -> NS.empty, NS.singleton (G_label label) + +let add_link from_node to_node graph = + try + NM.add from_node (NS.add to_node (NM.find from_node graph)) graph + with + | Not_found -> NM.add from_node (NS.singleton to_node) graph + +let leaves graph = + List.fold_left (fun acc (from_node, to_nodes) -> NS.filter (fun to_node -> Node.compare to_node from_node != 0) (NS.union acc to_nodes)) + NS.empty + (NM.bindings graph) + +(* Ensure that all leaves exist in the graph *) +let fix_leaves graph = + NS.fold (fun leaf graph -> if NM.mem leaf graph then graph else NM.add leaf NS.empty graph) (leaves graph) graph + +let instrs_graph instrs = + let icounter = ref 0 in + let graph = ref NM.empty in + + let rec add_instr last_instr instr = + incr icounter; + let node = G_instr (!icounter, instr) in + match instr with + | I_block instrs | I_try_block instrs -> + List.fold_left add_instr last_instr instrs + | I_if (_, then_instrs, else_instrs, _) -> + begin + let inputs, _ = instr_deps instr in (* if has no outputs *) + graph := add_link last_instr node !graph; + NS.iter (fun input -> graph := add_link input node !graph) inputs; + let n1 = List.fold_left add_instr node then_instrs in + let n2 = List.fold_left add_instr node else_instrs in + incr icounter; + let join = G_instr (!icounter, I_comment "join") in + graph := add_link n1 join !graph; + graph := add_link n2 join !graph; + join + end + | I_goto label -> + begin + let _, outputs = instr_deps instr in + graph := add_link last_instr node !graph; + NS.iter (fun output -> graph := add_link node output !graph) outputs; + incr icounter; + G_instr (!icounter, I_comment "after goto") + end + | _ -> + begin + let inputs, outputs = instr_deps instr in + graph := add_link last_instr node !graph; + NS.iter (fun input -> graph := add_link input node !graph) inputs; + NS.iter (fun output -> graph := add_link node output !graph) outputs; + node + end + in + List.fold_left add_instr G_start instrs; + fix_leaves !graph + +let make_dot id graph = + Util.opt_colors := false; + let to_string node = String.escaped (string_of_node node) in + let node_color = function + | G_start _ -> "lightpink" + | G_id _ -> "yellow" + | G_instr (_, I_decl _) -> "olivedrab1" + | G_instr (_, I_init _) -> "springgreen" + | G_instr (_, I_alloc _) -> "palegreen" + | G_instr (_, I_clear _) -> "peachpuff" + | G_instr (_, I_goto _) -> "orange1" + | G_instr (_, I_label _) -> "white" + | G_instr (_, I_raw _) -> "khaki" + | G_instr _ -> "azure" + | G_label _ -> "lightpink" + in + let edge_color from_node to_node = + match from_node, to_node with + | G_start _, _ -> "goldenrod4" + | G_label _, _ -> "darkgreen" + | _ , G_label _ -> "goldenrod4" + | G_instr _, G_instr _ -> "black" + | G_id _ , G_instr _ -> "blue3" + | G_instr _, G_id _ -> "red3" + | _ , _ -> "coral3" + in + let out_chan = open_out (Util.zencode_string (string_of_id id) ^ ".gv") in + output_string out_chan "digraph DEPS {\n"; + let make_node from_node = + output_string out_chan (Printf.sprintf " \"%s\" [fillcolor=%s;style=filled];\n" (to_string from_node) (node_color from_node)) + in + let make_line from_node to_node = + output_string out_chan (Printf.sprintf " \"%s\" -> \"%s\" [color=%s];\n" (to_string from_node) (to_string to_node) (edge_color from_node to_node)) + in + NM.bindings graph |> List.iter (fun (from_node, _) -> make_node from_node); + NM.bindings graph |> List.iter (fun (from_node, to_nodes) -> NS.iter (make_line from_node) to_nodes); + output_string out_chan "}\n"; + Util.opt_colors := true; + close_out out_chan + +(**************************************************************************) +(* 6. Code generation *) (**************************************************************************) let sgen_id id = Util.zencode_string (string_of_id id) @@ -1946,6 +2118,7 @@ let codegen_def' ctx = function ^^ string (Printf.sprintf "%s %s;" (sgen_ctyp ctyp) (sgen_id id)) | CDEF_fundef (id, ret_arg, args, instrs) -> + if !opt_ddump_flow_graphs then make_dot id (instrs_graph instrs) else (); let instrs = add_local_labels instrs in List.iter (fun instr -> prerr_endline (Pretty_print_sail.to_string (pp_instr instr))) instrs; let _, Typ_aux (fn_typ, _) = Env.get_val_spec id ctx.tc_env in |
