transform: Add FoldTrivialSingleUseLets

This transform is intended to clean up the output of the SPIR-V reader, so that we can pattern match loops that can be transformed into a for-loop.

Bug: tint:952
Change-Id: Iba58e4e1f6e20daaf7715e493df53346cdb7c89f
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56766
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
diff --git a/include/tint/tint.h b/include/tint/tint.h
index 9a51bbe..d989f92 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -26,6 +26,7 @@
 #include "src/sem/type_manager.h"
 #include "src/transform/binding_remapper.h"
 #include "src/transform/first_index_offset.h"
+#include "src/transform/fold_trivial_single_use_lets.h"
 #include "src/transform/manager.h"
 #include "src/transform/renamer.h"
 #include "src/transform/robustness.h"
diff --git a/samples/main.cc b/samples/main.cc
index e80d9f4..49b8a93 100644
--- a/samples/main.cc
+++ b/samples/main.cc
@@ -88,6 +88,7 @@
   --transform <name list>   -- Runs transforms, name list is comma separated
                                Available transforms:
                                 first_index_offset
+                                fold_trivial_single_use_lets
                                 renamer
                                 robustness
   --parse-only              -- Stop after parsing the input
@@ -708,6 +709,8 @@
       transform_inputs.Add<tint::transform::FirstIndexOffset::BindingPoint>(0,
                                                                             0);
       transform_manager.Add<tint::transform::FirstIndexOffset>();
