From 41ab734709a03ce8d31cfccc0d93b75fb7a37fd9 Mon Sep 17 00:00:00 2001 From: Utku Melemetci <55263178+Yey007@users.noreply.github.com> Date: Wed, 15 May 2024 23:58:08 +0300 Subject: [PATCH] Add MLI Files (#43) * Centralize IR maps * Clean up and document regalloc * Move register module to its own file * Put things back in asm.ml and document * Add mli for ast * Add mli for parse_lex * Add mli for type * Add mli for branch_condition * Add mli for IR * Add mli for operand * Add mli for passes * Document variable * Add mli for cli * Add mli for meta * Add mli for utilities --- .ocamlinit | 9 --- README.md | 2 +- lib/backend/asm.ml | 36 ++------- lib/backend/asm.mli | 127 ++++++++++++++++++++++++++++++ lib/backend/asm_emit.ml | 8 +- lib/backend/asm_emit.mli | 2 +- lib/backend/liveliness.ml | 2 +- lib/backend/liveliness.mli | 2 - lib/backend/regalloc/regalloc.ml | 38 ++++----- lib/backend/regalloc/regalloc.mli | 16 +++- lib/dune | 2 +- lib/frontend/analysis.ml | 1 + lib/frontend/analysis.mli | 4 +- lib/frontend/ast.ml | 77 +----------------- lib/frontend/ast.mli | 33 ++++++++ lib/frontend/astType.ml | 63 +++++++++++++++ lib/frontend/ir_gen.ml | 2 +- lib/frontend/ir_gen.mli | 2 +- lib/frontend/parse_lex.ml | 19 ++--- lib/frontend/parse_lex.mli | 10 +++ lib/frontend/parser.mly | 8 +- lib/frontend/type.ml | 6 -- lib/frontend/type.mli | 42 ++++++++++ lib/ir/branch_condition.ml | 1 - lib/ir/branch_condition.mli | 10 +++ lib/ir/id.mli | 2 + lib/ir/ir.ml | 9 +-- lib/ir/ir.mli | 19 +++++ lib/ir/map/idMap.ml | 1 + lib/ir/map/idMap.mli | 1 + lib/ir/map/variableMap.ml | 1 + lib/ir/map/variableMap.mli | 2 + lib/ir/operand.mli | 17 ++++ lib/ir/pass.ml | 2 +- lib/ir/pass.mli | 17 +++- lib/ir/passes.ml | 17 ++-- lib/ir/passes.mli | 15 ++++ lib/ir/variable.mli | 8 ++ lib/user/cli.ml | 5 +- lib/user/cli.mli | 25 ++++++ lib/user/driver.ml | 3 +- lib/user/meta.ml | 7 +- lib/user/meta.mli | 22 ++++++ lib/util/platform.mli | 34 ++++++++ lib/util/rep_ok.ml | 2 - lib/util/rep_ok.mli | 3 + lib/util/util.ml | 36 --------- lib/util/util.mli | 51 ++++++++++++ test/test_liveliness.ml | 8 +- test/test_regalloc.ml | 28 ++++--- 50 files changed, 591 insertions(+), 266 deletions(-) create mode 100644 lib/backend/asm.mli create mode 100644 lib/frontend/ast.mli create mode 100644 lib/frontend/astType.ml create mode 100644 lib/frontend/parse_lex.mli create mode 100644 lib/frontend/type.mli create mode 100644 lib/ir/branch_condition.mli create mode 100644 lib/ir/ir.mli create mode 100644 lib/ir/map/idMap.ml create mode 100644 lib/ir/map/idMap.mli create mode 100644 lib/ir/map/variableMap.ml create mode 100644 lib/ir/map/variableMap.mli create mode 100644 lib/ir/operand.mli create mode 100644 lib/ir/passes.mli create mode 100644 lib/user/cli.mli create mode 100644 lib/user/meta.mli create mode 100644 lib/util/platform.mli create mode 100644 lib/util/rep_ok.mli create mode 100644 lib/util/util.mli diff --git a/.ocamlinit b/.ocamlinit index 5b39909..d10eb90 100644 --- a/.ocamlinit +++ b/.ocamlinit @@ -5,12 +5,3 @@ open X86ISTMB #install_printer Ast.pp_prog #install_printer Variable.pp #install_printer Id.pp - -let show_regalloc file = - let source = Util.read_file file in - let prog = Parse_lex.lex_and_parse source in - let cfg = Ir_gen.generate prog |> List.hd in - let liveliness = Liveliness.analysis_of cfg in - let ordering = InstrOrdering.make cfg in - print_endline (Cfg.to_string cfg); - Regalloc.allocate_for cfg liveliness ordering |> Regalloc.Ir.VariableMap.to_seq |> List.of_seq diff --git a/README.md b/README.md index 1167eb8..f81ed90 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ![CI Status](https://github.com/ethanuppal/cs3110_compiler/actions/workflows/ci.yaml/badge.svg) > "x86 is simple trust me bro" -> Last updated: 2024-05-15 13:54:50.602606 +> Last updated: 2024-05-15 16:42:39.370097 ``` $ ./main -h diff --git a/lib/backend/asm.ml b/lib/backend/asm.ml index 929a5db..7090a86 100644 --- a/lib/backend/asm.ml +++ b/lib/backend/asm.ml @@ -50,7 +50,8 @@ module Register = struct stack and usually needs special care. *) let callee_saved_data_registers = [ RBX; R12; R13; R14; R15 ] - let parameter_passing_registers = [ RDI; RSI; RDX; RCX; R8; R9 ] + let data_registers = callee_saved_data_registers @ callee_saved_data_registers + let parameter_registers = [ RDI; RSI; RDX; RCX; R8; R9 ] end module Operand = struct @@ -69,16 +70,7 @@ module Operand = struct | RelativeLabel rel_label -> "[rel " ^ rel_label ^ "]" end -module Label : sig - type t - - (** [make ~is_global:is_global, ~is_external:is_external name] is a label - named [name], global if and only if [is_global], and external if and only - if [is_external], but not both. *) - val make : is_global:bool -> is_external:bool -> string -> t - - val to_nasm : t -> string -end = struct +module Label = struct type t = { is_global : bool; is_external : bool; @@ -133,19 +125,7 @@ module Instruction = struct | Label label -> Label.to_nasm label end -module Section : sig - (** Values of type [t] are assembly sections. *) - type t - - (** `make name align` is new section with name [name] and alignment [align]. *) - val make : string -> int -> t - - (** [add section instr] adds [instr] to the end of [section]. *) - val add : t -> Instruction.t -> unit - - val add_all : t -> Instruction.t list -> unit - val to_nasm : t -> string -end = struct +module Section = struct type t = { name : string; align : int; @@ -168,13 +148,7 @@ end = struct |> String.concat "\n" end -module AssemblyFile : sig - type t - - val make : unit -> t - val add : t -> Section.t -> unit - val to_nasm : t -> string -end = struct +module AssemblyFile = struct type t = Section.t BatDynArray.t let make () = BatDynArray.make 16 diff --git a/lib/backend/asm.mli b/lib/backend/asm.mli new file mode 100644 index 0000000..436642b --- /dev/null +++ b/lib/backend/asm.mli @@ -0,0 +1,127 @@ +(** Contains functionality for dealing with x86-64 registers. *) +module Register : sig + (** Represents an x86-64 register. *) + type t = + | RAX + | RBX + | RCX + | RDX + | RBP + | RSP + | RSI + | RDI + | R8 + | R9 + | R10 + | R11 + | R12 + | R13 + | R14 + | R15 + + (** [to_nasm reg] is the NASM representation of [reg]. *) + val to_nasm : t -> string + + (** Provides an arbitrary ordering of registers. *) + val compare : t -> t -> int + + (** Caller saved registers. rsp is excluded because it is used to manage the + stack and usually needs special care. *) + val caller_saved_data_registers : t list + + (** Caller saved registers. rbp is excluded because it is used to manage the + stack and usually needs special care. *) + val callee_saved_data_registers : t list + + (** All register that can be used to store general data. *) + val data_registers : t list + + (** Register that can be used to pass parameters, in order of first parameter + to last parameter according to the System V ABI. *) + val parameter_registers : t list +end + +(** Contains functionality for dealing with x86-64 operands. *) +module Operand : sig + (** Represents an operand to an instruction. *) + type t = + | Register of Register.t + | Deref of Register.t * int + | Intermediate of int + | Label of string + | RelativeLabel of string + + (** [to_nasm op] is the NASM representation of [op]. *) + val to_nasm : t -> string +end + +(** Contains functionality for dealing with NASM block labels. *) +module Label : sig + type t + + (** [make ~is_global:is_global, ~is_external:is_external name] is a label + named [name], global if and only if [is_global], and external if and only + if [is_external], but not both. *) + val make : is_global:bool -> is_external:bool -> string -> t + + (** [to_nasm label] is the NASM representation of [label]. *) + val to_nasm : t -> string +end + +(** Contains functionality for dealing with x86-64 instructions. *) +module Instruction : sig + (** Represents and x86-64 instruction. *) + type t = + | Mov of Operand.t * Operand.t + | Add of Operand.t * Operand.t + | Sub of Operand.t * Operand.t + | IMul of Operand.t + | Push of Operand.t + | Pop of Operand.t + | Call of Operand.t + | Cmp of Operand.t * Operand.t + | Jmp of Operand.t + | Je of Operand.t + | Jne of Operand.t + | Ret + | Syscall + | Label of Label.t + + (** [to_nasm instr] is the NASM representation of [instr]. *) + val to_nasm : t -> string +end + +(** Contains functionality for creating assembly sections. *) +module Section : sig + (** Values of type [t] are assembly sections. *) + type t + + (** `make name align` is new section with name [name] and alignment [align]. *) + val make : string -> int -> t + + (** [add section instr] adds [instr] to the end of [section]. *) + val add : t -> Instruction.t -> unit + + (** [add_all section instructions] adds all instructions in [instructions] to + [section] in order. It is equivalent to calling + [List.iter (Section.add section) instructions]*) + val add_all : t -> Instruction.t list -> unit + + (** [to_nasm section] is the NASM representation of [section]. *) + val to_nasm : t -> string +end + +(** Contains functionality for creating assembly files. *) +module AssemblyFile : sig + (** Represents an assembly file containing many assembly sections. *) + type t + + (** [make ()] is an empty assembly file with no sections. *) + val make : unit -> t + + (** [add file section] adds [section] to [file]. *) + val add : t -> Section.t -> unit + + (** [to_nasm file] is the NASM code for [file]. *) + val to_nasm : t -> string +end diff --git a/lib/backend/asm_emit.ml b/lib/backend/asm_emit.ml index e5e7498..8e43b3b 100644 --- a/lib/backend/asm_emit.ml +++ b/lib/backend/asm_emit.ml @@ -4,7 +4,7 @@ module ParameterPassingContext = struct mutable regs : Asm.Register.t list; } - let make () = { pos = 0; regs = Asm.Register.parameter_passing_registers } + let make () = { pos = 0; regs = Asm.Register.parameter_registers } let get_next ctx = if List.is_empty ctx.regs then ( @@ -35,7 +35,7 @@ let align_offset bytes = if amount_over = 0 then 0 else stack_alignment - amount_over let emit_var regalloc var = - match Ir.VariableMap.find regalloc var with + match VariableMap.find regalloc var with | Regalloc.Register reg -> Asm.Operand.Register reg | Spill i -> Asm.Operand.Deref (RBP, (-var_size * i) - var_size) @@ -70,7 +70,7 @@ let emit_restore_registers text registers = let emit_call text regalloc name args = emit_save_registers text Asm.Register.caller_saved_data_registers; let param_moves = - Util.zip_shortest args Asm.Register.parameter_passing_registers + Util.zip_shortest args Asm.Register.parameter_registers |> List.map (fun (arg, reg) -> Asm.Instruction.Mov (Register reg, emit_oper regalloc arg)) in @@ -143,7 +143,7 @@ let emit_preamble ~text = let emit_cfg ~text cfg regalloc = let max_spill = - Ir.VariableMap.fold + VariableMap.fold (fun _var alloc acc -> match alloc with | Regalloc.Spill count -> max count acc diff --git a/lib/backend/asm_emit.mli b/lib/backend/asm_emit.mli index cd62aa2..1156190 100644 --- a/lib/backend/asm_emit.mli +++ b/lib/backend/asm_emit.mli @@ -5,4 +5,4 @@ val emit_preamble : text:Asm.Section.t -> unit (** [emit ~text:text cfg regalloc] emits the function [cfg] with register allocation [regalloc] into the assembly code section [text]. *) val emit_cfg : - text:Asm.Section.t -> Cfg.t -> Regalloc.allocation Ir.VariableMap.t -> unit + text:Asm.Section.t -> Cfg.t -> Regalloc.allocation VariableMap.t -> unit diff --git a/lib/backend/liveliness.ml b/lib/backend/liveliness.ml index 452abf1..c3e591f 100644 --- a/lib/backend/liveliness.ml +++ b/lib/backend/liveliness.ml @@ -67,7 +67,7 @@ module BasicBlockAnalysis = struct let to_string analysis = let analysis = rep_ok analysis in "BasicBlockAnalysis {" - ^ (Seq.init (Array.length analysis) id + ^ (Seq.init (Array.length analysis) Fun.id |> List.of_seq |> List.map (fun i -> "\n ir[" ^ string_of_int i ^ "] <=> {live_in = " diff --git a/lib/backend/liveliness.mli b/lib/backend/liveliness.mli index 75b6822..1c5a4fa 100644 --- a/lib/backend/liveliness.mli +++ b/lib/backend/liveliness.mli @@ -1,5 +1,3 @@ -open Util - (** A value of type [VariableSet.t] is a set of IR variables. *) module VariableSet : sig include Set.S with type elt = Variable.t diff --git a/lib/backend/regalloc/regalloc.ml b/lib/backend/regalloc/regalloc.ml index d12fe91..4622a16 100644 --- a/lib/backend/regalloc/regalloc.ml +++ b/lib/backend/regalloc/regalloc.ml @@ -1,5 +1,3 @@ -open Util - (* TODO: standardize instruction id? *) type instr_id = Id.t * int @@ -19,15 +17,9 @@ type allocation = module BBAnalysis = Liveliness.BasicBlockAnalysis -(* todo make this less awkward why are registers just listed here maybe pass em - in or smth *) -let registers = - let open Asm.Register in - [ RAX; RBX; RCX; RDX; RSI; RDI; R8; R9; R10; R11; R12; R13; R14; R15 ] - let live_intervals (cfg : Cfg.t) (liveliness : BBAnalysis.t IdMap.t) (ordering : InstrOrdering.t) = - let tbl = Ir.VariableMap.create 16 in + let tbl = VariableMap.create 16 in let expand_interval original live_id = let cmp = InstrOrdering.compare ordering in @@ -39,13 +31,13 @@ let live_intervals (cfg : Cfg.t) (liveliness : BBAnalysis.t IdMap.t) let update_table instr_id used_set = Liveliness.VariableSet.iter (fun live -> - let current_opt = Ir.VariableMap.find_opt tbl live in + let current_opt = VariableMap.find_opt tbl live in let new_interval = match current_opt with | None -> { start = instr_id; stop = instr_id } | Some current -> expand_interval current instr_id in - Ir.VariableMap.replace tbl live new_interval) + VariableMap.replace tbl live new_interval) used_set in @@ -65,18 +57,17 @@ let live_intervals (cfg : Cfg.t) (liveliness : BBAnalysis.t IdMap.t) done) cfg; - Ir.VariableMap.to_seq tbl |> List.of_seq + VariableMap.to_seq tbl |> List.of_seq (* Algorithm source: https://en.wikipedia.org/wiki/Register_allocation#Pseudocode *) -let linear_scan (intervals : (Variable.t * interval) list) - (ordering : InstrOrdering.t) = +let linear_scan intervals ordering registers = let compare_instr_id = InstrOrdering.compare ordering in let compare_pair_start (_, i1) (_, i2) = compare_instr_id i1.start i2.start in let compare_pair_end (_, i1) (_, i2) = compare_instr_id i1.stop i2.stop in let sorted_intervals = List.sort compare_pair_start intervals in - let assigned_alloc : allocation Ir.VariableMap.t = Ir.VariableMap.create 4 in + let assigned_alloc : allocation VariableMap.t = VariableMap.create 4 in let module RegSet = Set.Make (Asm.Register) in let free_registers : RegSet.t ref = ref (RegSet.of_list registers) in @@ -97,7 +88,7 @@ let linear_scan (intervals : (Variable.t * interval) list) (fun (var, interval) -> let keep = compare_instr_id interval.stop current.start >= 0 in (if not keep then - let alloc = Ir.VariableMap.find assigned_alloc var in + let alloc = VariableMap.find assigned_alloc var in match alloc with | Register r -> free_registers := RegSet.add r !free_registers | Spill _ -> failwith "Interval in active cannot be spilled"); @@ -110,15 +101,14 @@ let linear_scan (intervals : (Variable.t * interval) list) if compare_instr_id spill_interval.stop interval.stop > 0 then ( (* spill guaranteed to be assigned an actual register *) - let alloc = Ir.VariableMap.find assigned_alloc spill_var in + let alloc = VariableMap.find assigned_alloc spill_var in assert ( match alloc with | Spill _ -> false | _ -> true); - Ir.VariableMap.replace assigned_alloc var alloc; - Ir.VariableMap.replace assigned_alloc spill_var - (Spill (next_spill_loc ())); + VariableMap.replace assigned_alloc var alloc; + VariableMap.replace assigned_alloc spill_var (Spill (next_spill_loc ())); (* this sucks. can we maybe keep active in reverse order? *) BatRefList.Index.remove_at active (BatRefList.length active - 1); @@ -126,7 +116,7 @@ let linear_scan (intervals : (Variable.t * interval) list) (* add_sort is buggy... TODO: new impl *) BatRefList.push active (var, interval); BatRefList.sort ~cmp:compare_pair_end active) - else Ir.VariableMap.replace assigned_alloc var (Spill (next_spill_loc ())) + else VariableMap.replace assigned_alloc var (Spill (next_spill_loc ())) in List.iter @@ -135,7 +125,7 @@ let linear_scan (intervals : (Variable.t * interval) list) match RegSet.choose_opt !free_registers with | Some register -> free_registers := RegSet.remove register !free_registers; - Ir.VariableMap.replace assigned_alloc var (Register register); + VariableMap.replace assigned_alloc var (Register register); BatRefList.push active (var, interval); BatRefList.sort ~cmp:compare_pair_end active | None -> spill_at_interval (var, interval)) @@ -143,6 +133,6 @@ let linear_scan (intervals : (Variable.t * interval) list) assigned_alloc -let allocate_for cfg liveliness ordering = +let allocate_for cfg registers liveliness ordering = let vars_with_intervals = live_intervals cfg liveliness ordering in - linear_scan vars_with_intervals ordering + linear_scan vars_with_intervals ordering registers diff --git a/lib/backend/regalloc/regalloc.mli b/lib/backend/regalloc/regalloc.mli index 8874a0b..3481c05 100644 --- a/lib/backend/regalloc/regalloc.mli +++ b/lib/backend/regalloc/regalloc.mli @@ -1,13 +1,21 @@ -open Util - +(** Represents the hardware allocated for a variable. [Register reg] means the + variable has been allocated to a Asm.Register. [Spill i] means the variable + is to be spilled to the stack. The location [i] will be unique and count up + from zero for an allocation scheme produced by [allocate_for]. *) type allocation = | Register of Asm.Register.t | Spill of int -val registers : Asm.Register.t list +(** [allocate_for cfg registers liveliness ordering] allocates a register in + [registers] (or a spill location) to each variable in [cfg] based on the + liveliness analysis of [cfg] ([liveliness]) and an arbitrary ordering of the + instructions in [cfg] ([ordering]). + Requires that [liveliness] and [ordering] were both produced from [cfg] in + the state that it is being passed in. *) val allocate_for : Cfg.t -> + Asm.Register.t list -> Liveliness.BasicBlockAnalysis.t IdMap.t -> InstrOrdering.t -> - allocation Ir.VariableMap.t + allocation VariableMap.t diff --git a/lib/dune b/lib/dune index 12b9041..d84fec8 100644 --- a/lib/dune +++ b/lib/dune @@ -3,7 +3,7 @@ (library (public_name x86ISTMB) (name x86ISTMB) - (libraries batteries menhirLib ANSITerminal) + (libraries batteries menhirLib) (preprocess (pps ppx_inline_test)) (instrumentation diff --git a/lib/frontend/analysis.ml b/lib/frontend/analysis.ml index 62caeac..6283b7c 100644 --- a/lib/frontend/analysis.ml +++ b/lib/frontend/analysis.ml @@ -1,3 +1,4 @@ +open AstType open Ast type analysis_error_info = diff --git a/lib/frontend/analysis.mli b/lib/frontend/analysis.mli index e171946..3b211f7 100644 --- a/lib/frontend/analysis.mli +++ b/lib/frontend/analysis.mli @@ -12,11 +12,11 @@ exception AnalyzerError of { info : analysis_error_info; msg : string option; - ast : (Ast.expr, Ast.stmt) Either.t; + ast : (AstType.expr, AstType.stmt) Either.t; } (** After [infer prog], the contents of [prog] will have resolved types based on inference rules. @raise AnalyzerError on failure. *) -val infer : Ast.prog -> unit +val infer : AstType.prog -> unit diff --git a/lib/frontend/ast.ml b/lib/frontend/ast.ml index 1af8250..e72eb18 100644 --- a/lib/frontend/ast.ml +++ b/lib/frontend/ast.ml @@ -1,72 +1,5 @@ -(* Future: https://v2.ocaml.org/manual/gadts-tutorial.html *) -(* This is good for enforcing things at the type level. *) +open AstType -(** An arithmetic operation. *) -type op = - | Plus - | Minus - | Times - | Divide - | Mod - | Equals - | BitAnd - -(** An expression can be evaluated to a value. *) -type expr = - | Var of { - name : string; - mutable ty : Type.t option; - } - | ConstInt of int - | ConstBool of bool - | Infix of { - lhs : expr; - op : op; - rhs : expr; - mutable ty : Type.t option; - } - | Prefix of { - op : op; - rhs : expr; - mutable ty : Type.t option; - } - | Call of { - name : string list; - args : expr list; - mutable ty : Type.t option; - } - -(** A statement can be executed. *) -and stmt = - | If of { - cond : expr; - body : stmt list; - } - | ExprStatement of expr - | Declaration of { - name : string; - hint : Type.t option; - expr : expr; - } - | Assignment of string * expr - | Function of { - name : string; - params : (string * Type.t) list; - return : Type.t; - body : stmt list; - } - | Print of expr - | Return of expr option - | Namespace of { - name : string; - contents : stmt list; - } - -(** A program is a series of statements. *) -type prog = stmt list - -(** [type_of_expr expr] is the type of [expr] to the extent that it is currently - resolved. *) let type_of_expr = function | Var { name = _; ty } -> ty | ConstInt _ -> Some Type.int_prim_type @@ -75,8 +8,6 @@ let type_of_expr = function | Prefix { op = _; rhs = _; ty } -> ty | Call { name = _; args = _; ty } -> ty -(** [expr_is_const expr] if and only if [expr] is a constant (i.e., cannot have - an address taken of it). *) let expr_is_const = function | ConstInt _ | ConstBool _ -> true | _ -> false @@ -153,11 +84,7 @@ let stmt_to_string = in stmt_to_string_aux "" -(** TODO: to string functions *) -let pp_op fmt = - let open Util in - op_to_string >> Format.pp_print_string fmt - +let pp_op = Util.pp_of op_to_string let pp_expr = Util.pp_of expr_to_string let pp_stmt = Util.pp_of stmt_to_string diff --git a/lib/frontend/ast.mli b/lib/frontend/ast.mli new file mode 100644 index 0000000..28e3983 --- /dev/null +++ b/lib/frontend/ast.mli @@ -0,0 +1,33 @@ +open AstType + +(** [type_of_expr expr] is the type of [expr] to the extent that it is currently + resolved. *) +val type_of_expr : expr -> Type.t option + +(** [expr_is_const expr] if and only if [expr] is a constant (i.e., cannot have + an address taken of it). *) +val expr_is_const : expr -> bool + +(** [op_to_string op] is a string representation of [op]. *) +val op_to_string : op -> string + +(** [expr_to_string expr] is a string representation of [expr]. *) +val expr_to_string : expr -> string + +(** [stmt_to_string stmt] is a string representation of [stmt]. *) +val stmt_to_string : stmt -> string + +(** [pp_op fmt op] pretty prints [op] to [fmt] in the same manner as + [op_to_string]. *) +val pp_op : Format.formatter -> op -> unit + +(** [pp_expr fmt expr] pretty prints [expr] to [fmt] in the same manner as + [expr_to_string]. *) +val pp_expr : Format.formatter -> expr -> unit + +(** [pp_stmt fmt stmt] pretty prints [stmt] to [fmt] in the same manner as + [stmt_to_string]. *) +val pp_stmt : Format.formatter -> stmt -> unit + +(** [pp_prog fmt prog] pretty prints an entire program [prog] to [fmt]. *) +val pp_prog : Format.formatter -> stmt list -> unit diff --git a/lib/frontend/astType.ml b/lib/frontend/astType.ml new file mode 100644 index 0000000..c59d6d2 --- /dev/null +++ b/lib/frontend/astType.ml @@ -0,0 +1,63 @@ +(** An arithmetic operation. *) +type op = + | Plus + | Minus + | Times + | Divide + | Mod + | Equals + | BitAnd + +(** An expression can be evaluated to a value. *) +type expr = + | Var of { + name : string; + mutable ty : Type.t option; + } + | ConstInt of int + | ConstBool of bool + | Infix of { + lhs : expr; + op : op; + rhs : expr; + mutable ty : Type.t option; + } + | Prefix of { + op : op; + rhs : expr; + mutable ty : Type.t option; + } + | Call of { + name : string list; + args : expr list; + mutable ty : Type.t option; + } + +(** A statement can be executed. *) +and stmt = + | If of { + cond : expr; + body : stmt list; + } + | ExprStatement of expr + | Declaration of { + name : string; + hint : Type.t option; + expr : expr; + } + | Assignment of string * expr + | Function of { + name : string; + params : (string * Type.t) list; + return : Type.t; + body : stmt list; + } + | Print of expr + | Return of expr option + | Namespace of { + name : string; + contents : stmt list; + } + +(** A program is a series of statements. *) +type prog = stmt list diff --git a/lib/frontend/ir_gen.ml b/lib/frontend/ir_gen.ml index e23262c..4d3cc00 100644 --- a/lib/frontend/ir_gen.ml +++ b/lib/frontend/ir_gen.ml @@ -1,4 +1,4 @@ -open Ast +open AstType open Util exception UnboundVariable of { name : string } diff --git a/lib/frontend/ir_gen.mli b/lib/frontend/ir_gen.mli index 4fd151c..858bc94 100644 --- a/lib/frontend/ir_gen.mli +++ b/lib/frontend/ir_gen.mli @@ -1,3 +1,3 @@ (** [generate ast] is a list of control flow graphs, where each control flow graph represents one top-level function. *) -val generate : Ast.prog -> Cfg.t list +val generate : AstType.prog -> Cfg.t list diff --git a/lib/frontend/parse_lex.ml b/lib/frontend/parse_lex.ml index 58a774a..c87d196 100644 --- a/lib/frontend/parse_lex.ml +++ b/lib/frontend/parse_lex.ml @@ -1,15 +1,15 @@ exception ParserError of string let function_auto_unit_return = function - | Ast.Function { name; params; return; body } -> + | AstType.Function { name; params; return; body } -> let last_stmt_is_return = if List.is_empty body then false else match List.rev body |> List.hd with - | Ast.Return _ -> true + | AstType.Return _ -> true | _ -> false in - Ast.Function + AstType.Function { name; params; @@ -21,12 +21,6 @@ let function_auto_unit_return = function } | other -> other -(** [lex_and_parse ~filename:filename input] is the list of statements - represented by the source code string [input]. Optionally, - [~filename:filename] can be passed to indicate that the path of the source - was [filename]; by default, it is [""]. - - @raise ParserError on parsing error. *) let lex_and_parse ?(filename = "") input = let syntax_error_msg lexbuf = let pos = Lexing.lexeme_start_p lexbuf in @@ -44,9 +38,10 @@ let lex_and_parse ?(filename = "") input = List.map (fun stmt -> match stmt with - | Ast.Namespace { name; contents } -> - Ast.Namespace + | AstType.Namespace { name; contents } -> + AstType.Namespace { name; contents = List.map function_auto_unit_return contents } - | Ast.Function func -> function_auto_unit_return (Ast.Function func) + | AstType.Function func -> + function_auto_unit_return (AstType.Function func) | other -> other) prog diff --git a/lib/frontend/parse_lex.mli b/lib/frontend/parse_lex.mli new file mode 100644 index 0000000..0db5cae --- /dev/null +++ b/lib/frontend/parse_lex.mli @@ -0,0 +1,10 @@ +(** An error indicating that parsing failed with an attached message. *) +exception ParserError of string + +(** [lex_and_parse ~filename:filename input] is the list of statements + represented by the source code string [input]. Optionally, + [~filename:filename] can be passed to indicate that the path of the source + was [filename]; by default, it is [""]. + + @raise ParserError on parsing error. *) +val lex_and_parse : ?filename:string -> string -> AstType.stmt list diff --git a/lib/frontend/parser.mly b/lib/frontend/parser.mly index 0f73a4a..ae32f91 100644 --- a/lib/frontend/parser.mly +++ b/lib/frontend/parser.mly @@ -1,5 +1,5 @@ %{ - open Ast +open AstType %} %token INT_LIT @@ -14,10 +14,10 @@ %left PLUS MINUS %left TIMES -%start main +%start main -%type stmt -%type expr +%type stmt +%type expr %type ty %% diff --git a/lib/frontend/type.ml b/lib/frontend/type.ml index cdc4842..14e1df3 100644 --- a/lib/frontend/type.ml +++ b/lib/frontend/type.ml @@ -1,11 +1,9 @@ module Primitive = struct - (** [t] represents a primitive type. *) type t = | Int | Bool | Unit - (** [to_string prim_type] is the string representation of [prim_type]. *) let to_string = function | Int -> "Int" | Bool -> "Bool" @@ -16,7 +14,6 @@ type stmt_type = | Terminal | Nonterminal -(** [t] represents a type. *) type t = | Prim of Primitive.t | Pointer of t @@ -26,7 +23,6 @@ type t = return : t; } -(** [to_string ty] is the string representation of [ty]. *) let rec to_string = function | Prim prim -> Primitive.to_string prim | Pointer ty -> to_string ty ^ "*" @@ -40,8 +36,6 @@ let int_prim_type = Prim Int let bool_prim_type = Prim Bool let unit_prim_type = Prim Unit -(** [deref ty] is [Some ty'] if [ty = Pointer ty'] for some [ty'] and [None] - otherwise. *) let deref = function | Pointer ty' -> Some ty' | _ -> None diff --git a/lib/frontend/type.mli b/lib/frontend/type.mli new file mode 100644 index 0000000..1f69a2b --- /dev/null +++ b/lib/frontend/type.mli @@ -0,0 +1,42 @@ +(** Module for primitive types. *) +module Primitive : sig + (** [t] represents a primitive type. *) + type t = + | Int + | Bool + | Unit + + (** [to_string prim_type] is the string representation of [prim_type]. *) + val to_string : t -> string +end + +(** Represents whether a statement is terminal or nonterminal. *) +type stmt_type = + | Terminal + | Nonterminal + +(** [t] represents a type. *) +type t = + | Prim of Primitive.t + | Pointer of t + | Var of string + | FunctionType of { + params : t list; + return : t; + } + +(** [to_string ty] is the string representation of [ty]. *) +val to_string : t -> string + +(** A type representing an integer. *) +val int_prim_type : t + +(** A type representing a boolean. *) +val bool_prim_type : t + +(** A type representing a unit. *) +val unit_prim_type : t + +(** [deref ty] is [Some ty'] if [ty = Pointer ty'] for some [ty'] and [None] + otherwise. *) +val deref : t -> t option diff --git a/lib/ir/branch_condition.ml b/lib/ir/branch_condition.ml index 61ecd49..8fca843 100644 --- a/lib/ir/branch_condition.ml +++ b/lib/ir/branch_condition.ml @@ -3,7 +3,6 @@ type t = | Never | Conditional of Operand.t -(* TODO: pretty print *) let to_string = function | Always -> "true" | Conditional (Constant cond) when cond <> 0 -> "true" diff --git a/lib/ir/branch_condition.mli b/lib/ir/branch_condition.mli new file mode 100644 index 0000000..ea11c18 --- /dev/null +++ b/lib/ir/branch_condition.mli @@ -0,0 +1,10 @@ +(** Represents a condition for branching. [Always] means the branch should + always be taken, [Never] means the branch should never be taken, and + [Conditional op] means the branch should be taken only if [op] is zero. *) +type t = + | Always + | Never + | Conditional of Operand.t + +(** [to_string cond] is a string representation of [cond]. *) +val to_string : t -> string diff --git a/lib/ir/id.mli b/lib/ir/id.mli index ef13fd8..59e703a 100644 --- a/lib/ir/id.mli +++ b/lib/ir/id.mli @@ -20,8 +20,10 @@ val compare : id -> id -> int (** [hash id] is a hash of [id]. *) val hash : id -> int +(** [pp fmt id] pretty prints [id] to [fmt] *) val pp : Format.formatter -> id -> unit +(** [Gen] handles the generation of unique ids. *) module Gen : sig (** Values of type [t] are unique-identifier generators. *) type t diff --git a/lib/ir/ir.ml b/lib/ir/ir.ml index 2d06b31..29bf451 100644 --- a/lib/ir/ir.ml +++ b/lib/ir/ir.ml @@ -1,8 +1,3 @@ -type constant = int - -module VariableMap = Hashtbl.Make (Variable) - -(** The kabIR for x86istmb. *) type t = | Assign of Variable.t * Operand.t | Add of Variable.t * Operand.t * Operand.t @@ -15,8 +10,6 @@ type t = | GetParam of Variable.t | Return of Operand.t option -(** [kill_of ir] is [Some var] if [var] is assigned to in [ir] and [None] - otherwise. *) let kill_of = function | Assign (var, _) | Add (var, _, _) @@ -53,7 +46,7 @@ let to_string = (args |> List.map Operand.to_string |> String.concat ",") | GetParam var -> sprintf "%s = " (Variable.to_string var) | Return op -> - sprintf "return%s" + sprintf "return %s" (match op with | Some op -> " " ^ Operand.to_string op | None -> "") diff --git a/lib/ir/ir.mli b/lib/ir/ir.mli new file mode 100644 index 0000000..645f3f1 --- /dev/null +++ b/lib/ir/ir.mli @@ -0,0 +1,19 @@ +(** The intermediate representation for x86istmb. *) +type t = + | Assign of Variable.t * Operand.t + | Add of Variable.t * Operand.t * Operand.t + | Sub of Variable.t * Operand.t * Operand.t + | Ref of Variable.t * Operand.t + | Deref of Variable.t * Operand.t + | TestEqual of Variable.t * Operand.t * Operand.t + | DebugPrint of Operand.t + | Call of Variable.t * string list * Operand.t list + | GetParam of Variable.t + | Return of Operand.t option + +(** [kill_of ir] is [Some var] if [var] is assigned to in [ir] and [None] + otherwise. *) +val kill_of : t -> Variable.t option + +(** [to_string ir] is a string representation of the IRk instruction [ir]. *) +val to_string : t -> string diff --git a/lib/ir/map/idMap.ml b/lib/ir/map/idMap.ml new file mode 100644 index 0000000..99b2cab --- /dev/null +++ b/lib/ir/map/idMap.ml @@ -0,0 +1 @@ +include Hashtbl.Make (Id) diff --git a/lib/ir/map/idMap.mli b/lib/ir/map/idMap.mli new file mode 100644 index 0000000..9be8eba --- /dev/null +++ b/lib/ir/map/idMap.mli @@ -0,0 +1 @@ +include Hashtbl.S with type key = Id.t diff --git a/lib/ir/map/variableMap.ml b/lib/ir/map/variableMap.ml new file mode 100644 index 0000000..3b4bc56 --- /dev/null +++ b/lib/ir/map/variableMap.ml @@ -0,0 +1 @@ +include Hashtbl.Make (Variable) diff --git a/lib/ir/map/variableMap.mli b/lib/ir/map/variableMap.mli new file mode 100644 index 0000000..51484f3 --- /dev/null +++ b/lib/ir/map/variableMap.mli @@ -0,0 +1,2 @@ + +include Hashtbl.S with type key = Variable.t \ No newline at end of file diff --git a/lib/ir/operand.mli b/lib/ir/operand.mli new file mode 100644 index 0000000..2932f4f --- /dev/null +++ b/lib/ir/operand.mli @@ -0,0 +1,17 @@ +(** Represents the operand of an IR instruction. *) +type t = + | Variable of Variable.t + | Constant of int + +(** [make_var var] is a variable operand representing the variable [var]. *) +val make_var : Variable.t -> t + +(** [make_const const] is a constant operand representing the constant [const]. *) +val make_const : int -> t + +(** [to_string operand] is the string representation of [operand]. *) +val to_string : t -> string + +(** [var_of_opt operand] is [Some var] if [operand] is a variable operand, and + [None] otherwise. *) +val var_of_opt : t -> Variable.t option diff --git a/lib/ir/pass.ml b/lib/ir/pass.ml index a2ff6d9..6b3d0cf 100644 --- a/lib/ir/pass.ml +++ b/lib/ir/pass.ml @@ -19,6 +19,6 @@ let execute pass bb liveliness = in execute_aux bb pass -module type PASS = sig +module type Sig = sig val pass : t end diff --git a/lib/ir/pass.mli b/lib/ir/pass.mli index 650f9b0..81eb8c7 100644 --- a/lib/ir/pass.mli +++ b/lib/ir/pass.mli @@ -1,11 +1,26 @@ +(** Represents an optimization pass that can be executed on IR. *) type t +(** [make f] is a basic pass that runs a basic block and its liveliness analysis + through [f]. *) val make : (Basic_block.t * Liveliness.BasicBlockAnalysis.t -> unit) -> t + +(** [sequence p1 p2] is a pass that first runs [p1] then [p2]. *) val sequence : t -> t -> t + +(** [combine lst] is a pass that runs each pass in [list] in sequence. *) val combine : t list -> t + +(** [repeat n pass] is a pass that runs [pass] [n] times.*) val repeat : int -> t -> t + +(** [execute pass block liveliness] runs [block] and its analysis [liveliness] + through [pass]. [block] may be mutated. [liveliness] must correspond to the + analysis of [block] in its current state. *) val execute : t -> Basic_block.t -> Liveliness.BasicBlockAnalysis.t -> unit -module type PASS = sig +(** A signature for modules that implement optimization passes. *) +module type Sig = sig + (** The pass itself, usually a basic pass created with [make]. *) val pass : t end diff --git a/lib/ir/passes.ml b/lib/ir/passes.ml index 2e10fe2..c3246ec 100644 --- a/lib/ir/passes.ml +++ b/lib/ir/passes.ml @@ -1,4 +1,4 @@ -module ConstFold : Pass.PASS = struct +module ConstFold : Pass.Sig = struct let const_fold (bb, _) = for i = 0 to Basic_block.length_of bb - 1 do match Basic_block.get_ir bb i with @@ -10,19 +10,16 @@ module ConstFold : Pass.PASS = struct (Ir.Assign (var, Operand.make_const (lhs - rhs))) | _ -> () done - (* ; match Basic_block.condition_of bb with | Conditional (Constant cond) -> - Basic_block.set_condition bb (if cond = 0 then Never else Always) | _ -> - () *) let pass = Pass.make const_fold end -module CopyProp : Pass.PASS = struct +module CopyProp : Pass.Sig = struct let copy_prop (bb, _) = - let vals = Ir.VariableMap.create 16 in + let vals = VariableMap.create 16 in let subs = function | Operand.Variable var -> ( - match Ir.VariableMap.find_opt vals var with + match VariableMap.find_opt vals var with | Some oper -> oper | None -> Operand.make_var var) | oper -> oper @@ -30,7 +27,7 @@ module CopyProp : Pass.PASS = struct for i = 0 to Basic_block.length_of bb - 1 do match Basic_block.get_ir bb i with | Assign (var, oper) -> - Ir.VariableMap.replace vals var oper; + VariableMap.replace vals var oper; Basic_block.set_ir bb i (Assign (var, subs oper)) | Add (var, oper1, oper2) -> Basic_block.set_ir bb i (Add (var, subs oper1, subs oper2)) @@ -46,7 +43,7 @@ module CopyProp : Pass.PASS = struct let pass = Pass.make copy_prop end -module DeadCode : Pass.PASS = struct +module DeadCode : Pass.Sig = struct let dead_code (bb, analysis) = let length = Basic_block.length_of bb in for rev_i = 0 to Basic_block.length_of bb - 1 do @@ -67,6 +64,6 @@ end let apply passes cfg liveliness = let apply_pass pass bb = - Pass.execute pass bb (Util.IdMap.find liveliness (Basic_block.id_of bb)) + Pass.execute pass bb (IdMap.find liveliness (Basic_block.id_of bb)) in passes |> List.iter (fun pass -> Cfg.iter (apply_pass pass) cfg) diff --git a/lib/ir/passes.mli b/lib/ir/passes.mli new file mode 100644 index 0000000..ce80db3 --- /dev/null +++ b/lib/ir/passes.mli @@ -0,0 +1,15 @@ +(** Constant folding optimization pass. *) +module ConstFold : Pass.Sig + +(** Copy propagation optimization pass. *) +module CopyProp : Pass.Sig + +(** Dead code elimination optimization pass. *) +module DeadCode : Pass.Sig + +(** [apply passes cfg liveliness] applies each pass in [passes] to [cfg] in + order, using the liveliness information for [cfg] ([liveliness]). + + Requires that [liveliness] was generated from the current state of [cfg]. *) +val apply : + Pass.t list -> Cfg.t -> Liveliness.BasicBlockAnalysis.t IdMap.t -> unit diff --git a/lib/ir/variable.mli b/lib/ir/variable.mli index 5234878..bc43840 100644 --- a/lib/ir/variable.mli +++ b/lib/ir/variable.mli @@ -10,7 +10,15 @@ val id_of : t -> Id.t (** [to_string var] is [var] as a string. *) val to_string : t -> string +(** [pp fmt var] pretty prints [var] to [fmt]. *) val pp : Format.formatter -> t -> unit + +(** Provides an arbitrary ordering of variables. *) val compare : t -> t -> int + +(** [equal var1 var2] is true when [id_of var1 = id_of var2], and false + otherwise. *) val equal : t -> t -> bool + +(** [hash var] is the hashcode of [var]. *) val hash : t -> int diff --git a/lib/user/cli.ml b/lib/user/cli.ml index d10f916..8928f03 100644 --- a/lib/user/cli.ml +++ b/lib/user/cli.ml @@ -1,10 +1,8 @@ -(** Compiler flags *) type flag = | OnlyIR | OnlyObject | Optimize -(** The various parses of CLI arguments. *) type action = | Error of { msg : string } | Help @@ -14,12 +12,11 @@ type action = flags : flag list; } -type t = { +type parse_result = { prog : string; action : action; } -(** [parse args] is the command line [args] parsed. *) let parse args = let parse_aux = function | [ "-h" ] | [ "--help" ] -> Help diff --git a/lib/user/cli.mli b/lib/user/cli.mli new file mode 100644 index 0000000..4a7c6a7 --- /dev/null +++ b/lib/user/cli.mli @@ -0,0 +1,25 @@ +(** Compiler flags *) +type flag = + | OnlyIR + | OnlyObject + | Optimize + +(** The various actions the program can take. *) +type action = + | Error of { msg : string } + | Help + | Version + | Compile of { + paths : string list; + flags : flag list; + } + +(** The result of parsing CLI arguments. [prog] is the name/path of the running + executable. [action] is what output the user requested. *) +type parse_result = { + prog : string; + action : action; +} + +(** [parse args] is the command line arguments [args], parsed. *) +val parse : string array -> parse_result diff --git a/lib/user/driver.ml b/lib/user/driver.ml index c76b74a..9810cd1 100644 --- a/lib/user/driver.ml +++ b/lib/user/driver.ml @@ -49,8 +49,9 @@ let compile paths flags build_dir_loc = ] cfg liveliness_analysis; let instr_ordering = InstrOrdering.make cfg in + let registers = Asm.Register.data_registers in let regalloc = - Regalloc.allocate_for cfg liveliness_analysis instr_ordering + Regalloc.allocate_for cfg registers liveliness_analysis instr_ordering in Asm_emit.emit_cfg ~text:text_section cfg regalloc) cfgs; diff --git a/lib/user/meta.ml b/lib/user/meta.ml index 7723ff3..55c255b 100644 --- a/lib/user/meta.ml +++ b/lib/user/meta.ml @@ -1,9 +1,4 @@ -module Version : sig - type t - - val make : int -> int -> int -> t - val to_string : t -> string -end = struct +module Version = struct type t = int * int * int let make maj min pat = (maj, min, pat) diff --git a/lib/user/meta.mli b/lib/user/meta.mli new file mode 100644 index 0000000..0353b03 --- /dev/null +++ b/lib/user/meta.mli @@ -0,0 +1,22 @@ +(** Module for versioning. *) +module Version : sig + (** Represents a version of this program *) + type t + + (** [make major minor patch] is the program version [major.minor.patch]. *) + val make : int -> int -> int -> t + + (** [to_string version] is the string representation of [version]. *) + val to_string : t -> string +end + +(** Represents metadata about this program. *) +type t = { + name : string; + version : Version.t; + description : string; + authors : string list; +} + +(** The metadata for this program. *) +val get : t diff --git a/lib/util/platform.mli b/lib/util/platform.mli new file mode 100644 index 0000000..84cfb1c --- /dev/null +++ b/lib/util/platform.mli @@ -0,0 +1,34 @@ +(** Represents a type of operating system and, in the case of MacOS, a major + version. *) +type os = + | MacOS of int + | Linux + | Unknown + +(** Represents a type of CPU architecture. *) +type cpu_arch = + | Arm + | X86_64 + | Unknown + +(** A platform, consisting of an OS and cpu architecture. *) +type platform = { + os : os; + cpu_arch : cpu_arch; +} + +(** [get_platform ()] is the platform for the system running this code. *) +val get_platform : unit -> platform + +(** [clang_target platform] is the target clang should compile the runtime for + on [platform]. *) +val clang_target : platform -> string option + +(** [object_format platform] is the object file format that should be used on + [platform]. *) +val object_format : platform -> string option + +(** [command_prefix platform] is the prefix for running executables produced by + the compiler. For example, on MacOS with Arm, exectuables must be prefixed + with [arch -x86_64]. *) +val command_prefix : platform -> string diff --git a/lib/util/rep_ok.ml b/lib/util/rep_ok.ml index 7c3eed8..67e4010 100644 --- a/lib/util/rep_ok.ml +++ b/lib/util/rep_ok.ml @@ -1,3 +1 @@ -(** [check] if and only if internal representation invariants should be enforced - at runtime. *) let check = true diff --git a/lib/util/rep_ok.mli b/lib/util/rep_ok.mli new file mode 100644 index 0000000..b57b89e --- /dev/null +++ b/lib/util/rep_ok.mli @@ -0,0 +1,3 @@ +(** [check] if and only if internal representation invariants should be enforced + at runtime. *) +val check : bool diff --git a/lib/util/util.ml b/lib/util/util.ml index aa0a48b..22c5523 100644 --- a/lib/util/util.ml +++ b/lib/util/util.ml @@ -1,21 +1,9 @@ -(** [id x = x] for all [x]. *) -let id x = x - -(** [f >> g] is the function that first evaluates [f] on its input and then [g] - on the output of [f]. *) let ( >> ) f g x = g (f x) - -(** [read_file path] is the contents of the file at [path]. If the file does not - exist, the behavior is undefined. *) let read_file filename = BatFile.with_file_in filename BatIO.read_all -(** [write_file filename content] writes [content] to the file at [filename]. If - the file already exists, it is overwritten. *) let write_file filename content = BatFile.with_file_out filename (fun oc -> BatIO.write_line oc content) -(** [get_command_output command] is the standard output of running [command] in - the shell. *) let get_command_output command = let ic = Unix.open_process_in command in let rec read_lines acc = @@ -28,7 +16,6 @@ let get_command_output command = in read_lines "" -(** [contains_substring str sub] if and only if [str] contains [sub]. *) let contains_substring str sub = let re = Str.regexp_string sub in try @@ -36,25 +23,8 @@ let contains_substring str sub = true with Not_found -> false -(** [(uncurry f) (x, y) = f x y]. *) let uncurry f (x, y) = f x y -(** [merge_paths [path1; path2; ...]] is the result of merging the path - components [path1], [path2], and so on, which we let [path]. The function's - output is specified by the following invariants in the general case (see - further below): - - - If [path1] started with ['/'], so does [path]. - - For each pair of adjacent paths [path1], [path2] in the input list, they - appear in the same relative position in [path] but with every ['/'] - removed from the end of [path1] and the start of [path2] and a single - ['/'] inserted in their place. - - There are also the following exceptions: - - - [merge_paths [] = ""] - - [merge_paths [""] = ""] - - [merge_paths [""; ""; ...] = ""] *) let merge_paths paths = let string_to_chars = String.to_seq >> List.of_seq in let trim_slashes str = @@ -81,8 +51,6 @@ let merge_paths paths = |> List.filter (( = ) "" >> not) |> String.concat "/" -(** [basename path] is largest suffix of [path] not containing the character - ['/']. *) let basename = let rec basename_aux = function | [] | '/' :: _ -> "" @@ -95,8 +63,4 @@ let rec zip_shortest lst1 lst2 = | h1 :: t1, h2 :: t2 -> (h1, h2) :: zip_shortest t1 t2 | _, _ -> [] -(** [pp_of string_of] is a pretty printer for a type with the string conversion - function [string_of] that simply prints the result of [string_of] inline. *) let pp_of string_of fmt x = Format.fprintf fmt "%s" (string_of x) - -module IdMap = Hashtbl.Make (Id) diff --git a/lib/util/util.mli b/lib/util/util.mli new file mode 100644 index 0000000..4e71541 --- /dev/null +++ b/lib/util/util.mli @@ -0,0 +1,51 @@ +(** [f >> g] is the function that first evaluates [f] on its input and then [g] + on the output of [f]. *) +val ( >> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c + +(** [read_file path] is the contents of the file at [path]. If the file does not + exist, the behavior is undefined. *) +val read_file : string -> string + +(** [write_file filename content] writes [content] to the file at [filename]. If + the file already exists, it is overwritten. *) +val write_file : string -> string -> unit + +(** [get_command_output command] is the standard output of running [command] in + the shell. *) +val get_command_output : string -> string + +(** [contains_substring str sub] if and only if [str] contains [sub]. *) +val contains_substring : string -> string -> bool + +(** [(uncurry f) (x, y) = f x y]. *) +val uncurry : ('a -> 'b -> 'c) -> 'a * 'b -> 'c + +(** [merge_paths [path1; path2; ...]] is the result of merging the path + components [path1], [path2], and so on, which we let [path]. The function's + output is specified by the following invariants in the general case (see + further below): + + - If [path1] started with ['/'], so does [path]. + - For each pair of adjacent paths [path1], [path2] in the input list, they + appear in the same relative position in [path] but with every ['/'] + removed from the end of [path1] and the start of [path2] and a single + ['/'] inserted in their place. + + There are also the following exceptions: + + - [merge_paths [] = ""] + - [merge_paths [""] = ""] + - [merge_paths [""; ""; ...] = ""] *) +val merge_paths : string list -> string + +(** [basename path] is largest suffix of [path] not containing the character + ['/']. *) +val basename : string -> string + +(** [zip_shortest [a1; a2; ... an] [b1; b2; ... bm]] is + [(a1, b1); (a2, b2); ... (ak, bk)] where [k = min(n, m)]. *) +val zip_shortest : 'a list -> 'b list -> ('a * 'b) list + +(** [pp_of string_of] is a pretty printer for a type with the string conversion + function [string_of] that simply prints the result of [string_of] inline. *) +val pp_of : ('a -> string) -> Format.formatter -> 'a -> unit diff --git a/test/test_liveliness.ml b/test/test_liveliness.ml index 36002e0..2c422ce 100644 --- a/test/test_liveliness.ml +++ b/test/test_liveliness.ml @@ -18,7 +18,7 @@ let one_instruction_test () = let i2 = Variable.make () in Basic_block.add_ir bb (Ir.Add (i0, Operand.make_var i1, Operand.make_var i2)); let _, analysis = - Liveliness.analysis_of cfg |> Util.IdMap.to_seq |> List.of_seq |> List.hd + Liveliness.analysis_of cfg |> IdMap.to_seq |> List.of_seq |> List.hd in (check bool) "live_in should contain read-only variable" true (Liveliness.VariableSet.mem i1 @@ -45,7 +45,7 @@ let two_instruction_test () = Basic_block.add_ir bb (Ir.Add (i0, Operand.make_var i1, Operand.make_var i2)); Basic_block.add_ir bb (Ir.Add (i4, Operand.make_var i1, Operand.make_var i3)); let _, analysis = - Liveliness.analysis_of cfg |> Util.IdMap.to_seq |> List.of_seq |> List.hd + Liveliness.analysis_of cfg |> IdMap.to_seq |> List.of_seq |> List.hd in (* print_endline (Cfg.to_string cfg); print_endline (Liveliness.BasicBlockAnalysis.to_string analysis); *) @@ -83,9 +83,7 @@ let two_basic_block_test () = let i4 = Variable.make () in Basic_block.add_ir bb (Ir.Add (i0, Operand.make_var i1, Operand.make_var i2)); Basic_block.add_ir bb2 (Ir.Add (i0, Operand.make_var i3, Operand.make_var i4)); - let analyses = - Liveliness.analysis_of cfg |> Util.IdMap.to_seq |> List.of_seq - in + let analyses = Liveliness.analysis_of cfg |> IdMap.to_seq |> List.of_seq in (check int) "the liveliness analysis should have returned analyses for both basic \ blocks in the cfg" diff --git a/test/test_regalloc.ml b/test/test_regalloc.ml index c5058b3..e1367ff 100644 --- a/test/test_regalloc.ml +++ b/test/test_regalloc.ml @@ -26,11 +26,12 @@ let basic_vars = let liveliness = Liveliness.analysis_of cfg in let ordering = InstrOrdering.make cfg in - let allocations = Regalloc.allocate_for cfg liveliness ordering in + let registers = Asm.Register.data_registers in + let allocations = Regalloc.allocate_for cfg registers liveliness ordering in (check bool) "var0 and var1 are allocated separately" false (allocations_same - (Ir.VariableMap.find allocations var0) - (Ir.VariableMap.find allocations var1)) + (VariableMap.find allocations var0) + (VariableMap.find allocations var1)) in Alcotest.test_case "basic case" `Quick test @@ -51,19 +52,21 @@ let write_after_dead = let liveliness = Liveliness.analysis_of cfg in let ordering = InstrOrdering.make cfg in - let allocations = Regalloc.allocate_for cfg liveliness ordering in + let registers = Asm.Register.data_registers in + let allocations = Regalloc.allocate_for cfg registers liveliness ordering in (check bool) "var0 and var1 are allocated separately" false (allocations_same - (Ir.VariableMap.find allocations var0) - (Ir.VariableMap.find allocations var1)) + (VariableMap.find allocations var0) + (VariableMap.find allocations var1)) in Alcotest.test_case "write after dead" `Quick test let spill_basic = let test () = let cfg = Cfg.make [ "spill_basic" ] in + let registers = Asm.Register.data_registers in let entry = Cfg.entry_to cfg in - let reg_count = List.length Regalloc.registers in + let reg_count = List.length registers in let vars = Seq.of_dispenser (Variable.make >> Option.some) |> Seq.take (reg_count + 1) @@ -77,8 +80,8 @@ let spill_basic = let liveliness = Liveliness.analysis_of cfg in let ordering = InstrOrdering.make cfg in - let allocations = Regalloc.allocate_for cfg liveliness ordering in - let alloc_list = List.map (Ir.VariableMap.find allocations) vars in + let allocations = Regalloc.allocate_for cfg registers liveliness ordering in + let alloc_list = List.map (VariableMap.find allocations) vars in List.iteri (fun i var1 -> List.iteri @@ -96,8 +99,9 @@ let spill_basic = let spill_special_case = let test () = let cfg = Cfg.make [ "spill_special_case" ] in + let registers = Asm.Register.data_registers in let entry = Cfg.entry_to cfg in - let reg_count = List.length Regalloc.registers in + let reg_count = List.length registers in let vars = Seq.of_dispenser (Variable.make >> Option.some) |> Seq.take (reg_count + 1) @@ -119,8 +123,8 @@ let spill_special_case = let liveliness = Liveliness.analysis_of cfg in let ordering = InstrOrdering.make cfg in - let allocations = Regalloc.allocate_for cfg liveliness ordering in - let alloc_list = List.map (Ir.VariableMap.find allocations) vars in + let allocations = Regalloc.allocate_for cfg registers liveliness ordering in + let alloc_list = List.map (VariableMap.find allocations) vars in List.iteri (fun i var1 ->