[transform] Add ForLoopToLoop

Transforms a for-loop into a loop.
Will be required by the SPIR-V writer.

Bug: tint:952
Change-Id: Iba859bd144d702cee85374f2cfcb94cd7465ca98
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57202
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 08ff4d0..3977915 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -570,6 +570,8 @@
     "transform/fold_constants.h",
     "transform/fold_trivial_single_use_lets.cc",
     "transform/fold_trivial_single_use_lets.h",
+    "transform/for_loop_to_loop.cc",
+    "transform/for_loop_to_loop.h",
     "transform/inline_pointer_lets.cc",
     "transform/inline_pointer_lets.h",
     "transform/loop_to_for_loop.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3d2d8d6..4143cfa 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -295,6 +295,8 @@
   transform/fold_constants.h
   transform/fold_trivial_single_use_lets.cc
   transform/fold_trivial_single_use_lets.h
+  transform/for_loop_to_loop.cc
+  transform/for_loop_to_loop.h
   transform/inline_pointer_lets.cc
   transform/inline_pointer_lets.h
   transform/loop_to_for_loop.cc
@@ -882,6 +884,7 @@
       transform/first_index_offset_test.cc
       transform/fold_constants_test.cc
       transform/fold_trivial_single_use_lets_test.cc
+      transform/for_loop_to_loop_test.cc
       transform/inline_pointer_lets_test.cc
       transform/loop_to_for_loop_test.cc
       transform/pad_array_elements_test.cc
