Explorar o código

Merge pull request #3786 from HaxeFoundation/loop_unroll

Loop unrolling
Simon Krajewski %!s(int64=10) %!d(string=hai) anos
pai
achega
6ff91ebbb9

+ 32 - 15
analyzer.ml

@@ -396,8 +396,12 @@ module Simplifier = struct
 					| e :: el ->
 						begin match e.eexpr with
 							| TVar(v,Some e1) when Meta.has Meta.CompilerGenerated v.v_meta ->
-								var_map := IntMap.add v.v_id (loop e1) !var_map;
-								loop2 el
+								if el = [] then
+									[loop e1]
+								else begin
+									var_map := IntMap.add v.v_id (loop e1) !var_map;
+									loop2 el
+								end
 							| TVar(v,None) when not (com.platform = Php) ->
 								begin match el with
 									| {eexpr = TBinop(OpAssign,{eexpr = TLocal v2},e2)} :: el when v == v2 ->
@@ -1242,11 +1246,15 @@ end
 module LocalDce = struct
 	let apply e =
 		let is_used v = Meta.has Meta.Used v.v_meta || type_has_analyzer_option v.v_type flag_no_local_dce in
+		let is_ref_type t = match t with
+			| TType({t_path = ["cs"],("Ref" | "Out")},_) -> true
+			| _ -> false
+		in
 		let use v = v.v_meta <- (Meta.Used,[],Ast.null_pos) :: v.v_meta in
 		let has_side_effect e = match e.eexpr with
 			| TVar(v,None) -> is_used v
 			| TVar(v,Some e1) -> is_used v || Optimizer.has_side_effect e1
-			| TBinop(OpAssign,{eexpr = TLocal v},e2) -> is_used v || Optimizer.has_side_effect e2
+			| TBinop((OpAssign | OpAssignOp _),{eexpr = TLocal v},e2) -> is_used v || Optimizer.has_side_effect e2 || is_ref_type v.v_type
 			| _ -> Optimizer.has_side_effect e
 		in
 		let rec collect e = match e.eexpr with
@@ -1258,47 +1266,56 @@ module LocalDce = struct
 			| _ ->
 				Type.iter collect e
 		in
-		let rec loop e = match e.eexpr with
+		let rec loop need_val e = match e.eexpr with
 			| TLocal v ->
 				use v;
 				e
 			| TBinop(OpAssign,({eexpr = TLocal v} as e1),e2) ->
-				let e2 = loop e2 in
-				if not (is_used v) then
+				let e2 = loop false e2 in
+				if not (is_used v) && not (is_ref_type v.v_type) then
 					e2
 				else
 					{e with eexpr = TBinop(OpAssign,{e1 with eexpr = TLocal v},e2)}
 			| TVar(v,Some e1) when not (is_used v) ->
-				let e1 = loop e1 in
+				let e1 = loop true e1 in
 				e1
 			| TWhile(e1,e2,flag) ->
 				collect e2;
-				let e2 = loop e2 in
-				let e1 = loop e1 in
+				let e2 = loop false e2 in
+				let e1 = loop false e1 in
 				{e with eexpr = TWhile(e1,e2,flag)}
 			| TFor(v,e1,e2) ->
 				collect e2;
-				let e2 = loop e2 in
-				let e1 = loop e1 in
+				let e2 = loop false e2 in
+				let e1 = loop false e1 in
 				{e with eexpr = TFor(v,e1,e2)}
 			| TBlock el ->
 				let rec block el = match el with
 					| e :: el ->
 						let el = block el in
-						if el <> [] && not (has_side_effect e) then
+						if not need_val && not (has_side_effect e) then
 							el
 						else begin
-							let e = loop e in
+							let e = loop false e in
 							e :: el
 						end
 					| [] ->
 						[]
 				in
 				{e with eexpr = TBlock (block el)}
+			| TCall(e1, el) ->
+				let e1 = loop false e1 in
+				let el = List.map (loop true) el in
+				{e with eexpr = TCall(e1,el)}
+			| TIf(e1,e2,e3) ->
+				let e3 = match e3 with None -> None | Some e -> Some (loop need_val e) in
+				let e2 = loop need_val e2 in
+				let e1 = loop false e1 in
+				{e with eexpr = TIf(e1,e2,e3)}
 			| _ ->
-				Type.map_expr loop e
+				Type.map_expr (loop false) e
 		in
-		loop e
+		loop false e
 end
 
 module Config = struct

+ 2 - 0
common.ml

@@ -197,6 +197,7 @@ module Define = struct
 		| JsEs5
 		| JsFlatten
 		| KeepOldOutput
+		| LoopUnrollMaxCost
 		| Macro
 		| MacroTimes
 		| NekoSource
