Add break-if support.

This CL adds support for `break-if` to Tint.

Bug: tint:1633, tint:1451
Change-Id: I30dfd62a3e09255624ff76ebe0cdd3a3c7cf9c5f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/106420
Auto-Submit: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: dan sinclair <dsinclair@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 8f28a9a..6560b6f 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -205,6 +205,8 @@
     "ast/bool.h",
     "ast/bool_literal_expression.cc",
     "ast/bool_literal_expression.h",
+    "ast/break_if_statement.cc",
+    "ast/break_if_statement.h",
     "ast/break_statement.cc",
     "ast/break_statement.h",
     "ast/builtin_attribute.cc",
@@ -421,6 +423,7 @@
     "sem/behavior.h",
     "sem/binding_point.h",
     "sem/bool.h",
+    "sem/break_if_statement.h",
     "sem/builtin.h",
     "sem/builtin_type.h",
     "sem/call.h",
@@ -630,6 +633,8 @@
     "sem/block_statement.cc",
     "sem/bool.cc",
     "sem/bool.h",
+    "sem/break_if_statement.cc",
+    "sem/break_if_statement.h",
     "sem/builtin.cc",
     "sem/builtin.h",
     "sem/builtin_type.cc",
@@ -1016,6 +1021,7 @@
       "ast/block_statement_test.cc",
       "ast/bool_literal_expression_test.cc",
       "ast/bool_test.cc",
+      "ast/break_if_statement_test.cc",
       "ast/break_statement_test.cc",
       "ast/builtin_attribute_test.cc",
       "ast/builtin_texture_helper_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 43d8b1b..af4384c 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -73,6 +73,8 @@
   ast/bool_literal_expression.h
   ast/bool.cc
   ast/bool.h
+  ast/break_if_statement.cc
+  ast/break_if_statement.h
   ast/break_statement.cc
   ast/break_statement.h
   ast/builtin_attribute.cc
@@ -292,6 +294,8 @@
   sem/block_statement.h
   sem/bool.cc
   sem/bool.h
+  sem/break_if_statement.cc
+  sem/break_if_statement.h
   sem/builtin_type.cc
   sem/builtin_type.h
   sem/builtin.cc
@@ -708,6 +712,7 @@
     ast/block_statement_test.cc
     ast/bool_literal_expression_test.cc
     ast/bool_test.cc
+    ast/break_if_statement_test.cc
     ast/break_statement_test.cc
     ast/builtin_attribute_test.cc
     ast/builtin_texture_helper_test.cc
