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:
parent
65cd25951a
commit
f4075a7195
|
@ -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
|
||||||
|
|
|
@ -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;}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
Loading…
Reference in New Issue