Browse Source

add copy propagation

Simon Krajewski 9 years ago
parent
commit
c268c62f28
2 changed files with 152 additions and 13 deletions
  1. 114 12
      analyzer.ml
  2. 38 1
      tests/optimization/src/TestJs.hx

+ 114 - 12
analyzer.ml

@@ -133,6 +133,7 @@ module Config = struct
 	type t = {
 		analyzer_use : bool;
 		const_propagation : bool;
+		copy_propagation : bool;
 		local_dce : bool;
 		dot_debug : bool;
 	}
@@ -140,6 +141,8 @@ module Config = struct
 	let flag_no_check = "no_check"
 	let flag_no_const_propagation = "no_const_propagation"
 	let flag_const_propagation = "const_propagation"
+	let flag_no_copy_propagation = "no_copy_propagation"
+	let flag_copy_propagation = "copy_propagation"
 	let flag_no_local_dce = "no_local_dce"
 	let flag_local_dce = "local_dce"
 	let flag_ignore = "ignore"
@@ -194,6 +197,7 @@ module Config = struct
 		{
 			analyzer_use = true;
 			const_propagation = not (Common.raw_defined com "analyzer-no-const-propagation");
+			copy_propagation = not (Common.raw_defined com "analyzer-no-copy-propagation");
 			local_dce = not (Common.raw_defined com "analyzer-no-local-dce");
 			dot_debug = false;
 		}
@@ -204,6 +208,8 @@ module Config = struct
 				List.fold_left (fun config e -> match fst e with
 					| EConst (Ident s) when s = flag_no_const_propagation -> { config with const_propagation = false}
 					| EConst (Ident s) when s = flag_const_propagation -> { config with const_propagation = true}
+					| EConst (Ident s) when s = flag_no_copy_propagation -> { config with copy_propagation = false}
+					| EConst (Ident s) when s = flag_copy_propagation -> { config with copy_propagation = true}
 					| EConst (Ident s) when s = flag_no_local_dce -> { config with local_dce = false}
 					| EConst (Ident s) when s = flag_local_dce -> { config with local_dce = true}
 					| EConst (Ident s) when s = flag_dot_debug -> {config with dot_debug = true}
@@ -485,9 +491,10 @@ module BasicBlock = struct
 		| BKUnreachable   (* The unique unreachable block *)
 
 	type cfg_edge_Flag =
-		| FlagExecutable     (* Used by constant propagation to handle live edges *)
-		| FlagDceDone    (* Used by DCE to keep track of handled edges *)
-		| FlagCodeMotionDone (* Used by code motion to track handled edges *)
+		| FlagExecutable      (* Used by constant propagation to handle live edges *)
+		| FlagDceDone         (* Used by DCE to keep track of handled edges *)
+		| FlagCodeMotionDone  (* Used by code motion to track handled edges *)
+		| FlagCopyPropagation (* Used by copy propagation to track handled eges *)
 
 	type cfg_edge_kind =
 		| CFGGoto                (* An unconditional branch *)
@@ -894,7 +901,6 @@ module TexprTransformer = struct
 			let assign e =
 				if not !was_assigned then begin
 					was_assigned := true;
-					add_var_def g bb v;
 					add_texpr g bb (mk (TVar(v,None)) ctx.com.basic.tvoid ev.epos);
 				end;
 				mk (TBinop(OpAssign,ev,e)) ev.etype e.epos
@@ -904,6 +910,7 @@ module TexprTransformer = struct
 				block_element bb e
 			with Exit ->
 				let bb,e = value bb e in
+				add_var_def g bb v;
 				add_texpr g bb (mk (TVar(v,Some e)) ctx.com.basic.tvoid ev.epos);
 				bb
 			end;
@@ -1472,13 +1479,13 @@ module type DataFlowApi = sig
 	type t
 	val flag : BasicBlock.cfg_edge_Flag
 	val transfer : analyzer_context -> BasicBlock.t -> texpr -> t (* The transfer function *)
-	val equals : t -> t -> bool                                 (* The equality function *)
-	val bottom : t                                              (* The bottom element of the lattice *)
-	val top : t                                                 (* The top element of the lattice *)
-	val get_cell : int -> t                                     (* Lattice cell getter *)
-	val set_cell : int -> t -> unit                             (* Lattice cell setter *)
-	val init : analyzer_context -> unit                         (* The initialization function which is called at the start *)
-	val commit : analyzer_context -> unit                       (* The commit function which is called at the end *)
+	val equals : t -> t -> bool                                   (* The equality function *)
+	val bottom : t                                                (* The bottom element of the lattice *)
+	val top : t                                                   (* The top element of the lattice *)
+	val get_cell : int -> t                                       (* Lattice cell getter *)
+	val set_cell : int -> t -> unit                               (* Lattice cell setter *)
+	val init : analyzer_context -> unit                           (* The initialization function which is called at the start *)
+	val commit : analyzer_context -> unit                         (* The commit function which is called at the end *)
 	val conditional : bool                                        (* Whether or not conditional branches are checked *)
 end
 
@@ -1763,6 +1770,99 @@ module ConstPropagation = DataFlow(struct
 		);
 end)
 