@@ -277,6 +278,7 @@ module Define = struct
 		| JsEs5 -> ("js_es5","Generate JS for ES5-compliant runtimes")
 		| JsFlatten -> ("js_flatten","Generate classes to use fewer object property lookups")
 		| KeepOldOutput -> ("keep_old_output","Keep old source files in the output directory (for C#/Java)")
+		| LoopUnrollMaxCost -> ("loop_unroll_max_cost","Maximum cost (number of expressions * iterations) before loop unrolling is canceled (default 250)")
 		| Macro -> ("macro","Defined when we compile code in the macro context")
 		| MacroTimes -> ("macro_times","Display per-macro timing when used with --times")
 		| NetVer -> ("net_ver", "<version:20-45> Sets the .NET version to be targeted")

+ 30 - 0
optimizer.ml

@@ -665,6 +665,36 @@ let rec optimize_for_loop ctx (i,pi) e1 e2 p =
 					NormalWhile
 				)) t_void p;
 			])
+	| TArrayDecl el, TInst({ cl_path = [],"Array" },[pt]) ->
+		begin try
+			let num_expr = ref 0 in
+			let rec loop e = match fst e with
+				| EContinue | EBreak ->
+					raise Exit
+				| _ ->
+					incr num_expr;
+					Ast.map_expr loop e
+			in
+			ignore(loop e2);
+			let v = add_local ctx i pt in
+			let e2 = type_expr ctx e2 NoValue in
+			let cost = (List.length el) * !num_expr in
+			let max_cost = try
+				int_of_string (Common.defined_value ctx.com Define.LoopUnrollMaxCost)
+			with Not_found ->
+				250
+			in
+			if cost > max_cost then raise Exit;
+			let eloc = mk (TLocal v) v.v_type p in
+			let el = List.map (fun e ->
+				let e_assign = mk (TBinop(OpAssign,eloc,e)) e.etype e.epos in
+				concat e_assign e2
+			) el in
+			let ev = mk (TVar(v, None)) ctx.t.tvoid p in
+			Some (mk (TBlock (ev :: el)) ctx.t.tvoid p)
+		with Exit ->
+			gen_int_iter pt get_next_array_element get_array_length
+		end
 	| _ , TInst({ cl_path = [],"Array" },[pt])
 	| _ , TInst({ cl_path = ["flash"],"Vector" },[pt]) ->
 		gen_int_iter pt get_next_array_element get_array_length

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

