[spirv-reader] Emit loop and continuing
Bug: tint:3
Change-Id: Iaced5ee41f6b27ab350432fc1c2cdff6042ba191
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22423
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 50c947a..d6a25bd 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -1556,7 +1556,9 @@
auto outer_kind = entering_constructs[1]->kind;
if (outer_kind != Construct::kContinue) {
return Fail() << "internal error: bad construct nesting. Only Continue "
- "construct can be outer construct on same block";
+ "construct can be outer construct on same block. Got "
+ "outer kind "
+ << int(outer_kind) << " inner kind " << int(inner_kind);
}
if (inner_kind == Construct::kContinue) {
return Fail() << "internal error: unsupported construct nesting: "
@@ -1588,10 +1590,28 @@
return Fail() << "internal error: nested function construct";
case Construct::kLoop:
- return Fail() << "unhandled: loop construct";
+ if (!EmitLoopStart(construct)) {
+ return false;
+ }
+ if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+ return false;
+ }
+ break;
case Construct::kContinue:
- return Fail() << "unhandled: continue construct";
+ if (block_info.is_single_block_loop) {
+ if (!EmitLoopStart(construct)) {
+ return false;
+ }
+ if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+ return false;
+ }
+ } else {
+ if (!EmitContinuingStart(construct)) {
+ return false;
+ }
+ }
+ break;
case Construct::kIfSelection:
if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
@@ -1709,6 +1729,30 @@
return success();
}
+bool FunctionEmitter::EmitLoopStart(const Construct* construct) {
+ auto* loop = AddStatement(std::make_unique<ast::LoopStatement>())->AsLoop();
+ PushNewStatementBlock(
+ construct, construct->end_id,
+ [loop](StatementBlock* s) { loop->set_body(std::move(s->statements)); });
+ return success();
+}
+
+bool FunctionEmitter::EmitContinuingStart(const Construct* construct) {
+ // A continue construct has the same depth as its associated loop
+ // construct. Start a continue construct.
+ auto* loop_candidate = LastStatement();
+ if (!loop_candidate->IsLoop()) {
+ return Fail() << "internal error: starting continue construct, "
+ "expected loop on top of stack";
+ }
+ auto* loop = loop_candidate->AsLoop();
+ PushNewStatementBlock(construct, construct->end_id,
+ [loop](StatementBlock* s) {
+ loop->set_continuing(std::move(s->statements));
+ });
+ return success();
+}
+
bool FunctionEmitter::EmitNormalTerminator(const BlockInfo&) {
// TODO(dneto): emit fallthrough, break, continue, return, kill
return true;
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index a7fec4c..f53dbef 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -297,6 +297,20 @@
/// @returns false if emission failed.
bool EmitIfStart(const BlockInfo& block_info);
+ /// Emits a LoopStatement, and pushes a new StatementBlock to accumulate
+ /// the remaining instructions in the current block and subsequent blocks
+ /// in the loop.
+ /// @param construct the loop construct
+ /// @returns false if emission failed.
+ bool EmitLoopStart(const Construct* construct);
+
+ /// Emits a ContinuingStatement, and pushes a new StatementBlock to accumulate
+ /// the remaining instructions in the current block and subsequent blocks
+ /// in the continue construct.
+ /// @param construct the continue construct
+ /// @returns false if emission failed.
+ bool EmitContinuingStart(const Construct* construct);
+
/// Emits the non-control-flow parts of a basic block, but only once.
/// The |already_emitted| parameter indicates whether the code has already
/// been emitted, and is used to signal that this invocation actually emitted
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index e10b3b0..1460556 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -7226,6 +7226,406 @@
)"));
}
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_TrueBackedge) {
+ // TODO(dneto): emit conditional break
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %20 %99
+
+ %99 = OpLabel
+ OpStore %var %999
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_SingleBlock_FalseBackedge) {
+ // TODO(dneto): emit conditional break
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %99 %20
+
+ %99 = OpLabel
+ OpStore %var %999
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_BothBackedge) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %20 %20
+
+ %99 = OpLabel
+ OpStore %var %999
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_UnconditionalBackege) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %20 None
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %999
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_SingleBlockContinue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %50 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %50
+
+ %50 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %999
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{999}
+}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_MultiBlockContinue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %50 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %50
+
+ %50 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %60
+
+ %60 = OpLabel
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %999
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{999}
+}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_ContinueNestIf) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %50 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %50
+
+ %50 = OpLabel ; continue target; also if-header
+ OpStore %var %uint_3
+ OpSelectionMerge %80 None
+ OpBranchConditional %cond2 %60 %80
+
+ %60 = OpLabel
+ OpStore %var %uint_4
+ OpBranch %80
+
+ %80 = OpLabel
+ OpStore %var %uint_5
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %999
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+ }
+ Else{
+ {
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{999}
+}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_Never) {
+ // Test case where both branches exit. e.g both go to merge.
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_HeaderBreakAndContinue) {
+ // Header block branches to merge, and to an outer continue.
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_TrueToBody) {
+ // TODO(dneto): Needs break-unless
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_FalseToBody) {
+ // TODO(dneto): Needs break-if
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_NestedIfContinue) {
+ // TODO(dneto): Needs "continue" terminator support
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyAlwaysBreaks) {
+ // TODO(dneto): Needs "continue" terminator support
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyConditionallyBreaks) {
+ // TODO(dneto): Needs "break" support
+}
+
} // namespace
} // namespace spirv
} // namespace reader