Explorar o código

Reconstruct do-loops (#7979)

* [analyzer] reconstruct do-while loops

closes #5896

* ignore HL

* [jvm] add crude do-while

* [lua] fix do-while loop code generation #7979

* remove bad optimization

* add test, disable for Hushlank

---------

Co-authored-by: Justin Donaldson <[email protected]>
Simon Krajewski %!s(int64=2) %!d(string=hai) anos
pai
achega
1b7d3746a6

+ 6 - 2
src/generators/genjvm.ml

@@ -1933,18 +1933,22 @@ class texpr_to_jvm
 				)
 		| TSwitch(e1,cases,def) ->
 			self#switch ret e1 cases def
-		| TWhile(e1,e2,flag) -> (* TODO: do-while *)
+		| TWhile(e1,e2,flag) ->
 			block_exits <- ExitLoop :: block_exits;
 			let is_true_loop = match (Texpr.skip e1).eexpr with TConst (TBool true) -> true | _ -> false in
 			let continue_label = jm#spawn_label "continue" in
 			let break_label = jm#spawn_label "break" in
 			let body_label = jm#spawn_label "body" in
+			let restore = jm#start_branch in
+			if flag = DoWhile then begin
+				body_label#goto;
+				restore();
+			end;
 			let old_continue = continue in
 			continue <- Some continue_label;
 			let old_break = break in
 			break <- Some break_label;
 			continue_label#here;
-			let restore = jm#start_branch in
 			if not is_true_loop then self#condition false e1 body_label break_label;
 			let pop_scope = jm#push_scope in
 			body_label#here;

+ 12 - 8
src/generators/genlua.ml

@@ -566,20 +566,26 @@ and gen_cond ctx cond =
     gen_value ctx cond;
     ctx.iife_assign <- false
 
-and gen_loop ctx label cond e =
+and gen_loop ctx cond do_while e =
     let old_in_loop = ctx.in_loop in
     ctx.in_loop <- true;
     let old_handle_continue = ctx.handle_continue in
     let will_continue = has_continue e in
     ctx.handle_continue <- has_continue e;
     ctx.break_depth <- ctx.break_depth + 1;
-    if will_continue then begin
+    if will_continue then
         println ctx "local _hx_continue_%i = false;" ctx.break_depth;
-    end;
+    if do_while then
+        println ctx "local _hx_do_first_%i = true;" ctx.break_depth;
     let b = open_block ctx in
-    print ctx "%s " label;
+    print ctx "while ";
     gen_cond ctx cond;
+    if do_while then
+        print ctx " or _hx_do_first_%i" ctx.break_depth;
     print ctx " do ";
+    if do_while then
+        newline ctx;
+        println ctx "_hx_do_first_%i = false;" ctx.break_depth;
     if will_continue then print ctx "repeat ";
     gen_block_element ctx e;
     if will_continue then begin
@@ -955,11 +961,9 @@ and gen_expr ?(local=true) ctx e = begin
         gen_value ctx e;
         spr ctx (Ast.s_unop op)
     | TWhile (cond,e,Ast.NormalWhile) ->
-        gen_loop ctx "while" cond e
+        gen_loop ctx cond false e;
     | TWhile (cond,e,Ast.DoWhile) ->
-        gen_block_element ctx e;
-        newline ctx;
-        gen_loop ctx "while" cond e
+        gen_loop ctx cond true e;
     | TObjectDecl [] ->
         spr ctx "_hx_e()";
         ctx.separator <- true

+ 28 - 1
src/optimization/analyzerTexpr.ml

@@ -1038,16 +1038,43 @@ module Cleanup = struct
 						let e1 = match e1.eexpr with TUnop(_,_,e1) -> e1 | _ -> {e1 with eexpr = TUnop(Not,Prefix,e1)} in
 						{e with eexpr = TWhile(e1,{eb with eexpr = TBlock el2},NormalWhile)}
 					| TBlock el ->
+						let do_while = ref None in
+						let locals = ref IntMap.empty in
+						let rec collect_vars e = match e.eexpr with
+							| TVar(v,e1) ->
+								locals := IntMap.add v.v_id true !locals;
+								Option.may collect_vars e1
+							| _ ->
+								Type.iter collect_vars e
+						in
+						let rec references_local e = match e.eexpr with
+							| 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 ->
+								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 ->
+								do_while := Some econd;
+								[]
 							| {eexpr = TBreak | TContinue | TReturn _ | TThrow _} as e :: el ->
 								[e]
 							| e :: el ->
+								collect_vars e;
 								e :: (loop2 el)
 							| [] ->
 								[]
 						in
 						let el = loop2 el in
-						{e with eexpr = TWhile(e1,{e2 with eexpr = TBlock el},NormalWhile)}
+						begin match !do_while with
+						| None ->
+							{e with eexpr = TWhile(e1,{e2 with eexpr = TBlock el},NormalWhile)}
+						| Some econd ->
+							let econd = {econd with eexpr = TUnop(Not,Prefix,econd)} in
+							{e with eexpr = TWhile(econd,{e2 with eexpr = TBlock el},DoWhile)}
+						end;
 					| _ ->
 						{e with eexpr = TWhile(e1,e2,NormalWhile)}
 				end

+ 22 - 0
tests/optimization/src/TestJs.hx

@@ -26,6 +26,8 @@ private enum EnumFlagTest {
 
 @:analyzer(no_user_var_fusion)
 class TestJs {
+	static var notFalse = true;
+
 	//@:js('var x = 10;"" + x;var x1 = 10;"" + x1;var x2 = 10.0;"" + x2;var x3 = "10";x3;var x4 = true;"" + x4;')
 	//static function testStdString() {
 	//var x = 10;
@@ -720,6 +722,26 @@ class TestJs {
 	static function testIssue10740_forceInlineInSafeNav() {
 		inline Issue10740.inst?.f();
 	}
+
+	@:js('
+		var offset = 0;
+		do {
+			TestJs.use(offset);
+			if(offset >= 3) {
+				break;
+			}
+			offset = 3;
+		} while(TestJs.notFalse);
+	')
+	static function testDoWhile() {
+		var offset = 0;
+		do {
+			use(offset);
+			if (offset >= 3)
+				break;
+			offset = 3;
+		} while (notFalse);
+	}
 }
 
 class Issue9227 {