From 11d9d0e3f0a17adec525c48dc7f6648bc2cf52ec Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Mon, 6 May 2024 02:30:04 -0400 Subject: [PATCH 1/6] Update Makefile --- Makefile | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 09ad0da..678bb2f 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ test: build .PHONY: quick_test quick_test: build - opam exec -- dune exec ./test/test_x86ISTMB.exe -- -q + opam exec -- dune exec ./test/test_x86ISTMB.exe -- --quick-tests .PHONY: utop utop: README @@ -70,4 +70,4 @@ serve: docs .PHONY: cloc cloc: @make build > /dev/null - @echo "$$(cloc bin lib --json | jq .SUM.code) lines of code" + @echo "$$(cloc --by-file --include-lang=OCaml bin lib test --json | jq .SUM.code) lines of code" diff --git a/README.md b/README.md index f45ef6e..bcf5d7f 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-06 01:39:50.818851 +> Last updated: 2024-05-06 02:29:47.767086 ``` $ ./main -h From 7a4f71bbdd5f10a32b5c92bde3d4ef0404b846e3 Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Mon, 6 May 2024 22:16:54 -0400 Subject: [PATCH 2/6] Untested IR optimizations --- README.md | 2 +- lib/ir/basic_block.ml | 2 ++ lib/ir/basic_block.mli | 11 ++++++++++ lib/ir/pass.ml | 24 +++++++++++++++++++++ lib/ir/pass.mli | 11 ++++++++++ lib/ir/passes.ml | 47 ++++++++++++++++++++++++++++++++++++++++++ lib/ir/variable.ml | 2 ++ lib/ir/variable.mli | 2 ++ 8 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 lib/ir/pass.ml create mode 100644 lib/ir/pass.mli create mode 100644 lib/ir/passes.ml diff --git a/README.md b/README.md index bcf5d7f..a410c38 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-06 02:29:47.767086 +> Last updated: 2024-05-06 22:10:46.308724 ``` $ ./main -h diff --git a/lib/ir/basic_block.ml b/lib/ir/basic_block.ml index e326747..49791c3 100644 --- a/lib/ir/basic_block.ml +++ b/lib/ir/basic_block.ml @@ -20,6 +20,8 @@ let length_of bb = BatDynArray.length bb.contents let condition_of bb = bb.condition let set_condition bb cond = bb.condition <- cond let add_ir basic_block ir = BatDynArray.add basic_block.contents ir +let get_ir basic_block index = BatDynArray.get basic_block.contents index +let set_ir basic_block index ir = BatDynArray.set basic_block.contents index ir let to_list basic_block = BatDynArray.to_list basic_block.contents let equal bb1 bb2 = bb1.id = bb2.id let hash bb = Id.int_of bb.id |> Int.hash diff --git a/lib/ir/basic_block.mli b/lib/ir/basic_block.mli index 6122d03..23e55e1 100644 --- a/lib/ir/basic_block.mli +++ b/lib/ir/basic_block.mli @@ -20,6 +20,17 @@ val set_condition : t -> Branch_condition.t -> unit (** [add_ir basic_block ir] adds [ir] to the end of [basic_block]. *) val add_ir : t -> Ir.t -> unit +(** [get_ir bb idx] is the IR instruction at index [idx] in [bb]. + + Requires: [Basic_block.length_of bb > idx]. *) +val get_ir : t -> int -> Ir.t + +(** [set_ir bb idx ir] replaces the IR instruction at index [idx] in [bb] with + [ir]. + + Requires: [Basic_block.length_of bb > idx]. *) +val set_ir : t -> int -> Ir.t -> unit + (** [to_list basic_block] are the IR operations in [basic_block] in order as a list. *) val to_list : t -> Ir.t list diff --git a/lib/ir/pass.ml b/lib/ir/pass.ml new file mode 100644 index 0000000..e207fcf --- /dev/null +++ b/lib/ir/pass.ml @@ -0,0 +1,24 @@ +type t = + | Basic of (Basic_block.t -> unit) + | Combine of t list + | Repeat of t * int + +let make f = Basic f +let compose pass1 pass2 = Combine [ pass1; pass2 ] +let combine passes = Combine passes +let repeat n pass = Repeat (pass, n) + +let execute pass bb = + let rec execute_aux bb = function + | Basic f -> f bb + | Combine passes -> List.iter (execute_aux bb) passes + | Repeat (pass, n) -> + for _ = 0 to n - 1 do + execute_aux bb pass + done + in + execute_aux bb pass + +module type PASS = sig + val pass : t +end diff --git a/lib/ir/pass.mli b/lib/ir/pass.mli new file mode 100644 index 0000000..233d21b --- /dev/null +++ b/lib/ir/pass.mli @@ -0,0 +1,11 @@ +type t + +val make : (Basic_block.t -> unit) -> t +val compose : t -> t -> t +val combine : t list -> t +val repeat : int -> t -> t +val execute : t -> Basic_block.t -> unit + +module type PASS = sig + val pass : t +end diff --git a/lib/ir/passes.ml b/lib/ir/passes.ml new file mode 100644 index 0000000..eaa5413 --- /dev/null +++ b/lib/ir/passes.ml @@ -0,0 +1,47 @@ +module ConstFold : Pass.PASS = struct + let const_fold bb = + for i = 0 to Basic_block.length_of bb - 1 do + match Basic_block.get_ir bb i with + | Add (var, Operand.Constant lhs, Operand.Constant rhs) -> + Basic_block.set_ir bb i + (Ir.Assign (var, Operand.make_const (lhs + rhs))) + | Sub (var, Operand.Constant lhs, Operand.Constant rhs) -> + Basic_block.set_ir bb i + (Ir.Assign (var, Operand.make_const (lhs - rhs))) + | _ -> () + done + + let pass = Pass.make const_fold +end + +module CopyProp : Pass.PASS = struct + module VariableMap = Hashtbl.Make (Variable) + + let copy_prop bb = + let vals = VariableMap.create 16 in + let subs = function + | Operand.Variable var -> ( + match VariableMap.find_opt vals var with + | Some oper -> oper + | None -> Operand.make_var var) + | oper -> oper + in + for i = 0 to Basic_block.length_of bb - 1 do + match Basic_block.get_ir bb i with + | Assign (var, oper) -> VariableMap.replace vals var oper + | Add (var, oper1, oper2) -> + Basic_block.set_ir bb i (Add (var, subs oper1, subs oper2)) + | Sub (var, oper1, oper2) -> + Basic_block.set_ir bb i (Sub (var, subs oper1, subs oper2)) + | TestEqual (var, oper1, oper2) -> + Basic_block.set_ir bb i (TestEqual (var, subs oper1, subs oper2)) + | Ref (var, oper) -> Basic_block.set_ir bb i (Ref (var, subs oper)) + | Deref (var, oper) -> Basic_block.set_ir bb i (Deref (var, subs oper)) + | _ -> () + done + + let pass = Pass.make copy_prop +end + +let apply passes cfg = + passes |> List.iter (fun pass -> Cfg.iter (Pass.execute pass) cfg) diff --git a/lib/ir/variable.ml b/lib/ir/variable.ml index 332df61..54587b7 100644 --- a/lib/ir/variable.ml +++ b/lib/ir/variable.ml @@ -7,3 +7,5 @@ let make () = Id.Gen.next var_gen let id_of var = var let to_string = Id.int_of >> string_of_int >> ( ^ ) "i" let compare = Id.compare +let equal = Id.equal +let hash = Id.hash diff --git a/lib/ir/variable.mli b/lib/ir/variable.mli index 04541b2..b5d8d95 100644 --- a/lib/ir/variable.mli +++ b/lib/ir/variable.mli @@ -11,3 +11,5 @@ val id_of : t -> Id.t val to_string : t -> string val compare : t -> t -> int +val equal : t -> t -> bool +val hash : t -> int From 81d7d3c8e299eb8911d076dabf145cf88375971f Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Mon, 6 May 2024 22:29:51 -0400 Subject: [PATCH 3/6] Restructure project --- README.md | 2 +- lib/backend/asm.ml | 6 +++++- lib/{ => core}/rep_ok.ml | 0 lib/{ => core}/util.ml | 0 lib/{ => user}/cli.ml | 0 lib/user/driver.ml | 0 lib/{ => user}/meta.ml | 0 readme.py | 10 ++++++++-- 8 files changed, 14 insertions(+), 4 deletions(-) rename lib/{ => core}/rep_ok.ml (100%) rename lib/{ => core}/util.ml (100%) rename lib/{ => user}/cli.ml (100%) create mode 100644 lib/user/driver.ml rename lib/{ => user}/meta.ml (100%) diff --git a/README.md b/README.md index a410c38..8c5ac4b 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-06 22:10:46.308724 +> Last updated: 2024-05-06 22:27:17.446212 ``` $ ./main -h diff --git a/lib/backend/asm.ml b/lib/backend/asm.ml index 37b4cb3..5f6b6d2 100644 --- a/lib/backend/asm.ml +++ b/lib/backend/asm.ml @@ -144,7 +144,11 @@ end = struct ("section ." ^ section.name) :: (display_indent ^ "align " ^ string_of_int section.align) :: (BatDynArray.to_list section.contents - |> List.map (Instruction.to_nasm >> ( ^ ) display_indent)) + |> List.map (fun instr -> + let str = Instruction.to_nasm instr in + match instr with + | Label _ -> str + | _ -> display_indent ^ str)) |> String.concat "\n" end diff --git a/lib/rep_ok.ml b/lib/core/rep_ok.ml similarity index 100% rename from lib/rep_ok.ml rename to lib/core/rep_ok.ml diff --git a/lib/util.ml b/lib/core/util.ml similarity index 100% rename from lib/util.ml rename to lib/core/util.ml diff --git a/lib/cli.ml b/lib/user/cli.ml similarity index 100% rename from lib/cli.ml rename to lib/user/cli.ml diff --git a/lib/user/driver.ml b/lib/user/driver.ml new file mode 100644 index 0000000..e69de29 diff --git a/lib/meta.ml b/lib/user/meta.ml similarity index 100% rename from lib/meta.ml rename to lib/user/meta.ml diff --git a/readme.py b/readme.py index dcaba97..e1006c1 100644 --- a/readme.py +++ b/readme.py @@ -1,6 +1,12 @@ if __name__ == "__main__": - import subprocess + import subprocess, os from datetime import datetime as dt + + def find_file_in_directory(filename, directory): + for root, _, files in os.walk(directory): + if filename in files: + return os.path.join(root, filename) + return None # requires : ./main has been built with open("README.md.template", "r") as f: @@ -15,7 +21,7 @@ def put(var, text): put( "VERSION_NUM", subprocess.check_output( - "opam exec -- ocaml -e '#use \"./lib/meta.ml\";; print_endline (Version.to_string get.version)'", + f"opam exec -- ocaml -e '#use \"{find_file_in_directory('meta.ml', './lib')}\";; print_endline (Version.to_string get.version)'", shell=True, text=True, ), From 2eea7b29058275b44bab1b9fddf7d1ad0ea3897e Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Mon, 6 May 2024 22:51:58 -0400 Subject: [PATCH 4/6] Start driver --- README.md | 3 ++- bin/main.ml | 61 +-------------------------------------------- lib/user/cli.ml | 58 ++++++++++++++++++++++-------------------- lib/user/driver.ml | 41 ++++++++++++++++++++++++++++++ lib/user/driver.mli | 1 + 5 files changed, 76 insertions(+), 88 deletions(-) create mode 100644 lib/user/driver.mli diff --git a/README.md b/README.md index 8c5ac4b..3fccad9 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-06 22:27:17.446212 +> Last updated: 2024-05-06 22:51:51.462153 ``` $ ./main -h @@ -20,6 +20,7 @@ Usage: ./main [-h|-v] -v,--version prints version info -g,--gen only produces IR -O,--optimize runs optimizations +-c,--compile only produces object files ``` ``` $ ./main -v diff --git a/bin/main.ml b/bin/main.ml index 4daa706..ddc2623 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -1,62 +1,3 @@ open X86ISTMB -let print_error = Printf.eprintf "error: %s" - -let print_help prog = - let open Printf in - printf "%s\n" Meta.get.description; - printf "\n"; - printf "Usage: %s [-h|-v]\n" prog; - printf " or: %s FILE [-g][-O]\n" prog; - printf "\n"; - printf "-h,--help prints this info\n"; - printf "-v,--version prints version info\n"; - printf "-g,--gen only produces IR\n"; - printf "-O,--optimize runs optimizations\n"; - ignore () - -let print_version () = - let open Printf in - printf "%s %s\n" Meta.get.name (Meta.Version.to_string Meta.get.version); - printf "\n"; - printf "Written by: %s\n" (String.concat ", " Meta.get.authors) - -let show_ir statements = - Analysis.infer statements; - let ir = Ir_gen.generate statements in - let main_cfg = List.hd ir in - let blocks = Cfg.blocks_of main_cfg in - List.iter - (fun b -> - let lst = Basic_block.to_list b in - Printf.printf "Block %i:\n" (Basic_block.id_of b |> Id.int_of); - List.iter (fun bir -> print_endline (Ir.to_string bir)) lst; - print_newline ()) - blocks; - let edges = Cfg.edges_of main_cfg in - List.iter - (fun (b1, e, b2) -> - Printf.printf "Block %i" (Basic_block.id_of b1 |> Id.int_of); - Printf.printf " --%s (%s)--> " - (Basic_block.condition_of b1 |> Branch_condition.to_string) - (if e then "t" else "f"); - Printf.printf "Block %i" (Basic_block.id_of b2 |> Id.int_of); - print_newline ()) - edges - -let file_driver path flags = - let source = Util.read_file path in - try - let statements = Parse_lex.lex_and_parse source in - ignore statements; - if List.mem Cli.OnlyIR flags then show_ir statements - else failwith "compiler not done yet" - with Parse_lex.ParseError msg -> print_error (msg ^ "\n") - -let () = - match Sys.argv |> Cli.parse with - | Help { prog } -> print_help prog - | Version { prog = _ } -> print_version () - | File { prog = _; path; flags } -> file_driver path flags - | Error { prog; msg } -> - Printf.sprintf "%s\nuse %s -h\n" msg prog |> print_error +let () = Driver.main Sys.argv diff --git a/lib/user/cli.ml b/lib/user/cli.ml index 4493453..d10f916 100644 --- a/lib/user/cli.ml +++ b/lib/user/cli.ml @@ -1,39 +1,43 @@ (** Compiler flags *) type flag = | OnlyIR + | OnlyObject | Optimize (** The various parses of CLI arguments. *) -type t = - | Error of { - prog : string; - msg : string; - } - | Help of { prog : string } - | Version of { prog : string } - | File of { - prog : string; - path : string; +type action = + | Error of { msg : string } + | Help + | Version + | Compile of { + paths : string list; flags : flag list; } +type t = { + prog : string; + action : action; +} + (** [parse args] is the command line [args] parsed. *) -let parse : string array -> t = - let open Util in +let parse args = let parse_aux = function - | [ prog; "-h" ] | [ prog; "--help" ] -> Help { prog } - | [ prog; "-v" ] | [ prog; "--version" ] -> Version { prog } - | prog :: path :: rest -> - let flags = - rest - |> List.filter_map (fun s -> - match s with - | "-g" | "--gen" -> Some OnlyIR - | "-O" | "--optimize" -> Some Optimize - | _ -> None) - in - File { prog; path; flags } - | prog :: _ -> Error { prog; msg = "invalid arguments" } - | _ -> failwith "program invoked with empty argument array" + | [ "-h" ] | [ "--help" ] -> Help + | [ "-v" ] | [ "--version" ] -> Version + | args -> + let paths = ref [] in + let flags = ref [] in + List.iter + (fun s -> + match s with + | "-g" | "--gen" -> flags := OnlyIR :: !flags + | "-O" | "--optimize" -> flags := Optimize :: !flags + | "-c" | "--compile" -> flags := OnlyObject :: !flags + | _ -> paths := s :: !paths) + args; + Compile { paths = !paths; flags = !flags } in - Array.to_list >> parse_aux + { + prog = Array.get args 0; + action = args |> Array.to_list |> List.tl |> parse_aux; + } diff --git a/lib/user/driver.ml b/lib/user/driver.ml index e69de29..06752fb 100644 --- a/lib/user/driver.ml +++ b/lib/user/driver.ml @@ -0,0 +1,41 @@ +let print_error = Printf.eprintf "error: %s" + +let print_help prog = + let open Printf in + printf "%s\n" Meta.get.description; + printf "\n"; + printf "Usage: %s [-h|-v]\n" prog; + printf " or: %s FILE [-g][-O]\n" prog; + printf "\n"; + printf "-h,--help prints this info\n"; + printf "-v,--version prints version info\n"; + printf "-g,--gen only produces IR\n"; + printf "-O,--optimize runs optimizations\n"; + printf "-c,--compile only produces object files\n"; + ignore () + +let print_version () = + let open Printf in + printf "%s %s\n" Meta.get.name (Meta.Version.to_string Meta.get.version); + printf "\n"; + printf "Written by: %s\n" (String.concat ", " Meta.get.authors) + +let compile paths flags = + ignore paths; + ignore flags; + failwith "not impl" +(* let was_passed flag = List.mem flag flags in + + let source = Util.read_file path in try let statements = + Parse_lex.lex_and_parse source in ignore statements; if List.mem Cli.OnlyIR + flags then show_ir statements else failwith "compiler not done yet" with + Parse_lex.ParseError msg -> print_error (msg ^ "\n") *) + +let main args = + let parse = Cli.parse args in + match parse.action with + | Help -> print_help parse.prog + | Version -> print_version () + | Compile { paths; flags } -> compile paths flags + | Error { msg } -> + Printf.sprintf "%s\nuse %s -h\n" msg parse.prog |> print_error diff --git a/lib/user/driver.mli b/lib/user/driver.mli new file mode 100644 index 0000000..591e3f5 --- /dev/null +++ b/lib/user/driver.mli @@ -0,0 +1 @@ +val main : string array -> unit From 874b6dbb17426ae221bd3cecba1ff775e7808d72 Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Tue, 7 May 2024 09:07:11 -0400 Subject: [PATCH 5/6] Add liveliness data to passes --- README.md | 2 +- lib/ir/pass.ml | 6 +++--- lib/ir/pass.mli | 4 ++-- lib/ir/passes.ml | 11 +++++++---- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3fccad9..bf30969 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-06 22:51:51.462153 +> Last updated: 2024-05-07 09:07:03.449958 ``` $ ./main -h diff --git a/lib/ir/pass.ml b/lib/ir/pass.ml index e207fcf..67dadba 100644 --- a/lib/ir/pass.ml +++ b/lib/ir/pass.ml @@ -1,5 +1,5 @@ type t = - | Basic of (Basic_block.t -> unit) + | Basic of (Basic_block.t * Liveliness.BasicBlockAnalysis.t -> unit) | Combine of t list | Repeat of t * int @@ -8,9 +8,9 @@ let compose pass1 pass2 = Combine [ pass1; pass2 ] let combine passes = Combine passes let repeat n pass = Repeat (pass, n) -let execute pass bb = +let execute pass bb liveliness = let rec execute_aux bb = function - | Basic f -> f bb + | Basic f -> f (bb, liveliness) | Combine passes -> List.iter (execute_aux bb) passes | Repeat (pass, n) -> for _ = 0 to n - 1 do diff --git a/lib/ir/pass.mli b/lib/ir/pass.mli index 233d21b..1546aa7 100644 --- a/lib/ir/pass.mli +++ b/lib/ir/pass.mli @@ -1,10 +1,10 @@ type t -val make : (Basic_block.t -> unit) -> t +val make : (Basic_block.t * Liveliness.BasicBlockAnalysis.t -> unit) -> t val compose : t -> t -> t val combine : t list -> t val repeat : int -> t -> t -val execute : t -> Basic_block.t -> unit +val execute : t -> Basic_block.t -> Liveliness.BasicBlockAnalysis.t -> unit module type PASS = sig val pass : t diff --git a/lib/ir/passes.ml b/lib/ir/passes.ml index eaa5413..bad192f 100644 --- a/lib/ir/passes.ml +++ b/lib/ir/passes.ml @@ -1,5 +1,5 @@ module ConstFold : Pass.PASS = struct - let const_fold bb = + let const_fold (bb, _) = for i = 0 to Basic_block.length_of bb - 1 do match Basic_block.get_ir bb i with | Add (var, Operand.Constant lhs, Operand.Constant rhs) -> @@ -17,7 +17,7 @@ end module CopyProp : Pass.PASS = struct module VariableMap = Hashtbl.Make (Variable) - let copy_prop bb = + let copy_prop (bb, _) = let vals = VariableMap.create 16 in let subs = function | Operand.Variable var -> ( @@ -43,5 +43,8 @@ module CopyProp : Pass.PASS = struct let pass = Pass.make copy_prop end -let apply passes cfg = - passes |> List.iter (fun pass -> Cfg.iter (Pass.execute pass) cfg) +let apply passes cfg liveliness = + let apply_pass pass bb = + Pass.execute pass bb (Util.IdMap.find liveliness (Basic_block.id_of bb)) + in + passes |> List.iter (fun pass -> Cfg.iter (apply_pass pass) cfg) From 6f7f76b47ffe44fe9625e2efa33aebf6c277a320 Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Tue, 7 May 2024 11:30:01 -0400 Subject: [PATCH 6/6] Test IR opts, fix liveliness and ir opts, start driver --- README.md | 2 +- lib/backend/liveliness.ml | 96 ++++++++++++++++++---------------- lib/backend/liveliness.mli | 2 + lib/ir/basic_block.ml | 1 + lib/ir/basic_block.mli | 6 +++ lib/ir/ir.ml | 11 ++++ lib/ir/ir_sim.ml | 4 +- lib/ir/ir_sim.mli | 2 + lib/ir/passes.ml | 18 +++++++ lib/user/driver.ml | 23 ++++---- test/printing_progs/0.x86istmb | 15 ++++++ test/snapshot/snapshot.ml | 8 +-- test/snapshot/snapshot.mli | 4 +- test/snapshots/ir/ir4.in | 15 ++++++ test/snapshots/ir/ir4.out | 8 +++ test/test_context.ml | 2 +- test/test_passes.ml | 55 +++++++++++++++++++ test/test_snapshots.ml | 9 ++-- test/test_x86ISTMB.ml | 5 +- 19 files changed, 218 insertions(+), 68 deletions(-) create mode 100644 test/printing_progs/0.x86istmb create mode 100644 test/snapshots/ir/ir4.in create mode 100644 test/snapshots/ir/ir4.out create mode 100644 test/test_passes.ml diff --git a/README.md b/README.md index bf30969..95c6a67 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-07 09:07:03.449958 +> Last updated: 2024-05-07 11:28:52.101596 ``` $ ./main -h diff --git a/lib/backend/liveliness.ml b/lib/backend/liveliness.ml index 2dd3bb3..534a567 100644 --- a/lib/backend/liveliness.ml +++ b/lib/backend/liveliness.ml @@ -2,7 +2,14 @@ open Util open Ir (** A set of IR variables. *) -module VariableSet = Set.Make (Variable) +module VariableSet = struct + include Set.Make (Variable) + + let to_string set = + "{" + ^ (to_list set |> List.map Variable.to_string |> String.concat ", ") + ^ "}" +end (** Liveliness analysis of an IR operation. *) type instr_analysis = { @@ -59,22 +66,14 @@ module BasicBlockAnalysis = struct let to_string analysis = let analysis = rep_ok analysis in - let set_to_string set = - let elements_string = - VariableSet.elements set - |> List.map Variable.to_string - |> String.concat ", " - in - "{" ^ elements_string ^ "}" - in "BasicBlockAnalysis {" ^ (Seq.init (Array.length analysis) id |> List.of_seq |> List.map (fun i -> "\n ir[" ^ string_of_int i ^ "] <=> {live_in = " - ^ set_to_string (live_before_instr analysis i) + ^ VariableSet.to_string (live_before_instr analysis i) ^ ", live_out = " - ^ set_to_string (live_after_instr analysis i) + ^ VariableSet.to_string (live_after_instr analysis i) ^ "}") |> String.concat "") ^ "\n}" @@ -84,45 +83,51 @@ end liveliness rules for instruction [ir] at index [ir_index] in basic block [bb], where [bb] is in [cfg] and has associated liveliness analysis [analysis = IdMap.find liveliness (Basic_block.id_of bb)], and where - [is_final] if and only if [ir] is the final instruction in [bb], and returns - whether any updates were made to liveliness information. *) + [is_final] if and only if [ir] is the final instruction in [bb], updating + partial results in [liveliness] and returning whether any updates were made + to liveliness information. *) let apply_rules liveliness analysis cfg bb ir ir_idx ~is_final = - let result = ref false in let instr_analysis = analysis.(ir_idx) in - let update_live_out new_live_out = - let old_live_out = instr_analysis.live_out in - instr_analysis.live_out <- new_live_out; - if old_live_out <> new_live_out then result := true + let old_live_in = instr_analysis.live_in in + let old_live_out = instr_analysis.live_out in + let check_for_changes () = + if + VariableSet.cardinal old_live_in + > VariableSet.cardinal instr_analysis.live_in + then failwith "??"; + (match (old_live_out, instr_analysis.live_out) with + | Some old_live_out, Some new_live_out -> + not (VariableSet.equal old_live_out new_live_out) + | None, None -> false + | _ -> true) + || not (VariableSet.equal old_live_in instr_analysis.live_in) in - let update_live_in new_live_in = - let old_live_in = instr_analysis.live_in in - instr_analysis.live_in <- new_live_in; - if old_live_in <> new_live_in then result := true + let bring_incoming () = + (if is_final then + let live_out_of_succ = + Cfg.out_edges cfg bb + |> List.fold_left + (fun acc (bb_succ, _) -> + let incoming_live_partial = + IdMap.find liveliness (Basic_block.id_of bb_succ) + |> BasicBlockAnalysis.live_in + in + VariableSet.union acc incoming_live_partial) + VariableSet.empty + in + instr_analysis.live_out <- Some live_out_of_succ); + instr_analysis.live_in <- + VariableSet.union instr_analysis.live_in + (BasicBlockAnalysis.live_after_instr analysis ir_idx) in let read_var var = - update_live_in (VariableSet.add var instr_analysis.live_in) + instr_analysis.live_in <- VariableSet.add var instr_analysis.live_in in - let read_op op = Operand.var_of_opt op |> Option.map read_var |> ignore in + let read_op = Operand.var_of_opt >> Option.map read_var >> ignore in let write_var var = - let incoming_live = - VariableSet.remove var - (BasicBlockAnalysis.live_after_instr analysis ir_idx) - in - update_live_in (VariableSet.union instr_analysis.live_in incoming_live) + instr_analysis.live_in <- VariableSet.remove var instr_analysis.live_in in - (if is_final then - let live_out_of_succ = - Cfg.out_edges cfg bb - |> List.fold_left - (fun acc (bb_succ, _) -> - let incoming_live_partial = - IdMap.find liveliness (Basic_block.id_of bb_succ) - |> BasicBlockAnalysis.live_in - in - VariableSet.union acc incoming_live_partial) - VariableSet.empty - in - update_live_out (Some live_out_of_succ)); + bring_incoming (); (match ir with | DebugPrint op -> read_op op | Assign (var, op) | Deref (var, op) | Ref (var, op) -> @@ -132,10 +137,11 @@ let apply_rules liveliness analysis cfg bb ir ir_idx ~is_final = write_var var; read_op op1; read_op op2); - !result + check_for_changes () (** [pass work_list liveliness cfg bb] performs a single pass of liveliness - analysis on a basic block *) + analysis on a basic block, updating partial results in [liveliness] and + returning whether any updates were made to liveliness information. *) let pass work_list liveliness cfg bb = let result = ref false in let analysis = IdMap.find liveliness (Basic_block.id_of bb) in @@ -153,7 +159,7 @@ let pass work_list liveliness cfg bb = !result (** [iterate liveliness cfg] performs an iteration of liveliness analysis on - [cfg], updating partial results in [liveliness], and returning whether any + [cfg], updating partial results in [liveliness] and returning whether any changes were made. *) let iterate liveliness cfg = let work_list = Queue.create () in diff --git a/lib/backend/liveliness.mli b/lib/backend/liveliness.mli index f2169d0..75b6822 100644 --- a/lib/backend/liveliness.mli +++ b/lib/backend/liveliness.mli @@ -3,6 +3,8 @@ 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 + + val to_string : t -> string end (** Liveliness analysis of a basic block. *) diff --git a/lib/ir/basic_block.ml b/lib/ir/basic_block.ml index 49791c3..7aeb656 100644 --- a/lib/ir/basic_block.ml +++ b/lib/ir/basic_block.ml @@ -22,6 +22,7 @@ let set_condition bb cond = bb.condition <- cond let add_ir basic_block ir = BatDynArray.add basic_block.contents ir let get_ir basic_block index = BatDynArray.get basic_block.contents index let set_ir basic_block index ir = BatDynArray.set basic_block.contents index ir +let rem_ir basic_block index = BatDynArray.remove_at index basic_block.contents let to_list basic_block = BatDynArray.to_list basic_block.contents let equal bb1 bb2 = bb1.id = bb2.id let hash bb = Id.int_of bb.id |> Int.hash diff --git a/lib/ir/basic_block.mli b/lib/ir/basic_block.mli index 23e55e1..d13ba29 100644 --- a/lib/ir/basic_block.mli +++ b/lib/ir/basic_block.mli @@ -31,6 +31,12 @@ val get_ir : t -> int -> Ir.t Requires: [Basic_block.length_of bb > idx]. *) val set_ir : t -> int -> Ir.t -> unit +(** [rem_ir bb idx] removes the IR instruction at index [idx] in [bb], shifting + all the subsequent indices/IR instructions backward. + + Requires: [Basic_block.length_of bb > idx]. *) +val rem_ir : t -> int -> unit + (** [to_list basic_block] are the IR operations in [basic_block] in order as a list. *) val to_list : t -> Ir.t list diff --git a/lib/ir/ir.ml b/lib/ir/ir.ml index aea8713..37efb12 100644 --- a/lib/ir/ir.ml +++ b/lib/ir/ir.ml @@ -11,6 +11,17 @@ type t = | DebugPrint of Operand.t (* | Call of string * Operand.t list *) +(** [kill_of ir] is [Some var] if [var] is assigned to in [ir] and [None] + otherwise. *) +let kill_of = function + | Assign (var, _) + | Add (var, _, _) + | Sub (var, _, _) + | Ref (var, _) + | Deref (var, _) + | TestEqual (var, _, _) -> Some var + | DebugPrint _ -> None + let to_string = let open Printf in function diff --git a/lib/ir/ir_sim.ml b/lib/ir/ir_sim.ml index afda2cd..affb8cc 100644 --- a/lib/ir/ir_sim.ml +++ b/lib/ir/ir_sim.ml @@ -44,6 +44,8 @@ let run simulator cfg = | Some bb2 -> run_aux bb2 | None -> () in - run_aux entry + run_aux entry; + Context.pop simulator.context let output_of simulator = simulator.output +let clear_output simulator = simulator.output <- "" diff --git a/lib/ir/ir_sim.mli b/lib/ir/ir_sim.mli index 6aa3037..594581c 100644 --- a/lib/ir/ir_sim.mli +++ b/lib/ir/ir_sim.mli @@ -10,3 +10,5 @@ val run : t -> Cfg.t -> unit (** [dump simulator] is the current standard output of [simulator] as a human-readable string. *) val output_of : t -> string + +val clear_output : t -> unit diff --git a/lib/ir/passes.ml b/lib/ir/passes.ml index bad192f..05f2c2f 100644 --- a/lib/ir/passes.ml +++ b/lib/ir/passes.ml @@ -43,6 +43,24 @@ module CopyProp : Pass.PASS = struct let pass = Pass.make copy_prop end +module DeadCode : Pass.PASS = 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 + let i = length - rev_i - 1 in + let live_out = + Liveliness.BasicBlockAnalysis.live_after_instr analysis i + in + match Basic_block.get_ir bb i |> Ir.kill_of with + | Some var -> + if not (Liveliness.VariableSet.mem var live_out) then + Basic_block.rem_ir bb i + | None -> () + done + + let pass = Pass.make dead_code +end + let apply passes cfg liveliness = let apply_pass pass bb = Pass.execute pass bb (Util.IdMap.find liveliness (Basic_block.id_of bb)) diff --git a/lib/user/driver.ml b/lib/user/driver.ml index 06752fb..edd3d91 100644 --- a/lib/user/driver.ml +++ b/lib/user/driver.ml @@ -20,16 +20,21 @@ let print_version () = printf "\n"; printf "Written by: %s\n" (String.concat ", " Meta.get.authors) -let compile paths flags = - ignore paths; - ignore flags; - failwith "not impl" -(* let was_passed flag = List.mem flag flags in +let compile paths _ = + Printf.printf "assumes [paths] has one file, ignores flags\n"; + let source = Util.read_file (List.hd paths) in + try + let statements = Parse_lex.lex_and_parse source in + Analysis.infer statements; + let ir = Ir_gen.generate statements in + let main_cfg = List.hd ir in + ignore (Liveliness.analysis_of main_cfg); + let simulator = Ir_sim.make () in + Ir_sim.run simulator main_cfg; + print_string (Ir_sim.output_of simulator) + with Parse_lex.ParseError msg -> print_error (msg ^ "\n") - let source = Util.read_file path in try let statements = - Parse_lex.lex_and_parse source in ignore statements; if List.mem Cli.OnlyIR - flags then show_ir statements else failwith "compiler not done yet" with - Parse_lex.ParseError msg -> print_error (msg ^ "\n") *) +(* let was_passed flag = List.mem flag flags in *) let main args = let parse = Cli.parse args in diff --git a/test/printing_progs/0.x86istmb b/test/printing_progs/0.x86istmb new file mode 100644 index 0000000..60b8f09 --- /dev/null +++ b/test/printing_progs/0.x86istmb @@ -0,0 +1,15 @@ +func main() { + let x = 0 + let y = 2 + let z = x + let w = z + y + print x + print y + print z + print w + z = 10 + print x + print y + print z + print w +} diff --git a/test/snapshot/snapshot.ml b/test/snapshot/snapshot.ml index 7e088ae..4fd60d5 100644 --- a/test/snapshot/snapshot.ml +++ b/test/snapshot/snapshot.ml @@ -1,11 +1,11 @@ open Alcotest open X86ISTMB -type transform = string -> string +type transform = (string -> string) * speed_level let ignore_file_name = "IGNORE" -let make_test_suite root suite transform = +let make_test_suite root suite (transform_f, speed) = let open Util in let snapshots_folder = Util.merge_paths [ Project_root.path; root; suite ] in let files = Sys.readdir snapshots_folder in @@ -33,7 +33,7 @@ let make_test_suite root suite transform = let input = read_file input_path in let expected = read_file output_path in try - let actual = transform input in + let actual = transform_f input in (check string) "Using the given input transformer should yield matching output to the \ expected." @@ -45,6 +45,6 @@ let make_test_suite root suite transform = snapshots |> List.filter_map (fun snapshot -> if should_ignore_snapshot snapshot then None - else Some (snapshot, `Quick, snapshot_test snapshot)) + else Some (snapshot, speed, snapshot_test snapshot)) in (suite_name, snapshot_tests) diff --git a/test/snapshot/snapshot.mli b/test/snapshot/snapshot.mli index 75be402..3491940 100644 --- a/test/snapshot/snapshot.mli +++ b/test/snapshot/snapshot.mli @@ -1,7 +1,7 @@ -(** Let [f] be of type [transform]. Then, [f contents] is the result of +(** Let [(f, _)] be of type [transform]. Then, [f contents] is the result of transforming the string [contents] (which will usually come from a snapshot file) *) -type transform = string -> string +type transform = (string -> string) * Alcotest.speed_level (** [make_test_suite root suite f] is a snapshot test suit from snapshots in [root/suite] using snapshot transformation [f] (see the documentation for diff --git a/test/snapshots/ir/ir4.in b/test/snapshots/ir/ir4.in new file mode 100644 index 0000000..60b8f09 --- /dev/null +++ b/test/snapshots/ir/ir4.in @@ -0,0 +1,15 @@ +func main() { + let x = 0 + let y = 2 + let z = x + let w = z + y + print x + print y + print z + print w + z = 10 + print x + print y + print z + print w +} diff --git a/test/snapshots/ir/ir4.out b/test/snapshots/ir/ir4.out new file mode 100644 index 0000000..2ed48bb --- /dev/null +++ b/test/snapshots/ir/ir4.out @@ -0,0 +1,8 @@ +0 +2 +0 +2 +0 +2 +10 +2 diff --git a/test/test_context.ml b/test/test_context.ml index 8e327c0..149802e 100644 --- a/test/test_context.ml +++ b/test/test_context.ml @@ -105,7 +105,7 @@ let insert_get = in QCheck_alcotest.to_alcotest ~long:true test -let suite = +let test_suite = ( "lib/frontend/context.ml", [ make_is_empty; diff --git a/test/test_passes.ml b/test/test_passes.ml new file mode 100644 index 0000000..947e9cd --- /dev/null +++ b/test/test_passes.ml @@ -0,0 +1,55 @@ +open X86ISTMB +open Alcotest + +let make_opts_test passes = + let ir0_source = + Util.read_file + (Util.merge_paths [ Project_root.path; "test/printing_progs/0.x86istmb" ]) + in + let statements = Parse_lex.lex_and_parse ir0_source in + Analysis.infer statements; + let ir = Ir_gen.generate statements in + let main_cfg = List.hd ir in + let liveliness_analysis = Liveliness.analysis_of main_cfg in + let simulator = Ir_sim.make () in + Ir_sim.run simulator main_cfg; + let unopt_output = Ir_sim.output_of simulator in + Passes.apply passes main_cfg liveliness_analysis; + Ir_sim.clear_output simulator; + Ir_sim.run simulator main_cfg; + let opt_output = Ir_sim.output_of simulator in + (check string) "optimization should not change program behavior" unopt_output + opt_output + +let fixed_ir_opts_tests = + [ + ([ Passes.ConstFold.pass ], "const fold ir opt"); + ([ Passes.CopyProp.pass ], "copy prop ir opt"); + ([ Passes.DeadCode.pass ], "dead code ir opt"); + ( [ + Pass.combine + [ Passes.ConstFold.pass; Passes.CopyProp.pass; Passes.DeadCode.pass ]; + ], + "combined ir opt" ); + ( [ + Pass.compose Passes.ConstFold.pass Passes.CopyProp.pass |> Pass.repeat 10; + Passes.DeadCode.pass; + ], + "complex ir opt" ); + ] + |> List.map (fun (passes, name) -> + test_case name `Quick (fun () -> make_opts_test passes)) + +(* let qcheck_ir_opts () = let open QCheck in let test = let open QCheck.Gen in + let pass_options = [| Passes.ConstFold.pass; Passes.CopyProp.pass; + Passes.DeadCode.pass |] in let get_pass () = let* idx = int_bound + (Array.length pass_options) in pass_options.(idx) in let gen_pass () = let + rec gen_pass_aux temp pass = let* prob = float 1.0 in if prob < exp (-.temp) + then pass else let* mutation = int_bound 3 in (match mutation with | 0 -> + let* n = int_bound 10 in Pass.repeat n pass | 1 -> Pass.compose pass + (get_pass ()) | 2 -> Pass.combine pass :: Seq.unfold () | _ -> pass) |> + gen_pass_aux (temp +. 1.0) in gen_pass_aux 1.0 (get_pass ()) in let pass = + gen_pass () in make_opts_test [ pass ] in QCheck_alcotest.to_alcotest + ~long:true (Test.make ~name:"random ir opt passes" ~count:100 test) *) + +let test_suite = ("lib/ir/passes.ml", fixed_ir_opts_tests) diff --git a/test/test_snapshots.ml b/test/test_snapshots.ml index e012840..9596383 100644 --- a/test/test_snapshots.ml +++ b/test/test_snapshots.ml @@ -12,7 +12,7 @@ let type_suite = Printexc.to_string (Analysis.TypeInferenceError err) ^ "\n" | e -> raise e in - Snapshot.make_test_suite snapshots_root "type" transform + Snapshot.make_test_suite snapshots_root "type" (transform, `Quick) (** [ir_transform] is the result of running the IR simulator on the source code [input]. *) @@ -22,11 +22,14 @@ let ir_transform input = Analysis.infer statements; let ir = Ir_gen.generate statements in let main_cfg = List.hd ir in + ignore (Liveliness.analysis_of main_cfg); let simulator = Ir_sim.make () in Ir_sim.run simulator main_cfg; Ir_sim.output_of simulator -let ir_suite = Snapshot.make_test_suite snapshots_root "ir" ir_transform +let ir_suite = + Snapshot.make_test_suite snapshots_root "ir" (ir_transform, `Quick) (** not sure why this is separate from [ir_suite]. *) -let basic_suite = Snapshot.make_test_suite snapshots_root "basic" ir_transform +let basic_suite = + Snapshot.make_test_suite snapshots_root "basic" (ir_transform, `Quick) diff --git a/test/test_x86ISTMB.ml b/test/test_x86ISTMB.ml index cec72e2..65c1263 100644 --- a/test/test_x86ISTMB.ml +++ b/test/test_x86ISTMB.ml @@ -54,8 +54,9 @@ let () = Test_snapshots.ir_suite; Test_snapshots.type_suite; Test_snapshots.basic_suite; - Test_digraph.test_suite; Test_liveliness.test_suite; - Test_context.suite; + Test_passes.test_suite; + Test_digraph.test_suite; + Test_context.test_suite; ] |> Alcotest.run "x86ISTMB"