diff --git a/src/transform/for_loop_to_loop.cc b/src/transform/for_loop_to_loop.cc
new file mode 100644
index 0000000..5f553a8
--- /dev/null
+++ b/src/transform/for_loop_to_loop.cc
@@ -0,0 +1,65 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/transform/for_loop_to_loop.h"
+
+#include "src/ast/break_statement.h"
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::ForLoopToLoop);
+
+namespace tint {
+namespace transform {
+ForLoopToLoop::ForLoopToLoop() = default;
+
+ForLoopToLoop::~ForLoopToLoop() = default;
+
+void ForLoopToLoop::Run(CloneContext& ctx, const DataMap&, DataMap&) {
+  ctx.ReplaceAll([&](ast::ForLoopStatement* for_loop) -> ast::Statement* {
+    ast::StatementList stmts;
+    if (auto* cond = for_loop->condition()) {
+      // !condition
+      auto* not_cond = ctx.dst->create<ast::UnaryOpExpression>(
+          ast::UnaryOp::kNot, ctx.Clone(cond));
+
+      // { break; }
+      auto* break_body = ctx.dst->Block(ctx.dst->create<ast::BreakStatement>());
+
+      // if (!condition) { break; }
+      stmts.emplace_back(ctx.dst->If(not_cond, break_body));
+    }
+    for (auto* stmt : for_loop->body()->statements()) {
+      stmts.emplace_back(ctx.Clone(stmt));
+    }
+
+    ast::BlockStatement* continuing = nullptr;
+    if (auto* cont = for_loop->continuing()) {
+      continuing = ctx.dst->Block(ctx.Clone(cont));
+    }
+
+    auto* body = ctx.dst->Block(stmts);
+    auto* loop = ctx.dst->create<ast::LoopStatement>(body, continuing);
+
+    if (auto* init = for_loop->initializer()) {
+      return ctx.dst->Block(ctx.Clone(init), loop);
+    }
+
+    return loop;
+  });
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/transform/for_loop_to_loop.h b/src/transform/for_loop_to_loop.h
new file mode 100644
index 0000000..0b738ec
--- /dev/null
+++ b/src/transform/for_loop_to_loop.h
@@ -0,0 +1,46 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TRANSFORM_FOR_LOOP_TO_LOOP_H_
+#define SRC_TRANSFORM_FOR_LOOP_TO_LOOP_H_
+
+#include "src/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// ForLoopToLoop is a Transform that converts a for-loop statement into a loop
+/// statement. This is required by the SPIR-V writer.
+class ForLoopToLoop : public Castable<ForLoopToLoop, Transform> {
+ public:
+  /// Constructor
+  ForLoopToLoop();
+
+  /// Destructor
+  ~ForLoopToLoop() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx, const DataMap& inputs, DataMap& outputs) override;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TRANSFORM_FOR_LOOP_TO_LOOP_H_
diff --git a/src/transform/for_loop_to_loop_test.cc b/src/transform/for_loop_to_loop_test.cc
new file mode 100644
index 0000000..aab88ec
--- /dev/null
+++ b/src/transform/for_loop_to_loop_test.cc
@@ -0,0 +1,341 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/transform/for_loop_to_loop.h"
+
+#include "src/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using ForLoopToLoopTest = TransformTest;
+
+TEST_F(ForLoopToLoopTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = src;
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test an empty for loop.
+TEST_F(ForLoopToLoopTest, Empty) {
+  auto* src = R"(
+fn f() {
+  for (;;) {
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  loop {
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop with non-empty body.
+TEST_F(ForLoopToLoopTest, Body) {
+  auto* src = R"(
+fn f() {
+  for (;;) {
+    discard;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  loop {
+    discard;
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop declaring a variable in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementDecl) {
+  auto* src = R"(
+fn f() {
+  for (var i: i32;;) {
+
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    var i : i32;
+    loop {
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop declaring and initializing a variable in the initializer
+// statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementDeclEqual) {
+  auto* src = R"(
+fn f() {
+  for (var i: i32 = 0;;) {
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    var i : i32 = 0;
+    loop {
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop declaring a const variable in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementConstDecl) {
+  auto* src = R"(
+fn f() {
+  for (let i: i32 = 0;;) {
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  {
+    let i : i32 = 0;
+    loop {
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop assigning a variable in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementAssignment) {
+  auto* src = R"(
+fn f() {
+  var i: i32;
+  for (i = 0;;) {
+
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var i : i32;
+  {
+    i = 0;
+    loop {
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop calling a function in the initializer statement.
+TEST_F(ForLoopToLoopTest, InitializerStatementFuncCall) {
+  auto* src = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  for (a(b,c);;) { }
+}
+)";
+
+  auto* expect = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  {
+    a(b, c);
+    loop {
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop with a break condition
+TEST_F(ForLoopToLoopTest, BreakCondition) {
+  auto* src = R"(
+fn f() {
+  for (; 0 == 1;) {
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  loop {
+    if (!((0 == 1))) {
+      break;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop assigning a variable in the continuing statement.
+TEST_F(ForLoopToLoopTest, ContinuingAssignment) {
+  auto* src = R"(
+fn f() {
+  var x: i32;
+  for (;;x = 2) {
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var x : i32;
+  loop {
+
+    continuing {
+      x = 2;
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop calling a function in the continuing statement.
+TEST_F(ForLoopToLoopTest, ContinuingFuncCall) {
+  auto* src = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  for (;;a(b,c)) {
+  }
+}
+)";
+
+  auto* expect = R"(
+fn a(x : i32, y : i32) {
+}
+
+fn f() {
+  var b : i32;
+  var c : i32;
+  loop {
+
+    continuing {
+      a(b, c);
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+// Test a for loop with all statements non-empty.
+TEST_F(ForLoopToLoopTest, All) {
+  auto* src = R"(
+fn f() {
+  var a : i32;
+  for(var i : i32 = 0; i < 4; i = i + 1) {
+    if (a == 0) {
+      continue;
+    }
+    a = a + 2;
+  }
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  var a : i32;
+  {
+    var i : i32 = 0;
+    loop {
+      if (!((i < 4))) {
+        break;
+      }
+      if ((a == 0)) {
+        continue;
+      }
+      a = (a + 2);
+
+      continuing {
+        i = (i + 1);
+      }
+    }
+  }
+}
+)";
+
+  auto got = Run<ForLoopToLoop>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/transform/loop_to_for_loop.h b/src/transform/loop_to_for_loop.h
index a227d70..0c855fb 100644
--- a/src/transform/loop_to_for_loop.h
+++ b/src/transform/loop_to_for_loop.h
@@ -15,9 +15,6 @@
 #ifndef SRC_TRANSFORM_LOOP_TO_FOR_LOOP_H_
 #define SRC_TRANSFORM_LOOP_TO_FOR_LOOP_H_
 
-#include <string>
-#include <unordered_map>
-
 #include "src/transform/transform.h"
 
 namespace tint {
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 1e1d989..c6c8b8f 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -286,6 +286,7 @@
     "../src/transform/first_index_offset_test.cc",
     "../src/transform/fold_constants_test.cc",
     "../src/transform/fold_trivial_single_use_lets_test.cc",
+    "../src/transform/for_loop_to_loop_test.cc",
     "../src/transform/inline_pointer_lets_test.cc",
     "../src/transform/loop_to_for_loop_test.cc",
     "../src/transform/pad_array_elements_test.cc",