diff --git a/src/tint/ast/break_if_statement.cc b/src/tint/ast/break_if_statement.cc
new file mode 100644
index 0000000..a931541
--- /dev/null
+++ b/src/tint/ast/break_if_statement.cc
@@ -0,0 +1,43 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/break_if_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::BreakIfStatement);
+
+namespace tint::ast {
+
+BreakIfStatement::BreakIfStatement(ProgramID pid,
+                                   NodeID nid,
+                                   const Source& src,
+                                   const Expression* cond)
+    : Base(pid, nid, src), condition(cond) {
+    TINT_ASSERT(AST, condition);
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
+}
+
+BreakIfStatement::BreakIfStatement(BreakIfStatement&&) = default;
+
+BreakIfStatement::~BreakIfStatement() = default;
+
+const BreakIfStatement* BreakIfStatement::Clone(CloneContext* ctx) const {
+    // Clone arguments outside of create() call to have deterministic ordering
+    auto src = ctx->Clone(source);
+    auto* cond = ctx->Clone(condition);
+    return ctx->dst->create<BreakIfStatement>(src, cond);
+}
+
+}  // namespace tint::ast
diff --git a/src/tint/ast/break_if_statement.h b/src/tint/ast/break_if_statement.h
new file mode 100644
index 0000000..fe83d92
--- /dev/null
+++ b/src/tint/ast/break_if_statement.h
@@ -0,0 +1,50 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_AST_BREAK_IF_STATEMENT_H_
+#define SRC_TINT_AST_BREAK_IF_STATEMENT_H_
+
+#include <utility>
+
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/expression.h"
+
+namespace tint::ast {
+
+/// A break if statement
+class BreakIfStatement final : public Castable<BreakIfStatement, Statement> {
+  public:
+    /// Constructor
+    /// @param pid the identifier of the program that owns this node
+    /// @param nid the unique node identifier
+    /// @param src the source of this node
+    /// @param condition the if condition
+    BreakIfStatement(ProgramID pid, NodeID nid, const Source& src, const Expression* condition);
+
+    /// Move constructor
+    BreakIfStatement(BreakIfStatement&&);
+    ~BreakIfStatement() override;
+
+    /// Clones this node and all transitive child nodes using the `CloneContext` `ctx`.
+    /// @param ctx the clone context
+    /// @return the newly cloned node
+    const BreakIfStatement* Clone(CloneContext* ctx) const override;
+
+    /// The if condition or nullptr if none set
+    const Expression* const condition;
+};
+
+}  // namespace tint::ast
+
+#endif  // SRC_TINT_AST_BREAK_IF_STATEMENT_H_
diff --git a/src/tint/ast/break_if_statement_test.cc b/src/tint/ast/break_if_statement_test.cc
new file mode 100644
index 0000000..1ff37a2
--- /dev/null
+++ b/src/tint/ast/break_if_statement_test.cc
@@ -0,0 +1,58 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/break_if_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint::ast {
+namespace {
+
+using BreakIfStatementTest = TestHelper;
+
+TEST_F(BreakIfStatementTest, Creation) {
+    auto* cond = Expr("cond");
+    auto* stmt = BreakIf(Source{Source::Location{20, 2}}, cond);
+    auto src = stmt->source;
+    EXPECT_EQ(src.range.begin.line, 20u);
+    EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(BreakIfStatementTest, IsBreakIf) {
+    auto* stmt = BreakIf(Expr(true));
+    EXPECT_TRUE(stmt->Is<BreakIfStatement>());
+}
+
+TEST_F(BreakIfStatementTest, Assert_Null_Condition) {
+    EXPECT_FATAL_FAILURE(
+        {
+            ProgramBuilder b;
+            b.BreakIf(nullptr);
+        },
+        "internal compiler error");
+}
+
+TEST_F(BreakIfStatementTest, Assert_DifferentProgramID_Cond) {
+    EXPECT_FATAL_FAILURE(
+        {
+            ProgramBuilder b1;
+            ProgramBuilder b2;
+            b1.BreakIf(b2.Expr(true));
+        },
+        "internal compiler error");
+}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 986133c..25e6214 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -30,6 +30,7 @@
 #include "src/tint/ast/bitcast_expression.h"
 #include "src/tint/ast/bool.h"
 #include "src/tint/ast/bool_literal_expression.h"
+#include "src/tint/ast/break_if_statement.h"
 #include "src/tint/ast/break_statement.h"
 #include "src/tint/ast/call_expression.h"
 #include "src/tint/ast/call_statement.h"
@@ -2416,6 +2417,23 @@
     /// @returns the break statement pointer
     const ast::BreakStatement* Break() { return create<ast::BreakStatement>(); }
 
+    /// Creates a ast::BreakIfStatement with input condition
+    /// @param source the source information for the if statement
+    /// @param condition the if statement condition expression
+    /// @returns the break-if statement pointer
+    template <typename CONDITION>
+    const ast::BreakIfStatement* BreakIf(const Source& source, CONDITION&& condition) {
+        return create<ast::BreakIfStatement>(source, Expr(std::forward<CONDITION>(condition)));
+    }
+
+    /// Creates a ast::BreakIfStatement with input condition
+    /// @param condition the if statement condition expression
+    /// @returns the break-if statement pointer
+    template <typename CONDITION>
+    const ast::BreakIfStatement* BreakIf(CONDITION&& condition) {
+        return create<ast::BreakIfStatement>(Expr(std::forward<CONDITION>(condition)));
+    }
+
     /// Creates an ast::ContinueStatement
     /// @param source the source information
     /// @returns the continue statement pointer
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 3bd7703..90fb8a8 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -19,6 +19,7 @@
 #include "src/tint/ast/array.h"
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_if_statement.h"
 #include "src/tint/ast/break_statement.h"
 #include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/continue_statement.h"
@@ -2265,8 +2266,7 @@
 
 ForHeader::~ForHeader() = default;
 
-// (variable_statement | variable_updating_statement |
-// func_call_statement)?
+// (variable_statement | variable_updating_statement | func_call_statement)?
 Maybe<const ast::Statement*> ParserImpl::for_header_initializer() {
     auto call = func_call_statement();
     if (call.errored) {
@@ -2317,10 +2317,7 @@
 }
 
 // for_header
-//   : (variable_statement | variable_updating_statement | func_call_statement)?
-//   SEMICOLON
-//      expression? SEMICOLON
-//      (variable_updating_statement | func_call_statement)?
+//   : for_header_initializer? SEMICOLON expression? SEMICOLON for_header_continuing?
 Expect<std::unique_ptr<ForHeader>> ParserImpl::expect_for_header() {
     auto initializer = for_header_initializer();
     if (initializer.errored) {
@@ -2444,28 +2441,58 @@
 // break_if_statement:
 //    'break' 'if' expression semicolon
 Maybe<const ast::Statement*> ParserImpl::break_if_statement() {
-    // TODO(crbug.com/tint/1451): Add support for break-if
-    return Failure::kNoMatch;
+    auto& t1 = peek();
+    auto& t2 = peek(1);
+
+    // Match both the `break` and `if` at the same time.
+    if (!t1.Is(Token::Type::kBreak) || !t2.Is(Token::Type::kIf)) {
+        return Failure::kNoMatch;
+    }
+    next();  // Consume the peek
+    next();  // Consume the peek
+
+    auto expr = expression();
+    if (expr.errored) {
+        return Failure::kErrored;
+    }
+    if (!expr.matched) {
+        return add_error(t1, "expected expression for `break if`");
+    }
+    if (!match(Token::Type::kSemicolon)) {
+        return add_error(peek(), "expected ';' for `break if` statement");
+    }
+
+    return create<ast::BreakIfStatement>(t1.source(), expr.value);
 }
 
 // continuing_compound_statement:
 //   brace_left statement* break_if_statement? brace_right
 Maybe<const ast::BlockStatement*> ParserImpl::continuing_compound_statement() {
     return expect_brace_block("", [&]() -> Expect<ast::BlockStatement*> {
-        auto stmts = expect_statements();
-        if (stmts.errored) {
-            return Failure::kErrored;
+        StatementList stmts;
+
+        while (continue_parsing()) {
+            // Note, break-if has to parse before statements because statements includes `break`
+            auto break_if = break_if_statement();
+            if (break_if.errored) {
+                return Failure::kErrored;
+            }
+            if (break_if.matched) {
+                stmts.Push(break_if.value);
+                continue;
+            }
+
+            auto stmt = statement();
+            if (stmt.errored) {
+                return Failure::kErrored;
+            }
+            if (!stmt.matched) {
+                break;
+            }
+            stmts.Push(stmt.value);
         }
 
-        auto break_if = break_if_statement();
-        if (break_if.errored) {
-            return Failure::kErrored;
-        }
-        if (break_if.matched) {
-            stmts.value.Push(break_if.value);
-        }
-
-        return create<ast::BlockStatement>(Source{}, stmts.value);
+        return create<ast::BlockStatement>(Source{}, stmts);
     });
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
index a3e51b4..2b8033f 100644
--- a/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
@@ -110,5 +110,57 @@
     EXPECT_EQ(p->error(), "1:29: expected ';' for discard statement");
 }
 
+TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf) {
+    auto p = parser("loop { continuing { break if 1 + 2 < 5; }}");
+    auto e = p->loop_statement();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    ASSERT_EQ(e->body->statements.Length(), 0u);
+    ASSERT_EQ(e->continuing->statements.Length(), 1u);
+    EXPECT_TRUE(e->continuing->statements[0]->Is<ast::BreakIfStatement>());
+}
+
+TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf_MissingExpr) {
+    auto p = parser("loop { continuing { break if; }}");
+    auto e = p->loop_statement();
+    EXPECT_FALSE(e.matched);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_EQ(p->error(), "1:21: expected expression for `break if`");
+}
+
+TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf_InvalidExpr) {
+    auto p = parser("loop { continuing { break if switch; }}");
+    auto e = p->loop_statement();
+    EXPECT_FALSE(e.matched);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_EQ(p->error(), "1:21: expected expression for `break if`");
+}
+
+TEST_F(ParserImplTest, LoopStmt_NoContinuing_BreakIf) {
+    auto p = parser("loop { break if true; }");
+    auto e = p->loop_statement();
+    EXPECT_FALSE(e.matched);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_EQ(p->error(), "1:14: expected ';' for break statement");
+}
+
+TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf_MissingSemicolon) {
+    auto p = parser("loop { continuing { break if 1 + 2 < 5 }}");
+    auto e = p->loop_statement();
+    EXPECT_FALSE(e.matched);
+    EXPECT_TRUE(e.errored);
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(e.value, nullptr);
+    EXPECT_EQ(p->error(), "1:40: expected ';' for `break if` statement");
+}
+
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 7a63efb..b6001c1 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -25,6 +25,7 @@
 #include "src/tint/ast/atomic.h"
 #include "src/tint/ast/block_statement.h"
 #include "src/tint/ast/bool.h"
+#include "src/tint/ast/break_if_statement.h"
 #include "src/tint/ast/break_statement.h"
 #include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/compound_assignment_statement.h"
@@ -263,9 +264,8 @@
                 TINT_DEFER(scope_stack_.Pop());
                 TraverseStatements(b->statements);
             },
-            [&](const ast::CallStatement* r) {  //
-                TraverseExpression(r->expr);
-            },
+            [&](const ast::BreakIfStatement* b) { TraverseExpression(b->condition); },
+            [&](const ast::CallStatement* r) { TraverseExpression(r->expr); },
             [&](const ast::CompoundAssignmentStatement* a) {
                 TraverseExpression(a->lhs);
                 TraverseExpression(a->rhs);
@@ -292,9 +292,7 @@
                     TraverseStatement(i->else_statement);
                 }
             },
-            [&](const ast::ReturnStatement* r) {  //
-                TraverseExpression(r->value);
-            },
+            [&](const ast::ReturnStatement* r) { TraverseExpression(r->value); },
             [&](const ast::SwitchStatement* s) {
                 TraverseExpression(s->condition);
                 for (auto* c : s->body) {
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index a984c20..ae314b8 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -1260,7 +1260,7 @@
                    Block(                               //
                        Assign(V, V))),                  //
              Loop(Block(Assign(V, V)),                  //
-                  Block(Assign(V, V))),                 //
+                  Block(Assign(V, V), BreakIf(V))),     //
              Switch(V,                                  //
                     Case(CaseSelector(1_i),             //
                          Block(Assign(V, V))),          //
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index bbabef7..f4f4c98 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -57,6 +57,7 @@
 #include "src/tint/sem/abstract_int.h"
 #include "src/tint/sem/array.h"
 #include "src/tint/sem/atomic.h"
+#include "src/tint/sem/break_if_statement.h"
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/depth_multisampled_texture.h"
 #include "src/tint/sem/depth_texture.h"
@@ -1213,6 +1214,7 @@
         // Non-Compound statements
         [&](const ast::AssignmentStatement* a) { return AssignmentStatement(a); },
         [&](const ast::BreakStatement* b) { return BreakStatement(b); },
+        [&](const ast::BreakIfStatement* b) { return BreakIfStatement(b); },
         [&](const ast::CallStatement* c) { return CallStatement(c); },
         [&](const ast::CompoundAssignmentStatement* c) { return CompoundAssignmentStatement(c); },
         [&](const ast::ContinueStatement* c) { return ContinueStatement(c); },
@@ -3224,6 +3226,22 @@
     });
 }
 
