[spirv-reader] Emit break-if as needed.

This CL updates the SPIRV-Reader to emit `break-if` nodes instead of
`if-break` statements.

Bug: tint:1724
Change-Id: I8cd568f5e90a950acc5a42a470345273a5f1e6bc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/111103
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index b344246..f97d169 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -3210,8 +3210,8 @@
                 return false;
             }
             AddStatement(create<ast::ReturnStatement>(Source{}, value.expr));
-        }
             return true;
+        }
         case spv::Op::OpKill:
             // For now, assume SPIR-V OpKill has same semantics as WGSL discard.
             // TODO(dneto): https://github.com/gpuweb/gpuweb/issues/676
@@ -3266,21 +3266,58 @@
                 return Fail() << "Fallthrough not supported in WGSL";
             }
 
+            // In the case of a continuing block a `break-if` needs to be emitted for either an
+            // if-break or an if-else-break statement. This only happens inside the continue block.
+            // It's possible for a continue block to also be the loop block, so checks are needed
+            // that this is a continue construct and the header construct will cause a continuing
+            // construct to be emitted. (i.e. the header is not `continue is entire loop`.
+            bool needs_break_if = false;
+            if ((true_kind == EdgeKind::kLoopBreak || false_kind == EdgeKind::kLoopBreak) &&
+                block_info.construct && block_info.construct->kind == Construct::Kind::kContinue) {
+                auto* header = GetBlockInfo(block_info.construct->begin_id);
+
+                TINT_ASSERT(Reader, header->construct &&
+                                        header->construct->kind == Construct::Kind::kContinue);
+                if (!header->is_continue_entire_loop) {
+                    needs_break_if = true;
+                }
+            }
+
             // At this point, at most one edge is kForward or kIfBreak.
 
-            // Emit an 'if' statement to express the *other* branch as a conditional
-            // break or continue.  Either or both of these could be nullptr.
-            // (A nullptr is generated for kIfBreak, kForward, or kBack.)
-            // Also if one of the branches is an if-break out of an if-selection
-            // requiring a flow guard, then get that flow guard name too.  It will
-            // come from at most one of these two branches.
-            std::string flow_guard;
-            auto* true_branch = MakeBranchDetailed(block_info, *true_info, &flow_guard);
-            auto* false_branch = MakeBranchDetailed(block_info, *false_info, &flow_guard);
+            // If this is a continuing block and a `break` is to be emitted, then this needs to be
+            // converted to a `break-if`. This may involve inverting the condition if this was a
+            // `break-unless`.
+            if (needs_break_if) {
+                if (true_kind == EdgeKind::kLoopBreak && false_kind == EdgeKind::kLoopBreak) {
+                    // Both branches break ... ?
+                    return Fail() << "Both branches of if inside continuing break.";
+                }
 
-            AddStatement(MakeSimpleIf(cond, true_branch, false_branch));
-            if (!flow_guard.empty()) {
-                PushGuard(flow_guard, statements_stack_.Back().GetEndId());
+                if (true_kind == EdgeKind::kLoopBreak) {
+                    AddStatement(create<ast::BreakIfStatement>(Source{}, cond));
+                } else {
+                    AddStatement(create<ast::BreakIfStatement>(
+                        Source{},
+                        create<ast::UnaryOpExpression>(Source{}, ast::UnaryOp::kNot, cond)));
+                }
+                return true;
+
+            } else {
+                // Emit an 'if' statement to express the *other* branch as a conditional
+                // break or continue.  Either or both of these could be nullptr.
+                // (A nullptr is generated for kIfBreak, kForward, or kBack.)
+                // Also if one of the branches is an if-break out of an if-selection
+                // requiring a flow guard, then get that flow guard name too.  It will
+                // come from at most one of these two branches.
+                std::string flow_guard;
+                auto* true_branch = MakeBranchDetailed(block_info, *true_info, &flow_guard);
+                auto* false_branch = MakeBranchDetailed(block_info, *false_info, &flow_guard);
+
+                AddStatement(MakeSimpleIf(cond, true_branch, false_branch));
+                if (!flow_guard.empty()) {
+                    PushGuard(flow_guard, statements_stack_.Back().GetEndId());
+                }
             }
             return true;
         }
diff --git a/src/tint/reader/spirv/function_cfg_test.cc b/src/tint/reader/spirv/function_cfg_test.cc
index 88dc78e..c5800fb 100644
--- a/src/tint/reader/spirv/function_cfg_test.cc
+++ b/src/tint/reader/spirv/function_cfg_test.cc
@@ -638,9 +638,8 @@
     EXPECT_FALSE(bi99->is_continue_entire_loop);
 }
 
