[dart2js] Choose between while- and for- loops

Change-Id: I6b564775e42649a974b72331528e02803a41616e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/447880
Reviewed-by: Mayank Patke <fishythefish@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/js/rewrite_async.dart b/pkg/compiler/lib/src/js/rewrite_async.dart
index 9c05884..065b576 100644
--- a/pkg/compiler/lib/src/js/rewrite_async.dart
+++ b/pkg/compiler/lib/src/js/rewrite_async.dart
@@ -776,7 +776,7 @@
       rewrittenBody = js.LabeledStatement(outerLabelName, rewrittenBody);
     }
     rewrittenBody = js.js
-        .statement('while (true) #', rewrittenBody)
+        .statement('for (;;) #', rewrittenBody)
         .withSourceInformation(bodySourceInformation);
     List<js.VariableInitialization> variables = [];
 
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 519cacf..394f212 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -1109,11 +1109,11 @@
           currentContainer = body;
           visitBodyIgnoreLabels(info);
           currentContainer = oldContainer;
-          loop = js.For(
-            jsInitialization,
-            jsCondition,
-            jsUpdates,
-            unwrapStatement(body),
+          loop = newLoop(
+            init: jsInitialization,
+            condition: jsCondition,
+            update: jsUpdates,
+            body: unwrapStatement(body),
             sourceInformation: info.sourceInformation,
           );
         } else {
@@ -1129,7 +1129,6 @@
             jsCondition = generateExpression(condition);
             currentContainer = body;
           } else {
-            jsCondition = newLiteralBool(true, info.sourceInformation);
             currentContainer = body;
             generateStatements(condition);
             use(condition.conditionExpression!);
@@ -1151,9 +1150,9 @@
             visitBodyIgnoreLabels(info);
           }
           currentContainer = oldContainer;
-          loop = js.While(
-            jsCondition!,
-            unwrapStatement(body),
+          loop = newLoop(
+            condition: jsCondition,
+            body: unwrapStatement(body),
             sourceInformation: info.sourceInformation,
           );
         }
@@ -1206,9 +1205,8 @@
           // If the condition is dead code, we turn the do-while into
           // a simpler while because we will never reach the condition
           // at the end of the loop anyway.
-          loop = js.While(
-            newLiteralBool(true, info.sourceInformation),
-            unwrapStatement(body),
+          loop = newLoop(
+            body: unwrapStatement(body),
             sourceInformation: info.sourceInformation,
           );
         } else {
@@ -2836,6 +2834,45 @@
     }
   }
 
+  /// Returns a 'for' loop or 'while' loop depending on which parts of a 'for'
+  /// loop are present.  In effect, choose the loop kind by applying these
+  /// reductions:
+  ///
+  ///     for(init;true;update) -->  for(init;;update)
+  ///     for(;cond;)  -->  while(cond)
+  ///     while(true)  -->  for(;;)
+  js.Loop newLoop({
+    js.Expression? init,
+    js.Expression? condition, // `null` means indefinite, i.e. `true`.
+    js.Expression? update,
+    required js.Statement body,
+    SourceInformation? sourceInformation,
+  }) {
+    // `condition` with `true` replaced with `null`:
+    final conditionOrNull = switch (condition) {
+      js.LiteralBool(value: true) => null,
+      js.LiteralNumber(value: '1') => null,
+      // Minified `true` is `!0`:
+      js.Prefix(op: '!', argument: js.LiteralNumber(value: '0')) => null,
+      _ => condition,
+    };
+
+    if (init == null && update == null && conditionOrNull != null) {
+      return js.While(
+        conditionOrNull,
+        body,
+        sourceInformation: sourceInformation,
+      );
+    }
+    return js.For(
+      init,
+      conditionOrNull,
+      update,
+      body,
+      sourceInformation: sourceInformation,
+    );
+  }
+
   void generateConstant(
     ConstantValue constant,
     SourceInformation? sourceInformation,
diff --git a/pkg/compiler/test/async_await/async_await_js_transform_test.dart b/pkg/compiler/test/async_await/async_await_js_transform_test.dart
index e349294..35fd080e 100644
--- a/pkg/compiler/test/async_await/async_await_js_transform_test.dart
+++ b/pkg/compiler/test/async_await/async_await_js_transform_test.dart
@@ -78,7 +78,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -116,7 +116,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -168,7 +168,7 @@
       __errorStack.push(__result);
       __goto = __handler;
     }