+sem::Statement* Resolver::BreakIfStatement(const ast::BreakIfStatement* stmt) {
+    auto* sem = builder_->create<sem::BreakIfStatement>(stmt, current_compound_statement_,
+                                                        current_function_);
+    return StatementScope(stmt, sem, [&] {
+        auto* cond = Expression(stmt->condition);
+        if (!cond) {
+            return false;
+        }
+        sem->SetCondition(cond);
+        sem->Behaviors() = cond->Behaviors();
+        sem->Behaviors().Add(sem::Behavior::kBreak);
+
+        return validator_.BreakIfStatement(sem, current_statement_);
+    });
+}
+
 sem::Statement* Resolver::CallStatement(const ast::CallStatement* stmt) {
     auto* sem =
         builder_->create<sem::Statement>(stmt, current_compound_statement_, current_function_);
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 2ca8747a..bd7edf7 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -208,6 +208,7 @@
     sem::Statement* AssignmentStatement(const ast::AssignmentStatement*);
     sem::BlockStatement* BlockStatement(const ast::BlockStatement*);
     sem::Statement* BreakStatement(const ast::BreakStatement*);
+    sem::Statement* BreakIfStatement(const ast::BreakIfStatement*);
     sem::Statement* CallStatement(const ast::CallStatement*);
     sem::CaseStatement* CaseStatement(const ast::CaseStatement*, const sem::Type*);
     sem::Statement* CompoundAssignmentStatement(const ast::CompoundAssignmentStatement*);
diff --git a/src/tint/resolver/resolver_behavior_test.cc b/src/tint/resolver/resolver_behavior_test.cc
index cf91501..150512a 100644
--- a/src/tint/resolver/resolver_behavior_test.cc
+++ b/src/tint/resolver/resolver_behavior_test.cc
@@ -569,6 +569,16 @@
     EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
 }
 
+TEST_F(ResolverBehaviorTest, StmtLoopEmpty_BreakIf) {
+    auto* stmt = Loop(Block(), Block(BreakIf(true)));
+    WrapInFunction(stmt);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* sem = Sem().Get(stmt);
+    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
+}
+
 TEST_F(ResolverBehaviorTest, StmtReturn) {
     auto* stmt = Return();
     WrapInFunction(stmt);
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 5495356..1955c9d 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -215,7 +215,8 @@
     auto* continuing_lhs = Expr("v");
     auto* continuing_rhs = Expr(2.3_f);
 
-    auto* continuing = Block(Assign(continuing_lhs, continuing_rhs));
+    auto* break_if = BreakIf(false);
+    auto* continuing = Block(Assign(continuing_lhs, continuing_rhs), break_if);
     auto* stmt = Loop(body, continuing);
     WrapInFunction(v, stmt);
 
@@ -233,6 +234,7 @@
     EXPECT_EQ(BlockOf(body_rhs), body);
     EXPECT_EQ(BlockOf(continuing_lhs), continuing);
     EXPECT_EQ(BlockOf(continuing_rhs), continuing);
+    EXPECT_EQ(BlockOf(break_if), continuing);
 }
 
 TEST_F(ResolverTest, Stmt_Return) {
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 3b089c3..e8f1bb9 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -537,6 +537,51 @@
                 return cf;
             },
 
+            [&](const ast::BreakIfStatement* b) {
+                // This works very similar to the IfStatement uniformity below, execpt instead of
+                // processing the body, we directly inline the BreakStatement uniformity from
+                // above.
+
+                auto [_, v_cond] = ProcessExpression(cf, b->condition);
+
+                // Add a diagnostic node to capture the control flow change.
+                auto* v = current_function_->CreateNode("break_if_stmt", b);
+                v->affects_control_flow = true;
+                v->AddEdge(v_cond);
+
+                {
+                    auto* parent = sem_.Get(b)->FindFirstParent<sem::LoopStatement>();
+                    TINT_ASSERT(Resolver, current_function_->loop_switch_infos.count(parent));
+                    auto& info = current_function_->loop_switch_infos.at(parent);
+
+                    // Propagate variable values to the loop exit nodes.
+                    for (auto* var : current_function_->local_var_decls) {
+                        // Skip variables that were declared inside this loop.
+                        if (auto* lv = var->As<sem::LocalVariable>();
+                            lv && lv->Statement()->FindFirstParent(
+                                      [&](auto* s) { return s == parent; })) {
+                            continue;
+                        }
+
+                        // Add an edge from the variable exit node to its value at this point.
+                        auto* exit_node = utils::GetOrCreate(info.var_exit_nodes, var, [&]() {
+                            auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
+                            return CreateNode(name + "_value_" + info.type + "_exit");
+                        });
+
+                        exit_node->AddEdge(current_function_->variables.Get(var));
+                    }
+                }
+
+                auto* sem_break_if = sem_.Get(b);
+                if (sem_break_if->Behaviors() != sem::Behaviors{sem::Behavior::kNext}) {
+                    auto* cf_end = CreateNode("break_if_CFend");
+                    cf_end->AddEdge(v);
+                    return cf_end;
+                }
+                return cf;
+            },
+
             [&](const ast::CallStatement* c) {
                 auto [cf1, _] = ProcessCall(cf, c->expr);
                 return cf1;
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index 6ba3410..249db64 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -1125,9 +1125,7 @@
     workgroupBarrier();
     continuing {
       i = i + 1;
-      if (i == n) {
-        break;
-      }
+      break if (i == n);
     }
   }
 }
@@ -1146,9 +1144,7 @@
     workgroupBarrier();
     continuing {
       i = i + 1;
-      if (i == n) {
-        break;
-      }
+      break if (i == n);
     }
   }
 }
@@ -1161,12 +1157,12 @@
     ^^^^^^^^^^^^^^^^
 
 test:10:7 note: control flow depends on non-uniform value