-TEST_F(
-    SpvParserCFGTest,
-    RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional) {  // NOLINT
+TEST_F(SpvParserCFGTest,
+       RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional) {
     auto p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -2605,7 +2604,7 @@
 }
 
 TEST_F(SpvParserCFGTest,
-       VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateContinueTarget) {  // NOLINT
+       VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateContinueTarget) {
     auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -4451,9 +4450,8 @@
     EXPECT_EQ(bi40->succ_edge[20], EdgeKind::kBack);
 }
 
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader) {  // NOLINT
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader) {
     auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -4489,9 +4487,8 @@
     EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
 }
 
-TEST_F(
-    SpvParserCFGTest,
-    ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader) {  // NOLINT
+TEST_F(SpvParserCFGTest,
+       ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader) {
     auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -6051,7 +6048,7 @@
 }
 
 TEST_F(SpvParserCFGTest,
-       FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) {  // NOLINT
+       FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) {
     auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -6762,8 +6759,7 @@
 }
 
 TEST_F(SpvParserCFGTest,
-       FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) {  // NOLINT -
-                                                                                      // line length
+       FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) {
     auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -6795,10 +6791,8 @@
                    "merge block for header block 20 (violates dominance rule)"));
 }
 
-TEST_F(
-    SpvParserCFGTest,
-    FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) {  // NOLINT - line
-                                                                                    // length
+TEST_F(SpvParserCFGTest,
+       FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) {
     auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -9474,9 +9468,7 @@
 }
 
 TEST_F(SpvParserCFGTest,
-       EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Unconditional) {  // NOLINT
-                                                                                           // - line
-                                                                                           // length
+       EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Unconditional) {
     auto p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -9519,10 +9511,46 @@
     ASSERT_EQ(expect, got);
 }
 
-TEST_F(
-    SpvParserCFGTest,
-    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional) {  // NOLINT -
-                                                                                      // line length
+TEST_F(SpvParserCFGTest,
+       EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional_BreakIf) {
+    auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_1
+     OpBranchConditional %cond %99 %20  ; exit, and backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto got = test::ToString(p->program(), ast_body);
+    auto* expect = R"(loop {
+
+  continuing {
+    var_1 = 1u;
+    break if false;
+  }
+}
+return;
+)";
+    ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest,
+       EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional) {
     auto p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -9551,10 +9579,48 @@
 
   continuing {
     var_1 = 1u;
-    if (false) {
-    } else {
-      break;
-    }
+    break if !(false);
+  }
+}
+return;
+)";
+    ASSERT_EQ(expect, got);
+}
+
+TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopBreak_FromContinueConstructTail) {
+    auto p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %50 None
+     OpBranchConditional %cond %30 %99
+     %30 = OpLabel
+     OpBranch %50
+     %50 = OpLabel
+     OpBranch %60
+     %60 = OpLabel
+     OpBranchConditional %cond %20 %99
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto got = test::ToString(p->program(), ast_body);
+    auto* expect = R"(loop {
+  if (false) {
+  } else {
+    break;
+  }
+
+  continuing {
+    break if !(false);
   }
 }
 return;
@@ -10009,9 +10075,7 @@
   var_1 = 1u;
 
   continuing {
-    if (false) {
-      break;
-    }
+    break if false;
   }
 }
 var_1 = 5u;
@@ -10052,10 +10116,7 @@
   var_1 = 1u;
 
   continuing {
-    if (false) {
-    } else {
-      break;
-    }
+    break if !(false);
   }
 }
 var_1 = 5u;
@@ -10878,9 +10939,8 @@
     ASSERT_EQ(expect, got);
 }
 
-TEST_F(
-    SpvParserCFGTest,
-    EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing) {  // NOLINT
+TEST_F(SpvParserCFGTest,
+       EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing) {
     // Like the previous tests, but with an empty continuing clause.
     auto p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
diff --git a/src/tint/reader/spirv/function_var_test.cc b/src/tint/reader/spirv/function_var_test.cc
index 5b83105..1119667 100644
--- a/src/tint/reader/spirv/function_var_test.cc
+++ b/src/tint/reader/spirv/function_var_test.cc
@@ -689,9 +689,7 @@
 
   continuing {
     x_1 = 4u;
-    if (false) {
-      break;
-    }
+    break if false;
   }
 }
 x_1 = 5u;
@@ -1610,9 +1608,7 @@
     x_999 = false;
 
     continuing {
-      if (true) {
-        break;
-      }
+      break if true;
     }
   }
 }
@@ -1694,9 +1690,7 @@
     x_999 = false;
 
     continuing {
-      if (true) {
-        break;
-      }
+      break if true;
     }
   }
 
diff --git a/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl b/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl
index e235b6e..0528f31 100644
--- a/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl
+++ b/test/tint/vk-gl-cts/graphicsfuzz/write-red-after-search/0-opt.wgsl
@@ -103,10 +103,7 @@
           continuing {
             let x_382 : f32 = x_27.injectionSwitch.x;
             let x_384 : f32 = x_27.injectionSwitch.y;
-            if ((x_382 > x_384)) {
-            } else {
-              break;
-            }
+            break if !(x_382 > x_384);
           }
         }
         continue;