[tint][ir] Add ValueToLet transform
Moves values into local variable declarations, where necessary for textual writers.
Change-Id: Ia3b11233904a8f17fb861d71bcf1cf12de9390c6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/162541
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/ir/let.h b/src/tint/lang/core/ir/let.h
index c55ed3c..a2fe222 100644
--- a/src/tint/lang/core/ir/let.h
+++ b/src/tint/lang/core/ir/let.h
@@ -49,6 +49,9 @@
/// @copydoc Instruction::Clone()
Let* Clone(CloneContext& ctx) override;
+ /// @param value the new let value
+ void SetValue(ir::Value* value) { SetOperand(kValueOperandOffset, value); }
+
/// @returns the value
ir::Value* Value() { return operands_[kValueOperandOffset]; }
diff --git a/src/tint/lang/core/ir/module.cc b/src/tint/lang/core/ir/module.cc
index 8719e2b..4738c14 100644
--- a/src/tint/lang/core/ir/module.cc
+++ b/src/tint/lang/core/ir/module.cc
@@ -67,4 +67,8 @@
value_to_name_.Replace(value, name);
}
+void Module::ClearName(Value* value) {
+ value_to_name_.Remove(value);
+}
+
} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index a792254..fba89fb 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -92,6 +92,10 @@
/// @param name the desired name of the value
void SetName(Value* value, Symbol name);
+ /// Removes the name from @p value
+ /// @param value the value to remove the name from
+ void ClearName(Value* value);
+
/// @return the type manager for the module
core::type::Manager& Types() { return constant_values.types; }
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index 8870a6b..b93dfc2 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -54,6 +54,7 @@
"robustness.cc",
"shader_io.cc",
"std140.cc",
+ "value_to_let.cc",
"vectorize_scalar_matrix_constructors.cc",
"zero_init_workgroup_memory.cc",
],
@@ -73,6 +74,7 @@
"robustness.h",
"shader_io.h",
"std140.h",
+ "value_to_let.h",
"vectorize_scalar_matrix_constructors.h",
"zero_init_workgroup_memory.h",
],
@@ -120,6 +122,7 @@
"preserve_padding_test.cc",
"robustness_test.cc",
"std140_test.cc",
+ "value_to_let_test.cc",
"vectorize_scalar_matrix_constructors_test.cc",
"zero_init_workgroup_memory_test.cc",
] + select({
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index 3465e11..f1d92a8 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -69,6 +69,8 @@
lang/core/ir/transform/shader_io.h
lang/core/ir/transform/std140.cc
lang/core/ir/transform/std140.h
+ lang/core/ir/transform/value_to_let.cc
+ lang/core/ir/transform/value_to_let.h
lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
lang/core/ir/transform/zero_init_workgroup_memory.cc
@@ -118,6 +120,7 @@
lang/core/ir/transform/preserve_padding_test.cc
lang/core/ir/transform/robustness_test.cc
lang/core/ir/transform/std140_test.cc
+ lang/core/ir/transform/value_to_let_test.cc
lang/core/ir/transform/vectorize_scalar_matrix_constructors_test.cc
lang/core/ir/transform/zero_init_workgroup_memory_test.cc
)
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 7343823..1e7245e 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -74,6 +74,8 @@
"shader_io.h",
"std140.cc",
"std140.h",
+ "value_to_let.cc",
+ "value_to_let.h",
"vectorize_scalar_matrix_constructors.cc",
"vectorize_scalar_matrix_constructors.h",
"zero_init_workgroup_memory.cc",
@@ -120,6 +122,7 @@
"preserve_padding_test.cc",
"robustness_test.cc",
"std140_test.cc",
+ "value_to_let_test.cc",
"vectorize_scalar_matrix_constructors_test.cc",
"zero_init_workgroup_memory_test.cc",
]
diff --git a/src/tint/lang/core/ir/transform/value_to_let.cc b/src/tint/lang/core/ir/transform/value_to_let.cc
new file mode 100644
index 0000000..7580900
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/value_to_let.cc
@@ -0,0 +1,186 @@
+// 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.
+
+#include "src/tint/lang/core/ir/transform/value_to_let.h"
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::core::ir::transform {
+
+namespace {
+
+/// Access is an enumerator of memory access operations
+enum class Access : uint8_t { kLoad, kStore };
+/// Accesses is a set of of Access
+using Accesses = EnumSet<Access>;
+
+/// @returns the accesses that may be performed by the instruction @p inst
+Accesses AccessesFor(ir::Instruction* inst) {
+ return tint::Switch<Accesses>(
+ inst, //
+ [&](const ir::Load*) { return Access::kLoad; }, //
+ [&](const ir::LoadVectorElement*) { return Access::kLoad; }, //
+ [&](const ir::Store*) { return Access::kStore; }, //
+ [&](const ir::StoreVectorElement*) { return Access::kStore; }, //
+ [&](const ir::Call*) {
+ return Accesses{Access::kLoad, Access::kStore};
+ },
+ [&](Default) { return Accesses{}; });
+}
+
+/// PIMPL state for the transform.
+struct State {
+ /// The IR module.
+ Module& ir;
+
+ /// The IR builder.
+ 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()) {
+ Process(block);
+ }
+ }
+
+ private:
+ void Process(ir::Block* block) {
+ // A set of possibly-inlinable values returned by a instructions that has not yet been
+ // marked-for or ruled-out-for inlining.
+ Hashset<ir::InstructionResult*, 32> pending_resolution;
+ // The accesses of the values in pending_resolution.
+ Access pending_access = Access::kLoad;
+
+ auto put_pending_in_lets = [&] {
+ for (auto* pending : pending_resolution) {
+ PutInLet(pending);
+ }
+ pending_resolution.Clear();
+ };
+
+ auto maybe_put_in_let = [&](auto* inst) {
+ if (auto* result = inst->Result(0)) {
+ auto& usages = result->Usages();
+ switch (usages.Count()) {
+ case 0: // No usage
+ break;
+ case 1: { // Single usage
+ auto* usage = (*usages.begin()).instruction;
+ if (usage->Block() == inst->Block()) {
+ // Usage in same block. Assign to pending_resolution, as we don't
+ // know whether its safe to inline yet.
+ pending_resolution.Add(result);
+ } else {
+ // Usage from another block. Cannot inline.
+ inst = PutInLet(result);
+ }
+ break;
+ }
+ default: // Value has multiple usages. Cannot inline.
+ inst = PutInLet(result);
+ break;
+ }
+ }
+ };
+
+ for (ir::Instruction* inst = block->Front(); inst; inst = inst->next) {
+ // This transform assumes that all multi-result instructions have been replaced
+ TINT_ASSERT(inst->Results().Length() < 2);
+
+ // The memory accesses of this instruction
+ auto accesses = AccessesFor(inst);
+
+ for (auto* operand : inst->Operands()) {
+ // If the operand is in pending_resolution, then we know it has a single use and
+ // because it hasn't been removed with put_pending_in_lets(), we know its safe to
+ // inline without breaking access ordering. By inlining the operand, we are pulling
+ // the operand's instruction into the same statement as this instruction, so this
+ // instruction adopts the access of the operand.
+ if (auto* result = As<InstructionResult>(operand)) {
+ if (pending_resolution.Remove(result)) {
+ // Var and Let are always statements, and so can never be inlined. As such,
+ // they do not need to propagate the pending resolution through them.
+ if (!inst->IsAnyOf<Var, Let>()) {
+ accesses.Add(pending_access);
+ }
+ }
+ }
+ }
+
+ if (accesses.Contains(Access::kStore)) { // Note: Also handles load + store
+ put_pending_in_lets();
+ maybe_put_in_let(inst);
+ } else if (accesses.Contains(Access::kLoad)) {
+ if (pending_access != Access::kLoad) {
+ put_pending_in_lets();
+ pending_access = Access::kLoad;
+ }
+ maybe_put_in_let(inst);
+ }
+ }
+ }
+
+ /// PutInLet places the value into a new 'let' instruction, immediately after the value's
+ /// instruction
+ /// @param value the value to place into the 'let'
+ /// @return the created 'let' instruction.
+ ir::Let* PutInLet(ir::InstructionResult* value) {
+ auto* inst = value->Instruction();
+ auto* let = b.Let(value->Type());
+ value->ReplaceAllUsesWith(let->Result(0));
+ let->SetValue(value);
+ let->InsertAfter(inst);
+ if (auto name = b.ir.NameOf(value); name.IsValid()) {
+ b.ir.SetName(let->Result(0), name);
+ b.ir.ClearName(value);
+ }
+ return let;
+ }
+};
+
+} // namespace
+
+Result<SuccessType> ValueToLet(Module& ir) {
+ auto result = ValidateAndDumpIfNeeded(ir, "ValueToLet transform");
+ if (!result) {
+ return result;
+ }
+
+ State{ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/value_to_let.h b/src/tint/lang/core/ir/transform/value_to_let.h
new file mode 100644
index 0000000..895bc09
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/value_to_let.h
@@ -0,0 +1,54 @@
+// 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.
+
+#ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_VALUE_TO_LET_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_VALUE_TO_LET_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// 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(Module& module);
+
+} // namespace tint::core::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_VALUE_TO_LET_H_
diff --git a/src/tint/lang/core/ir/transform/value_to_let_test.cc b/src/tint/lang/core/ir/transform/value_to_let_test.cc
new file mode 100644
index 0000000..91e04d9
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/value_to_let_test.cc
@@ -0,0 +1,637 @@
+// 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.
+
+#include "src/tint/lang/core/ir/transform/value_to_let.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+using IR_ValueToLetTest = TransformTest;
+
+TEST_F(IR_ValueToLetTest, Empty) {
+ auto* expect = R"(
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, NoModify_Blah) {
+ auto* func = b.Function("F", ty.void_());
+ b.Append(func->Block(), [&] { b.Return(func); });
+
+ auto* src = R"(
+%F = func():void -> %b1 {
+ %b1 = block {
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, NoModify_Unsequenced) {
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* x = b.Let("x", 1_i);
+ auto* y = b.Let("y", 2_i);
+ auto* z = b.Let("z", b.Add<i32>(x, y));
+ b.Return(fn, z);
+ });
+
+ auto* src = R"(
+%F = func():i32 -> %b1 {
+ %b1 = block {
+ %x:i32 = let 1i
+ %y:i32 = let 2i
+ %4:i32 = add %x, %y
+ %z:i32 = let %4
+ ret %z
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+TEST_F(IR_ValueToLetTest, NoModify_SequencedValueUsedWithNonSequenced) {
+ auto* i = b.Var<private_, i32>("i");
+ b.ir.root_block->Append(i);
+
+ auto* p = b.FunctionParam<i32>("p");
+ auto* rmw = b.Function("rmw", ty.i32());
+ rmw->SetParams({p});
+ b.Append(rmw->Block(), [&] {
+ auto* v = b.Let("v", b.Add<i32>(b.Load(i), p));
+ b.Store(i, v);
+ b.Return(rmw, v);
+ });
+
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* x = b.Name("x", b.Call(rmw, 1_i));
+ // select is called with one, inlinable sequenced operand and two non-sequenced values.
+ auto* y = b.Name("y", b.Call<i32>(core::BuiltinFn::kSelect, 2_i, x, false));
+ b.Return(fn, y);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %i:ptr<private, i32, read_write> = var
+}
+
+%rmw = func(%p:i32):i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, %p
+ %v:i32 = let %5
+ store %i, %v
+ ret %v
+ }
+}
+%F = func():i32 -> %b3 {
+ %b3 = block {
+ %x:i32 = call %rmw, 1i
+ %y:i32 = select 2i, %x, false
+ ret %y
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, NoModify_Inlinable_NestedCalls) {
+ auto* i = b.Var<private_, i32>("i");
+ b.ir.root_block->Append(i);
+
+ auto* p = b.FunctionParam<i32>("p");
+ auto* rmw = b.Function("rmw", ty.i32());
+ rmw->SetParams({p});
+ b.Append(rmw->Block(), [&] {
+ auto* v = b.Let("v", b.Add<i32>(b.Load(i), p));
+ b.Store(i, v);
+ b.Return(rmw, v);
+ });
+
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* x = b.Name("x", b.Call(rmw, 1_i));
+ auto* y = b.Name("y", b.Call(rmw, x));
+ auto* z = b.Name("z", b.Call(rmw, y));
+ b.Return(fn, z);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %i:ptr<private, i32, read_write> = var
+}
+
+%rmw = func(%p:i32):i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, %p
+ %v:i32 = let %5
+ store %i, %v
+ ret %v
+ }
+}
+%F = func():i32 -> %b3 {
+ %b3 = block {
+ %x:i32 = call %rmw, 1i
+ %y:i32 = call %rmw, %x
+ %z:i32 = call %rmw, %y
+ ret %z
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, NoModify_LetUsedTwice) {
+ auto* i = b.Var<private_, i32>("i");
+ b.ir.root_block->Append(i);
+
+ auto* p = b.FunctionParam<i32>("p");
+ auto* rmw = b.Function("rmw", ty.i32());
+ rmw->SetParams({p});
+ b.Append(rmw->Block(), [&] {
+ auto* v = b.Let("v", b.Add<i32>(b.Load(i), p));
+ b.Store(i, v);
+ b.Return(rmw, v);
+ });
+
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ // No need to create more lets, as these are already in lets
+ auto* x = b.Let("x", b.Call(rmw, 1_i));
+ auto* y = b.Name("y", b.Add<i32>(x, x));
+ b.Return(fn, y);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %i:ptr<private, i32, read_write> = var
+}
+
+%rmw = func(%p:i32):i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, %p
+ %v:i32 = let %5
+ store %i, %v
+ ret %v
+ }
+}
+%F = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %rmw, 1i
+ %x:i32 = let %8
+ %y:i32 = add %x, %x
+ ret %y
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, NoModify_VarUsedTwice) {
+ auto* p = b.FunctionParam<ptr<function, i32, read_write>>("p");
+ auto* fn_g = b.Function("g", ty.i32());
+ fn_g->SetParams({p});
+ b.Append(fn_g->Block(), [&] { b.Return(fn_g, b.Load(p)); });
+
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Var<function, i32>("v");
+ auto* x = b.Let("x", b.Call(fn_g, v));
+ auto* y = b.Let("y", b.Call(fn_g, v));
+ b.Return(fn, b.Add<i32>(x, y));
+ });
+
+ auto* src = R"(
+%g = func(%p:ptr<function, i32, read_write>):i32 -> %b1 {
+ %b1 = block {
+ %3:i32 = load %p
+ ret %3
+ }
+}
+%F = func():i32 -> %b2 {
+ %b2 = block {
+ %v:ptr<function, i32, read_write> = var
+ %6:i32 = call %g, %v
+ %x:i32 = let %6
+ %8:i32 = call %g, %v
+ %y:i32 = let %8
+ %10:i32 = add %x, %y
+ ret %10
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, VarLoadUsedTwice) {
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Var<function, i32>("v");
+ auto* l = b.Name("l", b.Load(v));
+ b.Return(fn, b.Add<i32>(l, l));
+ });
+
+ auto* src = R"(
+%F = func():i32 -> %b1 {
+ %b1 = block {
+ %v:ptr<function, i32, read_write> = var
+ %l:i32 = load %v
+ %4:i32 = add %l, %l
+ ret %4
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%F = func():i32 -> %b1 {
+ %b1 = block {
+ %v:ptr<function, i32, read_write> = var
+ %3:i32 = load %v
+ %l:i32 = let %3
+ %5:i32 = add %l, %l
+ ret %5
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, VarLoad_ThenStore_ThenUse) {
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Var<function, i32>("v");
+ auto* l = b.Name("l", b.Load(v));
+ b.Store(v, 1_i);
+ b.Return(fn, l);
+ });
+
+ auto* src = R"(
+%F = func():i32 -> %b1 {
+ %b1 = block {
+ %v:ptr<function, i32, read_write> = var
+ %l:i32 = load %v
+ store %v, 1i
+ ret %l
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%F = func():i32 -> %b1 {
+ %b1 = block {
+ %v:ptr<function, i32, read_write> = var
+ %3:i32 = load %v
+ %l:i32 = let %3
+ store %v, 1i
+ ret %l
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, TwoCalls_ThenUseReturnValues) {
+ auto* i = b.Var<private_, i32>("i");
+ b.ir.root_block->Append(i);
+
+ auto* p = b.FunctionParam<i32>("p");
+ auto* rmw = b.Function("rmw", ty.i32());
+ rmw->SetParams({p});
+ b.Append(rmw->Block(), [&] {
+ auto* v = b.Let("v", b.Add<i32>(b.Load(i), p));
+ b.Store(i, v);
+ b.Return(rmw, v);
+ });
+
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* x = b.Name("x", b.Call(rmw, 1_i));
+ auto* y = b.Name("y", b.Call(rmw, 2_i));
+ auto* z = b.Name("z", b.Add<i32>(x, y));
+ b.Return(fn, z);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %i:ptr<private, i32, read_write> = var
+}
+
+%rmw = func(%p:i32):i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, %p
+ %v:i32 = let %5
+ store %i, %v
+ ret %v
+ }
+}
+%F = func():i32 -> %b3 {
+ %b3 = block {
+ %x:i32 = call %rmw, 1i
+ %y:i32 = call %rmw, 2i
+ %z:i32 = add %x, %y
+ ret %z
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %i:ptr<private, i32, read_write> = var
+}
+
+%rmw = func(%p:i32):i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, %p
+ %v:i32 = let %5
+ store %i, %v
+ ret %v
+ }
+}
+%F = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %rmw, 1i
+ %x:i32 = let %8
+ %y:i32 = call %rmw, 2i
+ %z:i32 = add %x, %y
+ ret %z
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+
+ Run(ValueToLet); // running a second time should be no-op
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, SequencedUsedInDifferentBlock) {
+ auto* i = b.Var<private_, i32>("i");
+ b.ir.root_block->Append(i);
+
+ auto* p = b.FunctionParam<i32>("p");
+ auto* rmw = b.Function("rmw", ty.i32());
+ rmw->SetParams({p});
+ b.Append(rmw->Block(), [&] {
+ auto* v = b.Let("v", b.Add<i32>(b.Load(i), p));
+ b.Store(i, v);
+ b.Return(rmw, v);
+ });
+
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* x = b.Name("x", b.Call(rmw, 1_i));
+ auto* if_ = b.If(true);
+ b.Append(if_->True(), [&] { //
+ b.Return(fn, x);
+ });
+ b.Return(fn, 2_i);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %i:ptr<private, i32, read_write> = var
+}
+
+%rmw = func(%p:i32):i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, %p
+ %v:i32 = let %5
+ store %i, %v
+ ret %v
+ }
+}
+%F = func():i32 -> %b3 {
+ %b3 = block {
+ %x:i32 = call %rmw, 1i
+ if true [t: %b4] { # if_1
+ %b4 = block { # true
+ ret %x
+ }
+ }
+ ret 2i
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %i:ptr<private, i32, read_write> = var
+}
+
+%rmw = func(%p:i32):i32 -> %b2 {
+ %b2 = block {
+ %4:i32 = load %i
+ %5:i32 = add %4, %p
+ %v:i32 = let %5
+ store %i, %v
+ ret %v
+ }
+}
+%F = func():i32 -> %b3 {
+ %b3 = block {
+ %8:i32 = call %rmw, 1i
+ %x:i32 = let %8
+ if true [t: %b4] { # if_1
+ %b4 = block { # true
+ ret %x
+ }
+ }
+ ret 2i
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+
+ Run(ValueToLet); // running a second time should be no-op
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, NameMe1) {
+ auto* fn = b.Function("F", ty.i32());
+ b.Append(fn->Block(), [&] {
+ auto* v = b.Var<function, i32>("v");
+ auto* x = b.Load(v);
+ auto* y = b.Add<i32>(x, 1_i);
+ b.Store(v, 2_i);
+ b.Return(fn, y);
+ });
+
+ auto* src = R"(
+%F = func():i32 -> %b1 {
+ %b1 = block {
+ %v:ptr<function, i32, read_write> = var
+ %3:i32 = load %v
+ %4:i32 = add %3, 1i
+ store %v, 2i
+ ret %4
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%F = func():i32 -> %b1 {
+ %b1 = block {
+ %v:ptr<function, i32, read_write> = var
+ %3:i32 = load %v
+ %4:i32 = add %3, 1i
+ %5:i32 = let %4
+ store %v, 2i
+ ret %5
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+
+ Run(ValueToLet); // running a second time should be no-op
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_ValueToLetTest, NameMe2) {
+ auto* fn = b.Function("F", ty.void_());
+ b.Append(fn->Block(), [&] {
+ auto* i = b.Name("i", b.Call<i32>(core::BuiltinFn::kMax, 1_i, 2_i));
+ auto* v = b.Var<function>("v", i);
+ auto* x = b.Name("x", b.Call<i32>(core::BuiltinFn::kMax, 3_i, 4_i));
+ auto* y = b.Name("y", b.Load(v));
+ auto* z = b.Name("z", b.Add<i32>(y, x));
+ b.Store(v, z);
+ b.Return(fn);
+ });
+
+ auto* src = R"(
+%F = func():void -> %b1 {
+ %b1 = block {
+ %i:i32 = max 1i, 2i
+ %v:ptr<function, i32, read_write> = var, %i
+ %x:i32 = max 3i, 4i
+ %y:i32 = load %v
+ %z:i32 = add %y, %x
+ store %v, %z
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%F = func():void -> %b1 {
+ %b1 = block {
+ %i:i32 = max 1i, 2i
+ %v:ptr<function, i32, read_write> = var, %i
+ %x:i32 = max 3i, 4i
+ %y:i32 = load %v
+ %z:i32 = add %y, %x
+ store %v, %z
+ ret
+ }
+}
+)";
+
+ Run(ValueToLet);
+
+ EXPECT_EQ(expect, str());
+
+ Run(ValueToLet); // running a second time should be no-op
+
+ EXPECT_EQ(expect, str());
+}
+} // namespace
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/msl/writer/printer/printer.cc b/src/tint/lang/msl/writer/printer/printer.cc
index f982e92..29ccd88 100644
--- a/src/tint/lang/msl/writer/printer/printer.cc
+++ b/src/tint/lang/msl/writer/printer/printer.cc
@@ -387,7 +387,6 @@
Switch(
v, //
[&](const core::ir::Constant* c) { EmitConstant(out, c); }, //
- // [&](core::ir::FunctionParam* fp) {}, //
[&](const core::ir::InstructionResult* r) {
Switch(
r->Instruction(), //