-      if (i == n) {
-      ^^
+      break if (i == n);
+      ^^^^^
 
-test:10:16 note: reading from read_write storage buffer 'n' may result in a non-uniform value
-      if (i == n) {
-               ^
+test:10:22 note: reading from read_write storage buffer 'n' may result in a non-uniform value
+      break if (i == n);
+                     ^
 )");
 }
 
@@ -1180,9 +1176,7 @@
     continuing {
       workgroupBarrier();
       i = i + 1;
-      if (i == n) {
-        break;
-      }
+      break if (i == n);
     }
   }
 }
@@ -1201,9 +1195,7 @@
     continuing {
       workgroupBarrier();
       i = i + 1;
-      if (i == n) {
-        break;
-      }
+      break if (i == n);
     }
   }
 }
@@ -1216,12 +1208,12 @@
       ^^^^^^^^^^^^^^^^
 
 test:10:7 note: control flow depends on non-uniform value
-      if (i == n) {
-      ^^
+      break if (i == n);
+      ^^^^^
 
-test:10:16 note: reading from read_write storage buffer 'n' may result in a non-uniform value
-      if (i == n) {
-               ^
+test:10:22 note: reading from read_write storage buffer 'n' may result in a non-uniform value
+      break if (i == n);
+                     ^
 )");
 }
 
@@ -1249,9 +1241,7 @@
     continuing {
       // Pretend that this isn't an infinite loop, in case the interrupt is a
       // continue statement.
-      if (false) {
-        break;
-      }
+      break if (false);
     }
   }
 }
@@ -1597,9 +1587,7 @@
       if (v == 0) {
         workgroupBarrier();
       }
