diff --git a/bin/main.ml b/bin/main.ml index abbb28a..784727d 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -30,8 +30,8 @@ let preprocess src = let _ = run_command preprocess_cmd in output -let compile stage preprocessed_src = - let _ = Compile.compile stage preprocessed_src in +let compile stage optimizations preprocessed_src = + let _ = Compile.compile stage optimizations preprocessed_src in (* remove preprocessed src *) let cleanup_preprocessed = Printf.sprintf "rm %s" preprocessed_src in run_command cleanup_preprocessed; @@ -54,7 +54,7 @@ let assemble_and_link ?(link = true) ?(cleanup = true) ?(libs = []) src = let cleanup_cmd = Printf.sprintf "rm %s" assembly_file in run_command cleanup_cmd -let driver target debug libs extra_credit int_only stage src = +let driver target debug libs extra_credit int_only stage optimizations src = let _ = Settings.platform := target; Settings.debug := debug; @@ -62,12 +62,13 @@ let driver target debug libs extra_credit int_only stage src = Settings.int_only := int_only in let preprocessed_name = preprocess src in - let assembly_name = compile stage preprocessed_name in + let assembly_name = compile stage optimizations preprocessed_name in match stage with | Settings.Executable -> - assemble_and_link ~link:true ~cleanup:(not debug) ~libs assembly_name + assemble_and_link ~link:true ~cleanup:(not debug.dump_asm) ~libs + assembly_name | Settings.Obj -> - assemble_and_link ~link:false ~cleanup:(not debug) assembly_name + assemble_and_link ~link:false ~cleanup:(not debug.dump_asm) assembly_name | _ -> () (* Command-line options *) @@ -152,15 +153,130 @@ let int_only = Arg.(value & flag & info [ "int-only" ] ~doc) let debug = - let doc = - "Write out pre- and post-register-allocation assembly and DOT files of \ - interference graphs." + let dump_tacky_opt = + let doc = "Dump TACKY at start and end of optimization phase." in + Arg.(value & flag & info [ "dump-tacky" ] ~doc) in - Arg.(value & flag & info [ "d" ] ~doc) + let dump_asm_opt = + let doc = "Dump assembly before and after pseudoreg allocation" in + Arg.(value & flag & info [ "dump-asm" ] ~doc) + in + let dump_const_fold_opt = + let doc = + "Dump TACKY immediately before and after each constant folding pass. \ + Ignored if this optimization is not enabled." + in + Arg.(value & flag & info [ "dump-const-fold" ] ~doc) + in + let dump_dse_opt = + let doc = + "Dump debug info (e.g. control flow graph) during dead store \ + elimination. Ignored if this optimization is not enabled." + in + Arg.(value & flag & info [ "dump-dse" ] ~doc) + in + let dump_copy_prop_opt = + let doc = + "Dump debug info (e.g. control flow graph) during copy pro. Ignored if \ + this optimization is not enabled." + in + Arg.(value & flag & info [ "dump-copy-prop" ] ~doc) + in + let dump_unreachable_elim_opt = + let doc = + "Dump debug info (e.g. control flow graph) during unreachable code elim. \ + Ignored if this optimization is not enabled." + in + Arg.(value & flag & info [ "dump-unreachable-elim" ] ~doc) + in + let dump_fun_opt = + let doc = + "Function to dump extra optimization details for (if not specified, dump \ + info for all functions). Affects optimization-specific --dump* options \ + but not --dump-tacky or --dump-asm." + in + Arg.(value & opt (some' string) None & info [ "dump-fun" ] ~doc) + in + let set_debug_options dump_asm dump_tacky constant_folding + dead_store_elimination copy_propagation unreachable_code_elimination + dump_fun = + Settings. + { + dump_asm; + dump_tacky; + dump_optimizations = + { + constant_folding; + dead_store_elimination; + unreachable_code_elimination; + copy_propagation; + }; + dump_fun; + } + in + Cmdliner.Term.( + const set_debug_options + $ dump_asm_opt + $ dump_tacky_opt + $ dump_const_fold_opt + $ dump_dse_opt + $ dump_copy_prop_opt + $ dump_unreachable_elim_opt + $ dump_fun_opt) let src_file = Arg.(required & pos 0 (some non_dir_file) None & info [] ~docv:"files") +(* optimization options *) +let optimization_options = + let fold_constants = + let doc = "Enable constant folding" in + Arg.(value & flag & info [ "fold-constants" ] ~doc) + in + let eliminate_dead_stores = + let doc = "Enable dead store elimination" in + Arg.(value & flag & info [ "eliminate-dead-stores" ] ~doc) + in + let propagate_copies = + let doc = "Enable copy-propagation" in + Arg.(value & flag & info [ "propagate-copies" ] ~doc) + in + let eliminate_unreachable_code = + let doc = "Enable unreachable code eliminaiton" in + Arg.(value & flag & info [ "eliminate-unreachable-code" ] ~doc) + in + let optimize = + let doc = "enable optimizations" in + Arg.(value & flag & info [ "o"; "optimize" ] ~doc) + in + let set_options optimize constant_folding dead_store_elimination + copy_propagation unreachable_code_elimination = + if optimize then + (* TODO maybe warn if both --optimize and any of the other options are set, since those options will be redundant? *) + Settings. + { + constant_folding = true; + dead_store_elimination = true; + unreachable_code_elimination = true; + copy_propagation = true; + } + else + Settings. + { + constant_folding; + dead_store_elimination; + copy_propagation; + unreachable_code_elimination; + } + in + Cmdliner.Term.( + const set_options + $ optimize + $ fold_constants + $ eliminate_dead_stores + $ propagate_copies + $ eliminate_unreachable_code) + let cmd = let doc = "A not-quite-C compiler" in let info = Cmd.info "nqcc" ~doc in @@ -173,6 +289,7 @@ let cmd = $ extra_credit $ int_only $ stage + $ optimization_options $ src_file) let main () = exit (Cmd.eval cmd) diff --git a/lib/backend/codegen.ml b/lib/backend/codegen.ml index bc7ce08..74f8fa3 100644 --- a/lib/backend/codegen.ml +++ b/lib/backend/codegen.ml @@ -109,15 +109,7 @@ let convert_type = function ("Internal error, converting type to assembly: " ^ Types.show t) [@coverage off] -let tacky_type = function - (* note: this reports the type of ConstChar as SChar instead of Char, doesn't matter in this context *) - | Tacky.Constant c -> Const.type_of_const c - | Tacky.Var v -> ( - try (Symbols.get v).t - with Not_found -> - failwith ("Internal error: " ^ v ^ " not in symbol table")) - -let asm_type = convert_type % tacky_type +let asm_type = convert_type % Tacky.type_of_val let convert_unop = function | Tacky.Complement -> Assembly.Not @@ -268,7 +260,7 @@ let classify_type tag = classes let classify_tacky_val v = - match tacky_type v with + match Tacky.type_of_val v with | Structure tag -> classify_type tag | Union tag -> classify_type tag | _ -> @@ -298,7 +290,7 @@ let classify_parameters tacky_vals return_on_stack = | Constant _ -> failwith "Internal error: constant byte array" [@coverage off] in - let var_size = Type_utils.get_size (tacky_type v) in + let var_size = Type_utils.get_size (Tacky.type_of_val v) in let classes = classify_tacky_val v in let updated_int, updated_dbl, use_stack = if List.hd classes = Mem then @@ -355,7 +347,7 @@ let classify_parameters tacky_vals return_on_stack = let classify_return_value retval = let open Assembly in - let retval_type = tacky_type retval in + let retval_type = Tacky.type_of_val retval in let classify_return_val_helper tag = let classes = classify_type tag in let var_name = @@ -512,7 +504,7 @@ let convert_return_instruction = function | Some v -> let int_retvals, dbl_retvals, return_on_stack = classify_return_value v in if return_on_stack then - let byte_count = Type_utils.get_size (tacky_type v) in + let byte_count = Type_utils.get_size (Tacky.type_of_val v) in let get_ptr = Assembly.Mov (Quadword, Memory (BP, -8), Reg AX) in let copy_into_ptr = copy_bytes (convert_val v) (Assembly.Memory (AX, 0)) ~byte_count @@ -540,7 +532,7 @@ let convert_return_instruction = function return_ints @ return_dbls @ [ Ret ] let convert_instruction = function - | Tacky.Copy { src; dst } when Type_utils.is_scalar (tacky_type src) -> + | Tacky.Copy { src; dst } when Type_utils.is_scalar (Tacky.type_of_val src) -> let t = asm_type src in let asm_src = convert_val src in let asm_dst = convert_val dst in @@ -548,7 +540,7 @@ let convert_instruction = function | Tacky.Copy { src; dst } -> let asm_src = convert_val src in let asm_dst = convert_val dst in - let byte_count = Type_utils.get_size (tacky_type src) in + let byte_count = Type_utils.get_size (Tacky.type_of_val src) in copy_bytes asm_src asm_dst ~byte_count | Tacky.Return maybe_val -> convert_return_instruction maybe_val | Tacky.Unary { op = Not; src; dst } -> @@ -571,7 +563,7 @@ let convert_instruction = function Mov (dst_t, zero, asm_dst); SetCC (E, asm_dst); ] - | Tacky.Unary { op = Negate; src; dst } when tacky_type src = Double -> + | Tacky.Unary { op = Negate; src; dst } when Tacky.type_of_val src = Double -> let asm_src = convert_val src in let asm_dst = convert_val dst in let negative_zero = add_constant ~alignment:16 (-0.0) in @@ -602,7 +594,7 @@ let convert_instruction = function else let signed = if src_t = Double then false - else Type_utils.is_signed (tacky_type src1) + else Type_utils.is_signed (Tacky.type_of_val src1) in let cond_code = convert_cond_code signed op in [ @@ -613,7 +605,7 @@ let convert_instruction = function (* Division/modulo *) | (Divide | Mod) when src_t <> Double -> let result_reg = if op = Divide then Assembly.AX else DX in - if Type_utils.is_signed (tacky_type src1) then + if Type_utils.is_signed (Tacky.type_of_val src1) then [ Mov (src_t, asm_src1, Reg AX); Cdq src_t; @@ -628,7 +620,7 @@ let convert_instruction = function Mov (src_t, Reg result_reg, asm_dst); ] | BitshiftLeft | BitshiftRight -> ( - let is_signed = Type_utils.is_signed (tacky_type src1) in + let is_signed = Type_utils.is_signed (Tacky.type_of_val src1) in let asm_op = convert_shift_op is_signed op in let asm_t = asm_type src1 in match asm_src2 with @@ -651,7 +643,8 @@ let convert_instruction = function Mov (src_t, asm_src1, asm_dst); Binary { op = asm_op; t = src_t; src = asm_src2; dst = asm_dst }; ]) - | Tacky.Load { src_ptr; dst } when Type_utils.is_scalar (tacky_type dst) -> + | Tacky.Load { src_ptr; dst } + when Type_utils.is_scalar (Tacky.type_of_val dst) -> let asm_src_ptr = convert_val src_ptr in let asm_dst = convert_val dst in let t = asm_type dst in @@ -659,10 +652,11 @@ let convert_instruction = function | Tacky.Load { src_ptr; dst } -> let asm_src_ptr = convert_val src_ptr in let asm_dst = convert_val dst in - let byte_count = Type_utils.get_size (tacky_type dst) in + let byte_count = Type_utils.get_size (Tacky.type_of_val dst) in Mov (Quadword, asm_src_ptr, Reg R9) :: copy_bytes (Memory (R9, 0)) asm_dst ~byte_count - | Tacky.Store { src; dst_ptr } when Type_utils.is_scalar (tacky_type src) -> + | Tacky.Store { src; dst_ptr } + when Type_utils.is_scalar (Tacky.type_of_val src) -> let asm_src = convert_val src in let t = asm_type src in let asm_dst_ptr = convert_val dst_ptr in @@ -670,7 +664,7 @@ let convert_instruction = function | Tacky.Store { src; dst_ptr } -> let asm_src = convert_val src in let asm_dst_ptr = convert_val dst_ptr in - let byte_count = Type_utils.get_size (tacky_type src) in + let byte_count = Type_utils.get_size (Tacky.type_of_val src) in Mov (Quadword, asm_dst_ptr, Reg R9) :: copy_bytes asm_src (Memory (R9, 0)) ~byte_count | Tacky.GetAddress { src; dst } -> @@ -756,7 +750,7 @@ let convert_instruction = function | Tacky.UIntToDouble { src; dst } -> let asm_src = convert_val src in let asm_dst = convert_val dst in - if tacky_type src = Types.UChar then + if Tacky.type_of_val src = Types.UChar then [ MovZeroExtend { @@ -767,7 +761,7 @@ let convert_instruction = function }; Cvtsi2sd (Longword, Reg R9, asm_dst); ] - else if tacky_type src = Types.UInt then + else if Tacky.type_of_val src = Types.UInt then [ MovZeroExtend { @@ -805,9 +799,9 @@ let convert_instruction = function | Tacky.DoubleToUInt { src; dst } -> let asm_src = convert_val src in let asm_dst = convert_val dst in - if tacky_type dst = Types.UChar then + if Tacky.type_of_val dst = Types.UChar then [ Cvttsd2si (Longword, asm_src, Reg R9); Mov (Byte, Reg R9, asm_dst) ] - else if tacky_type dst = Types.UInt then + else if Tacky.type_of_val dst = Types.UInt then Assembly. [ Cvttsd2si (Quadword, asm_src, Reg R9); @@ -835,21 +829,21 @@ let convert_instruction = function Binary { op = Add; t = Quadword; src = r; dst = asm_dst }; Label end_lbl; ] - | CopyToOffset { src; dst; offset } when Type_utils.is_scalar (tacky_type src) - -> + | CopyToOffset { src; dst; offset } + when Type_utils.is_scalar (Tacky.type_of_val src) -> [ Mov (asm_type src, convert_val src, PseudoMem (dst, offset)) ] | CopyToOffset { src; dst; offset } -> let asm_src = convert_val src in let asm_dst = Assembly.PseudoMem (dst, offset) in - let byte_count = Type_utils.get_size (tacky_type src) in + let byte_count = Type_utils.get_size (Tacky.type_of_val src) in copy_bytes asm_src asm_dst ~byte_count | CopyFromOffset { src; dst; offset } - when Type_utils.is_scalar (tacky_type dst) -> + when Type_utils.is_scalar (Tacky.type_of_val dst) -> [ Mov (asm_type dst, PseudoMem (src, offset), convert_val dst) ] | CopyFromOffset { src; dst; offset } -> let asm_src = Assembly.PseudoMem (src, offset) in let asm_dst = convert_val dst in - let byte_count = Type_utils.get_size (tacky_type dst) in + let byte_count = Type_utils.get_size (Tacky.type_of_val dst) in copy_bytes asm_src asm_dst ~byte_count | AddPtr { ptr; index = Constant (Const.ConstLong c); scale; dst } -> (* note that typechecker converts index to long diff --git a/lib/cfg.ml b/lib/cfg.ml new file mode 100644 index 0000000..ac226cf --- /dev/null +++ b/lib/cfg.ml @@ -0,0 +1,278 @@ +open Batteries + +(* a simplified instruction type that can represent both TACKY and assembly instructions *) +type simple_instr = + | Label of string + | ConditionalJump of string + | UnconditionalJump of string + | Return + | Other + +module type INSTR = sig + type instr + + (* functions for classifying basic blocks *) + val simplify : instr -> simple_instr + val pp_instr : Format.formatter -> instr -> unit +end + +module Cfg (Instr : INSTR) = struct + type node_id = Entry | Block of int | Exit + + (* Cfg is parameterized by type of val we compute and type of instruction (we use both Tacky and assembly instructions )*) + + type 'v basic_block = { + id : node_id; + instructions : ('v * Instr.instr) list; + mutable preds : node_id list; + mutable succs : node_id list; + value : 'v; + } + + type 'v t = { + (* store basic blocks in association list, indexed by block # *) + basic_blocks : (int * 'v basic_block) list; + mutable entry_succs : node_id list; + mutable exit_preds : node_id list; + (* extra context for debugging *) + ctx : Context.t; + } + + (* useful functions *) + + let get_succs nd_id cfg = + match nd_id with + | Entry -> cfg.entry_succs + | Block n -> + let nd = List.assoc n cfg.basic_blocks in + nd.succs + | Exit -> [] + + let get_block_value blocknum cfg = + let nd = List.assoc blocknum cfg.basic_blocks in + nd.value + + let update_successors ~f nd_id g = + match nd_id with + | Entry -> g.entry_succs <- f g.entry_succs + | Block n -> + let blk = List.assoc n g.basic_blocks in + blk.succs <- f blk.succs + | Exit -> failwith "Internal error: malformed CFG" [@coverage off] + + let update_predecessors ~f nd_id g = + match nd_id with + | Entry -> failwith "Internal error: malformed CFG" [@coverage off] + | Block n -> + let blk = List.assoc n g.basic_blocks in + blk.preds <- f blk.preds + | Exit -> g.exit_preds <- f g.exit_preds + + let add_edge pred succ g = + let add_id nd_id id_list = + if List.mem nd_id id_list then id_list else nd_id :: id_list + in + update_successors ~f:(add_id succ) pred g; + update_predecessors ~f:(add_id pred) succ g + + let remove_edge pred succ g = + let remove_id nd_id id_list = Batteries.List.remove id_list nd_id in + update_successors ~f:(remove_id succ) pred g; + update_predecessors ~f:(remove_id pred) succ g + + (* constructing the CFG *) + let partition_into_basic_blocks instructions = + let f (finished_blocks, current_block) i = + match Instr.simplify i with + | Label _ -> + let finished_blocks' = + if List.is_empty current_block then finished_blocks + else List.rev current_block :: finished_blocks + in + (finished_blocks', [ i ]) + | ConditionalJump _ | UnconditionalJump _ | Return -> + let finished = List.rev (i :: current_block) in + (finished :: finished_blocks, []) + | Other -> (finished_blocks, i :: current_block) + in + let finished, last = List.fold_left f ([], []) instructions in + let all_blocks = + if List.is_empty last then finished else List.rev last :: finished + in + List.rev all_blocks + + let add_all_edges g = + (* build map from labels to the IDS of the blocks that they start with *) + let label_map = + List.fold + (fun lbl_map (_, blk) -> + match Instr.simplify (snd (List.hd blk.instructions)) with + | Label lbl -> Map.add lbl blk.id lbl_map + | _ -> lbl_map) + Map.empty g.basic_blocks + in + + (* add outgoing edges from a single basic block *) + let process_node (id_num, block) = + let next_block = + if id_num = fst (List.last g.basic_blocks) then Exit + else Block (id_num + 1) + in + let (), last_instr = List.last block.instructions in + + match Instr.simplify last_instr with + | Return -> add_edge block.id Exit g + | UnconditionalJump target -> + let target_id = Map.find target label_map in + add_edge block.id target_id g + | ConditionalJump target -> + let target_id = Map.find target label_map in + add_edge block.id next_block g; + add_edge block.id target_id g + | _ -> add_edge block.id next_block g + in + add_edge Entry (Block 0) g; + List.iter process_node g.basic_blocks + + let instructions_to_cfg ctx instructions = + let to_node idx instructions = + let ann x = ((), x) in + ( idx, + { + id = Block idx; + instructions = List.map ann instructions; + preds = []; + succs = []; + value = (); + } ) + in + let cfg = + { + basic_blocks = + List.mapi to_node (partition_into_basic_blocks instructions); + entry_succs = []; + exit_preds = []; + ctx; + } + in + + add_all_edges cfg; + cfg + + (* converting back to instructions *) + let cfg_to_instructions g = + let blk_to_instrs (_, { instructions; _ }) = List.map snd instructions in + List.concat_map blk_to_instrs g.basic_blocks + + (* working with annotations *) + let initialize_annotation cfg dummy_val = + let initialize_instruction (_, i) = (dummy_val, i) in + let initialize_block (idx, b) = + ( idx, + { + b with + instructions = List.map initialize_instruction b.instructions; + value = dummy_val; + } ) + in + { cfg with basic_blocks = List.map initialize_block cfg.basic_blocks } + + let strip_annotations cfg = initialize_annotation cfg () + + (* debugging *) + let print_graphviz tag pp_val cfg = + let filename = Debug.mk_filename tag cfg.ctx ".dot" in + let path = + if Filename.is_relative filename then + Filename.concat (Sys.getcwd ()) filename + else filename + in + let chan = open_out path in + let formatter = Format.formatter_of_out_channel chan in + let pp_node_id out = function + | Exit -> Format.pp_print_string out "exit" + | Entry -> Format.pp_print_string out "entry" + | Block n -> Format.fprintf out "block%d" n + in + let pp_annotated_instruction out (v, i) = + Format.pp_open_box out 0; + Format.fprintf out + "@{@,\ + @[%a@]@,\ + %a@}@," + Instr.pp_instr i pp_val v; + Format.pp_close_box out () + in + let pp_block_instructions out blk = + Format.pp_open_vbox out 0; + Format.pp_open_tag out "table"; + Format.fprintf out "@{@{%a@}@}@," pp_node_id + blk.id; + Format.pp_print_list pp_annotated_instruction out blk.instructions; + (* print block annotations *) + Format.fprintf out "@{%a@}@," pp_val blk.value; + Format.pp_close_tag out (); + Format.pp_close_box out () + in + let pp_block out (lbl, b) = + Format.fprintf out "block%d[label=<%a>]" lbl pp_block_instructions b + in + let pp_entry_edge out lbl = + Format.fprintf out "entry -> %a" pp_node_id lbl + in + let pp_edge i out succ = + Format.fprintf out "block%d -> %a" i pp_node_id succ + in + let pp_edges out ((lbl : int), (blk : 'a basic_block)) = + Format.open_vbox 0; + Format.pp_print_list (pp_edge lbl) out blk.succs; + Format.close_box () + in + Format.pp_set_tags formatter true; + Format.pp_open_vbox formatter 0; + Format.pp_print_string formatter "digraph {"; + Format.pp_print_break formatter 0 2; + Format.pp_open_vbox formatter 0; + Format.pp_print_string formatter "labeljust=l"; + Format.pp_print_cut formatter (); + Format.pp_print_string formatter "node[shape=\"box\"]"; + Format.pp_print_cut formatter (); + Format.pp_print_string formatter "entry[label=\"ENTRY\"]"; + Format.pp_print_cut formatter (); + Format.pp_print_string formatter "exit[label=\"EXIT\"]"; + Format.pp_print_cut formatter (); + Format.pp_print_list pp_block formatter cfg.basic_blocks; + Format.pp_print_cut formatter (); + Format.pp_print_list pp_entry_edge formatter cfg.entry_succs; + Format.pp_print_cut formatter (); + Format.pp_print_list pp_edges formatter cfg.basic_blocks; + Format.pp_close_box formatter (); + Format.pp_print_cut formatter (); + Format.pp_print_string formatter "}"; + Format.pp_close_box formatter (); + Format.pp_print_newline formatter (); + close_out chan; + let cmd = + Printf.sprintf "dot -Tpng %s -o %s" filename + (Filename.chop_extension filename ^ ".png") + in + if Sys.command cmd <> 0 then failwith ("graphviz fail: " ^ cmd) + else if Sys.command ("rm " ^ filename) <> 0 then + failwith "failed to remove DOT file" + [@@coverage off] +end + +module TackyCfg = Cfg (struct + type instr = Tacky.instruction + + let simplify = function + | Tacky.Label l -> Label l + | Jump target -> UnconditionalJump target + | JumpIfZero (_, target) -> ConditionalJump target + | JumpIfNotZero (_, target) -> ConditionalJump target + | Return _ -> Return + | _ -> Other + + let pp_instr = Tacky_print.pp_instruction ~escape_brackets:true + [@@coverage off] +end) diff --git a/lib/cfg.mli b/lib/cfg.mli new file mode 100644 index 0000000..1711a51 --- /dev/null +++ b/lib/cfg.mli @@ -0,0 +1,34 @@ +module TackyCfg : sig + type node_id = Entry | Block of int | Exit + + (* Cfg is parameterized by type of val we compute and type of instruction (we use both Tacky and assembly instructions )*) + + type 'v basic_block = { + id : node_id; + instructions : ('v * Tacky.instruction) list; + mutable preds : node_id list; + mutable succs : node_id list; + value : 'v; + } + + type 'v t = { + (* store basic blocks in association list, indexed by block # *) + basic_blocks : (int * 'v basic_block) list; + mutable entry_succs : node_id list; + mutable exit_preds : node_id list; + ctx : Context.t; + } + + val instructions_to_cfg : Context.t -> Tacky.instruction list -> unit t + val cfg_to_instructions : 'v t -> Tacky.instruction list + val get_succs : node_id -> 'v t -> node_id list + val get_block_value : int -> 'v t -> 'v + val add_edge : node_id -> node_id -> 'v t -> unit + val remove_edge : node_id -> node_id -> 'v t -> unit + val initialize_annotation : 'a t -> 'b -> 'b t + val strip_annotations : 'a t -> unit t + + (* debugging *) + val print_graphviz : + string -> (Format.formatter -> 'v -> unit) -> 'v t -> unit +end diff --git a/lib/compile.ml b/lib/compile.ml index d1c0317..d4dae3f 100644 --- a/lib/compile.ml +++ b/lib/compile.ml @@ -1,6 +1,6 @@ open Batteries -let compile stage src_file = +let compile stage optimizations src_file = (* read in the file - TODO use streams? *) let source_lines = File.lines_of src_file in (* concatenate all the lines *) @@ -32,14 +32,18 @@ let compile stage src_file = (* Convert the AST to TACKY *) let tacky = Tacky_gen.gen validated_ast3 in (* print to file (src filename with .debug.tacky extension) if debug is enabled*) - Tacky_print.debug_print_tacky src_file tacky; + Tacky_print.debug_print_tacky ~tag:"pre_opt" ~file:src_file tacky; + (* optimize it! *) + let optimized_tacky = Optimize.optimize optimizations src_file tacky in + Tacky_print.debug_print_tacky ~tag:"post_opt" ~file:src_file + optimized_tacky; if stage = Settings.Tacky then () else (* Assembly generation has three steps: * 1. convert TACKY to assembly *) - let asm_ast = Codegen.gen tacky in + let asm_ast = Codegen.gen optimized_tacky in (* print pre-pseudoreg-allocation assembly if debug enabled *) - (if !Settings.debug then + (if !Settings.debug.dump_asm then let prealloc_filename = Filename.chop_extension src_file ^ ".prealloc.debug.s" in diff --git a/lib/compile.mli b/lib/compile.mli index addba2a..bc07a70 100644 --- a/lib/compile.mli +++ b/lib/compile.mli @@ -1 +1 @@ -val compile : Settings.stage -> string -> unit +val compile : Settings.stage -> Settings.optimizations -> string -> unit diff --git a/lib/const.ml b/lib/const.ml index 1016be5..4c796a0 100644 --- a/lib/const.ml +++ b/lib/const.ml @@ -10,6 +10,7 @@ type t = | ConstUInt of UInt32.t | ConstULong of UInt64.t | ConstDouble of Float.t +[@@deriving eq, ord] (* print functions for debugging *) let show = function @@ -36,3 +37,12 @@ let type_of_const = function | ConstUInt _ -> Types.UInt | ConstULong _ -> Types.ULong | ConstDouble _ -> Types.Double + +let to_int = function + | ConstChar c -> Int8.to_int c + | ConstUChar c -> UInt8.to_int c + | ConstInt i -> Int32.to_int i + | ConstLong l -> Int64.to_int l + | ConstUInt u -> UInt32.to_int u + | ConstULong ul -> UInt64.to_int ul + | ConstDouble d -> Float.to_int d diff --git a/lib/context.ml b/lib/context.ml new file mode 100644 index 0000000..0128f31 --- /dev/null +++ b/lib/context.ml @@ -0,0 +1,2 @@ +(* Extra context about a TACKY function; used primarily for debugging *) +type t = { filename : string; fun_name : string; params : string list } diff --git a/lib/dune b/lib/dune index e3bd448..ee56aef 100644 --- a/lib/dune +++ b/lib/dune @@ -4,23 +4,32 @@ (name nqcc) (inline_tests) (modules + address_taken ast assembly assembly_symbols + cfg cnums codegen collect_switch_cases compile const const_convert + constant_folding + context + copy_prop + dead_store_elim + debug emit extended_big_int + optimize_utils identifier_resolution initializers instruction_fixup label_loops lex num_interfaces + optimize parse replace_pseudos rounding @@ -35,8 +44,9 @@ types type_utils settings - unique_ids - validate_labels) + validate_labels + unique_ids + unreachable_code_elim) (libraries batteries ppx_deriving ppx_inline_test camlp-streams bisect_ppx) (preprocess (pps ppx_deriving.show ppx_deriving.eq ppx_deriving.ord ppx_inline_test)) diff --git a/lib/optimizations/address_taken.ml b/lib/optimizations/address_taken.ml new file mode 100644 index 0000000..8e32f60 --- /dev/null +++ b/lib/optimizations/address_taken.ml @@ -0,0 +1,9 @@ +open Batteries + +let analyze instrs = + let addr_taken = function + | Tacky.GetAddress { src = Var v; _ } -> Some v + | _ -> None + in + + Set.of_list (List.filter_map addr_taken instrs) \ No newline at end of file diff --git a/lib/optimizations/address_taken.mli b/lib/optimizations/address_taken.mli new file mode 100644 index 0000000..688015c --- /dev/null +++ b/lib/optimizations/address_taken.mli @@ -0,0 +1 @@ +val analyze : Tacky.instruction list -> string Batteries.Set.t \ No newline at end of file diff --git a/lib/optimizations/constant_folding.ml b/lib/optimizations/constant_folding.ml new file mode 100644 index 0000000..bba7349 --- /dev/null +++ b/lib/optimizations/constant_folding.ml @@ -0,0 +1,227 @@ +open Cnums +open Num_interfaces + +let evaluate_cast src_const dst = + let dst_type = Tacky.type_of_val dst in + let converted_src = + try Const_convert.const_convert dst_type src_const + with Failure _ -> + (* Undefined behavior (e.g. out of range conversion) so just use 0; + * behavior is undefined if this cast is ever executed. *) + Const_convert.const_convert dst_type Const.int_zero + in + + Some (Tacky.Copy { src = Constant converted_src; dst }) + +module type Evaluatable = sig + include BasicNumLike + + val to_const : t -> Const.t +end + +module ConstEvaluator (E : Evaluatable) = struct + open E.Infix + open E.Compare + + let int_of_bool b = if b then Const.int_one else Const.int_zero + + let eval_unop v = function + | Tacky.Not -> int_of_bool (v = E.zero) + | Tacky.Complement -> E.to_const (E.lognot v) + | Tacky.Negate -> E.to_const (E.neg v) + + let eval_binop v1 v2 = function + (* result is same type as source values *) + | Tacky.Add -> E.to_const (v1 + v2) + | Subtract -> E.to_const (v1 - v2) + | Multiply -> E.to_const (v1 * v2) + | Divide -> ( + (* don't fail on division by zero; it may not actually be executed at runtime *) + try E.to_const (v1 / v2) with Division_by_zero -> E.to_const E.zero) + | Mod -> ( + try E.(to_const (rem v1 v2)) + with Division_by_zero -> E.to_const E.zero) + | BitwiseAnd -> E.(to_const (logand v1 v2)) + | BitwiseOr -> E.(to_const (logor v1 v2)) + | BitwiseXor -> E.(to_const (logxor v1 v2)) + (* result is int *) + | Equal -> int_of_bool (v1 = v2) + | NotEqual -> int_of_bool (v1 <> v2) + | LessThan -> int_of_bool (v1 < v2) + | LessOrEqual -> int_of_bool (v1 <= v2) + | GreaterThan -> int_of_bool (v1 > v2) + | GreaterOrEqual -> int_of_bool (v1 >= v2) + | BitshiftLeft | BitshiftRight -> + failwith "Internal error: bitshift operations aren't handled here" + + let eval_left_shift v shift_count = E.(to_const (shift_left v shift_count)) + let eval_right_shift v shift_count = E.(to_const (shift_right v shift_count)) +end + +module IntEvaluator = ConstEvaluator (struct + include Batteries.Int32 + + let to_const i = Const.ConstInt i +end) + +let%test "not_zero" = + IntEvaluator.eval_unop Int32.zero Tacky.Not = Const.int_one + +let%test "not_one" = IntEvaluator.eval_unop Int32.one Tacky.Not = Const.int_zero + +module LongEvaluator = ConstEvaluator (struct + include Batteries.Int64 + + let to_const l = Const.ConstLong l +end) + +module UIntEvaluator = ConstEvaluator (struct + include UInt32 + + let to_const u = Const.ConstUInt u +end) + +module ULongEvaluator = ConstEvaluator (struct + include UInt64 + + let to_const ul = Const.ConstULong ul +end) + +module CharEvaluator = ConstEvaluator (struct + include Int8 + + let to_const c = Const.ConstChar c +end) + +module UCharEvaluator = ConstEvaluator (struct + include UInt8 + + let to_const uc = Const.ConstUChar uc +end) + +module DoubleEvaluator = ConstEvaluator (struct + include Float + + let to_const d = Const.ConstDouble d + let rem _ = (failwith "Remainder of double not supported" [@coverage off]) + + let lognot _ = + (failwith "Bitwise complement of double not supported" [@coverage off]) + + let logand _ = + (failwith "Bitwise complement of double not supported" [@coverage off]) + + let logor _ = + (failwith "Bitwise complement of double not supported" [@coverage off]) + + let logxor _ = + (failwith "Bitwise complement of double not supported" [@coverage off]) + + let shift_left _ = + (failwith "Bitwise shift of double not supported" [@coverage off]) + + let shift_right _ = + (failwith "Bitwise shift of double not supported" [@coverage off]) +end) + +let evaluate_unop op = function + | Const.ConstChar c -> CharEvaluator.eval_unop c op + | ConstUChar uc -> UCharEvaluator.eval_unop uc op + | ConstInt i -> IntEvaluator.eval_unop i op + | ConstUInt u -> UIntEvaluator.eval_unop u op + | ConstLong l -> LongEvaluator.eval_unop l op + | ConstULong ul -> ULongEvaluator.eval_unop ul op + | ConstDouble d -> DoubleEvaluator.eval_unop d op + +let evaluate_binop op v1 v2 = + match (v1, v2) with + | Const.ConstChar c1, Const.ConstChar c2 -> CharEvaluator.eval_binop c1 c2 op + | ConstUChar c1, ConstUChar c2 -> UCharEvaluator.eval_binop c1 c2 op + | ConstInt i1, ConstInt i2 -> IntEvaluator.eval_binop i1 i2 op + | ConstUInt i1, ConstUInt i2 -> UIntEvaluator.eval_binop i1 i2 op + | ConstLong l1, ConstLong l2 -> LongEvaluator.eval_binop l1 l2 op + | ConstULong l1, ConstULong l2 -> ULongEvaluator.eval_binop l1 l2 op + | ConstDouble d1, ConstDouble d2 -> DoubleEvaluator.eval_binop d1 d2 op + | _ -> failwith "Internal error: mismatched types" [@coverage off] + +let evaluate_leftshift v1 v2 = + let shift_count = Const.to_int v2 in + match v1 with + | Const.ConstChar c -> CharEvaluator.eval_left_shift c shift_count + | ConstUChar uc -> UCharEvaluator.eval_left_shift uc shift_count + | ConstInt i -> IntEvaluator.eval_left_shift i shift_count + | ConstUInt u -> UIntEvaluator.eval_left_shift u shift_count + | ConstLong l -> LongEvaluator.eval_left_shift l shift_count + | ConstULong ul -> ULongEvaluator.eval_left_shift ul shift_count + | ConstDouble _ -> + failwith "Internal error: bitshift operation applied to double!" + +let evaluate_rightshift v1 v2 = + let shift_count = Const.to_int v2 in + match v1 with + | Const.ConstChar c -> CharEvaluator.eval_right_shift c shift_count + | ConstUChar uc -> UCharEvaluator.eval_right_shift uc shift_count + | ConstInt i -> IntEvaluator.eval_right_shift i shift_count + | ConstUInt u -> UIntEvaluator.eval_right_shift u shift_count + | ConstLong l -> LongEvaluator.eval_right_shift l shift_count + | ConstULong ul -> ULongEvaluator.eval_right_shift ul shift_count + | ConstDouble _ -> + failwith "Internal error: bitshift operation applied to double!" + +let is_zero c = evaluate_unop Not c = Const.ConstInt Int32.one + +let optimize_instruction = function + | Tacky.Unary { op; src = Constant c; dst } -> + let new_src = evaluate_unop op c in + Some (Tacky.Copy { src = Constant new_src; dst }) + | Binary { op = BitshiftLeft; src1 = Constant c1; src2 = Constant c2; dst } -> + let new_src = evaluate_leftshift c1 c2 in + Some (Copy { src = Constant new_src; dst }) + | Binary { op = BitshiftRight; src1 = Constant c1; src2 = Constant c2; dst } + -> + let new_src = evaluate_rightshift c1 c2 in + Some (Copy { src = Constant new_src; dst }) + | Binary { op; src1 = Constant c1; src2 = Constant c2; dst } -> + let new_src = evaluate_binop op c1 c2 in + Some (Copy { src = Constant new_src; dst }) + | JumpIfZero (Constant c, target) -> + if is_zero c then Some (Jump target) else None + | JumpIfNotZero (Constant c, target) -> + if is_zero c then None else Some (Jump target) + (* type conversions *) + | Truncate { src = Constant c; dst } -> evaluate_cast c dst + | SignExtend { src = Constant c; dst } -> evaluate_cast c dst + | ZeroExtend { src = Constant c; dst } -> evaluate_cast c dst + | DoubleToInt { src = Constant c; dst } -> evaluate_cast c dst + | DoubleToUInt { src = Constant c; dst } -> evaluate_cast c dst + | IntToDouble { src = Constant c; dst } -> evaluate_cast c dst + | UIntToDouble { src = Constant c; dst } -> evaluate_cast c dst + (* if this copies b/t values of different types, we'll replace it with a copy using + * src constant w/ same type as dst *) + | Copy { src = Constant c; dst } -> evaluate_cast c dst + (* other instructions can't be constnat folded *) + | i -> Some i + +let debug_print tag ctx instructions = + if + !Settings.debug.dump_optimizations.constant_folding + && Debug.is_dump_target ctx + then ( + let tag' = tag ^ ".const_fold" in + let filename = Debug.mk_filename tag' ctx ".tacky" in + let chan = open_out filename in + let formatter = Format.formatter_of_out_channel chan in + let open Context in + Tacky_print.pp_function_definition false ctx.fun_name ctx.params formatter + instructions; + Format.pp_print_newline formatter (); + close_out chan) + else () + +let optimize ctx instructions = + debug_print "pre" ctx instructions; + let optimized_instructions = + List.filter_map optimize_instruction instructions + in + debug_print "post" ctx optimized_instructions; + optimized_instructions diff --git a/lib/optimizations/constant_folding.mli b/lib/optimizations/constant_folding.mli new file mode 100644 index 0000000..8b8036a --- /dev/null +++ b/lib/optimizations/constant_folding.mli @@ -0,0 +1 @@ +val optimize : Context.t -> Tacky.instruction list -> Tacky.instruction list diff --git a/lib/optimizations/copy_prop.ml b/lib/optimizations/copy_prop.ml new file mode 100644 index 0000000..347e7d2 --- /dev/null +++ b/lib/optimizations/copy_prop.ml @@ -0,0 +1,235 @@ +open Batteries + +module G = struct + include Cfg.TackyCfg +end + +(* represent a reaching copy as pair of tacky vals *) +type cp = { src : Tacky.tacky_val; dst : Tacky.tacky_val } [@@deriving ord] + +let pp_cp fmt copy = + Format.fprintf fmt "%a = %a" Tacky_print.pp_tacky_val copy.dst + Tacky_print.pp_tacky_val copy.src + +(* be careful to always use our own eq/compare instead of built-in one b/c 0.0 and -0.0 compare equal, and NaN doesn't equal itself, using (=) *) +module ReachingCopies = Set.Make (struct + type t = cp + + let compare = compare_cp +end) + +let eq = Tacky.equal_tacky_val + +let debug_print ~extra_tag cfg = + if + !Settings.debug.dump_optimizations.copy_propagation + && Debug.is_dump_target G.(cfg.ctx) + then + let copies_printer fmt copies = + Format.pp_open_box fmt 0; + Format.pp_print_list + ~pp_sep:(fun fmt () -> Format.pp_print_string fmt ", ") + pp_cp fmt + (ReachingCopies.to_list copies); + Format.pp_close_box fmt () + in + let tag = "copy_prop." ^ extra_tag in + G.(print_graphviz tag copies_printer cfg) + +(* check whether two operands of Copy are, for our purposes, the same type *) +let same_type v1 v2 = + let t1, t2 = Tacky.(type_of_val v1, type_of_val v2) in + t1 = t2 || Type_utils.is_signed t1 = Type_utils.is_signed t2 + +let var_is_aliased aliased_vars = function + | Tacky.Constant _ -> false + (* all static variables are potentially aliased *) + | Var v when Set.mem v aliased_vars || Optimize_utils.is_static v -> true + | _ -> false + +let filter_updated copies updated = + (* A copy is killed if we've updated its src or destination *) + let is_killed cp = eq cp.src updated || eq cp.dst updated in + ReachingCopies.filter (not % is_killed) copies + +let transfer aliased_vars (block : ReachingCopies.t G.basic_block) + (initial_reaching_copies : ReachingCopies.t) = + let is_aliased = var_is_aliased aliased_vars in + let process_instr current_copies (_, i) = + let annotated_instr = (current_copies, i) in + let new_copies = + match i with + | Tacky.Copy { src; dst } -> + if ReachingCopies.mem { src = dst; dst = src } current_copies then + (* dst and src already have the same value, so there's no effect *) + current_copies + else if same_type src dst then + filter_updated current_copies dst |> ReachingCopies.add { src; dst } + (* if types are the same, kill dst but don't count this as a reaching copy *) + else filter_updated current_copies dst + | FunCall { dst; _ } -> + (* first filter out copies killed by dst *) + let copies' = + match dst with + | Some d -> filter_updated current_copies d + | None -> current_copies + in + + (* then filter out copies that are static *) + ReachingCopies.filter + (fun cp -> not (is_aliased cp.src || is_aliased cp.dst)) + copies' + | Store _ -> + let not_killed cp = not (is_aliased cp.src || is_aliased cp.dst) in + ReachingCopies.filter not_killed current_copies + | i -> ( + match Optimize_utils.get_dst i with + | Some dst -> filter_updated current_copies dst + | None -> current_copies) + in + (new_copies, annotated_instr) + in + let final_reaching_copies, annotated_instructions = + List.fold_left_map process_instr initial_reaching_copies block.instructions + in + { + block with + instructions = annotated_instructions; + value = final_reaching_copies; + } + +let meet ident cfg (block : ReachingCopies.t G.basic_block) = + (* arbitrartily choose value *) + let update_incoming incoming = function + | G.Entry -> ReachingCopies.empty + | Exit -> failwith "Internal error" [@coverage off] + | Block n -> + (* get current value for this block *) + let v = G.get_block_value n cfg in + ReachingCopies.inter v incoming + in + + List.fold update_incoming ident block.preds + +let collect_all_copies cfg = + let f = function + | Tacky.Copy { src; dst } when same_type src dst -> Some { src; dst } + | _ -> None + in + cfg |> G.cfg_to_instructions |> List.filter_map f |> ReachingCopies.of_list + +let find_reaching_copies aliased_vars cfg = + let ident = collect_all_copies cfg in + let starting_cfg = G.initialize_annotation cfg ident in + + let rec process_worklist current_cfg + (worklist : (int * ReachingCopies.t G.basic_block) list) = + debug_print ~extra_tag:"in_progress" current_cfg; + match worklist with + | [] -> current_cfg (* we're done *) + | (block_idx, blk) :: rest -> + let old_annotation = blk.value in + let incoming_copies = meet ident current_cfg blk in + let block' = transfer aliased_vars blk incoming_copies in + let updated_cfg = + G. + { + current_cfg with + basic_blocks = + List.modify block_idx (fun _ -> block') current_cfg.basic_blocks; + } + in + let new_worklist = + if ReachingCopies.equal old_annotation block'.value then rest + else + (* add successors to worklist *) + List.fold + (fun wklist -> function + | G.Exit -> wklist + | Entry -> + failwith "Internal error: malformed CFG" [@coverage off] + | Block n -> + if List.mem_assoc n wklist then wklist + else wklist @ [ (n, List.assoc n updated_cfg.basic_blocks) ]) + rest block'.succs + in + process_worklist updated_cfg new_worklist + in + process_worklist starting_cfg starting_cfg.basic_blocks + +let rewrite_instruction (reaching_copies, i) = + (* filter out useless copies *) + match i with + | Tacky.Copy { src; dst } + when ReachingCopies.mem { src; dst } reaching_copies + || ReachingCopies.mem { src = dst; dst = src } reaching_copies -> + None + | _ -> + (* we're not filtering it out, so replace src instead *) + let replace op = + match op with + (* we never replace constants *) + | Tacky.Constant _ -> op + | Tacky.Var _ -> ( + try + (* find th reaching copy whose destination is op *) + let matching_copy = + reaching_copies + |> ReachingCopies.to_list + |> List.find (fun cp -> eq cp.dst op) + in + matching_copy.src + (* didn't find one, just return op itself *) + with Not_found -> op) + in + + let new_i = + match i with + | Tacky.Copy { src; dst } -> Tacky.Copy { src = replace src; dst } + | Unary u -> Unary { u with src = replace u.src } + | Binary b -> + Binary { b with src1 = replace b.src1; src2 = replace b.src2 } + | Return v -> Return (Option.map replace v) + | JumpIfZero (v, target) -> JumpIfZero (replace v, target) + | JumpIfNotZero (v, target) -> JumpIfNotZero (replace v, target) + | FunCall f -> FunCall { f with args = List.map replace f.args } + | SignExtend sx -> SignExtend { sx with src = replace sx.src } + | ZeroExtend zx -> ZeroExtend { zx with src = replace zx.src } + | DoubleToInt d2i -> DoubleToInt { d2i with src = replace d2i.src } + | IntToDouble i2d -> IntToDouble { i2d with src = replace i2d.src } + | DoubleToUInt d2u -> DoubleToUInt { d2u with src = replace d2u.src } + | UIntToDouble u2d -> UIntToDouble { u2d with src = replace u2d.src } + | Truncate t -> Truncate { t with src = replace t.src } + | Load l -> Load { l with src_ptr = replace l.src_ptr } + | Store s -> Store { s with src = replace s.src } + | AddPtr ap -> + AddPtr { ap with ptr = replace ap.ptr; index = replace ap.index } + | CopyToOffset c2o -> CopyToOffset { c2o with src = replace c2o.src } + | CopyFromOffset cfo -> ( + match replace (Var cfo.src) with + | Var replaced -> CopyFromOffset { cfo with src = replaced } + | Constant _ -> failwith "internal error" [@coverage off]) + | Label _ | Jump _ | GetAddress _ -> i + in + Some (reaching_copies, new_i) + +let optimize aliased_vars (cfg : 'a Cfg.TackyCfg.t) : 'b Cfg.TackyCfg.t = + let annotated_cfg = find_reaching_copies aliased_vars cfg in + let _ = debug_print ~extra_tag:"annotated" annotated_cfg in + let rewrite_block (idx, block) = + ( idx, + G. + { + block with + instructions = List.filter_map rewrite_instruction block.instructions; + } ) + in + let transformed_cfg = + { + annotated_cfg with + basic_blocks = List.map rewrite_block annotated_cfg.basic_blocks; + } + in + let _ = debug_print ~extra_tag:"transformed" transformed_cfg in + (* remove annotations since we no longer need them *) + G.strip_annotations transformed_cfg diff --git a/lib/optimizations/copy_prop.mli b/lib/optimizations/copy_prop.mli new file mode 100644 index 0000000..9b719a2 --- /dev/null +++ b/lib/optimizations/copy_prop.mli @@ -0,0 +1,2 @@ +val optimize : + string Batteries.Set.t -> unit Cfg.TackyCfg.t -> unit Cfg.TackyCfg.t diff --git a/lib/optimizations/dead_store_elim.ml b/lib/optimizations/dead_store_elim.ml new file mode 100644 index 0000000..b507faf --- /dev/null +++ b/lib/optimizations/dead_store_elim.ml @@ -0,0 +1,179 @@ +open Batteries + +module G = struct + include Cfg.TackyCfg +end + +type annotated_block = string Set.t G.basic_block + +let debug_print ~extra_tag cfg = + if + !Settings.debug.dump_optimizations.dead_store_elimination + && Debug.is_dump_target G.(cfg.ctx) + then + let livevar_printer fmt live_vars = + Format.pp_open_box fmt 0; + Format.pp_print_list + ~pp_sep:(fun fmt () -> Format.pp_print_string fmt ", ") + Format.pp_print_string fmt (Set.to_list live_vars); + Format.pp_close_box fmt () + in + let tag = "dse." ^ extra_tag in + G.(print_graphviz tag livevar_printer cfg) + +let transfer static_and_aliased_vars (block : annotated_block) + (end_live_variables : string Set.t) = + let remove_var var var_set = + match var with + | Tacky.Var v -> Set.remove v var_set + | Tacky.Constant _ -> failwith "Internal error" [@coverage off] + in + + let add_var v var_set = + match v with + | Tacky.Constant _ -> var_set + | Var name -> Set.add name var_set + in + let add_vars used_vals var_set = List.fold_right add_var used_vals var_set in + + let process_instr current_live_vars (_, i) = + let annotated_instr = (current_live_vars, i) in + let new_live_vars = + match i with + | Tacky.Binary b -> + current_live_vars |> remove_var b.dst |> add_vars [ b.src1; b.src2 ] + | Tacky.Unary u -> current_live_vars |> remove_var u.dst |> add_var u.src + | JumpIfZero (v, _) -> add_var v current_live_vars + | JumpIfNotZero (v, _) -> add_var v current_live_vars + | Copy c -> current_live_vars |> remove_var c.dst |> add_var c.src + | Return (Some v) -> current_live_vars |> add_var v + | FunCall f -> + let live_vars' = + match f.dst with + | Some d -> remove_var d current_live_vars + | None -> current_live_vars + in + let live_vars'' = add_vars f.args live_vars' in + Set.union live_vars'' static_and_aliased_vars + (* part II instructions *) + | SignExtend sx -> + current_live_vars |> remove_var sx.dst |> add_var sx.src + | ZeroExtend zx -> + current_live_vars |> remove_var zx.dst |> add_var zx.src + | DoubleToInt d2i -> + current_live_vars |> remove_var d2i.dst |> add_var d2i.src + | IntToDouble i2d -> + current_live_vars |> remove_var i2d.dst |> add_var i2d.src + | DoubleToUInt d2u -> + current_live_vars |> remove_var d2u.dst |> add_var d2u.src + | UIntToDouble u2d -> + current_live_vars |> remove_var u2d.dst |> add_var u2d.src + | Truncate t -> current_live_vars |> remove_var t.dst |> add_var t.src + | AddPtr { ptr; index; dst; _ } -> + current_live_vars |> remove_var dst |> add_vars [ ptr; index ] + | GetAddress { dst; _ } -> remove_var dst current_live_vars + | Load { src_ptr; dst } -> + current_live_vars + |> remove_var dst + |> add_var src_ptr + |> Set.union static_and_aliased_vars + | Store { src; dst_ptr } -> add_vars [ src; dst_ptr ] current_live_vars + | CopyToOffset { src; _ } -> add_var src current_live_vars + | CopyFromOffset { src; dst; _ } -> + current_live_vars |> remove_var dst |> add_var (Var src) + (* instructions with no impact *) + | Jump _ | Label _ | Return None -> current_live_vars + in + (new_live_vars, annotated_instr) + in + let incoming_live_vars, annotated_reversed_instructions = + block.instructions + |> List.rev + |> List.fold_left_map process_instr end_live_variables + in + { + block with + instructions = List.rev annotated_reversed_instructions; + value = incoming_live_vars; + } + +let meet static_vars cfg (block : annotated_block) = + let update_live live = function + | G.Entry -> failwith "Internal error: malformed CFG" [@coverage off] + | Exit -> Set.union live static_vars + | Block n -> Set.union live (G.get_block_value n cfg) + in + List.fold update_live Set.empty block.succs + +let find_live_variables static_vars aliased_vars (cfg : unit G.t) = + let starting_cfg = G.initialize_annotation cfg Set.empty in + let static_and_aliased_vars = Set.union static_vars aliased_vars in + let rec process_worklist current_cfg (worklist : (int * annotated_block) list) + = + debug_print ~extra_tag:"in_progress" current_cfg; + match worklist with + | [] -> current_cfg (* we're done*) + | (block_idx, blk) :: rest -> + let old_annotation = blk.value in + let live_vars_at_exit = meet static_vars current_cfg blk in + let block' = transfer static_and_aliased_vars blk live_vars_at_exit in + let updated_cfg = + G. + { + current_cfg with + basic_blocks = + List.modify block_idx (fun _ -> block') current_cfg.basic_blocks; + } + in + let new_worklist = + if Set.equal old_annotation block'.value then rest + else + List.fold + (fun wklist -> function + | G.Entry -> wklist + | Exit -> + failwith "Internal error: malformed CFG" [@coverage off] + | Block n -> + if List.mem_assoc n wklist then wklist + else (n, List.assoc n updated_cfg.basic_blocks) :: wklist) + rest block'.preds + in + process_worklist updated_cfg new_worklist + in + process_worklist starting_cfg (List.rev starting_cfg.basic_blocks) + +let is_dead_store (live_vars, i) = + match i with + | Tacky.FunCall _ -> false + | Tacky.Store _ -> false + | _ -> ( + match Optimize_utils.get_dst i with + | Some (Var v) when not (Set.mem v live_vars) -> true + | _ -> false) + +let optimize aliased_vars cfg = + let entry_is_static = function + | v, Symbols.{ attrs = StaticAttr _; _ } -> Some v + | _ -> None + in + let static_vars = + Symbols.bindings () |> List.filter_map entry_is_static |> Set.of_list + in + let annotated_cfg = find_live_variables static_vars aliased_vars cfg in + let _ = debug_print ~extra_tag:"annotated" annotated_cfg in + let rewrite_block (idx, block) = + ( idx, + G. + { + block with + instructions = List.filter (not % is_dead_store) block.instructions; + } ) + in + let transformed_cfg = + { + annotated_cfg with + basic_blocks = List.map rewrite_block annotated_cfg.basic_blocks; + } + in + let _ = debug_print ~extra_tag:"transformed" transformed_cfg in + G.strip_annotations transformed_cfg diff --git a/lib/optimizations/dead_store_elim.mli b/lib/optimizations/dead_store_elim.mli new file mode 100644 index 0000000..9b719a2 --- /dev/null +++ b/lib/optimizations/dead_store_elim.mli @@ -0,0 +1,2 @@ +val optimize : + string Batteries.Set.t -> unit Cfg.TackyCfg.t -> unit Cfg.TackyCfg.t diff --git a/lib/optimizations/optimize.ml b/lib/optimizations/optimize.ml new file mode 100644 index 0000000..bafa6d2 --- /dev/null +++ b/lib/optimizations/optimize.ml @@ -0,0 +1,48 @@ +open Batteries +open Cfg +(* main optimization pipeline *) + +let rec optimize_fun ctx (opts : Settings.optimizations) = function + | [] -> [] + | instructions -> + let aliased_vars = Address_taken.analyze instructions in + (* run the optimization pipeline *) + let constant_folded = + if opts.constant_folding then Constant_folding.optimize ctx instructions + else instructions + in + let cfg = TackyCfg.instructions_to_cfg ctx constant_folded in + let cfg1 = + if opts.unreachable_code_elimination then + Unreachable_code_elim.optimize cfg + else cfg + in + let cfg2 = + if opts.copy_propagation then Copy_prop.optimize aliased_vars cfg1 + else cfg1 + in + let cfg3 = + if opts.dead_store_elimination then + Dead_store_elim.optimize aliased_vars cfg2 + else cfg2 + in + (* once we're done optimization, convert back to instruction list *) + let optimized_instructions = TackyCfg.cfg_to_instructions cfg3 in + + if List.equal Tacky.equal_instruction instructions optimized_instructions + then optimized_instructions + (* If we found any optimizations, run it again! *) + else optimize_fun ctx opts optimized_instructions + +let optimize opts src_file tacky_program = + let open Tacky in + let (Program tls) = tacky_program in + let handle_tl = function + | Function f -> + let ctx = + Context.{ filename = src_file; fun_name = f.name; params = f.params } + in + Function { f with body = optimize_fun ctx opts f.body } + | other_tl -> other_tl + in + Program (List.map handle_tl tls) diff --git a/lib/optimizations/optimize.mli b/lib/optimizations/optimize.mli new file mode 100644 index 0000000..50a252b --- /dev/null +++ b/lib/optimizations/optimize.mli @@ -0,0 +1 @@ +val optimize : Settings.optimizations -> string -> Tacky.t -> Tacky.t diff --git a/lib/optimizations/optimize_utils.ml b/lib/optimizations/optimize_utils.ml new file mode 100644 index 0000000..e178efd --- /dev/null +++ b/lib/optimizations/optimize_utils.ml @@ -0,0 +1,23 @@ +(* a tiny helper function to get the destination of a tacky instruction; useful for both copy propagation and dead store elimination *) +let get_dst = function + | Tacky.Copy { dst; _ } -> Some dst + | FunCall { dst; _ } -> dst + | Unary { dst; _ } -> Some dst + | Binary { dst; _ } -> Some dst + | SignExtend { dst; _ } -> Some dst + | ZeroExtend { dst; _ } -> Some dst + | DoubleToInt { dst; _ } -> Some dst + | DoubleToUInt { dst; _ } -> Some dst + | UIntToDouble { dst; _ } -> Some dst + | IntToDouble { dst; _ } -> Some dst + | Truncate { dst; _ } -> Some dst + | GetAddress { dst; _ } -> Some dst + | Load { dst; _ } -> Some dst + | AddPtr { dst; _ } -> Some dst + | CopyToOffset { dst; _ } -> Some (Var dst) + | CopyFromOffset { dst; _ } -> Some dst + | Store _ -> None + | Return _ | Jump _ | JumpIfZero _ | JumpIfNotZero _ | Label _ -> None + +let is_static v = + match (Symbols.get v).attrs with Symbols.StaticAttr _ -> true | _ -> false diff --git a/lib/optimizations/optimize_utils.mli b/lib/optimizations/optimize_utils.mli new file mode 100644 index 0000000..78828c2 --- /dev/null +++ b/lib/optimizations/optimize_utils.mli @@ -0,0 +1,2 @@ +val get_dst : Tacky.instruction -> Tacky.tacky_val option +val is_static : string -> bool \ No newline at end of file diff --git a/lib/optimizations/unreachable_code_elim.ml b/lib/optimizations/unreachable_code_elim.ml new file mode 100644 index 0000000..803c8fc --- /dev/null +++ b/lib/optimizations/unreachable_code_elim.ml @@ -0,0 +1,109 @@ +open Batteries + +module G = struct + include Cfg.TackyCfg +end + +let eliminate_unreachable_blocks (cfg : unit G.t) = + let rec dfs explored node_id = + (* we've already explored this node *) + if Set.mem node_id explored then explored + else + let explored' = Set.add node_id explored in + let succs = G.get_succs node_id cfg in + List.fold_left dfs explored' succs + in + let reachable_block_ids = dfs Set.empty G.Entry in + + let f (_, (blk : unit G.basic_block)) = + if Set.mem blk.id reachable_block_ids then true + else ( + (* Not reachable - remove edges and filter it out *) + List.iter (fun p -> G.remove_edge p blk.id cfg) blk.preds; + List.iter (fun s -> G.remove_edge blk.id s cfg) blk.succs; + false) + in + + let updated_blocks = List.filter f cfg.basic_blocks in + G.{ cfg with basic_blocks = updated_blocks } + +let eliminate_useless_jumps (cfg : unit G.t) = + let drop_last lst = List.take (List.length lst - 1) lst in + let update idx ((n, (blk : unit G.basic_block)) as numbered_block) = + if idx = List.length cfg.basic_blocks - 1 then + (* don't modify last block *) + numbered_block + else + match List.last blk.instructions with + | _, Tacky.(Jump _ | JumpIfZero _ | JumpIfNotZero _) -> + let _, default_succ = List.at cfg.basic_blocks (idx + 1) in + if List.for_all (fun nd -> nd = default_succ.id) blk.succs then + (* jump instruction is useless, we can drop it *) + (n, { blk with instructions = drop_last blk.instructions }) + else numbered_block + | _ -> numbered_block + in + + { cfg with basic_blocks = List.mapi update cfg.basic_blocks } + +let eliminate_useless_labels (cfg : unit G.t) = + let update idx ((n, (blk : unit G.basic_block)) as numbered_block) = + match blk.instructions with + | (_, Tacky.Label _) :: rest -> + let default_pred = + if idx = 0 then G.Entry + else (snd (List.at cfg.basic_blocks (idx - 1))).id + in + if List.for_all (fun nd -> nd = default_pred) blk.preds then + (n, { blk with instructions = rest }) + else numbered_block + | _ -> numbered_block + in + { cfg with basic_blocks = List.mapi update cfg.basic_blocks } + +let remove_empty_blocks (cfg : unit G.t) = + let remove (_, (blk : unit G.basic_block)) = + if blk.instructions = [] then + (* block is empty, remove it + but first update edges + *) + match (blk.preds, blk.succs) with + | [ pred ], [ succ ] -> + (* add edge from predecessor to successor, remove edges from either to blk *) + G.remove_edge pred blk.id cfg; + G.remove_edge blk.id succ cfg; + G.add_edge pred succ cfg; + false + | _ -> + failwith + "Empty block should have exactly one predecessor and one successor" + [@coverage off] + else true + in + { cfg with basic_blocks = List.filter remove cfg.basic_blocks } + +(* Print CFG if debug flag is on *) +let debug_print ~extra_tag cfg = + let _ = + if + !Settings.debug.dump_optimizations.unreachable_code_elimination + && Debug.is_dump_target G.(cfg.ctx) + then + (* no annotations, so annotation printer is no-op*) + let nop_printer _fmt () = () in + let tag = "unreachable." ^ extra_tag in + G.(print_graphviz tag nop_printer cfg) + in + (* return it so we can use this in pipeline*) + cfg + +let optimize cfg = + debug_print ~extra_tag:"pre" cfg + |> eliminate_unreachable_blocks + |> debug_print ~extra_tag:"post_unreachable_block_elim" + |> eliminate_useless_jumps + |> debug_print ~extra_tag:"post_jump_elim" + |> eliminate_useless_labels + |> debug_print ~extra_tag:"post_lbl_elim" + |> remove_empty_blocks + |> debug_print ~extra_tag:"post_empty_block_elim" diff --git a/lib/optimizations/unreachable_code_elim.mli b/lib/optimizations/unreachable_code_elim.mli new file mode 100644 index 0000000..998a3bc --- /dev/null +++ b/lib/optimizations/unreachable_code_elim.mli @@ -0,0 +1 @@ +val optimize : unit Cfg.TackyCfg.t -> unit Cfg.TackyCfg.t diff --git a/lib/settings.ml b/lib/settings.ml index 85cc31c..a1a8d1e 100644 --- a/lib/settings.ml +++ b/lib/settings.ml @@ -20,7 +20,40 @@ type extra_credit = | Nan | Union -let platform = ref OS_X (* default to OS X *) +let platform = ref OS_X (* default to OS_X *) let extra_credit_flags = ref [] let int_only = ref false -let debug = ref false + +type optimizations = { + constant_folding : bool; + dead_store_elimination : bool; + unreachable_code_elimination : bool; + copy_propagation : bool; +} + +type debug_options = { + (* dumping intermediate representations *) + dump_tacky : bool; + dump_asm : bool; + (* dumping extra info about specific optimizations*) + dump_optimizations : optimizations; + (* If specified, we dump optimization info only for this function; + * otherwise dump for all functions + * doesn't impact dump_tacky/dump_asm, which always dump the whole program *) + dump_fun : string option; +} + +let debug = + ref + { + dump_tacky = false; + dump_asm = false; + dump_optimizations = + { + constant_folding = false; + dead_store_elimination = false; + unreachable_code_elimination = false; + copy_propagation = false; + }; + dump_fun = None; + } diff --git a/lib/settings.mli b/lib/settings.mli index 2d8fa52..57c3998 100644 --- a/lib/settings.mli +++ b/lib/settings.mli @@ -1,7 +1,24 @@ type stage = Lex | Parse | Validate | Tacky | Codegen | Assembly | Obj | Executable type target = OS_X | Linux type extra_credit = Bitwise | Compound | Increment | Goto | Switch | Nan | Union +type optimizations = { + constant_folding : bool; + dead_store_elimination : bool; + unreachable_code_elimination : bool; + copy_propagation : bool; +} +type debug_options = { + (* dumping intermediate representations *) + dump_tacky : bool; + dump_asm : bool; + (* dumping extra info about specific optimizations*) + dump_optimizations : optimizations; + (* If specified, we dump optimization info only for this function; + * otherwise dump for all functions + * doesn't impact dump_tacky/dump_asm, which always dump the whole program *) + dump_fun : string option; +} val platform : target ref val extra_credit_flags : extra_credit list ref -val debug : bool ref val int_only: bool ref +val debug : debug_options ref diff --git a/lib/tacky.ml b/lib/tacky.ml index 137af6a..52f772b 100644 --- a/lib/tacky.ml +++ b/lib/tacky.ml @@ -1,6 +1,8 @@ [@@@coverage exclude_file] -type unary_operator = Complement | Negate | Not [@@deriving show] +open Cnums + +type unary_operator = Complement | Negate | Not [@@deriving show, eq, ord] type binary_operator = | Add @@ -19,13 +21,39 @@ type binary_operator = | LessOrEqual | GreaterThan | GreaterOrEqual -[@@deriving show] +[@@deriving show, eq, ord] -type tacky_val = Constant of Const.t | Var of string +(* we need a custom comparison function for constants to make sure that 0.0 and -0.0 don't compare equal *) +let const_compare a b = + match (a, b) with + | Const.ConstDouble d1, Const.ConstDouble d2 + when Float.is_nan d1 && Float.is_nan d2 -> + (* For these purposes, nans should compare equal *) + 0 + | Const.ConstDouble d1, Const.ConstDouble d2 when d1 = d2 -> + Float.compare (Float.copysign 1. d1) (Float.copysign 1. d2) + | _ -> Const.compare a b + +type tacky_val = + | Constant of + (Const.t + [@compare fun a b -> const_compare a b] + [@equal fun a b -> const_compare a b = 0]) + | Var of string +[@@deriving eq, ord] let show_tacky_val = function Constant c -> Const.show c | Var v -> v let pp_tacky_val fmt v = Format.pp_print_string fmt (show_tacky_val v) +(* TODO maybe this should be in a separate module? *) +let type_of_val = function + (* note: this reports the type of ConstChar as SChar instead of Char, doesn't matter in this context *) + | Constant c -> Const.type_of_const c + | Var v -> ( + try (Symbols.get v).t + with Not_found -> + failwith ("Internal error: " ^ v ^ " not in symbol table")) + type instruction = | Return of tacky_val option | SignExtend of { src : tacky_val; dst : tacky_val } @@ -59,7 +87,7 @@ type instruction = | JumpIfNotZero of tacky_val * string | Label of string | FunCall of { f : string; args : tacky_val list; dst : tacky_val option } -[@@deriving show { with_path = false }] +[@@deriving show { with_path = false }, eq, ord] type top_level = | Function of { diff --git a/lib/tacky_print.ml b/lib/tacky_print.ml index 19023de..3c2a185 100644 --- a/lib/tacky_print.ml +++ b/lib/tacky_print.ml @@ -15,7 +15,8 @@ let pp_unary_operator out = function | Negate -> Format.pp_print_string out "-" | Not -> Format.pp_print_string out "!" -let pp_binary_operator out = function +(* optional escape_brackets argument lets us escape < and > when instructions appear in HTML-style tables in graphviz *) +let pp_binary_operator ?(escape_brackets = false) out = function | Add -> Format.pp_print_string out "+" | Subtract -> Format.pp_print_string out "-" | Multiply -> Format.pp_print_string out "*" @@ -28,10 +29,18 @@ let pp_binary_operator out = function | BitwiseXor -> Format.pp_print_string out "^" | Equal -> Format.pp_print_string out "==" | NotEqual -> Format.pp_print_string out "!=" - | LessThan -> Format.pp_print_string out "<" - | LessOrEqual -> Format.pp_print_string out "<=" - | GreaterThan -> Format.pp_print_string out ">" - | GreaterOrEqual -> Format.pp_print_string out "<=" + | LessThan -> + let s = if escape_brackets then "<" else "<" in + Format.pp_print_string out s + | LessOrEqual -> + let s = if escape_brackets then "<=" else "<=" in + Format.pp_print_string out s + | GreaterThan -> + let s = if escape_brackets then ">" else ">" in + Format.pp_print_string out s + | GreaterOrEqual -> + let s = if escape_brackets then ">=" else ">=" in + Format.pp_print_string out s let const_to_string = function | Const.ConstInt i -> Int32.to_string i @@ -46,7 +55,7 @@ let pp_tacky_val out = function | Constant i -> Format.pp_print_string out (const_to_string i) | Var s -> Format.pp_print_string out s -let pp_instruction out = function +let pp_instruction ?(escape_brackets = false) out = function | Return None -> Format.pp_print_string out "Return" | Return (Some v) -> Format.fprintf out "Return(%a)" pp_tacky_val v | Unary { op; src; dst } -> @@ -54,7 +63,8 @@ let pp_instruction out = function pp_tacky_val src | Binary { op; src1; src2; dst } -> Format.fprintf out "%a = %a %a %a" pp_tacky_val dst pp_tacky_val src1 - pp_binary_operator op pp_tacky_val src2 + (pp_binary_operator ~escape_brackets) + op pp_tacky_val src2 | Copy { src; dst } -> Format.fprintf out "%a = %a" pp_tacky_val dst pp_tacky_val src | Jump s -> Format.fprintf out "Jump(%s)" s @@ -105,7 +115,8 @@ let pp_instruction out = function | CopyFromOffset { src; offset; dst } -> Format.fprintf out "%a = %s[offset = %d]" pp_tacky_val dst src offset -let pp_function_definition global name params out body = +let pp_function_definition ?(escape_brackets = false) global name params out + body = Format.pp_open_vbox out 0; if global then Format.pp_print_string out "global "; Format.fprintf out "%s(%a):" name @@ -113,13 +124,13 @@ let pp_function_definition global name params out body = params; Format.pp_print_break out 0 4; Format.pp_open_vbox out 0; - Format.pp_print_list pp_instruction out body; + Format.pp_print_list (pp_instruction ~escape_brackets) out body; Format.pp_close_box out (); Format.pp_close_box out () -let pp_tl out = function +let pp_tl ?(escape_brackets = false) out = function | Function { global; name; params; body } -> - pp_function_definition global name params out body + pp_function_definition ~escape_brackets global name params out body | StaticVariable { global; name; init; t } -> Format.pp_open_vbox out 0; if global then Format.pp_print_string out "global "; @@ -131,20 +142,22 @@ let pp_tl out = function Initializers.pp_static_init init; Format.pp_close_box out () -let pp_program out (Program tls) = +let pp_program ?(escape_brackets = false) out (Program tls) = Format.pp_open_vbox out 0; Format.pp_print_list ~pp_sep:(fun out () -> (* print _two_ newlines b/t top levels *) Format.pp_print_cut out (); Format.pp_print_cut out ()) - pp_tl out tls; + (pp_tl ~escape_brackets) out tls; Format.pp_close_box out (); Format.pp_print_newline out () (* flush *) -let debug_print_tacky src_filename tacky_prog = - if !Settings.debug then ( - let tacky_file = Filename.chop_extension src_filename ^ ".debug.tacky" in +let debug_print_tacky ~tag ~file tacky_prog = + if !Settings.debug.dump_tacky then ( + let tacky_file = + Printf.sprintf "%s.%s.tacky" (Filename.remove_extension file) tag + in let chan = open_out tacky_file in let formatter = Format.formatter_of_out_channel chan in pp_program formatter tacky_prog; diff --git a/lib/util/cnums.ml b/lib/util/cnums.ml index 23d6806..0ce5a4c 100644 --- a/lib/util/cnums.ml +++ b/lib/util/cnums.ml @@ -6,7 +6,20 @@ module Float = struct [@@@coverage off] - type t = float [@@deriving show] + type t = float [@@deriving show, eq] + + (* Override Compare to handle NaN correctly *) + module Compare = struct + (* Ugly batteries thing *) + type bat__compare_t = float + + let ( = ) = Stdlib.( = ) + let ( > ) = Stdlib.( > ) + let ( >= ) = Stdlib.( >= ) + let ( < ) = Stdlib.( < ) + let ( <= ) = Stdlib.( <= ) + let ( <> ) = Stdlib.( <> ) + end [@@@coverage on] end @@ -14,11 +27,13 @@ end module Int8 : NumLike = struct [@@@coverage off] - type t = int32 [@@deriving show] + type t = int32 [@@deriving show, eq, ord] [@@@coverage on] let zero = Int32.zero + let lognot = Int32.lognot + let rem = Int32.rem (* internal function to sign-or-zero-extend into upper bytes *) let reset_upper_bytes x = @@ -31,6 +46,25 @@ module Int8 : NumLike = struct let bitmask = 0xffffff00l in Int32.logor x bitmask + let neg = reset_upper_bytes % Int32.neg + + module Infix = struct + let ( + ) x y = reset_upper_bytes Int32.Infix.(x + y) + let ( - ) x y = reset_upper_bytes Int32.Infix.(x - y) + + (* don't need to explicitly wrap division; if x and y are in range of int8, result will be too *) + let ( / ) = Int32.Infix.( / ) + let ( * ) x y = reset_upper_bytes Int32.Infix.(x * y) + end + + module Compare = Int32.Compare + + let logand x y = reset_upper_bytes (Int32.logand x y) + let logor x y = reset_upper_bytes (Int32.logor x y) + let logxor x y = reset_upper_bytes (Int32.logxor x y) + let shift_left x y = reset_upper_bytes (Int32.shift_left x y) + let shift_right x y = reset_upper_bytes (Int32.shift_right x y) + let check_range x = if x > 127l || x < -128l then failwith "Out of range" else x @@ -52,20 +86,59 @@ module Int8 : NumLike = struct let to_string = Int32.to_string end +module MakeCompare (C : sig + type t + + val compare : t -> t -> int +end) : Compare with type t = C.t = struct + type t = C.t + + let ( = ) x y = C.compare x y = 0 + let ( <> ) x y = C.compare x y <> 0 + let ( > ) x y = C.compare x y > 0 + let ( >= ) x y = C.compare x y >= 0 + let ( < ) x y = C.compare x y < 0 + let ( <= ) x y = C.compare x y <= 0 +end + module UInt8 : NumLike = struct [@@@coverage off] - type t = int32 [@@deriving show] + type t = int32 [@@deriving show, eq] [@@@coverage on] let zero = Int32.zero + let lognot = Int32.lognot + let compare = Int32.unsigned_compare + let rem = Int32.unsigned_rem (* internal function to sign-or-zero-extend into upper bytes *) let reset_upper_bytes x = let bitmask = 0x000000ffl in Int32.logand x bitmask + let neg = reset_upper_bytes % Int32.neg + + module Infix = struct + let ( + ) x y = reset_upper_bytes Int32.Infix.(x + y) + let ( - ) x y = reset_upper_bytes Int32.Infix.(x - y) + let ( / ) x y = Int32.unsigned_div x y + let ( * ) x y = reset_upper_bytes Int32.Infix.(x * y) + end + + module Compare = MakeCompare (struct + type t = int32 + + let compare = Int32.unsigned_compare + end) + + let logand x y = reset_upper_bytes (Int32.logand x y) + let logor x y = reset_upper_bytes (Int32.logor x y) + let logxor x y = reset_upper_bytes (Int32.logxor x y) + let shift_left x y = reset_upper_bytes (Int32.shift_left x y) + let shift_right x y = reset_upper_bytes (Int32.shift_right_logical x y) + let of_int i = let x = Int32.of_int i in reset_upper_bytes x @@ -87,11 +160,34 @@ end module UInt32 : NumLike = struct [@@@coverage off] - type t = int32 [@@deriving show] + type t = int32 [@@deriving show, eq] [@@@coverage on] let zero = Int32.zero + let compare = Int32.unsigned_compare + let rem = Int32.unsigned_rem + let neg = Int32.neg + let lognot = Int32.lognot + let shift_left x y = Int32.shift_left x y + let shift_right x y = Int32.shift_right_logical x y + + module Infix = struct + let ( + ) x y = Int32.Infix.(x + y) + let ( - ) x y = Int32.Infix.(x - y) + let ( / ) = Int32.unsigned_div + let ( * ) x y = Int32.Infix.(x * y) + end + + module Compare = MakeCompare (struct + type t = int32 + + let compare = Int32.unsigned_compare + end) + + let logand x y = Int32.logand x y + let logor x y = Int32.logor x y + let logxor x y = Int32.logxor x y let of_int = Int32.of_int let to_int x = @@ -119,11 +215,34 @@ let%test "uint_type_conversion" = module UInt64 : NumLike = struct [@@@coverage off] - type t = int64 [@@deriving show] + type t = int64 [@@deriving show, eq] [@@@coverage on] let zero = Int64.zero + let compare = Int64.unsigned_compare + let neg = Int64.neg + let lognot = Int64.lognot + let rem = Int64.unsigned_rem + + module Infix = struct + let ( + ) x y = Int64.Infix.(x + y) + let ( - ) x y = Int64.Infix.(x - y) + let ( / ) = Int64.unsigned_div + let ( * ) x y = Int64.Infix.(x * y) + end + + module Compare = MakeCompare (struct + type t = int64 + + let compare = Int64.unsigned_compare + end) + + let logand x y = Int64.logand x y + let logor x y = Int64.logor x y + let logxor x y = Int64.logxor x y + let shift_left x y = Int64.shift_left x y + let shift_right x y = Int64.shift_right_logical x y let of_int = Int64.of_int let to_int x = diff --git a/lib/util/debug.ml b/lib/util/debug.ml new file mode 100644 index 0000000..51c1cda --- /dev/null +++ b/lib/util/debug.ml @@ -0,0 +1,18 @@ +open Context + +let is_dump_target ctx = + match !Settings.debug.dump_fun with + (* If we didn't specify which fun to dump info about, dump info for all of them *) + | None -> true + (* Otherwise, check if the current fun is the target fun *) + | Some target when ctx.fun_name = target -> true + | Some _other -> false + +(* prefix is some user-friendly label indicating e.g. current optimization, ext is file extension, including . *) +let mk_filename prefix ctx ext = + let base_filename = + ctx.filename |> Filename.chop_extension |> Filename.basename + in + Unique_ids.make_label + (Printf.sprintf "%s.%s.%s" prefix ctx.fun_name base_filename) + ^ ext diff --git a/lib/util/num_interfaces.ml b/lib/util/num_interfaces.ml index 41c704e..ba81f36 100644 --- a/lib/util/num_interfaces.ml +++ b/lib/util/num_interfaces.ml @@ -1,10 +1,49 @@ +module type Infix = sig + type t + + val ( + ) : t -> t -> t + val ( - ) : t -> t -> t + val ( / ) : t -> t -> t + val ( * ) : t -> t -> t +end + +module type Compare = sig + type t + + val ( = ) : t -> t -> bool + val ( <> ) : t -> t -> bool + val ( < ) : t -> t -> bool + val ( <= ) : t -> t -> bool + val ( > ) : t -> t -> bool + val ( >= ) : t -> t -> bool +end + (* basic interface supporting integer conversions, * satisfied by both our types and the ones provided by batteries *) -module type NumLike = sig +module type BasicNumLike = sig type t + module Infix : Infix with type t := t + module Compare : Compare with type t := t + val zero : t + val compare : t -> t -> int + val equal : t -> t -> bool + val lognot : t -> t + val neg : t -> t + val rem : t -> t -> t + + (* Bitwise operations *) + val logand : t -> t -> t + val logor : t -> t -> t + val logxor : t -> t -> t + val shift_left : t -> int -> t + val shift_right : t -> int -> t +end + +module type NumLike = sig + include BasicNumLike (* conversions *) val of_int32 : int32 -> t