diff --git a/docs/compound_statements.md b/docs/compound_statements.md new file mode 100644 index 0000000000..7d28b1d4ac --- /dev/null +++ b/docs/compound_statements.md @@ -0,0 +1,115 @@ +# Compound Statements + +Compound statements are statements that can hold other statements. + +This document maps the WGSL compound statements to their semantic tree representations. + +## if statement + +WGSL: +``` +if (condition_a) { + statement_a; +} else if (condition_b) { + statement_b; +} else { + statement_c; +} +``` + +Semantic tree: +``` +sem::IfStatement { + condition_a + sem::BlockStatement { + statement_a + } + sem::ElseStatement { + condition_b + sem::BlockStatement { + statement_b + } + } + sem::ElseStatement { + sem::BlockStatement { + statement_c + } + } +} +``` + +## for loop + +WGSL: +``` +for (initializer; condition; continuing) { + statement; +} +``` + +Semantic tree: +``` +sem::ForLoopStatement { + sem::Statement initializer + sem::Expression condition + sem::Statement continuing + + sem::LoopBlockStatement { + sem::Statement statement + } +} +``` + +## loop + +WGSL: +``` +loop (condition) { + statement_a; + continuing { + statement_b; + } +} +``` + +Semantic tree: +``` +sem::LoopStatement { + sem::Expression condition + + sem::LoopBlockStatement { + sem::Statement statement_a + sem::LoopContinuingBlockStatement { + sem::Statement statement_b + } + } +} +``` + + +## switch statement + +WGSL: +``` +switch (condition) { + case literal_a, literal_b: { + statement_a; + } + default { + statement_b; + } +} +``` + +Semantic tree: +``` +sem::SwitchStatement { + sem::Expression condition + sem::SwitchCaseBlockStatement { + sem::Statement statement_a + } + sem::SwitchCaseBlockStatement { + sem::Statement statement_b + } +} +``` diff --git a/src/BUILD.gn b/src/BUILD.gn index a7c93346fc..83c42bf3ff 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -497,58 +497,40 @@ libtint_source_set("libtint_core_all_src") { "resolver/resolver.h", "scope_stack.h", "sem/array.h", - "sem/atomic_type.cc", "sem/atomic_type.h", "sem/binding_point.h", - "sem/bool_type.cc", "sem/bool_type.h", "sem/call.h", "sem/call_target.h", - "sem/constant.cc", "sem/constant.h", - "sem/depth_texture_type.cc", "sem/depth_texture_type.h", "sem/expression.h", - "sem/external_texture_type.cc", "sem/external_texture_type.h", - "sem/f32_type.cc", "sem/f32_type.h", - "sem/i32_type.cc", + "sem/for_loop_statement.h", "sem/i32_type.h", + "sem/if_statement.h", "sem/info.h", "sem/intrinsic.h", - "sem/intrinsic_type.cc", "sem/intrinsic_type.h", - "sem/matrix_type.cc", + "sem/loop_statement.h", "sem/matrix_type.h", - "sem/multisampled_texture_type.cc", "sem/multisampled_texture_type.h", "sem/node.h", - "sem/parameter_usage.cc", "sem/parameter_usage.h", "sem/pipeline_stage_set.h", - "sem/pointer_type.cc", "sem/pointer_type.h", - "sem/reference_type.cc", "sem/reference_type.h", - "sem/sampled_texture_type.cc", "sem/sampled_texture_type.h", - "sem/sampler_type.cc", "sem/sampler_type.h", - "sem/storage_texture_type.cc", "sem/storage_texture_type.h", - "sem/texture_type.cc", + "sem/switch_statement.h", "sem/texture_type.h", - "sem/type.cc", "sem/type.h", - "sem/type_manager.cc", "sem/type_manager.h", "sem/type_mappings.h", - "sem/u32_type.cc", "sem/u32_type.h", - "sem/vector_type.cc", "sem/vector_type.h", - "sem/void_type.cc", "sem/void_type.h", "source.cc", "source.h", @@ -633,18 +615,80 @@ libtint_source_set("libtint_core_all_src") { libtint_source_set("libtint_sem_src") { sources = [ "sem/array.cc", + "sem/array.h", + "sem/atomic_type.cc", + "sem/atomic_type.h", + "sem/binding_point.h", "sem/block_statement.cc", + "sem/bool_type.cc", + "sem/bool_type.h", "sem/call.cc", + "sem/call.h", "sem/call_target.cc", + "sem/call_target.h", + "sem/constant.cc", + "sem/constant.h", + "sem/depth_texture_type.cc", + "sem/depth_texture_type.h", "sem/expression.cc", + "sem/expression.h", + "sem/external_texture_type.cc", + "sem/external_texture_type.h", + "sem/f32_type.cc", + "sem/f32_type.h", + "sem/for_loop_statement.cc", + "sem/for_loop_statement.h", "sem/function.cc", + "sem/i32_type.cc", + "sem/i32_type.h", + "sem/if_statement.cc", + "sem/if_statement.h", "sem/info.cc", + "sem/info.h", "sem/intrinsic.cc", + "sem/intrinsic.h", + "sem/intrinsic_type.cc", + "sem/intrinsic_type.h", + "sem/loop_statement.cc", + "sem/loop_statement.h", + "sem/matrix_type.cc", + "sem/matrix_type.h", "sem/member_accessor_expression.cc", + "sem/multisampled_texture_type.cc", + "sem/multisampled_texture_type.h", "sem/node.cc", + "sem/node.h", + "sem/parameter_usage.cc", + "sem/parameter_usage.h", + "sem/pipeline_stage_set.h", + "sem/pointer_type.cc", + "sem/pointer_type.h", + "sem/reference_type.cc", + "sem/reference_type.h", + "sem/sampled_texture_type.cc", + "sem/sampled_texture_type.h", + "sem/sampler_type.cc", + "sem/sampler_type.h", "sem/statement.cc", + "sem/storage_texture_type.cc", + "sem/storage_texture_type.h", "sem/struct.cc", + "sem/switch_statement.cc", + "sem/switch_statement.h", + "sem/texture_type.cc", + "sem/texture_type.h", + "sem/type.cc", + "sem/type.h", + "sem/type_manager.cc", + "sem/type_manager.h", + "sem/type_mappings.h", + "sem/u32_type.cc", + "sem/u32_type.h", "sem/variable.cc", + "sem/vector_type.cc", + "sem/vector_type.h", + "sem/void_type.cc", + "sem/void_type.h", ] public_deps = [ ":libtint_core_all_src" ] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a98007449..f5685bdaa4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -336,8 +336,14 @@ set(TINT_LIB_SRCS sem/external_texture_type.h sem/f32_type.cc sem/f32_type.h + sem/for_loop_statement.cc + sem/for_loop_statement.h sem/i32_type.cc sem/i32_type.h + sem/if_statement.cc + sem/if_statement.h + sem/loop_statement.cc + sem/loop_statement.h sem/matrix_type.cc sem/matrix_type.h sem/multisampled_texture_type.cc @@ -352,6 +358,8 @@ set(TINT_LIB_SRCS sem/sampler_type.h sem/storage_texture_type.cc sem/storage_texture_type.h + sem/switch_statement.cc + sem/switch_statement.h sem/texture_type.cc sem/texture_type.h sem/type.cc @@ -622,10 +630,10 @@ if(${TINT_BUILD_TESTS}) resolver/assignment_validation_test.cc resolver/atomics_test.cc resolver/atomics_validation_test.cc - resolver/block_test.cc resolver/builtins_validation_test.cc resolver/call_test.cc resolver/call_validation_test.cc + resolver/compound_statement_test.cc resolver/control_block_validation_test.cc resolver/decoration_validation_test.cc resolver/entry_point_validation_test.cc diff --git a/src/resolver/block_test.cc b/src/resolver/block_test.cc deleted file mode 100644 index ab213f6bed..0000000000 --- a/src/resolver/block_test.cc +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2021 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/resolver/resolver.h" - -#include "gmock/gmock.h" -#include "src/resolver/resolver_test_helper.h" -#include "src/sem/block_statement.h" - -namespace tint { -namespace resolver { -namespace { - -using ResolverBlockTest = ResolverTest; - -TEST_F(ResolverBlockTest, FunctionBlock) { - // fn F() { - // var x : 32; - // } - auto* stmt = Decl(Var("x", ty.i32())); - auto* f = Func("F", {}, ty.void_(), {stmt}); - - ASSERT_TRUE(r()->Resolve()) << r()->error(); - - auto* s = Sem().Get(stmt); - ASSERT_NE(s, nullptr); - ASSERT_NE(s->Block(), nullptr); - ASSERT_TRUE(s->Block()->Is()); - EXPECT_EQ(s->Block(), s->Block()->FindFirstParent()); - EXPECT_EQ(s->Block(), - s->Block()->FindFirstParent()); - EXPECT_EQ(s->Block()->As()->Function(), f); - EXPECT_EQ(s->Block()->Parent(), nullptr); -} - -TEST_F(ResolverBlockTest, Block) { - // fn F() { - // { - // var x : 32; - // } - // } - auto* stmt = Decl(Var("x", ty.i32())); - auto* block = Block(stmt); - auto* f = Func("F", {}, ty.void_(), {block}); - - 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()); - EXPECT_EQ(s->Block()->Parent(), - s->Block()->FindFirstParent()); - ASSERT_TRUE(s->Block()->Parent()->Is()); - EXPECT_EQ(s->Block()->Parent()->As()->Function(), - f); - EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr); -} - -TEST_F(ResolverBlockTest, LoopBlock) { - // 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()); - ASSERT_TRUE(Is(s->Block()->Parent()->Parent())); - EXPECT_EQ(s->Block()->Parent()->Parent(), - s->Block()->FindFirstParent()); - EXPECT_EQ(s->Block() - ->Parent() - ->Parent() - ->As() - ->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()); - ASSERT_TRUE( - Is(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()); - ASSERT_TRUE(Is(s->Stmt()->Block())); - } - { - auto* s = Sem().Get(cont); - ASSERT_NE(s, nullptr); - ASSERT_NE(s->Block(), nullptr); - EXPECT_EQ(s->Block(), - s->Block()->FindFirstParent()); - ASSERT_TRUE( - Is(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()); - ASSERT_TRUE( - Is(s->Block()->Parent()->Parent())); - EXPECT_EQ(s->Block()->Parent()->Parent(), - s->Block()->FindFirstParent()); - EXPECT_EQ(s->Block() - ->Parent() - ->Parent() - ->As() - ->Function(), - f); - EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr); - } -} -// TODO(bclayton): Add tests for other block types -// (LoopContinuingBlockStatement, SwitchCaseBlockStatement) - -} // namespace -} // namespace resolver -} // namespace tint diff --git a/src/resolver/compound_statement_test.cc b/src/resolver/compound_statement_test.cc new file mode 100644 index 0000000000..a13cac6de7 --- /dev/null +++ b/src/resolver/compound_statement_test.cc @@ -0,0 +1,384 @@ +// Copyright 2021 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/resolver/resolver.h" + +#include "gmock/gmock.h" +#include "src/resolver/resolver_test_helper.h" +#include "src/sem/block_statement.h" +#include "src/sem/for_loop_statement.h" +#include "src/sem/if_statement.h" +#include "src/sem/loop_statement.h" +#include "src/sem/switch_statement.h" + +namespace tint { +namespace resolver { +namespace { + +using ResolverCompoundStatementTest = ResolverTest; + +TEST_F(ResolverCompoundStatementTest, FunctionBlock) { + // fn F() { + // var x : 32; + // } + auto* stmt = Decl(Var("x", ty.i32())); + auto* f = Func("F", {}, ty.void_(), {stmt}); + + ASSERT_TRUE(r()->Resolve()) << r()->error(); + + auto* s = Sem().Get(stmt); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + ASSERT_TRUE(s->Block()->Is()); + EXPECT_EQ(s->Block(), s->FindFirstParent()); + EXPECT_EQ(s->Block(), s->FindFirstParent()); + EXPECT_EQ(s->Block()->As()->Function(), f); + EXPECT_EQ(s->Block()->Parent(), nullptr); +} + +TEST_F(ResolverCompoundStatementTest, Block) { + // fn F() { + // { + // var x : 32; + // } + // } + auto* stmt = Decl(Var("x", ty.i32())); + auto* block = Block(stmt); + auto* f = Func("F", {}, ty.void_(), {block}); + + ASSERT_TRUE(r()->Resolve()) << r()->error(); + + { + auto* s = Sem().Get(block); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s, s->Block()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + } + { + auto* s = Sem().Get(stmt); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Block(), s->FindFirstParent()); + EXPECT_EQ(s->Block()->Parent(), + s->FindFirstParent()); + ASSERT_TRUE(s->Block()->Parent()->Is()); + EXPECT_EQ( + s->Block()->Parent()->As()->Function(), f); + EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr); + } +} + +TEST_F(ResolverCompoundStatementTest, Loop) { + // fn F() { + // loop { + // stmt_a; + // continuing { + // stmt_b; + // } + // } + // } + auto* stmt_a = Ignore(1); + auto* stmt_b = Ignore(1); + auto* loop = Loop(Block(stmt_a), Block(stmt_b)); + auto* f = Func("F", {}, ty.void_(), {loop}); + + ASSERT_TRUE(r()->Resolve()) << r()->error(); + + { + auto* s = Sem().Get(loop); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + } + { + auto* s = Sem().Get(stmt_a); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + + EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent()->Parent())); + + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_TRUE( + Is(s->Parent()->Parent()->Parent())); + + EXPECT_EQ(s->FindFirstParent()->Function(), f); + + EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), nullptr); + } + { + auto* s = Sem().Get(stmt_b); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Parent(), s->Block()); + + EXPECT_EQ(s->Parent(), + s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent())); + + EXPECT_EQ(s->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent()->Parent())); + + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent()->Parent()->Parent())); + + EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_TRUE(Is( + s->Parent()->Parent()->Parent()->Parent())); + EXPECT_EQ(s->FindFirstParent()->Function(), f); + + EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent()->Parent(), nullptr); + } +} + +TEST_F(ResolverCompoundStatementTest, ForLoop) { + // 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(for_); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + } + { + auto* s = Sem().Get(init); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent())); + EXPECT_EQ(s->Block(), s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent()->Parent())); + } + { // Condition expression's statement is the for-loop itself + auto* e = Sem().Get(cond); + ASSERT_NE(e, nullptr); + auto* s = e->Stmt(); + ASSERT_NE(s, nullptr); + ASSERT_TRUE(Is(s)); + ASSERT_NE(s->Parent(), nullptr); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_TRUE(Is(s->Block())); + } + { + auto* s = Sem().Get(cont); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent())); + EXPECT_EQ(s->Block(), s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent()->Parent())); + } + { + auto* s = Sem().Get(stmt); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Block(), s->FindFirstParent()); + EXPECT_TRUE(Is(s->Parent()->Parent())); + EXPECT_EQ(s->Block()->Parent(), + s->FindFirstParent()); + ASSERT_TRUE( + Is(s->Block()->Parent()->Parent())); + EXPECT_EQ(s->Block()->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_EQ(s->Block() + ->Parent() + ->Parent() + ->As() + ->Function(), + f); + EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr); + } +} + +TEST_F(ResolverCompoundStatementTest, If) { + // fn F() { + // if (cond_a) { + // stat_a; + // } elseif (cond_b) { + // stat_b; + // } else { + // stat_c; + // } + // } + + auto* cond_a = Expr(true); + auto* stmt_a = Ignore(1); + auto* cond_b = Expr(true); + auto* stmt_b = Ignore(1); + auto* stmt_c = Ignore(1); + auto* if_stmt = If(cond_a, Block(stmt_a), Else(cond_b, Block(stmt_b)), + Else(nullptr, Block(stmt_c))); + WrapInFunction(if_stmt); + + ASSERT_TRUE(r()->Resolve()) << r()->error(); + + { + auto* s = Sem().Get(if_stmt); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + } + { + auto* e = Sem().Get(cond_a); + ASSERT_NE(e, nullptr); + auto* s = e->Stmt(); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + } + { + auto* s = Sem().Get(stmt_a); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + } + { + auto* e = Sem().Get(cond_b); + ASSERT_NE(e, nullptr); + auto* s = e->Stmt(); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent(), s->Block()); + } + { + auto* s = Sem().Get(stmt_b); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), + s->FindFirstParent()); + } + { + auto* s = Sem().Get(stmt_c); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), + s->FindFirstParent()); + } +} + +TEST_F(ResolverCompoundStatementTest, Switch) { + // fn F() { + // switch (expr) { + // case 1: { + // stmt_a; + // } + // case 2: { + // stmt_b; + // } + // default: { + // stmt_c; + // } + // } + // } + + auto* expr = Expr(5); + auto* stmt_a = Ignore(1); + auto* stmt_b = Ignore(1); + auto* stmt_c = Ignore(1); + auto* swi = + Switch(expr, Case(Literal(1), Block(stmt_a)), + Case(Literal(2), Block(stmt_b)), DefaultCase(Block(stmt_c))); + WrapInFunction(swi); + + ASSERT_TRUE(r()->Resolve()) << r()->error(); + + { + auto* s = Sem().Get(swi); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + } + { + auto* e = Sem().Get(expr); + ASSERT_NE(e, nullptr); + auto* s = e->Stmt(); + ASSERT_NE(s, nullptr); + EXPECT_TRUE(s->Is()); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + } + { + auto* s = Sem().Get(stmt_a); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + } + { + auto* s = Sem().Get(stmt_b); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + } + { + auto* s = Sem().Get(stmt_c); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->Parent(), s->FindFirstParent()); + EXPECT_EQ(s->Parent(), s->Block()); + EXPECT_EQ(s->Parent()->Parent(), + s->FindFirstParent()); + EXPECT_EQ(s->Parent()->Parent()->Parent(), + s->FindFirstParent()); + } +} + +} // namespace +} // namespace resolver +} // namespace tint diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc index 8390108d30..eac0b637b0 100644 --- a/src/resolver/resolver.cc +++ b/src/resolver/resolver.cc @@ -51,7 +51,10 @@ #include "src/sem/atomic_type.h" #include "src/sem/call.h" #include "src/sem/depth_texture_type.h" +#include "src/sem/for_loop_statement.h" #include "src/sem/function.h" +#include "src/sem/if_statement.h" +#include "src/sem/loop_statement.h" #include "src/sem/member_accessor_expression.h" #include "src/sem/multisampled_texture_type.h" #include "src/sem/pointer_type.h" @@ -61,6 +64,7 @@ #include "src/sem/statement.h" #include "src/sem/storage_texture_type.h" #include "src/sem/struct.h" +#include "src/sem/switch_statement.h" #include "src/sem/variable.h" #include "src/utils/defer.h" #include "src/utils/get_or_create.h" @@ -1568,16 +1572,14 @@ bool Resolver::Function(ast::Function* func) { if (func->body()) { Mark(func->body()); - if (current_statement_) { + if (current_compound_statement_) { TINT_ICE(Resolver, diagnostics_) - << "Resolver::Function() called with a current statement"; + << "Resolver::Function() called with a current compound statement"; return false; } auto* sem_block = builder_->create(func); builder_->Sem().Add(func->body(), sem_block); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block); - if (!BlockScope(func->body(), - [&] { return Statements(func->body()->list()); })) { + if (!Scope(sem_block, [&] { return Statements(func->body()->list()); })) { return false; } } @@ -1725,17 +1727,11 @@ bool Resolver::ValidateStatements(const ast::StatementList& stmts) { } bool Resolver::Statement(ast::Statement* stmt) { - sem::Statement* sem_statement; - if (stmt->As()) { - sem_statement = builder_->create( - stmt->As(), current_statement_); - } else { - sem_statement = builder_->create(stmt, current_statement_); + if (stmt->Is()) { + AddError("case statement can only be used inside a switch statement", + stmt->source()); + return false; } - builder_->Sem().Add(stmt, sem_statement); - - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement); - if (stmt->Is()) { TINT_ICE(Resolver, diagnostics_) << "Resolver::Statement() encountered an Else statement. Else " @@ -1744,15 +1740,35 @@ bool Resolver::Statement(ast::Statement* stmt) { return false; } + // Compound statements. These create their own sem::CompoundStatement + // bindings. + if (auto* b = stmt->As()) { + return BlockStatement(b); + } + if (auto* l = stmt->As()) { + return ForLoopStatement(l); + } + if (auto* l = stmt->As()) { + return LoopStatement(l); + } + if (auto* i = stmt->As()) { + return IfStatement(i); + } + if (auto* s = stmt->As()) { + return SwitchStatement(s); + } + + // Non-Compound statements + sem::Statement* sem_statement = + builder_->create(stmt, current_compound_statement_); + builder_->Sem().Add(stmt, sem_statement); + TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement); if (auto* a = stmt->As()) { return Assignment(a); } - if (auto* b = stmt->As()) { - return BlockScope(b, [&] { return Statements(b->list()); }); - } if (stmt->Is()) { - if (!current_block_->FindFirstParent() && - !current_block_->FindFirstParent()) { + if (!sem_statement->FindFirstParent() && + !sem_statement->FindFirstParent()) { AddError("break statement must be in a loop or switch case", stmt->source()); return false; @@ -1769,9 +1785,6 @@ bool Resolver::Statement(ast::Statement* stmt) { } return true; } - if (auto* c = stmt->As()) { - return CaseStatement(c); - } if (stmt->Is()) { // Set if we've hit the first continue statement in our parent loop if (auto* loop_block = @@ -1793,21 +1806,9 @@ bool Resolver::Statement(ast::Statement* stmt) { if (stmt->Is()) { return true; } - if (auto* i = stmt->As()) { - return IfStatement(i); - } - if (auto* l = stmt->As()) { - return LoopStatement(l); - } - if (auto* l = stmt->As()) { - return ForLoopStatement(l); - } if (auto* r = stmt->As()) { return Return(r); } - if (auto* s = stmt->As()) { - return Switch(s); - } if (auto* v = stmt->As()) { return VariableDeclStatement(v); } @@ -1819,51 +1820,59 @@ bool Resolver::Statement(ast::Statement* stmt) { } bool Resolver::CaseStatement(ast::CaseStatement* stmt) { + auto* sem = builder_->create( + stmt->body(), current_compound_statement_); + builder_->Sem().Add(stmt, sem); + builder_->Sem().Add(stmt->body(), sem); Mark(stmt->body()); for (auto* sel : stmt->selectors()) { Mark(sel); } - auto* sem_block = builder_->create( - stmt->body(), current_statement_); - builder_->Sem().Add(stmt->body(), sem_block); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block); - return BlockScope(stmt->body(), - [&] { return Statements(stmt->body()->list()); }); + return Scope(sem, [&] { return Statements(stmt->body()->list()); }); } bool Resolver::IfStatement(ast::IfStatement* stmt) { - Mark(stmt->condition()); - if (!Expression(stmt->condition())) { - return false; - } - - auto* cond_type = TypeOf(stmt->condition())->UnwrapRef(); - if (!cond_type->Is()) { - AddError("if statement condition must be bool, got " + - cond_type->FriendlyName(builder_->Symbols()), - stmt->condition()->source()); - return false; - } - - Mark(stmt->body()); - { - auto* sem_block = - builder_->create(stmt->body(), current_statement_); - builder_->Sem().Add(stmt->body(), sem_block); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block); - if (!BlockScope(stmt->body(), - [&] { return Statements(stmt->body()->list()); })) { + auto* sem = + builder_->create(stmt, current_compound_statement_); + builder_->Sem().Add(stmt, sem); + return Scope(sem, [&] { + Mark(stmt->condition()); + if (!Expression(stmt->condition())) { return false; } - } - for (auto* else_stmt : stmt->else_statements()) { - Mark(else_stmt); - auto* sem_else_stmt = - builder_->create(else_stmt, current_statement_); - builder_->Sem().Add(else_stmt, sem_else_stmt); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_else_stmt); - if (auto* cond = else_stmt->condition()) { + auto* cond_type = TypeOf(stmt->condition())->UnwrapRef(); + if (!cond_type->Is()) { + AddError("if statement condition must be bool, got " + + cond_type->FriendlyName(builder_->Symbols()), + stmt->condition()->source()); + return false; + } + + Mark(stmt->body()); + auto* body = builder_->create( + stmt->body(), current_compound_statement_); + builder_->Sem().Add(stmt->body(), body); + if (!Scope(body, [&] { return Statements(stmt->body()->list()); })) { + return false; + } + + for (auto* else_stmt : stmt->else_statements()) { + Mark(else_stmt); + if (!ElseStatement(else_stmt)) { + return false; + } + } + return true; + }); +} + +bool Resolver::ElseStatement(ast::ElseStatement* stmt) { + auto* sem = + builder_->create(stmt, current_compound_statement_); + builder_->Sem().Add(stmt, sem); + return Scope(sem, [&] { + if (auto* cond = stmt->condition()) { Mark(cond); if (!Expression(cond)) { return false; @@ -1877,95 +1886,93 @@ bool Resolver::IfStatement(ast::IfStatement* stmt) { return false; } } - Mark(else_stmt->body()); - { - auto* sem_block = builder_->create( - else_stmt->body(), current_statement_); - builder_->Sem().Add(else_stmt->body(), sem_block); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block); - if (!BlockScope(else_stmt->body(), - [&] { return Statements(else_stmt->body()->list()); })) { - return false; - } - } - } - return true; + + Mark(stmt->body()); + auto* body = builder_->create( + stmt->body(), current_compound_statement_); + builder_->Sem().Add(stmt->body(), body); + return Scope(body, [&] { return Statements(stmt->body()->list()); }); + }); +} + +bool Resolver::BlockStatement(ast::BlockStatement* stmt) { + auto* sem = builder_->create( + stmt->As(), current_compound_statement_); + builder_->Sem().Add(stmt, sem); + return Scope(sem, [&] { return Statements(stmt->list()); }); } bool Resolver::LoopStatement(ast::LoopStatement* stmt) { - // We don't call DetermineBlockStatement on the body and continuing block as - // these would make their BlockInfo siblings as in the AST, but we want the - // body BlockInfo to parent the continuing BlockInfo for semantics and - // validation. Also, we need to set their types differently. - Mark(stmt->body()); + auto* sem = + builder_->create(stmt, current_compound_statement_); + builder_->Sem().Add(stmt, sem); + return Scope(sem, [&] { + Mark(stmt->body()); - auto* sem_block_body = builder_->create( - stmt->body(), current_statement_); - builder_->Sem().Add(stmt->body(), sem_block_body); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_body); - return BlockScope(stmt->body(), [&] { - if (!Statements(stmt->body()->list())) { - return false; - } - if (stmt->continuing()) { // has_continuing() also checks for empty() - Mark(stmt->continuing()); - } - if (stmt->has_continuing()) { - auto* sem_block_continuing = - builder_->create( - stmt->continuing(), current_statement_); - builder_->Sem().Add(stmt->continuing(), sem_block_continuing); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_continuing); - if (!BlockScope(stmt->continuing(), - [&] { return Statements(stmt->continuing()->list()); })) { + auto* body = builder_->create( + stmt->body(), current_compound_statement_); + builder_->Sem().Add(stmt->body(), body); + return Scope(body, [&] { + if (!Statements(stmt->body()->list())) { return false; } - } - return true; + if (stmt->continuing()) { // has_continuing() also checks for empty() + Mark(stmt->continuing()); + } + if (stmt->has_continuing()) { + auto* continuing = builder_->create( + stmt->continuing(), current_compound_statement_); + builder_->Sem().Add(stmt->continuing(), continuing); + if (!Scope(continuing, + [&] { return Statements(stmt->continuing()->list()); })) { + return false; + } + } + return true; + }); }); } bool Resolver::ForLoopStatement(ast::ForLoopStatement* stmt) { - Mark(stmt->body()); - - auto* sem_block_body = builder_->create( - 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); - if (!Statement(initializer)) { - return false; - } - } - - if (auto* condition = stmt->condition()) { - Mark(condition); - if (!Expression(condition)) { - return false; + auto* sem = builder_->create( + stmt, current_compound_statement_); + builder_->Sem().Add(stmt, sem); + return Scope(sem, [&] { + if (auto* initializer = stmt->initializer()) { + Mark(initializer); + if (!Statement(initializer)) { + return false; + } } - if (!TypeOf(condition)->Is()) { - AddError("for-loop condition must be bool, got " + TypeNameOf(condition), - condition->source()); - return false; - } - } + if (auto* condition = stmt->condition()) { + Mark(condition); + if (!Expression(condition)) { + return false; + } - if (auto* continuing = stmt->continuing()) { - Mark(continuing); - if (!Statement(continuing)) { - return false; + if (!TypeOf(condition)->Is()) { + AddError( + "for-loop condition must be bool, got " + TypeNameOf(condition), + condition->source()); + return false; + } } - } - return BlockScope(stmt->body(), - [&] { return Statements(stmt->body()->list()); }); + if (auto* continuing = stmt->continuing()) { + Mark(continuing); + if (!Statement(continuing)) { + return false; + } + } + + Mark(stmt->body()); + + auto* body = builder_->create( + stmt->body(), current_compound_statement_); + builder_->Sem().Add(stmt->body(), body); + return Scope(body, [&] { return Statements(stmt->body()->statements()); }); + }); } bool Resolver::Expressions(const ast::ExpressionList& list) { @@ -3036,7 +3043,9 @@ bool Resolver::VariableDeclStatement(const ast::VariableDeclStatement* stmt) { } variable_stack_.set(var->symbol(), info); - current_block_->AddDecl(var); + if (current_block_) { // Not all statements are inside a block + current_block_->AddDecl(var); + } if (!ValidateVariable(info)) { return false; @@ -3858,26 +3867,26 @@ bool Resolver::ValidateSwitch(const ast::SwitchStatement* s) { return true; } -bool Resolver::Switch(ast::SwitchStatement* s) { - Mark(s->condition()); - if (!Expression(s->condition())) { - return false; - } - for (auto* case_stmt : s->body()) { - Mark(case_stmt); - - sem::Statement* sem_statement = - builder_->create(case_stmt, current_statement_); - builder_->Sem().Add(case_stmt, sem_statement); - TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement); - if (!CaseStatement(case_stmt)) { +bool Resolver::SwitchStatement(ast::SwitchStatement* stmt) { + auto* sem = + builder_->create(stmt, current_compound_statement_); + builder_->Sem().Add(stmt, sem); + return Scope(sem, [&] { + Mark(stmt->condition()); + if (!Expression(stmt->condition())) { return false; } - } - if (!ValidateSwitch(s)) { - return false; - } - return true; + for (auto* case_stmt : stmt->body()) { + Mark(case_stmt); + if (!CaseStatement(case_stmt)) { + return false; + } + } + if (!ValidateSwitch(stmt)) { + return false; + } + return true; + }); } bool Resolver::Assignment(ast::AssignmentStatement* a) { @@ -4032,18 +4041,22 @@ bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc, } template -bool Resolver::BlockScope(const ast::BlockStatement* block, F&& callback) { - auto* sem_block = builder_->Sem().Get(block); - if (!sem_block) { - TINT_ICE(Resolver, diagnostics_) - << "Resolver::BlockScope() called on a block for " - "which semantic information is not available"; - return false; - } - TINT_SCOPED_ASSIGNMENT(current_block_, - const_cast(sem_block)); +bool Resolver::Scope(sem::CompoundStatement* stmt, F&& callback) { + auto* prev_current_statement = current_statement_; + auto* prev_current_compound_statement = current_compound_statement_; + auto* prev_current_block = current_block_; + current_statement_ = stmt; + current_compound_statement_ = stmt; + current_block_ = stmt->As(); variable_stack_.push_scope(); - TINT_DEFER(variable_stack_.pop_scope()); + + TINT_DEFER({ + TINT_DEFER(variable_stack_.pop_scope()); + current_block_ = prev_current_block; + current_compound_statement_ = prev_current_compound_statement; + current_statement_ = prev_current_statement; + }); + return callback(); } diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h index bd2bf626ae..680efe54e4 100644 --- a/src/resolver/resolver.h +++ b/src/resolver/resolver.h @@ -242,9 +242,11 @@ class Resolver { bool Assignment(ast::AssignmentStatement* a); bool Binary(ast::BinaryExpression*); bool Bitcast(ast::BitcastExpression*); + bool BlockStatement(ast::BlockStatement*); bool Call(ast::CallExpression*); bool CaseStatement(ast::CaseStatement*); bool Constructor(ast::ConstructorExpression*); + bool ElseStatement(ast::ElseStatement*); bool Expression(ast::Expression*); bool Expressions(const ast::ExpressionList&); bool ForLoopStatement(ast::ForLoopStatement*); @@ -260,7 +262,7 @@ class Resolver { bool Return(ast::ReturnStatement* ret); bool Statement(ast::Statement*); bool Statements(const ast::StatementList&); - bool Switch(ast::SwitchStatement* s); + bool SwitchStatement(ast::SwitchStatement* s); bool UnaryOp(ast::UnaryOpExpression*); bool VariableDeclStatement(const ast::VariableDeclStatement*); @@ -394,11 +396,14 @@ class Resolver { const sem::Type* type, std::string type_name = ""); - /// Constructs a new semantic BlockStatement with the given type and with - /// #current_block_ as its parent, assigns this to #current_block_, and then - /// calls `callback`. The original #current_block_ is restored on exit. + /// Assigns `stmt` to #current_statement_, #current_compound_statement_, and + /// possibly #current_block_, pushes the variable scope, then calls + /// `callback`. Before returning #current_statement_, + /// #current_compound_statement_, and #current_block_ are restored to their + /// original values, and the variable scope is popped. + /// @returns the value returned by callback template - bool BlockScope(const ast::BlockStatement* block, F&& callback); + bool Scope(sem::CompoundStatement* stmt, F&& callback); /// Returns a human-readable string representation of the vector type name /// with the given parameters. @@ -449,7 +454,6 @@ class Resolver { ProgramBuilder* const builder_; diag::List& diagnostics_; std::unique_ptr const intrinsic_table_; - sem::BlockStatement* current_block_ = nullptr; ScopeStack variable_stack_; std::unordered_map symbol_to_function_; std::vector entry_points_; @@ -466,6 +470,8 @@ class Resolver { FunctionInfo* current_function_ = nullptr; sem::Statement* current_statement_ = nullptr; + sem::CompoundStatement* current_compound_statement_ = nullptr; + sem::BlockStatement* current_block_ = nullptr; BlockAllocator variable_infos_; BlockAllocator function_infos_; }; diff --git a/src/sem/block_statement.cc b/src/sem/block_statement.cc index 9cdb421faf..614fd06b3a 100644 --- a/src/sem/block_statement.cc +++ b/src/sem/block_statement.cc @@ -21,14 +21,12 @@ TINT_INSTANTIATE_TYPEINFO(tint::sem::BlockStatement); TINT_INSTANTIATE_TYPEINFO(tint::sem::FunctionBlockStatement); TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopBlockStatement); -TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopContinuingBlockStatement); -TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchCaseBlockStatement); namespace tint { namespace sem { BlockStatement::BlockStatement(const ast::BlockStatement* declaration, - const Statement* parent) + const CompoundStatement* parent) : Base(declaration, parent) {} BlockStatement::~BlockStatement() = default; @@ -47,25 +45,15 @@ FunctionBlockStatement::FunctionBlockStatement(const ast::Function* function) FunctionBlockStatement::~FunctionBlockStatement() = default; LoopBlockStatement::LoopBlockStatement(const ast::BlockStatement* declaration, - const Statement* parent) - : Base(declaration, parent) {} + const CompoundStatement* parent) + : Base(declaration, parent) { + TINT_ASSERT(Semantic, parent); +} LoopBlockStatement::~LoopBlockStatement() = default; void LoopBlockStatement::SetFirstContinue(size_t first_continue) { first_continue_ = first_continue; } -LoopContinuingBlockStatement::LoopContinuingBlockStatement( - const ast::BlockStatement* declaration, - const Statement* parent) - : Base(declaration, parent) {} -LoopContinuingBlockStatement::~LoopContinuingBlockStatement() = default; - -SwitchCaseBlockStatement::SwitchCaseBlockStatement( - const ast::BlockStatement* declaration, - const Statement* parent) - : Base(declaration, parent) {} -SwitchCaseBlockStatement::~SwitchCaseBlockStatement() = default; - } // namespace sem } // namespace tint diff --git a/src/sem/block_statement.h b/src/sem/block_statement.h index c61c3f888a..cc76148e5b 100644 --- a/src/sem/block_statement.h +++ b/src/sem/block_statement.h @@ -34,13 +34,13 @@ namespace sem { /// Holds semantic information about a block, such as parent block and variables /// declared in the block. -class BlockStatement : public Castable { +class BlockStatement : public Castable { public: /// Constructor /// @param declaration the AST node for this block statement /// @param parent the owning statement BlockStatement(const ast::BlockStatement* declaration, - const Statement* parent); + const CompoundStatement* parent); /// Destructor ~BlockStatement() override; @@ -49,33 +49,6 @@ class BlockStatement : public Castable { /// statement const ast::BlockStatement* Declaration() const; - /// @returns the closest enclosing block that satisfies the given predicate, - /// which may be the block itself, or nullptr if no match is found - /// @param pred a predicate that the resulting block must satisfy - template - const BlockStatement* FindFirstParent(Pred&& pred) const { - const BlockStatement* curr = this; - while (curr && !pred(curr)) { - curr = curr->Block(); - } - return curr; - } - - /// @returns the statement itself if it matches the template type `T`, - /// otherwise the nearest enclosing block that matches `T`, or nullptr if - /// there is none. - template - const T* FindFirstParent() const { - const BlockStatement* curr = this; - while (curr) { - if (auto* block = curr->As()) { - return block; - } - curr = curr->Block(); - } - return nullptr; - } - /// @returns the declarations associated with this block const std::vector& Decls() const { return decls_; } @@ -105,14 +78,14 @@ class FunctionBlockStatement ast::Function const* const function_; }; -/// Holds semantic information about a loop block or a for-loop block +/// Holds semantic information about a loop body block or for-loop body block class LoopBlockStatement : public Castable { public: /// Constructor /// @param declaration the AST node for this block statement /// @param parent the owning statement LoopBlockStatement(const ast::BlockStatement* declaration, - const Statement* parent); + const CompoundStatement* parent); /// Destructor ~LoopBlockStatement() override; @@ -134,34 +107,6 @@ class LoopBlockStatement : public Castable { size_t first_continue_ = kNoContinue; }; -/// Holds semantic information about a loop continuing block -class LoopContinuingBlockStatement - : public Castable { - public: - /// Constructor - /// @param declaration the AST node for this block statement - /// @param parent the owning statement - LoopContinuingBlockStatement(const ast::BlockStatement* declaration, - const Statement* parent); - - /// Destructor - ~LoopContinuingBlockStatement() override; -}; - -/// Holds semantic information about a switch case block -class SwitchCaseBlockStatement - : public Castable { - public: - /// Constructor - /// @param declaration the AST node for this block statement - /// @param parent the owning statement - SwitchCaseBlockStatement(const ast::BlockStatement* declaration, - const Statement* parent); - - /// Destructor - ~SwitchCaseBlockStatement() override; -}; - } // namespace sem } // namespace tint diff --git a/src/sem/for_loop_statement.cc b/src/sem/for_loop_statement.cc new file mode 100644 index 0000000000..59b6738996 --- /dev/null +++ b/src/sem/for_loop_statement.cc @@ -0,0 +1,31 @@ +// Copyright 2021 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/sem/for_loop_statement.h" + +#include "src/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::sem::ForLoopStatement); + +namespace tint { +namespace sem { + +ForLoopStatement::ForLoopStatement(const ast::ForLoopStatement* declaration, + CompoundStatement* parent) + : Base(declaration, parent) {} + +ForLoopStatement::~ForLoopStatement() = default; + +} // namespace sem +} // namespace tint diff --git a/src/sem/for_loop_statement.h b/src/sem/for_loop_statement.h new file mode 100644 index 0000000000..2ee92e4fed --- /dev/null +++ b/src/sem/for_loop_statement.h @@ -0,0 +1,45 @@ +// Copyright 2021 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_SEM_FOR_LOOP_STATEMENT_H_ +#define SRC_SEM_FOR_LOOP_STATEMENT_H_ + +#include "src/sem/statement.h" + +namespace tint { +namespace ast { +class ForLoopStatement; +} // namespace ast +} // namespace tint + +namespace tint { +namespace sem { + +/// Holds semantic information about a for-loop statement +class ForLoopStatement : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this for-loop statement + /// @param parent the owning statement + ForLoopStatement(const ast::ForLoopStatement* declaration, + CompoundStatement* parent); + + /// Destructor + ~ForLoopStatement() override; +}; + +} // namespace sem +} // namespace tint + +#endif // SRC_SEM_FOR_LOOP_STATEMENT_H_ diff --git a/src/sem/if_statement.cc b/src/sem/if_statement.cc new file mode 100644 index 0000000000..4b5d843fe4 --- /dev/null +++ b/src/sem/if_statement.cc @@ -0,0 +1,38 @@ +// Copyright 2021 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/sem/if_statement.h" + +#include "src/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::sem::IfStatement); +TINT_INSTANTIATE_TYPEINFO(tint::sem::ElseStatement); + +namespace tint { +namespace sem { + +IfStatement::IfStatement(const ast::IfStatement* declaration, + CompoundStatement* parent) + : Base(declaration, parent) {} + +IfStatement::~IfStatement() = default; + +ElseStatement::ElseStatement(const ast::ElseStatement* declaration, + CompoundStatement* parent) + : Base(declaration, parent) {} + +ElseStatement::~ElseStatement() = default; + +} // namespace sem +} // namespace tint diff --git a/src/sem/if_statement.h b/src/sem/if_statement.h new file mode 100644 index 0000000000..d615a449b3 --- /dev/null +++ b/src/sem/if_statement.h @@ -0,0 +1,59 @@ +// Copyright 2021 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_SEM_IF_STATEMENT_H_ +#define SRC_SEM_IF_STATEMENT_H_ + +#include "src/sem/statement.h" + +// Forward declarations +namespace tint { +namespace ast { +class IfStatement; +class ElseStatement; +} // namespace ast +} // namespace tint + +namespace tint { +namespace sem { + +/// Holds semantic information about an if statement +class IfStatement : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this if statement + /// @param parent the owning statement + IfStatement(const ast::IfStatement* declaration, CompoundStatement* parent); + + /// Destructor + ~IfStatement() override; +}; + +/// Holds semantic information about an else statement +class ElseStatement : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this else statement + /// @param parent the owning statement + ElseStatement(const ast::ElseStatement* declaration, + CompoundStatement* parent); + + /// Destructor + ~ElseStatement() override; +}; + +} // namespace sem +} // namespace tint + +#endif // SRC_SEM_IF_STATEMENT_H_ diff --git a/src/sem/loop_statement.cc b/src/sem/loop_statement.cc new file mode 100644 index 0000000000..62ea00d6e7 --- /dev/null +++ b/src/sem/loop_statement.cc @@ -0,0 +1,40 @@ +// Copyright 2021 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/sem/loop_statement.h" + +#include "src/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopStatement); +TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopContinuingBlockStatement); + +namespace tint { +namespace sem { + +LoopStatement::LoopStatement(const ast::LoopStatement* declaration, + CompoundStatement* parent) + : Base(declaration, parent) {} + +LoopStatement::~LoopStatement() = default; + +LoopContinuingBlockStatement::LoopContinuingBlockStatement( + const ast::BlockStatement* declaration, + const CompoundStatement* parent) + : Base(declaration, parent) { + TINT_ASSERT(Semantic, parent); +} +LoopContinuingBlockStatement::~LoopContinuingBlockStatement() = default; + +} // namespace sem +} // namespace tint diff --git a/src/sem/loop_statement.h b/src/sem/loop_statement.h new file mode 100644 index 0000000000..c80bc8c40b --- /dev/null +++ b/src/sem/loop_statement.h @@ -0,0 +1,60 @@ +// Copyright 2021 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_SEM_LOOP_STATEMENT_H_ +#define SRC_SEM_LOOP_STATEMENT_H_ + +#include "src/sem/block_statement.h" + +// Forward declarations +namespace tint { +namespace ast { +class LoopStatement; +} // namespace ast +} // namespace tint + +namespace tint { +namespace sem { + +/// Holds semantic information about a loop statement +class LoopStatement : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this loop statement + /// @param parent the owning statement + LoopStatement(const ast::LoopStatement* declaration, + CompoundStatement* parent); + + /// Destructor + ~LoopStatement() override; +}; + +/// Holds semantic information about a loop continuing block +class LoopContinuingBlockStatement + : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this block statement + /// @param parent the owning statement + LoopContinuingBlockStatement(const ast::BlockStatement* declaration, + const CompoundStatement* parent); + + /// Destructor + ~LoopContinuingBlockStatement() override; +}; + +} // namespace sem +} // namespace tint + +#endif // SRC_SEM_LOOP_STATEMENT_H_ diff --git a/src/sem/statement.cc b/src/sem/statement.cc index 85ece66871..0accb48b1b 100644 --- a/src/sem/statement.cc +++ b/src/sem/statement.cc @@ -21,32 +21,31 @@ #include "src/sem/statement.h" TINT_INSTANTIATE_TYPEINFO(tint::sem::Statement); +TINT_INSTANTIATE_TYPEINFO(tint::sem::CompoundStatement); namespace tint { namespace sem { -Statement::Statement(const ast::Statement* declaration, const Statement* parent) +Statement::Statement(const ast::Statement* declaration, + const CompoundStatement* parent) : declaration_(declaration), parent_(parent) {} const BlockStatement* Statement::Block() const { - auto* stmt = parent_; - while (stmt != nullptr) { - if (auto* block_stmt = stmt->As()) { - return block_stmt; - } - stmt = stmt->parent_; - } - return nullptr; + return FindFirstParent(); } const ast::Function* Statement::Function() const { - if (auto* block = Block()) { - if (auto* fbs = block->FindFirstParent()) { - return fbs->Function(); - } + if (auto* fbs = FindFirstParent()) { + return fbs->Function(); } return nullptr; } +CompoundStatement::CompoundStatement(const ast::Statement* declaration, + const CompoundStatement* parent) + : Base(declaration, parent) {} + +CompoundStatement::~CompoundStatement() = default; + } // namespace sem } // namespace tint diff --git a/src/sem/statement.h b/src/sem/statement.h index f58e7a7e2b..821a8a5f9a 100644 --- a/src/sem/statement.h +++ b/src/sem/statement.h @@ -31,19 +31,34 @@ class BlockStatement; namespace tint { namespace sem { +/// Forward declaration +class CompoundStatement; + /// Statement holds the semantic information for a statement. class Statement : public Castable { public: /// Constructor /// @param declaration the AST node for this statement /// @param parent the owning statement - Statement(const ast::Statement* declaration, const Statement* parent); + Statement(const ast::Statement* declaration, const CompoundStatement* parent); /// @return the AST node for this statement const ast::Statement* Declaration() const { return declaration_; } /// @return the statement that encloses this statement - const Statement* Parent() const { return parent_; } + const CompoundStatement* Parent() const { return parent_; } + + /// @returns the closest enclosing parent that satisfies the given predicate, + /// which may be the statement itself, or nullptr if no match is found + /// @param pred a predicate that the resulting block must satisfy + template + const CompoundStatement* FindFirstParent(Pred&& pred) const; + + /// @returns the statement itself if it matches the template type `T`, + /// otherwise the nearest enclosing statement that matches `T`, or nullptr if + /// there is none. + template + const T* FindFirstParent() const; /// @return the closest enclosing block for this statement const BlockStatement* Block() const; @@ -53,9 +68,52 @@ class Statement : public Castable { private: ast::Statement const* const declaration_; - Statement const* const parent_; + CompoundStatement const* const parent_; }; +/// CompoundStatement is the base class of statements that can hold other +/// statements. +class CompoundStatement : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this statement + /// @param parent the owning statement + CompoundStatement(const ast::Statement* declaration, + const CompoundStatement* parent); + + /// Destructor + ~CompoundStatement() override; +}; + +template +const CompoundStatement* Statement::FindFirstParent(Pred&& pred) const { + if (auto* self = As()) { + if (pred(self)) { + return self; + } + } + const auto* curr = parent_; + while (curr && !pred(curr)) { + curr = curr->Parent(); + } + return curr; +} + +template +const T* Statement::FindFirstParent() const { + if (auto* p = As()) { + return p; + } + const auto* curr = parent_; + while (curr) { + if (auto* p = curr->As()) { + return p; + } + curr = curr->Parent(); + } + return nullptr; +} + } // namespace sem } // namespace tint diff --git a/src/sem/switch_statement.cc b/src/sem/switch_statement.cc new file mode 100644 index 0000000000..c99ff719bd --- /dev/null +++ b/src/sem/switch_statement.cc @@ -0,0 +1,40 @@ +// Copyright 2021 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/sem/switch_statement.h" + +#include "src/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchStatement); +TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchCaseBlockStatement); + +namespace tint { +namespace sem { + +SwitchStatement::SwitchStatement(const ast::SwitchStatement* declaration, + CompoundStatement* parent) + : Base(declaration, parent) {} + +SwitchStatement::~SwitchStatement() = default; + +SwitchCaseBlockStatement::SwitchCaseBlockStatement( + const ast::BlockStatement* declaration, + const CompoundStatement* parent) + : Base(declaration, parent) { + TINT_ASSERT(Semantic, parent); +} +SwitchCaseBlockStatement::~SwitchCaseBlockStatement() = default; + +} // namespace sem +} // namespace tint diff --git a/src/sem/switch_statement.h b/src/sem/switch_statement.h new file mode 100644 index 0000000000..f7bb735a1a --- /dev/null +++ b/src/sem/switch_statement.h @@ -0,0 +1,60 @@ +// Copyright 2021 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_SEM_SWITCH_STATEMENT_H_ +#define SRC_SEM_SWITCH_STATEMENT_H_ + +#include "src/sem/block_statement.h" + +// Forward declarations +namespace tint { +namespace ast { +class SwitchStatement; +} // namespace ast +} // namespace tint + +namespace tint { +namespace sem { + +/// Holds semantic information about an switch statement +class SwitchStatement : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this switch statement + /// @param parent the owning statement + SwitchStatement(const ast::SwitchStatement* declaration, + CompoundStatement* parent); + + /// Destructor + ~SwitchStatement() override; +}; + +/// Holds semantic information about a switch case block +class SwitchCaseBlockStatement + : public Castable { + public: + /// Constructor + /// @param declaration the AST node for this block statement + /// @param parent the owning statement + SwitchCaseBlockStatement(const ast::BlockStatement* declaration, + const CompoundStatement* parent); + + /// Destructor + ~SwitchCaseBlockStatement() override; +}; + +} // namespace sem +} // namespace tint + +#endif // SRC_SEM_SWITCH_STATEMENT_H_ diff --git a/test/BUILD.gn b/test/BUILD.gn index f80e0726b5..5192ad9ac7 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -229,10 +229,10 @@ tint_unittests_source_set("tint_unittests_core_src") { "../src/resolver/assignment_validation_test.cc", "../src/resolver/atomics_test.cc", "../src/resolver/atomics_validation_test.cc", - "../src/resolver/block_test.cc", "../src/resolver/builtins_validation_test.cc", "../src/resolver/call_test.cc", "../src/resolver/call_validation_test.cc", + "../src/resolver/compound_statement_test.cc", "../src/resolver/control_block_validation_test.cc", "../src/resolver/decoration_validation_test.cc", "../src/resolver/entry_point_validation_test.cc",