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>
This commit is contained in:
dan sinclair 2022-10-20 22:45:50 +00:00 committed by Dawn LUCI CQ
parent f8c07d4753
commit b8b0c21918
49 changed files with 983 additions and 74 deletions

View File

@ -205,6 +205,8 @@ libtint_source_set("libtint_core_all_src") {
"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 @@ libtint_source_set("libtint_core_all_src") {
"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 @@ libtint_source_set("libtint_sem_src") {
"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 @@ if (tint_build_unittests) {
"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",

View File

@ -73,6 +73,8 @@ set(TINT_LIB_SRCS
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 @@ set(TINT_LIB_SRCS
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 @@ if(TINT_BUILD_TESTS)
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

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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 @@ class ProgramBuilder {
/// @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

View File

@ -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(const ast::Statement* init,
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 @@ Maybe<const ast::Statement*> ParserImpl::for_header_continuing() {
}
// 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 @@ Maybe<const ast::ContinueStatement*> ParserImpl::continue_statement() {
// 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);
});
}

View File

@ -110,5 +110,57 @@ TEST_F(ParserImplTest, LoopStmt_InvalidContinuing) {
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

View File

@ -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 @@ class DependencyScanner {
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 @@ class DependencyScanner {
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) {

View File

@ -1260,7 +1260,7 @@ TEST_F(ResolverDependencyGraphTraversalTest, SymbolsReached) {
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))), //

View File

@ -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 @@ sem::Statement* Resolver::Statement(const ast::Statement* stmt) {
// 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::BreakStatement(const ast::BreakStatement* stmt) {
});
}
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_);

View File

@ -208,6 +208,7 @@ class Resolver {
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*);

View File

@ -569,6 +569,16 @@ TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContIfTrueBreak) {
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);

View File

@ -215,7 +215,8 @@ TEST_F(ResolverTest, Stmt_Loop) {
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 @@ TEST_F(ResolverTest, Stmt_Loop) {
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) {

View File

@ -537,6 +537,51 @@ class UniformityGraph {
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;

View File

@ -1125,9 +1125,7 @@ fn foo() {
workgroupBarrier();
continuing {
i = i + 1;
if (i == n) {
break;
}
break if (i == n);
}
}
}
@ -1146,9 +1144,7 @@ fn foo() {
workgroupBarrier();
continuing {
i = i + 1;
if (i == n) {
break;
}
break if (i == n);
}
}
}
@ -1161,12 +1157,12 @@ fn foo() {
^^^^^^^^^^^^^^^^
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 @@ fn foo() {
continuing {
workgroupBarrier();
i = i + 1;
if (i == n) {
break;
}
break if (i == n);
}
}
}
@ -1201,9 +1195,7 @@ fn foo() {
continuing {
workgroupBarrier();
i = i + 1;
if (i == n) {
break;
}
break if (i == n);
}
}
}
@ -1216,12 +1208,12 @@ fn foo() {
^^^^^^^^^^^^^^^^
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 @@ fn foo() {
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 @@ fn foo() {
if (v == 0) {
workgroupBarrier();
}
if (true) {
break;
}
break if (true);
}
}
}

View File

@ -870,6 +870,75 @@ TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Indirect) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueInContinuing) {
// }
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfElseInContinuing) {
// }
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInContinuing) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfInIfInContinuing) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueMultipleStmtsInContinuing) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfElseMultipleStmtsInContinuing) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfElseIfInContinuing) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfNonEmptyElseInContinuing) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfElseNonEmptyTrueInContinuing) {
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 @@ TEST_F(ResolverValidationTest, Stmt_BreakInIfInContinuingNotLast) {
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"

View File

@ -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 @@ bool Validator::BreakStatement(const sem::Statement* stmt,
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 @@ bool Validator::WhileStatement(const sem::WhileStatement* stmt) const {
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>()) {

View File

@ -51,6 +51,7 @@ namespace tint::sem {
class Array;
class Atomic;
class BlockStatement;
class BreakIfStatement;
class Builtin;
class Call;
class CaseStatement;
@ -256,6 +257,13 @@ class Validator {
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

View File

@ -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

View File

@ -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_

View File

@ -712,6 +712,16 @@ bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
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 @@ bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
[&](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)) {

View File

@ -139,6 +139,10 @@ class GeneratorImpl : public TextGenerator {
/// @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

View File

@ -65,6 +65,31 @@ TEST_F(GlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) {
)");
}
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_(), {});

View File

@ -942,6 +942,16 @@ bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
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 @@ bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
[&](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)) {

View File

@ -125,6 +125,10 @@ class GeneratorImpl : public TextGenerator {
/// @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

View File

@ -65,6 +65,31 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_LoopWithContinuing) {
)");
}
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_(), {});

View File

@ -660,6 +660,16 @@ bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
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 @@ bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
[&](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)) { //

View File

@ -129,6 +129,10 @@ class GeneratorImpl : public TextGenerator {
/// @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

View File

@ -65,6 +65,31 @@ TEST_F(MslGeneratorImplTest, Emit_LoopWithContinuing) {
)");
}
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);

View File

@ -455,6 +455,19 @@ bool Builder::GenerateBreakStatement(const ast::BreakStatement*) {
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 @@ bool Builder::GenerateIfStatement(const ast::IfStatement* stmt) {
// 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 @@ bool Builder::GenerateStatement(const ast::Statement* stmt) {
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 @@ bool Builder::GenerateStatement(const ast::Statement* stmt) {
return true; // Not emitted
},
[&](Default) {
error_ = "Unknown statement: " + std::string(stmt->TypeInfo().name);
error_ = "unknown statement type: " + std::string(stmt->TypeInfo().name);
return false;
});
}

View File

@ -248,6 +248,10 @@ class Builder {
/// @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

View File

@ -234,12 +234,11 @@ OpBranch %1
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 @@ OpBranchConditional %6 %2 %1
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 @@ TEST_F(BuilderTest, Loop_WithContinuing_BreakUnless) {
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 @@ OpBranch %4
%4 = OpLabel
OpBranch %3
%3 = OpLabel
OpBranchConditional %6 %1 %2
OpBranchConditional %6 %2 %1
%2 = OpLabel
)");
}
@ -300,13 +298,12 @@ TEST_F(BuilderTest, Loop_WithContinuing_BreakIf_ConditionIsVar) {
// 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 @@ TEST_F(BuilderTest, Loop_WithContinuing_BreakIf_Nested) {
// 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);

View File

@ -958,6 +958,7 @@ bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
[&](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 @@ bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
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: {";

View File

@ -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 @@ class GeneratorImpl : public TextGenerator {
/// @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

View File

@ -65,6 +65,32 @@ TEST_F(WgslGeneratorImplTest, Emit_LoopWithContinuing) {
)");
}
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);}; ; ) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}