-    while (true)
+    for (;;)
       __outer1:
         switch (__goto) {
           case 0:
@@ -281,7 +281,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -338,7 +338,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -474,7 +474,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -563,7 +563,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -628,7 +628,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -713,7 +713,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -799,7 +799,7 @@
       __errorStack.push(__result);
       __goto = __handler;
     }
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -912,7 +912,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -995,7 +995,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -1122,7 +1122,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -1196,7 +1196,7 @@
   var body = _wrapJsFunctionForAsync(function(__errorCode, __result) {
     if (__errorCode === 1)
       return rethrowHelper(__result, __completer);
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -1275,7 +1275,7 @@
       __errorStack.push(__result);
       __goto = __handler;
     }
-    while (true)
+    for (;;)
       switch (__goto) {
         case 0:
           // Function start
@@ -1348,7 +1348,7 @@
         __errorStack.push(__result);
         __goto = __handler;
       }
-      while (true)
+      for (;;)
         switch (__goto) {
           case 0:
             // Function start
diff --git a/pkg/compiler/test/codegen/licm_test.dart b/pkg/compiler/test/codegen/licm_test.dart
index f194de4..2b6e5fb 100644
--- a/pkg/compiler/test/codegen/licm_test.dart
+++ b/pkg/compiler/test/codegen/licm_test.dart
@@ -28,7 +28,7 @@
     await compileAndMatch(
       TEST,
       'main',
-      RegExp('if \\(typeof count !== "number"\\)(.|\\n)*while'),
+      RegExp('if \\(typeof count !== "number"\\)(.|\\n)*(while|for)'),
     );
   }
 
diff --git a/pkg/compiler/test/dump_info/data/deferred_future/main.dart b/pkg/compiler/test/dump_info/data/deferred_future/main.dart
index 5f3d7fb..3c4e219 100644
--- a/pkg/compiler/test/dump_info/data/deferred_future/main.dart
+++ b/pkg/compiler/test/dump_info/data/deferred_future/main.dart
@@ -102,7 +102,7 @@
   "id": "library/memory:sdk/tests/web/native/main.dart::",
   "kind": "library",
   "name": "<unnamed>",
-  "size": 861,
+  "size": 857,
   "children": [
     "function/memory:sdk/tests/web/native/main.dart::main"
   ],
@@ -137,7 +137,7 @@
   "id": "function/memory:sdk/tests/web/native/main.dart::main",
   "kind": "function",
   "name": "main",
-  "size": 861,
+  "size": 857,
   "outputUnit": "outputUnit/main",
   "parent": "library/memory:sdk/tests/web/native/main.dart::",
   "children": [],
@@ -152,7 +152,7 @@
   "parameters": [],
   "sideEffects": "SideEffects(reads nothing; writes nothing)",
   "inlinedCount": 0,
-  "code": "main() {\n      var $async$goto = 0,\n        $async$completer = A._makeAsyncAwaitCompleter(type$.dynamic);\n      var $async$main = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {\n        if ($async$errorCode === 1)\n          return A._asyncRethrow($async$result, $async$completer);\n        while (true)\n          switch ($async$goto) {\n            case 0:\n              // Function start\n              $async$goto = 2;\n              return A._asyncAwait(A.loadDeferredLibrary(\"lib1\", \"\"), $async$main);\n            case 2:\n              // returning from await.\n              A.checkDeferredIsLoaded(\"lib1\");\n              A.checkDeferredIsLoaded(\"lib1\");\n              // implicit return\n              return A._asyncReturn(null, $async$completer);\n          }\n      });\n      return A._asyncStartSync($async$main, $async$completer);\n    }",
+  "code": "main() {\n      var $async$goto = 0,\n        $async$completer = A._makeAsyncAwaitCompleter(type$.dynamic);\n      var $async$main = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {\n        if ($async$errorCode === 1)\n          return A._asyncRethrow($async$result, $async$completer);\n        for (;;)\n          switch ($async$goto) {\n            case 0:\n              // Function start\n              $async$goto = 2;\n              return A._asyncAwait(A.loadDeferredLibrary(\"lib1\", \"\"), $async$main);\n            case 2:\n              // returning from await.\n              A.checkDeferredIsLoaded(\"lib1\");\n              A.checkDeferredIsLoaded(\"lib1\");\n              // implicit return\n              return A._asyncReturn(null, $async$completer);\n          }\n      });\n      return A._asyncStartSync($async$main, $async$completer);\n    }",
   "type": "dynamic Function()",
   "functionKind": 0
 }],