-      if (true) {
-        break;
-      }
+      break if (true);
     }
   }
 }
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
index d7fddb4..c86a040 100644
--- a/src/tint/resolver/validation_test.cc
+++ b/src/tint/resolver/validation_test.cc
@@ -870,6 +870,75 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
+TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf) {
+    // loop  {
+    //     continuing {
+    //         break if true;
+    //     }
+    // }
+
+    auto* body = Block();
+    auto* continuing = Block(BreakIf(true));
+    auto* loop_stmt = Loop(body, continuing);
+    WrapInFunction(loop_stmt);
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf_Not_Last) {
+    // loop  {
+    //     var z : i32;
+    //     continuing {
+    //         break if true;
+    //         z = 2i;
+    //     }
+    // }
+
+    auto* body = Block(Decl(Var("z", ty.i32())));
+    auto* continuing =
+        Block(Source{{10, 9}}, BreakIf(Source{{12, 23}}, true), Assign(Expr("z"), 2_i));
+    auto* loop_stmt = Loop(body, continuing);
+    WrapInFunction(loop_stmt);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:23 error: break-if must be last statement in a continuing block
+10:9 note: see continuing block here)");
+}
+
+TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf_Duplicate) {
+    // loop  {
+    //     continuing {
+    //         break if true;
+    //         break if false;
+    //     }
+    // }
+
+    auto* body = Block();
+    auto* continuing = Block(Source{{10, 9}}, BreakIf(Source{{12, 23}}, true), BreakIf(false));
+    auto* loop_stmt = Loop(body, continuing);
+    WrapInFunction(loop_stmt);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:23 error: break-if must be last statement in a continuing block
+10:9 note: see continuing block here)");
+}
+
+TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf_NonBool) {
+    // loop  {
+    //     continuing {
+    //         break if 1i;
+    //     }
+    // }
+
+    auto* body = Block();
+    auto* continuing = Block(BreakIf(Expr(Source{{12, 23}}, 1_i)));
+    auto* loop_stmt = Loop(body, continuing);
+    WrapInFunction(loop_stmt);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:23 error: break-if statement condition must be bool, got i32)");
+}
+
 TEST_F(ResolverTest, Stmt_ForLoop_ReturnInContinuing_Direct) {
     // for(;; return) {
     //   break;
@@ -1055,6 +1124,9 @@
                                                   // }
     WrapInFunction(Loop(Block(), cont));
     EXPECT_TRUE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.");
 }
 
 TEST_F(ResolverValidationTest, Stmt_BreakInIfElseInContinuing) {
@@ -1066,6 +1138,9 @@
                                              // }
     WrapInFunction(Loop(Block(), cont));
     EXPECT_TRUE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.");
 }
 
 TEST_F(ResolverValidationTest, Stmt_BreakInContinuing) {
@@ -1075,6 +1150,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
@@ -1092,6 +1169,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
@@ -1109,6 +1188,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
@@ -1126,6 +1207,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
@@ -1142,6 +1225,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
@@ -1159,6 +1244,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
@@ -1176,6 +1263,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
@@ -1192,6 +1281,8 @@
     WrapInFunction(Loop(Block(), cont));
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
+              "12:34 warning: use of deprecated language feature: `break` must not be used to exit "
+              "from a continuing block. Use break-if instead.\n"
               "12:34 error: break statement in a continuing block must be the single "
               "statement of an if statement's true or false block, and that if "
               "statement must be the last statement of the continuing block\n"
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 5775ba7..2355b6a 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -51,6 +51,7 @@
 #include "src/tint/sem/abstract_numeric.h"
 #include "src/tint/sem/array.h"
 #include "src/tint/sem/atomic.h"
+#include "src/tint/sem/break_if_statement.h"
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/depth_multisampled_texture.h"
 #include "src/tint/sem/depth_texture.h"
@@ -1462,6 +1463,11 @@
         return false;
     }
     if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true, current_statement)) {
+        AddWarning(
+            "use of deprecated language feature: `break` must not be used to exit from "
+            "a continuing block. Use break-if instead.",
+            stmt->Declaration()->source);
+
         auto fail = [&](const char* note_msg, const Source& note_src) {
             constexpr const char* kErrorMsg =
                 "break statement in a continuing block must be the single statement of an if "
@@ -1632,6 +1638,35 @@
     return true;
 }
 
+bool Validator::BreakIfStatement(const sem::BreakIfStatement* stmt,
+                                 sem::Statement* current_statement) const {
+    auto* cond_ty = stmt->Condition()->Type()->UnwrapRef();
+    if (!cond_ty->Is<sem::Bool>()) {
+        AddError("break-if statement condition must be bool, got " + sem_.TypeNameOf(cond_ty),
+                 stmt->Condition()->Declaration()->source);
+        return false;
+    }
+
+    for (const auto* s = current_statement; s != nullptr; s = s->Parent()) {
+        if (s->Is<sem::LoopStatement>()) {
+            break;
+        }
+        if (s->Is<sem::LoopContinuingBlockStatement>()) {
+            if (s->Declaration()->As<ast::BlockStatement>()->statements.Back() !=
+                stmt->Declaration()) {
+                AddError("break-if must be last statement in a continuing block",
+                         stmt->Declaration()->source);
+                AddNote("see continuing block here", s->Declaration()->source);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    AddError("break-if must in a continuing block", stmt->Declaration()->source);
+    return false;
+}
+
 bool Validator::IfStatement(const sem::IfStatement* stmt) const {
     auto* cond_ty = stmt->Condition()->Type()->UnwrapRef();
     if (!cond_ty->Is<sem::Bool>()) {
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 34d4e98..efc3842 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -51,6 +51,7 @@
 class Array;
 class Atomic;
 class BlockStatement;
+class BreakIfStatement;
 class Builtin;
 class Call;
 class CaseStatement;
@@ -256,6 +257,13 @@
         const std::unordered_map<OverrideId, const sem::Variable*>& override_id,
         const std::unordered_map<const sem::Type*, const Source&>& atomic_composite_info) const;
 
+    /// Validates a break-if statement
+    /// @param stmt the statement to validate
+    /// @param current_statement the current statement being resolved
+    /// @returns true on success, false otherwise
+    bool BreakIfStatement(const sem::BreakIfStatement* stmt,
+                          sem::Statement* current_statement) const;
+
     /// Validates an if statement
     /// @param stmt the statement to validate
     /// @returns true on success, false otherwise
diff --git a/src/tint/sem/break_if_statement.cc b/src/tint/sem/break_if_statement.cc
new file mode 100644
index 0000000..c9fc014
--- /dev/null
+++ b/src/tint/sem/break_if_statement.cc
@@ -0,0 +1,34 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/sem/break_if_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::BreakIfStatement);
+
+namespace tint::sem {
+
+BreakIfStatement::BreakIfStatement(const ast::BreakIfStatement* declaration,
+                                   const CompoundStatement* parent,
+                                   const sem::Function* function)
+    : Base(declaration, parent, function) {}
+
+BreakIfStatement::~BreakIfStatement() = default;
+
+const ast::BreakIfStatement* BreakIfStatement::Declaration() const {
+    return static_cast<const ast::BreakIfStatement*>(Base::Declaration());
+}
+
+}  // namespace tint::sem
diff --git a/src/tint/sem/break_if_statement.h b/src/tint/sem/break_if_statement.h
new file mode 100644
index 0000000..bb1e3e5
--- /dev/null
+++ b/src/tint/sem/break_if_statement.h
@@ -0,0 +1,60 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_BREAK_IF_STATEMENT_H_
+#define SRC_TINT_SEM_BREAK_IF_STATEMENT_H_
+
+#include "src/tint/sem/statement.h"
+
+// Forward declarations
+namespace tint::ast {
+class BreakIfStatement;
+}  // namespace tint::ast
+namespace tint::sem {
+class Expression;
+}  // namespace tint::sem
+
+namespace tint::sem {
+
+/// Holds semantic information about a break-if statement
+class BreakIfStatement final : public Castable<BreakIfStatement, CompoundStatement> {
+  public:
+    /// Constructor
+    /// @param declaration the AST node for this break-if statement
+    /// @param parent the owning statement
+    /// @param function the owning function
+    BreakIfStatement(const ast::BreakIfStatement* declaration,
+                     const CompoundStatement* parent,
+                     const sem::Function* function);
+
+    /// Destructor
+    ~BreakIfStatement() override;
+
+    /// @returns the AST node
+    const ast::BreakIfStatement* Declaration() const;
+
+    /// @returns the break-if-statement condition expression
+    const Expression* Condition() const { return condition_; }
+
+    /// Sets the break-if-statement condition expression
+    /// @param condition the break-if condition expression
+    void SetCondition(const Expression* condition) { condition_ = condition; }
+
+  private:
+    const Expression* condition_ = nullptr;
+};
+
+}  // namespace tint::sem
+
+#endif  // SRC_TINT_SEM_BREAK_IF_STATEMENT_H_
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index ab28bf7..fc9de73 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -712,6 +712,16 @@
     return true;
 }
 
+bool GeneratorImpl::EmitBreakIf(const ast::BreakIfStatement* b) {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, b->condition)) {
+        return false;
+    }
+    out << ") { break; }";
+    return true;
+}
+
 bool GeneratorImpl::EmitCall(std::ostream& out, const ast::CallExpression* expr) {
     auto* call = builder_.Sem().Get<sem::Call>(expr);
     auto* target = call->Target();
@@ -2616,6 +2626,7 @@
         [&](const ast::AssignmentStatement* a) { return EmitAssign(a); },
         [&](const ast::BlockStatement* b) { return EmitBlock(b); },
         [&](const ast::BreakStatement* b) { return EmitBreak(b); },
+        [&](const ast::BreakIfStatement* b) { return EmitBreakIf(b); },
         [&](const ast::CallStatement* c) {
             auto out = line();
             if (!EmitCall(out, c->expr)) {
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
index 2cd5d5d..889b406 100644
--- a/src/tint/writer/glsl/generator_impl.h
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -139,6 +139,10 @@
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted successfully
     bool EmitBreak(const ast::BreakStatement* stmt);
+    /// Handles a break-if statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitBreakIf(const ast::BreakIfStatement* stmt);
     /// Handles generating a call expression
     /// @param out the output of the expression stream
     /// @param expr the call expression
diff --git a/src/tint/writer/glsl/generator_impl_loop_test.cc b/src/tint/writer/glsl/generator_impl_loop_test.cc
index e638eb3..7aa94be 100644
--- a/src/tint/writer/glsl/generator_impl_loop_test.cc
+++ b/src/tint/writer/glsl/generator_impl_loop_test.cc
@@ -65,6 +65,31 @@
 )");
 }
 
+TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithContinuing_BreakIf) {
+    Func("a_statement", {}, ty.void_(), {});
+
+    auto* body = Block(create<ast::DiscardStatement>());
+    auto* continuing = Block(CallStmt(Call("a_statement")), BreakIf(true));
+    auto* l = Loop(body, continuing);
+
+    Func("F", utils::Empty, ty.void_(), utils::Vector{l},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
+
+    GeneratorImpl& gen = Build();
+
+    gen.increment_indent();
+
+    ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+    EXPECT_EQ(gen.result(), R"(  while (true) {
+    discard;
+    {
+      a_statement();
+      if (true) { break; }
+    }
+  }
+)");
+}
+
 TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) {
     Func("a_statement", {}, ty.void_(), {});
 
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 1dc3b10..e42274d 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -942,6 +942,16 @@
     return true;
 }
 
+bool GeneratorImpl::EmitBreakIf(const ast::BreakIfStatement* b) {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, b->condition)) {
+        return false;
+    }
+    out << ") { break; }";
+    return true;
+}
+
 bool GeneratorImpl::EmitCall(std::ostream& out, const ast::CallExpression* expr) {
     auto* call = builder_.Sem().Get<sem::Call>(expr);
     auto* target = call->Target();
@@ -3591,6 +3601,9 @@
         [&](const ast::BreakStatement* b) {  //
             return EmitBreak(b);
         },
+        [&](const ast::BreakIfStatement* b) {  //
+            return EmitBreakIf(b);
+        },
         [&](const ast::CallStatement* c) {  //
             auto out = line();
             if (!EmitCall(out, c->expr)) {
diff --git a/src/tint/writer/hlsl/generator_impl.h b/src/tint/writer/hlsl/generator_impl.h
index faef168..2742f00 100644
--- a/src/tint/writer/hlsl/generator_impl.h
+++ b/src/tint/writer/hlsl/generator_impl.h
@@ -125,6 +125,10 @@
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted successfully
     bool EmitBreak(const ast::BreakStatement* stmt);
+    /// Handles a break-if statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitBreakIf(const ast::BreakIfStatement* stmt);
     /// Handles generating a call expression
     /// @param out the output of the expression stream
     /// @param expr the call expression
diff --git a/src/tint/writer/hlsl/generator_impl_loop_test.cc b/src/tint/writer/hlsl/generator_impl_loop_test.cc
index 238fdd2..3d8219b 100644
--- a/src/tint/writer/hlsl/generator_impl_loop_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_loop_test.cc
@@ -65,6 +65,31 @@
 )");
 }
 
+TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopWithContinuing_BreakIf) {
+    Func("a_statement", {}, ty.void_(), {});
+
+    auto* body = Block(create<ast::DiscardStatement>());
+    auto* continuing = Block(CallStmt(Call("a_statement")), BreakIf(true));
+    auto* l = Loop(body, continuing);
+
+    Func("F", utils::Empty, ty.void_(), utils::Vector{l},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
+
+    GeneratorImpl& gen = Build();
+
+    gen.increment_indent();
+
+    ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+    EXPECT_EQ(gen.result(), R"(  while (true) {
+    discard;
+    {
+      a_statement();
+      if (true) { break; }
+    }
+  }
+)");
+}
+
 TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopNestedWithContinuing) {
     Func("a_statement", {}, ty.void_(), {});
 
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index c7c7b63..28478b8 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -660,6 +660,16 @@
     return true;
 }
 
