[msl] Add RemoveContinueInSwitch transform
The textual backends inline continuing blocks at all `continue`
callsites, which means that they may generate a `break` that breaks
from a switch instead of the intended loop.
This transforms sets a flag and performs the `continue` after switch
instruction instead.
Bug: 42251016
Change-Id: I3826dc21de9f1035afa7de2baac94a14bcbf3bf2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/204377
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index e562c61..b90c54f 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -52,6 +52,7 @@
"direct_variable_access.cc",
"multiplanar_external_texture.cc",
"preserve_padding.cc",
+ "remove_continue_in_switch.cc",
"remove_terminator_args.cc",
"rename_conflicts.cc",
"robustness.cc",
@@ -75,6 +76,7 @@
"direct_variable_access.h",
"multiplanar_external_texture.h",
"preserve_padding.h",
+ "remove_continue_in_switch.h",
"remove_terminator_args.h",
"rename_conflicts.h",
"robustness.h",
@@ -129,6 +131,7 @@
"helper_test.h",
"multiplanar_external_texture_test.cc",
"preserve_padding_test.cc",
+ "remove_continue_in_switch_test.cc",
"remove_terminator_args_test.cc",
"rename_conflicts_test.cc",
"robustness_test.cc",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index 046016d..1687918 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -67,6 +67,8 @@
lang/core/ir/transform/multiplanar_external_texture.h
lang/core/ir/transform/preserve_padding.cc
lang/core/ir/transform/preserve_padding.h
+ lang/core/ir/transform/remove_continue_in_switch.cc
+ lang/core/ir/transform/remove_continue_in_switch.h
lang/core/ir/transform/remove_terminator_args.cc
lang/core/ir/transform/remove_terminator_args.h
lang/core/ir/transform/rename_conflicts.cc
@@ -132,6 +134,7 @@
lang/core/ir/transform/helper_test.h
lang/core/ir/transform/multiplanar_external_texture_test.cc
lang/core/ir/transform/preserve_padding_test.cc
+ lang/core/ir/transform/remove_continue_in_switch_test.cc
lang/core/ir/transform/remove_terminator_args_test.cc
lang/core/ir/transform/rename_conflicts_test.cc
lang/core/ir/transform/robustness_test.cc
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 61b989a..9387529 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -71,6 +71,8 @@
"multiplanar_external_texture.h",
"preserve_padding.cc",
"preserve_padding.h",
+ "remove_continue_in_switch.cc",
+ "remove_continue_in_switch.h",
"remove_terminator_args.cc",
"remove_terminator_args.h",
"rename_conflicts.cc",
@@ -130,6 +132,7 @@
"helper_test.h",
"multiplanar_external_texture_test.cc",
"preserve_padding_test.cc",
+ "remove_continue_in_switch_test.cc",
"remove_terminator_args_test.cc",
"rename_conflicts_test.cc",
"robustness_test.cc",
diff --git a/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc b/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
new file mode 100644
index 0000000..d056862
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
@@ -0,0 +1,136 @@
+// 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/core/ir/transform/remove_continue_in_switch.h"
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/utils/ice/ice.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::core::ir::transform {
+
+namespace {
+
+/// 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()};
+
+ /// A map from `switch` instruction to the flag used to indicate whether a `continue` was hit.
+ Hashmap<Switch*, Var*, 4> continue_flag_for_switch{};
+
+ /// Process the module.
+ void Process() {
+ // Look for `continue` instructions.
+ for (auto* inst : ir.Instructions()) {
+ auto* cont = inst->As<Continue>();
+ if (!cont) {
+ continue;
+ }
+
+ // Skip `continue` instructions in loops whose continuing blocks do not end with
+ // `breakif`, as these continuing blocks can be safely inlined.
+ if (!tint::Is<BreakIf>(cont->Loop()->Continuing()->Terminator())) {
+ continue;
+ }
+
+ // Check if this `continue` is inside a `switch` that is inside the `loop`.
+ // Do this by walking up the stack of control flow instructions until we see a `loop`.
+ // If we hit a `switch` before we see the loop, we need to replace the `continue`.
+ auto* parent = cont->Block()->Parent();
+ while (!parent->Is<Loop>()) {
+ if (auto* swtch = parent->As<Switch>()) {
+ ReplaceContinue(cont, swtch);
+ break;
+ }
+ parent = parent->Block()->Parent();
+ }
+ }
+ }
+
+ /// Replace a `continue` instruction.
+ /// @param cont the `continue` to replace
+ /// @param swtch the `switch` instruction that it is nested inside
+ void ReplaceContinue(Continue* cont, Switch* swtch) {
+ auto* flag = GetContinueFlag(swtch, cont->Loop());
+ b.InsertBefore(cont, [&] {
+ b.Store(flag, true);
+ b.ExitSwitch(swtch);
+ });
+ cont->Destroy();
+ }
+
+ /// Get or create the flag used to indicate whether a `continue` was hit.
+ /// @param swtch the `switch` instruction to get the flag for
+ /// @param loop the `loop` that is the target of `continue` instruction in this switch
+ /// @returns the flag variable
+ Var* GetContinueFlag(Switch* swtch, Loop* loop) {
+ return continue_flag_for_switch.GetOrAdd(swtch, [&] {
+ // Declare the flag before the switch statement.
+ auto* flag = b.Var<function, bool>("tint_continue");
+ flag->InsertBefore(swtch);
+
+ // Check the flag after the `switch` instruction and `continue` if it was set.
+ b.InsertAfter(swtch, [&] {
+ auto* check = b.If(b.Load(flag));
+ b.Append(check->True(), [&] { //
+ b.Continue(loop);
+ });
+ });
+
+ return flag;
+ });
+ }
+};
+
+} // namespace
+
+Result<SuccessType> RemoveContinueInSwitch(Module& ir) {
+ auto result = ValidateAndDumpIfNeeded(ir, "RemoveContinueInSwitch transform",
+ core::ir::Capabilities{
+ core::ir::Capability::kAllowVectorElementPointer,
+ });
+ if (result != Success) {
+ return result;
+ }
+
+ State{ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/remove_continue_in_switch.h b/src/tint/lang/core/ir/transform/remove_continue_in_switch.h
new file mode 100644
index 0000000..ff12e61
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/remove_continue_in_switch.h
@@ -0,0 +1,52 @@
+// 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_CORE_IR_TRANSFORM_REMOVE_CONTINUE_IN_SWITCH_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_CONTINUE_IN_SWITCH_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// RemoveContinueInSwitch is a transform that removes `continue` instructions that are nested
+/// inside `switch` instructions, and instead sets a flag which is checked after the `switch`.
+///
+/// This is needed because textual backends inline loop continuing blocks at the location of each
+/// `continue`, which would lead to a `break` unintentionally breaking from the switch instead of
+/// the loop.
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> RemoveContinueInSwitch(Module& module);
+
+} // namespace tint::core::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_CONTINUE_IN_SWITCH_H_
diff --git a/src/tint/lang/core/ir/transform/remove_continue_in_switch_test.cc b/src/tint/lang/core/ir/transform/remove_continue_in_switch_test.cc
new file mode 100644
index 0000000..02c47e2
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/remove_continue_in_switch_test.cc
@@ -0,0 +1,614 @@
+// 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/core/ir/transform/remove_continue_in_switch.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_RemoveContinueInSwitchTest = TransformTest;
+
+TEST_F(IR_RemoveContinueInSwitchTest, NoModify_ContinueNotInSwitch) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* swtch = b.Switch(42_i);
+ auto* def_case = b.DefaultCase(swtch);
+ b.Append(def_case, [&] { //
+ b.ExitSwitch(swtch);
+ });
+ b.Continue(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2] { # loop_1
+ $B2: { # body
+ switch 42i [c: (default, $B3)] { # switch_1
+ $B3: { # case
+ exit_switch # switch_1
+ }
+ }
+ continue # -> $B4
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(RemoveContinueInSwitch);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveContinueInSwitchTest, NoModify_ContinueInSwitchCase_WithoutBreakIf) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* swtch = b.Switch(42_i);
+ auto* def_case = b.DefaultCase(swtch);
+ b.Append(def_case, [&] { //
+ b.Continue(loop);
+ });
+ b.Continue(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2] { # loop_1
+ $B2: { # body
+ switch 42i [c: (default, $B3)] { # switch_1
+ $B3: { # case
+ continue # -> $B4
+ }
+ }
+ continue # -> $B4
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(RemoveContinueInSwitch);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveContinueInSwitchTest, ContinueInSwitchCase_WithBreakIf) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* swtch = b.Switch(42_i);
+ auto* def_case = b.DefaultCase(swtch);
+ b.Append(def_case, [&] { //
+ b.Continue(loop);
+ });
+ b.Continue(loop);
+ b.Append(loop->Continuing(), [&] { //
+ b.BreakIf(loop, true);
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ switch 42i [c: (default, $B4)] { # switch_1
+ $B4: { # case
+ continue # -> $B3
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ %tint_continue:ptr<function, bool, read_write> = var
+ switch 42i [c: (default, $B4)] { # switch_1
+ $B4: { # case
+ store %tint_continue, true
+ exit_switch # switch_1
+ }
+ }
+ %3:bool = load %tint_continue
+ if %3 [t: $B5] { # if_1
+ $B5: { # true
+ continue # -> $B3
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(RemoveContinueInSwitch);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveContinueInSwitchTest, ContinueInMultipleCases) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* swtch = b.Switch(42_i);
+ auto* case_a = b.Case(swtch, Vector{b.Constant(1_i)});
+ b.Append(case_a, [&] { //
+ b.Continue(loop);
+ });
+ auto* case_b = b.Case(swtch, Vector{b.Constant(2_i)});
+ b.Append(case_b, [&] { //
+ b.Continue(loop);
+ });
+ auto* def_case = b.DefaultCase(swtch);
+ b.Append(def_case, [&] { //
+ b.Continue(loop);
+ });
+ b.Continue(loop);
+ b.Append(loop->Continuing(), [&] { //
+ b.BreakIf(loop, true);
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ switch 42i [c: (1i, $B4), c: (2i, $B5), c: (default, $B6)] { # switch_1
+ $B4: { # case
+ continue # -> $B3
+ }
+ $B5: { # case
+ continue # -> $B3
+ }
+ $B6: { # case
+ continue # -> $B3
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ %tint_continue:ptr<function, bool, read_write> = var
+ switch 42i [c: (1i, $B4), c: (2i, $B5), c: (default, $B6)] { # switch_1
+ $B4: { # case
+ store %tint_continue, true
+ exit_switch # switch_1
+ }
+ $B5: { # case
+ store %tint_continue, true
+ exit_switch # switch_1
+ }
+ $B6: { # case
+ store %tint_continue, true
+ exit_switch # switch_1
+ }
+ }
+ %3:bool = load %tint_continue
+ if %3 [t: $B7] { # if_1
+ $B7: { # true
+ continue # -> $B3
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(RemoveContinueInSwitch);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveContinueInSwitchTest, ContinueInMultipleSwitches) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* swtch_a = b.Switch(42_i);
+ auto* def_case_a = b.DefaultCase(swtch_a);
+ b.Append(def_case_a, [&] { //
+ b.Continue(loop);
+ });
+ auto* swtch_b = b.Switch(43_i);
+ auto* def_case_b = b.DefaultCase(swtch_b);
+ b.Append(def_case_b, [&] { //
+ b.Continue(loop);
+ });
+ auto* swtch_c = b.Switch(44_i);
+ auto* def_case_c = b.DefaultCase(swtch_c);
+ b.Append(def_case_c, [&] { //
+ b.Continue(loop);
+ });
+ b.Continue(loop);
+ b.Append(loop->Continuing(), [&] { //
+ b.BreakIf(loop, true);
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ switch 42i [c: (default, $B4)] { # switch_1
+ $B4: { # case
+ continue # -> $B3
+ }
+ }
+ switch 43i [c: (default, $B5)] { # switch_2
+ $B5: { # case
+ continue # -> $B3
+ }
+ }
+ switch 44i [c: (default, $B6)] { # switch_3
+ $B6: { # case
+ continue # -> $B3
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ %tint_continue:ptr<function, bool, read_write> = var
+ switch 42i [c: (default, $B4)] { # switch_1
+ $B4: { # case
+ store %tint_continue, true
+ exit_switch # switch_1
+ }
+ }
+ %3:bool = load %tint_continue
+ if %3 [t: $B5] { # if_1
+ $B5: { # true
+ continue # -> $B3
+ }
+ }
+ %tint_continue_1:ptr<function, bool, read_write> = var # %tint_continue_1: 'tint_continue'
+ switch 43i [c: (default, $B6)] { # switch_2
+ $B6: { # case
+ store %tint_continue_1, true
+ exit_switch # switch_2
+ }
+ }
+ %5:bool = load %tint_continue_1
+ if %5 [t: $B7] { # if_2
+ $B7: { # true
+ continue # -> $B3
+ }
+ }
+ %tint_continue_2:ptr<function, bool, read_write> = var # %tint_continue_2: 'tint_continue'
+ switch 44i [c: (default, $B8)] { # switch_3
+ $B8: { # case
+ store %tint_continue_2, true
+ exit_switch # switch_3
+ }
+ }
+ %7:bool = load %tint_continue_2
+ if %7 [t: $B9] { # if_3
+ $B9: { # true
+ continue # -> $B3
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(RemoveContinueInSwitch);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveContinueInSwitchTest, ContinueInSwitchCaseNestedInsideIf) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* outer_if = b.If(true);
+ b.Append(outer_if->True(), [&] {
+ auto* swtch = b.Switch(42_i);
+ auto* def_case = b.DefaultCase(swtch);
+ b.Append(def_case, [&] { //
+ auto* inner_if = b.If(true);
+ b.Append(inner_if->True(), [&] { //
+ b.Continue(loop);
+ });
+ b.Unreachable();
+ });
+ b.Unreachable();
+ });
+ b.Continue(loop);
+ b.Append(loop->Continuing(), [&] { //
+ b.BreakIf(loop, true);
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ if true [t: $B4] { # if_1
+ $B4: { # true
+ switch 42i [c: (default, $B5)] { # switch_1
+ $B5: { # case
+ if true [t: $B6] { # if_2
+ $B6: { # true
+ continue # -> $B3
+ }
+ }
+ unreachable
+ }
+ }
+ unreachable
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ if true [t: $B4] { # if_1
+ $B4: { # true
+ %tint_continue:ptr<function, bool, read_write> = var
+ switch 42i [c: (default, $B5)] { # switch_1
+ $B5: { # case
+ if true [t: $B6] { # if_2
+ $B6: { # true
+ store %tint_continue, true
+ exit_switch # switch_1
+ }
+ }
+ unreachable
+ }
+ }
+ %3:bool = load %tint_continue
+ if %3 [t: $B7] { # if_3
+ $B7: { # true
+ continue # -> $B3
+ }
+ }
+ unreachable
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(RemoveContinueInSwitch);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveContinueInSwitchTest, ContinueInSwitchInsideAnotherSwitch) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* outer_switch = b.Switch(42_i);
+ auto* outer_def_case = b.DefaultCase(outer_switch);
+ b.Append(outer_def_case, [&] { //
+ auto* inner_switch = b.Switch(42_i);
+ auto* inner_def_case = b.DefaultCase(inner_switch);
+ b.Append(inner_def_case, [&] { //
+ b.Continue(loop);
+ });
+ b.ExitSwitch(outer_switch);
+ });
+ b.Continue(loop);
+ b.Append(loop->Continuing(), [&] { //
+ b.BreakIf(loop, true);
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ switch 42i [c: (default, $B4)] { # switch_1
+ $B4: { # case
+ switch 42i [c: (default, $B5)] { # switch_2
+ $B5: { # case
+ continue # -> $B3
+ }
+ }
+ exit_switch # switch_1
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ %tint_continue:ptr<function, bool, read_write> = var
+ switch 42i [c: (default, $B4)] { # switch_1
+ $B4: { # case
+ %tint_continue_1:ptr<function, bool, read_write> = var # %tint_continue_1: 'tint_continue'
+ switch 42i [c: (default, $B5)] { # switch_2
+ $B5: { # case
+ store %tint_continue_1, true
+ exit_switch # switch_2
+ }
+ }
+ %4:bool = load %tint_continue_1
+ if %4 [t: $B6] { # if_1
+ $B6: { # true
+ store %tint_continue, true
+ exit_switch # switch_1
+ }
+ }
+ exit_switch # switch_1
+ }
+ }
+ %5:bool = load %tint_continue
+ if %5 [t: $B7] { # if_2
+ $B7: { # true
+ continue # -> $B3
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(RemoveContinueInSwitch);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/msl/writer/raise/raise.cc b/src/tint/lang/msl/writer/raise/raise.cc
index 8a2309f..1e51dad 100644
--- a/src/tint/lang/msl/writer/raise/raise.cc
+++ b/src/tint/lang/msl/writer/raise/raise.cc
@@ -38,6 +38,7 @@
#include "src/tint/lang/core/ir/transform/demote_to_helper.h"
#include "src/tint/lang/core/ir/transform/multiplanar_external_texture.h"
#include "src/tint/lang/core/ir/transform/preserve_padding.h"
+#include "src/tint/lang/core/ir/transform/remove_continue_in_switch.h"
#include "src/tint/lang/core/ir/transform/remove_terminator_args.h"
#include "src/tint/lang/core/ir/transform/rename_conflicts.h"
#include "src/tint/lang/core/ir/transform/robustness.h"
@@ -121,6 +122,7 @@
RUN_TRANSFORM(core::ir::transform::PreservePadding, module);
RUN_TRANSFORM(core::ir::transform::VectorizeScalarMatrixConstructors, module);
+ RUN_TRANSFORM(core::ir::transform::RemoveContinueInSwitch, module);
// DemoteToHelper must come before any transform that introduces non-core instructions.
RUN_TRANSFORM(core::ir::transform::DemoteToHelper, module);
diff --git a/test/tint/bug/tint/2039.wgsl.expected.ir.msl b/test/tint/bug/tint/2039.wgsl.expected.ir.msl
index 495d616..fd6830b 100644
--- a/test/tint/bug/tint/2039.wgsl.expected.ir.msl
+++ b/test/tint/bug/tint/2039.wgsl.expected.ir.msl
@@ -5,19 +5,24 @@
uint out = 0u;
{
while(true) {
+ bool tint_continue = false;
switch(2) {
case 1:
{
- {
- if (true) { break; }
- }
- continue;
+ tint_continue = true;
+ break;
}
default:
{
break;
}
}
+ if (tint_continue) {
+ {
+ if (true) { break; }
+ }
+ continue;
+ }
out = (out + 1u);
{
if (true) { break; }
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl
new file mode 100644
index 0000000..0b65b9c
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl
@@ -0,0 +1,18 @@
+@compute @workgroup_size(1)
+fn f() {
+ var i : i32 = 0;
+ loop {
+ switch (i) {
+ case 0: {
+ continue;
+ }
+ default:{
+ break;
+ }
+ }
+ continuing {
+ i = i + 1;
+ break if i >= 4;
+ }
+ }
+}
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.dxc.hlsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..3aebdc6
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.dxc.hlsl
@@ -0,0 +1,29 @@
+[numthreads(1, 1, 1)]
+void f() {
+ int i = 0;
+ bool tint_continue = false;
+ while (true) {
+ tint_continue = false;
+ switch(i) {
+ case 0: {
+ tint_continue = true;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ if (tint_continue) {
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ }
+ return;
+}
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.fxc.hlsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..3aebdc6
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.fxc.hlsl
@@ -0,0 +1,29 @@
+[numthreads(1, 1, 1)]
+void f() {
+ int i = 0;
+ bool tint_continue = false;
+ while (true) {
+ tint_continue = false;
+ switch(i) {
+ case 0: {
+ tint_continue = true;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ if (tint_continue) {
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ }
+ return;
+}
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.glsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.glsl
new file mode 100644
index 0000000..8de39f2
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.glsl
@@ -0,0 +1,35 @@
+#version 310 es
+
+void f() {
+ int i = 0;
+ bool tint_continue = false;
+ while (true) {
+ tint_continue = false;
+ switch(i) {
+ case 0: {
+ tint_continue = true;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ if (tint_continue) {
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ }
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ f();
+ return;
+}
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.dxc.hlsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.dxc.hlsl
new file mode 100644
index 0000000..4edaaba
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.dxc.hlsl
@@ -0,0 +1,29 @@
+
+[numthreads(1, 1, 1)]
+void f() {
+ int i = 0;
+ {
+ while(true) {
+ switch(i) {
+ case 0:
+ {
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ }
+}
+
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.fxc.hlsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.fxc.hlsl
new file mode 100644
index 0000000..4edaaba
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.fxc.hlsl
@@ -0,0 +1,29 @@
+
+[numthreads(1, 1, 1)]
+void f() {
+ int i = 0;
+ {
+ while(true) {
+ switch(i) {
+ case 0:
+ {
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ }
+}
+
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.glsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.glsl
new file mode 100644
index 0000000..c48695f
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.glsl
@@ -0,0 +1,11 @@
+SKIP: FAILED
+
+../../src/tint/lang/glsl/writer/printer/printer.cc:252 internal compiler error: Switch() matched no cases. Type: tint::core::ir::Loop
+********************************************************************
+* The tint shader compiler has encountered an unexpected error. *
+* *
+* Please help us fix this issue by submitting a bug report at *
+* crbug.com/tint with the source program that triggered the bug. *
+********************************************************************
+
+tint executable returned error: signal: trace/BPT trap
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.msl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.msl
new file mode 100644
index 0000000..d73f2a3
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.ir.msl
@@ -0,0 +1,34 @@
+#include <metal_stdlib>
+using namespace metal;
+
+kernel void f() {
+ int i = 0;
+ {
+ while(true) {
+ bool tint_continue = false;
+ switch(i) {
+ case 0:
+ {
+ tint_continue = true;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ if (tint_continue) {
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ {
+ i = (i + 1);
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ }
+}
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.msl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.msl
new file mode 100644
index 0000000..e0ef3e1
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.msl
@@ -0,0 +1,37 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+#define TINT_ISOLATE_UB(VOLATILE_NAME) \
+ volatile bool VOLATILE_NAME = true; \
+ if (VOLATILE_NAME)
+
+kernel void f() {
+ int i = 0;
+ bool tint_continue = false;
+ TINT_ISOLATE_UB(tint_volatile_true) while(true) {
+ tint_continue = false;
+ switch(i) {
+ case 0: {
+ tint_continue = true;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ if (tint_continue) {
+ {
+ i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)));
+ if ((i >= 4)) { break; }
+ }
+ continue;
+ }
+ {
+ i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)));
+ if ((i >= 4)) { break; }
+ }
+ }
+ return;
+}
+
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.spvasm b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.spvasm
new file mode 100644
index 0000000..055d49b
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.spvasm
@@ -0,0 +1,47 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 1
+; Bound: 24
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %f "f"
+ OpExecutionMode %f LocalSize 1 1 1
+ OpName %f "f"
+ OpName %i "i"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %int_4 = OpConstant %int 4
+ %bool = OpTypeBool
+ %f = OpFunction %void None %3
+ %4 = OpLabel
+ %i = OpVariable %_ptr_Function_int Function
+ OpStore %i %int_0
+ OpBranch %11
+ %11 = OpLabel
+ OpLoopMerge %12 %10 None
+ OpBranch %9
+ %9 = OpLabel
+ %13 = OpLoad %int %i None
+ OpSelectionMerge %16 None
+ OpSwitch %13 %14 0 %15
+ %15 = OpLabel
+ OpBranch %10
+ %14 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %17 = OpLoad %int %i None
+ %18 = OpIAdd %int %17 %int_1
+ OpStore %i %18 None
+ %20 = OpLoad %int %i None
+ %21 = OpSGreaterThanEqual %bool %20 %int_4
+ OpBranchConditional %21 %12 %11
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.wgsl b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.wgsl
new file mode 100644
index 0000000..1b15b09
--- /dev/null
+++ b/test/tint/loops/continue_in_switch_with_breakif.wgsl.expected.wgsl
@@ -0,0 +1,19 @@
+@compute @workgroup_size(1)
+fn f() {
+ var i : i32 = 0;
+ loop {
+ switch(i) {
+ case 0: {
+ continue;
+ }
+ default: {
+ break;
+ }
+ }
+
+ continuing {
+ i = (i + 1);
+ break if (i >= 4);
+ }
+ }
+}