wgsl: Add support for increment/decrement statements

Implemented in both the reader and writer with E2E tests. Other
backends will ICE for now.

Bug: tint:1488
Change-Id: Ied2afa55a338347f427dee98a4076643ac432d9c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/86003
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
James Price
2022-04-07 13:42:45 +00:00
parent 2f9e31cefb
commit b02fe31e46
117 changed files with 1579 additions and 34 deletions

View File

@@ -903,6 +903,7 @@ if(TINT_BUILD_TESTS)
reader/wgsl/parser_impl_global_variable_decl_test.cc
reader/wgsl/parser_impl_if_stmt_test.cc
reader/wgsl/parser_impl_inclusive_or_expression_test.cc
reader/wgsl/parser_impl_increment_decrement_stmt_test.cc
reader/wgsl/parser_impl_logical_and_expression_test.cc
reader/wgsl/parser_impl_logical_or_expression_test.cc
reader/wgsl/parser_impl_loop_stmt_test.cc

View File

@@ -25,6 +25,7 @@
#include "src/tint/ast/fallthrough_statement.h"
#include "src/tint/ast/id_attribute.h"
#include "src/tint/ast/if_statement.h"
#include "src/tint/ast/increment_decrement_statement.h"
#include "src/tint/ast/invariant_attribute.h"
#include "src/tint/ast/loop_statement.h"
#include "src/tint/ast/return_statement.h"
@@ -1464,6 +1465,8 @@ Expect<ast::StatementList> ParserImpl::expect_statements() {
// | continue_stmt SEMICOLON
// | DISCARD SEMICOLON
// | assignment_stmt SEMICOLON
// | increment_stmt SEMICOLON
// | decrement_stmt SEMICOLON
Maybe<const ast::Statement*> ParserImpl::statement() {
while (match(Token::Type::kSemicolon)) {
// Skip empty statements
@@ -1520,6 +1523,8 @@ Maybe<const ast::Statement*> ParserImpl::statement() {
// | continue_stmt SEMICOLON
// | DISCARD SEMICOLON
// | assignment_stmt SEMICOLON
// | increment_stmt SEMICOLON
// | decrement_stmt SEMICOLON
Maybe<const ast::Statement*> ParserImpl::non_block_statement() {
auto stmt = [&]() -> Maybe<const ast::Statement*> {
auto ret_stmt = return_stmt();
@@ -1878,7 +1883,8 @@ ForHeader::ForHeader(const ast::Statement* init,
ForHeader::~ForHeader() = default;
// (variable_stmt | assignment_stmt | func_call_stmt)?
// (variable_stmt | increment_stmt | decrement_stmt | assignment_stmt |
// func_call_stmt)?
Maybe<const ast::Statement*> ParserImpl::for_header_initializer() {
auto call = func_call_stmt();
if (call.errored)
@@ -1901,7 +1907,7 @@ Maybe<const ast::Statement*> ParserImpl::for_header_initializer() {
return Failure::kNoMatch;
}
// (assignment_stmt | func_call_stmt)?
// (increment_stmt | decrement_stmt | assignment_stmt | func_call_stmt)?
Maybe<const ast::Statement*> ParserImpl::for_header_continuing() {
auto call_stmt = func_call_stmt();
if (call_stmt.errored)
@@ -2104,14 +2110,6 @@ Maybe<const ast::Expression*> ParserImpl::postfix_expression(
Source source;
while (continue_parsing()) {
if (match(Token::Type::kPlusPlus, &source) ||
match(Token::Type::kMinusMinus, &source)) {
add_error(source,
"postfix increment and decrement operators are reserved for a "
"future WGSL version");
return Failure::kErrored;
}
if (match(Token::Type::kBracketLeft, &source)) {
auto res = sync(
Token::Type::kBracketRight, [&]() -> Maybe<const ast::Expression*> {
@@ -2689,6 +2687,10 @@ Maybe<ast::BinaryOp> ParserImpl::compound_assignment_operator() {
// assignment_stmt
// | lhs_expression ( equal | compound_assignment_operator ) expression
// | underscore equal expression
// increment_stmt
// | lhs_expression PLUS_PLUS
// decrement_stmt
// | lhs_expression MINUS_MINUS
Maybe<const ast::Statement*> ParserImpl::assignment_stmt() {
auto t = peek();
auto source = t.source();
@@ -2712,6 +2714,16 @@ Maybe<const ast::Statement*> ParserImpl::assignment_stmt() {
lhs = create<ast::PhonyExpression>(source);
}
// Handle increment and decrement statements.
// We do this here because the parsing of the LHS expression overlaps with
// the assignment statement, and we cannot tell which we are parsing until we
// hit the ++/--/= token.
if (match(Token::Type::kPlusPlus)) {
return create<ast::IncrementDecrementStatement>(source, lhs.value, true);
} else if (match(Token::Type::kMinusMinus)) {
return create<ast::IncrementDecrementStatement>(source, lhs.value, false);
}
auto compound_op = compound_assignment_operator();
if (compound_op.errored) {
return Failure::kErrored;

View File

@@ -1118,6 +1118,14 @@ fn f() { return 1 | >; }
)");
}
TEST_F(ParserImplErrorTest, PostfixIncrementAsExpr) {
EXPECT("fn f() { var x : i32; let y = x++; }",
R"(test.wgsl:1:32 error: expected ';' for variable declaration
fn f() { var x : i32; let y = x++; }
^^
)");
}
TEST_F(ParserImplErrorTest, RelationalInvalidExpr) {
EXPECT("fn f() { return 1 < >; }",
R"(test.wgsl:1:21 error: unable to parse right side of < expression

View File

@@ -112,6 +112,19 @@ TEST_F(ForStmtTest, InitializerStatementAssignment) {
EXPECT_TRUE(fl->body->Empty());
}
// Test a for loop incrementing a variable in the initializer statement.
TEST_F(ForStmtTest, InitializerStatementIncrement) {
auto p = parser("for (i++;;) { }");
auto fl = p->for_stmt();
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(fl.errored);
ASSERT_TRUE(fl.matched);
EXPECT_TRUE(Is<ast::IncrementDecrementStatement>(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) {
auto p = parser("for (a(b,c) ;;) { }");
@@ -151,6 +164,19 @@ TEST_F(ForStmtTest, ContinuingAssignment) {
EXPECT_TRUE(fl->body->Empty());
}
// Test a for loop with an increment statement as the continuing statement.
TEST_F(ForStmtTest, ContinuingIncrement) {
auto p = parser("for (;; x++) { }");
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::IncrementDecrementStatement>(fl->continuing));
EXPECT_TRUE(fl->body->Empty());
}
// Test a for loop calling a function in the continuing statement.
TEST_F(ForStmtTest, ContinuingFuncCall) {
auto p = parser("for (;; a(b,c)) { }");

View File

@@ -0,0 +1,132 @@
// 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/reader/wgsl/parser_impl_test_helper.h"
namespace tint::reader::wgsl {
namespace {
TEST_F(ParserImplTest, IncrementDecrementStmt_Increment) {
auto p = parser("a++");
auto e = p->assignment_stmt();
EXPECT_TRUE(e.matched);
EXPECT_FALSE(e.errored);
EXPECT_FALSE(p->has_error()) << p->error();
ASSERT_NE(e.value, nullptr);
auto* i = e->As<ast::IncrementDecrementStatement>();
ASSERT_NE(i, nullptr);
ASSERT_NE(i->lhs, nullptr);
ASSERT_TRUE(i->lhs->Is<ast::IdentifierExpression>());
auto* ident = i->lhs->As<ast::IdentifierExpression>();
EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
EXPECT_TRUE(i->increment);
}
TEST_F(ParserImplTest, IncrementDecrementStmt_Decrement) {
auto p = parser("a--");
auto e = p->assignment_stmt();
EXPECT_TRUE(e.matched);
EXPECT_FALSE(e.errored);
EXPECT_FALSE(p->has_error()) << p->error();
ASSERT_NE(e.value, nullptr);
auto* i = e->As<ast::IncrementDecrementStatement>();
ASSERT_NE(i, nullptr);
ASSERT_NE(i->lhs, nullptr);
ASSERT_TRUE(i->lhs->Is<ast::IdentifierExpression>());
auto* ident = i->lhs->As<ast::IdentifierExpression>();
EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
EXPECT_FALSE(i->increment);
}
TEST_F(ParserImplTest, IncrementDecrementStmt_Parenthesized) {
auto p = parser("(a)++");
auto e = p->assignment_stmt();
EXPECT_TRUE(e.matched);
EXPECT_FALSE(e.errored);
EXPECT_FALSE(p->has_error()) << p->error();
ASSERT_NE(e.value, nullptr);
auto* i = e->As<ast::IncrementDecrementStatement>();
ASSERT_NE(i, nullptr);
ASSERT_NE(i->lhs, nullptr);
ASSERT_TRUE(i->lhs->Is<ast::IdentifierExpression>());
auto* ident = i->lhs->As<ast::IdentifierExpression>();
EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
EXPECT_TRUE(i->increment);
}
TEST_F(ParserImplTest, IncrementDecrementStmt_ToMember) {
auto p = parser("a.b.c[2].d++");
auto e = p->assignment_stmt();
EXPECT_TRUE(e.matched);
EXPECT_FALSE(e.errored);
EXPECT_FALSE(p->has_error()) << p->error();
ASSERT_NE(e.value, nullptr);
auto* i = e->As<ast::IncrementDecrementStatement>();
ASSERT_NE(i, nullptr);
ASSERT_NE(i->lhs, nullptr);
EXPECT_TRUE(i->increment);
ASSERT_TRUE(i->lhs->Is<ast::MemberAccessorExpression>());
auto* mem = i->lhs->As<ast::MemberAccessorExpression>();
ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
auto* ident = mem->member->As<ast::IdentifierExpression>();
EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("d"));
ASSERT_TRUE(mem->structure->Is<ast::IndexAccessorExpression>());
auto* idx = mem->structure->As<ast::IndexAccessorExpression>();
ASSERT_NE(idx->index, nullptr);
ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
ASSERT_TRUE(idx->object->Is<ast::MemberAccessorExpression>());
mem = idx->object->As<ast::MemberAccessorExpression>();
ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
ident = mem->member->As<ast::IdentifierExpression>();
EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("c"));
ASSERT_TRUE(mem->structure->Is<ast::MemberAccessorExpression>());
mem = mem->structure->As<ast::MemberAccessorExpression>();
ASSERT_TRUE(mem->structure->Is<ast::IdentifierExpression>());
ident = mem->structure->As<ast::IdentifierExpression>();
EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
ASSERT_TRUE(mem->member->Is<ast::IdentifierExpression>());
ident = mem->member->As<ast::IdentifierExpression>();
EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("b"));
}
TEST_F(ParserImplTest, IncrementDecrementStmt_InvalidLHS) {
auto p = parser("{}++");
auto e = p->assignment_stmt();
EXPECT_FALSE(e.matched);
EXPECT_FALSE(e.errored);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_EQ(e.value, nullptr);
}
} // namespace
} // namespace tint::reader::wgsl

View File

@@ -233,30 +233,6 @@ TEST_F(ParserImplTest, SingularExpression_Array_NestedIndexAccessor) {
EXPECT_EQ(index_expr->symbol, p->builder().Symbols().Get("c"));
}
TEST_F(ParserImplTest, SingularExpression_PostfixPlusPlus) {
auto p = parser("a++");
auto e = p->singular_expression();
EXPECT_FALSE(e.matched);
EXPECT_TRUE(e.errored);
EXPECT_EQ(e.value, nullptr);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(),
"1:2: postfix increment and decrement operators are reserved for a "
"future WGSL version");
}
TEST_F(ParserImplTest, SingularExpression_PostfixMinusMinus) {
auto p = parser("a--");
auto e = p->singular_expression();
EXPECT_FALSE(e.matched);
EXPECT_TRUE(e.errored);
EXPECT_EQ(e.value, nullptr);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(),
"1:2: postfix increment and decrement operators are reserved for a "
"future WGSL version");
}
} // namespace
} // namespace wgsl
} // namespace reader

View File

@@ -922,6 +922,9 @@ bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
[&](const ast::DiscardStatement* d) { return EmitDiscard(d); },
[&](const ast::FallthroughStatement* f) { return EmitFallthrough(f); },
[&](const ast::IfStatement* i) { return EmitIf(i); },
[&](const ast::IncrementDecrementStatement* l) {
return EmitIncrementDecrement(l);
},
[&](const ast::LoopStatement* l) { return EmitLoop(l); },
[&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
[&](const ast::ReturnStatement* r) { return EmitReturn(r); },
@@ -1072,6 +1075,16 @@ bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
return true;
}
bool GeneratorImpl::EmitIncrementDecrement(
const ast::IncrementDecrementStatement* stmt) {
auto out = line();
if (!EmitExpression(out, stmt->lhs)) {
return false;
}
out << (stmt->increment ? "++" : "--") << ";";
return true;
}
bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
line() << "discard;";
return true;

View File

@@ -133,6 +133,10 @@ class GeneratorImpl : public TextGenerator {
/// @param stmt the statement to emit
/// @returns true if the statement was successfully emitted
bool EmitIf(const ast::IfStatement* stmt);
/// Handles an increment/decrement statement
/// @param stmt the statement to emit
/// @returns true if the statement was successfully emitted
bool EmitIncrementDecrement(const ast::IncrementDecrementStatement* stmt);
/// Handles generating a discard statement
/// @param stmt the discard statement
/// @returns true if the statement was successfully emitted