+bool GeneratorImpl::EmitBreakIf(const ast::BreakIfStatement* b) {
+    auto out = line();
+    out << "if (";
+    if (!EmitExpression(out, b->condition)) {
+        return false;
+    }
+    out << ") { break; }";
+    return true;
+}
+
 bool GeneratorImpl::EmitCall(std::ostream& out, const ast::CallExpression* expr) {
     auto* call = program_->Sem().Get<sem::Call>(expr);
     auto* target = call->Target();
@@ -2433,6 +2443,9 @@
         [&](const ast::BreakStatement* b) {  //
             return EmitBreak(b);
         },
+        [&](const ast::BreakIfStatement* b) {  //
+            return EmitBreakIf(b);
+        },
         [&](const ast::CallStatement* c) {  //
             auto out = line();
             if (!EmitCall(out, c->expr)) {  //
diff --git a/src/tint/writer/msl/generator_impl.h b/src/tint/writer/msl/generator_impl.h
index 5e16fbe..188bfea 100644
--- a/src/tint/writer/msl/generator_impl.h
+++ b/src/tint/writer/msl/generator_impl.h
@@ -129,6 +129,10 @@
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted successfully
     bool EmitBreak(const ast::BreakStatement* stmt);
+    /// Handles a break-if statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitBreakIf(const ast::BreakIfStatement* stmt);
     /// Handles generating a call expression
     /// @param out the output of the expression stream
     /// @param expr the call expression
diff --git a/src/tint/writer/msl/generator_impl_loop_test.cc b/src/tint/writer/msl/generator_impl_loop_test.cc
index 274ee941..1dd5430 100644
--- a/src/tint/writer/msl/generator_impl_loop_test.cc
+++ b/src/tint/writer/msl/generator_impl_loop_test.cc
@@ -65,6 +65,31 @@
 )");
 }
 
+TEST_F(MslGeneratorImplTest, Emit_LoopWithContinuing_BreakIf) {
+    Func("a_statement", {}, ty.void_(), {});
+
+    auto* body = Block(create<ast::DiscardStatement>());
+    auto* continuing = Block(CallStmt(Call("a_statement")), BreakIf(true));
+    auto* l = Loop(body, continuing);
+
+    Func("F", utils::Empty, ty.void_(), utils::Vector{l},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
+
+    GeneratorImpl& gen = Build();
+
+    gen.increment_indent();
+
+    ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+    EXPECT_EQ(gen.result(), R"(  while (true) {
+    discard_fragment();
+    {
+      a_statement();
+      if (true) { break; }
+    }
+  }
+)");
+}
+
 TEST_F(MslGeneratorImplTest, Emit_LoopNestedWithContinuing) {
     Func("a_statement", {}, ty.void_(), utils::Empty);
 
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index c93a448..91b16b4 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -455,6 +455,19 @@
     return true;
 }
 
+bool Builder::GenerateBreakIfStatement(const ast::BreakIfStatement* stmt) {
+    TINT_ASSERT(Writer, !backedge_stack_.empty());
+    const auto cond_id = GenerateExpressionWithLoadIfNeeded(stmt->condition);
+    if (!cond_id) {
+        return false;
+    }
+    const ContinuingInfo& ci = continuing_stack_.back();
+    backedge_stack_.back() =
+        Backedge(spv::Op::OpBranchConditional,
+                 {Operand(cond_id), Operand(ci.break_target_id), Operand(ci.loop_header_id)});
+    return true;
+}
+
 bool Builder::GenerateContinueStatement(const ast::ContinueStatement*) {
     if (continue_stack_.empty()) {
         error_ = "Attempted to continue without a continue block";
@@ -3400,6 +3413,8 @@
         //  continuing { ...
         //    if (cond) {} else {break;}
         //  }
+        //
+        // TODO(crbug.com/tint/1451): Remove this when the if break construct is made an error.
         auto is_just_a_break = [](const ast::BlockStatement* block) {
             return block && (block->statements.Length() == 1) &&
                    block->Last()->Is<ast::BreakStatement>();
@@ -3643,6 +3658,7 @@
         stmt, [&](const ast::AssignmentStatement* a) { return GenerateAssignStatement(a); },
         [&](const ast::BlockStatement* b) { return GenerateBlockStatement(b); },
         [&](const ast::BreakStatement* b) { return GenerateBreakStatement(b); },
+        [&](const ast::BreakIfStatement* b) { return GenerateBreakIfStatement(b); },
         [&](const ast::CallStatement* c) { return GenerateCallExpression(c->expr) != 0; },
         [&](const ast::ContinueStatement* c) { return GenerateContinueStatement(c); },
         [&](const ast::DiscardStatement* d) { return GenerateDiscardStatement(d); },
@@ -3659,7 +3675,7 @@
             return true;  // Not emitted
         },
         [&](Default) {
-            error_ = "Unknown statement: " + std::string(stmt->TypeInfo().name);
+            error_ = "unknown statement type: " + std::string(stmt->TypeInfo().name);
             return false;
         });
 }
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
index 9e633f1..1e412fa 100644
--- a/src/tint/writer/spirv/builder.h
+++ b/src/tint/writer/spirv/builder.h
@@ -248,6 +248,10 @@
     /// @param stmt the statement to generate
     /// @returns true if the statement was successfully generated
     bool GenerateBreakStatement(const ast::BreakStatement* stmt);
+    /// Generates a break-if statement
+    /// @param stmt the statement to generate
+    /// @returns true if the statement was successfully generated
+    bool GenerateBreakIfStatement(const ast::BreakIfStatement* stmt);
     /// Generates a continue statement
     /// @param stmt the statement to generate
     /// @returns true if the statement was successfully generated
diff --git a/src/tint/writer/spirv/builder_loop_test.cc b/src/tint/writer/spirv/builder_loop_test.cc
index 3ab27ef..e27cac4 100644
--- a/src/tint/writer/spirv/builder_loop_test.cc
+++ b/src/tint/writer/spirv/builder_loop_test.cc
@@ -234,12 +234,11 @@
 TEST_F(BuilderTest, Loop_WithContinuing_BreakIf) {
     // loop {
     //   continuing {
-    //     if (true) { break; }
+    //     break if (true);
     //   }
     // }
 
-    auto* if_stmt = If(Expr(true), Block(Break()));
-    auto* continuing = Block(if_stmt);
+    auto* continuing = Block(BreakIf(true));
     auto* loop = Loop(Block(), continuing);
     WrapInFunction(loop);
 
@@ -267,11 +266,10 @@
 TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless) {
     // loop {
     //   continuing {
-    //     if (true) {} else { break; }
+    //     break if (false);
     //   }
     // }
-    auto* if_stmt = If(Expr(true), Block(), Else(Block(Break())));
-    auto* continuing = Block(if_stmt);
+    auto* continuing = Block(BreakIf(false));
     auto* loop = Loop(Block(), continuing);
     WrapInFunction(loop);
 
@@ -281,7 +279,7 @@
 
     EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
     EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
-%6 = OpConstantTrue %5
+%6 = OpConstantNull %5
 )");
     EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
               R"(OpBranch %1
@@ -291,7 +289,7 @@
 %4 = OpLabel
 OpBranch %3
 %3 = OpLabel
-OpBranchConditional %6 %1 %2
+OpBranchConditional %6 %2 %1
 %2 = OpLabel
 )");
 }
