2
0
Эх сурвалжийг харах

Merge branch 'development' into hxb_server_cache_simn_cleanup

Simon Krajewski 1 жил өмнө
parent
commit
caa7604e0a

+ 5 - 0
src-json/warning.json

@@ -98,6 +98,11 @@
 		"doc": "A type path is being used that is supposed to be reserved on the current target",
 		"parent": "WTyper"
 	},
+	{
+		"name": "WInlineOptimizedField",
+		"doc": "A cached field which was optimized might lead to different output when inlined",
+		"parent": "WTyper"
+	},
 	{
 		"name": "WPatternMatcher",
 		"doc": "Warnings related to the pattern matcher",

+ 14 - 2
src/filters/filters.ml

@@ -708,6 +708,15 @@ let save_class_state com t =
 			a.a_meta <- List.filter (fun (m,_,_) -> m <> Meta.ValueUsed) a.a_meta
 		)
 
+let might_need_cf_unoptimized c cf =
+	match cf.cf_kind,c.cl_kind with
+	| Method MethInline,_ ->
+		true
+	| _,KGeneric ->
+		true
+	| _ ->
+		has_class_field_flag cf CfGeneric
+
 let run tctx main before_destruction =
 	let com = tctx.com in
 	let detail_times = (try int_of_string (Common.defined_value_safe com ~default:"0" Define.FilterTimes) with _ -> 0) in
@@ -723,8 +732,11 @@ let run tctx main before_destruction =
 				(* Save cf_expr_unoptimized early: We want to inline with the original expression
 				   on the next compilation. *)
 				if not cached then begin
-					let field cf =
-						cf.cf_expr_unoptimized <- cf.cf_expr
+					let field cf = match cf.cf_expr,cf.cf_expr_unoptimized with
+						| Some e,None when might_need_cf_unoptimized cls cf ->
+							cf.cf_expr_unoptimized <- Some e
+						| _ ->
+							()
 					in
 					List.iter field cls.cl_ordered_fields;
 					List.iter field cls.cl_ordered_statics;

+ 1 - 4
src/generators/genhl.ml

@@ -2698,13 +2698,10 @@ and eval_expr ctx e =
 		ctx.m.mbreaks <- [];
 		ctx.m.mcontinues <- [];
 		ctx.m.mloop_trys <- ctx.m.mtrys;
-		let start = jump ctx (fun p -> OJAlways p) in
 		let continue_pos = current_pos ctx in
 		let ret = jump_back ctx in
-		let j = jump_expr ctx cond false in
-		start();
 		ignore(eval_expr ctx eloop);
-		set_curpos ctx (max_pos e);
+		let j = jump_expr ctx cond false in
 		ret();
 		j();
 		List.iter (fun f -> f (current_pos ctx)) ctx.m.mbreaks;

+ 8 - 5
src/generators/hlopt.ml

@@ -947,8 +947,8 @@ let _optimize (f:fundecl) =
 				(* loop : first pass does not recurse, second pass uses cache *)
 				if b2.bloop && b2.bstart < b.bstart then (match b2.bneed_all with None -> acc | Some s -> ISet.union acc s) else
 				ISet.union acc (live b2)
-			) ISet.empty b.bnext in
-			let need_sub = ISet.filter (fun r ->
+			) ISet.empty in
+			let need_sub bl = ISet.filter (fun r ->
 				try
 					let w = PMap.find r b.bwrite in
 					set_live r (w + 1) b.bend;
@@ -956,8 +956,8 @@ let _optimize (f:fundecl) =
 				with Not_found ->
 					set_live r b.bstart b.bend;
 					true
-			) need_sub in
-			let need = ISet.union b.bneed need_sub in
+			) (need_sub bl) in
+			let need = ISet.union b.bneed (need_sub b.bnext) in
 			b.bneed_all <- Some need;
 			if b.bloop then begin
 				(*
@@ -974,8 +974,11 @@ let _optimize (f:fundecl) =
 				in
 				List.iter (fun b2 -> if b2.bstart > b.bstart then clear b2) b.bprev;
 				List.iter (fun b -> ignore(live b)) b.bnext;
+				(* do-while loop : recompute self after recompute all next *)
+				let need = ISet.union b.bneed (need_sub b.bnext) in
+				b.bneed_all <- Some need;
 			end;
-			need
+			Option.get b.bneed_all
 	in
 	ignore(live root);
 

+ 2 - 3
src/optimization/analyzerTexpr.ml

@@ -1069,12 +1069,11 @@ module Cleanup = struct
 							| TLocal v when IntMap.mem v.v_id !locals -> true
 							| _ -> check_expr references_local e
 						in
-						let can_do = match com.platform with Hl -> false | _ -> true in
 						let rec loop2 el = match el with
-							| [{eexpr = TBreak}] when is_true_expr e1 && can_do && not has_continue ->
+							| [{eexpr = TBreak}] when is_true_expr e1 && not has_continue ->
 								do_while := Some (Texpr.Builder.make_bool com.basic true e1.epos);
 								[]
-							| [{eexpr = TIf(econd,{eexpr = TBlock[{eexpr = TBreak}]},None)}] when is_true_expr e1 && not (references_local econd) && can_do && not has_continue ->
+							| [{eexpr = TIf(econd,{eexpr = TBlock[{eexpr = TBreak}]},None)}] when is_true_expr e1 && not (references_local econd) && not has_continue ->
 								do_while := Some econd;
 								[]
 							| {eexpr = TBreak | TContinue | TReturn _ | TThrow _} as e :: el ->

+ 19 - 9
src/typing/calls.ml

@@ -67,17 +67,27 @@ let make_call ctx e params t ?(force_inline=false) p =
 		);
 		let params = List.map (Optimizer.reduce_expression ctx) params in
 		let force_inline = is_forced_inline cl f in
