Split back out two transforms previously combined into PromoteSideEffectsToDeclTest
That is, bring back both VarForDynamicIndex and
PromoteInitializersToConstVar. This is not a complete revert, though:
* VarForDynamicIndex no longer depends on ForLoopToLoop
* Both can cope with hoisting from "else if"
* More unit tests were added in the interim
Delete PromoteSideEffectsToDecl for now. This may be brought back to
handle ensuring order of evaluation.
Bug: tint:1300
Change-Id: I8bbae46377ec4603cc02c1eb3f0661a8461a19fa
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/79940
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 7e67660..62e24d2 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -470,8 +470,8 @@
"transform/num_workgroups_from_uniform.h",
"transform/pad_array_elements.cc",
"transform/pad_array_elements.h",
- "transform/promote_side_effects_to_decl.cc",
- "transform/promote_side_effects_to_decl.h",
+ "transform/promote_initializers_to_const_var.cc",
+ "transform/promote_initializers_to_const_var.h",
"transform/remove_phonies.cc",
"transform/remove_phonies.h",
"transform/remove_unreachable_statements.cc",
@@ -488,6 +488,10 @@
"transform/transform.h",
"transform/unshadow.cc",
"transform/unshadow.h",
+ "transform/utils/hoist_to_decl_before.cc",
+ "transform/utils/hoist_to_decl_before.h",
+ "transform/var_for_dynamic_index.cc",
+ "transform/var_for_dynamic_index.h",
"transform/vectorize_scalar_matrix_constructors.cc",
"transform/vectorize_scalar_matrix_constructors.h",
"transform/vertex_pulling.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0e6f289..dff6c74 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -343,8 +343,8 @@
transform/num_workgroups_from_uniform.h
transform/pad_array_elements.cc
transform/pad_array_elements.h
- transform/promote_side_effects_to_decl.cc
- transform/promote_side_effects_to_decl.h
+ transform/promote_initializers_to_const_var.cc
+ transform/promote_initializers_to_const_var.h
transform/remove_phonies.cc
transform/remove_phonies.h
transform/remove_unreachable_statements.cc
@@ -363,12 +363,16 @@
transform/unshadow.h
transform/vectorize_scalar_matrix_constructors.cc
transform/vectorize_scalar_matrix_constructors.h
+ transform/var_for_dynamic_index.cc
+ transform/var_for_dynamic_index.h
transform/vertex_pulling.cc
transform/vertex_pulling.h
transform/wrap_arrays_in_structs.cc
transform/wrap_arrays_in_structs.h
transform/zero_init_workgroup_memory.cc
transform/zero_init_workgroup_memory.h
+ transform/utils/hoist_to_decl_before.cc
+ transform/utils/hoist_to_decl_before.h
sem/bool_type.cc
sem/bool_type.h
sem/depth_texture_type.cc
@@ -1011,7 +1015,7 @@
transform/multiplanar_external_texture_test.cc
transform/num_workgroups_from_uniform_test.cc
transform/pad_array_elements_test.cc
- transform/promote_side_effects_to_decl_test.cc
+ transform/promote_initializers_to_const_var_test.cc
transform/remove_phonies_test.cc
transform/remove_unreachable_statements_test.cc
transform/renamer_test.cc
@@ -1020,10 +1024,12 @@
transform/single_entry_point_test.cc
transform/test_helper.h
transform/unshadow_test.cc
+ transform/var_for_dynamic_index_test.cc
transform/vectorize_scalar_matrix_constructors_test.cc
transform/vertex_pulling_test.cc
transform/wrap_arrays_in_structs_test.cc
transform/zero_init_workgroup_memory_test.cc
+ transform/utils/hoist_to_decl_before_test.cc
)
endif()
diff --git a/src/transform/glsl.cc b/src/transform/glsl.cc
index 9ae2371..46cc848 100644
--- a/src/transform/glsl.cc
+++ b/src/transform/glsl.cc
@@ -28,7 +28,7 @@
#include "src/transform/loop_to_for_loop.h"
#include "src/transform/manager.h"
#include "src/transform/pad_array_elements.h"
-#include "src/transform/promote_side_effects_to_decl.h"
+#include "src/transform/promote_initializers_to_const_var.h"
#include "src/transform/remove_phonies.h"
#include "src/transform/renamer.h"
#include "src/transform/simplify_pointers.h"
@@ -89,10 +89,7 @@
data.Add<BindingRemapper::Remappings>(bp, ac, /* mayCollide */ true);
}
manager.Add<ExternalTextureTransform>();
-
- data.Add<PromoteSideEffectsToDecl::Config>(
- /* type_ctor_to_let */ true, /* dynamic_index_to_var */ false);
- manager.Add<PromoteSideEffectsToDecl>();
+ manager.Add<PromoteInitializersToConstVar>();
manager.Add<PadArrayElements>();
manager.Add<AddEmptyEntryPoint>();
diff --git a/src/transform/promote_initializers_to_const_var.cc b/src/transform/promote_initializers_to_const_var.cc
new file mode 100644
index 0000000..0c68118
--- /dev/null
+++ b/src/transform/promote_initializers_to_const_var.cc
@@ -0,0 +1,83 @@
+// Copyright 2022 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/promote_initializers_to_const_var.h"
+#include "src/program_builder.h"
+#include "src/sem/call.h"
+#include "src/sem/statement.h"
+#include "src/sem/type_constructor.h"
+#include "src/transform/utils/hoist_to_decl_before.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::PromoteInitializersToConstVar);
+
+namespace tint::transform {
+
+PromoteInitializersToConstVar::PromoteInitializersToConstVar() = default;
+
+PromoteInitializersToConstVar::~PromoteInitializersToConstVar() = default;
+
+void PromoteInitializersToConstVar::Run(CloneContext& ctx,
+ const DataMap&,
+ DataMap&) const {
+ HoistToDeclBefore hoist_to_decl_before(ctx);
+
+ // Hoists array and structure initializers to a constant variable, declared
+ // just before the statement of usage.
+ auto type_ctor_to_let = [&](const ast::CallExpression* expr) {
+ auto* ctor = ctx.src->Sem().Get(expr);
+ if (!ctor->Target()->Is<sem::TypeConstructor>()) {
+ return true;
+ }
+ auto* sem_stmt = ctor->Stmt();
+ if (!sem_stmt) {
+ // Expression is outside of a statement. This usually means the
+ // expression is part of a global (module-scope) constant declaration.
+ // These must be constexpr, and so cannot contain the type of
+ // expressions that must be sanitized.
+ return true;
+ }
+
+ auto* stmt = sem_stmt->Declaration();
+
+ if (auto* src_var_decl = stmt->As<ast::VariableDeclStatement>()) {
+ if (src_var_decl->variable->constructor == expr) {
+ // This statement is just a variable declaration with the
+ // initializer as the constructor value. This is what we're
+ // attempting to transform to, and so ignore.
+ return true;
+ }
+ }
+
+ auto* src_ty = ctor->Type();
+ if (!src_ty->IsAnyOf<sem::Array, sem::Struct>()) {
+ // We only care about array and struct initializers
+ return true;
+ }
+
+ return hoist_to_decl_before.Add(ctor, expr, true);
+ };
+
+ for (auto* node : ctx.src->ASTNodes().Objects()) {
+ if (auto* call_expr = node->As<ast::CallExpression>()) {
+ if (!type_ctor_to_let(call_expr)) {
+ return;
+ }
+ }
+ }
+
+ hoist_to_decl_before.Apply();
+ ctx.Clone();
+}
+
+} // namespace tint::transform
diff --git a/src/transform/promote_initializers_to_const_var.h b/src/transform/promote_initializers_to_const_var.h
new file mode 100644
index 0000000..310f8fc
--- /dev/null
+++ b/src/transform/promote_initializers_to_const_var.h
@@ -0,0 +1,48 @@
+// Copyright 2022 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_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
+#define SRC_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
+
+#include "src/transform/transform.h"
+
+namespace tint::transform {
+
+/// A transform that hoists the array and structure initializers to a constant
+/// variable, declared just before the statement of usage.
+/// @see crbug.com/tint/406
+class PromoteInitializersToConstVar
+ : public Castable<PromoteInitializersToConstVar, Transform> {
+ public:
+ /// Constructor
+ PromoteInitializersToConstVar();
+
+ /// Destructor
+ ~PromoteInitializersToConstVar() 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) const override;
+};
+
+} // namespace tint::transform
+
+#endif // SRC_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
diff --git a/src/transform/promote_initializers_to_const_var_test.cc b/src/transform/promote_initializers_to_const_var_test.cc
new file mode 100644
index 0000000..c9210dd
--- /dev/null
+++ b/src/transform/promote_initializers_to_const_var_test.cc
@@ -0,0 +1,491 @@
+// 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/promote_initializers_to_const_var.h"
+
+#include "src/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using PromoteInitializersToConstVarTest = TransformTest;
+
+TEST_F(PromoteInitializersToConstVarTest, EmptyModule) {
+ auto* src = "";
+ auto* expect = "";
+
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, BasicArray) {
+ auto* src = R"(
+fn f() {
+ var f0 = 1.0;
+ var f1 = 2.0;
+ var f2 = 3.0;
+ var f3 = 4.0;
+ var i = array<f32, 4u>(f0, f1, f2, f3)[2];
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var f0 = 1.0;
+ var f1 = 2.0;
+ var f2 = 3.0;
+ var f3 = 4.0;
+ let tint_symbol = array<f32, 4u>(f0, f1, f2, f3);
+ var i = tint_symbol[2];
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, BasicStruct) {
+ auto* src = R"(
+struct S {
+ a : i32;
+ b : f32;
+ c : vec3<f32>;
+};
+
+fn f() {
+ var x = S(1, 2.0, vec3<f32>()).b;
+}
+)";
+
+ auto* expect = R"(
+struct S {
+ a : i32;
+ b : f32;
+ c : vec3<f32>;
+}
+
+fn f() {
+ let tint_symbol = S(1, 2.0, vec3<f32>());
+ var x = tint_symbol.b;
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInit) {
+ auto* src = R"(
+fn f() {
+ var insert_after = 1;
+ for(var i = array<f32, 4u>(0.0, 1.0, 2.0, 3.0)[2]; ; ) {
+ break;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var insert_after = 1;
+ let tint_symbol = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+ for(var i = tint_symbol[2]; ; ) {
+ break;
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, StructInForLoopInit) {
+ auto* src = R"(
+struct S {
+ a : i32;
+ b : f32;
+ c : vec3<f32>;
+};
+
+fn f() {
+ var insert_after = 1;
+ for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
+ break;
+ }
+}
+)";
+
+ auto* expect = R"(
+struct S {
+ a : i32;
+ b : f32;
+ c : vec3<f32>;
+}
+
+fn f() {
+ var insert_after = 1;
+ let tint_symbol = S(1, 2.0, vec3<f32>());
+ for(var x = tint_symbol.b; ; ) {
+ break;
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCond) {
+ auto* src = R"(
+fn f() {
+ var f = 1.0;
+ for(; f == array<f32, 1u>(f)[0]; f = f + 1.0) {
+ var marker = 1;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var f = 1.0;
+ loop {
+ let tint_symbol = array<f32, 1u>(f);
+ if (!((f == tint_symbol[0]))) {
+ break;
+ }
+ {
+ var marker = 1;
+ }
+
+ continuing {
+ f = (f + 1.0);
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCont) {
+ auto* src = R"(
+fn f() {
+ var f = 0.0;
+ for(; f < 10.0; f = f + array<f32, 1u>(1.0)[0]) {
+ var marker = 1;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var f = 0.0;
+ loop {
+ if (!((f < 10.0))) {
+ break;
+ }
+ {
+ var marker = 1;
+ }
+
+ continuing {
+ let tint_symbol = array<f32, 1u>(1.0);
+ f = (f + tint_symbol[0]);
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInitCondCont) {
+ auto* src = R"(
+fn f() {
+ for(var f = array<f32, 1u>(0.0)[0];
+ f < array<f32, 1u>(1.0)[0];
+ f = f + array<f32, 1u>(2.0)[0]) {
+ var marker = 1;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ let tint_symbol = array<f32, 1u>(0.0);
+ {
+ var f = tint_symbol[0];
+ loop {
+ let tint_symbol_1 = array<f32, 1u>(1.0);
+ if (!((f < tint_symbol_1[0]))) {
+ break;
+ }
+ {
+ var marker = 1;
+ }
+
+ continuing {
+ let tint_symbol_2 = array<f32, 1u>(2.0);
+ f = (f + tint_symbol_2[0]);
+ }
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIf) {
+ auto* src = R"(
+fn f() {
+ var f = 1.0;
+ if (true) {
+ var marker = 0;
+ } else if (f == array<f32, 2u>(f, f)[0]) {
+ var marker = 1;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var f = 1.0;
+ if (true) {
+ var marker = 0;
+ } else {
+ let tint_symbol = array<f32, 2u>(f, f);
+ if ((f == tint_symbol[0])) {
+ var marker = 1;
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIfChain) {
+ auto* src = R"(
+fn f() {
+ var f = 1.0;
+ if (true) {
+ var marker = 0;
+ } else if (true) {
+ var marker = 1;
+ } else if (f == array<f32, 2u>(f, f)[0]) {
+ var marker = 2;
+ } else if (f == array<f32, 2u>(f, f)[1]) {
+ var marker = 3;
+ } else if (true) {
+ var marker = 4;
+ } else {
+ var marker = 5;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var f = 1.0;
+ if (true) {
+ var marker = 0;
+ } else if (true) {
+ var marker = 1;
+ } else {
+ let tint_symbol = array<f32, 2u>(f, f);
+ if ((f == tint_symbol[0])) {
+ var marker = 2;
+ } else {
+ let tint_symbol_1 = array<f32, 2u>(f, f);
+ if ((f == tint_symbol_1[1])) {
+ var marker = 3;
+ } else if (true) {
+ var marker = 4;
+ } else {
+ var marker = 5;
+ }
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, ArrayInArrayArray) {
+ auto* src = R"(
+fn f() {
+ var i = array<array<f32, 2u>, 2u>(array<f32, 2u>(1.0, 2.0), array<f32, 2u>(3.0, 4.0))[0][1];
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ let tint_symbol = array<f32, 2u>(1.0, 2.0);
+ let tint_symbol_1 = array<f32, 2u>(3.0, 4.0);
+ let tint_symbol_2 = array<array<f32, 2u>, 2u>(tint_symbol, tint_symbol_1);
+ var i = tint_symbol_2[0][1];
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, StructNested) {
+ auto* src = R"(
+struct S1 {
+ a : i32;
+};
+
+struct S2 {
+ a : i32;
+ b : S1;
+ c : i32;
+};
+
+struct S3 {
+ a : S2;
+};
+
+fn f() {
+ var x = S3(S2(1, S1(2), 3)).a.b.a;
+}
+)";
+
+ auto* expect = R"(
+struct S1 {
+ a : i32;
+}
+
+struct S2 {
+ a : i32;
+ b : S1;
+ c : i32;
+}
+
+struct S3 {
+ a : S2;
+}
+
+fn f() {
+ let tint_symbol = S1(2);
+ let tint_symbol_1 = S2(1, tint_symbol, 3);
+ let tint_symbol_2 = S3(tint_symbol_1);
+ var x = tint_symbol_2.a.b.a;
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, Mixed) {
+ auto* src = R"(
+struct S1 {
+ a : i32;
+};
+
+struct S2 {
+ a : array<S1, 3u>;
+};
+
+fn f() {
+ var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
+}
+)";
+
+ auto* expect = R"(
+struct S1 {
+ a : i32;
+}
+
+struct S2 {
+ a : array<S1, 3u>;
+}
+
+fn f() {
+ let tint_symbol = S1(1);
+ let tint_symbol_1 = S1(2);
+ let tint_symbol_2 = S1(3);
+ let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
+ let tint_symbol_4 = S2(tint_symbol_3);
+ var x = tint_symbol_4.a[1].a;
+}
+)";
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToConstVarTest, NoChangeOnVarDecl) {
+ auto* src = R"(
+struct S {
+ a : i32;
+ b : f32;
+ c : i32;
+}
+
+fn f() {
+ var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+ var local_str = S(1, 2.0, 3);
+}
+
+let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+
+let module_str : S = S(1, 2.0, 3);
+)";
+
+ auto* expect = src;
+
+ DataMap data;
+ auto got = Run<PromoteInitializersToConstVar>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+} // namespace
+} // namespace transform
+} // namespace tint
diff --git a/src/transform/promote_side_effects_to_decl.cc b/src/transform/promote_side_effects_to_decl.cc
deleted file mode 100644
index 520e845..0000000
--- a/src/transform/promote_side_effects_to_decl.cc
+++ /dev/null
@@ -1,420 +0,0 @@
-// 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/promote_side_effects_to_decl.h"
-
-#include <string>
-#include <unordered_map>
-#include <utility>
-
-#include "src/program_builder.h"
-#include "src/sem/block_statement.h"
-#include "src/sem/call.h"
-#include "src/sem/expression.h"
-#include "src/sem/for_loop_statement.h"
-#include "src/sem/if_statement.h"
-#include "src/sem/statement.h"
-#include "src/sem/type_constructor.h"
-#include "src/utils/reverse.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::PromoteSideEffectsToDecl);
-TINT_INSTANTIATE_TYPEINFO(tint::transform::PromoteSideEffectsToDecl::Config);
-
-namespace tint {
-namespace transform {
-
-/// Private implementation of PromoteSideEffectsToDecl transform
-class PromoteSideEffectsToDecl::State {
- private:
- CloneContext& ctx;
- const Config& cfg;
- ProgramBuilder& b;
-
- /// Holds information about a for-loop that needs to be decomposed into a
- /// loop, so that declaration statements can be inserted before the condition
- /// expression or continuing statement.
- struct LoopInfo {
- ast::StatementList cond_decls;
- ast::StatementList cont_decls;
- };
-
- /// Holds information about 'if's with 'else-if' statements that need to be
- /// decomposed into 'if {else}' so that declaration statements can be inserted
- /// before the condition expression.
- struct IfInfo {
- /// Info for each else-if that needs decomposing
- struct ElseIfInfo {
- /// Decls to insert before condition
- ast::StatementList cond_decls;
- };
-
- /// 'else if's that need to be decomposed to 'else { if }'
- std::unordered_map<const sem::ElseStatement*, ElseIfInfo> else_ifs;
- };
-
- // For-loops that need to be decomposed to loops.
- std::unordered_map<const sem::ForLoopStatement*, LoopInfo> loops;
-
- /// If statements with 'else if's that need to be decomposed to 'else { if }'
- std::unordered_map<const sem::IfStatement*, IfInfo> ifs;
-
- // Inserts `decl` before `sem_expr`, possibly marking a for-loop to be
- // converted to a loop, or an else-if to an else { if }..
- bool InsertBefore(const sem::Expression* sem_expr,
- const ast::VariableDeclStatement* decl) {
- auto* sem_stmt = sem_expr->Stmt();
- auto* stmt = sem_stmt->Declaration();
-
- if (auto* else_if = sem_stmt->As<sem::ElseStatement>()) {
- // Expression used in 'else if' condition.
- // Need to convert 'else if' to 'else { if }'.
- auto& if_info = ifs[else_if->Parent()->As<sem::IfStatement>()];
- if_info.else_ifs[else_if].cond_decls.push_back(decl);
- return true;
- }
-
- if (auto* fl = sem_stmt->As<sem::ForLoopStatement>()) {
- // Expression used in for-loop condition.
- // For-loop needs to be decomposed to a loop.
- loops[fl].cond_decls.emplace_back(decl);
- return true;
- }
-
- auto* parent = sem_stmt->Parent(); // The statement's parent
- if (auto* block = parent->As<sem::BlockStatement>()) {
- // Expression's statement sits in a block. Simple case.
- // Insert the decl before the parent statement
- ctx.InsertBefore(block->Declaration()->statements, stmt, decl);
- return true;
- }
-
- if (auto* fl = parent->As<sem::ForLoopStatement>()) {
- // Expression is used in a for-loop. These require special care.
- if (fl->Declaration()->initializer == stmt) {
- // Expression used in for-loop initializer.
- // Insert the let above the for-loop.
- ctx.InsertBefore(fl->Block()->Declaration()->statements,
- fl->Declaration(), decl);
- return true;
- }
-
- if (fl->Declaration()->continuing == stmt) {
- // Expression used in for-loop continuing.
- // For-loop needs to be decomposed to a loop.
- loops[fl].cont_decls.emplace_back(decl);
- return true;
- }
-
- TINT_ICE(Transform, b.Diagnostics())
- << "unhandled use of expression in for-loop";
- return false;
- }
-
- TINT_ICE(Transform, b.Diagnostics())
- << "unhandled expression parent statement type: "
- << parent->TypeInfo().name;
- return false;
- }
-
- // Hoists array and structure initializers to a constant variable, declared
- // just before the statement of usage.
- bool TypeConstructorToLet(const ast::CallExpression* expr) {
- auto* ctor = ctx.src->Sem().Get(expr);
- if (!ctor->Target()->Is<sem::TypeConstructor>()) {
- return true;
- }
- auto* sem_stmt = ctor->Stmt();
- if (!sem_stmt) {
- // Expression is outside of a statement. This usually means the
- // expression is part of a global (module-scope) constant declaration.
- // These must be constexpr, and so cannot contain the type of
- // expressions that must be sanitized.
- return true;
- }
-
- auto* stmt = sem_stmt->Declaration();
-
- if (auto* src_var_decl = stmt->As<ast::VariableDeclStatement>()) {
- if (src_var_decl->variable->constructor == expr) {
- // This statement is just a variable declaration with the
- // initializer as the constructor value. This is what we're
- // attempting to transform to, and so ignore.
- return true;
- }
- }
-
- auto* src_ty = ctor->Type();
- if (!src_ty->IsAnyOf<sem::Array, sem::Struct>()) {
- // We only care about array and struct initializers
- return true;
- }
-
- // Construct the let that holds the hoisted initializer
- auto name = b.Sym();
- auto* let = b.Const(name, nullptr, ctx.Clone(expr));
- auto* let_decl = b.Decl(let);
-
- if (!InsertBefore(ctor, let_decl)) {
- return false;
- }
-
- // Replace the initializer expression with a reference to the let
- ctx.Replace(expr, b.Expr(name));
- return true;
- }
-
- // Extracts array and matrix values that are dynamically indexed to a
- // temporary `var` local that is then indexed.
- bool DynamicIndexToVar(const ast::IndexAccessorExpression* access_expr) {
- auto* index_expr = access_expr->index;
- auto* object_expr = access_expr->object;
- auto& sem = ctx.src->Sem();
-
- if (sem.Get(index_expr)->ConstantValue()) {
- // Index expression resolves to a compile time value.
- // As this isn't a dynamic index, we can ignore this.
- return true;
- }
-
- auto* indexed = sem.Get(object_expr);
- if (!indexed->Type()->IsAnyOf<sem::Array, sem::Matrix>()) {
- // We only care about array and matrices.
- return true;
- }
-
- // Construct a `var` declaration to hold the value in memory.
- // TODO(bclayton): group multiple accesses in the same object.
- // e.g. arr[i] + arr[i+1] // Don't create two vars for this
- auto var_name = b.Symbols().New("var_for_index");
- auto* var_decl = b.Decl(b.Var(var_name, nullptr, ctx.Clone(object_expr)));
-
- if (!InsertBefore(indexed, var_decl)) {
- return false;
- }
-
- // Replace the original index expression with the new `var`.
- ctx.Replace(object_expr, b.Expr(var_name));
- return true;
- }
-
- // Converts any for-loops marked for conversion to loops, inserting
- // registered declaration statements before the condition or continuing
- // statement.
- void ForLoopsToLoops() {
- if (loops.empty()) {
- return;
- }
-
- // At least one for-loop needs to be transformed into a loop.
- ctx.ReplaceAll(
- [&](const ast::ForLoopStatement* stmt) -> const ast::Statement* {
- auto& sem = ctx.src->Sem();
-
- if (auto* fl = sem.Get(stmt)) {
- if (auto it = loops.find(fl); it != loops.end()) {
- auto& info = it->second;
- auto* for_loop = fl->Declaration();
- // For-loop needs to be decomposed to a loop.
- // Build the loop body's statements.
- // Start with any let declarations for the conditional
- // expression.
- auto body_stmts = info.cond_decls;
- // If the for-loop has a condition, emit this next as:
- // if (!cond) { break; }
- if (auto* cond = for_loop->condition) {
- // !condition
- auto* not_cond = b.create<ast::UnaryOpExpression>(
- ast::UnaryOp::kNot, ctx.Clone(cond));
- // { break; }
- auto* break_body = b.Block(b.create<ast::BreakStatement>());
- // if (!condition) { break; }
- body_stmts.emplace_back(b.If(not_cond, break_body));
- }
- // Next emit the for-loop body
- body_stmts.emplace_back(ctx.Clone(for_loop->body));
-
- // Finally create the continuing block if there was one.
- const ast::BlockStatement* continuing = nullptr;
- if (auto* cont = for_loop->continuing) {
- // Continuing block starts with any let declarations used by
- // the continuing.
- auto cont_stmts = info.cont_decls;
- cont_stmts.emplace_back(ctx.Clone(cont));
- continuing = b.Block(cont_stmts);
- }
-
- auto* body = b.Block(body_stmts);
- auto* loop = b.Loop(body, continuing);
- if (auto* init = for_loop->initializer) {
- return b.Block(ctx.Clone(init), loop);
- }
- return loop;
- }
- }
- return nullptr;
- });
- }
-
- void ElseIfsToElseWithNestedIfs() {
- if (ifs.empty()) {
- return;
- }
-
- ctx.ReplaceAll([&](const ast::IfStatement* if_stmt) //
- -> const ast::IfStatement* {
- auto& sem = ctx.src->Sem();
- auto* sem_if = sem.Get(if_stmt);
- if (!sem_if) {
- return nullptr;
- }
-
- auto it = ifs.find(sem_if);
- if (it == ifs.end()) {
- return nullptr;
- }
- auto& if_info = it->second;
-
- // This if statement has "else if"s that need to be converted to "else
- // { if }"s
-
- ast::ElseStatementList next_else_stmts;
- next_else_stmts.reserve(if_stmt->else_statements.size());
-
- for (auto* else_stmt : utils::Reverse(if_stmt->else_statements)) {
- if (else_stmt->condition == nullptr) {
- // The last 'else', keep as is
- next_else_stmts.insert(next_else_stmts.begin(), ctx.Clone(else_stmt));
-
- } else {
- auto* sem_else_if = sem.Get(else_stmt);
-
- auto it2 = if_info.else_ifs.find(sem_else_if);
- if (it2 == if_info.else_ifs.end()) {
- // 'else if' we don't need to modify (no decls to insert), so
- // keep as is
- next_else_stmts.insert(next_else_stmts.begin(),
- ctx.Clone(else_stmt));
-
- } else {
- // 'else if' we need to replace with 'else <decls> { if }'
- auto& else_if_info = it2->second;
-
- // Build the else body's statements, starting with let decls for
- // the conditional expression
- auto& body_stmts = else_if_info.cond_decls;
-
- // Build nested if
- body_stmts.emplace_back(b.If(ctx.Clone(else_stmt->condition),
- ctx.Clone(else_stmt->body),
- next_else_stmts));
-
- // Build else
- auto* else_with_nested_if = b.Else(b.Block(body_stmts));
-
- // This will be used in parent if (either another nested if, or
- // top-level if)
- next_else_stmts = {else_with_nested_if};
- }
- }
- }
-
- // Build a new top-level if with new else statements
- if (next_else_stmts.empty()) {
- TINT_ICE(Transform, b.Diagnostics())
- << "Expected else statements to insert into new if";
- }
- auto* new_if = b.If(ctx.Clone(if_stmt->condition),
- ctx.Clone(if_stmt->body), next_else_stmts);
- return new_if;
- });
- }
-
- public:
- /// Constructor
- /// @param ctx_in the CloneContext primed with the input program and
- /// @param cfg_in the transform config
- /// ProgramBuilder
- explicit State(CloneContext& ctx_in, const Config& cfg_in)
- : ctx(ctx_in), cfg(cfg_in), b(*ctx_in.dst) {}
-
- /// Runs the transform
- void Run() {
- // Scan the AST nodes for expressions that need to be promoted to their own
- // constant or variable declaration.
-
- // Note: Correct handling of nested expressions is guaranteed due to the
- // depth-first traversal of the ast::Node::Clone() methods:
- //
- // The inner-most expressions are traversed first, and they are hoisted
- // to variables declared just above the statement of use. The outer
- // expression will then be hoisted, inserting themselves between the
- // inner declaration and the statement of use. This pattern applies
- // correctly to any nested depth.
- //
- // Depth-first traversal of the AST is guaranteed because AST nodes are
- // fully immutable and require their children to be constructed first so
- // their pointer can be passed to the parent's constructor.
-
- for (auto* node : ctx.src->ASTNodes().Objects()) {
- if (cfg.type_ctor_to_let) {
- if (auto* call_expr = node->As<ast::CallExpression>()) {
- if (!TypeConstructorToLet(call_expr)) {
- return;
- }
- }
- }
-
- if (cfg.dynamic_index_to_var) {
- if (auto* access_expr = node->As<ast::IndexAccessorExpression>()) {
- if (!DynamicIndexToVar(access_expr)) {
- return;
- }
- }
- }
- }
-
- ForLoopsToLoops();
- ElseIfsToElseWithNestedIfs();
-
- ctx.Clone();
- }
-};
-
-PromoteSideEffectsToDecl::PromoteSideEffectsToDecl() = default;
-PromoteSideEffectsToDecl::~PromoteSideEffectsToDecl() = default;
-
-void PromoteSideEffectsToDecl::Run(CloneContext& ctx,
- const DataMap& inputs,
- DataMap&) const {
- auto* cfg = inputs.Get<Config>();
- if (cfg == nullptr) {
- ctx.dst->Diagnostics().add_error(
- diag::System::Transform,
- "missing transform data for " + std::string(TypeInfo().name));
- return;
- }
-
- State state(ctx, *cfg);
- state.Run();
-}
-
-PromoteSideEffectsToDecl::Config::Config(bool type_ctor_to_let_in,
- bool dynamic_index_to_var_in)
- : type_ctor_to_let(type_ctor_to_let_in),
- dynamic_index_to_var(dynamic_index_to_var_in) {}
-
-PromoteSideEffectsToDecl::Config::~Config() = default;
-
-} // namespace transform
-} // namespace tint
diff --git a/src/transform/promote_side_effects_to_decl.h b/src/transform/promote_side_effects_to_decl.h
deleted file mode 100644
index 7bfc31e..0000000
--- a/src/transform/promote_side_effects_to_decl.h
+++ /dev/null
@@ -1,75 +0,0 @@
-// 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_PROMOTE_SIDE_EFFECTS_TO_DECL_H_
-#define SRC_TRANSFORM_PROMOTE_SIDE_EFFECTS_TO_DECL_H_
-
-#include "src/transform/transform.h"
-
-namespace tint {
-namespace transform {
-
-/// A transform that hoists expressions with side-effects to a variable
-/// declaration just before the statement of usage. This transform may also
-/// decompose for-loops into loops so that let declarations can be emitted
-/// before loop condition expressions and/or continuing statements. It may also
-/// similarly decompose 'else if's to 'else { if }'s for the same reason.
-/// @see crbug.com/tint/406
-class PromoteSideEffectsToDecl
- : public Castable<PromoteSideEffectsToDecl, Transform> {
- public:
- /// Constructor
- PromoteSideEffectsToDecl();
-
- /// Destructor
- ~PromoteSideEffectsToDecl() override;
-
- /// Configuration options for the transform.
- struct Config : public Castable<Config, Data> {
- /// Constructor
- /// @param type_ctor_to_let whether to hoist type constructor expressions
- /// to a let
- /// @param dynamic_index_to_var whether to hoist dynamic indexed
- /// expressions to a var
- Config(bool type_ctor_to_let, bool dynamic_index_to_var);
-
- /// Destructor
- ~Config() override;
-
- /// Whether to hoist type constructor expressions to a let
- const bool type_ctor_to_let;
-
- /// Whether to hoist dynamic indexed expressions to a var
- const bool dynamic_index_to_var;
- };
-
- 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) const override;
-
- private:
- class State;
-};
-
-} // namespace transform
-} // namespace tint
-
-#endif // SRC_TRANSFORM_PROMOTE_SIDE_EFFECTS_TO_DECL_H_
diff --git a/src/transform/promote_side_effects_to_decl_test.cc b/src/transform/promote_side_effects_to_decl_test.cc
deleted file mode 100644
index c520aa1..0000000
--- a/src/transform/promote_side_effects_to_decl_test.cc
+++ /dev/null
@@ -1,1080 +0,0 @@
-// 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/promote_side_effects_to_decl.h"
-
-#include "src/transform/test_helper.h"
-
-namespace tint {
-namespace transform {
-namespace {
-
-using PromoteSideEffectsToDeclTest = TransformTest;
-
-TEST_F(PromoteSideEffectsToDeclTest, EmptyModule) {
- auto* src = "";
- auto* expect = "";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_BasicArray) {
- auto* src = R"(
-fn f() {
- var f0 = 1.0;
- var f1 = 2.0;
- var f2 = 3.0;
- var f3 = 4.0;
- var i = array<f32, 4u>(f0, f1, f2, f3)[2];
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var f0 = 1.0;
- var f1 = 2.0;
- var f2 = 3.0;
- var f3 = 4.0;
- let tint_symbol = array<f32, 4u>(f0, f1, f2, f3);
- var i = tint_symbol[2];
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_BasicStruct) {
- auto* src = R"(
-struct S {
- a : i32;
- b : f32;
- c : vec3<f32>;
-};
-
-fn f() {
- var x = S(1, 2.0, vec3<f32>()).b;
-}
-)";
-
- auto* expect = R"(
-struct S {
- a : i32;
- b : f32;
- c : vec3<f32>;
-}
-
-fn f() {
- let tint_symbol = S(1, 2.0, vec3<f32>());
- var x = tint_symbol.b;
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_ArrayInForLoopInit) {
- auto* src = R"(
-fn f() {
- var insert_after = 1;
- for(var i = array<f32, 4u>(0.0, 1.0, 2.0, 3.0)[2]; ; ) {
- break;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var insert_after = 1;
- let tint_symbol = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
- for(var i = tint_symbol[2]; ; ) {
- break;
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_StructInForLoopInit) {
- auto* src = R"(
-struct S {
- a : i32;
- b : f32;
- c : vec3<f32>;
-};
-
-fn f() {
- var insert_after = 1;
- for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
- break;
- }
-}
-)";
-
- auto* expect = R"(
-struct S {
- a : i32;
- b : f32;
- c : vec3<f32>;
-}
-
-fn f() {
- var insert_after = 1;
- let tint_symbol = S(1, 2.0, vec3<f32>());
- for(var x = tint_symbol.b; ; ) {
- break;
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_ArrayInForLoopCond) {
- auto* src = R"(
-fn f() {
- var f = 1.0;
- for(; f == array<f32, 1u>(f)[0]; f = f + 1.0) {
- var marker = 1;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var f = 1.0;
- loop {
- let tint_symbol = array<f32, 1u>(f);
- if (!((f == tint_symbol[0]))) {
- break;
- }
- {
- var marker = 1;
- }
-
- continuing {
- f = (f + 1.0);
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_ArrayInForLoopCont) {
- auto* src = R"(
-fn f() {
- var f = 0.0;
- for(; f < 10.0; f = f + array<f32, 1u>(1.0)[0]) {
- var marker = 1;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var f = 0.0;
- loop {
- if (!((f < 10.0))) {
- break;
- }
- {
- var marker = 1;
- }
-
- continuing {
- let tint_symbol = array<f32, 1u>(1.0);
- f = (f + tint_symbol[0]);
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_ArrayInForLoopInitCondCont) {
- auto* src = R"(
-fn f() {
- for(var f = array<f32, 1u>(0.0)[0];
- f < array<f32, 1u>(1.0)[0];
- f = f + array<f32, 1u>(2.0)[0]) {
- var marker = 1;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- let tint_symbol = array<f32, 1u>(0.0);
- {
- var f = tint_symbol[0];
- loop {
- let tint_symbol_1 = array<f32, 1u>(1.0);
- if (!((f < tint_symbol_1[0]))) {
- break;
- }
- {
- var marker = 1;
- }
-
- continuing {
- let tint_symbol_2 = array<f32, 1u>(2.0);
- f = (f + tint_symbol_2[0]);
- }
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_ArrayInElseIf) {
- auto* src = R"(
-fn f() {
- var f = 1.0;
- if (true) {
- var marker = 0;
- } else if (f == array<f32, 2u>(f, f)[0]) {
- var marker = 1;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var f = 1.0;
- if (true) {
- var marker = 0;
- } else {
- let tint_symbol = array<f32, 2u>(f, f);
- if ((f == tint_symbol[0])) {
- var marker = 1;
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_ArrayInElseIfChain) {
- auto* src = R"(
-fn f() {
- var f = 1.0;
- if (true) {
- var marker = 0;
- } else if (true) {
- var marker = 1;
- } else if (f == array<f32, 2u>(f, f)[0]) {
- var marker = 2;
- } else if (f == array<f32, 2u>(f, f)[1]) {
- var marker = 3;
- } else if (true) {
- var marker = 4;
- } else {
- var marker = 5;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var f = 1.0;
- if (true) {
- var marker = 0;
- } else if (true) {
- var marker = 1;
- } else {
- let tint_symbol = array<f32, 2u>(f, f);
- if ((f == tint_symbol[0])) {
- var marker = 2;
- } else {
- let tint_symbol_1 = array<f32, 2u>(f, f);
- if ((f == tint_symbol_1[1])) {
- var marker = 3;
- } else if (true) {
- var marker = 4;
- } else {
- var marker = 5;
- }
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_ArrayInArrayArray) {
- auto* src = R"(
-fn f() {
- var i = array<array<f32, 2u>, 2u>(array<f32, 2u>(1.0, 2.0), array<f32, 2u>(3.0, 4.0))[0][1];
-}
-)";
-
- auto* expect = R"(
-fn f() {
- let tint_symbol = array<f32, 2u>(1.0, 2.0);
- let tint_symbol_1 = array<f32, 2u>(3.0, 4.0);
- let tint_symbol_2 = array<array<f32, 2u>, 2u>(tint_symbol, tint_symbol_1);
- var i = tint_symbol_2[0][1];
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_StructNested) {
- auto* src = R"(
-struct S1 {
- a : i32;
-};
-
-struct S2 {
- a : i32;
- b : S1;
- c : i32;
-};
-
-struct S3 {
- a : S2;
-};
-
-fn f() {
- var x = S3(S2(1, S1(2), 3)).a.b.a;
-}
-)";
-
- auto* expect = R"(
-struct S1 {
- a : i32;
-}
-
-struct S2 {
- a : i32;
- b : S1;
- c : i32;
-}
-
-struct S3 {
- a : S2;
-}
-
-fn f() {
- let tint_symbol = S1(2);
- let tint_symbol_1 = S2(1, tint_symbol, 3);
- let tint_symbol_2 = S3(tint_symbol_1);
- var x = tint_symbol_2.a.b.a;
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_Mixed) {
- auto* src = R"(
-struct S1 {
- a : i32;
-};
-
-struct S2 {
- a : array<S1, 3u>;
-};
-
-fn f() {
- var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
-}
-)";
-
- auto* expect = R"(
-struct S1 {
- a : i32;
-}
-
-struct S2 {
- a : array<S1, 3u>;
-}
-
-fn f() {
- let tint_symbol = S1(1);
- let tint_symbol_1 = S1(2);
- let tint_symbol_2 = S1(3);
- let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
- let tint_symbol_4 = S2(tint_symbol_3);
- var x = tint_symbol_4.a[1].a;
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, TypeCtorToLet_NoChangeOnVarDecl) {
- auto* src = R"(
-struct S {
- a : i32;
- b : f32;
- c : i32;
-}
-
-fn f() {
- var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
- var local_str = S(1, 2.0, 3);
-}
-
-let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-
-let module_str : S = S(1, 2.0, 3);
-)";
-
- auto* expect = src;
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ true,
- /* dynamic_index_to_var */ false);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_ArrayIndexDynamic) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 4>(1, 2, 3, 4);
- let x = p[i];
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 4>(1, 2, 3, 4);
- var var_for_index = p;
- let x = var_for_index[i];
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_MatrixIndexDynamic) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- let x = p[i];
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- var var_for_index = p;
- let x = var_for_index[i];
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_ArrayIndexDynamicChain) {
- auto* src = R"(
-fn f() {
- var i : i32;
- var j : i32;
- let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
- let x = p[i][j];
-}
-)";
-
- // TODO(bclayton): Optimize this case:
- // This output is not as efficient as it could be.
- // We only actually need to hoist the inner-most array to a `var`
- // (`var_for_index`), as later indexing operations will be working with
- // references, not values.
- auto* expect = R"(
-fn f() {
- var i : i32;
- var j : i32;
- let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
- var var_for_index = p;
- var var_for_index_1 = var_for_index[i];
- let x = var_for_index_1[j];
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_ArrayIndexInForLoopInit) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
- for(let x = p[i]; ; ) {
- break;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
- var var_for_index = p;
- for(let x = var_for_index[i]; ; ) {
- break;
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_MatrixIndexInForLoopInit) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- for(let x = p[i]; ; ) {
- break;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- var var_for_index = p;
- for(let x = var_for_index[i]; ; ) {
- break;
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_ArrayIndexInForLoopCond) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 2>(1, 2);
- for(; p[i] < 3; ) {
- break;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 2>(1, 2);
- loop {
- var var_for_index = p;
- if (!((var_for_index[i] < 3))) {
- break;
- }
- {
- break;
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_MatrixIndexInForLoopCond) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- for(; p[i].x < 3.0; ) {
- break;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- loop {
- var var_for_index = p;
- if (!((var_for_index[i].x < 3.0))) {
- break;
- }
- {
- break;
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_MatrixIndexInForLoopCondWithNestedIndex) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- for(; p[i].x < 3.0; ) {
- if (p[i].x < 1.0) {
- var marker = 1;
- }
- break;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- loop {
- var var_for_index = p;
- if (!((var_for_index[i].x < 3.0))) {
- break;
- }
- {
- var var_for_index_1 = p;
- if ((var_for_index_1[i].x < 1.0)) {
- var marker = 1;
- }
- break;
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_ArrayIndexInElseIf) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 2>(1, 2);
- if (false) {
- var marker = 0;
- } else if (p[i] < 3) {
- var marker = 1;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 2>(1, 2);
- if (false) {
- var marker = 0;
- } else {
- var var_for_index = p;
- if ((var_for_index[i] < 3)) {
- var marker = 1;
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_ArrayIndexInElseIfChain) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 2>(1, 2);
- if (true) {
- var marker = 0;
- } else if (true) {
- var marker = 1;
- } else if (p[i] < 3) {
- var marker = 2;
- } else if (p[i] < 4) {
- var marker = 3;
- } else if (true) {
- var marker = 4;
- } else {
- var marker = 5;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = array<i32, 2>(1, 2);
- if (true) {
- var marker = 0;
- } else if (true) {
- var marker = 1;
- } else {
- var var_for_index = p;
- if ((var_for_index[i] < 3)) {
- var marker = 2;
- } else {
- var var_for_index_1 = p;
- if ((var_for_index_1[i] < 4)) {
- var marker = 3;
- } else if (true) {
- var marker = 4;
- } else {
- var marker = 5;
- }
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_MatrixIndexInElseIf) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- if (false) {
- var marker_if = 1;
- } else if (p[i].x < 3.0) {
- var marker_else_if = 1;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- if (false) {
- var marker_if = 1;
- } else {
- var var_for_index = p;
- if ((var_for_index[i].x < 3.0)) {
- var marker_else_if = 1;
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_MatrixIndexInElseIfChain) {
- auto* src = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- if (true) {
- var marker = 0;
- } else if (true) {
- var marker = 1;
- } else if (p[i].x < 3.0) {
- var marker = 2;
- } else if (p[i].y < 3.0) {
- var marker = 3;
- } else if (true) {
- var marker = 4;
- } else {
- var marker = 5;
- }
-}
-)";
-
- auto* expect = R"(
-fn f() {
- var i : i32;
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- if (true) {
- var marker = 0;
- } else if (true) {
- var marker = 1;
- } else {
- var var_for_index = p;
- if ((var_for_index[i].x < 3.0)) {
- var marker = 2;
- } else {
- var var_for_index_1 = p;
- if ((var_for_index_1[i].y < 3.0)) {
- var marker = 3;
- } else if (true) {
- var marker = 4;
- } else {
- var marker = 5;
- }
- }
- }
-}
-)";
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_ArrayIndexLiteral) {
- auto* src = R"(
-fn f() {
- let p = array<i32, 4>(1, 2, 3, 4);
- let x = p[1];
-}
-)";
-
- auto* expect = src;
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_MatrixIndexLiteral) {
- auto* src = R"(
-fn f() {
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- let x = p[1];
-}
-)";
-
- auto* expect = src;
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_ArrayIndexConstantLet) {
- auto* src = R"(
-fn f() {
- let p = array<i32, 4>(1, 2, 3, 4);
- let c = 1;
- let x = p[c];
-}
-)";
-
- auto* expect = src;
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_MatrixIndexConstantLet) {
- auto* src = R"(
-fn f() {
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- let c = 1;
- let x = p[c];
-}
-)";
-
- auto* expect = src;
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest, DynamicIndexToVar_ArrayIndexLiteralChain) {
- auto* src = R"(
-fn f() {
- let a = array<i32, 2>(1, 2);
- let b = array<i32, 2>(3, 4);
- let p = array<array<i32, 2>, 2>(a, b);
- let x = p[0][1];
-}
-)";
-
- auto* expect = src;
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteSideEffectsToDeclTest,
- DynamicIndexToVar_MatrixIndexLiteralChain) {
- auto* src = R"(
-fn f() {
- let p = mat2x2(1.0, 2.0, 3.0, 4.0);
- let x = p[0][1];
-}
-)";
-
- auto* expect = src;
-
- DataMap data;
- data.Add<PromoteSideEffectsToDecl::Config>(/* type_ctor_to_let */ false,
- /* dynamic_index_to_var */ true);
- auto got = Run<PromoteSideEffectsToDecl>(src, data);
-
- EXPECT_EQ(expect, str(got));
-}
-
-} // namespace
-} // namespace transform
-} // namespace tint
diff --git a/src/transform/test_helper.h b/src/transform/test_helper.h
index 3b3004a..ae62fce 100644
--- a/src/transform/test_helper.h
+++ b/src/transform/test_helper.h
@@ -28,6 +28,37 @@
namespace tint {
namespace transform {
+/// @param program the program to get an output WGSL string from
+/// @returns the output program as a WGSL string, or an error string if the
+/// program is not valid.
+inline std::string str(const Program& program) {
+ diag::Formatter::Style style;
+ style.print_newline_at_end = false;
+
+ if (!program.IsValid()) {
+ return diag::Formatter(style).format(program.Diagnostics());
+ }
+
+ writer::wgsl::Options options;
+ auto result = writer::wgsl::Generate(&program, options);
+ if (!result.success) {
+ return "WGSL writer failed:\n" + result.error;
+ }
+
+ auto res = result.wgsl;
+ if (res.empty()) {
+ return res;
+ }
+ // The WGSL sometimes has two trailing newlines. Strip them
+ while (res.back() == '\n') {
+ res.pop_back();
+ }
+ if (res.empty()) {
+ return res;
+ }
+ return "\n" + res + "\n";
+}
+
/// Helper class for testing transforms
template <typename BASE>
class TransformTestBase : public BASE {
@@ -104,31 +135,7 @@
/// @returns the output program as a WGSL string, or an error string if the
/// program is not valid.
std::string str(const Output& output) {
- diag::Formatter::Style style;
- style.print_newline_at_end = false;
-
- if (!output.program.IsValid()) {
- return diag::Formatter(style).format(output.program.Diagnostics());
- }
-
- writer::wgsl::Options options;
- auto result = writer::wgsl::Generate(&output.program, options);
- if (!result.success) {
- return "WGSL writer failed:\n" + result.error;
- }
-
- auto res = result.wgsl;
- if (res.empty()) {
- return res;
- }
- // The WGSL sometimes has two trailing newlines. Strip them
- while (res.back() == '\n') {
- res.pop_back();
- }
- if (res.empty()) {
- return res;
- }
- return "\n" + res + "\n";
+ return transform::str(output.program);
}
private:
diff --git a/src/transform/utils/hoist_to_decl_before.cc b/src/transform/utils/hoist_to_decl_before.cc
new file mode 100644
index 0000000..2448506
--- /dev/null
+++ b/src/transform/utils/hoist_to_decl_before.cc
@@ -0,0 +1,309 @@
+// Copyright 2022 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/utils/hoist_to_decl_before.h"
+
+#include <unordered_map>
+
+#include "src/ast/variable_decl_statement.h"
+#include "src/sem/block_statement.h"
+#include "src/sem/for_loop_statement.h"
+#include "src/sem/if_statement.h"
+#include "src/utils/reverse.h"
+
+namespace tint::transform {
+
+/// Private implementation of HoistToDeclBefore transform
+class HoistToDeclBefore::State {
+ CloneContext& ctx;
+ ProgramBuilder& b;
+
+ /// Holds information about a for-loop that needs to be decomposed into a
+ /// loop, so that declaration statements can be inserted before the
+ /// condition expression or continuing statement.
+ struct LoopInfo {
+ ast::StatementList cond_decls;
+ ast::StatementList cont_decls;
+ };
+
+ /// Holds information about 'if's with 'else-if' statements that need to be
+ /// decomposed into 'if {else}' so that declaration statements can be
+ /// inserted before the condition expression.
+ struct IfInfo {
+ /// Info for each else-if that needs decomposing
+ struct ElseIfInfo {
+ /// Decls to insert before condition
+ ast::StatementList cond_decls;
+ };
+
+ /// 'else if's that need to be decomposed to 'else { if }'
+ std::unordered_map<const sem::ElseStatement*, ElseIfInfo> else_ifs;
+ };
+
+ /// For-loops that need to be decomposed to loops.
+ std::unordered_map<const sem::ForLoopStatement*, LoopInfo> loops;
+
+ /// If statements with 'else if's that need to be decomposed to 'else { if
+ /// }'
+ std::unordered_map<const sem::IfStatement*, IfInfo> ifs;
+
+ // Inserts `decl` before `sem_expr`, possibly marking a for-loop to be
+ // converted to a loop, or an else-if to an else { if }.
+ bool InsertBefore(const sem::Expression* sem_expr,
+ const ast::VariableDeclStatement* decl) {
+ auto* sem_stmt = sem_expr->Stmt();
+ auto* stmt = sem_stmt->Declaration();
+
+ if (auto* else_if = sem_stmt->As<sem::ElseStatement>()) {
+ // Expression used in 'else if' condition.
+ // Need to convert 'else if' to 'else { if }'.
+ auto& if_info = ifs[else_if->Parent()->As<sem::IfStatement>()];
+ if_info.else_ifs[else_if].cond_decls.push_back(decl);
+ return true;
+ }
+
+ if (auto* fl = sem_stmt->As<sem::ForLoopStatement>()) {
+ // Expression used in for-loop condition.
+ // For-loop needs to be decomposed to a loop.
+ loops[fl].cond_decls.emplace_back(decl);
+ return true;
+ }
+
+ auto* parent = sem_stmt->Parent(); // The statement's parent
+ if (auto* block = parent->As<sem::BlockStatement>()) {
+ // Expression's statement sits in a block. Simple case.
+ // Insert the decl before the parent statement
+ ctx.InsertBefore(block->Declaration()->statements, stmt, decl);
+ return true;
+ }
+
+ if (auto* fl = parent->As<sem::ForLoopStatement>()) {
+ // Expression is used in a for-loop. These require special care.
+ if (fl->Declaration()->initializer == stmt) {
+ // Expression used in for-loop initializer.
+ // Insert the let above the for-loop.
+ ctx.InsertBefore(fl->Block()->Declaration()->statements,
+ fl->Declaration(), decl);
+ return true;
+ }
+
+ if (fl->Declaration()->continuing == stmt) {
+ // Expression used in for-loop continuing.
+ // For-loop needs to be decomposed to a loop.
+ loops[fl].cont_decls.emplace_back(decl);
+ return true;
+ }
+
+ TINT_ICE(Transform, b.Diagnostics())
+ << "unhandled use of expression in for-loop";
+ return false;
+ }
+
+ TINT_ICE(Transform, b.Diagnostics())
+ << "unhandled expression parent statement type: "
+ << parent->TypeInfo().name;
+ return false;
+ }
+
+ // Converts any for-loops marked for conversion to loops, inserting
+ // registered declaration statements before the condition or continuing
+ // statement.
+ void ForLoopsToLoops() {
+ if (loops.empty()) {
+ return;
+ }
+
+ // At least one for-loop needs to be transformed into a loop.
+ ctx.ReplaceAll(
+ [&](const ast::ForLoopStatement* stmt) -> const ast::Statement* {
+ auto& sem = ctx.src->Sem();
+
+ if (auto* fl = sem.Get(stmt)) {
+ if (auto it = loops.find(fl); it != loops.end()) {
+ auto& info = it->second;
+ auto* for_loop = fl->Declaration();
+ // For-loop needs to be decomposed to a loop.
+ // Build the loop body's statements.
+ // Start with any let declarations for the conditional
+ // expression.
+ auto body_stmts = info.cond_decls;
+ // If the for-loop has a condition, emit this next as:
+ // if (!cond) { break; }
+ if (auto* cond = for_loop->condition) {
+ // !condition
+ auto* not_cond = b.create<ast::UnaryOpExpression>(
+ ast::UnaryOp::kNot, ctx.Clone(cond));
+ // { break; }
+ auto* break_body = b.Block(b.create<ast::BreakStatement>());
+ // if (!condition) { break; }
+ body_stmts.emplace_back(b.If(not_cond, break_body));
+ }
+ // Next emit the for-loop body
+ body_stmts.emplace_back(ctx.Clone(for_loop->body));
+
+ // Finally create the continuing block if there was one.
+ const ast::BlockStatement* continuing = nullptr;
+ if (auto* cont = for_loop->continuing) {
+ // Continuing block starts with any let declarations used by
+ // the continuing.
+ auto cont_stmts = info.cont_decls;
+ cont_stmts.emplace_back(ctx.Clone(cont));
+ continuing = b.Block(cont_stmts);
+ }
+
+ auto* body = b.Block(body_stmts);
+ auto* loop = b.Loop(body, continuing);
+ if (auto* init = for_loop->initializer) {
+ return b.Block(ctx.Clone(init), loop);
+ }
+ return loop;
+ }
+ }
+ return nullptr;
+ });
+ }
+
+ void ElseIfsToElseWithNestedIfs() {
+ if (ifs.empty()) {
+ return;
+ }
+
+ ctx.ReplaceAll([&](const ast::IfStatement* if_stmt) //
+ -> const ast::IfStatement* {
+ auto& sem = ctx.src->Sem();
+ auto* sem_if = sem.Get(if_stmt);
+ if (!sem_if) {
+ return nullptr;
+ }
+
+ auto it = ifs.find(sem_if);
+ if (it == ifs.end()) {
+ return nullptr;
+ }
+ auto& if_info = it->second;
+
+ // This if statement has "else if"s that need to be converted to "else
+ // { if }"s
+
+ ast::ElseStatementList next_else_stmts;
+ next_else_stmts.reserve(if_stmt->else_statements.size());
+
+ for (auto* else_stmt : utils::Reverse(if_stmt->else_statements)) {
+ if (else_stmt->condition == nullptr) {
+ // The last 'else', keep as is
+ next_else_stmts.insert(next_else_stmts.begin(), ctx.Clone(else_stmt));
+
+ } else {
+ auto* sem_else_if = sem.Get(else_stmt);
+
+ auto it2 = if_info.else_ifs.find(sem_else_if);
+ if (it2 == if_info.else_ifs.end()) {
+ // 'else if' we don't need to modify (no decls to insert), so
+ // keep as is
+ next_else_stmts.insert(next_else_stmts.begin(),
+ ctx.Clone(else_stmt));
+
+ } else {
+ // 'else if' we need to replace with 'else <decls> { if }'
+ auto& else_if_info = it2->second;
+
+ // Build the else body's statements, starting with let decls for
+ // the conditional expression
+ auto& body_stmts = else_if_info.cond_decls;
+
+ // Build nested if
+ auto* cond = ctx.Clone(else_stmt->condition);
+ auto* body = ctx.Clone(else_stmt->body);
+ body_stmts.emplace_back(b.If(cond, body, next_else_stmts));
+
+ // Build else
+ auto* else_with_nested_if = b.Else(b.Block(body_stmts));
+
+ // This will be used in parent if (either another nested if, or
+ // top-level if)
+ next_else_stmts = {else_with_nested_if};
+ }
+ }
+ }
+
+ // Build a new top-level if with new else statements
+ if (next_else_stmts.empty()) {
+ TINT_ICE(Transform, b.Diagnostics())
+ << "Expected else statements to insert into new if";
+ }
+ auto* cond = ctx.Clone(if_stmt->condition);
+ auto* body = ctx.Clone(if_stmt->body);
+ auto* new_if = b.If(cond, body, next_else_stmts);
+ return new_if;
+ });
+ }
+
+ public:
+ /// Constructor
+ /// @param ctx_in the clone context
+ explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
+
+ /// Hoists `expr` to a `let` or `var` with optional `decl_name`, inserting it
+ /// before `before_expr`.
+ /// @param before_expr expression to insert `expr` before
+ /// @param expr expression to hoist
+ /// @param as_const hoist to `let` if true, otherwise to `var`
+ /// @param decl_name optional name to use for the variable/constant name
+ /// @return true on success
+ bool HoistToDeclBefore(const sem::Expression* before_expr,
+ const ast::Expression* expr,
+ bool as_const,
+ const char* decl_name = "") {
+ // Construct the let/var that holds the hoisted expr
+ auto name = b.Symbols().New(decl_name);
+ auto* v = as_const ? b.Const(name, nullptr, ctx.Clone(expr))
+ : b.Var(name, nullptr, ctx.Clone(expr));
+ auto* decl = b.Decl(v);
+
+ if (!InsertBefore(before_expr, decl)) {
+ return false;
+ }
+
+ // Replace the initializer expression with a reference to the let
+ ctx.Replace(expr, b.Expr(name));
+ return true;
+ }
+
+ /// Applies any scheduled insertions from previous calls to Add() to
+ /// CloneContext. Call this once before ctx.Clone().
+ /// @return true on success
+ bool Apply() {
+ ForLoopsToLoops();
+ ElseIfsToElseWithNestedIfs();
+ return true;
+ }
+};
+
+HoistToDeclBefore::HoistToDeclBefore(CloneContext& ctx)
+ : state_(std::make_unique<State>(ctx)) {}
+
+HoistToDeclBefore::~HoistToDeclBefore() {}
+
+bool HoistToDeclBefore::Add(const sem::Expression* before_expr,
+ const ast::Expression* expr,
+ bool as_const,
+ const char* decl_name) {
+ return state_->HoistToDeclBefore(before_expr, expr, as_const, decl_name);
+}
+
+bool HoistToDeclBefore::Apply() {
+ return state_->Apply();
+}
+
+} // namespace tint::transform
diff --git a/src/transform/utils/hoist_to_decl_before.h b/src/transform/utils/hoist_to_decl_before.h
new file mode 100644
index 0000000..48082a2
--- /dev/null
+++ b/src/transform/utils/hoist_to_decl_before.h
@@ -0,0 +1,61 @@
+// Copyright 2022 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_UTILS_HOIST_TO_DECL_BEFORE_H_
+#define SRC_TRANSFORM_UTILS_HOIST_TO_DECL_BEFORE_H_
+
+#include <memory>
+
+#include "src/sem/expression.h"
+#include "src/transform/transform.h"
+
+namespace tint::transform {
+
+/// Utility class that can be used to hoist expressions before other
+/// expressions, possibly converting 'for' loops to 'loop's and 'else if to
+// 'else if'.
+class HoistToDeclBefore {
+ public:
+ /// Constructor
+ /// @param ctx the clone context
+ explicit HoistToDeclBefore(CloneContext& ctx);
+
+ /// Destructor
+ ~HoistToDeclBefore();
+
+ /// Hoists `expr` to a `let` or `var` with optional `decl_name`, inserting it
+ /// before `before_expr`.
+ /// @param before_expr expression to insert `expr` before
+ /// @param expr expression to hoist
+ /// @param as_const hoist to `let` if true, otherwise to `var`
+ /// @param decl_name optional name to use for the variable/constant name
+ /// @return true on success
+ bool Add(const sem::Expression* before_expr,
+ const ast::Expression* expr,
+ bool as_const,
+ const char* decl_name = "");
+
+ /// Applies any scheduled insertions from previous calls to Add() to
+ /// CloneContext. Call this once before ctx.Clone().
+ /// @return true on success
+ bool Apply();
+
+ private:
+ class State;
+ std::unique_ptr<State> state_;
+};
+
+} // namespace tint::transform
+
+#endif // SRC_TRANSFORM_UTILS_HOIST_TO_DECL_BEFORE_H_
diff --git a/src/transform/utils/hoist_to_decl_before_test.cc b/src/transform/utils/hoist_to_decl_before_test.cc
new file mode 100644
index 0000000..5dddde4
--- /dev/null
+++ b/src/transform/utils/hoist_to_decl_before_test.cc
@@ -0,0 +1,221 @@
+// Copyright 2022 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 <utility>
+
+#include "gtest/gtest-spi.h"
+#include "src/program_builder.h"
+#include "src/transform/test_helper.h"
+#include "src/transform/utils/hoist_to_decl_before.h"
+
+namespace tint::transform {
+namespace {
+
+using HoistToDeclBeforeTest = ::testing::Test;
+
+TEST_F(HoistToDeclBeforeTest, VarInit) {
+ // fn f() {
+ // var a = 1;
+ // }
+ ProgramBuilder b;
+ auto* expr = b.Expr(1);
+ auto* var = b.Decl(b.Var("a", nullptr, expr));
+ b.Func("f", {}, b.ty.void_(), {var});
+
+ Program original(std::move(b));
+ ProgramBuilder cloned_b;
+ CloneContext ctx(&cloned_b, &original);
+
+ HoistToDeclBefore hoistToDeclBefore(ctx);
+ auto* sem_expr = ctx.src->Sem().Get(expr);
+ hoistToDeclBefore.Add(sem_expr, expr, true);
+ hoistToDeclBefore.Apply();
+
+ ctx.Clone();
+ Program cloned(std::move(cloned_b));
+
+ auto* expect = R"(
+fn f() {
+ let tint_symbol = 1;
+ var a = tint_symbol;
+}
+)";
+
+ EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ForLoopInit) {
+ // fn f() {
+ // for(var a = 1; true; ) {
+ // }
+ // }
+ ProgramBuilder b;
+ auto* expr = b.Expr(1);
+ auto* s =
+ b.For(b.Decl(b.Var("a", nullptr, expr)), b.Expr(true), {}, b.Block());
+ b.Func("f", {}, b.ty.void_(), {s});
+
+ Program original(std::move(b));
+ ProgramBuilder cloned_b;
+ CloneContext ctx(&cloned_b, &original);
+
+ HoistToDeclBefore hoistToDeclBefore(ctx);
+ auto* sem_expr = ctx.src->Sem().Get(expr);
+ hoistToDeclBefore.Add(sem_expr, expr, true);
+ hoistToDeclBefore.Apply();
+
+ ctx.Clone();
+ Program cloned(std::move(cloned_b));
+
+ auto* expect = R"(
+fn f() {
+ let tint_symbol = 1;
+ for(var a = tint_symbol; true; ) {
+ }
+}
+)";
+
+ EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ForLoopCond) {
+ // fn f() {
+ // var a : bool;
+ // for(; a; ) {
+ // }
+ // }
+ ProgramBuilder b;
+ auto* var = b.Decl(b.Var("a", b.ty.bool_()));
+ auto* expr = b.Expr("a");
+ auto* s = b.For({}, expr, {}, b.Block());
+ b.Func("f", {}, b.ty.void_(), {var, s});
+
+ Program original(std::move(b));
+ ProgramBuilder cloned_b;
+ CloneContext ctx(&cloned_b, &original);
+
+ HoistToDeclBefore hoistToDeclBefore(ctx);
+ auto* sem_expr = ctx.src->Sem().Get(expr);
+ hoistToDeclBefore.Add(sem_expr, expr, true);
+ hoistToDeclBefore.Apply();
+
+ ctx.Clone();
+ Program cloned(std::move(cloned_b));
+
+ auto* expect = R"(
+fn f() {
+ var a : bool;
+ loop {
+ let tint_symbol = a;
+ if (!(tint_symbol)) {
+ break;
+ }
+ {
+ }
+ }
+}
+)";
+
+ EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ForLoopCont) {
+ // fn f() {
+ // for(; true; var a = 1) {
+ // }
+ // }
+ ProgramBuilder b;
+ auto* expr = b.Expr(1);
+ auto* s =
+ b.For({}, b.Expr(true), b.Decl(b.Var("a", nullptr, expr)), b.Block());
+ b.Func("f", {}, b.ty.void_(), {s});
+
+ Program original(std::move(b));
+ ProgramBuilder cloned_b;
+ CloneContext ctx(&cloned_b, &original);
+
+ HoistToDeclBefore hoistToDeclBefore(ctx);
+ auto* sem_expr = ctx.src->Sem().Get(expr);
+ hoistToDeclBefore.Add(sem_expr, expr, true);
+ hoistToDeclBefore.Apply();
+
+ ctx.Clone();
+ Program cloned(std::move(cloned_b));
+
+ auto* expect = R"(
+fn f() {
+ loop {
+ if (!(true)) {
+ break;
+ }
+ {
+ }
+
+ continuing {
+ let tint_symbol = 1;
+ var a = tint_symbol;
+ }
+ }
+}
+)";
+
+ EXPECT_EQ(expect, str(cloned));
+}
+
+TEST_F(HoistToDeclBeforeTest, ElseIf) {
+ // fn f() {
+ // var a : bool;
+ // if (true) {
+ // } else if (a) {
+ // } else {
+ // }
+ // }
+ ProgramBuilder b;
+ auto* var = b.Decl(b.Var("a", b.ty.bool_()));
+ auto* expr = b.Expr("a");
+ auto* s = b.If(b.Expr(true), b.Block(), //
+ b.Else(expr, b.Block()), //
+ b.Else(b.Block()));
+ b.Func("f", {}, b.ty.void_(), {var, s});
+
+ Program original(std::move(b));
+ ProgramBuilder cloned_b;
+ CloneContext ctx(&cloned_b, &original);
+
+ HoistToDeclBefore hoistToDeclBefore(ctx);
+ auto* sem_expr = ctx.src->Sem().Get(expr);
+ hoistToDeclBefore.Add(sem_expr, expr, true);
+ hoistToDeclBefore.Apply();
+
+ ctx.Clone();
+ Program cloned(std::move(cloned_b));
+
+ auto* expect = R"(
+fn f() {
+ var a : bool;
+ if (true) {
+ } else {
+ let tint_symbol = a;
+ if (tint_symbol) {
+ } else {
+ }
+ }
+}
+)";
+
+ EXPECT_EQ(expect, str(cloned));
+}
+
+} // namespace
+} // namespace tint::transform
diff --git a/src/transform/var_for_dynamic_index.cc b/src/transform/var_for_dynamic_index.cc
new file mode 100644
index 0000000..44c994d
--- /dev/null
+++ b/src/transform/var_for_dynamic_index.cc
@@ -0,0 +1,68 @@
+// Copyright 2022 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/var_for_dynamic_index.h"
+#include "src/program_builder.h"
+#include "src/transform/utils/hoist_to_decl_before.h"
+
+namespace tint::transform {
+
+VarForDynamicIndex::VarForDynamicIndex() = default;
+
+VarForDynamicIndex::~VarForDynamicIndex() = default;
+
+void VarForDynamicIndex::Run(CloneContext& ctx,
+ const DataMap&,
+ DataMap&) const {
+ HoistToDeclBefore hoist_to_decl_before(ctx);
+
+ // Extracts array and matrix values that are dynamically indexed to a
+ // temporary `var` local that is then indexed.
+ auto dynamic_index_to_var =
+ [&](const ast::IndexAccessorExpression* access_expr) {
+ auto* index_expr = access_expr->index;
+ auto* object_expr = access_expr->object;
+ auto& sem = ctx.src->Sem();
+
+ if (sem.Get(index_expr)->ConstantValue()) {
+ // Index expression resolves to a compile time value.
+ // As this isn't a dynamic index, we can ignore this.
+ return true;
+ }
+
+ auto* indexed = sem.Get(object_expr);
+ if (!indexed->Type()->IsAnyOf<sem::Array, sem::Matrix>()) {
+ // We only care about array and matrices.
+ return true;
+ }
+
+ // TODO(bclayton): group multiple accesses in the same object.
+ // e.g. arr[i] + arr[i+1] // Don't create two vars for this
+ return hoist_to_decl_before.Add(indexed, object_expr, false,
+ "var_for_index");
+ };
+
+ for (auto* node : ctx.src->ASTNodes().Objects()) {
+ if (auto* access_expr = node->As<ast::IndexAccessorExpression>()) {
+ if (!dynamic_index_to_var(access_expr)) {
+ return;
+ }
+ }
+ }
+
+ hoist_to_decl_before.Apply();
+ ctx.Clone();
+}
+
+} // namespace tint::transform
diff --git a/src/transform/var_for_dynamic_index.h b/src/transform/var_for_dynamic_index.h
new file mode 100644
index 0000000..a04fa06
--- /dev/null
+++ b/src/transform/var_for_dynamic_index.h
@@ -0,0 +1,48 @@
+// Copyright 2022 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_VAR_FOR_DYNAMIC_INDEX_H_
+#define SRC_TRANSFORM_VAR_FOR_DYNAMIC_INDEX_H_
+
+#include "src/transform/transform.h"
+
+namespace tint::transform {
+
+/// A transform that extracts array and matrix values that are dynamically
+/// indexed to a temporary `var` local before performing the index. This
+/// transform is used by the SPIR-V writer as there is no SPIR-V instruction
+/// that can dynamically index a non-pointer composite.
+class VarForDynamicIndex : public Transform {
+ public:
+ /// Constructor
+ VarForDynamicIndex();
+
+ /// Destructor
+ ~VarForDynamicIndex() 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) const override;
+};
+
+} // namespace tint::transform
+
+#endif // SRC_TRANSFORM_VAR_FOR_DYNAMIC_INDEX_H_
diff --git a/src/transform/var_for_dynamic_index_test.cc b/src/transform/var_for_dynamic_index_test.cc
new file mode 100644
index 0000000..91289ef
--- /dev/null
+++ b/src/transform/var_for_dynamic_index_test.cc
@@ -0,0 +1,553 @@
+// 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/var_for_dynamic_index.h"
+#include "src/transform/for_loop_to_loop.h"
+
+#include "src/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using VarForDynamicIndexTest = TransformTest;
+
+TEST_F(VarForDynamicIndexTest, EmptyModule) {
+ auto* src = "";
+ auto* expect = "";
+
+ auto got = Run<ForLoopToLoop, VarForDynamicIndex>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexDynamic) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 4>(1, 2, 3, 4);
+ let x = p[i];
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 4>(1, 2, 3, 4);
+ var var_for_index = p;
+ let x = var_for_index[i];
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexDynamic) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ let x = p[i];
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ var var_for_index = p;
+ let x = var_for_index[i];
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexDynamicChain) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ var j : i32;
+ let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+ let x = p[i][j];
+}
+)";
+
+ // TODO(bclayton): Optimize this case:
+ // This output is not as efficient as it could be.
+ // We only actually need to hoist the inner-most array to a `var`
+ // (`var_for_index`), as later indexing operations will be working with
+ // references, not values.
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ var j : i32;
+ let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+ var var_for_index = p;
+ var var_for_index_1 = var_for_index[i];
+ let x = var_for_index_1[j];
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInForLoopInit) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+ for(let x = p[i]; ; ) {
+ break;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = array<array<i32, 2>, 2>(array<i32, 2>(1, 2), array<i32, 2>(3, 4));
+ var var_for_index = p;
+ for(let x = var_for_index[i]; ; ) {
+ break;
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopInit) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ for(let x = p[i]; ; ) {
+ break;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ var var_for_index = p;
+ for(let x = var_for_index[i]; ; ) {
+ break;
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInForLoopCond) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 2>(1, 2);
+ for(; p[i] < 3; ) {
+ break;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 2>(1, 2);
+ loop {
+ var var_for_index = p;
+ if (!((var_for_index[i] < 3))) {
+ break;
+ }
+ {
+ break;
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopCond) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ for(; p[i].x < 3.0; ) {
+ break;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ loop {
+ var var_for_index = p;
+ if (!((var_for_index[i].x < 3.0))) {
+ break;
+ }
+ {
+ break;
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInForLoopCondWithNestedIndex) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ for(; p[i].x < 3.0; ) {
+ if (p[i].x < 1.0) {
+ var marker = 1;
+ }
+ break;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ loop {
+ var var_for_index = p;
+ if (!((var_for_index[i].x < 3.0))) {
+ break;
+ }
+ {
+ var var_for_index_1 = p;
+ if ((var_for_index_1[i].x < 1.0)) {
+ var marker = 1;
+ }
+ break;
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInElseIf) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 2>(1, 2);
+ if (false) {
+ var marker = 0;
+ } else if (p[i] < 3) {
+ var marker = 1;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 2>(1, 2);
+ if (false) {
+ var marker = 0;
+ } else {
+ var var_for_index = p;
+ if ((var_for_index[i] < 3)) {
+ var marker = 1;
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexInElseIfChain) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 2>(1, 2);
+ if (true) {
+ var marker = 0;
+ } else if (true) {
+ var marker = 1;
+ } else if (p[i] < 3) {
+ var marker = 2;
+ } else if (p[i] < 4) {
+ var marker = 3;
+ } else if (true) {
+ var marker = 4;
+ } else {
+ var marker = 5;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = array<i32, 2>(1, 2);
+ if (true) {
+ var marker = 0;
+ } else if (true) {
+ var marker = 1;
+ } else {
+ var var_for_index = p;
+ if ((var_for_index[i] < 3)) {
+ var marker = 2;
+ } else {
+ var var_for_index_1 = p;
+ if ((var_for_index_1[i] < 4)) {
+ var marker = 3;
+ } else if (true) {
+ var marker = 4;
+ } else {
+ var marker = 5;
+ }
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInElseIf) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ if (false) {
+ var marker_if = 1;
+ } else if (p[i].x < 3.0) {
+ var marker_else_if = 1;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ if (false) {
+ var marker_if = 1;
+ } else {
+ var var_for_index = p;
+ if ((var_for_index[i].x < 3.0)) {
+ var marker_else_if = 1;
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexInElseIfChain) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ if (true) {
+ var marker = 0;
+ } else if (true) {
+ var marker = 1;
+ } else if (p[i].x < 3.0) {
+ var marker = 2;
+ } else if (p[i].y < 3.0) {
+ var marker = 3;
+ } else if (true) {
+ var marker = 4;
+ } else {
+ var marker = 5;
+ }
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var i : i32;
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ if (true) {
+ var marker = 0;
+ } else if (true) {
+ var marker = 1;
+ } else {
+ var var_for_index = p;
+ if ((var_for_index[i].x < 3.0)) {
+ var marker = 2;
+ } else {
+ var var_for_index_1 = p;
+ if ((var_for_index_1[i].y < 3.0)) {
+ var marker = 3;
+ } else if (true) {
+ var marker = 4;
+ } else {
+ var marker = 5;
+ }
+ }
+ }
+}
+)";
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexLiteral) {
+ auto* src = R"(
+fn f() {
+ let p = array<i32, 4>(1, 2, 3, 4);
+ let x = p[1];
+}
+)";
+
+ auto* expect = src;
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexLiteral) {
+ auto* src = R"(
+fn f() {
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ let x = p[1];
+}
+)";
+
+ auto* expect = src;
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexConstantLet) {
+ auto* src = R"(
+fn f() {
+ let p = array<i32, 4>(1, 2, 3, 4);
+ let c = 1;
+ let x = p[c];
+}
+)";
+
+ auto* expect = src;
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexConstantLet) {
+ auto* src = R"(
+fn f() {
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ let c = 1;
+ let x = p[c];
+}
+)";
+
+ auto* expect = src;
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, ArrayIndexLiteralChain) {
+ auto* src = R"(
+fn f() {
+ let a = array<i32, 2>(1, 2);
+ let b = array<i32, 2>(3, 4);
+ let p = array<array<i32, 2>, 2>(a, b);
+ let x = p[0][1];
+}
+)";
+
+ auto* expect = src;
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, MatrixIndexLiteralChain) {
+ auto* src = R"(
+fn f() {
+ let p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ let x = p[0][1];
+}
+)";
+
+ auto* expect = src;
+
+ DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+} // namespace
+} // namespace transform
+} // namespace tint
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 96cc8b0..e8caac3 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -58,7 +58,7 @@
#include "src/transform/manager.h"
#include "src/transform/num_workgroups_from_uniform.h"
#include "src/transform/pad_array_elements.h"
-#include "src/transform/promote_side_effects_to_decl.h"
+#include "src/transform/promote_initializers_to_const_var.h"
#include "src/transform/remove_phonies.h"
#include "src/transform/simplify_pointers.h"
#include "src/transform/unshadow.h"
@@ -192,10 +192,7 @@
// will be transformed by CalculateArrayLength
manager.Add<transform::CalculateArrayLength>();
manager.Add<transform::ExternalTextureTransform>();
-
- data.Add<transform::PromoteSideEffectsToDecl::Config>(
- /* type_ctor_to_let */ true, /* dynamic_index_to_var */ false);
- manager.Add<transform::PromoteSideEffectsToDecl>();
+ manager.Add<transform::PromoteInitializersToConstVar>();
manager.Add<transform::PadArrayElements>();
manager.Add<transform::AddEmptyEntryPoint>();
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 920df33..adace18 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -64,7 +64,7 @@
#include "src/transform/manager.h"
#include "src/transform/module_scope_var_to_entry_point_param.h"
#include "src/transform/pad_array_elements.h"
-#include "src/transform/promote_side_effects_to_decl.h"
+#include "src/transform/promote_initializers_to_const_var.h"
#include "src/transform/remove_phonies.h"
#include "src/transform/simplify_pointers.h"
#include "src/transform/unshadow.h"
@@ -163,10 +163,7 @@
}
manager.Add<transform::CanonicalizeEntryPointIO>();
manager.Add<transform::ExternalTextureTransform>();
-
- data.Add<transform::PromoteSideEffectsToDecl::Config>(
- /* type_ctor_to_let */ true, /* dynamic_index_to_var */ false);
- manager.Add<transform::PromoteSideEffectsToDecl>();
+ manager.Add<transform::PromoteInitializersToConstVar>();
manager.Add<transform::VectorizeScalarMatrixConstructors>();
manager.Add<transform::WrapArraysInStructs>();
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 8dcb271..c3012f4 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -48,10 +48,10 @@
#include "src/transform/fold_constants.h"
#include "src/transform/for_loop_to_loop.h"
#include "src/transform/manager.h"
-#include "src/transform/promote_side_effects_to_decl.h"
#include "src/transform/remove_unreachable_statements.h"
#include "src/transform/simplify_pointers.h"
#include "src/transform/unshadow.h"
+#include "src/transform/var_for_dynamic_index.h"
#include "src/transform/vectorize_scalar_matrix_constructors.h"
#include "src/transform/zero_init_workgroup_memory.h"
#include "src/utils/defer.h"
@@ -272,10 +272,7 @@
manager.Add<transform::CanonicalizeEntryPointIO>();
manager.Add<transform::AddEmptyEntryPoint>();
manager.Add<transform::AddSpirvBlockAttribute>();
-
- data.Add<transform::PromoteSideEffectsToDecl::Config>(
- /* type_ctor_to_let */ false, /* dynamic_index_to_var */ true);
- manager.Add<transform::PromoteSideEffectsToDecl>();
+ manager.Add<transform::VarForDynamicIndex>();
data.Add<transform::CanonicalizeEntryPointIO::Config>(
transform::CanonicalizeEntryPointIO::Config(
diff --git a/test/BUILD.gn b/test/BUILD.gn
index fd880da..76c24b7 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -324,7 +324,7 @@
"../src/transform/multiplanar_external_texture_test.cc",
"../src/transform/num_workgroups_from_uniform_test.cc",
"../src/transform/pad_array_elements_test.cc",
- "../src/transform/promote_side_effects_to_decl_test.cc",
+ "../src/transform/promote_initializers_to_const_var_test.cc",
"../src/transform/remove_phonies_test.cc",
"../src/transform/remove_unreachable_statements_test.cc",
"../src/transform/renamer_test.cc",
@@ -334,6 +334,8 @@
"../src/transform/test_helper.h",
"../src/transform/transform_test.cc",
"../src/transform/unshadow_test.cc",
+ "../src/transform/utils/hoist_to_decl_before_test.cc",
+ "../src/transform/var_for_dynamic_index_test.cc",
"../src/transform/vectorize_scalar_matrix_constructors_test.cc",
"../src/transform/vertex_pulling_test.cc",
"../src/transform/wrap_arrays_in_structs_test.cc",