[tint][wgsl] Pull expression inlining out to raise transform
Change-Id: Ie12c6773da64db1003667131a472694ea4b040e7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/168643
Reviewed-by: dan sinclair <dsinclair@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/wgsl/writer/BUILD.bazel b/src/tint/lang/wgsl/writer/BUILD.bazel
index 38ef94d..23adfef 100644
--- a/src/tint/lang/wgsl/writer/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/BUILD.bazel
@@ -91,17 +91,35 @@
alwayslink = True,
srcs = [
"options_test.cc",
+ "writer_test.cc",
],
deps = [
+ "//src/tint/api/common",
+ "//src/tint/lang/core",
+ "//src/tint/lang/core/constant",
+ "//src/tint/lang/core/intrinsic",
+ "//src/tint/lang/core/ir",
+ "//src/tint/lang/core/ir:test",
+ "//src/tint/lang/core/type",
+ "//src/tint/lang/wgsl",
+ "//src/tint/lang/wgsl/ast",
+ "//src/tint/lang/wgsl/common",
+ "//src/tint/lang/wgsl/features",
+ "//src/tint/lang/wgsl/program",
+ "//src/tint/lang/wgsl/sem",
+ "//src/tint/lang/wgsl/writer/ir_to_program",
+ "//src/tint/lang/wgsl/writer/raise",
"//src/tint/utils/containers",
"//src/tint/utils/diagnostic",
"//src/tint/utils/ice",
+ "//src/tint/utils/id",
"//src/tint/utils/macros",
"//src/tint/utils/math",
"//src/tint/utils/memory",
"//src/tint/utils/reflection",
"//src/tint/utils/result",
"//src/tint/utils/rtti",
+ "//src/tint/utils/symbol",
"//src/tint/utils/text",
"//src/tint/utils/traits",
"@gtest",
diff --git a/src/tint/lang/wgsl/writer/BUILD.cmake b/src/tint/lang/wgsl/writer/BUILD.cmake
index add1cf2..5ca9163 100644
--- a/src/tint/lang/wgsl/writer/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/BUILD.cmake
@@ -100,18 +100,36 @@
################################################################################
tint_add_target(tint_lang_wgsl_writer_test test
lang/wgsl/writer/options_test.cc
+ lang/wgsl/writer/writer_test.cc
)
tint_target_add_dependencies(tint_lang_wgsl_writer_test test
+ tint_api_common
+ tint_lang_core
+ tint_lang_core_constant
+ tint_lang_core_intrinsic
+ tint_lang_core_ir
+ tint_lang_core_ir_test
+ tint_lang_core_type
+ tint_lang_wgsl
+ tint_lang_wgsl_ast
+ tint_lang_wgsl_common
+ tint_lang_wgsl_features
+ tint_lang_wgsl_program
+ tint_lang_wgsl_sem
+ tint_lang_wgsl_writer_ir_to_program
+ tint_lang_wgsl_writer_raise
tint_utils_containers
tint_utils_diagnostic
tint_utils_ice
+ tint_utils_id
tint_utils_macros
tint_utils_math
tint_utils_memory
tint_utils_reflection
tint_utils_result
tint_utils_rtti
+ tint_utils_symbol
tint_utils_text
tint_utils_traits
)
diff --git a/src/tint/lang/wgsl/writer/BUILD.gn b/src/tint/lang/wgsl/writer/BUILD.gn
index e28a833..14ebcde 100644
--- a/src/tint/lang/wgsl/writer/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/BUILD.gn
@@ -90,18 +90,38 @@
if (tint_build_unittests) {
if (tint_build_wgsl_writer) {
tint_unittests_source_set("unittests") {
- sources = [ "options_test.cc" ]
+ sources = [
+ "options_test.cc",
+ "writer_test.cc",
+ ]
deps = [
"${tint_src_dir}:gmock_and_gtest",
+ "${tint_src_dir}/api/common",
+ "${tint_src_dir}/lang/core",
+ "${tint_src_dir}/lang/core/constant",
+ "${tint_src_dir}/lang/core/intrinsic",
+ "${tint_src_dir}/lang/core/ir",
+ "${tint_src_dir}/lang/core/ir:unittests",
+ "${tint_src_dir}/lang/core/type",
+ "${tint_src_dir}/lang/wgsl",
+ "${tint_src_dir}/lang/wgsl/ast",
+ "${tint_src_dir}/lang/wgsl/common",
+ "${tint_src_dir}/lang/wgsl/features",
+ "${tint_src_dir}/lang/wgsl/program",
+ "${tint_src_dir}/lang/wgsl/sem",
+ "${tint_src_dir}/lang/wgsl/writer/ir_to_program",
+ "${tint_src_dir}/lang/wgsl/writer/raise",
"${tint_src_dir}/utils/containers",
"${tint_src_dir}/utils/diagnostic",
"${tint_src_dir}/utils/ice",
+ "${tint_src_dir}/utils/id",
"${tint_src_dir}/utils/macros",
"${tint_src_dir}/utils/math",
"${tint_src_dir}/utils/memory",
"${tint_src_dir}/utils/reflection",
"${tint_src_dir}/utils/result",
"${tint_src_dir}/utils/rtti",
+ "${tint_src_dir}/utils/symbol",
"${tint_src_dir}/utils/text",
"${tint_src_dir}/utils/traits",
]
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/BUILD.bazel b/src/tint/lang/wgsl/writer/ir_to_program/BUILD.bazel
index 1c5947a..f1efc80 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/ir_to_program/BUILD.bazel
@@ -85,7 +85,6 @@
"ir_to_program_test.h",
] + select({
":tint_build_wgsl_writer": [
- "inlining_test.cc",
"ir_to_program_test.cc",
],
"//conditions:default": [],
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/BUILD.cmake b/src/tint/lang/wgsl/writer/ir_to_program/BUILD.cmake
index 9828bc9..813f157 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/ir_to_program/BUILD.cmake
@@ -121,7 +121,6 @@
if(TINT_BUILD_WGSL_WRITER)
tint_target_add_sources(tint_lang_wgsl_writer_ir_to_program_test test
- "lang/wgsl/writer/ir_to_program/inlining_test.cc"
"lang/wgsl/writer/ir_to_program/ir_to_program_test.cc"
)
tint_target_add_dependencies(tint_lang_wgsl_writer_ir_to_program_test test
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/BUILD.gn b/src/tint/lang/wgsl/writer/ir_to_program/BUILD.gn
index 3f9583c..1698652 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/ir_to_program/BUILD.gn
@@ -116,10 +116,7 @@
]
if (tint_build_wgsl_writer) {
- sources += [
- "inlining_test.cc",
- "ir_to_program_test.cc",
- ]
+ sources += [ "ir_to_program_test.cc" ]
deps += [ "${tint_src_dir}/lang/wgsl/writer" ]
}
}
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/inlining_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/inlining_test.cc
deleted file mode 100644
index e69347d..0000000
--- a/src/tint/lang/wgsl/writer/ir_to_program/inlining_test.cc
+++ /dev/null
@@ -1,1408 +0,0 @@
-// Copyright 2023 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-// list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-// GEN_BUILD:CONDITION(tint_build_wgsl_writer)
-
-#include <string>
-
-#include "src/tint/lang/core/ir/disassembler.h"
-#include "src/tint/lang/core/type/array.h"
-#include "src/tint/lang/core/type/matrix.h"
-#include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h"
-#include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.h"
-#include "src/tint/lang/wgsl/writer/writer.h"
-#include "src/tint/utils/text/string.h"
-
-namespace tint::wgsl::writer {
-namespace {
-
-using namespace tint::core::number_suffixes; // NOLINT
-using namespace tint::core::fluent_types; // NOLINT
-
-using IRToProgramInliningTest = IRToProgramTest;
-
-////////////////////////////////////////////////////////////////////////////////
-// Load / Store
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramInliningTest, LoadVar_ThenStoreVar_ThenUseLoad) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- b.Store(var, 2_i);
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- v = 2i;
- return v_1;
-}
-)");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Binary op
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramInliningTest, BinaryOpUnsequencedLHSThenUnsequencedRHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
- auto* rhs = b.Add(ty.i32(), 3_i, 4_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- return ((1i + 2i) + (3i + 4i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, BinaryOpSequencedLHSThenUnsequencedRHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
- auto* rhs = b.Add(ty.i32(), 2_i, 3_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- return (a(1i) + (2i + 3i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, BinaryOpUnsequencedLHSThenSequencedRHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
- auto* rhs = b.Call(ty.i32(), fn_a, 3_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- return ((1i + 2i) + a(3i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, BinaryOpSequencedLHSThenSequencedRHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
- auto* rhs = b.Call(ty.i32(), fn_a, 2_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- return (a(1i) + a(2i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, BinaryOpUnsequencedRHSThenUnsequencedLHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* rhs = b.Add(ty.i32(), 3_i, 4_i);
- auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- return ((1i + 2i) + (3i + 4i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, BinaryOpUnsequencedRHSThenSequencedLHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* rhs = b.Add(ty.i32(), 2_i, 3_i);
- auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- return (a(1i) + (2i + 3i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, BinaryOpSequencedRHSThenUnsequencedLHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* rhs = b.Call(ty.i32(), fn_a, 3_i);
- auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- return ((1i + 2i) + a(3i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, BinaryOpSequencedRHSThenSequencedLHS) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] {
- auto* rhs = b.Call(ty.i32(), fn_a, 2_i);
- auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
- auto* bin = b.Add(ty.i32(), lhs, rhs);
- b.Return(fn_b, bin);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b() -> i32 {
- let v_1 = a(2i);
- return (a(1i) + v_1);
-}
-)");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Call
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramInliningTest, CallSequencedXYZ) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
- fn_b->SetParams(
- {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
-
- auto* fn_c = b.Function("c", ty.i32());
- b.Append(fn_c->Block(), [&] {
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* call = b.Call(ty.i32(), fn_b, x, y, z);
- b.Return(fn_c, call);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b(v_1 : i32, v_2 : i32, v_3 : i32) -> i32 {
- return 0i;
-}
-
-fn c() -> i32 {
- return b(a(1i), a(2i), a(3i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, CallSequencedYXZ) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
- fn_b->SetParams(
- {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
-
- auto* fn_c = b.Function("c", ty.i32());
- b.Append(fn_c->Block(), [&] {
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* call = b.Call(ty.i32(), fn_b, x, y, z);
- b.Return(fn_c, call);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b(v_1 : i32, v_2 : i32, v_3 : i32) -> i32 {
- return 0i;
-}
-
-fn c() -> i32 {
- let v_4 = a(2i);
- return b(a(1i), v_4, a(3i));
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, CallSequencedXZY) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
- fn_b->SetParams(
- {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
-
- auto* fn_c = b.Function("c", ty.i32());
- b.Append(fn_c->Block(), [&] {
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* call = b.Call(ty.i32(), fn_b, x, y, z);
- b.Return(fn_c, call);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b(v_1 : i32, v_2 : i32, v_3 : i32) -> i32 {
- return 0i;
-}
-
-fn c() -> i32 {
- let v_4 = a(1i);
- let v_5 = a(3i);
- return b(v_4, a(2i), v_5);
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, CallSequencedZXY) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
- fn_b->SetParams(
- {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
-
- auto* fn_c = b.Function("c", ty.i32());
- b.Append(fn_c->Block(), [&] {
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* call = b.Call(ty.i32(), fn_b, x, y, z);
- b.Return(fn_c, call);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b(v_1 : i32, v_2 : i32, v_3 : i32) -> i32 {
- return 0i;
-}
-
-fn c() -> i32 {
- let v_4 = a(3i);
- return b(a(1i), a(2i), v_4);
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, CallSequencedYZX) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
- fn_b->SetParams(
- {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
-
- auto* fn_c = b.Function("c", ty.i32());
- b.Append(fn_c->Block(), [&] {
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* call = b.Call(ty.i32(), fn_b, x, y, z);
- b.Return(fn_c, call);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b(v_1 : i32, v_2 : i32, v_3 : i32) -> i32 {
- return 0i;
-}
-
-fn c() -> i32 {
- let v_4 = a(2i);
- let v_5 = a(3i);
- return b(a(1i), v_4, v_5);
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, CallSequencedZYX) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn_b = b.Function("b", ty.i32());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
- fn_b->SetParams(
- {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
-
- auto* fn_c = b.Function("c", ty.i32());
- b.Append(fn_c->Block(), [&] {
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* call = b.Call(ty.i32(), fn_b, x, y, z);
- b.Return(fn_c, call);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 0i;
-}
-
-fn b(v_1 : i32, v_2 : i32, v_3 : i32) -> i32 {
- return 0i;
-}
-
-fn c() -> i32 {
- let v_4 = a(3i);
- let v_5 = a(2i);
- return b(a(1i), v_5, v_4);
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenCallVoidFn_ThenUseLoad) {
- auto* fn_a = b.Function("a", ty.void_());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a); });
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- b.Call(ty.void_(), fn_a);
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn a() {
-}
-
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- a();
- return v_1;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenCallUnusedi32Fn_ThenUseLoad) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- b.Call(ty.i32(), fn_a);
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> i32 {
- return 1i;
-}
-
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- a();
- return v_1;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenCalli32Fn_ThenUseLoadBeforeCall) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- auto* call = b.Call(ty.i32(), fn_a);
- b.Return(fn, b.Add(ty.i32(), load, call));
- });
-
- EXPECT_WGSL(R"(
-fn a() -> i32 {
- return 1i;
-}
-
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- return (v + a());
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenCalli32Fn_ThenUseCallBeforeLoad) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- auto* call = b.Call(ty.i32(), fn_a);
- b.Return(fn, b.Add(ty.i32(), call, load));
- });
-
- EXPECT_WGSL(R"(
-fn a() -> i32 {
- return 1i;
-}
-
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- return (a() + v_1);
-}
-)");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Access
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramInliningTest, Access_ArrayOfArrayOfArray_XYZ) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Var(ty.ptr<function, array<array<array<i32, 3>, 4>, 5>>());
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
- b.Return(fn, b.Load(access));
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> i32 {
- var v_1 : array<array<array<i32, 3u>, 4u>, 5u>;
- return v_1[a(1i)][a(2i)][a(3i)];
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, Access_ArrayOfArrayOfArray_YXZ) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Var(ty.ptr<function, array<array<array<i32, 3>, 4>, 5>>());
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
- b.Return(fn, b.Load(access));
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> i32 {
- var v_1 : array<array<array<i32, 3u>, 4u>, 5u>;
- let v_2 = a(2i);
- return v_1[a(1i)][v_2][a(3i)];
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, Access_ArrayOfArrayOfArray_ZXY) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Var(ty.ptr<function, array<array<array<i32, 3>, 4>, 5>>());
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
- b.Return(fn, b.Load(access));
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> i32 {
- var v_1 : array<array<array<i32, 3u>, 4u>, 5u>;
- let v_2 = a(3i);
- return v_1[a(1i)][a(2i)][v_2];
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, Access_ArrayOfArrayOfArray_ZYX) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Var(ty.ptr<function, array<array<array<i32, 3>, 4>, 5>>());
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
- b.Return(fn, b.Load(access));
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> i32 {
- var v_1 : array<array<array<i32, 3u>, 4u>, 5u>;
- let v_2 = a(3i);
- let v_3 = a(2i);
- return v_1[a(1i)][v_3][v_2];
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, Access_ArrayOfMat3x4f_XYZ) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.f32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* access = b.Access(ty.f32(), arr, x, y, z);
- b.Return(fn, access);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> f32 {
- return array<mat3x4<f32>, 5u>()[a(1i)][a(2i)][a(3i)];
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, Access_ArrayOfMat3x4f_YXZ) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.f32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* access = b.Access(ty.f32(), arr, x, y, z);
- b.Return(fn, access);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> f32 {
- let v_1 = a(2i);
- return array<mat3x4<f32>, 5u>()[a(1i)][v_1][a(3i)];
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, Access_ArrayOfMat3x4f_ZXY) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.f32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* access = b.Access(ty.f32(), arr, x, y, z);
- b.Return(fn, access);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> f32 {
- let v_1 = a(3i);
- return array<mat3x4<f32>, 5u>()[a(1i)][a(2i)][v_1];
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, Access_ArrayOfMat3x4f_ZYX) {
- auto* fn_a = b.Function("a", ty.i32());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
- fn_a->SetParams({b.FunctionParam(ty.i32())});
-
- auto* fn = b.Function("f", ty.f32());
- b.Append(fn->Block(), [&] {
- auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
- auto* z = b.Call(ty.i32(), fn_a, 3_i);
- auto* y = b.Call(ty.i32(), fn_a, 2_i);
- auto* x = b.Call(ty.i32(), fn_a, 1_i);
- auto* access = b.Access(ty.f32(), arr, x, y, z);
- b.Return(fn, access);
- });
-
- EXPECT_WGSL(R"(
-fn a(v : i32) -> i32 {
- return 1i;
-}
-
-fn f() -> f32 {
- let v_1 = a(3i);
- let v_2 = a(2i);
- return array<mat3x4<f32>, 5u>()[a(1i)][v_2][v_1];
-}
-)");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// If
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramInliningTest, UnsequencedOutsideIf) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* v = b.Add(ty.i32(), 1_i, 2_i);
- auto* if_ = b.If(true);
- b.Append(if_->True(), [&] { b.Return(fn, v); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- if (true) {
- return (1i + 2i);
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, SequencedOutsideIf) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- var->SetInitializer(b.Constant(1_i));
- auto* v_1 = b.Load(var);
- auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
- auto* if_ = b.If(true);
- b.Append(if_->True(), [&] { b.Return(fn, v_2); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32 = 1i;
- let v_1 = (v + 2i);
- if (true) {
- return v_1;
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, UnsequencedUsedByIfCondition) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* v = b.Equal(ty.bool_(), 1_i, 2_i);
- auto* if_ = b.If(v);
- b.Append(if_->True(), [&] { b.Return(fn, 3_i); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- if ((1i == 2i)) {
- return 3i;
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, SequencedUsedByIfCondition) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- var->SetInitializer(b.Constant(1_i));
- auto* v_1 = b.Load(var);
- auto* v_2 = b.Equal(ty.bool_(), v_1, 2_i);
- auto* if_ = b.If(v_2);
- b.Append(if_->True(), [&] { b.Return(fn, 3_i); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32 = 1i;
- if ((v == 2i)) {
- return 3i;
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenWriteToVarInIf_ThenUseLoad) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- auto* if_ = b.If(true);
- b.Append(if_->True(), [&] {
- b.Store(var, 2_i);
- b.ExitIf(if_);
- });
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- if (true) {
- v = 2i;
- }
- return v_1;
-}
-)");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Switch
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramInliningTest, UnsequencedOutsideSwitch) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* v = b.Add(ty.i32(), 1_i, 2_i);
- auto* switch_ = b.Switch(3_i);
- auto* case_ = b.DefaultCase(switch_);
- b.Append(case_, [&] { b.Return(fn, v); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- switch(3i) {
- default: {
- return (1i + 2i);
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, SequencedOutsideSwitch) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- var->SetInitializer(b.Constant(1_i));
- auto* v_1 = b.Load(var);
- auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
- auto* switch_ = b.Switch(3_i);
- auto* case_ = b.DefaultCase(switch_);
- b.Append(case_, [&] { b.Return(fn, v_2); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32 = 1i;
- let v_1 = (v + 2i);
- switch(3i) {
- default: {
- return v_1;
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, UnsequencedUsedBySwitchCondition) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* v = b.Add(ty.i32(), 1_i, 2_i);
- auto* switch_ = b.Switch(v);
- auto* case_ = b.DefaultCase(switch_);
- b.Append(case_, [&] { b.Return(fn, 3_i); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- switch((1i + 2i)) {
- default: {
- return 3i;
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, SequencedUsedBySwitchCondition) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- var->SetInitializer(b.Constant(1_i));
- auto* v_1 = b.Load(var);
- auto* switch_ = b.Switch(v_1);
- auto* case_ = b.DefaultCase(switch_);
- b.Append(case_, [&] { b.Return(fn, 3_i); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32 = 1i;
- switch(v) {
- default: {
- return 3i;
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenWriteToVarInSwitch_ThenUseLoad) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- auto* switch_ = b.Switch(1_i);
- auto* case_ = b.DefaultCase(switch_);
- b.Append(case_, [&] {
- b.Store(var, 2_i);
- b.ExitSwitch(switch_);
- });
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- switch(1i) {
- default: {
- v = 2i;
- }
- }
- return v_1;
-}
-)");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Loop
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramInliningTest, UnsequencedOutsideLoopInitializer) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- auto* v = b.Add(ty.i32(), 1_i, 2_i);
- auto* loop = b.Loop();
- b.Append(loop->Initializer(), [&] {
- b.Store(var, v);
- b.NextIteration(loop);
- });
- b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- {
- v = (1i + 2i);
- loop {
- break;
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, SequencedOutsideLoopInitializer) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- auto* v_1 = b.Load(var);
- auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
- auto* loop = b.Loop();
- b.Append(loop->Initializer(), [&] {
- b.Store(var, v_2);
- b.NextIteration(loop);
- });
- b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- let v_1 = (v + 2i);
- {
- v = v_1;
- loop {
- break;
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenWriteToVarInLoopInitializer_ThenUseLoad) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- auto* loop = b.Loop();
- b.Append(loop->Initializer(), [&] {
- b.Store(var, 2_i);
- b.NextIteration(loop);
- });
- b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- {
- v = 2i;
- loop {
- break;
- }
- }
- return v_1;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, UnsequencedOutsideLoopBody) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* v = b.Add(ty.i32(), 1_i, 2_i);
- auto* loop = b.Loop();
- b.Append(loop->Body(), [&] { b.Return(fn, v); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- loop {
- return (1i + 2i);
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, SequencedOutsideLoopBody) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- auto* v_1 = b.Load(var);
- auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
- auto* loop = b.Loop();
- b.Append(loop->Body(), [&] { b.Return(fn, v_2); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- let v_1 = (v + 2i);
- loop {
- return v_1;
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenWriteToVarInLoopBody_ThenUseLoad) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- auto* loop = b.Loop();
- b.Append(loop->Body(), [&] {
- b.Store(var, 2_i);
- b.ExitLoop(loop);
- });
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- loop {
- v = 2i;
- break;
- }
- return v_1;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, UnsequencedOutsideLoopContinuing) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* v = b.Add(ty.i32(), 1_i, 2_i);
- auto* loop = b.Loop();
- b.Append(loop->Body(), [&] { b.Continue(loop); });
- b.Append(loop->Continuing(), [&] { b.BreakIf(loop, b.Equal(ty.bool_(), v, 3_i)); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- loop {
-
- continuing {
- break if ((1i + 2i) == 3i);
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, SequencedOutsideLoopContinuing) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- auto* v_1 = b.Load(var);
- auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
- auto* loop = b.Loop();
- b.Append(loop->Body(), [&] { b.Continue(loop); });
- b.Append(loop->Continuing(), [&] { b.BreakIf(loop, b.Equal(ty.bool_(), v_2, 3_i)); });
- b.Return(fn, 0_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- let v_1 = (v + 2i);
- loop {
-
- continuing {
- break if (v_1 == 3i);
- }
- }
- return 0i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVar_ThenWriteToVarInLoopContinuing_ThenUseLoad) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* load = b.Load(var);
- auto* loop = b.Loop();
- b.Append(loop->Body(), [&] { b.Continue(loop); });
- b.Append(loop->Continuing(), [&] {
- b.Store(var, 2_i);
- b.BreakIf(loop, true);
- });
- b.Return(fn, load);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- let v_1 = v;
- loop {
-
- continuing {
- v = 2i;
- break if true;
- }
- }
- return v_1;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVarInLoopInitializer_ThenReadAndWriteToVarInLoopBody) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* loop = b.Loop();
- b.Append(loop->Initializer(), [&] {
- auto* load = b.Load(var);
- b.NextIteration(loop);
- b.Append(loop->Body(), [&] {
- b.Store(var, b.Add(ty.i32(), load, 1_i));
- b.ExitLoop(loop);
- });
- });
- b.Return(fn, 3_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- {
- let v_1 = v;
- loop {
- v = (v_1 + 1i);
- break;
- }
- }
- return 3i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVarInLoopInitializer_ThenReadAndWriteToVarInLoopContinuing) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* loop = b.Loop();
- b.Append(loop->Initializer(), [&] {
- auto* load = b.Load(var);
- b.NextIteration(loop);
- b.Append(loop->Body(), [&] { b.Continue(loop); });
- b.Append(loop->Continuing(), [&] {
- b.Store(var, b.Add(ty.i32(), load, 1_i));
- b.BreakIf(loop, true);
- });
- });
- b.Return(fn, 3_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- {
- let v_1 = v;
- loop {
-
- continuing {
- v = (v_1 + 1i);
- break if true;
- }
- }
- }
- return 3i;
-}
-)");
-}
-
-TEST_F(IRToProgramInliningTest, LoadVarInLoopBody_ThenReadAndWriteToVarInLoopContinuing) {
- auto* fn = b.Function("f", ty.i32());
- b.Append(fn->Block(), [&] {
- auto* var = b.Var(ty.ptr<function, i32>());
- b.Store(var, 1_i);
- auto* loop = b.Loop();
- b.Append(loop->Body(), [&] {
- auto* load = b.Load(var);
- b.Continue(loop);
-
- b.Append(loop->Continuing(), [&] {
- b.Store(var, b.Add(ty.i32(), load, 1_i));
- b.BreakIf(loop, true);
- });
- });
- b.Return(fn, 3_i);
- });
-
- EXPECT_WGSL(R"(
-fn f() -> i32 {
- var v : i32;
- v = 1i;
- loop {
- let v_1 = v;
-
- continuing {
- v = (v_1 + 1i);
- break if true;
- }
- }
- return 3i;
-}
-)");
-}
-
-} // namespace
-} // namespace tint::wgsl::writer
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index 028fee4..c0ee5e3 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -177,9 +177,6 @@
/// The current switch case block
const core::ir::Block* current_switch_case_ = nullptr;
- /// Values that can be inlined.
- Hashset<const core::ir::Value*, 64> can_inline_;
-
/// Set of enable directives emitted.
Hashset<wgsl::Extension, 4> enables_;
@@ -333,7 +330,6 @@
StatementList Statements(const core::ir::Block* block) {
StatementList stmts;
if (block) {
- MarkInlinable(block);
TINT_SCOPED_ASSIGNMENT(statements_, &stmts);
for (auto* inst : *block) {
Instruction(inst);
@@ -342,73 +338,6 @@
return stmts;
}
- void MarkInlinable(const core::ir::Block* block) {
- // An ordered list of possibly-inlinable values returned by sequenced instructions that have
- // not yet been marked-for or ruled-out-for inlining.
- UniqueVector<const core::ir::Value*, 32> pending_resolution;
-
- // Walk the instructions of the block starting with the first.
- for (auto* inst : *block) {
- // Is the instruction sequenced?
- bool sequenced = inst->Sequenced();
-
- // Walk the instruction's operands starting with the right-most.
- auto operands = inst->Operands();
- for (auto* operand : tint::Reverse(operands)) {
- if (!pending_resolution.Contains(operand)) {
- continue;
- }
- // Operand is in 'pending_resolution'
-
- if (pending_resolution.TryPop(operand)) {
- // Operand was the last sequenced value to be added to 'pending_resolution'
- // This operand can be inlined as it does not change the sequencing order.
- can_inline_.Add(operand);
- sequenced = true; // Inherit the 'sequenced' flag from the inlined value
- } else {
- // Operand was in 'pending_resolution', but was not the last sequenced value to
- // be added. Inlining this operand would break the sequencing order, so must be
- // emitted as a let. All preceding pending values must also be emitted as a
- // let to prevent them being inlined and breaking the sequencing order.
- // Remove all the values in pending upto and including 'operand'.
- for (size_t i = 0; i < pending_resolution.Length(); i++) {
- if (pending_resolution[i] == operand) {
- pending_resolution.Erase(0, i + 1);
- break;
- }
- }
- }
- }
-
- if (inst->Results().Length() == 1) {
- // Instruction has a single result value.
- // Check to see if the result of this instruction is a candidate for inlining.
- auto* result = inst->Result(0);
- // Only values with a single usage can be inlined.
- // Named values are not inlined, as we want to emit the name for a let.
- if (result->NumUsages() == 1 && !mod.NameOf(result).IsValid()) {
- if (sequenced) {
- // The value comes from a sequenced instruction. We need to ensure
- // instruction ordering so add it to 'pending_resolution'.
- pending_resolution.Add(result);
- } else {
- // The value comes from an unsequenced instruction. Just inline.
- can_inline_.Add(result);
- }
- continue;
- }
- }
-
- // At this point the value has been ruled out for inlining.
-
- if (sequenced) {
- // A sequenced instruction with zero or multiple return values cannot be inlined.
- // All preceding sequenced instructions cannot be inlined past this point.
- pending_resolution.Clear();
- }
- }
- }
-
void Append(const ast::Statement* inst) { statements_->Push(inst); }
void Instruction(const core::ir::Instruction* inst) {
@@ -493,7 +422,6 @@
const ast::Expression* cond = nullptr;
StatementList body_stmts;
{
- MarkInlinable(l->Body());
TINT_SCOPED_ASSIGNMENT(statements_, &body_stmts);
for (auto* inst : *l->Body()) {
if (body_stmts.IsEmpty()) {
@@ -658,9 +586,14 @@
}
void Let(const core::ir::Let* let) {
- Symbol name = NameFor(let->Result(0));
- Append(b.Decl(b.Let(name, Expr(let->Value(), PtrKind::kPtr))));
- Bind(let->Result(0), name, PtrKind::kPtr);
+ auto* result = let->Result(0);
+ if (mod.NameOf(result).IsValid() || result->NumUsages() > 0) {
+ Symbol name = NameFor(result);
+ Append(b.Decl(b.Let(name, Expr(let->Value(), PtrKind::kPtr))));
+ Bind(result, name, PtrKind::kPtr);
+ } else {
+ Append(b.Assign(b.Phony(), Expr(let->Value(), PtrKind::kPtr)));
+ }
}
void Store(const core::ir::Store* store) {
@@ -1142,30 +1075,10 @@
const ast::Expression* expr,
PtrKind ptr_kind = PtrKind::kRef) {
TINT_ASSERT(value);
- if (can_inline_.Remove(value)) {
- // Value will be inlined at its place of usage.
- if (TINT_LIKELY(bindings_.Add(value, InlinedValue{expr, ptr_kind}))) {
- return;
- }
- } else {
- if (value->Type()->Is<core::type::Pointer>()) {
- expr = ToPtrKind(expr, ptr_kind, PtrKind::kPtr);
- }
- auto mod_name = mod.NameOf(value);
- if (!value->IsUsed() && !mod_name.IsValid()) {
- // Value has no usages and no name.
- // Assign to a phony. These support more data types than a 'let', and avoids
- // allocation of unused names.
- Append(b.Assign(b.Phony(), expr));
- } else {
- Symbol name = NameFor(value, mod_name.NameView());
- Append(b.Decl(b.Let(name, expr)));
- Bind(value, name, PtrKind::kPtr);
- }
- return;
+ // Value will be inlined at its place of usage.
+ if (TINT_UNLIKELY(!bindings_.Add(value, InlinedValue{expr, ptr_kind}))) {
+ TINT_ICE() << "Bind(" << value->TypeInfo().name << ") called twice for same value";
}
-
- TINT_ICE() << "Bind(" << value->TypeInfo().name << ") called twice for same value";
}
/// Associates the IR value @p value with the AST 'var', 'let' or parameter with the name @p
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
index c2fff06..15395bb 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
@@ -1006,218 +1006,7 @@
)");
}
-TEST_F(IRToProgramTest, ShortCircuit_And_Let_2) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam("a", ty.bool_());
- auto* pb = b.FunctionParam("b", ty.bool_());
- fn->SetParams({pa, pb});
-
- b.Append(fn->Block(), [&] {
- auto* if_ = b.If(pa);
- if_->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if_->True(), [&] { b.ExitIf(if_, pb); });
- b.Append(if_->False(), [&] { b.ExitIf(if_, false); });
-
- mod.SetName(if_, "l");
- b.Return(fn, if_);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool) -> bool {
- let l = (a && b);
- return l;
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_And_Let_3_ab_c) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam("a", ty.bool_());
- auto* pb = b.FunctionParam("b", ty.bool_());
- auto* pc = b.FunctionParam("c", ty.bool_());
- fn->SetParams({pa, pb, pc});
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(pa);
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, pb); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
-
- auto* if2 = b.If(if1);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, pc); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
-
- mod.SetName(if2, "l");
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool, c : bool) -> bool {
- let l = ((a && b) && c);
- return l;
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_And_Let_3_a_bc) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam("a", ty.bool_());
- auto* pb = b.FunctionParam("b", ty.bool_());
- auto* pc = b.FunctionParam("c", ty.bool_());
- fn->SetParams({pa, pb, pc});
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(pa);
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] {
- auto* if2 = b.If(pb);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, pc); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
-
- b.ExitIf(if1, if2);
- });
- b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
-
- mod.SetName(if1, "l");
- b.Return(fn, if1);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool, c : bool) -> bool {
- let l = (a && (b && c));
- return l;
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_And_Call_2) {
- auto* fn_a = b.Function("a", ty.bool_());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- auto* if_ = b.If(b.Call(ty.bool_(), fn_a));
- if_->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if_->True(), [&] { b.ExitIf(if_, b.Call(ty.bool_(), fn_b)); });
- b.Append(if_->False(), [&] { b.ExitIf(if_, false); });
-
- b.Return(fn, if_);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn f() -> bool {
- return (a() && b());
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_And_Call_3_ab_c) {
- auto* fn_a = b.Function("a", ty.bool_());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn_c = b.Function("c", ty.bool_());
- b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_b)); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
-
- auto* if2 = b.If(if1);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
-
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn c() -> bool {
- return true;
-}
-
-fn f() -> bool {
- return ((a() && b()) && c());
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_And_Call_3_a_bc) {
- auto* fn_a = b.Function("a", ty.bool_());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn_c = b.Function("c", ty.bool_());
- b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] {
- auto* if2 = b.If(b.Call(ty.bool_(), fn_b));
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
-
- b.ExitIf(if1, if2);
- });
- b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
-
- b.Return(fn, if1);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn c() -> bool {
- return true;
-}
-
-fn f() -> bool {
- return (a() && (b() && c()));
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_Or_Param_2) {
+TEST_F(IRToProgramTest, ShortCircuit_Or_2) {
auto* fn = b.Function("f", ty.bool_());
auto* pa = b.FunctionParam("a", ty.bool_());
auto* pb = b.FunctionParam("b", ty.bool_());
@@ -1239,7 +1028,7 @@
)");
}
-TEST_F(IRToProgramTest, ShortCircuit_Or_Param_3_ab_c) {
+TEST_F(IRToProgramTest, ShortCircuit_Or_3_ab_c) {
auto* fn = b.Function("f", ty.bool_());
auto* pa = b.FunctionParam("a", ty.bool_());
auto* pb = b.FunctionParam("b", ty.bool_());
@@ -1267,7 +1056,7 @@
)");
}
-TEST_F(IRToProgramTest, ShortCircuit_Or_Param_3_a_bc) {
+TEST_F(IRToProgramTest, ShortCircuit_Or_3_a_bc) {
auto* fn = b.Function("f", ty.bool_());
auto* pa = b.FunctionParam("a", ty.bool_());
auto* pb = b.FunctionParam("b", ty.bool_());
@@ -1297,217 +1086,6 @@
)");
}
-TEST_F(IRToProgramTest, ShortCircuit_Or_Let_2) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam("a", ty.bool_());
- auto* pb = b.FunctionParam("b", ty.bool_());
- fn->SetParams({pa, pb});
-
- b.Append(fn->Block(), [&] {
- auto* if_ = b.If(pa);
- if_->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if_->True(), [&] { b.ExitIf(if_, true); });
- b.Append(if_->False(), [&] { b.ExitIf(if_, pb); });
-
- mod.SetName(if_, "l");
- b.Return(fn, if_);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool) -> bool {
- let l = (a || b);
- return l;
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_Or_Let_3_ab_c) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam("a", ty.bool_());
- auto* pb = b.FunctionParam("b", ty.bool_());
- auto* pc = b.FunctionParam("c", ty.bool_());
- fn->SetParams({pa, pb, pc});
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(pa);
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, pb); });
-
- auto* if2 = b.If(if1);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, pc); });
-
- mod.SetName(if2, "l");
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool, c : bool) -> bool {
- let l = ((a || b) || c);
- return l;
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_Or_Let_3_a_bc) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam("a", ty.bool_());
- auto* pb = b.FunctionParam("b", ty.bool_());
- auto* pc = b.FunctionParam("c", ty.bool_());
- fn->SetParams({pa, pb, pc});
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(pa);
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
- b.Append(if1->False(), [&] {
- auto* if2 = b.If(pb);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, pc); });
-
- b.ExitIf(if1, if2);
- });
-
- mod.SetName(if1, "l");
- b.Return(fn, if1);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool, c : bool) -> bool {
- let l = (a || (b || c));
- return l;
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_Or_Call_2) {
- auto* fn_a = b.Function("a", ty.bool_());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- auto* if_ = b.If(b.Call(ty.bool_(), fn_a));
- if_->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if_->True(), [&] { b.ExitIf(if_, true); });
- b.Append(if_->False(), [&] { b.ExitIf(if_, b.Call(ty.bool_(), fn_b)); });
-
- b.Return(fn, if_);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn f() -> bool {
- return (a() || b());
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_Or_Call_3_ab_c) {
- auto* fn_a = b.Function("a", ty.bool_());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn_c = b.Function("c", ty.bool_());
- b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_b)); });
-
- auto* if2 = b.If(if1);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
-
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn c() -> bool {
- return true;
-}
-
-fn f() -> bool {
- return ((a() || b()) || c());
-}
-)");
-}
-
-TEST_F(IRToProgramTest, ShortCircuit_Or_Call_3_a_bc) {
- auto* fn_a = b.Function("a", ty.bool_());
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn_c = b.Function("c", ty.bool_());
- b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
- b.Append(if1->False(), [&] {
- auto* if2 = b.If(b.Call(ty.bool_(), fn_b));
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
-
- b.ExitIf(if1, if2);
- });
-
- b.Return(fn, if1);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn c() -> bool {
- return true;
-}
-
-fn f() -> bool {
- return (a() || (b() || c()));
-}
-)");
-}
-
TEST_F(IRToProgramTest, ShortCircuit_Mixed) {
auto* fn_b = b.Function("b", ty.bool_());
b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
@@ -1552,257 +1130,7 @@
}
fn f(a : bool, c : bool) -> bool {
- let l = ((a || b()) && (c || d()));
- return l;
-}
-)");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Non short-circuiting binary ops
-// Similar to the above, but cannot be short-circuited as the RHS is evaluated
-// outside of the if block.
-////////////////////////////////////////////////////////////////////////////////
-TEST_F(IRToProgramTest, NonShortCircuit_And_ParamCallParam_a_bc) {
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam(ty.bool_());
- auto* pc = b.FunctionParam(ty.bool_());
- mod.SetName(pa, "a");
- mod.SetName(pc, "c");
- fn->SetParams({pa, pc});
-
- b.Append(fn->Block(), [&] {
- // 'b() && c' is evaluated before 'a'.
- auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, pc); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
-
- auto* if2 = b.If(pa);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, if1); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn b() -> bool {
- return true;
-}
-
-fn f(a : bool, c : bool) -> bool {
- let v = (b() && c);
- return (a && v);
-}
-)");
-}
-
-TEST_F(IRToProgramTest, NonShortCircuit_And_Call_3_a_bc) {
- auto* fn_a = b.Function("a", ty.bool_());
-
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
-
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn_c = b.Function("c", ty.bool_());
-
- b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- // 'b() && c()' is evaluated before 'a()'.
- auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_c)); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
-
- auto* if2 = b.If(b.Call(ty.bool_(), fn_a));
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, if1); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
-
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn c() -> bool {
- return true;
-}
-
-fn f() -> bool {
- let v = (b() && c());
- return (a() && v);
-}
-)");
-}
-
-TEST_F(IRToProgramTest, NonShortCircuit_And_Param_3_a_bc_EarlyEval) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam(ty.bool_());
- auto* pb = b.FunctionParam(ty.bool_());
- auto* pc = b.FunctionParam(ty.bool_());
- mod.SetName(pa, "a");
- mod.SetName(pb, "b");
- mod.SetName(pc, "c");
- fn->SetParams({pa, pb, pc});
-
- b.Append(fn->Block(), [&] {
- // 'b && c' is evaluated outside the true block of if2, but these can be moved to the RHS
- // of the 'a &&' as the 'b && c' is not sequenced.
- auto* if1 = b.If(pb);
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, pc); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
-
- auto* if2 = b.If(pa);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, if1); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
-
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool, c : bool) -> bool {
- let v = (b && c);
- return (a && v);
-}
-)");
-}
-
-TEST_F(IRToProgramTest, NonShortCircuit_Or_ParamCallParam_a_bc) {
- auto* fn_b = b.Function("b", ty.bool_());
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam(ty.bool_());
- auto* pc = b.FunctionParam(ty.bool_());
- mod.SetName(pa, "a");
- mod.SetName(pc, "c");
- fn->SetParams({pa, pc});
-
- b.Append(fn->Block(), [&] {
- // 'b() && c' is evaluated before 'a'.
- auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, pc); });
-
- auto* if2 = b.If(pa);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, if1); });
-
- mod.SetName(if2, "l");
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn b() -> bool {
- return true;
-}
-
-fn f(a : bool, c : bool) -> bool {
- let v = (b() || c);
- let l = (a || v);
- return l;
-}
-)");
-}
-
-TEST_F(IRToProgramTest, NonShortCircuit_Or_Call_3_a_bc) {
- auto* fn_a = b.Function("a", ty.bool_());
-
- b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
-
- auto* fn_b = b.Function("b", ty.bool_());
-
- b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
-
- auto* fn_c = b.Function("c", ty.bool_());
-
- b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
-
- auto* fn = b.Function("f", ty.bool_());
-
- b.Append(fn->Block(), [&] {
- auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_c)); });
-
- auto* if2 = b.If(b.Call(ty.bool_(), fn_a));
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, if1); });
-
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn a() -> bool {
- return true;
-}
-
-fn b() -> bool {
- return true;
-}
-
-fn c() -> bool {
- return true;
-}
-
-fn f() -> bool {
- let v = (b() || c());
- return (a() || v);
-}
-)");
-}
-
-TEST_F(IRToProgramTest, NonShortCircuit_Or_Param_3_a_bc_EarlyEval) {
- auto* fn = b.Function("f", ty.bool_());
- auto* pa = b.FunctionParam(ty.bool_());
- auto* pb = b.FunctionParam(ty.bool_());
- auto* pc = b.FunctionParam(ty.bool_());
- mod.SetName(pa, "a");
- mod.SetName(pb, "b");
- mod.SetName(pc, "c");
- fn->SetParams({pa, pb, pc});
-
- b.Append(fn->Block(), [&] {
- // 'b || c' is evaluated outside the true block of if2, but these can be moved to the RHS
- // of the 'a ||' as the 'b || c' is not sequenced.
- auto* if1 = b.If(pb);
- if1->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
- b.Append(if1->False(), [&] { b.ExitIf(if1, pc); });
-
- auto* if2 = b.If(pa);
- if2->SetResults(b.InstructionResult(ty.bool_()));
- b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
- b.Append(if2->False(), [&] { b.ExitIf(if2, if1); });
-
- b.Return(fn, if2);
- });
-
- EXPECT_WGSL(R"(
-fn f(a : bool, b : bool, c : bool) -> bool {
- let v = (b || c);
- return (a || v);
+ return ((a || b()) && (c || d()));
}
)");
}
diff --git a/src/tint/lang/wgsl/writer/raise/BUILD.bazel b/src/tint/lang/wgsl/writer/raise/BUILD.bazel
index e403363..00fe151 100644
--- a/src/tint/lang/wgsl/writer/raise/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/raise/BUILD.bazel
@@ -41,10 +41,12 @@
srcs = [
"raise.cc",
"rename_conflicts.cc",
+ "value_to_let.cc",
],
hdrs = [
"raise.h",
"rename_conflicts.h",
+ "value_to_let.h",
],
deps = [
"//src/tint/api/common",
@@ -79,6 +81,7 @@
srcs = [
"raise_test.cc",
"rename_conflicts_test.cc",
+ "value_to_let_test.cc",
],
deps = [
"//src/tint/api/common",
diff --git a/src/tint/lang/wgsl/writer/raise/BUILD.cmake b/src/tint/lang/wgsl/writer/raise/BUILD.cmake
index cf18c7f..6bc7469 100644
--- a/src/tint/lang/wgsl/writer/raise/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/raise/BUILD.cmake
@@ -43,6 +43,8 @@
lang/wgsl/writer/raise/raise.h
lang/wgsl/writer/raise/rename_conflicts.cc
lang/wgsl/writer/raise/rename_conflicts.h
+ lang/wgsl/writer/raise/value_to_let.cc
+ lang/wgsl/writer/raise/value_to_let.h
)
tint_target_add_dependencies(tint_lang_wgsl_writer_raise lib
@@ -77,6 +79,7 @@
tint_add_target(tint_lang_wgsl_writer_raise_test test
lang/wgsl/writer/raise/raise_test.cc
lang/wgsl/writer/raise/rename_conflicts_test.cc
+ lang/wgsl/writer/raise/value_to_let_test.cc
)
tint_target_add_dependencies(tint_lang_wgsl_writer_raise_test test
diff --git a/src/tint/lang/wgsl/writer/raise/BUILD.gn b/src/tint/lang/wgsl/writer/raise/BUILD.gn
index 53b013a..f873e62 100644
--- a/src/tint/lang/wgsl/writer/raise/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/raise/BUILD.gn
@@ -48,6 +48,8 @@
"raise.h",
"rename_conflicts.cc",
"rename_conflicts.h",
+ "value_to_let.cc",
+ "value_to_let.h",
]
deps = [
"${tint_src_dir}/api/common",
@@ -79,6 +81,7 @@
sources = [
"raise_test.cc",
"rename_conflicts_test.cc",
+ "value_to_let_test.cc",
]
deps = [
"${tint_src_dir}:gmock_and_gtest",
diff --git a/src/tint/lang/wgsl/writer/raise/raise.cc b/src/tint/lang/wgsl/writer/raise/raise.cc
index 9a64c26..e2c4f56 100644
--- a/src/tint/lang/wgsl/writer/raise/raise.cc
+++ b/src/tint/lang/wgsl/writer/raise/raise.cc
@@ -36,6 +36,7 @@
#include "src/tint/lang/wgsl/builtin_fn.h"
#include "src/tint/lang/wgsl/ir/builtin_call.h"
#include "src/tint/lang/wgsl/writer/raise/rename_conflicts.h"
+#include "src/tint/lang/wgsl/writer/raise/value_to_let.h"
namespace tint::wgsl::writer {
namespace {
@@ -233,6 +234,9 @@
if (auto result = raise::RenameConflicts(mod); result != Success) {
return result.Failure();
}
+ if (auto result = raise::ValueToLet(mod); result != Success) {
+ return result.Failure();
+ }
return Success;
}
diff --git a/src/tint/lang/wgsl/writer/raise/raise_test.cc b/src/tint/lang/wgsl/writer/raise/raise_test.cc
index 499d41b..fbdb64b 100644
--- a/src/tint/lang/wgsl/writer/raise/raise_test.cc
+++ b/src/tint/lang/wgsl/writer/raise/raise_test.cc
@@ -60,6 +60,7 @@
%f = func():void -> %b1 {
%b1 = block {
%2:i32 = wgsl.max 1i, 2i
+ %3:i32 = let %2
ret
}
}
@@ -154,8 +155,9 @@
%3:void = wgsl.workgroupBarrier
store %W, 42i
%4:i32 = load %W
- %5:void = wgsl.workgroupBarrier
- ret %4
+ %5:i32 = let %4
+ %6:void = wgsl.workgroupBarrier
+ ret %5
}
}
)";
diff --git a/src/tint/lang/wgsl/writer/raise/value_to_let.cc b/src/tint/lang/wgsl/writer/raise/value_to_let.cc
new file mode 100644
index 0000000..ea14ec2
--- /dev/null
+++ b/src/tint/lang/wgsl/writer/raise/value_to_let.cc
@@ -0,0 +1,199 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/writer/raise/value_to_let.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/utils/containers/reverse.h"
+
+namespace tint::wgsl::writer::raise {
+
+namespace {
+
+/// PIMPL state for the transform.
+struct State {
+ /// The IR module.
+ core::ir::Module& ir;
+
+ /// The IR builder.
+ core::ir::Builder b{ir};
+
+ /// The type manager.
+ core::type::Manager& ty{ir.Types()};
+
+ /// Process the module.
+ void Process() {
+ // Process each block.
+ for (auto* block : ir.blocks.Objects()) {
+ if (block != ir.root_block) {
+ Process(block);
+ }
+ }
+ }
+
+ private:
+ void Process(core::ir::Block* block) {
+ // An ordered list of possibly-inlinable values returned by sequenced instructions that have
+ // not yet been marked-for or ruled-out-for inlining.
+ UniqueVector<core::ir::InstructionResult*, 32> pending_resolution;
+
+ auto hoist_pending = [&](size_t count = std::numeric_limits<size_t>::max()) {
+ size_t n = std::min(count, pending_resolution.Length());
+ if (n > 0) {
+ for (size_t i = 0; i < n; i++) {
+ MaybeReplaceWithLet(pending_resolution[i]);
+ }
+ pending_resolution.Erase(0, n);
+ }
+ };
+
+ // Walk the instructions of the block starting with the first.
+ for (auto* inst = block->Front(); inst;) {
+ auto next = inst->next;
+ TINT_DEFER(inst = next);
+
+ // Is the instruction sequenced?
+ bool sequenced = inst->Sequenced();
+
+ // Walk the instruction's operands starting with the right-most.
+ auto operands = inst->Operands();
+ for (auto* operand : tint::Reverse(operands)) {
+ if (!operand) {
+ continue;
+ }
+
+ auto* value = operand->As<core::ir::InstructionResult>();
+ if (!pending_resolution.Contains(value)) {
+ continue;
+ }
+ // Operand is in 'pending_resolution'
+
+ if (pending_resolution.TryPop(value)) {
+ // Operand was the last sequenced value to be added to 'pending_resolution'
+ // This operand can be inlined as it does not change the sequencing order.
+ sequenced = true; // Inherit the 'sequenced' flag from the inlined value
+ } else {
+ // Operand was in 'pending_resolution', but was not the last sequenced value to
+ // be added. Inlining this operand would break the sequencing order, so must be
+ // emitted as a let. All preceding pending values must also be emitted as a
+ // let to prevent them being inlined and breaking the sequencing order.
+ // Remove all the values in pending up to and including 'operand'.
+ for (size_t i = 0; i < pending_resolution.Length(); i++) {
+ if (pending_resolution[i] == operand) {
+ hoist_pending(i + 1);
+ break;
+ }
+ }
+ }
+ }
+
+ if (inst->Results().Length() == 1) {
+ // Instruction has a single result value.
+ // Check to see if the result of this instruction is a candidate for inlining.
+ auto* result = inst->Result(0);
+ // Only values with a single usage can be inlined.
+ // Named values are not inlined, as we want to emit the name for a let.
+ if (CanInline(result)) {
+ if (sequenced) {
+ // The value comes from a sequenced instruction. We need to ensure
+ // instruction ordering so add it to 'pending_resolution'.
+ pending_resolution.Add(result);
+ }
+ continue;
+ }
+
+ MaybeReplaceWithLet(result);
+ }
+
+ // At this point the value has been ruled out for inlining.
+
+ if (sequenced) {
+ // A sequenced instruction with zero or multiple return values cannot be inlined.
+ // All preceding sequenced instructions cannot be inlined past this point.
+ hoist_pending();
+ }
+ }
+
+ hoist_pending();
+ }
+
+ bool CanInline(core::ir::InstructionResult* value) {
+ if (ir.NameOf(value).IsValid()) {
+ // Named values should become lets
+ return false;
+ }
+
+ if (value->NumUsages() != 1) {
+ // Zero or multiple uses cannot be inlined
+ return false;
+ }
+
+ return true;
+ }
+
+ void MaybeReplaceWithLet(core::ir::InstructionResult* value) {
+ auto* inst = value->Instruction();
+ if (inst->IsAnyOf<core::ir::Var, core::ir::Let>()) {
+ return;
+ }
+ if (inst->Is<core::ir::Call>() && value->Usages().IsEmpty()) {
+ bool must_use =
+ inst->Is<core::ir::BuiltinCall>() && !value->Type()->Is<core::type::Void>();
+ if (!must_use) {
+ return; // Call statement
+ }
+ }
+
+ auto* let = b.Let(value->Type());
+ value->ReplaceAllUsesWith(let->Result(0));
+ let->SetValue(value);
+ let->InsertAfter(inst);
+ if (auto name = ir.NameOf(value); name.IsValid()) {
+ ir.SetName(let, name.Name());
+ ir.ClearName(value);
+ }
+ }
+};
+
+} // namespace
+
+Result<SuccessType> ValueToLet(core::ir::Module& ir) {
+ auto result = core::ir::ValidateAndDumpIfNeeded(ir, "ValueToLet transform");
+ if (result != Success) {
+ return result;
+ }
+
+ State{ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::wgsl::writer::raise
diff --git a/src/tint/lang/wgsl/writer/raise/value_to_let.h b/src/tint/lang/wgsl/writer/raise/value_to_let.h
new file mode 100644
index 0000000..2a3b4c0
--- /dev/null
+++ b/src/tint/lang/wgsl/writer/raise/value_to_let.h
@@ -0,0 +1,54 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_WGSL_WRITER_RAISE_VALUE_TO_LET_H_
+#define SRC_TINT_LANG_WGSL_WRITER_RAISE_VALUE_TO_LET_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::wgsl::writer::raise {
+
+/// ValueToLet is a transform that moves "non-inlinable" instruction values to let instructions.
+/// An expression is considered "non-inlinable" if any of the the following are true:
+/// * The value has multiple uses.
+/// * The value's instruction is a load that when inlined would cross a store instruction.
+/// * The value's instruction is a store instruction that when inlined would cross a load or store
+/// instruction.
+/// * The value is used in a block different to the value's instruction.
+///
+/// @param module the module to transform
+/// @returns error diagnostics on failure
+Result<SuccessType> ValueToLet(core::ir::Module& module);
+
+} // namespace tint::wgsl::writer::raise
+
+#endif // SRC_TINT_LANG_WGSL_WRITER_RAISE_VALUE_TO_LET_H_
diff --git a/src/tint/lang/wgsl/writer/raise/value_to_let_test.cc b/src/tint/lang/wgsl/writer/raise/value_to_let_test.cc
new file mode 100644
index 0000000..d9ac335
--- /dev/null
+++ b/src/tint/lang/wgsl/writer/raise/value_to_let_test.cc
@@ -0,0 +1,2693 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/writer/raise/value_to_let.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+namespace tint::wgsl::writer::raise {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+using WgslWriter_ValueToLetTest = tint::core::ir::transform::TransformTest;
+
+TEST_F(WgslWriter_ValueToLetTest, Empty) {
+ auto* expect = R"(
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Load / Store
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenStoreVar_ThenUseLoad) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ b.Store(var, 2_i);
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ store %2, 2i
+ ret %3
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ %4:i32 = let %3
+ store %2, 2i
+ ret %4
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Binary op
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpUnsequencedLHSThenUnsequencedRHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
+ auto* rhs = b.Add(ty.i32(), 3_i, 4_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = add 1i, 2i
+ %5:i32 = add 3i, 4i
+ %6:i32 = add %4, %5
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpSequencedLHSThenUnsequencedRHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
+ auto* rhs = b.Add(ty.i32(), 2_i, 3_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = call %a, 1i
+ %5:i32 = add 2i, 3i
+ %6:i32 = add %4, %5
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpUnsequencedLHSThenSequencedRHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
+ auto* rhs = b.Call(ty.i32(), fn_a, 3_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = add 1i, 2i
+ %5:i32 = call %a, 3i
+ %6:i32 = add %4, %5
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpSequencedLHSThenSequencedRHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
+ auto* rhs = b.Call(ty.i32(), fn_a, 2_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = call %a, 1i
+ %5:i32 = call %a, 2i
+ %6:i32 = add %4, %5
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpUnsequencedRHSThenUnsequencedLHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* rhs = b.Add(ty.i32(), 3_i, 4_i);
+ auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = add 3i, 4i
+ %5:i32 = add 1i, 2i
+ %6:i32 = add %5, %4
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpUnsequencedRHSThenSequencedLHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* rhs = b.Add(ty.i32(), 2_i, 3_i);
+ auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = add 2i, 3i
+ %5:i32 = call %a, 1i
+ %6:i32 = add %5, %4
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpSequencedRHSThenUnsequencedLHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* rhs = b.Call(ty.i32(), fn_a, 3_i);
+ auto* lhs = b.Add(ty.i32(), 1_i, 2_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = call %a, 3i
+ %5:i32 = add 1i, 2i
+ %6:i32 = add %5, %4
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, BinaryOpSequencedRHSThenSequencedLHS) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] {
+ auto* rhs = b.Call(ty.i32(), fn_a, 2_i);
+ auto* lhs = b.Call(ty.i32(), fn_a, 1_i);
+ auto* bin = b.Add(ty.i32(), lhs, rhs);
+ b.Return(fn_b, bin);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = call %a, 2i
+ %5:i32 = call %a, 1i
+ %6:i32 = add %5, %4
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func():i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = call %a, 2i
+ %5:i32 = let %4
+ %6:i32 = call %a, 1i
+ %7:i32 = add %6, %5
+ ret %7
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Call
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslWriter_ValueToLetTest, CallSequencedXYZ) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
+ fn_b->SetParams(
+ {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
+
+ auto* fn_c = b.Function("c", ty.i32());
+ b.Append(fn_c->Block(), [&] {
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* call = b.Call(ty.i32(), fn_b, x, y, z);
+ b.Return(fn_c, call);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 1i
+ %9:i32 = call %a, 2i
+ %10:i32 = call %a, 3i
+ %11:i32 = call %b, %8, %9, %10
+ ret %11
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, CallSequencedYXZ) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
+ fn_b->SetParams(
+ {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
+
+ auto* fn_c = b.Function("c", ty.i32());
+ b.Append(fn_c->Block(), [&] {
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* call = b.Call(ty.i32(), fn_b, x, y, z);
+ b.Return(fn_c, call);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 2i
+ %9:i32 = call %a, 1i
+ %10:i32 = call %a, 3i
+ %11:i32 = call %b, %9, %8, %10
+ ret %11
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 2i
+ %9:i32 = let %8
+ %10:i32 = call %a, 1i
+ %11:i32 = call %a, 3i
+ %12:i32 = call %b, %10, %9, %11
+ ret %12
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, CallSequencedXZY) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
+ fn_b->SetParams(
+ {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
+
+ auto* fn_c = b.Function("c", ty.i32());
+ b.Append(fn_c->Block(), [&] {
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* call = b.Call(ty.i32(), fn_b, x, y, z);
+ b.Return(fn_c, call);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 1i
+ %9:i32 = call %a, 3i
+ %10:i32 = call %a, 2i
+ %11:i32 = call %b, %8, %10, %9
+ ret %11
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 1i
+ %9:i32 = let %8
+ %10:i32 = call %a, 3i
+ %11:i32 = let %10
+ %12:i32 = call %a, 2i
+ %13:i32 = call %b, %9, %12, %11
+ ret %13
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, CallSequencedZXY) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
+ fn_b->SetParams(
+ {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
+
+ auto* fn_c = b.Function("c", ty.i32());
+ b.Append(fn_c->Block(), [&] {
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* call = b.Call(ty.i32(), fn_b, x, y, z);
+ b.Return(fn_c, call);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 3i
+ %9:i32 = call %a, 1i
+ %10:i32 = call %a, 2i
+ %11:i32 = call %b, %9, %10, %8
+ ret %11
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 3i
+ %9:i32 = let %8
+ %10:i32 = call %a, 1i
+ %11:i32 = call %a, 2i
+ %12:i32 = call %b, %10, %11, %9
+ ret %12
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, CallSequencedYZX) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
+ fn_b->SetParams(
+ {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
+
+ auto* fn_c = b.Function("c", ty.i32());
+ b.Append(fn_c->Block(), [&] {
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* call = b.Call(ty.i32(), fn_b, x, y, z);
+ b.Return(fn_c, call);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 2i
+ %9:i32 = call %a, 3i
+ %10:i32 = call %a, 1i
+ %11:i32 = call %b, %10, %8, %9
+ ret %11
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 2i
+ %9:i32 = let %8
+ %10:i32 = call %a, 3i
+ %11:i32 = let %10
+ %12:i32 = call %a, 1i
+ %13:i32 = call %b, %12, %9, %11
+ ret %13
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, CallSequencedZYX) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 0_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn_b = b.Function("b", ty.i32());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, 0_i); });
+ fn_b->SetParams(
+ {b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32()), b.FunctionParam(ty.i32())});
+
+ auto* fn_c = b.Function("c", ty.i32());
+ b.Append(fn_c->Block(), [&] {
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* call = b.Call(ty.i32(), fn_b, x, y, z);
+ b.Return(fn_c, call);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 3i
+ %9:i32 = call %a, 2i
+ %10:i32 = call %a, 1i
+ %11:i32 = call %b, %10, %9, %8
+ ret %11
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 0i
+ }
+}
+%b = func(%4:i32, %5:i32, %6:i32):i32 -> %b2 {
+ %b2 = block {
+ ret 0i
+ }
+}
+%c = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %a, 3i
+ %9:i32 = let %8
+ %10:i32 = call %a, 2i
+ %11:i32 = let %10
+ %12:i32 = call %a, 1i
+ %13:i32 = call %b, %12, %11, %9
+ ret %13
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenCallVoidFn_ThenUseLoad) {
+ auto* fn_a = b.Function("a", ty.void_());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a); });
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ b.Call(ty.void_(), fn_a);
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%a = func():void -> %b1 {
+ %b1 = block {
+ ret
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:void = call %a
+ ret %4
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func():void -> %b1 {
+ %b1 = block {
+ ret
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:i32 = let %4
+ %6:void = call %a
+ ret %5
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenCallUnusedi32Fn_ThenUseLoad) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ b.Call(ty.i32(), fn_a);
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%a = func():i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:i32 = call %a
+ ret %4
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func():i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:i32 = let %4
+ %6:i32 = call %a
+ ret %5
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenCalli32Fn_ThenUseLoadBeforeCall) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ auto* call = b.Call(ty.i32(), fn_a);
+ b.Return(fn, b.Add(ty.i32(), load, call));
+ });
+
+ auto* src = R"(
+%a = func():i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:i32 = call %a
+ %6:i32 = add %4, %5
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func():i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:i32 = call %a
+ %6:i32 = add %4, %5
+ ret %6
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenCalli32Fn_ThenUseCallBeforeLoad) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ auto* call = b.Call(ty.i32(), fn_a);
+ b.Return(fn, b.Add(ty.i32(), call, load));
+ });
+
+ auto* src = R"(
+%a = func():i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:i32 = call %a
+ %6:i32 = add %5, %4
+ ret %6
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func():i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %3:ptr<function, i32, read_write> = var
+ store %3, 1i
+ %4:i32 = load %3
+ %5:i32 = let %4
+ %6:i32 = call %a
+ %7:i32 = add %6, %5
+ ret %7
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Access
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfArrayOfArray_XYZ) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Var<function, array<array<array<i32, 3>, 4>, 5>>();
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
+ b.Return(fn, b.Load(access));
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 1i
+ %6:i32 = call %a, 2i
+ %7:i32 = call %a, 3i
+ %8:ptr<function, i32, read_write> = access %4, %5, %6, %7
+ %9:i32 = load %8
+ ret %9
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 1i
+ %6:i32 = call %a, 2i
+ %7:i32 = call %a, 3i
+ %8:ptr<function, i32, read_write> = access %4, %5, %6, %7
+ %9:i32 = load %8
+ ret %9
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfArrayOfArray_YXZ) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Var<function, array<array<array<i32, 3>, 4>, 5>>();
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
+ b.Return(fn, b.Load(access));
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 2i
+ %6:i32 = call %a, 1i
+ %7:i32 = call %a, 3i
+ %8:ptr<function, i32, read_write> = access %4, %6, %5, %7
+ %9:i32 = load %8
+ ret %9
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 2i
+ %6:i32 = let %5
+ %7:i32 = call %a, 1i
+ %8:i32 = call %a, 3i
+ %9:ptr<function, i32, read_write> = access %4, %7, %6, %8
+ %10:i32 = load %9
+ ret %10
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfArrayOfArray_ZXY) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Var<function, array<array<array<i32, 3>, 4>, 5>>();
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
+ b.Return(fn, b.Load(access));
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 3i
+ %6:i32 = call %a, 1i
+ %7:i32 = call %a, 2i
+ %8:ptr<function, i32, read_write> = access %4, %6, %7, %5
+ %9:i32 = load %8
+ ret %9
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 3i
+ %6:i32 = let %5
+ %7:i32 = call %a, 1i
+ %8:i32 = call %a, 2i
+ %9:ptr<function, i32, read_write> = access %4, %7, %8, %6
+ %10:i32 = load %9
+ ret %10
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfArrayOfArray_ZYX) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Var<function, array<array<array<i32, 3>, 4>, 5>>();
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* access = b.Access(ty.ptr<function, i32>(), arr, x, y, z);
+ b.Return(fn, b.Load(access));
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 3i
+ %6:i32 = call %a, 2i
+ %7:i32 = call %a, 1i
+ %8:ptr<function, i32, read_write> = access %4, %7, %6, %5
+ %9:i32 = load %8
+ ret %9
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():i32 -> %b2 {
+ %b2 = block {
+ %4:ptr<function, array<array<array<i32, 3>, 4>, 5>, read_write> = var
+ %5:i32 = call %a, 3i
+ %6:i32 = let %5
+ %7:i32 = call %a, 2i
+ %8:i32 = let %7
+ %9:i32 = call %a, 1i
+ %10:ptr<function, i32, read_write> = access %4, %9, %8, %6
+ %11:i32 = load %10
+ ret %11
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfMat3x4f_XYZ) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.f32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* access = b.Access(ty.f32(), arr, x, y, z);
+ b.Return(fn, access);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 1i
+ %6:i32 = call %a, 2i
+ %7:i32 = call %a, 3i
+ %8:f32 = access %4, %5, %6, %7
+ ret %8
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 1i
+ %6:i32 = call %a, 2i
+ %7:i32 = call %a, 3i
+ %8:f32 = access %4, %5, %6, %7
+ ret %8
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfMat3x4f_YXZ) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.f32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* access = b.Access(ty.f32(), arr, x, y, z);
+ b.Return(fn, access);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 2i
+ %6:i32 = call %a, 1i
+ %7:i32 = call %a, 3i
+ %8:f32 = access %4, %6, %5, %7
+ ret %8
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 2i
+ %6:i32 = let %5
+ %7:i32 = call %a, 1i
+ %8:i32 = call %a, 3i
+ %9:f32 = access %4, %7, %6, %8
+ ret %9
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfMat3x4f_ZXY) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.f32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* access = b.Access(ty.f32(), arr, x, y, z);
+ b.Return(fn, access);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 3i
+ %6:i32 = call %a, 1i
+ %7:i32 = call %a, 2i
+ %8:f32 = access %4, %6, %7, %5
+ ret %8
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 3i
+ %6:i32 = let %5
+ %7:i32 = call %a, 1i
+ %8:i32 = call %a, 2i
+ %9:f32 = access %4, %7, %8, %6
+ ret %9
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, Access_ArrayOfMat3x4f_ZYX) {
+ auto* fn_a = b.Function("a", ty.i32());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, 1_i); });
+ fn_a->SetParams({b.FunctionParam(ty.i32())});
+
+ auto* fn = b.Function("f", ty.f32());
+ b.Append(fn->Block(), [&] {
+ auto* arr = b.Construct(ty.array<mat3x4<f32>, 5>());
+ auto* z = b.Call(ty.i32(), fn_a, 3_i);
+ auto* y = b.Call(ty.i32(), fn_a, 2_i);
+ auto* x = b.Call(ty.i32(), fn_a, 1_i);
+ auto* access = b.Access(ty.f32(), arr, x, y, z);
+ b.Return(fn, access);
+ });
+
+ auto* src = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 3i
+ %6:i32 = call %a, 2i
+ %7:i32 = call %a, 1i
+ %8:f32 = access %4, %7, %6, %5
+ ret %8
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%a = func(%2:i32):i32 -> %b1 {
+ %b1 = block {
+ ret 1i
+ }
+}
+%f = func():f32 -> %b2 {
+ %b2 = block {
+ %4:array<mat3x4<f32>, 5> = construct
+ %5:i32 = call %a, 3i
+ %6:i32 = let %5
+ %7:i32 = call %a, 2i
+ %8:i32 = let %7
+ %9:i32 = call %a, 1i
+ %10:f32 = access %4, %9, %8, %6
+ ret %10
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// If
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslWriter_ValueToLetTest, UnsequencedOutsideIf) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Add(ty.i32(), 1_i, 2_i);
+ auto* if_ = b.If(true);
+ b.Append(if_->True(), [&] { b.Return(fn, v); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:i32 = add 1i, 2i
+ if true [t: %b2] { # if_1
+ %b2 = block { # true
+ ret %2
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, SequencedOutsideIf) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ var->SetInitializer(b.Constant(1_i));
+ auto* v_1 = b.Load(var);
+ auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
+ auto* if_ = b.If(true);
+ b.Append(if_->True(), [&] { b.Return(fn, v_2); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ if true [t: %b2] { # if_1
+ %b2 = block { # true
+ ret %4
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ %5:i32 = let %4
+ if true [t: %b2] { # if_1
+ %b2 = block { # true
+ ret %5
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, UnsequencedUsedByIfCondition) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Equal(ty.bool_(), 1_i, 2_i);
+ auto* if_ = b.If(v);
+ b.Append(if_->True(), [&] { b.Return(fn, 3_i); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:bool = eq 1i, 2i
+ if %2 [t: %b2] { # if_1
+ %b2 = block { # true
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:bool = eq 1i, 2i
+ if %2 [t: %b2] { # if_1
+ %b2 = block { # true
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, SequencedUsedByIfCondition) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ var->SetInitializer(b.Constant(1_i));
+ auto* v_1 = b.Load(var);
+ auto* v_2 = b.Equal(ty.bool_(), v_1, 2_i);
+ auto* if_ = b.If(v_2);
+ b.Append(if_->True(), [&] { b.Return(fn, 3_i); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ %4:bool = eq %3, 2i
+ if %4 [t: %b2] { # if_1
+ %b2 = block { # true
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ %4:bool = eq %3, 2i
+ if %4 [t: %b2] { # if_1
+ %b2 = block { # true
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenWriteToVarInIf_ThenUseLoad) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ auto* if_ = b.If(true);
+ b.Append(if_->True(), [&] {
+ b.Store(var, 2_i);
+ b.ExitIf(if_);
+ });
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ if true [t: %b2] { # if_1
+ %b2 = block { # true
+ store %2, 2i
+ exit_if # if_1
+ }
+ }
+ ret %3
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ %4:i32 = let %3
+ if true [t: %b2] { # if_1
+ %b2 = block { # true
+ store %2, 2i
+ exit_if # if_1
+ }
+ }
+ ret %4
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Switch
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslWriter_ValueToLetTest, UnsequencedOutsideSwitch) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Add(ty.i32(), 1_i, 2_i);
+ auto* switch_ = b.Switch(3_i);
+ auto* case_ = b.DefaultCase(switch_);
+ b.Append(case_, [&] { b.Return(fn, v); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:i32 = add 1i, 2i
+ switch 3i [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ ret %2
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, SequencedOutsideSwitch) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ var->SetInitializer(b.Constant(1_i));
+ auto* v_1 = b.Load(var);
+ auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
+ auto* switch_ = b.Switch(3_i);
+ auto* case_ = b.DefaultCase(switch_);
+ b.Append(case_, [&] { b.Return(fn, v_2); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ switch 3i [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ ret %4
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ %5:i32 = let %4
+ switch 3i [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ ret %5
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, UnsequencedUsedBySwitchCondition) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Add(ty.i32(), 1_i, 2_i);
+ auto* switch_ = b.Switch(v);
+ auto* case_ = b.DefaultCase(switch_);
+ b.Append(case_, [&] { b.Return(fn, 3_i); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:i32 = add 1i, 2i
+ switch %2 [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:i32 = add 1i, 2i
+ switch %2 [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, SequencedUsedBySwitchCondition) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ var->SetInitializer(b.Constant(1_i));
+ auto* v_1 = b.Load(var);
+ auto* switch_ = b.Switch(v_1);
+ auto* case_ = b.DefaultCase(switch_);
+ b.Append(case_, [&] { b.Return(fn, 3_i); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ switch %3 [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var, 1i
+ %3:i32 = load %2
+ switch %3 [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ ret 3i
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenWriteToVarInSwitch_ThenUseLoad) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ auto* switch_ = b.Switch(1_i);
+ auto* case_ = b.DefaultCase(switch_);
+ b.Append(case_, [&] {
+ b.Store(var, 2_i);
+ b.ExitSwitch(switch_);
+ });
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ switch 1i [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ store %2, 2i
+ exit_switch # switch_1
+ }
+ }
+ ret %3
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ %4:i32 = let %3
+ switch 1i [c: (default, %b2)] { # switch_1
+ %b2 = block { # case
+ store %2, 2i
+ exit_switch # switch_1
+ }
+ }
+ ret %4
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Loop
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslWriter_ValueToLetTest, UnsequencedOutsideLoopInitializer) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ auto* v = b.Add(ty.i32(), 1_i, 2_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ b.Store(var, v);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ %3:i32 = add 1i, 2i
+ loop [i: %b2, b: %b3] { # loop_1
+ %b2 = block { # initializer
+ store %2, %3
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ exit_loop # loop_1
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, SequencedOutsideLoopInitializer) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ auto* v_1 = b.Load(var);
+ auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ b.Store(var, v_2);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ loop [i: %b2, b: %b3] { # loop_1
+ %b2 = block { # initializer
+ store %2, %4
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ exit_loop # loop_1
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ %5:i32 = let %4
+ loop [i: %b2, b: %b3] { # loop_1
+ %b2 = block { # initializer
+ store %2, %5
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ exit_loop # loop_1
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenWriteToVarInLoopInitializer_ThenUseLoad) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ b.Store(var, 2_i);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ loop [i: %b2, b: %b3] { # loop_1
+ %b2 = block { # initializer
+ store %2, 2i
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ exit_loop # loop_1
+ }
+ }
+ ret %3
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ %4:i32 = let %3
+ loop [i: %b2, b: %b3] { # loop_1
+ %b2 = block { # initializer
+ store %2, 2i
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ exit_loop # loop_1
+ }
+ }
+ ret %4
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, UnsequencedOutsideLoopBody) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Add(ty.i32(), 1_i, 2_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Return(fn, v); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:i32 = add 1i, 2i
+ loop [b: %b2] { # loop_1
+ %b2 = block { # body
+ ret %2
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, SequencedOutsideLoopBody) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ auto* v_1 = b.Load(var);
+ auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Return(fn, v_2); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ loop [b: %b2] { # loop_1
+ %b2 = block { # body
+ ret %4
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ %5:i32 = let %4
+ loop [b: %b2] { # loop_1
+ %b2 = block { # body
+ ret %5
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenWriteToVarInLoopBody_ThenUseLoad) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ b.Store(var, 2_i);
+ b.ExitLoop(loop);
+ });
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ loop [b: %b2] { # loop_1
+ %b2 = block { # body
+ store %2, 2i
+ exit_loop # loop_1
+ }
+ }
+ ret %3
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ %4:i32 = let %3
+ loop [b: %b2] { # loop_1
+ %b2 = block { # body
+ store %2, 2i
+ exit_loop # loop_1
+ }
+ }
+ ret %4
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, UnsequencedOutsideLoopContinuing) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Add(ty.i32(), 1_i, 2_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, b.Equal(ty.bool_(), v, 3_i)); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:i32 = add 1i, 2i
+ loop [b: %b2, c: %b3] { # loop_1
+ %b2 = block { # body
+ continue %b3
+ }
+ %b3 = block { # continuing
+ %3:bool = eq %2, 3i
+ break_if %3 %b2
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, SequencedOutsideLoopContinuing) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ auto* v_1 = b.Load(var);
+ auto* v_2 = b.Add(ty.i32(), v_1, 2_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, b.Equal(ty.bool_(), v_2, 3_i)); });
+ b.Return(fn, 0_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ loop [b: %b2, c: %b3] { # loop_1
+ %b2 = block { # body
+ continue %b3
+ }
+ %b3 = block { # continuing
+ %5:bool = eq %4, 3i
+ break_if %5 %b2
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ Run(ValueToLet);
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ %3:i32 = load %2
+ %4:i32 = add %3, 2i
+ %5:i32 = let %4
+ loop [b: %b2, c: %b3] { # loop_1
+ %b2 = block { # body
+ continue %b3
+ }
+ %b3 = block { # continuing
+ %6:bool = eq %5, 3i
+ break_if %6 %b2
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVar_ThenWriteToVarInLoopContinuing_ThenUseLoad) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* load = b.Load(var);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] {
+ b.Store(var, 2_i);
+ b.BreakIf(loop, true);
+ });
+ b.Return(fn, load);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ loop [b: %b2, c: %b3] { # loop_1
+ %b2 = block { # body
+ continue %b3
+ }
+ %b3 = block { # continuing
+ store %2, 2i
+ break_if true %b2
+ }
+ }
+ ret %3
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ %3:i32 = load %2
+ %4:i32 = let %3
+ loop [b: %b2, c: %b3] { # loop_1
+ %b2 = block { # body
+ continue %b3
+ }
+ %b3 = block { # continuing
+ store %2, 2i
+ break_if true %b2
+ }
+ }
+ ret %4
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVarInLoopInitializer_ThenReadAndWriteToVarInLoopBody) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ auto* load = b.Load(var);
+ b.NextIteration(loop);
+ b.Append(loop->Body(), [&] {
+ b.Store(var, b.Add(ty.i32(), load, 1_i));
+ b.ExitLoop(loop);
+ });
+ });
+ b.Return(fn, 3_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ loop [i: %b2, b: %b3] { # loop_1
+ %b2 = block { # initializer
+ %3:i32 = load %2
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ %4:i32 = add %3, 1i
+ store %2, %4
+ exit_loop # loop_1
+ }
+ }
+ ret 3i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ loop [i: %b2, b: %b3] { # loop_1
+ %b2 = block { # initializer
+ %3:i32 = load %2
+ %4:i32 = let %3
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ %5:i32 = add %4, 1i
+ store %2, %5
+ exit_loop # loop_1
+ }
+ }
+ ret 3i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVarInLoopInitializer_ThenReadAndWriteToVarInLoopContinuing) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ auto* load = b.Load(var);
+ b.NextIteration(loop);
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] {
+ b.Store(var, b.Add(ty.i32(), load, 1_i));
+ b.BreakIf(loop, true);
+ });
+ });
+ b.Return(fn, 3_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ loop [i: %b2, b: %b3, c: %b4] { # loop_1
+ %b2 = block { # initializer
+ %3:i32 = load %2
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ continue %b4
+ }
+ %b4 = block { # continuing
+ %4:i32 = add %3, 1i
+ store %2, %4
+ break_if true %b3
+ }
+ }
+ ret 3i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ loop [i: %b2, b: %b3, c: %b4] { # loop_1
+ %b2 = block { # initializer
+ %3:i32 = load %2
+ %4:i32 = let %3
+ next_iteration %b3
+ }
+ %b3 = block { # body
+ continue %b4
+ }
+ %b4 = block { # continuing
+ %5:i32 = add %4, 1i
+ store %2, %5
+ break_if true %b3
+ }
+ }
+ ret 3i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(WgslWriter_ValueToLetTest, LoadVarInLoopBody_ThenReadAndWriteToVarInLoopContinuing) {
+ auto* fn = b.Function("f", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* var = b.Var<function, i32>();
+ b.Store(var, 1_i);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* load = b.Load(var);
+ b.Continue(loop);
+
+ b.Append(loop->Continuing(), [&] {
+ b.Store(var, b.Add(ty.i32(), load, 1_i));
+ b.BreakIf(loop, true);
+ });
+ });
+ b.Return(fn, 3_i);
+ });
+
+ auto* src = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ loop [b: %b2, c: %b3] { # loop_1
+ %b2 = block { # body
+ %3:i32 = load %2
+ continue %b3
+ }
+ %b3 = block { # continuing
+ %4:i32 = add %3, 1i
+ store %2, %4
+ break_if true %b2
+ }
+ }
+ ret 3i
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%f = func():i32 -> %b1 {
+ %b1 = block {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 1i
+ loop [b: %b2, c: %b3] { # loop_1
+ %b2 = block { # body
+ %3:i32 = load %2
+ %4:i32 = let %3
+ continue %b3
+ }
+ %b3 = block { # continuing
+ %5:i32 = add %4, 1i
+ store %2, %5
+ break_if true %b2
+ }
+ }
+ ret 3i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::wgsl::writer::raise
diff --git a/src/tint/lang/wgsl/writer/writer_test.cc b/src/tint/lang/wgsl/writer/writer_test.cc
new file mode 100644
index 0000000..96bd0a4
--- /dev/null
+++ b/src/tint/lang/wgsl/writer/writer_test.cc
@@ -0,0 +1,1018 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/writer/writer.h"
+
+#include <string>
+
+#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+#include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h"
+#include "src/tint/lang/wgsl/writer/raise/raise.h"
+#include "src/tint/utils/text/string.h"
+
+namespace tint::wgsl::writer {
+namespace {
+
+/// Class used for IR to Program tests
+class WgslIRWriterTest : public core::ir::IRTestHelper {
+ public:
+ struct Result {
+ /// The resulting WGSL
+ std::string wgsl;
+ /// The resulting AST
+ std::string ast;
+ /// The resulting IR before raising
+ std::string ir_pre_raise;
+ /// The resulting IR after raising
+ std::string ir_post_raise;
+ /// The resulting error
+ std::string err;
+ /// The expected WGSL
+ std::string expected;
+ };
+
+ /// @returns the WGSL generated from the IR
+ Result Run(std::string_view expected_wgsl) {
+ Result result;
+
+ result.ir_pre_raise = core::ir::Disassemble(mod);
+
+ if (auto res = tint::wgsl::writer::Raise(mod); res != Success) {
+ result.err = res.Failure().reason.Str();
+ return result;
+ }
+
+ result.ir_post_raise = core::ir::Disassemble(mod);
+
+ writer::ProgramOptions program_options;
+ program_options.allowed_features = AllowedFeatures::Everything();
+ auto output_program = wgsl::writer::IRToProgram(mod, program_options);
+ if (!output_program.IsValid()) {
+ result.err = output_program.Diagnostics().Str();
+ result.ast = Program::printer(output_program);
+ return result;
+ }
+
+ auto output = wgsl::writer::Generate(output_program, {});
+ if (output != Success) {
+ std::stringstream ss;
+ ss << "wgsl::Generate() errored: " << output.Failure();
+ result.err = ss.str();
+ result.ast = Program::printer(output_program);
+ return result;
+ }
+
+ result.expected = tint::TrimSpace(expected_wgsl);
+ if (!result.expected.empty()) {
+ result.expected = "\n" + result.expected + "\n";
+ }
+
+ result.wgsl = std::string(tint::TrimSpace(output->wgsl));
+ if (!result.wgsl.empty()) {
+ result.wgsl = "\n" + result.wgsl + "\n";
+ }
+
+ return result;
+ }
+};
+
+std::ostream& operator<<(std::ostream& o, const WgslIRWriterTest::Result& res) {
+ if (!res.err.empty()) {
+ o << "============================" << std::endl
+ << "== Error ==" << std::endl
+ << "============================" << std::endl
+ << res.err << std::endl
+ << std::endl;
+ }
+ if (!res.ir_pre_raise.empty()) {
+ o << "============================" << std::endl
+ << "== IR (pre-raise) ==" << std::endl
+ << "============================" << std::endl
+ << res.ir_pre_raise << std::endl
+ << std::endl;
+ }
+ if (!res.ir_post_raise.empty()) {
+ o << "============================" << std::endl
+ << "== IR (post-raise) ==" << std::endl
+ << "============================" << std::endl
+ << res.ir_post_raise << std::endl
+ << std::endl;
+ }
+ if (!res.ast.empty()) {
+ o << "============================" << std::endl
+ << "== AST ==" << std::endl
+ << "============================" << std::endl
+ << res.ast << std::endl
+ << std::endl;
+ }
+ return o;
+}
+
+#define RUN_TEST(EXPECTED) \
+ do { \
+ if (auto res = Run(EXPECTED); res.err.empty()) { \
+ EXPECT_EQ(res.expected, res.wgsl) << res; \
+ } else { \
+ FAIL() << res; \
+ } \
+ } while (false)
+
+////////////////////////////////////////////////////////////////////////////////
+// Short-circuiting binary ops
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Param_2) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ fn->SetParams({pa, pb});
+
+ b.Append(fn->Block(), [&] {
+ auto* if_ = b.If(pa);
+ if_->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if_->True(), [&] { b.ExitIf(if_, pb); });
+ b.Append(if_->False(), [&] { b.ExitIf(if_, false); });
+
+ b.Return(fn, if_);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool) -> bool {
+ return (a && b);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Param_3_ab_c) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, pb); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ auto* if2 = b.If(if1);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, pc); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ return ((a && b) && c);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Param_3_a_bc) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] {
+ auto* if2 = b.If(pb);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, pc); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.ExitIf(if1, if2);
+ });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+ b.Return(fn, if1);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ return (a && (b && c));
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Let_2) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ fn->SetParams({pa, pb});
+
+ b.Append(fn->Block(), [&] {
+ auto* if_ = b.If(pa);
+ if_->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if_->True(), [&] { b.ExitIf(if_, pb); });
+ b.Append(if_->False(), [&] { b.ExitIf(if_, false); });
+
+ mod.SetName(if_, "l");
+ b.Return(fn, if_);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool) -> bool {
+ let l = (a && b);
+ return l;
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Let_3_ab_c) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, pb); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ auto* if2 = b.If(if1);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, pc); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ mod.SetName(if2, "l");
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ let l = ((a && b) && c);
+ return l;
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Let_3_a_bc) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] {
+ auto* if2 = b.If(pb);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, pc); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.ExitIf(if1, if2);
+ });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ mod.SetName(if1, "l");
+ b.Return(fn, if1);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ let l = (a && (b && c));
+ return l;
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Call_2) {
+ auto* fn_a = b.Function("a", ty.bool_());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ auto* if_ = b.If(b.Call(ty.bool_(), fn_a));
+ if_->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if_->True(), [&] { b.ExitIf(if_, b.Call(ty.bool_(), fn_b)); });
+ b.Append(if_->False(), [&] { b.ExitIf(if_, false); });
+
+ b.Return(fn, if_);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ return (a() && b());
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Call_3_ab_c) {
+ auto* fn_a = b.Function("a", ty.bool_());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn_c = b.Function("c", ty.bool_());
+ b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_b)); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ auto* if2 = b.If(if1);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn c() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ return ((a() && b()) && c());
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Call_3_a_bc) {
+ auto* fn_a = b.Function("a", ty.bool_());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn_c = b.Function("c", ty.bool_());
+ b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] {
+ auto* if2 = b.If(b.Call(ty.bool_(), fn_b));
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.ExitIf(if1, if2);
+ });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ b.Return(fn, if1);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn c() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ return (a() && (b() && c()));
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Param_2) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ fn->SetParams({pa, pb});
+
+ b.Append(fn->Block(), [&] {
+ auto* if_ = b.If(pa);
+ if_->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if_->True(), [&] { b.ExitIf(if_, true); });
+ b.Append(if_->False(), [&] { b.ExitIf(if_, pb); });
+
+ b.Return(fn, if_);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool) -> bool {
+ return (a || b);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Param_3_ab_c) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, pb); });
+
+ auto* if2 = b.If(if1);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, pc); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ return ((a || b) || c);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Param_3_a_bc) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] {
+ auto* if2 = b.If(pb);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, pc); });
+
+ b.ExitIf(if1, if2);
+ });
+
+ b.Return(fn, if1);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ return (a || (b || c));
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Let_2) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ fn->SetParams({pa, pb});
+
+ b.Append(fn->Block(), [&] {
+ auto* if_ = b.If(pa);
+ if_->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if_->True(), [&] { b.ExitIf(if_, true); });
+ b.Append(if_->False(), [&] { b.ExitIf(if_, pb); });
+
+ mod.SetName(if_, "l");
+ b.Return(fn, if_);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool) -> bool {
+ let l = (a || b);
+ return l;
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Let_3_ab_c) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, pb); });
+
+ auto* if2 = b.If(if1);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, pc); });
+
+ mod.SetName(if2, "l");
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ let l = ((a || b) || c);
+ return l;
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Let_3_a_bc) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pb = b.FunctionParam("b", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] {
+ auto* if2 = b.If(pb);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, pc); });
+
+ b.ExitIf(if1, if2);
+ });
+
+ mod.SetName(if1, "l");
+ b.Return(fn, if1);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ let l = (a || (b || c));
+ return l;
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Call_2) {
+ auto* fn_a = b.Function("a", ty.bool_());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ auto* if_ = b.If(b.Call(ty.bool_(), fn_a));
+ if_->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if_->True(), [&] { b.ExitIf(if_, true); });
+ b.Append(if_->False(), [&] { b.ExitIf(if_, b.Call(ty.bool_(), fn_b)); });
+
+ b.Return(fn, if_);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ return (a() || b());
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Call_3_ab_c) {
+ auto* fn_a = b.Function("a", ty.bool_());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn_c = b.Function("c", ty.bool_());
+ b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_b)); });
+
+ auto* if2 = b.If(if1);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn c() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ return ((a() || b()) || c());
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Call_3_a_bc) {
+ auto* fn_a = b.Function("a", ty.bool_());
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn_c = b.Function("c", ty.bool_());
+ b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_a));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] {
+ auto* if2 = b.If(b.Call(ty.bool_(), fn_b));
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, b.Call(ty.bool_(), fn_c)); });
+
+ b.ExitIf(if1, if2);
+ });
+
+ b.Return(fn, if1);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn c() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ return (a() || (b() || c()));
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Mixed) {
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn_d = b.Function("d", ty.bool_());
+ b.Append(fn_d->Block(), [&] { b.Return(fn_d, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam("a", ty.bool_());
+ auto* pc = b.FunctionParam("c", ty.bool_());
+ fn->SetParams({pa, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pa);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_b)); });
+
+ auto* if2 = b.If(if1);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] {
+ auto* if3 = b.If(pc);
+ if3->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if3->True(), [&] { b.ExitIf(if3, true); });
+ b.Append(if3->False(), [&] { b.ExitIf(if3, b.Call(ty.bool_(), fn_d)); });
+
+ b.ExitIf(if2, if3);
+ });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn b() -> bool {
+ return true;
+}
+
+fn d() -> bool {
+ return true;
+}
+
+fn f(a : bool, c : bool) -> bool {
+ return ((a || b()) && (c || d()));
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_ParamCallParam_a_bc_EarlyEval) {
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam(ty.bool_());
+ auto* pc = b.FunctionParam(ty.bool_());
+ mod.SetName(pa, "a");
+ mod.SetName(pc, "c");
+ fn->SetParams({pa, pc});
+
+ b.Append(fn->Block(), [&] {
+ // 'b() && c' is evaluated before 'a'.
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, pc); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ auto* if2 = b.If(pa);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, if1); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn b() -> bool {
+ return true;
+}
+
+fn f(a : bool, c : bool) -> bool {
+ let v = (b() && c);
+ return (a && v);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Call_3_a_bc_EarlyEval) {
+ auto* fn_a = b.Function("a", ty.bool_());
+
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn_c = b.Function("c", ty.bool_());
+
+ b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ // 'b() && c()' is evaluated before 'a()'.
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_c)); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ auto* if2 = b.If(b.Call(ty.bool_(), fn_a));
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, if1); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn c() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ let v = (b() && c());
+ return (a() && v);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_And_Param_3_a_bc_EarlyEval) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam(ty.bool_());
+ auto* pb = b.FunctionParam(ty.bool_());
+ auto* pc = b.FunctionParam(ty.bool_());
+ mod.SetName(pa, "a");
+ mod.SetName(pb, "b");
+ mod.SetName(pc, "c");
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pb);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, pc); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, false); });
+
+ auto* if2 = b.If(pa);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, if1); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, false); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ let v = (b && c);
+ return (a && v);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_ParamCallParam_a_bc_EarlyEval) {
+ auto* fn_b = b.Function("b", ty.bool_());
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam(ty.bool_());
+ auto* pc = b.FunctionParam(ty.bool_());
+ mod.SetName(pa, "a");
+ mod.SetName(pc, "c");
+ fn->SetParams({pa, pc});
+
+ b.Append(fn->Block(), [&] {
+ // 'b() && c' is evaluated before 'a'.
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, pc); });
+ auto* v = b.Let("v", if1);
+
+ auto* if2 = b.If(pa);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, v); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn b() -> bool {
+ return true;
+}
+
+fn f(a : bool, c : bool) -> bool {
+ let v = (b() || c);
+ return (a || v);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Call_3_a_bc_EarlyEval) {
+ auto* fn_a = b.Function("a", ty.bool_());
+
+ b.Append(fn_a->Block(), [&] { b.Return(fn_a, true); });
+
+ auto* fn_b = b.Function("b", ty.bool_());
+
+ b.Append(fn_b->Block(), [&] { b.Return(fn_b, true); });
+
+ auto* fn_c = b.Function("c", ty.bool_());
+
+ b.Append(fn_c->Block(), [&] { b.Return(fn_c, true); });
+
+ auto* fn = b.Function("f", ty.bool_());
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(b.Call(ty.bool_(), fn_b));
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, b.Call(ty.bool_(), fn_c)); });
+ auto* v = b.Let("v", if1);
+
+ auto* if2 = b.If(b.Call(ty.bool_(), fn_a));
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, v); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn a() -> bool {
+ return true;
+}
+
+fn b() -> bool {
+ return true;
+}
+
+fn c() -> bool {
+ return true;
+}
+
+fn f() -> bool {
+ let v = (b() || c());
+ return (a() || v);
+}
+)");
+}
+
+TEST_F(WgslIRWriterTest, ShortCircuit_Or_Param_3_a_bc_EarlyEval) {
+ auto* fn = b.Function("f", ty.bool_());
+ auto* pa = b.FunctionParam(ty.bool_());
+ auto* pb = b.FunctionParam(ty.bool_());
+ auto* pc = b.FunctionParam(ty.bool_());
+ mod.SetName(pa, "a");
+ mod.SetName(pb, "b");
+ mod.SetName(pc, "c");
+ fn->SetParams({pa, pb, pc});
+
+ b.Append(fn->Block(), [&] {
+ auto* if1 = b.If(pb);
+ if1->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if1->True(), [&] { b.ExitIf(if1, true); });
+ b.Append(if1->False(), [&] { b.ExitIf(if1, pc); });
+
+ auto* if2 = b.If(pa);
+ if2->SetResults(b.InstructionResult(ty.bool_()));
+ b.Append(if2->True(), [&] { b.ExitIf(if2, true); });
+ b.Append(if2->False(), [&] { b.ExitIf(if2, if1); });
+
+ b.Return(fn, if2);
+ });
+
+ RUN_TEST(R"(
+fn f(a : bool, b : bool, c : bool) -> bool {
+ let v = (b || c);
+ return (a || v);
+}
+)");
+}
+
+} // namespace
+} // namespace tint::wgsl::writer