[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",