+(*
+	Propagates locals to other locals if data flow analysis it's possible.
+
+	Respects scopes on targets where it matters (all except JS and As3).
+*)
+module CopyPropagation = DataFlow(struct
+	open BasicBlock
+	open Graph
+
+	type t =
+		| Top
+		| Bottom
+		| Local of (BasicBlock.t * tvar)
+
+	let to_string = function
+		| Top -> "Top"
+		| Bottom -> "Bottom"
+		| Local(bb,v) -> Printf.sprintf "%s<%i>" v.v_name v.v_id
+
+	let conditional = false
+	let flag = FlagCopyPropagation
+	let lattice = ref IntMap.empty
+
+	let get_cell i = try IntMap.find i !lattice with Not_found -> Top
+	let set_cell i ct = lattice := IntMap.add i ct !lattice
+
+	let top = Top
+	let bottom = Bottom
+
+	let equals t1 t2 = match t1,t2 with
+		| Top,Top -> true
+		| Bottom,Bottom -> true
+		| Local(_,v1),Local(_,v2) -> v1.v_id = v2.v_id
+		| _ -> false
+
+	let transfer ctx bb e =
+		let local bb v = try
+			begin match get_cell v.v_id with
+				| Bottom -> raise Not_found
+				| t -> t
+			end
+		with Not_found ->
+			Local(bb,v)
+		in
+		let rec loop e = match e.eexpr with
+			| TLocal v when not v.v_capture ->
+				let v' = get_var_origin ctx.graph v in
+				(* This restriction is in place due to how we currently reconstruct the AST. Multiple SSA-vars may be turned back to
+				   the same origin var, which creates interference that is not tracked in the analysis. We address this by only
+				   considering variables whose origin-variables are assigned to at most once. *)
+				let writes = try snd (IntMap.find v'.v_id ctx.graph.g_var_writes) with Not_found -> [] in
+				begin match writes with
+					| [bb] -> local bb v
+					| _ -> Bottom
+				end
+			| TParenthesis e1 | TMeta(_,e1) | TCast(e1,None) ->
+				loop e1
+			| _ ->
+				Bottom
+		in
+		loop e
+
+	let init ctx =
+		lattice := IntMap.empty
+
+	let commit ctx =
+		(* We don't care about the scope on JS and AS3 because they hoist var declarations. *)
+		let in_scope bb bb' = match ctx.com.platform with
+			| Js _ -> true
+			| Flash when Common.defined ctx.com Define.As3 -> true
+			| _ -> List.mem (List.hd bb'.bb_scopes) bb.bb_scopes
+		in
+		let rec commit bb e = match e.eexpr with
+			| TLocal v when not v.v_capture ->
+				begin try
+					let ct = IntMap.find v.v_id !lattice in
+					begin match ct with
+						| Local(bb',v') when in_scope bb bb' && (type_iseq_strict v.v_type v'.v_type) -> {e with eexpr = TLocal v'}
+						| _ -> raise Not_found
+					end
+				with Not_found ->
+					e
+				end
+			| TBinop((OpAssign | OpAssignOp _ as op),({eexpr = TLocal _} as e1),e2) ->
+				let e2 = commit bb e2 in
+				{e with eexpr = TBinop(op,e1,e2)}
+			| _ ->
+				Type.map_expr (commit bb) e
+		in
+		Graph.iter_dom_tree ctx.graph (fun bb ->
+			dynarray_map (commit bb) bb.bb_el
+		);
+end)
 
 module CodeMotion = DataFlow(struct
 	open Graph
@@ -2037,6 +2137,7 @@ module Debug = struct
 			| FlagExecutable -> "executable"
 			| FlagDceDone -> "dce-done"
 			| FlagCodeMotionDone -> "code-motion-done"
+			| FlagCopyPropagation -> "copy-propagation"
 		in
 		let label = label ^ match edge.cfg_flags with
 			| [] -> ""
@@ -2194,7 +2295,8 @@ module Run = struct
 			if not ctx.has_unbound then begin
 				with_timer "analyzer-ssa-apply" (fun () -> Ssa.apply ctx);
 				if config.const_propagation then with_timer "analyzer-const-propagation" (fun () -> ConstPropagation.apply ctx);
-				CodeMotion.apply ctx;
+				if config.copy_propagation then with_timer "analyzer-copy-propagation" (fun () -> CopyPropagation.apply ctx);
+				(* CodeMotion.apply ctx; *)
 				with_timer "analyzer-local-dce" (fun () -> LocalDce.apply ctx);
 			end;
 			if config.dot_debug then Debug.dot_debug ctx c cf;

+ 38 - 1
tests/optimization/src/TestJs.hx

@@ -45,6 +45,7 @@ class TestJs {
 
 	@:js("var a = 1;var tmp;var v2 = a;tmp = a + v2;tmp;")
 	@:analyzer(no_const_propagation)
+	@:analyzer(no_copy_propagation)
 	@:analyzer(no_check_has_effect)
 	@:analyzer(no_local_dce)
 	static function testInlineWithArgumentUsedMoreThanOnce() {
@@ -88,7 +89,7 @@ class TestJs {
 	}
 
 	@:js('var a = "";var e;var __ex0 = a;var _g = __ex0.toLowerCase();switch(_g) {case "e":e = 0;break;default:throw new Error();}')
-	@:analyzer(no_const_propagation, no_local_dce)
+	@:analyzer(no_const_propagation, no_local_dce, no_copy_propagation)
 	static function testRValueSwitchWithExtractors() {
 		var a = "";
 		var e = switch (a) {
@@ -390,6 +391,42 @@ class TestJs {
 		}
 	}
 
+	@:js('
+		var a = TestJs.getInt();
+		TestJs["use"](a);
+	')
+	static function testCopyPropagation1() {
+		var a = getInt();
+		var b = a;
+		use(b);
+	}
+
+	@:js('
+		var a = TestJs.getInt();
+		var b = a;
+		a = TestJs.getInt();
+		TestJs["use"](b);
+	')
+	static function testCopyPropagation2() {
+		var a = getInt();
+		var b = a;
+		a = getInt();
+		use(b);
+	}
+
+	@:js('
+		var b = TestJs.getInt();
+		TestJs["use"](b);
+	')
+	static function testCopyPropagation3() {
+		var a;
+		{
+			var b = getInt();
+			a = b;
+		}
+		use(a);
+	}
+
 	static function getInt(?d:Dynamic) { return 1; }
 	static function call(d1:Dynamic, d2:Dynamic) { return d1; }
 	static function use<T>(t:T) { }