[spirv] Fix terminator creation in MergeReturn

When we walk up the control stack to add missing terminators to `if`
instructions, we need to limit this to `if` instructions that were
added by the transform. Otherwise, we may add a terminator to a block
that is still being processed, and end up with multiple terminators.

Fixed: 343597426
Change-Id: If6c345e817a11e68396f5eeed29d38c5b7478323
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190723
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/spirv/writer/raise/merge_return.cc b/src/tint/lang/spirv/writer/raise/merge_return.cc
index 350afa4..077660e0 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return.cc
@@ -129,6 +129,7 @@
     /// @param block the block to process
     void ProcessBlock(core::ir::Block* block) {
         core::ir::If* inner_if = nullptr;
+        Vector<core::ir::If*, 4> inner_if_stack;
         for (auto* inst = *block->begin(); inst;) {  // For each instruction in 'block'
             // As we're modifying the block that we're iterating over, grab the pointer to the next
             // instruction before (potentially) moving 'inst' to another block.
@@ -167,6 +168,7 @@
                     if (next && (next != fn_return || fn_return->Value()) &&
                         !tint::IsAnyOf<core::ir::Exit, core::ir::Unreachable>(next)) {
                         inner_if = CreateIfContinueExecution(ctrl);
+                        inner_if_stack.Push(inner_if);
                     }
                 }
             }
@@ -195,9 +197,10 @@
                 inner_if->True()->Append(b.ExitIf(inner_if));
             }
 
