From 1c755b68e6ff93fa734c43e5b45e4cd528c92c11 Mon Sep 17 00:00:00 2001 From: dan sinclair Date: Tue, 1 Nov 2022 17:11:25 +0000 Subject: [PATCH] [IR] Add support for `for` statements This CL adds the ability to convert a for statement into a loop control flow node. Bug: tint:1718 Change-Id: Ibd55ae3b202518d3362267eaa1f507dce6a9fe56 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/107804 Kokoro: Kokoro Commit-Queue: Dan Sinclair Reviewed-by: Ben Clayton --- src/tint/ir/builder_impl.cc | 55 +++++++++++++++++- src/tint/ir/builder_impl.h | 6 ++ src/tint/ir/builder_impl_test.cc | 97 ++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc index 99733f8ea6..224d1ae3d1 100644 --- a/src/tint/ir/builder_impl.cc +++ b/src/tint/ir/builder_impl.cc @@ -20,6 +20,7 @@ #include "src/tint/ast/break_statement.h" #include "src/tint/ast/continue_statement.h" #include "src/tint/ast/fallthrough_statement.h" +#include "src/tint/ast/for_loop_statement.h" #include "src/tint/ast/function.h" #include "src/tint/ast/if_statement.h" #include "src/tint/ast/loop_statement.h" @@ -210,7 +211,7 @@ bool BuilderImpl::EmitStatement(const ast::Statement* stmt) { [&](const ast::FallthroughStatement*) { return EmitFallthrough(); }, [&](const ast::IfStatement* i) { return EmitIf(i); }, [&](const ast::LoopStatement* l) { return EmitLoop(l); }, - // [&](const ast::ForLoopStatement* l) { }, + [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); }, [&](const ast::WhileStatement* l) { return EmitWhile(l); }, [&](const ast::ReturnStatement* r) { return EmitReturn(r); }, [&](const ast::SwitchStatement* s) { return EmitSwitch(s); }, @@ -352,6 +353,58 @@ bool BuilderImpl::EmitWhile(const ast::WhileStatement* stmt) { return true; } +bool BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) { + auto* loop_node = builder_.CreateLoop(stmt); + builder_.Branch(loop_node->continuing_target, loop_node->start_target); + + if (stmt->initializer) { + // Emit the for initializer before branching to the loop + if (!EmitStatement(stmt->initializer)) { + return false; + } + } + + BranchTo(loop_node); + + ast_to_flow_[stmt] = loop_node; + + { + FlowStackScope scope(this, loop_node); + + current_flow_block_ = loop_node->start_target; + + if (stmt->condition) { + // TODO(dsinclair): Emit the instructions for the condition + + // Create an if (cond) {} else {break;} control flow + auto* if_node = builder_.CreateIf(nullptr); + builder_.Branch(if_node->true_target, if_node->merge_target); + builder_.Branch(if_node->false_target, loop_node->merge_target); + // TODO(dsinclair): set if condition register into if flow node + + BranchTo(if_node); + current_flow_block_ = if_node->merge_target; + } + + if (!EmitStatement(stmt->body)) { + return false; + } + + BranchToIfNeeded(loop_node->continuing_target); + + if (stmt->continuing) { + current_flow_block_ = loop_node->continuing_target; + if (!EmitStatement(stmt->continuing)) { + return false; + } + } + } + // The while loop always has a path to the merge target as the break statement comes before + // anything inside the loop. + current_flow_block_ = loop_node->merge_target; + return true; +} + bool BuilderImpl::EmitSwitch(const ast::SwitchStatement* stmt) { auto* switch_node = builder_.CreateSwitch(stmt); diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h index 7aa2b119ea..d6c4bc480d 100644 --- a/src/tint/ir/builder_impl.h +++ b/src/tint/ir/builder_impl.h @@ -34,6 +34,7 @@ class BlockStatement; class BreakIfStatement; class BreakStatement; class ContinueStatement; +class ForLoopStatement; class Function; class IfStatement; class LoopStatement; @@ -108,6 +109,11 @@ class BuilderImpl { /// @returns true if successful, false otherwise. bool EmitWhile(const ast::WhileStatement* stmt); + /// Emits a loop control node to the IR. + /// @param stmt the for loop statement + /// @returns true if successful, false otherwise. + bool EmitForLoop(const ast::ForLoopStatement* stmt); + /// Emits a switch statement /// @param stmt the switch statement /// @returns true if successful, false otherwise. diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc index 2f5fba24fc..1e2325e21d 100644 --- a/src/tint/ir/builder_impl_test.cc +++ b/src/tint/ir/builder_impl_test.cc @@ -942,6 +942,103 @@ TEST_F(IRBuilderImplTest, While_Return) { EXPECT_EQ(flow->merge_target->branch_target, func->end_target); } +// TODO(dsinclair): Enable when variable declarations and increment are supported +TEST_F(IRBuilderImplTest, DISABLED_For) { + // for(var i: 0; i < 10; i++) { + // } + // + // func -> loop -> loop start -> if true + // -> if false + // + // [if true] -> if merge + // [if false] -> loop merge + // [if merge] -> loop continuing + // [loop continuing] -> loop start + // [loop merge] -> func end + // + auto* ast_for = For(Decl(Var("i", ty.i32())), LessThan("i", 10_a), Increment("i"), Block()); + WrapInFunction(ast_for); + auto& b = Build(); + + auto r = b.Build(); + ASSERT_TRUE(r) << b.error(); + auto m = r.Move(); + + auto* ir_for = b.FlowNodeForAstNode(ast_for); + ASSERT_NE(ir_for, nullptr); + ASSERT_TRUE(ir_for->Is()); + + auto* flow = ir_for->As(); + ASSERT_NE(flow->start_target, nullptr); + ASSERT_NE(flow->continuing_target, nullptr); + ASSERT_NE(flow->merge_target, nullptr); + + ASSERT_NE(flow->start_target->branch_target, nullptr); + ASSERT_TRUE(flow->start_target->branch_target->Is()); + auto* if_flow = flow->start_target->branch_target->As(); + ASSERT_NE(if_flow->true_target, nullptr); + ASSERT_NE(if_flow->false_target, nullptr); + ASSERT_NE(if_flow->merge_target, nullptr); + + ASSERT_EQ(1u, m.functions.Length()); + auto* func = m.functions[0]; + + EXPECT_EQ(1u, func->end_target->inbound_branches.Length()); + EXPECT_EQ(1u, flow->inbound_branches.Length()); + EXPECT_EQ(2u, flow->start_target->inbound_branches.Length()); + EXPECT_EQ(1u, flow->continuing_target->inbound_branches.Length()); + EXPECT_EQ(1u, flow->merge_target->inbound_branches.Length()); + EXPECT_EQ(1u, if_flow->true_target->inbound_branches.Length()); + EXPECT_EQ(1u, if_flow->false_target->inbound_branches.Length()); + EXPECT_EQ(1u, if_flow->merge_target->inbound_branches.Length()); + + EXPECT_EQ(func->start_target->branch_target, flow); + EXPECT_EQ(flow->start_target->branch_target, if_flow); + EXPECT_EQ(if_flow->true_target->branch_target, if_flow->merge_target); + EXPECT_EQ(if_flow->false_target->branch_target, flow->merge_target); + EXPECT_EQ(if_flow->merge_target->branch_target, flow->continuing_target); + EXPECT_EQ(flow->continuing_target->branch_target, flow->start_target); + EXPECT_EQ(flow->merge_target->branch_target, func->end_target); +} + +TEST_F(IRBuilderImplTest, For_NoInitCondOrContinuing) { + // for (;;) { + // break; + // } + // + // func -> loop -> loop start -> loop merge -> func end + // + auto* ast_for = For(nullptr, nullptr, nullptr, Block(Break())); + WrapInFunction(ast_for); + auto& b = Build(); + + auto r = b.Build(); + ASSERT_TRUE(r) << b.error(); + auto m = r.Move(); + + auto* ir_for = b.FlowNodeForAstNode(ast_for); + ASSERT_NE(ir_for, nullptr); + ASSERT_TRUE(ir_for->Is()); + + auto* flow = ir_for->As(); + ASSERT_NE(flow->start_target, nullptr); + ASSERT_NE(flow->continuing_target, nullptr); + ASSERT_NE(flow->merge_target, nullptr); + + ASSERT_EQ(1u, m.functions.Length()); + auto* func = m.functions[0]; + + EXPECT_EQ(1u, flow->inbound_branches.Length()); + EXPECT_EQ(2u, flow->start_target->inbound_branches.Length()); + EXPECT_EQ(0u, flow->continuing_target->inbound_branches.Length()); + EXPECT_EQ(1u, flow->merge_target->inbound_branches.Length()); + EXPECT_EQ(1u, func->end_target->inbound_branches.Length()); + + EXPECT_EQ(flow->start_target->branch_target, flow->merge_target); + EXPECT_EQ(flow->continuing_target->branch_target, flow->start_target); + EXPECT_EQ(flow->merge_target->branch_target, func->end_target); +} + TEST_F(IRBuilderImplTest, Switch) { // func -> switch -> case 1 // -> case 2