@@ -300,13 +298,12 @@
     // loop {
     //   continuing {
     //     var cond = true;
-    //     if (cond) { break; }
+    //     break if (cond);
     //   }
     // }
 
     auto* cond_var = Decl(Var("cond", Expr(true)));
-    auto* if_stmt = If(Expr("cond"), Block(Break()));
-    auto* continuing = Block(cond_var, if_stmt);
+    auto* continuing = Block(cond_var, BreakIf("cond"));
     auto* loop = Loop(Block(), continuing);
     WrapInFunction(loop);
 
@@ -379,19 +376,17 @@
     //   continuing {
     //     loop {
     //       continuing {
-    //         if (true) { break; }
+    //         break if (true);
     //       }
     //     }
-    //     if (true) { break; }
+    //     break if (true);
     //   }
     // }
 
-    auto* inner_if_stmt = If(Expr(true), Block(Break()));
-    auto* inner_continuing = Block(inner_if_stmt);
+    auto* inner_continuing = Block(BreakIf(true));
     auto* inner_loop = Loop(Block(), inner_continuing);
 
-    auto* outer_if_stmt = If(Expr(true), Block(Break()));
-    auto* outer_continuing = Block(inner_loop, outer_if_stmt);
+    auto* outer_continuing = Block(inner_loop, BreakIf(true));
     auto* outer_loop = Loop(Block(), outer_continuing);
 
     WrapInFunction(outer_loop);
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index c62fbf5..bf7ebd7 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -958,6 +958,7 @@
         [&](const ast::AssignmentStatement* a) { return EmitAssign(a); },
         [&](const ast::BlockStatement* b) { return EmitBlock(b); },
         [&](const ast::BreakStatement* b) { return EmitBreak(b); },
+        [&](const ast::BreakIfStatement* b) { return EmitBreakIf(b); },
         [&](const ast::CallStatement* c) {
             auto out = line();
             if (!EmitCall(out, c->expr)) {
@@ -1023,6 +1024,17 @@
     return true;
 }
 
+bool GeneratorImpl::EmitBreakIf(const ast::BreakIfStatement* b) {
+    auto out = line();
+
+    out << "break if ";
+    if (!EmitExpression(out, b->condition)) {
+        return false;
+    }
+    out << ";";
+    return true;
+}
+
 bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
     if (stmt->selectors.Length() == 1 && stmt->ContainsDefault()) {
         line() << "default: {";
diff --git a/src/tint/writer/wgsl/generator_impl.h b/src/tint/writer/wgsl/generator_impl.h
index f4fc467..2b7f1c9 100644
--- a/src/tint/writer/wgsl/generator_impl.h
+++ b/src/tint/writer/wgsl/generator_impl.h
@@ -20,6 +20,7 @@
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/binary_expression.h"
 #include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_if_statement.h"
 #include "src/tint/ast/break_statement.h"
 #include "src/tint/ast/compound_assignment_statement.h"
 #include "src/tint/ast/continue_statement.h"
@@ -92,6 +93,10 @@
     /// @param stmt the statement to emit
     /// @returns true if the statement was emitted successfully
     bool EmitBreak(const ast::BreakStatement* stmt);
+    /// Handles a break-if statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitBreakIf(const ast::BreakIfStatement* stmt);
     /// Handles generating a call expression
     /// @param out the output of the expression stream
     /// @param expr the call expression
diff --git a/src/tint/writer/wgsl/generator_impl_loop_test.cc b/src/tint/writer/wgsl/generator_impl_loop_test.cc
index bf0eef6..48510bb 100644
--- a/src/tint/writer/wgsl/generator_impl_loop_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_loop_test.cc
@@ -65,6 +65,32 @@
 )");
 }
 