-            // Loop over the 'if' instructions, starting with the inner-most, and add any missing
-            // terminating instructions to the blocks holding the 'if'.
-            for (auto* i = inner_if; i; i = tint::As<core::ir::If>(i->Block()->Parent())) {
+            // Walk back down the stack of 'if' instructions that were created, and add any missing
+            // terminating instructions to the blocks holding them.
+            while (!inner_if_stack.IsEmpty()) {
+                auto* i = inner_if_stack.Pop();
                 if (!i->Block()->Terminator() && i->Block()->Parent()) {
                     // Append the exit instruction to the block holding the 'if'.
                     Vector<core::ir::InstructionResult*, 8> exit_args = i->Results();
diff --git a/src/tint/lang/spirv/writer/raise/merge_return_test.cc b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
index 9cb2f0a..7b7c0f1 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return_test.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
@@ -1413,6 +1413,306 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(SpirvWriter_MergeReturnTest, IfElse_NestedConsecutives) {
+    auto* value = b.FunctionParam(ty.i32());
+    auto* func = b.Function("foo", ty.i32());
+    func->SetParams({value});
+
+    b.Append(func->Block(), [&] {
+        auto* outer = b.If(b.Equal(ty.bool_(), value, 1_i));
+        b.Append(outer->True(), [&] {
+            auto* middle_first = b.If(b.Equal(ty.bool_(), value, 2_i));
+            b.Append(middle_first->True(), [&] {  //
+                b.Return(func, 202_i);
+            });
+
+            auto* middle_second = b.If(b.Equal(ty.bool_(), value, 3_i));
+            b.Append(middle_second->True(), [&] {
+                auto* inner_first = b.If(b.Equal(ty.bool_(), value, 4_i));
+                b.Append(inner_first->True(), [&] {  //
+                    b.Return(func, 404_i);
+                });
+
+                auto* inner_second = b.If(b.Equal(ty.bool_(), value, 5_i));
+                b.Append(inner_second->True(), [&] {  //
+                    b.Return(func, 505_i);
+                });
+
+                b.ExitIf(middle_second);
+            });
+
+            b.ExitIf(outer);
+        });
+
+        b.Return(func, 606_i);
+    });
+
+    auto* src = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %3:bool = eq %2, 1i
+    if %3 [t: $B2] {  # if_1
+      $B2: {  # true
+        %4:bool = eq %2, 2i
+        if %4 [t: $B3] {  # if_2
+          $B3: {  # true
+            ret 202i
+          }
+        }
+        %5:bool = eq %2, 3i
+        if %5 [t: $B4] {  # if_3
+          $B4: {  # true
+            %6:bool = eq %2, 4i
+            if %6 [t: $B5] {  # if_4
+              $B5: {  # true
+                ret 404i
+              }
+            }
+            %7:bool = eq %2, 5i
+            if %7 [t: $B6] {  # if_5
+              $B6: {  # true
+                ret 505i
+              }
+            }
+            exit_if  # if_3
+          }
+        }
+        exit_if  # if_1
+      }
+    }
+    ret 606i
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %return_value:ptr<function, i32, read_write> = var
+    %continue_execution:ptr<function, bool, read_write> = var, true
+    %5:bool = eq %2, 1i
+    if %5 [t: $B2] {  # if_1
+      $B2: {  # true
+        %6:bool = eq %2, 2i
+        if %6 [t: $B3] {  # if_2
+          $B3: {  # true
+            store %continue_execution, false
+            store %return_value, 202i
+            exit_if  # if_2
+          }
+        }
+        %7:bool = load %continue_execution
+        if %7 [t: $B4] {  # if_3
+          $B4: {  # true
+            %8:bool = eq %2, 3i
+            if %8 [t: $B5] {  # if_4
+              $B5: {  # true
+                %9:bool = eq %2, 4i
+                if %9 [t: $B6] {  # if_5
+                  $B6: {  # true
+                    store %continue_execution, false
+                    store %return_value, 404i
+                    exit_if  # if_5
+                  }
+                }
+                %10:bool = load %continue_execution
+                if %10 [t: $B7] {  # if_6
+                  $B7: {  # true
+                    %11:bool = eq %2, 5i
+                    if %11 [t: $B8] {  # if_7
+                      $B8: {  # true
+                        store %continue_execution, false
+                        store %return_value, 505i
+                        exit_if  # if_7
+                      }
+                    }
+                    exit_if  # if_6
+                  }
+                }
+                exit_if  # if_4
+              }
+            }
+            exit_if  # if_3
+          }
+        }
+        exit_if  # if_1
+      }
+    }
+    %12:bool = load %continue_execution
+    if %12 [t: $B9] {  # if_8
+      $B9: {  # true
+        store %return_value, 606i
+        exit_if  # if_8
+      }
+    }
+    %13:i32 = load %return_value
+    ret %13
+  }
+}
+)";
+
+    Run(MergeReturn);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvWriter_MergeReturnTest, IfElse_NestedConsecutives_WithResults) {
+    auto* value = b.FunctionParam(ty.i32());
+    auto* func = b.Function("foo", ty.i32());
+    func->SetParams({value});
+
+    b.Append(func->Block(), [&] {
+        auto* outer_result = b.InstructionResult(ty.i32());
+        auto* outer = b.If(b.Equal(ty.bool_(), value, 1_i));
+        outer->SetResults(Vector{outer_result});
+        b.Append(outer->True(), [&] {
+            auto* middle_first = b.If(b.Equal(ty.bool_(), value, 2_i));
+            b.Append(middle_first->True(), [&] {  //
+                b.Return(func, 202_i);
+            });
+
+            auto middle_result = b.InstructionResult(ty.i32());
+            auto* middle_second = b.If(b.Equal(ty.bool_(), value, 3_i));
+            middle_second->SetResults(Vector{middle_result});
+            b.Append(middle_second->True(), [&] {
+                auto* inner_first = b.If(b.Equal(ty.bool_(), value, 4_i));
+                b.Append(inner_first->True(), [&] {  //
+                    b.Return(func, 404_i);
+                });
+
+                auto inner_result = b.InstructionResult(ty.i32());
+                auto* inner_second = b.If(b.Equal(ty.bool_(), value, 5_i));
+                inner_second->SetResults(Vector{inner_result});
+                b.Append(inner_second->True(), [&] {  //
+                    b.ExitIf(inner_second, 505_i);
+                });
+
+                b.ExitIf(middle_second, inner_result);
+            });
+
+            b.ExitIf(outer, middle_result);
+        });
+
+        b.Return(func, outer_result);
+    });
+
+    auto* src = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %3:bool = eq %2, 1i
+    %4:i32 = if %3 [t: $B2] {  # if_1
+      $B2: {  # true
+        %5:bool = eq %2, 2i
+        if %5 [t: $B3] {  # if_2
+          $B3: {  # true
+            ret 202i
+          }
+        }
+        %6:bool = eq %2, 3i
+        %7:i32 = if %6 [t: $B4] {  # if_3
+          $B4: {  # true
+            %8:bool = eq %2, 4i
+            if %8 [t: $B5] {  # if_4
+              $B5: {  # true
+                ret 404i
+              }
+            }
+            %9:bool = eq %2, 5i
+            %10:i32 = if %9 [t: $B6] {  # if_5
+              $B6: {  # true
+                exit_if 505i  # if_5
+              }
+              # implicit false block: exit_if undef
+            }
+            exit_if %10  # if_3
+          }
+          # implicit false block: exit_if undef
+        }
+        exit_if %7  # if_1
+      }
+      # implicit false block: exit_if undef
+    }
+    ret %4
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %return_value:ptr<function, i32, read_write> = var
+    %continue_execution:ptr<function, bool, read_write> = var, true
+    %5:bool = eq %2, 1i
+    %6:i32 = if %5 [t: $B2] {  # if_1
+      $B2: {  # true
+        %7:bool = eq %2, 2i
+        if %7 [t: $B3] {  # if_2
+          $B3: {  # true
+            store %continue_execution, false
+            store %return_value, 202i
+            exit_if  # if_2
+          }
+        }
+        %8:bool = load %continue_execution
+        %9:i32 = if %8 [t: $B4] {  # if_3
+          $B4: {  # true
+            %10:bool = eq %2, 3i
+            %11:i32 = if %10 [t: $B5] {  # if_4
+              $B5: {  # true
+                %12:bool = eq %2, 4i
+                if %12 [t: $B6] {  # if_5
+                  $B6: {  # true
+                    store %continue_execution, false
+                    store %return_value, 404i
+                    exit_if  # if_5
+                  }
+                }
+                %13:bool = load %continue_execution
+                %14:i32 = if %13 [t: $B7] {  # if_6
+                  $B7: {  # true
+                    %15:bool = eq %2, 5i
+                    %16:i32 = if %15 [t: $B8] {  # if_7
+                      $B8: {  # true
+                        exit_if 505i  # if_7
+                      }
+                      # implicit false block: exit_if undef
+                    }
+                    exit_if %16  # if_6
+                  }
+                  # implicit false block: exit_if undef
+                }
+                exit_if %14  # if_4
+              }
+              # implicit false block: exit_if undef
+            }
+            exit_if %11  # if_3
+          }
+          # implicit false block: exit_if undef
+        }
+        exit_if %9  # if_1
+      }
+      # implicit false block: exit_if undef
+    }
+    %17:bool = load %continue_execution
+    if %17 [t: $B9] {  # if_8
+      $B9: {  # true
+        store %return_value, %6
+        exit_if  # if_8
+      }
+    }
+    %18:i32 = load %return_value
+    ret %18
+  }
+}
+)";
+
+    Run(MergeReturn);
+
+    EXPECT_EQ(expect, str());
+}
+
 TEST_F(SpirvWriter_MergeReturnTest, Loop_UnconditionalReturnInBody) {
     auto* func = b.Function("foo", ty.i32());
 
diff --git a/test/tint/bug/chromium/343597426.wgsl b/test/tint/bug/chromium/343597426.wgsl
new file mode 100644
index 0000000..5f34d79
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl
@@ -0,0 +1,14 @@
+fn foo(a: bool, b: bool, c: bool, d: bool, e: bool) {
+    if a {
+        if b {
+            return;
+        }
+        if c {
+            if d {
+                return;
+            }
+            if e {
+            }
+        }
+    }
+}
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.dxc.hlsl b/test/tint/bug/chromium/343597426.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..b06623c
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.dxc.hlsl
@@ -0,0 +1,19 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+void foo(bool a, bool b, bool c, bool d, bool e) {
+  if (a) {
+    if (b) {
+      return;
+    }
+    if (c) {
+      if (d) {
+        return;
+      }
+      if (e) {
+      }
+    }
+  }
+}
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.fxc.hlsl b/test/tint/bug/chromium/343597426.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..b06623c
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.fxc.hlsl
@@ -0,0 +1,19 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+void foo(bool a, bool b, bool c, bool d, bool e) {
+  if (a) {
+    if (b) {
+      return;
+    }
+    if (c) {
+      if (d) {
+        return;
+      }
+      if (e) {
+      }
+    }
+  }
+}
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.glsl b/test/tint/bug/chromium/343597426.wgsl.expected.glsl
new file mode 100644
index 0000000..82976e7
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void unused_entry_point() {
+  return;
+}
+void foo(bool a, bool b, bool c, bool d, bool e) {
+  if (a) {
+    if (b) {
+      return;
+    }
+    if (c) {
+      if (d) {
+        return;
+      }
+      if (e) {
+      }
+    }
+  }
+}
+
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.ir.msl b/test/tint/bug/chromium/343597426.wgsl.expected.ir.msl
new file mode 100644
index 0000000..8f6ddbb
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.ir.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+using namespace metal;
+
+void foo(bool a, bool b, bool c, bool d, bool e) {
+  if (a) {
+    if (b) {
+      return;
+    }
+    if (c) {
+      if (d) {
+        return;
+      }
+      if (e) {
+      }
+    }
+  }
+}
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.ir.spvasm b/test/tint/bug/chromium/343597426.wgsl.expected.ir.spvasm
new file mode 100644
index 0000000..cc1c1a7
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.ir.spvasm
@@ -0,0 +1,76 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 1
+; Bound: 33
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %foo "foo"
+               OpName %a "a"
+               OpName %b "b"
+               OpName %c "c"
+               OpName %d "d"
+               OpName %e "e"
+               OpName %continue_execution "continue_execution"
+               OpName %unused_entry_point "unused_entry_point"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+          %9 = OpTypeFunction %void %bool %bool %bool %bool %bool
+%_ptr_Function_bool = OpTypePointer Function %bool
+       %true = OpConstantTrue %bool
+      %false = OpConstantFalse %bool
+         %31 = OpTypeFunction %void
+        %foo = OpFunction %void None %9
+          %a = OpFunctionParameter %bool
+          %b = OpFunctionParameter %bool
+          %c = OpFunctionParameter %bool
+          %d = OpFunctionParameter %bool
+          %e = OpFunctionParameter %bool
+         %10 = OpLabel
+%continue_execution = OpVariable %_ptr_Function_bool Function
+               OpStore %continue_execution %true
+               OpSelectionMerge %14 None
+               OpBranchConditional %a %15 %14
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %b %17 %16
+         %17 = OpLabel
+               OpStore %continue_execution %false
+               OpBranch %16
+         %16 = OpLabel
+         %19 = OpLoad %bool %continue_execution
+               OpSelectionMerge %20 None
+               OpBranchConditional %19 %21 %20
+         %21 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %c %23 %22
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %d %25 %24
+         %25 = OpLabel
+               OpStore %continue_execution %false
+               OpBranch %24
+         %24 = OpLabel
+         %26 = OpLoad %bool %continue_execution
+               OpSelectionMerge %27 None
+               OpBranchConditional %26 %28 %27
+         %28 = OpLabel
+               OpSelectionMerge %29 None
+               OpBranchConditional %e %29 %29
+         %29 = OpLabel
+               OpBranch %27
+         %27 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+%unused_entry_point = OpFunction %void None %31
+         %32 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.msl b/test/tint/bug/chromium/343597426.wgsl.expected.msl
new file mode 100644
index 0000000..b9dd7c8
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.msl
@@ -0,0 +1,18 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void foo(bool a, bool b, bool c, bool d, bool e) {
+  if (a) {
+    if (b) {
+      return;
+    }
+    if (c) {
+      if (d) {
+        return;
+      }
+      if (e) {
+      }
+    }
+  }
+}
+
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.spvasm b/test/tint/bug/chromium/343597426.wgsl.expected.spvasm
new file mode 100644
index 0000000..1673b20
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.spvasm
@@ -0,0 +1,79 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 36
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %unused_entry_point "unused_entry_point"
+               OpName %foo "foo"
+               OpName %a "a"
+               OpName %b "b"
+               OpName %c "c"
+               OpName %d "d"
+               OpName %e "e"
+               OpName %tint_return_flag "tint_return_flag"
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+       %bool = OpTypeBool
+          %5 = OpTypeFunction %void %bool %bool %bool %bool %bool
+%_ptr_Function_bool = OpTypePointer Function %bool
+         %16 = OpConstantNull %bool
+       %true = OpConstantTrue %bool
+%unused_entry_point = OpFunction %void None %1
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        %foo = OpFunction %void None %5
+          %a = OpFunctionParameter %bool
+          %b = OpFunctionParameter %bool
+          %c = OpFunctionParameter %bool
+          %d = OpFunctionParameter %bool
+          %e = OpFunctionParameter %bool
+         %13 = OpLabel
+%tint_return_flag = OpVariable %_ptr_Function_bool Function %16
+               OpSelectionMerge %17 None
+               OpBranchConditional %a %18 %17
+         %18 = OpLabel
+               OpSelectionMerge %19 None
+               OpBranchConditional %b %20 %19
+         %20 = OpLabel
+               OpStore %tint_return_flag %true
+               OpBranch %19
+         %19 = OpLabel
+         %23 = OpLoad %bool %tint_return_flag
+         %22 = OpLogicalNot %bool %23
+               OpSelectionMerge %24 None
+               OpBranchConditional %22 %25 %24
+         %25 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %c %27 %26
+         %27 = OpLabel
+               OpSelectionMerge %28 None
+               OpBranchConditional %d %29 %28
+         %29 = OpLabel
+               OpStore %tint_return_flag %true
+               OpBranch %28
+         %28 = OpLabel
+         %31 = OpLoad %bool %tint_return_flag
+         %30 = OpLogicalNot %bool %31
+               OpSelectionMerge %32 None
+               OpBranchConditional %30 %33 %32
+         %33 = OpLabel
+               OpSelectionMerge %34 None
+               OpBranchConditional %e %35 %34
+         %35 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpBranch %32
+         %32 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/bug/chromium/343597426.wgsl.expected.wgsl b/test/tint/bug/chromium/343597426.wgsl.expected.wgsl
new file mode 100644
index 0000000..0310e83
--- /dev/null
+++ b/test/tint/bug/chromium/343597426.wgsl.expected.wgsl
@@ -0,0 +1,14 @@
+fn foo(a : bool, b : bool, c : bool, d : bool, e : bool) {
+  if (a) {
+    if (b) {
+      return;
+    }
+    if (c) {
+      if (d) {
+        return;
+      }
+      if (e) {
+      }
+    }
+  }
+}