[ir] Add loop hardening transform
This transform injects an additional loop index into loops that might
be infinite, which bounds them to a maximum of 2^64 iterations.
This can be used by all backends to avoid the potential for undefined
behavior to cause problems in downstream compiler stacks.
Bug: 380090814
Change-Id: Icdc952406ceee36389d7845f09e80b125f74340c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/217256
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index a4ed238..1e35311 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -53,6 +53,7 @@
"multiplanar_external_texture.cc",
"prepare_push_constants.cc",
"preserve_padding.cc",
+ "prevent_infinite_loops.cc",
"remove_continue_in_switch.cc",
"remove_terminator_args.cc",
"rename_conflicts.cc",
@@ -80,6 +81,7 @@
"multiplanar_external_texture.h",
"prepare_push_constants.h",
"preserve_padding.h",
+ "prevent_infinite_loops.h",
"remove_continue_in_switch.h",
"remove_terminator_args.h",
"rename_conflicts.h",
@@ -99,6 +101,7 @@
"//src/tint/lang/core/constant",
"//src/tint/lang/core/intrinsic",
"//src/tint/lang/core/ir",
+ "//src/tint/lang/core/ir/analysis",
"//src/tint/lang/core/ir/type",
"//src/tint/lang/core/type",
"//src/tint/utils",
@@ -136,6 +139,7 @@
"multiplanar_external_texture_test.cc",
"prepare_push_constants_test.cc",
"preserve_padding_test.cc",
+ "prevent_infinite_loops_test.cc",
"remove_continue_in_switch_test.cc",
"remove_terminator_args_test.cc",
"rename_conflicts_test.cc",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index e3373b6..de6cd8d 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/prepare_push_constants.h
lang/core/ir/transform/preserve_padding.cc
lang/core/ir/transform/preserve_padding.h
+ lang/core/ir/transform/prevent_infinite_loops.cc
+ lang/core/ir/transform/prevent_infinite_loops.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
@@ -98,6 +100,7 @@
tint_lang_core_constant
tint_lang_core_intrinsic
tint_lang_core_ir
+ tint_lang_core_ir_analysis
tint_lang_core_ir_type
tint_lang_core_type
tint_utils
@@ -137,6 +140,7 @@
lang/core/ir/transform/multiplanar_external_texture_test.cc
lang/core/ir/transform/prepare_push_constants_test.cc
lang/core/ir/transform/preserve_padding_test.cc
+ lang/core/ir/transform/prevent_infinite_loops_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
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 7068283..33d7478 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -73,6 +73,8 @@
"prepare_push_constants.h",
"preserve_padding.cc",
"preserve_padding.h",
+ "prevent_infinite_loops.cc",
+ "prevent_infinite_loops.h",
"remove_continue_in_switch.cc",
"remove_continue_in_switch.h",
"remove_terminator_args.cc",
@@ -104,6 +106,7 @@
"${tint_src_dir}/lang/core/constant",
"${tint_src_dir}/lang/core/intrinsic",
"${tint_src_dir}/lang/core/ir",
+ "${tint_src_dir}/lang/core/ir/analysis",
"${tint_src_dir}/lang/core/ir/type",
"${tint_src_dir}/lang/core/type",
"${tint_src_dir}/utils",
@@ -137,6 +140,7 @@
"multiplanar_external_texture_test.cc",
"prepare_push_constants_test.cc",
"preserve_padding_test.cc",
+ "prevent_infinite_loops_test.cc",
"remove_continue_in_switch_test.cc",
"remove_terminator_args_test.cc",
"rename_conflicts_test.cc",
diff --git a/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc b/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
new file mode 100644
index 0000000..0c8568b
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
@@ -0,0 +1,123 @@
+// 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/prevent_infinite_loops.h"
+
+#include "src/tint/lang/core/ir/analysis/loop_analysis.h"
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/traverse.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};
+
+ /// Process the module.
+ void Process() {
+ for (auto func : ir.functions) {
+ // Look for loops in the function that we cannot detect as being finite, and inject a
+ // a new loop index that bounds the loop to 2^64 iterations.
+ analysis::LoopAnalysis analysis(*func);
+ Traverse(func->Block(), [&](Loop* loop) {
+ if (!analysis.GetInfo(*loop)->IsFinite()) {
+ InjectExitCondition(loop);
+ }
+ });
+ }
+ }
+
+ /// Inject an exit condition into @p loop.
+ /// @param loop the `loop` to inject the condition into
+ void InjectExitCondition(Loop* loop) {
+ // Initializer:
+ // var idx: vec2u;
+ //
+ // Body:
+ // if all(idx == vec2(UINT32_MAX)) { break; }
+ //
+ // Continuing:
+ // idx.x += 1;
+ // idx.y += u32(idx.x == 0);
+
+ // Declare a new index variable at the top of the loop initializer.
+ auto* idx = b.Var<function, vec2<u32>>("tint_loop_idx");
+ if (loop->Initializer()->IsEmpty()) {
+ loop->Initializer()->Append(b.NextIteration(loop));
+ }
+ loop->Initializer()->Prepend(idx);
+
+ // Insert the new exit condition at the top of the loop body.
+ b.InsertBefore(loop->Body()->Front(), [&] {
+ auto* ifelse = b.If(
+ b.Call<bool>(BuiltinFn::kAll,
+ b.Equal<vec2<bool>>(b.Load(idx), b.Splat<vec2<u32>>(u32::Highest()))));
+ b.Append(ifelse->True(), [&] { b.ExitLoop(loop); });
+ });
+
+ // Increment the index variable at the top of the continuing block.
+ if (loop->Continuing()->IsEmpty()) {
+ loop->Continuing()->Append(b.NextIteration(loop));
+ }
+ b.InsertBefore(loop->Continuing()->Front(), [&] {
+ auto* low_inc = b.Add<u32>(b.LoadVectorElement(idx, 0_u), 1_u);
+ ir.SetName(low_inc->Result(0), ir.symbols.New("tint_low_inc"));
+ b.StoreVectorElement(idx, 0_u, low_inc);
+
+ auto* carry = b.Convert<u32>(b.Equal<bool>(low_inc, 0_u));
+ ir.SetName(carry->Result(0), ir.symbols.New("tint_carry"));
+ b.StoreVectorElement(idx, 1_u, b.Add<u32>(b.LoadVectorElement(idx, 1_u), carry));
+ });
+ }
+};
+
+} // namespace
+
+Result<SuccessType> PreventInfiniteLoops(Module& ir) {
+ auto result = ValidateAndDumpIfNeeded(ir, "core.PreventInfiniteLoops");
+ if (result != Success) {
+ return result;
+ }
+
+ State{ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/prevent_infinite_loops.h b/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
new file mode 100644
index 0000000..0481439
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
@@ -0,0 +1,50 @@
+// 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_PREVENT_INFINITE_LOOPS_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_PREVENT_INFINITE_LOOPS_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// PreventInfiniteLoops is a transform that injects an additional exit condition into loops that
+/// may be infinite, to prevent downstream compilers from making bad assumptions due to the
+/// undefined behavior of infinite loops.
+///
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> PreventInfiniteLoops(Module& module);
+
+} // namespace tint::core::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_PREVENT_INFINITE_LOOPS_H_
diff --git a/src/tint/lang/core/ir/transform/prevent_infinite_loops_test.cc b/src/tint/lang/core/ir/transform/prevent_infinite_loops_test.cc
new file mode 100644
index 0000000..7de5a56
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/prevent_infinite_loops_test.cc
@@ -0,0 +1,725 @@
+// 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/prevent_infinite_loops.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_PreventInfiniteLoopsTest = TransformTest;
+
+TEST_F(IR_PreventInfiniteLoopsTest, NoModify_SimpleFiniteLoop) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ auto* idx = b.Var<function, u32>("idx");
+ b.NextIteration(loop);
+
+ b.Append(loop->Body(), [&] {
+ auto* ifelse = b.If(b.LessThan<bool>(b.Load(idx), 10_u));
+ b.Append(ifelse->True(), [&] { //
+ b.ExitIf(ifelse);
+ });
+ b.Append(ifelse->False(), [&] { //
+ b.ExitLoop(loop);
+ });
+ b.Continue(loop);
+
+ b.Append(loop->Continuing(), [&] { //
+ b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
+ b.NextIteration(loop);
+ });
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:u32 = load %idx
+ %4:bool = lt %3, 10u
+ if %4 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %5:u32 = load %idx
+ %6:u32 = add %5, 1u
+ store %idx, %6
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreventInfiniteLoops);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreventInfiniteLoopsTest, InfiniteLoop) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ auto* idx = b.Var<function, u32>("idx");
+ b.NextIteration(loop);
+
+ b.Append(loop->Body(), [&] {
+ auto* ifelse = b.If(b.LessThan<bool>(b.Load(idx), 10_u));
+ b.Append(ifelse->True(), [&] { //
+ b.ExitIf(ifelse);
+ });
+ b.Append(ifelse->False(), [&] { //
+ b.ExitLoop(loop);
+ });
+ b.Store(idx, 0_u);
+ b.Continue(loop);
+
+ b.Append(loop->Continuing(), [&] { //
+ b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
+ b.NextIteration(loop);
+ });
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:u32 = load %idx
+ %4:bool = lt %3, 10u
+ if %4 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ store %idx, 0u
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %5:u32 = load %idx
+ %6:u32 = add %5, 1u
+ store %idx, %6
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %tint_loop_idx:ptr<function, vec2<u32>, read_write> = var
+ %idx:ptr<function, u32, read_write> = var
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %4:vec2<u32> = load %tint_loop_idx
+ %5:vec2<bool> = eq %4, vec2<u32>(4294967295u)
+ %6:bool = all %5
+ if %6 [t: $B5] { # if_1
+ $B5: { # true
+ exit_loop # loop_1
+ }
+ }
+ %7:u32 = load %idx
+ %8:bool = lt %7, 10u
+ if %8 [t: $B6, f: $B7] { # if_2
+ $B6: { # true
+ exit_if # if_2
+ }
+ $B7: { # false
+ exit_loop # loop_1
+ }
+ }
+ store %idx, 0u
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %9:u32 = load_vector_element %tint_loop_idx, 0u
+ %tint_low_inc:u32 = add %9, 1u
+ store_vector_element %tint_loop_idx, 0u, %tint_low_inc
+ %11:bool = eq %tint_low_inc, 0u
+ %tint_carry:u32 = convert %11
+ %13:u32 = load_vector_element %tint_loop_idx, 1u
+ %14:u32 = add %13, %tint_carry
+ store_vector_element %tint_loop_idx, 1u, %14
+ %15:u32 = load %idx
+ %16:u32 = add %15, 1u
+ store %idx, %16
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(PreventInfiniteLoops);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreventInfiniteLoopsTest, EmptyInitializer) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* ifelse = b.If(false);
+ b.Append(ifelse->True(), [&] { //
+ b.ExitLoop(loop);
+ });
+ b.Continue(loop);
+
+ b.Append(loop->Continuing(), [&] { //
+ b.NextIteration(loop);
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ if false [t: $B4] { # if_1
+ $B4: { # true
+ exit_loop # loop_1
+ }
+ }
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ next_iteration # -> $B2
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %tint_loop_idx:ptr<function, vec2<u32>, read_write> = var
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:vec2<u32> = load %tint_loop_idx
+ %4:vec2<bool> = eq %3, vec2<u32>(4294967295u)
+ %5:bool = all %4
+ if %5 [t: $B5] { # if_1
+ $B5: { # true
+ exit_loop # loop_1
+ }
+ }
+ if false [t: $B6] { # if_2
+ $B6: { # true
+ exit_loop # loop_1
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %6:u32 = load_vector_element %tint_loop_idx, 0u
+ %tint_low_inc:u32 = add %6, 1u
+ store_vector_element %tint_loop_idx, 0u, %tint_low_inc
+ %8:bool = eq %tint_low_inc, 0u
+ %tint_carry:u32 = convert %8
+ %10:u32 = load_vector_element %tint_loop_idx, 1u
+ %11:u32 = add %10, %tint_carry
+ store_vector_element %tint_loop_idx, 1u, %11
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(PreventInfiniteLoops);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreventInfiniteLoopsTest, EmptyContinuing) {
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] { //
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ auto* idx = b.Var<function, u32>("idx");
+ b.NextIteration(loop);
+
+ b.Append(loop->Body(), [&] {
+ auto* ifelse = b.If(b.LessThan<bool>(b.Load(idx), 10_u));
+ b.Append(ifelse->True(), [&] { //
+ b.ExitIf(ifelse);
+ });
+ b.Append(ifelse->False(), [&] { //
+ b.ExitLoop(loop);
+ });
+ b.Continue(loop);
+ });
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:u32 = load %idx
+ %4:bool = lt %3, 10u
+ if %4 [t: $B4, f: $B5] { # if_1
+ $B4: { # true
+ exit_if # if_1
+ }
+ $B5: { # false
+ exit_loop # loop_1
+ }
+ }
+ continue # -> $B6
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %tint_loop_idx:ptr<function, vec2<u32>, read_write> = var
+ %idx:ptr<function, u32, read_write> = var
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %4:vec2<u32> = load %tint_loop_idx
+ %5:vec2<bool> = eq %4, vec2<u32>(4294967295u)
+ %6:bool = all %5
+ if %6 [t: $B5] { # if_1
+ $B5: { # true
+ exit_loop # loop_1
+ }
+ }
+ %7:u32 = load %idx
+ %8:bool = lt %7, 10u
+ if %8 [t: $B6, f: $B7] { # if_2
+ $B6: { # true
+ exit_if # if_2
+ }
+ $B7: { # false
+ exit_loop # loop_1
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %9:u32 = load_vector_element %tint_loop_idx, 0u
+ %tint_low_inc:u32 = add %9, 1u
+ store_vector_element %tint_loop_idx, 0u, %tint_low_inc
+ %11:bool = eq %tint_low_inc, 0u
+ %tint_carry:u32 = convert %11
+ %13:u32 = load_vector_element %tint_loop_idx, 1u
+ %14:u32 = add %13, %tint_carry
+ store_vector_element %tint_loop_idx, 1u, %14
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(PreventInfiniteLoops);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreventInfiniteLoopsTest, MultipleNestedLoops) {
+ // We will create two pairs of nested loops:
+ // loop outer_1 { <- finite
+ // loop inner_1 { <- infinite
+ // }
+ // }
+ // loop outer_2 { <- infinite
+ // loop inner_2 { <- finite
+ // }
+ // }
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* loop_outer_1 = b.Loop();
+ b.Append(loop_outer_1->Initializer(), [&] { //
+ auto* idx_outer_1 = b.Var("idx", 0_u);
+ b.NextIteration(loop_outer_1);
+
+ b.Append(loop_outer_1->Body(), [&] {
+ auto* ifelse_outer = b.If(b.LessThan<bool>(b.Load(idx_outer_1), 10_u));
+ b.Append(ifelse_outer->True(), [&] { //
+ b.ExitLoop(loop_outer_1);
+ });
+
+ auto* loop_inner_1 = b.Loop();
+ b.Append(loop_inner_1->Initializer(), [&] { //
+ auto* idx_inner_1 = b.Var("idx", 0_u);
+ b.NextIteration(loop_inner_1);
+
+ b.Append(loop_inner_1->Body(), [&] {
+ auto* ifelse_inner = b.If(b.LessThan<bool>(b.Load(idx_inner_1), 10_u));
+ b.Append(ifelse_inner->True(), [&] { //
+ b.ExitLoop(loop_inner_1);
+ });
+ b.Continue(loop_inner_1);
+ });
+ b.Append(loop_inner_1->Continuing(), [&] { //
+ b.NextIteration(loop_inner_1);
+ });
+ });
+
+ b.Continue(loop_outer_1);
+ });
+ b.Append(loop_outer_1->Continuing(), [&] { //
+ b.Store(idx_outer_1, b.Add<u32>(b.Load(idx_outer_1), 1_u));
+ b.NextIteration(loop_outer_1);
+ });
+ });
+
+ auto* loop_outer_2 = b.Loop();
+ b.Append(loop_outer_2->Initializer(), [&] { //
+ auto* idx_outer_2 = b.Var("idx", 0_u);
+ b.NextIteration(loop_outer_2);
+
+ b.Append(loop_outer_2->Body(), [&] {
+ auto* ifelse_outer = b.If(b.LessThan<bool>(b.Load(idx_outer_2), 10_u));
+ b.Append(ifelse_outer->True(), [&] { //
+ b.ExitLoop(loop_outer_2);
+ });
+
+ auto* loop_inner_2 = b.Loop();
+ b.Append(loop_inner_2->Initializer(), [&] { //
+ auto* idx_inner_2 = b.Var("idx", 0_u);
+ b.NextIteration(loop_inner_2);
+
+ b.Append(loop_inner_2->Body(), [&] {
+ auto* ifelse_inner = b.If(b.LessThan<bool>(b.Load(idx_inner_2), 10_u));
+ b.Append(ifelse_inner->True(), [&] { //
+ b.ExitLoop(loop_inner_2);
+ });
+ b.Continue(loop_inner_2);
+ });
+ b.Append(loop_inner_2->Continuing(), [&] { //
+ b.Store(idx_inner_2, b.Add<u32>(b.Load(idx_inner_2), 1_u));
+ b.NextIteration(loop_inner_2);
+ });
+ });
+
+ b.Continue(loop_outer_2);
+ });
+ b.Append(loop_outer_2->Continuing(), [&] { //
+ b.NextIteration(loop_outer_2);
+ });
+ });
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var, 0u
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:u32 = load %idx
+ %4:bool = lt %3, 10u
+ if %4 [t: $B5] { # if_1
+ $B5: { # true
+ exit_loop # loop_1
+ }
+ }
+ loop [i: $B6, b: $B7, c: $B8] { # loop_2
+ $B6: { # initializer
+ %idx_1:ptr<function, u32, read_write> = var, 0u # %idx_1: 'idx'
+ next_iteration # -> $B7
+ }
+ $B7: { # body
+ %6:u32 = load %idx_1
+ %7:bool = lt %6, 10u
+ if %7 [t: $B9] { # if_2
+ $B9: { # true
+ exit_loop # loop_2
+ }
+ }
+ continue # -> $B8
+ }
+ $B8: { # continuing
+ next_iteration # -> $B7
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %8:u32 = load %idx
+ %9:u32 = add %8, 1u
+ store %idx, %9
+ next_iteration # -> $B3
+ }
+ }
+ loop [i: $B10, b: $B11, c: $B12] { # loop_3
+ $B10: { # initializer
+ %idx_2:ptr<function, u32, read_write> = var, 0u # %idx_2: 'idx'
+ next_iteration # -> $B11
+ }
+ $B11: { # body
+ %11:u32 = load %idx_2
+ %12:bool = lt %11, 10u
+ if %12 [t: $B13] { # if_3
+ $B13: { # true
+ exit_loop # loop_3
+ }
+ }
+ loop [i: $B14, b: $B15, c: $B16] { # loop_4
+ $B14: { # initializer
+ %idx_3:ptr<function, u32, read_write> = var, 0u # %idx_3: 'idx'
+ next_iteration # -> $B15
+ }
+ $B15: { # body
+ %14:u32 = load %idx_3
+ %15:bool = lt %14, 10u
+ if %15 [t: $B17] { # if_4
+ $B17: { # true
+ exit_loop # loop_4
+ }
+ }
+ continue # -> $B16
+ }
+ $B16: { # continuing
+ %16:u32 = load %idx_3
+ %17:u32 = add %16, 1u
+ store %idx_3, %17
+ next_iteration # -> $B15
+ }
+ }
+ continue # -> $B12
+ }
+ $B12: { # continuing
+ next_iteration # -> $B11
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var, 0u
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:u32 = load %idx
+ %4:bool = lt %3, 10u
+ if %4 [t: $B5] { # if_1
+ $B5: { # true
+ exit_loop # loop_1
+ }
+ }
+ loop [i: $B6, b: $B7, c: $B8] { # loop_2
+ $B6: { # initializer
+ %tint_loop_idx:ptr<function, vec2<u32>, read_write> = var
+ %idx_1:ptr<function, u32, read_write> = var, 0u # %idx_1: 'idx'
+ next_iteration # -> $B7
+ }
+ $B7: { # body
+ %7:vec2<u32> = load %tint_loop_idx
+ %8:vec2<bool> = eq %7, vec2<u32>(4294967295u)
+ %9:bool = all %8
+ if %9 [t: $B9] { # if_2
+ $B9: { # true
+ exit_loop # loop_2
+ }
+ }
+ %10:u32 = load %idx_1
+ %11:bool = lt %10, 10u
+ if %11 [t: $B10] { # if_3
+ $B10: { # true
+ exit_loop # loop_2
+ }
+ }
+ continue # -> $B8
+ }
+ $B8: { # continuing
+ %12:u32 = load_vector_element %tint_loop_idx, 0u
+ %tint_low_inc:u32 = add %12, 1u
+ store_vector_element %tint_loop_idx, 0u, %tint_low_inc
+ %14:bool = eq %tint_low_inc, 0u
+ %tint_carry:u32 = convert %14
+ %16:u32 = load_vector_element %tint_loop_idx, 1u
+ %17:u32 = add %16, %tint_carry
+ store_vector_element %tint_loop_idx, 1u, %17
+ next_iteration # -> $B7
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %18:u32 = load %idx
+ %19:u32 = add %18, 1u
+ store %idx, %19
+ next_iteration # -> $B3
+ }
+ }
+ loop [i: $B11, b: $B12, c: $B13] { # loop_3
+ $B11: { # initializer
+ %tint_loop_idx_1:ptr<function, vec2<u32>, read_write> = var # %tint_loop_idx_1: 'tint_loop_idx'
+ %idx_2:ptr<function, u32, read_write> = var, 0u # %idx_2: 'idx'
+ next_iteration # -> $B12
+ }
+ $B12: { # body
+ %22:vec2<u32> = load %tint_loop_idx_1
+ %23:vec2<bool> = eq %22, vec2<u32>(4294967295u)
+ %24:bool = all %23
+ if %24 [t: $B14] { # if_4
+ $B14: { # true
+ exit_loop # loop_3
+ }
+ }
+ %25:u32 = load %idx_2
+ %26:bool = lt %25, 10u
+ if %26 [t: $B15] { # if_5
+ $B15: { # true
+ exit_loop # loop_3
+ }
+ }
+ loop [i: $B16, b: $B17, c: $B18] { # loop_4
+ $B16: { # initializer
+ %idx_3:ptr<function, u32, read_write> = var, 0u # %idx_3: 'idx'
+ next_iteration # -> $B17
+ }
+ $B17: { # body
+ %28:u32 = load %idx_3
+ %29:bool = lt %28, 10u
+ if %29 [t: $B19] { # if_6
+ $B19: { # true
+ exit_loop # loop_4
+ }
+ }
+ continue # -> $B18
+ }
+ $B18: { # continuing
+ %30:u32 = load %idx_3
+ %31:u32 = add %30, 1u
+ store %idx_3, %31
+ next_iteration # -> $B17
+ }
+ }
+ continue # -> $B13
+ }
+ $B13: { # continuing
+ %32:u32 = load_vector_element %tint_loop_idx_1, 0u
+ %tint_low_inc_1:u32 = add %32, 1u
+ store_vector_element %tint_loop_idx_1, 0u, %tint_low_inc_1
+ %34:bool = eq %tint_low_inc_1, 0u
+ %tint_carry_1:u32 = convert %34
+ %36:u32 = load_vector_element %tint_loop_idx_1, 1u
+ %37:u32 = add %36, %tint_carry_1
+ store_vector_element %tint_loop_idx_1, 1u, %37
+ next_iteration # -> $B12
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(PreventInfiniteLoops);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::core::ir::transform