+TEST_F(WgslGeneratorImplTest, Emit_LoopWithContinuing_BreakIf) {
+    Func("a_statement", {}, ty.void_(), {});
+
+    auto* body = Block(create<ast::DiscardStatement>());
+    auto* continuing = Block(CallStmt(Call("a_statement")), BreakIf(true));
+    auto* l = Loop(body, continuing);
+
+    Func("F", utils::Empty, ty.void_(), utils::Vector{l},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
+
+    GeneratorImpl& gen = Build();
+
+    gen.increment_indent();
+
+    ASSERT_TRUE(gen.EmitStatement(l)) << gen.error();
+    EXPECT_EQ(gen.result(), R"(  loop {
+    discard;
+
+    continuing {
+      a_statement();
+      break if true;
+    }
+  }
+)");
+}
+
 TEST_F(WgslGeneratorImplTest, Emit_ForLoopWithMultiStmtInit) {
     // var<workgroup> a : atomic<i32>;
     // for({ignore(1i); ignore(2i);}; ; ) {
diff --git a/test/tint/bug/tint/1064.wgsl.expected.dxc.hlsl b/test/tint/bug/tint/1064.wgsl.expected.dxc.hlsl
index 56911a4..a739de0 100644
--- a/test/tint/bug/tint/1064.wgsl.expected.dxc.hlsl
+++ b/test/tint/bug/tint/1064.wgsl.expected.dxc.hlsl
@@ -1,3 +1,7 @@
+bug/tint/1064.wgsl:12:9 warning: use of deprecated language feature: `break` must not be used to exit from a continuing block. Use break-if instead.
+        break;
+        ^^^^^
+
 void main() {
   while (true) {
     if (false) {
diff --git a/test/tint/bug/tint/1064.wgsl.expected.fxc.hlsl b/test/tint/bug/tint/1064.wgsl.expected.fxc.hlsl
index 56911a4..a739de0 100644
--- a/test/tint/bug/tint/1064.wgsl.expected.fxc.hlsl
+++ b/test/tint/bug/tint/1064.wgsl.expected.fxc.hlsl
@@ -1,3 +1,7 @@
+bug/tint/1064.wgsl:12:9 warning: use of deprecated language feature: `break` must not be used to exit from a continuing block. Use break-if instead.
+        break;
+        ^^^^^
+
 void main() {
   while (true) {
     if (false) {
diff --git a/test/tint/bug/tint/1064.wgsl.expected.glsl b/test/tint/bug/tint/1064.wgsl.expected.glsl
index 290d580..2a91c86 100644
--- a/test/tint/bug/tint/1064.wgsl.expected.glsl
+++ b/test/tint/bug/tint/1064.wgsl.expected.glsl
@@ -1,3 +1,7 @@
+bug/tint/1064.wgsl:12:9 warning: use of deprecated language feature: `break` must not be used to exit from a continuing block. Use break-if instead.
+        break;
+        ^^^^^
+
 #version 310 es
 precision mediump float;
 
diff --git a/test/tint/bug/tint/1064.wgsl.expected.msl b/test/tint/bug/tint/1064.wgsl.expected.msl
index f9c26cc..212a90d 100644
--- a/test/tint/bug/tint/1064.wgsl.expected.msl
+++ b/test/tint/bug/tint/1064.wgsl.expected.msl
@@ -1,3 +1,7 @@
+bug/tint/1064.wgsl:12:9 warning: use of deprecated language feature: `break` must not be used to exit from a continuing block. Use break-if instead.
+        break;
+        ^^^^^
+
 #include <metal_stdlib>
 
 using namespace metal;
diff --git a/test/tint/bug/tint/1064.wgsl.expected.spvasm b/test/tint/bug/tint/1064.wgsl.expected.spvasm
index 139a110..1cb1a4e 100644
--- a/test/tint/bug/tint/1064.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/1064.wgsl.expected.spvasm
@@ -1,3 +1,7 @@
+bug/tint/1064.wgsl:12:9 warning: use of deprecated language feature: `break` must not be used to exit from a continuing block. Use break-if instead.
+        break;
+        ^^^^^
+
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 0
diff --git a/test/tint/bug/tint/1064.wgsl.expected.wgsl b/test/tint/bug/tint/1064.wgsl.expected.wgsl
index 2fe72ba..145f05d 100644
--- a/test/tint/bug/tint/1064.wgsl.expected.wgsl
+++ b/test/tint/bug/tint/1064.wgsl.expected.wgsl
@@ -1,3 +1,7 @@
+bug/tint/1064.wgsl:12:9 warning: use of deprecated language feature: `break` must not be used to exit from a continuing block. Use break-if instead.
+        break;
+        ^^^^^
+
 @fragment
 fn main() {
   loop {
diff --git a/test/tint/loops/loop_with_break_if.wgsl b/test/tint/loops/loop_with_break_if.wgsl
new file mode 100644
index 0000000..d97cc69
--- /dev/null
+++ b/test/tint/loops/loop_with_break_if.wgsl
@@ -0,0 +1,14 @@
+fn f() -> i32 {
+    var i : i32;
+    loop {
+        if (i > 4) {
+            return i;
+        }
+        continuing {
+            i = i + 1;
+
+            break if i == 4;
+        }
+    }
+    return i;
+}
diff --git a/test/tint/loops/loop_with_break_if.wgsl.expected.dxc.hlsl b/test/tint/loops/loop_with_break_if.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..bc0e11a
--- /dev/null
+++ b/test/tint/loops/loop_with_break_if.wgsl.expected.dxc.hlsl
@@ -0,0 +1,18 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+int f() {
+  int i = 0;
+  while (true) {
+    if ((i > 4)) {
+      return i;
+    }
+    {
+      i = (i + 1);
+      if ((i == 4)) { break; }
+    }
+  }
+  return i;
+}
diff --git a/test/tint/loops/loop_with_break_if.wgsl.expected.fxc.hlsl b/test/tint/loops/loop_with_break_if.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..bc0e11a
--- /dev/null
+++ b/test/tint/loops/loop_with_break_if.wgsl.expected.fxc.hlsl
@@ -0,0 +1,18 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+int f() {
+  int i = 0;
+  while (true) {
+    if ((i > 4)) {
+      return i;
+    }
+    {
+      i = (i + 1);
+      if ((i == 4)) { break; }
+    }
+  }
+  return i;
+}
diff --git a/test/tint/loops/loop_with_break_if.wgsl.expected.glsl b/test/tint/loops/loop_with_break_if.wgsl.expected.glsl
new file mode 100644
index 0000000..f68283e
--- /dev/null
+++ b/test/tint/loops/loop_with_break_if.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void unused_entry_point() {
+  return;
+}
+int f() {
+  int i = 0;
+  while (true) {
+    if ((i > 4)) {
+      return i;
+    }
+    {
+      i = (i + 1);
+      if ((i == 4)) { break; }
+    }
+  }
+  return i;
+}
+
diff --git a/test/tint/loops/loop_with_break_if.wgsl.expected.msl b/test/tint/loops/loop_with_break_if.wgsl.expected.msl
new file mode 100644
index 0000000..97788fa
--- /dev/null
+++ b/test/tint/loops/loop_with_break_if.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+int f() {
+  int i = 0;
+  while (true) {
+    if ((i > 4)) {
+      return i;
+    }
+    {
+      i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)));
+      if ((i == 4)) { break; }
+    }
+  }
+  return i;
+}
+
diff --git a/test/tint/loops/loop_with_break_if.wgsl.expected.spvasm b/test/tint/loops/loop_with_break_if.wgsl.expected.spvasm
new file mode 100644
index 0000000..a16a16e
--- /dev/null
+++ b/test/tint/loops/loop_with_break_if.wgsl.expected.spvasm
@@ -0,0 +1,53 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; 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 %f "f"
+               OpName %i "i"
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+          %5 = OpTypeFunction %int
+%_ptr_Function_int = OpTypePointer Function %int
+         %11 = OpConstantNull %int
+      %int_4 = OpConstant %int 4
+       %bool = OpTypeBool
+      %int_1 = OpConstant %int 1
+%unused_entry_point = OpFunction %void None %1
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %f = OpFunction %int None %5
+          %8 = OpLabel
+          %i = OpVariable %_ptr_Function_int Function %11
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %13 %14 None
+               OpBranch %15
+         %15 = OpLabel
+         %16 = OpLoad %int %i
+         %18 = OpSGreaterThan %bool %16 %int_4
+               OpSelectionMerge %20 None
+               OpBranchConditional %18 %21 %20
+         %21 = OpLabel
+         %22 = OpLoad %int %i
+               OpReturnValue %22
+         %20 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+         %23 = OpLoad %int %i
+         %25 = OpIAdd %int %23 %int_1
+               OpStore %i %25
+         %26 = OpLoad %int %i
+         %27 = OpIEqual %bool %26 %int_4
+               OpBranchConditional %27 %13 %12
+         %13 = OpLabel
+         %28 = OpLoad %int %i
+               OpReturnValue %28
+               OpFunctionEnd
diff --git a/test/tint/loops/loop_with_break_if.wgsl.expected.wgsl b/test/tint/loops/loop_with_break_if.wgsl.expected.wgsl
new file mode 100644
index 0000000..71a6c02
--- /dev/null
+++ b/test/tint/loops/loop_with_break_if.wgsl.expected.wgsl
@@ -0,0 +1,14 @@
+fn f() -> i32 {
+  var i : i32;
+  loop {
+    if ((i > 4)) {
+      return i;
+    }
+
+    continuing {
+      i = (i + 1);
+      break if (i == 4);
+    }
+  }
+  return i;
+}