resolver: Resolve for-loops

Still nothing creates these, yet.

Bug: tint:952
Change-Id: If35f9c68ede6c6e41b6e9510f0b60751fea5bd49
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56762
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
Ben Clayton 2021-07-02 19:27:42 +00:00
parent 65cd25951a
commit f4075a7195
7 changed files with 167 additions and 34 deletions

View File

@ -59,7 +59,6 @@ TEST_F(ResolverBlockTest, Block) {
auto* s = Sem().Get(stmt); auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr); ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr); ASSERT_NE(s->Block(), nullptr);
ASSERT_NE(s->Block()->Parent(), nullptr);
EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::BlockStatement>()); EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Block()->Parent(), EXPECT_EQ(s->Block()->Parent(),
s->Block()->FindFirstParent<sem::FunctionBlockStatement>()); s->Block()->FindFirstParent<sem::FunctionBlockStatement>());
@ -69,8 +68,98 @@ TEST_F(ResolverBlockTest, Block) {
EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr); EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr);
} }
// TODO(bclayton): Add tests for other block types (LoopBlockStatement, TEST_F(ResolverBlockTest, LoopBlock) {
// LoopContinuingBlockStatement, SwitchCaseBlockStatement) // fn F() {
// loop {
// var x : 32;
// }
// }
auto* stmt = Decl(Var("x", ty.i32()));
auto* loop = Loop(Block(stmt));
auto* f = Func("F", {}, ty.void_(), {loop});
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
EXPECT_EQ(s->Block()->Parent()->Parent(),
s->Block()->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()
->Parent()
->Parent()
->As<sem::FunctionBlockStatement>()
->Function(),
f);
EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
}
TEST_F(ResolverBlockTest, ForLoopBlock) {
// fn F() {
// for (var i : u32; true; i = i + 1u) {
// return;
// }
// }
auto* init = Decl(Var("i", ty.u32()));
auto* cond = Expr(true);
auto* cont = Assign("i", Add("i", 1u));
auto* stmt = Return();
auto* body = Block(stmt);
auto* for_ = For(init, cond, cont, body);
auto* f = Func("F", {}, ty.void_(), {for_});
ASSERT_TRUE(r()->Resolve()) << r()->error();
{
auto* s = Sem().Get(init);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(),
s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
}
{ // Condition expression's statement is the for-loop itself
auto* s = Sem().Get(cond);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Stmt()->Block(), nullptr);
EXPECT_EQ(
s->Stmt()->Block(),
s->Stmt()->Block()->FindFirstParent<sem::FunctionBlockStatement>());
ASSERT_TRUE(Is<sem::FunctionBlockStatement>(s->Stmt()->Block()));
}
{
auto* s = Sem().Get(cont);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(),
s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
}
{
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(),
s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
EXPECT_EQ(s->Block()->Parent()->Parent(),
s->Block()->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()
->Parent()
->Parent()
->As<sem::FunctionBlockStatement>()
->Function(),
f);
EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
}
}
// TODO(bclayton): Add tests for other block types
// (LoopContinuingBlockStatement, SwitchCaseBlockStatement)
} // namespace } // namespace
} // namespace resolver } // namespace resolver

View File