+    } else if (name == "fold_trivial_single_use_lets") {
+      transform_manager.Add<tint::transform::FoldTrivialSingleUseLets>();
     } else if (name == "renamer") {
       transform_manager.Add<tint::transform::Renamer>();
     } else if (name == "robustness") {
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 784edba..9fb6ab9 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -568,6 +568,8 @@
     "transform/first_index_offset.h",
     "transform/fold_constants.cc",
     "transform/fold_constants.h",
+    "transform/fold_trivial_single_use_lets.cc",
+    "transform/fold_trivial_single_use_lets.h",
     "transform/inline_pointer_lets.cc",
     "transform/inline_pointer_lets.h",
     "transform/manager.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 51dc420..de1abeb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -293,6 +293,8 @@
   transform/first_index_offset.h
   transform/fold_constants.cc
   transform/fold_constants.h
+  transform/fold_trivial_single_use_lets.cc
+  transform/fold_trivial_single_use_lets.h
   transform/inline_pointer_lets.cc
   transform/inline_pointer_lets.h
   transform/manager.cc
@@ -875,6 +877,7 @@
       transform/external_texture_transform_test.cc
       transform/first_index_offset_test.cc
       transform/fold_constants_test.cc
+      transform/fold_trivial_single_use_lets_test.cc
       transform/inline_pointer_lets_test.cc
       transform/pad_array_elements_test.cc
       transform/promote_initializers_to_const_var_test.cc
diff --git a/src/transform/fold_trivial_single_use_lets.cc b/src/transform/fold_trivial_single_use_lets.cc
new file mode 100644
index 0000000..d1deb8f
--- /dev/null
+++ b/src/transform/fold_trivial_single_use_lets.cc
@@ -0,0 +1,93 @@
+// 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/fold_trivial_single_use_lets.h"
+
+#include "src/program_builder.h"
+#include "src/sem/block_statement.h"
+#include "src/sem/function.h"
+#include "src/sem/statement.h"
+#include "src/sem/variable.h"
+#include "src/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::FoldTrivialSingleUseLets);
+
+namespace tint {
+namespace transform {
+namespace {
+
+ast::VariableDeclStatement* AsTrivialLetDecl(ast::Statement* stmt) {
+  auto* var_decl = stmt->As<ast::VariableDeclStatement>();
+  if (!var_decl) {
+    return nullptr;
+  }
+  auto* var = var_decl->variable();
+  if (!var->is_const()) {
+    return nullptr;
+  }
+  auto* ctor = var->constructor();
+  if (!IsAnyOf<ast::IdentifierExpression, ast::ScalarConstructorExpression>(
+          ctor)) {
+    return nullptr;
+  }
+  return var_decl;
+}
+
+}  // namespace
+
+FoldTrivialSingleUseLets::FoldTrivialSingleUseLets() = default;
+
+FoldTrivialSingleUseLets::~FoldTrivialSingleUseLets() = default;
+
+void FoldTrivialSingleUseLets::Run(CloneContext& ctx,
+                                   const DataMap&,
+                                   DataMap&) {
+  for (auto* node : ctx.src->ASTNodes().Objects()) {
+    if (auto* block = node->As<ast::BlockStatement>()) {
+      auto& stmts = block->statements();
+      for (size_t stmt_idx = 0; stmt_idx < stmts.size(); stmt_idx++) {
+        auto* stmt = stmts[stmt_idx];
+        if (auto* let_decl = AsTrivialLetDecl(stmt)) {
+          auto* let = let_decl->variable();
+          auto* sem_let = ctx.src->Sem().Get(let);
+          auto& users = sem_let->Users();
+          if (users.size() != 1) {
+            continue;  // Does not have a single user.
+          }
+
+          auto* user = users[0];
+          auto* user_stmt = user->Stmt()->Declaration();
+
+          for (size_t i = stmt_idx; i < stmts.size(); i++) {
+            if (user_stmt == stmts[i]) {
+              auto* user_expr = user->Declaration();
+              ctx.Remove(stmts, let_decl);
+              ctx.Replace(user_expr, ctx.Clone(let->constructor()));
+            }
+            if (!AsTrivialLetDecl(stmts[i])) {
+              // Stop if we hit a statement that isn't the single use of the
+              // let, and isn't a let itself.
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  ctx.Clone();
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/transform/fold_trivial_single_use_lets.h b/src/transform/fold_trivial_single_use_lets.h
new file mode 100644
index 0000000..05ff33e
--- /dev/null
+++ b/src/transform/fold_trivial_single_use_lets.h
@@ -0,0 +1,59 @@
+// 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_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
+#define SRC_TRANSFORM_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// FoldTrivialSingleUseLets is an optimizer for folding away trivial `let`
+/// statements into their single place of use. This transform is intended to
+/// clean up the SSA `let`s produced by the SPIR-V reader.
+/// `let`s can only be folded if:
+/// * There is a single usage of the `let` value.
+/// * The `let` is constructed with a ScalarConstructorExpression, or with an
+///   IdentifierExpression.
+/// * There are only other foldable `let`s between the `let` declaration and its
+///   single usage.
+/// These rules prevent any hoisting of the let that may affect execution
+/// behaviour.
+class FoldTrivialSingleUseLets
+    : public Castable<FoldTrivialSingleUseLets, Transform> {
+ public:
+  /// Constructor
+  FoldTrivialSingleUseLets();
+
+  /// Destructor
+  ~FoldTrivialSingleUseLets() 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_FOLD_TRIVIAL_SINGLE_USE_LETS_H_
diff --git a/src/transform/fold_trivial_single_use_lets_test.cc b/src/transform/fold_trivial_single_use_lets_test.cc
new file mode 100644
index 0000000..b571ce8
--- /dev/null
+++ b/src/transform/fold_trivial_single_use_lets_test.cc
@@ -0,0 +1,149 @@
+// 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/fold_trivial_single_use_lets.h"
+
+#include "src/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using FoldTrivialSingleUseLetsTest = TransformTest;
+
+TEST_F(FoldTrivialSingleUseLetsTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = "";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, Single) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  ignore(x);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  ignore(1);
+}
+)";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, Multiple) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  let y = 2;
+  let z = 3;
+  ignore(x + y + z);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  ignore(((1 + 2) + 3));
+}
+)";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, Chained) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  let y = x;
+  let z = y;
+  ignore(z);
+}
+)";
+
+  auto* expect = R"(
+fn f() {
+  ignore(1);
+}
+)";
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_NonTrivialLet) {
+  auto* src = R"(
+fn function_with_posssible_side_effect() -> i32 {
+  return 1;
+}
+
+fn f() {
+  let x = 1;
+  let y = function_with_posssible_side_effect();
+  ignore((x + y));
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_UseInSubBlock) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  {
+    ignore(x);
+  }
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(FoldTrivialSingleUseLetsTest, NoFold_MultipleUses) {
+  auto* src = R"(
+fn f() {
+  let x = 1;
+  ignore((x + x));
+}
+)";
+
+  auto* expect = src;
+
+  auto got = Run<FoldTrivialSingleUseLets>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 14ce32f..87941ac 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -284,6 +284,8 @@
     "../src/transform/decompose_memory_access_test.cc",
     "../src/transform/external_texture_transform_test.cc",
     "../src/transform/first_index_offset_test.cc",
+    "../src/transform/fold_constants_test.cc",
+    "../src/transform/fold_trivial_single_use_lets_test.cc",
     "../src/transform/inline_pointer_lets_test.cc",
     "../src/transform/pad_array_elements_test.cc",
     "../src/transform/promote_initializers_to_const_var_test.cc",