@@ -13,7 +13,7 @@ class TestJs {
 	//Std.string(x);
 	//}
 
-	@:js("var a = new List();var _g_head = a.h;var _g_val = null;while(_g_head != null) {var tmp;_g_val = _g_head[0];_g_head = _g_head[1];tmp = _g_val;tmp;}")
+	@:js("var a = new List();var _g_head = a.h;var _g_val = null;while(_g_head != null) {var tmp;_g_val = _g_head[0];_g_head = _g_head[1];tmp = _g_val;}")
 	static function testListIteratorInline() {
 		var a = new List();
 		for (v in a) { }
@@ -22,6 +22,7 @@ class TestJs {
 	@:js("var a = 1;var tmp;var v2 = a;tmp = a + v2;if(tmp > 0) {}")
 	@:analyzer(no_const_propagation)
 	@:analyzer(no_check_has_effect)
+	@:analyzer(no_local_dce)
 	static function testInlineWithArgumentUsedMoreThanOnce() {
 		var a = 1;
 		if (_inlineWithArgumentUsedMoreThanOnce(a) > 0) { }
@@ -34,6 +35,7 @@ class TestJs {
 
 	@:js("var a = [];var tmp;try {tmp = a[0];} catch( e ) {tmp = null;}if(tmp) {}")
 	@:analyzer(no_check_has_effect)
+	@:analyzer(no_local_dce)
 	static function testInlineWithComplexExpr() {
 		var a = [];
 		if (_inlineWithComplexExpr(a, 0)) {}

+ 92 - 17
tests/optimization/src/TestLocalDce.hx

@@ -15,24 +15,25 @@ private abstract MyEnum(String) to String {
 
 @:analyzer(no_check_has_effect)
 class TestLocalDce {
-	@:js('3;')
+	@:js('console.log(3);')
 	static function testNoOpRemoval() {
 		1;
 		2;
 		{}
-		3;
+		trace(3);
 	}
 
 	@:js('
-		27;
+		console.log(27);
 	')
 	static function testConstMath() {
 		var a = 1 + 2;
 		var b = 9 * 3;
+		trace(b);
 	}
 
 	@:js('
-		"foo";
+		console.log("foo");
 	')
 	static function testInlineCtor1() {
 		var c = new InlineCtor(12, "foo");
@@ -40,10 +41,11 @@ class TestLocalDce {
 		c.x = 13;
 		x = c.x;
 		var y = c.y;
+		trace(y);
 	}
 
 	@:js('
-		12;
+		console.log(12);
 	')
 	static function testInlineCtor2() {
 		var a = 0;
@@ -52,11 +54,11 @@ class TestLocalDce {
 			a = 2;
 			new InlineCtor(12, "foo");
 		}
-		a = c.x;
+		trace(a = c.x);
 	}
 
 	@:js('
-		1;
+		console.log(1);
 	')
 	static function testInlineCtor3() {
 		var a = 0;
@@ -65,11 +67,11 @@ class TestLocalDce {
 			a = 1;
 			new InlineCtor(2, "b");
 		}
-		b.x = a;
+		trace(b.x = a);
 	}
 
 	@:js('
-		2;
+		console.log(2);
 	')
 	static function testStructureInline1() {
 		var x = {
@@ -78,47 +80,120 @@ class TestLocalDce {
 		}
 		var y = x.foo;
 		var z = x.bar;
+		trace(z);
 	}
 
 	@:js('
-		"god";
+		TestLocalDce.keep("god");
 	')
 	static function testStructureInlineInvalidField() {
         var x = {
-            "oh-my": "god"
+            "oh-my": keep("god")
         };
 	}
 
 	@:js('
-		2;
+		console.log(2);
 	')
 	static function testArrayInline() {
 		var a = [1, 2];
 		var b = a.length;
+		trace(b);
 	}
 
 	@:js('
 		var a = [1,2];
-		a[-1];
+		console.log(a[-1]);
 	')
 	static function testArrayInlineCancelNegative() {
 		var a = [1, 2];
-		a[-1];
+		trace(a[-1]);
 	}
 
 	@:js('
 		var a = [1,2];
-		a[2];
+		console.log(a[2]);
 	')
 	static function testArrayInlineCancelExceeds() {
 		var a = [1, 2];
-		a[2];
+		trace(a[2]);
 	}
 
 	@:js('
-		"" + "a";
+		var s = "" + "a";
+		console.log(s);
 	')
 	static function testAbstractOverStringBinop() {
 		var s = "" + A;
+		trace(s);
+	}
+
+	@:js('
+		var s = TestLocalDce.keep(1);
+		s += 0;
+		s += 6;
+		s += 8;
+		console.log(s);
+	')
+	static function testLoopUnroll() {
+		var s = keep(1);
+		for (i in [0, 3, 4]) {
+			s += i * 2;
+		}
+		trace(s);
+	}
+
+	@:js('console.log(5.);')
+	static function testLoopUnrollDavid() {
+		var s = 0.0;
+		inline function foo(r)
+			return 2.0 + r;
+		for ( r in [0.0,1.0] )
+			s+=foo(r);
+		trace(s);
 	}
+
+	@:js('
+		var s = TestLocalDce.keep(1);
+		var _g = 0;
+		var _g1 = [0,3,4];
+		while(_g < _g1.length) {
+			var i = _g1[_g];
+			++_g;
+			s += i * 2;
+			continue;
+		}
+		console.log(s);
+	')
+	static function testLoopUnrollContinue() {
+		var s = keep(1);
+		for (i in [0, 3, 4]) {
+			s += i * 2;
+			continue;
+		}
+		trace(s);
+	}
+
+	@:js('
+		var s = TestLocalDce.keep(1);
+		var _g = 0;
+		var _g1 = [0,3,4];
+		while(0 < _g1.length) {
+			var i = _g1[0];
+			++_g;
+			s += i * 2;
+			break;
+		}
+		console.log(s);
+	')
+	static function testLoopUnrollBreak() {
+		var s = keep(1);
+		for (i in [0, 3, 4]) {
+			s += i * 2;
+			break;
+		}
+		trace(s);
+	}
+
+	static function keep(v:Dynamic) { return v; }
 }

+ 9 - 0
tests/unit/src/unit/TestCSharp.hx

@@ -295,6 +295,11 @@ class TestCSharp extends Test
 		i *= 2;
 	}
 
+	@:skipReflection private function refTestAssign(i:cs.Ref<Int>):Void
+	{
+		i = 2;
+	}
+
 	@:skipReflection private function outTest(out:cs.Out<Int>, x:Int):Void
 	{
 		out = x * 2;
@@ -317,6 +322,10 @@ class TestCSharp extends Test
 
 	public function testRef()
 	{
+		var i = 10;
+		refTestAssign(i);
+		eq(i, 2);
+
 		var i = 10;
 		refTest(i);
 		eq(i, 20);