From d8613596e27f166d85b2343457dda1dc01c07fdc Mon Sep 17 00:00:00 2001 From: David Neto Date: Tue, 2 Jun 2020 16:46:51 +0000 Subject: [PATCH] [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 --- src/reader/spirv/function.cc | 50 +++- src/reader/spirv/function.h | 14 + src/reader/spirv/function_cfg_test.cc | 400 ++++++++++++++++++++++++++ 3 files changed, 461 insertions(+), 3 deletions(-) diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc index 50c947a243..d6a25bda31 100644 --- a/src/reader/spirv/function.cc +++ b/src/reader/spirv/function.cc @@ -1556,7 +1556,9 @@ bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) { 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 @@ bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) { 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 @@ bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) { return success(); } +bool FunctionEmitter::EmitLoopStart(const Construct* construct) { + auto* loop = AddStatement(std::make_unique())->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 a7fec4c323..f53dbef177 100644 --- a/src/reader/spirv/function.h +++ b/src/reader/spirv/function.h @@ -297,6 +297,20 @@ class FunctionEmitter { /// @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 e10b3b014b..14605563d6 100644 --- a/src/reader/spirv/function_cfg_test.cc +++ b/src/reader/spirv/function_cfg_test.cc @@ -7226,6 +7226,406 @@ Assignment{ )")); } +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