@ -109,7 +109,7 @@ TEST_F(ResolverControlBlockValidationTest, SwitchWithTwoDefault_Fail) {
"12:34 error: switch statement must have exactly one default clause"); "12:34 error: switch statement must have exactly one default clause");
} }
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_continue) { TEST_F(ResolverControlBlockValidationTest, UnreachableCode_Loop_continue) {
// loop { // loop {
// continue; // continue;
// var z : i32; // var z : i32;
@ -122,6 +122,20 @@ TEST_F(ResolverControlBlockValidationTest, UnreachableCode_continue) {
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable"); EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
} }
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) {
// for (;;;) {
// continue;
// var z : i32;
// }
WrapInFunction(
For(nullptr, nullptr, nullptr,
Block(create<ast::ContinueStatement>(),
Decl(Source{{12, 34}},
Var("z", ty.i32(), ast::StorageClass::kNone)))));
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
}
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) { TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) {
// switch (a) { // switch (a) {
// case 1: { break; var a : u32 = 2;} // case 1: { break; var a : u32 = 2;}

View File

@ -28,6 +28,7 @@
#include "src/ast/disable_validation_decoration.h" #include "src/ast/disable_validation_decoration.h"
#include "src/ast/discard_statement.h" #include "src/ast/discard_statement.h"
#include "src/ast/fallthrough_statement.h" #include "src/ast/fallthrough_statement.h"
#include "src/ast/for_loop_statement.h"
#include "src/ast/if_statement.h" #include "src/ast/if_statement.h"
#include "src/ast/internal_decoration.h" #include "src/ast/internal_decoration.h"
#include "src/ast/interpolate_decoration.h" #include "src/ast/interpolate_decoration.h"
@ -1701,6 +1702,9 @@ bool Resolver::Statement(ast::Statement* stmt) {
if (auto* l = stmt->As<ast::LoopStatement>()) { if (auto* l = stmt->As<ast::LoopStatement>()) {
return LoopStatement(l); return LoopStatement(l);
} }
if (auto* l = stmt->As<ast::ForLoopStatement>()) {
return ForLoopStatement(l);
}
if (auto* r = stmt->As<ast::ReturnStatement>()) { if (auto* r = stmt->As<ast::ReturnStatement>()) {
return Return(r); return Return(r);
} }
@ -1824,6 +1828,45 @@ bool Resolver::LoopStatement(ast::LoopStatement* stmt) {
}); });
} }
bool Resolver::ForLoopStatement(ast::ForLoopStatement* stmt) {
Mark(stmt->body());
auto* sem_block_body = builder_->create<sem::LoopBlockStatement>(
stmt->body(), current_statement_);
builder_->Sem().Add(stmt->body(), sem_block_body);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_body);
if (auto* initializer = stmt->initializer()) {
Mark(initializer);
if (!Statement(initializer)) {
return false;
}
}
if (auto* condition = stmt->condition()) {
Mark(condition);
if (!Expression(condition)) {
return false;
}
if (!TypeOf(condition)->Is<sem::Bool>()) {
AddError("for-loop condition must be bool, got " + TypeNameOf(condition),
condition->source());
return false;
}
}
if (auto* continuing = stmt->continuing()) {
Mark(continuing);
if (!Statement(continuing)) {
return false;
}
}
return BlockScope(stmt->body(),
[&] { return Statements(stmt->body()->list()); });
}
bool Resolver::Expressions(const ast::ExpressionList& list) { bool Resolver::Expressions(const ast::ExpressionList& list) {
for (auto* expr : list) { for (auto* expr : list) {
Mark(expr); Mark(expr);

View File

@ -38,9 +38,10 @@ class ArrayAccessorExpression;
class BinaryExpression; class BinaryExpression;
class BitcastExpression; class BitcastExpression;
class CallExpression; class CallExpression;
class CaseStatement;
class CallStatement; class CallStatement;
class CaseStatement;
class ConstructorExpression; class ConstructorExpression;
class ForLoopStatement;
class Function; class Function;
class IdentifierExpression; class IdentifierExpression;
class LoopStatement; class LoopStatement;
@ -244,12 +245,13 @@ class Resolver {
bool Constructor(ast::ConstructorExpression*); bool Constructor(ast::ConstructorExpression*);
bool Expression(ast::Expression*); bool Expression(ast::Expression*);
bool Expressions(const ast::ExpressionList&); bool Expressions(const ast::ExpressionList&);
bool ForLoopStatement(ast::ForLoopStatement*);
bool Function(ast::Function*); bool Function(ast::Function*);
bool FunctionCall(const ast::CallExpression* call);
bool GlobalVariable(ast::Variable* var); bool GlobalVariable(ast::Variable* var);
bool Identifier(ast::IdentifierExpression*); bool Identifier(ast::IdentifierExpression*);
bool IfStatement(ast::IfStatement*); bool IfStatement(ast::IfStatement*);
bool IntrinsicCall(ast::CallExpression*, sem::IntrinsicType); bool IntrinsicCall(ast::CallExpression*, sem::IntrinsicType);
bool FunctionCall(const ast::CallExpression* call);
bool LoopStatement(ast::LoopStatement*); bool LoopStatement(ast::LoopStatement*);
bool MemberAccessor(ast::MemberAccessorExpression*); bool MemberAccessor(ast::MemberAccessorExpression*);
bool Parameter(ast::Variable* param); bool Parameter(ast::Variable* param);

View File

@ -652,6 +652,17 @@ TEST_F(ResolverTest, Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing) {
EXPECT_TRUE(r()->Resolve()); EXPECT_TRUE(r()->Resolve());
} }
TEST_F(ResolverTest, Stmt_ForLoop_CondIsNotBool) {
// for (; 1.0f; ) {
// }
WrapInFunction(For(nullptr, Expr(Source{{12, 34}}, 1.0f), nullptr, Block()));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: for-loop condition must be bool, got f32");
}
TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) { TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) {
WrapInFunction(Loop(Block(create<ast::ContinueStatement>(Source{{12, 34}})))); WrapInFunction(Loop(Block(create<ast::ContinueStatement>(Source{{12, 34}}))));
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();

View File

@ -105,7 +105,7 @@ class FunctionBlockStatement
ast::Function const* const function_; ast::Function const* const function_;
}; };
/// Holds semantic information about a loop block /// Holds semantic information about a loop block or a for-loop block
class LoopBlockStatement : public Castable<LoopBlockStatement, BlockStatement> { class LoopBlockStatement : public Castable<LoopBlockStatement, BlockStatement> {
public: public:
/// Constructor /// Constructor

View File

@ -17,7 +17,6 @@
#include "src/ast/block_statement.h" #include "src/ast/block_statement.h"
#include "src/ast/loop_statement.h" #include "src/ast/loop_statement.h"
#include "src/ast/statement.h" #include "src/ast/statement.h"
#include "src/debug.h"
#include "src/sem/block_statement.h" #include "src/sem/block_statement.h"
#include "src/sem/statement.h" #include "src/sem/statement.h"
@ -27,32 +26,7 @@ namespace tint {
namespace sem { namespace sem {
Statement::Statement(const ast::Statement* declaration, const Statement* parent) Statement::Statement(const ast::Statement* declaration, const Statement* parent)
: declaration_(declaration), parent_(parent) { : declaration_(declaration), parent_(parent) {}
#ifndef NDEBUG
if (parent_) {
auto* block = Block();
if (parent_ == block) {
// The parent of this statement is a block. We thus expect the statement
// to be an element of the block. There is one exception: a loop's
// continuing block has the loop's body as its parent, but the continuing
// block is not a statement in the body, so we rule out that case.
auto& stmts = block->Declaration()->statements();
if (std::find(stmts.begin(), stmts.end(), declaration) == stmts.end()) {
bool statement_is_continuing_for_loop = false;
if (parent_->parent_ != nullptr) {
if (auto* loop =
parent_->parent_->Declaration()->As<ast::LoopStatement>()) {
if (loop->has_continuing() && Declaration() == loop->continuing()) {
statement_is_continuing_for_loop = true;
}
}
}
TINT_ASSERT(Semantic, statement_is_continuing_for_loop);
}
}
}
#endif // NDEBUG
}
const BlockStatement* Statement::Block() const { const BlockStatement* Statement::Block() const {
auto* stmt = parent_; auto* stmt = parent_;