-		(match f.cf_expr_unoptimized,f.cf_expr with
-		| Some {eexpr = TFunction fd},_
-		| None,Some { eexpr = TFunction fd } ->
+		let inline fd =
 			Inline.type_inline ctx f fd ethis params t config p force_inline
+		in
+		begin match f.cf_expr_unoptimized with
+		| Some {eexpr = TFunction fd} ->
+			inline fd
 		| _ ->
-			(*
-				we can't inline because there is most likely a loop in the typing.
-				this can be caused by mutually recursive vars/functions, some of them
-				being inlined or not. In that case simply ignore inlining.
-			*)
-			raise Exit)
+			if has_class_field_flag f CfPostProcessed then
+				warning ctx  WInlineOptimizedField (Printf.sprintf "Inlining of cached field %s might lead to unexpected output" f.cf_name) p;
+			match f.cf_expr with
+			| Some ({ eexpr = TFunction fd } as e) ->
+				f.cf_expr_unoptimized <- Some (e);
+				inline fd
+			| _ ->
+				(*
+					we can't inline because there is most likely a loop in the typing.
+					this can be caused by mutually recursive vars/functions, some of them
+					being inlined or not. In that case simply ignore inlining.
+				*)
+				raise Exit
+		end
 	with Exit ->
 		mk (TCall (e,params)) t p
 

+ 4 - 0
tests/server/src/TestCase.hx

@@ -218,6 +218,10 @@ class TestCase implements ITest {
 		}
 	}
 
+	function assertSilence() {
+		return Assert.isTrue(lastResult.stderr == "");
+	}
+
 	function assertSuccess(?p:haxe.PosInfos) {
 		return Assert.isTrue(0 == errorMessages.length, p);
 	}

+ 1 - 1
tests/server/src/cases/ServerTests.hx

@@ -437,7 +437,7 @@ class ServerTests extends TestCase {
 		vfs.putContent("haxe/ds/Vector.hx", getTemplate("issues/Issue10986/Vector.hx"));
 		var args = ["-main", "Main", "--jvm", "Main.jar"];
 		runHaxe(args);
-		vfs.touchFile("haxe/ds/Vector.hx");
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("haxe/ds/Vector.hx")});
 		runHaxe(args);
 		assertSuccess();
 	}

+ 53 - 0
tests/server/src/cases/issues/Issue11460.hx

@@ -0,0 +1,53 @@
+package cases.issues;
+
+using StringTools;
+
+class Issue11460 extends TestCase {
+	function testClass(_) {
+		var mainContentWithInline = getTemplate("issues/Issue11460/Main.hx");
+		var mainContentWithoutInline = mainContentWithInline.replace("inline", "");
+		vfs.putContent("Main.hx", mainContentWithInline);
+		vfs.putContent("C.hx", getTemplate("issues/Issue11460/C.hx"));
+		var args = ["Main", "--interp"];
+
+		// initial cache
+		runHaxe(args);
+		assertSilence();
+
+		// touching Main doesn't do anything
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Main.hx")});
+		runHaxe(args);
+		assertSilence();
+
+		// touching both doesn't do anything
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Main.hx")});
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("C.hx")});
+		runHaxe(args);
+		assertSilence();
+
+		// removing the inline is fine
+		vfs.putContent("Main.hx", mainContentWithoutInline);
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Main.hx")});
+		runHaxe(args);
+		assertSilence();
+
+		// adding it back is fine too because C is still cached
+		vfs.putContent("Main.hx", mainContentWithInline);
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Main.hx")});
+		runHaxe(args);
+		assertSilence();
+
+		// removing the inline and changing C is still fine
+		vfs.putContent("Main.hx", mainContentWithoutInline);
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Main.hx")});
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("C.hx")});
+		runHaxe(args);
+		assertSilence();
+
+		// but adding it now gives us the warning
+		vfs.putContent("Main.hx", mainContentWithInline);
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Main.hx")});
+		runHaxe(args);
+		utest.Assert.match(~/WInlineOptimizedField/, lastResult.stderr);
+	}
+}

+ 1 - 1
tests/server/src/cases/issues/Issue9358.hx

@@ -6,7 +6,7 @@ class Issue9358 extends TestCase {
 		vfs.putContent("StateHandler.hx", getTemplate("issues/Issue9358/StateHandler.hx"));
 		var args = ["-cp", "src", "-m", "Main", "-hl", "hl.hl"];
 		runHaxe(args);
-		vfs.touchFile("Main.hx");
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Dependency.hx")});
 		runHaxe(args);
 		assertSuccess();
 	}

+ 0 - 10
tests/server/src/utils/Vfs.hx

@@ -17,16 +17,6 @@ class Vfs {
 		FileSystem.createDirectory(physicalPath);
 	}
 
-	public function touchFile(path:String) {
-		var path = getPhysicalPath(path);
-		FileSystem.createDirectory(path.dir);
-		var file = Fs.openSync(path.dir + "/" + path.file + "." + path.ext, 'a');
-		var last = Fs.fstatSync(file).mtime;
-		var notNow = last.delta(1000);
-		Fs.futimesSync(file, notNow, notNow);
-		Fs.closeSync(file);
-	}
-
 	public function overwriteContent(path:String, content:String) {
 		var path = getPhysicalPath(path).toString();
 		if (!FileSystem.exists(path)) {

+ 5 - 0
tests/server/test/templates/issues/Issue11460/C.hx

@@ -0,0 +1,5 @@
+class C {
+	static public function doSomething() {
+		trace("Ok I did something");
+	}
+}

+ 3 - 0
tests/server/test/templates/issues/Issue11460/Main.hx

@@ -0,0 +1,3 @@
+function main() {
+	inline C.doSomething();
+}