reader/wgsl: Generate ForLoopStatements

Instead of LoopStatements.

Update the writers to handle these.

Fixed: tint:952
Change-Id: Ibef66e133224810efc28c224d910b5e21f71f8d6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57203
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
This commit is contained in:
Ben Clayton
2021-07-08 21:23:33 +00:00
committed by Tint LUCI CQ
parent 03c8393213
commit 1b03f0a07a
67 changed files with 1059 additions and 1274 deletions

View File

@@ -2071,7 +2071,7 @@ Expect<std::unique_ptr<ForHeader>> ParserImpl::expect_for_header() {
// for_statement
// : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT
Maybe<ast::Statement*> ParserImpl::for_stmt() {
Maybe<ast::ForLoopStatement*> ParserImpl::for_stmt() {
Source source;
if (!match(Token::Type::kFor, &source))
return Failure::kNoMatch;
@@ -2086,45 +2086,9 @@ Maybe<ast::Statement*> ParserImpl::for_stmt() {
if (stmts.errored)
return Failure::kErrored;
// The for statement is a syntactic sugar on top of the loop statement.
// We create corresponding nodes in ast with the exact same behaviour
// as we would expect from the loop statement.
if (header->condition != nullptr) {
// !condition
auto* not_condition = create<ast::UnaryOpExpression>(
header->condition->source(), ast::UnaryOp::kNot, header->condition);
// { break; }
auto* break_stmt = create<ast::BreakStatement>(not_condition->source());
auto* break_body =
create<ast::BlockStatement>(not_condition->source(), ast::StatementList{
break_stmt,
});
// if (!condition) { break; }
auto* break_if_not_condition =
create<ast::IfStatement>(not_condition->source(), not_condition,
break_body, ast::ElseStatementList{});
stmts.value.insert(stmts.value.begin(), break_if_not_condition);
}
ast::BlockStatement* continuing_body = nullptr;
if (header->continuing != nullptr) {
continuing_body = create<ast::BlockStatement>(header->continuing->source(),
ast::StatementList{
header->continuing,
});
}
auto* body = create<ast::BlockStatement>(source, stmts.value);
auto* loop = create<ast::LoopStatement>(source, body, continuing_body);
if (header->initializer != nullptr) {
return create<ast::BlockStatement>(source, ast::StatementList{
header->initializer,
loop,
});
}
return loop;
return create<ast::ForLoopStatement>(
source, header->initializer, header->condition, header->continuing,
create<ast::BlockStatement>(stmts.value));
}
// func_call_stmt

View File

@@ -554,7 +554,7 @@ class ParserImpl {
Expect<std::unique_ptr<ForHeader>> expect_for_header();
/// Parses a `for_stmt` grammar element
/// @returns the parsed for loop or nullptr
Maybe<ast::Statement*> for_stmt();
Maybe<ast::ForLoopStatement*> for_stmt();
/// Parses a `continuing_stmt` grammar element
/// @returns the parsed statements
Maybe<ast::BlockStatement*> continuing_stmt();

View File

@@ -14,141 +14,154 @@
#include "src/reader/wgsl/parser_impl_test_helper.h"
#include "src/ast/discard_statement.h"
namespace tint {
namespace reader {
namespace wgsl {
namespace {
class ForStmtTest : public ParserImplTest {
public:
void TestForLoop(std::string loop_str, std::string for_str) {
auto p_loop = parser(loop_str);
auto e_loop = p_loop->expect_statements();
EXPECT_FALSE(e_loop.errored);
EXPECT_FALSE(p_loop->has_error()) << p_loop->error();
auto p_for = parser(for_str);
auto e_for = p_for->expect_statements();
EXPECT_FALSE(e_for.errored);
EXPECT_FALSE(p_for->has_error()) << p_for->error();
std::string loop = ast::BlockStatement({}, {}, e_loop.value).str(Sem());
std::string for_ = ast::BlockStatement({}, {}, e_for.value).str(Sem());
EXPECT_EQ(loop, for_);
}
};
using ForStmtTest = ParserImplTest;
// Test an empty for loop.
TEST_F(ForStmtTest, Empty) {
std::string for_str = "for (;;) { }";
std::string loop_str = "loop { }";
TestForLoop(loop_str, for_str);
auto p = parser("for (;;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_EQ(fl->initializer(), nullptr);
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_EQ(fl->continuing(), nullptr);
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop with non-empty body.
TEST_F(ForStmtTest, Body) {
std::string for_str = "for (;;) { discard; }";
std::string loop_str = "loop { discard; }";
TestForLoop(loop_str, for_str);
auto p = parser("for (;;) { discard; }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_EQ(fl->initializer(), nullptr);
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_EQ(fl->continuing(), nullptr);
ASSERT_EQ(fl->body()->size(), 1u);
EXPECT_TRUE(fl->body()->statements()[0]->Is<ast::DiscardStatement>());
}
// Test a for loop declaring a variable in the initializer statement.
TEST_F(ForStmtTest, InitializerStatementDecl) {
std::string for_str = "for (var i: i32 ;;) { }";
std::string loop_str = "{ var i: i32; loop { } }";
TestForLoop(loop_str, for_str);
auto p = parser("for (var i: i32 ;;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer()));
auto* var = fl->initializer()->As<ast::VariableDeclStatement>()->variable();
EXPECT_FALSE(var->is_const());
EXPECT_EQ(var->constructor(), nullptr);
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_EQ(fl->continuing(), nullptr);
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop declaring and initializing a variable in the initializer
// statement.
TEST_F(ForStmtTest, InitializerStatementDeclEqual) {
std::string for_str = "for (var i: i32 = 0 ;;) { }";
std::string loop_str = "{ var i: i32 = 0; loop { } }";
TestForLoop(loop_str, for_str);
auto p = parser("for (var i: i32 = 0 ;;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer()));
auto* var = fl->initializer()->As<ast::VariableDeclStatement>()->variable();
EXPECT_FALSE(var->is_const());
EXPECT_NE(var->constructor(), nullptr);
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_EQ(fl->continuing(), nullptr);
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop declaring a const variable in the initializer statement.
TEST_F(ForStmtTest, InitializerStatementConstDecl) {
std::string for_str = "for (let i: i32 = 0 ;;) { }";
std::string loop_str = "{ let i: i32 = 0; loop { } }";
TestForLoop(loop_str, for_str);
auto p = parser("for (let i: i32 = 0 ;;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
ASSERT_TRUE(Is<ast::VariableDeclStatement>(fl->initializer()));
auto* var = fl->initializer()->As<ast::VariableDeclStatement>()->variable();
EXPECT_TRUE(var->is_const());
EXPECT_NE(var->constructor(), nullptr);
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_EQ(fl->continuing(), nullptr);
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop assigning a variable in the initializer statement.
TEST_F(ForStmtTest, InitializerStatementAssignment) {
std::string for_str = "var i: i32; for (i = 0 ;;) { }";
std::string loop_str = "var i: i32; { i = 0; loop { } }";
TestForLoop(loop_str, for_str);
auto p = parser("for (i = 0 ;;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_TRUE(Is<ast::AssignmentStatement>(fl->initializer()));
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_EQ(fl->continuing(), nullptr);
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop calling a function in the initializer statement.
TEST_F(ForStmtTest, InitializerStatementFuncCall) {
std::string for_str = "for (a(b,c) ;;) { }";
std::string loop_str = "{ a(b,c); loop { } }";
TestForLoop(loop_str, for_str);
auto p = parser("for (a(b,c) ;;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_TRUE(Is<ast::CallStatement>(fl->initializer()));
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_EQ(fl->continuing(), nullptr);
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop with a break condition
TEST_F(ForStmtTest, BreakCondition) {
std::string for_str = "for (; 0 == 1;) { }";
std::string loop_str = "loop { if (!(0 == 1)) { break; } }";
TestForLoop(loop_str, for_str);
auto p = parser("for (; 0 == 1;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_EQ(fl->initializer(), nullptr);
EXPECT_TRUE(Is<ast::BinaryExpression>(fl->condition()));
EXPECT_EQ(fl->continuing(), nullptr);
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop assigning a variable in the continuing statement.
TEST_F(ForStmtTest, ContinuingAssignment) {
std::string for_str = "var x: i32; for (;; x = 2) { }";
std::string loop_str = "var x: i32; loop { continuing { x = 2; }}";
TestForLoop(loop_str, for_str);
auto p = parser("for (;; x = 2) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_EQ(fl->initializer(), nullptr);
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_TRUE(Is<ast::AssignmentStatement>(fl->continuing()));
EXPECT_TRUE(fl->body()->empty());
}
// Test a for loop calling a function in the continuing statement.
TEST_F(ForStmtTest, ContinuingFuncCall) {
std::string for_str = "for (;; a(b,c)) { }";
std::string loop_str = "loop { continuing { a(b,c); }}";
TestForLoop(loop_str, for_str);
}
// Test a for loop with all statements non-empty.
TEST_F(ForStmtTest, All) {
std::string for_str =
R"(for(var i : i32 = 0; i < 4; i = i + 1) {
if (a == 0) {
continue;
}
a = a + 2;
})";
std::string loop_str =
R"({ // Introduce new scope for loop variable i
var i : i32 = 0;
loop {
if (!(i < 4)) {
break;
}
if (a == 0) {
continue;
}
a = a + 2;
continuing {
i = i + 1;
}
}
};)";
TestForLoop(loop_str, for_str);
auto p = parser("for (;; a(b,c)) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_EQ(fl->initializer(), nullptr);
EXPECT_EQ(fl->condition(), nullptr);
EXPECT_TRUE(Is<ast::CallStatement>(fl->continuing()));
EXPECT_TRUE(fl->body()->empty());
}
class ForStmtErrorTest : public ParserImplTest {

View File

@@ -62,6 +62,7 @@
#include "src/sem/storage_texture_type.h"
#include "src/sem/struct.h"
#include "src/sem/variable.h"
#include "src/utils/defer.h"
#include "src/utils/get_or_create.h"
#include "src/utils/math.h"
#include "src/utils/scoped_assignment.h"
@@ -1915,6 +1916,10 @@ bool Resolver::ForLoopStatement(ast::ForLoopStatement* stmt) {
stmt->body(), current_statement_);
builder_->Sem().Add(stmt->body(), sem_block_body);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_body);
TINT_SCOPED_ASSIGNMENT(current_block_, sem_block_body);
variable_stack_.push_scope();
TINT_DEFER(variable_stack_.pop_scope());
if (auto* initializer = stmt->initializer()) {
Mark(initializer);
@@ -4011,9 +4016,8 @@ bool Resolver::BlockScope(const ast::BlockStatement* block, F&& callback) {
TINT_SCOPED_ASSIGNMENT(current_block_,
const_cast<sem::BlockStatement*>(sem_block));
variable_stack_.push_scope();
bool result = callback();
variable_stack_.pop_scope();
return result;
TINT_DEFER(variable_stack_.pop_scope());
return callback();
}
std::string Resolver::VectorPretty(uint32_t size,

View File

@@ -29,6 +29,7 @@
#include "src/sem/variable.h"
#include "src/transform/external_texture_transform.h"
#include "src/transform/fold_constants.h"
#include "src/transform/for_loop_to_loop.h"
#include "src/transform/inline_pointer_lets.h"
#include "src/transform/manager.h"
#include "src/transform/simplify.h"
@@ -50,6 +51,7 @@ Output Spirv::Run(const Program* in, const DataMap& data) {
manager.Add<Simplify>(); // Required for arrayLength()
manager.Add<FoldConstants>();
manager.Add<ExternalTextureTransform>();
manager.Add<ForLoopToLoop>(); // Must come after ZeroInitWorkgroupMemory
auto transformedInput = manager.Run(in, data);
auto* cfg = data.Get<Config>();

View File

@@ -43,6 +43,7 @@
#include "src/sem/variable.h"
#include "src/transform/calculate_array_length.h"
#include "src/transform/hlsl.h"
#include "src/utils/defer.h"
#include "src/utils/get_or_create.h"
#include "src/utils/scoped_assignment.h"
#include "src/writer/append_vector.h"
@@ -2574,6 +2575,15 @@ bool GeneratorImpl::EmitLoop(ast::LoopStatement* stmt) {
}
bool GeneratorImpl::EmitForLoop(ast::ForLoopStatement* stmt) {
// Nest a for loop with a new block. In HLSL the initializer scope is not
// nested by the for-loop, so we may get variable redefinitions.
line() << "{";
increment_indent();
TINT_DEFER({
decrement_indent();
line() << "}";
});
TextBuffer init_buf;
if (auto* init = stmt->initializer()) {
TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
@@ -2581,16 +2591,6 @@ bool GeneratorImpl::EmitForLoop(ast::ForLoopStatement* stmt) {
return false;
}
}
bool multi_stmt_init = init_buf.lines.size() > 1;
// For-loop has multi-statement initializer.
// This cannot be emitted with a regular for loop, so instead nest the loop in
// a new block scope prefixed with these initializer statements.
if (multi_stmt_init) {
line() << "{";
increment_indent();
current_buffer_->Append(init_buf);
init_buf.lines.clear(); // Don't emit the initializer again in the 'for'
}
TextBuffer cond_pre;
std::stringstream cond_buf;
@@ -2609,10 +2609,20 @@ bool GeneratorImpl::EmitForLoop(ast::ForLoopStatement* stmt) {
}
}
if (cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1) {
// For-loop has multi-statement conditional and / or continuing.
// This cannot be emitted with a regular for loop, so instead generate a
// `while(true)` loop.
// If the for-loop has a multi-statement conditional and / or continuing, then
// we cannot emit this as a regular for-loop in HLSL. Instead we need to
// generate a `while(true)` loop.
bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
// If the for-loop has multi-statement initializer, or is going to be emitted
// as a `while(true)` loop, then declare the initializer statement(s) before
// the loop.
if (init_buf.lines.size() > 1 || (stmt->initializer() && emit_as_loop)) {
current_buffer_->Append(init_buf);
init_buf.lines.clear(); // Don't emit the initializer again in the 'for'
}
if (emit_as_loop) {
auto emit_continuing = [&]() {
current_buffer_->Append(cont_buf);
return true;
@@ -2620,23 +2630,24 @@ bool GeneratorImpl::EmitForLoop(ast::ForLoopStatement* stmt) {
TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
line() << "while (true) {";
{
ScopedIndent si(this);
increment_indent();
TINT_DEFER({
decrement_indent();
line() << "}";
});
if (stmt->condition()) {
current_buffer_->Append(cond_pre);
line() << "if (!(" << cond_buf.str() << ")) { break; }";
}
if (!EmitStatements(stmt->body()->statements())) {
return false;
}
if (!emit_continuing()) {
return false;
}
if (stmt->condition()) {
current_buffer_->Append(cond_pre);
line() << "if (!(" << cond_buf.str() << ")) { break; }";
}
if (!EmitStatements(stmt->body()->statements())) {
return false;
}
if (!emit_continuing()) {
return false;
}
line() << "}";
} else {
// For-loop can be generated.
{
@@ -2666,12 +2677,6 @@ bool GeneratorImpl::EmitForLoop(ast::ForLoopStatement* stmt) {
return false;
}
}
line() << "}";
}
if (multi_stmt_init) {
decrement_indent();
line() << "}";
}

View File

@@ -161,8 +161,10 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoop) {
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(; ; ) {
return;
EXPECT_EQ(gen.result(), R"( {
for(; ; ) {
return;
}
}
)");
}
@@ -180,8 +182,10 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInit) {
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(int i = 0; ; ) {
return;
EXPECT_EQ(gen.result(), R"( {
for(int i = 0; ; ) {
return;
}
}
)");
}
@@ -227,8 +231,10 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCond) {
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(; true; ) {
return;
EXPECT_EQ(gen.result(), R"( {
for(; true; ) {
return;
}
}
)");
}
@@ -248,13 +254,15 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCond) {
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( while (true) {
bool tint_tmp = true;
if (tint_tmp) {
tint_tmp = false;
EXPECT_EQ(gen.result(), R"( {
while (true) {
bool tint_tmp = true;
if (tint_tmp) {
tint_tmp = false;
}
if (!((tint_tmp))) { break; }
return;
}
if (!((tint_tmp))) { break; }
return;
}
)");
}
@@ -273,8 +281,10 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleCont) {
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(; ; i = (i + 1)) {
return;
EXPECT_EQ(gen.result(), R"( {
for(; ; i = (i + 1)) {
return;
}
}
)");
}
@@ -295,13 +305,15 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithMultiStmtCont) {
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( while (true) {
return;
bool tint_tmp = true;
if (tint_tmp) {
tint_tmp = false;
EXPECT_EQ(gen.result(), R"( {
while (true) {
return;
bool tint_tmp = true;
if (tint_tmp) {
tint_tmp = false;
}
i = (tint_tmp);
}
i = (tint_tmp);
}
)");
}
@@ -320,8 +332,10 @@ TEST_F(HlslGeneratorImplTest_Loop, Emit_ForLoopWithSimpleInitCondCont) {
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(int i = 0; true; i = (i + 1)) {
return;
EXPECT_EQ(gen.result(), R"( {
for(int i = 0; true; i = (i + 1)) {
return;
}
}
)");
}

View File

@@ -54,6 +54,7 @@
#include "src/sem/vector_type.h"
#include "src/sem/void_type.h"
#include "src/transform/msl.h"
#include "src/utils/defer.h"
#include "src/utils/scoped_assignment.h"
#include "src/writer/float_to_string.h"
@@ -1493,6 +1494,116 @@ bool GeneratorImpl::EmitLoop(ast::LoopStatement* stmt) {
return true;
}
bool GeneratorImpl::EmitForLoop(ast::ForLoopStatement* stmt) {
TextBuffer init_buf;
if (auto* init = stmt->initializer()) {
TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
if (!EmitStatement(init)) {
return false;
}
}
TextBuffer cond_pre;
std::stringstream cond_buf;
if (auto* cond = stmt->condition()) {
TINT_SCOPED_ASSIGNMENT(current_buffer_, &cond_pre);
if (!EmitExpression(cond_buf, cond)) {
return false;
}
}
TextBuffer cont_buf;
if (auto* cont = stmt->continuing()) {
TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
if (!EmitStatement(cont)) {
return false;
}
}
// If the for-loop has a multi-statement conditional and / or continuing, then
// we cannot emit this as a regular for-loop in MSL. Instead we need to
// generate a `while(true)` loop.
bool emit_as_loop = cond_pre.lines.size() > 0 || cont_buf.lines.size() > 1;
// If the for-loop has multi-statement initializer, or is going to be emitted
// as a `while(true)` loop, then declare the initializer statement(s) before
// the loop in a new block.
bool nest_in_block =
init_buf.lines.size() > 1 || (stmt->initializer() && emit_as_loop);
if (nest_in_block) {
line() << "{";
increment_indent();
current_buffer_->Append(init_buf);
init_buf.lines.clear(); // Don't emit the initializer again in the 'for'
}
TINT_DEFER({
if (nest_in_block) {
decrement_indent();
line() << "}";
}
});
if (emit_as_loop) {
auto emit_continuing = [&]() {
current_buffer_->Append(cont_buf);
return true;
};
TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
line() << "while (true) {";
increment_indent();
TINT_DEFER({
decrement_indent();
line() << "}";
});
if (stmt->condition()) {
current_buffer_->Append(cond_pre);
line() << "if (!(" << cond_buf.str() << ")) { break; }";
}
if (!EmitStatements(stmt->body()->statements())) {
return false;
}
if (!emit_continuing()) {
return false;
}
} else {
// For-loop can be generated.
{
auto out = line();
out << "for";
{
ScopedParen sp(out);
if (!init_buf.lines.empty()) {
out << init_buf.lines[0].content << " ";
} else {
out << "; ";
}
out << cond_buf.str() << "; ";
if (!cont_buf.lines.empty()) {
out << TrimSuffix(cont_buf.lines[0].content, ";");
}
}
out << " {";
}
{
auto emit_continuing = [] { return true; };
TINT_SCOPED_ASSIGNMENT(emit_continuing_, emit_continuing);
if (!EmitStatementsWithIndent(stmt->body()->statements())) {
return false;
}
}
line() << "}";
}
return true;
}
bool GeneratorImpl::EmitDiscard(ast::DiscardStatement*) {
// TODO(dsinclair): Verify this is correct when the discard semantics are
// defined for WGSL (https://github.com/gpuweb/gpuweb/issues/361)
@@ -1635,6 +1746,9 @@ bool GeneratorImpl::EmitStatement(ast::Statement* stmt) {
if (auto* l = stmt->As<ast::LoopStatement>()) {
return EmitLoop(l);
}
if (auto* l = stmt->As<ast::ForLoopStatement>()) {
return EmitForLoop(l);
}
if (auto* r = stmt->As<ast::ReturnStatement>()) {
return EmitReturn(r);
}

View File

@@ -170,6 +170,10 @@ class GeneratorImpl : public TextGenerator {
/// @param stmt the statement to emit
/// @returns true if the statement was emitted
bool EmitLoop(ast::LoopStatement* stmt);
/// Handles a for loop statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted
bool EmitForLoop(ast::ForLoopStatement* stmt);
/// Handles a member accessor expression
/// @param out the output of the expression stream
/// @param expr the member accessor expression

View File

@@ -141,6 +141,227 @@ TEST_F(MslGeneratorImplTest, Emit_LoopWithVarUsedInContinuing) {
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoop) {
// for(; ; ) {
// return;
// }
auto* f = For(nullptr, nullptr, nullptr, Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(; ; ) {
return;
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleInit) {
// for(var i : i32; ; ) {
// return;
// }
auto* f = For(Decl(Var("i", ty.i32())), nullptr, nullptr, Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(int i = 0; ; ) {
return;
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtInit) {
// var<workgroup> a : atomic<i32>;
// for(var b = atomicCompareExchangeWeak(&a, 1, 2); ; ) {
// return;
// }
Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
auto* multi_stmt = Call("atomicCompareExchangeWeak", AddressOf("a"), 1, 2);
auto* f = For(Decl(Var("b", nullptr, multi_stmt)), nullptr, nullptr,
Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( {
int prev_value = 1;
bool matched = atomic_compare_exchange_weak_explicit(&(a), &prev_value, 2, memory_order_relaxed, memory_order_relaxed);
int2 b = int2(prev_value, matched);
for(; ; ) {
return;
}
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleCond) {
// for(; true; ) {
// return;
// }
auto* f = For(nullptr, true, nullptr, Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(; true; ) {
return;
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtCond) {
// var<workgroup> a : atomic<i32>;
// for(; atomicCompareExchangeWeak(&a, 1, 2).x == 0; ) {
// return;
// }
Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
auto* multi_stmt = create<ast::BinaryExpression>(
ast::BinaryOp::kEqual,
MemberAccessor(Call("atomicCompareExchangeWeak", AddressOf("a"), 1, 2),
"x"),
Expr(0));
auto* f = For(nullptr, multi_stmt, nullptr, Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( while (true) {
int prev_value = 1;
bool matched = atomic_compare_exchange_weak_explicit(&(a), &prev_value, 2, memory_order_relaxed, memory_order_relaxed);
if (!((int2(prev_value, matched).x == 0))) { break; }
return;
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleCont) {
// for(; ; i = i + 1) {
// return;
// }
auto* v = Decl(Var("i", ty.i32()));
auto* f = For(nullptr, nullptr, Assign("i", Add("i", 1)), Block(Return()));
WrapInFunction(v, f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(; ; i = (i + 1)) {
return;
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtCont) {
// var<workgroup> a : atomic<i32>;
// for(; ; ignore(atomicCompareExchangeWeak(&a, 1, 2))) {
// return;
// }
Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
auto* multi_stmt =
Ignore(Call("atomicCompareExchangeWeak", AddressOf("a"), 1, 2));
auto* f = For(nullptr, nullptr, multi_stmt, Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( while (true) {
return;
int prev_value = 1;
bool matched = atomic_compare_exchange_weak_explicit(&(a), &prev_value, 2, memory_order_relaxed, memory_order_relaxed);
(void) int2(prev_value, matched);
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithSimpleInitCondCont) {
// for(var i : i32; true; i = i + 1) {
// return;
// }
auto* f = For(Decl(Var("i", ty.i32())), true, Assign("i", Add("i", 1)),
Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( for(int i = 0; true; i = (i + 1)) {
return;
}
)");
}
TEST_F(MslGeneratorImplTest, Emit_ForLoopWithMultiStmtInitCondCont) {
// var<workgroup> a : atomic<i32>;
// for(var b = atomicCompareExchangeWeak(&a, 1, 2);
// atomicCompareExchangeWeak(&a, 1, 2).x == 0;
// ignore(atomicCompareExchangeWeak(&a, 1, 2))) {
// return;
// }
Global("a", ty.atomic<i32>(), ast::StorageClass::kWorkgroup);
auto* multi_stmt_a = Call("atomicCompareExchangeWeak", AddressOf("a"), 1, 2);
auto* multi_stmt_b = create<ast::BinaryExpression>(
ast::BinaryOp::kEqual,
MemberAccessor(Call("atomicCompareExchangeWeak", AddressOf("a"), 1, 2),
"x"),
Expr(0));
auto* multi_stmt_c =
Ignore(Call("atomicCompareExchangeWeak", AddressOf("a"), 1, 2));
auto* f = For(Decl(Var("b", nullptr, multi_stmt_a)), multi_stmt_b,
multi_stmt_c, Block(Return()));
WrapInFunction(f);
GeneratorImpl& gen = Build();
gen.increment_indent();
ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
EXPECT_EQ(gen.result(), R"( {
int prev_value = 1;
bool matched = atomic_compare_exchange_weak_explicit(&(a), &prev_value, 2, memory_order_relaxed, memory_order_relaxed);
int2 b = int2(prev_value, matched);
while (true) {
int prev_value_1 = 1;
bool matched_1 = atomic_compare_exchange_weak_explicit(&(a), &prev_value_1, 2, memory_order_relaxed, memory_order_relaxed);
if (!((int2(prev_value_1, matched_1).x == 0))) { break; }
return;
int prev_value_2 = 1;
bool matched_2 = atomic_compare_exchange_weak_explicit(&(a), &prev_value_2, 2, memory_order_relaxed, memory_order_relaxed);
(void) int2(prev_value_2, matched_2);
}
}
)");
}
} // namespace
} // namespace msl
} // namespace writer