[spirv-writer] Add start of break and continue.
This CL adds the beginning of break and continue support. The
conditional versions are not supported, just the non-conditional.
Bug: tint:5
Change-Id: I84418cffd3e29dc011c4313bf9aa3da4833c009f
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/20500
Reviewed-by: David Neto <dneto@google.com>
diff --git a/.gitignore b/.gitignore
index 6d3242f..139e452 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
build
buildtools
out
+testing
third_party/cpplint
third_party/binutils
third_party/googletest
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 86fc03a..1f24a10 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -495,7 +495,8 @@
const auto block_id = block.id();
auto* block_info = GetBlockInfo(block_id);
if (!block_info) {
- return Fail() << "internal error: block " << block_id << " missing; blocks should already "
+ return Fail() << "internal error: block " << block_id
+ << " missing; blocks should already "
"have been registered";
}
@@ -617,48 +618,47 @@
if (merge == 0) {
continue;
}
- // This is a header.
- const auto header = block_id;
- const auto* header_info = block_info;
- const auto header_pos = header_info->pos;
- const auto merge_pos = GetBlockInfo(merge)->pos;
+ // This is a header.
+ const auto header = block_id;
+ const auto* header_info = block_info;
+ const auto header_pos = header_info->pos;
+ const auto merge_pos = GetBlockInfo(merge)->pos;
- // Pos(H) < Pos(M(H))
- // Note: When recording merges we made sure H != M(H)
- if (merge_pos <= header_pos) {
- return Fail() << "Header " << header
- << " does not strictly dominate its merge block "
- << merge;
- // TODO(dneto): Report a path from the entry block to the merge block
- // without going through the header block.
- }
+ // Pos(H) < Pos(M(H))
+ // Note: When recording merges we made sure H != M(H)
+ if (merge_pos <= header_pos) {
+ return Fail() << "Header " << header
+ << " does not strictly dominate its merge block " << merge;
+ // TODO(dneto): Report a path from the entry block to the merge block
+ // without going through the header block.
+ }
- const auto ct = block_info->continue_for_header;
- if (ct == 0) {
- continue;
- }
- // Furthermore, this is a loop header.
- const auto* ct_info = GetBlockInfo(ct);
- const auto ct_pos = ct_info->pos;
- // Pos(H) <= Pos(CT(H)), with equality only for single-block loops.
- if (header_info->is_single_block_loop && ct_pos != header_pos) {
- Fail() << "Internal error: Single block loop. CT pos is not the "
- "header pos. Should have already checked this";
- }
- if (!header_info->is_single_block_loop && (ct_pos <= header_pos)) {
- Fail() << "Loop header " << header
- << " does not dominate its continue target " << ct;
- }
- // Pos(CT(H)) < Pos(M(H))
- // Note: When recording merges we made sure CT(H) != M(H)
- if (merge_pos <= ct_pos) {
- return Fail() << "Merge block " << merge
- << " for loop headed at block " << header
- << " appears at or before the loop's continue "
- "construct headed by "
- "block "
- << ct;
- }
+ const auto ct = block_info->continue_for_header;
+ if (ct == 0) {
+ continue;
+ }
+ // Furthermore, this is a loop header.
+ const auto* ct_info = GetBlockInfo(ct);
+ const auto ct_pos = ct_info->pos;
+ // Pos(H) <= Pos(CT(H)), with equality only for single-block loops.
+ if (header_info->is_single_block_loop && ct_pos != header_pos) {
+ Fail() << "Internal error: Single block loop. CT pos is not the "
+ "header pos. Should have already checked this";
+ }
+ if (!header_info->is_single_block_loop && (ct_pos <= header_pos)) {
+ Fail() << "Loop header " << header
+ << " does not dominate its continue target " << ct;
+ }
+ // Pos(CT(H)) < Pos(M(H))
+ // Note: When recording merges we made sure CT(H) != M(H)
+ if (merge_pos <= ct_pos) {
+ return Fail() << "Merge block " << merge << " for loop headed at block "
+ << header
+ << " appears at or before the loop's continue "
+ "construct headed by "
+ "block "
+ << ct;
+ }
}
return success();
}
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index a3a7e89..2a32c8e 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -84,6 +84,19 @@
return model;
}
+// A terminator is anything which will case a SPIR-V terminator to be emitted.
+// This means things like breaks, fallthroughs and continues which all emit an
+// OpBranch or return for the OpReturn emission.
+bool LastIsTerminator(const ast::StatementList& stmts) {
+ if (stmts.empty()) {
+ return false;
+ }
+
+ auto* last = stmts.back().get();
+ return last->IsBreak() || last->IsContinue() || last->IsReturn() ||
+ last->IsKill() || last->IsFallthrough();
+}
+
} // namespace
Builder::Builder(ast::Module* mod) : mod_(mod), scope_stack_({}) {}
@@ -178,6 +191,24 @@
return true;
}
+bool Builder::GenerateBreakStatement(ast::BreakStatement*) {
+ if (merge_stack_.empty()) {
+ error_ = "Attempted to break with a merge block";
+ return false;
+ }
+ push_function_inst(spv::Op::OpBranch, {Operand::Int(merge_stack_.back())});
+ return true;
+}
+
+bool Builder::GenerateContinueStatement(ast::ContinueStatement*) {
+ if (continue_stack_.empty()) {
+ error_ = "Attempted to continue with a continue block";
+ return false;
+ }
+ push_function_inst(spv::Op::OpBranch, {Operand::Int(continue_stack_.back())});
+ return true;
+}
+
bool Builder::GenerateEntryPoint(ast::EntryPoint* ep) {
auto name = ep->name();
if (name.empty()) {
@@ -988,12 +1019,19 @@
{Operand::Int(merge_block_id), Operand::Int(continue_block_id),
Operand::Int(SpvLoopControlMaskNone)});
+ continue_stack_.push_back(continue_block_id);
+ merge_stack_.push_back(merge_block_id);
+
push_function_inst(spv::Op::OpBranch, {Operand::Int(body_block_id)});
push_function_inst(spv::Op::OpLabel, {body_block});
if (!GenerateStatementList(stmt->body())) {
return false;
}
- push_function_inst(spv::Op::OpBranch, {Operand::Int(continue_block_id)});
+
+ // We only branch if the last element of the body didn't already branch.
+ if (!LastIsTerminator(stmt->body())) {
+ push_function_inst(spv::Op::OpBranch, {Operand::Int(continue_block_id)});
+ }
push_function_inst(spv::Op::OpLabel, {continue_block});
if (!GenerateStatementList(stmt->continuing())) {
@@ -1001,6 +1039,9 @@
}
push_function_inst(spv::Op::OpBranch, {Operand::Int(loop_header_id)});
+ merge_stack_.pop_back();
+ continue_stack_.pop_back();
+
push_function_inst(spv::Op::OpLabel, {merge_block});
return true;
@@ -1019,6 +1060,12 @@
if (stmt->IsAssign()) {
return GenerateAssignStatement(stmt->AsAssign());
}
+ if (stmt->IsBreak()) {
+ return GenerateBreakStatement(stmt->AsBreak());
+ }
+ if (stmt->IsContinue()) {
+ return GenerateContinueStatement(stmt->AsContinue());
+ }
if (stmt->IsIf()) {
return GenerateIfStatement(stmt->AsIf());
}
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index a425cb1..32af780 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -149,6 +149,14 @@
/// @param assign the statement to generate
/// @returns true if the statement was successfully generated
bool GenerateAssignStatement(ast::AssignmentStatement* assign);
+ /// Generates a break statement
+ /// @param stmt the statement to generate
+ /// @returns true if the statement was successfully generated
+ bool GenerateBreakStatement(ast::BreakStatement* stmt);
+ /// Generates a continue statement
+ /// @param stmt the statement to generate
+ /// @returns true if the statement was successfully generated
+ bool GenerateContinueStatement(ast::ContinueStatement* stmt);
/// Generates an entry point instruction
/// @param ep the entry point
/// @returns true if the instruction was generated, false otherwise
@@ -308,6 +316,8 @@
std::unordered_map<std::string, uint32_t> const_to_id_;
ScopeStack<uint32_t> scope_stack_;
std::unordered_map<uint32_t, ast::Variable*> spirv_id_to_variable_;
+ std::vector<uint32_t> merge_stack_;
+ std::vector<uint32_t> continue_stack_;
};
} // namespace spirv
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index 99f1f5c..f09f0cf 100644
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/writer/spirv/builder_accessor_expression_test.cc
@@ -278,8 +278,8 @@
ast::type::AliasType alias("Inner", &inner_struct);
ast::StructMemberList outer_members;
- outer_members.push_back(std::make_unique<ast::StructMember>(
- "inner", &alias, std::move(decos)));
+ outer_members.push_back(
+ std::make_unique<ast::StructMember>("inner", &alias, std::move(decos)));
ast::type::StructType s_type(std::make_unique<ast::Struct>(
ast::StructDecoration::kNone, std::move(outer_members)));
diff --git a/src/writer/spirv/builder_loop_test.cc b/src/writer/spirv/builder_loop_test.cc
index ec258d9..6333fad 100644
--- a/src/writer/spirv/builder_loop_test.cc
+++ b/src/writer/spirv/builder_loop_test.cc
@@ -16,6 +16,8 @@
#include "gtest/gtest.h"
#include "src/ast/assignment_statement.h"
+#include "src/ast/break_statement.h"
+#include "src/ast/continue_statement.h"
#include "src/ast/identifier_expression.h"
#include "src/ast/int_literal.h"
#include "src/ast/loop_statement.h"
@@ -166,9 +168,73 @@
)");
}
-TEST_F(BuilderTest, DISABLED_Loop_WithContinuing) {}
+TEST_F(BuilderTest, Loop_WithContinue) {
+ // loop {
+ // continue;
+ // }
+ ast::StatementList body;
+ body.push_back(std::make_unique<ast::ContinueStatement>());
-TEST_F(BuilderTest, DISABLED_Loop_WithBreak) {}
+ ast::StatementList continuing;
+ ast::LoopStatement expr(std::move(body), std::move(continuing));
+
+ Context ctx;
+ ast::Module mod;
+ TypeDeterminer td(&ctx, &mod);
+ ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
+
+ Builder b(&mod);
+ b.push_function(Function{});
+
+ EXPECT_TRUE(b.GenerateLoopStatement(&expr)) << b.error();
+ EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+ R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, DISABLED_Loop_WithConditionalContinue) {}
+
+TEST_F(BuilderTest, Loop_WithBreak) {
+ // loop {
+ // break;
+ // }
+ ast::StatementList body;
+ body.push_back(std::make_unique<ast::BreakStatement>());
+
+ ast::StatementList continuing;
+ ast::LoopStatement expr(std::move(body), std::move(continuing));
+
+ Context ctx;
+ ast::Module mod;
+ TypeDeterminer td(&ctx, &mod);
+ ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
+
+ Builder b(&mod);
+ b.push_function(Function{});
+
+ EXPECT_TRUE(b.GenerateLoopStatement(&expr)) << b.error();
+ EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+ R"(OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 None
+OpBranch %4
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+)");
+}
+
+TEST_F(BuilderTest, DISABLED_Loop_WithConditionalBreak) {}
} // namespace
} // namespace spirv