[spirv-reader] Support ifbreak with other forward edge
Add a guard variable for flow control within that if-selection.
Also, the premerge blocks are always surrounded by an if-selection,
to ensure we cause reconvergence at the end of the original if-selection
construct, just like in the original SPIR-V.
Bug: tint:3
Change-Id: I614c6840e539bf9a338058beb5b6f70484e3320a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/23182
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 75276f2..99b44d6 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -15,6 +15,7 @@
#include "src/reader/spirv/function.h"
#include <algorithm>
+#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <utility>
@@ -29,6 +30,7 @@
#include "src/ast/as_expression.h"
#include "src/ast/assignment_statement.h"
#include "src/ast/binary_expression.h"
+#include "src/ast/bool_literal.h"
#include "src/ast/break_statement.h"
#include "src/ast/call_expression.h"
#include "src/ast/case_statement.h"
@@ -45,6 +47,7 @@
#include "src/ast/sint_literal.h"
#include "src/ast/storage_class.h"
#include "src/ast/switch_statement.h"
+#include "src/ast/type/bool_type.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/type_constructor_expression.h"
#include "src/ast/uint_literal.h"
@@ -438,6 +441,36 @@
StatementBlock{construct, end_id, action, ast::StatementList{}, nullptr});
}
+void FunctionEmitter::PushGuard(const std::string& guard_name,
+ uint32_t end_id) {
+ assert(!statements_stack_.empty());
+ assert(!guard_name.empty());
+ // Guard control flow by the guard variable. Introduce a new
+ // if-selection with a then-clause ending at the same block
+ // as the statement block at the top of the stack.
+ const auto& top = statements_stack_.back();
+ auto* const guard_stmt =
+ AddStatement(std::make_unique<ast::IfStatement>())->AsIf();
+ guard_stmt->set_condition(
+ std::make_unique<ast::IdentifierExpression>(guard_name));
+ PushNewStatementBlock(top.construct_, end_id,
+ [guard_stmt](StatementBlock* s) {
+ guard_stmt->set_body(std::move(s->statements_));
+ });
+}
+
+void FunctionEmitter::PushTrueGuard(uint32_t end_id) {
+ assert(!statements_stack_.empty());
+ const auto& top = statements_stack_.back();
+ auto* const guard_stmt =
+ AddStatement(std::make_unique<ast::IfStatement>())->AsIf();
+ guard_stmt->set_condition(MakeTrue());
+ PushNewStatementBlock(top.construct_, end_id,
+ [guard_stmt](StatementBlock* s) {
+ guard_stmt->set_body(std::move(s->statements_));
+ });
+}
+
const ast::StatementList& FunctionEmitter::ast_body() {
assert(!statements_stack_.empty());
return statements_stack_[0].statements_;
@@ -1141,10 +1174,13 @@
// There should only be one backedge per backedge block.
uint32_t num_backedges = 0;
- // Track destinations for normal forward edges, either kForward,
- // kCaseFallThrough, or kIfBreak. These count toward the need
- // to have a merge instruction.
+ // Track destinations for normal forward edges, either kForward
+ // or kCaseFallThroughkIfBreak. These count toward the need
+ // to have a merge instruction. We also track kIfBreak edges
+ // because when used with normal forward edges, we'll need
+ // to generate a flow guard variable.
std::vector<uint32_t> normal_forward_edges;
+ std::vector<uint32_t> if_break_edges;
if (successors.empty() && src_construct.enclosing_continue) {
// Kill and return are not allowed in a continue construct.
@@ -1263,10 +1299,12 @@
// The edge-kind has been finalized.
if ((edge_kind == EdgeKind::kForward) ||
- (edge_kind == EdgeKind::kCaseFallThrough) ||
- (edge_kind == EdgeKind::kIfBreak)) {
+ (edge_kind == EdgeKind::kCaseFallThrough)) {
normal_forward_edges.push_back(dest);
}
+ if (edge_kind == EdgeKind::kIfBreak) {
+ if_break_edges.push_back(dest);
+ }
if ((edge_kind == EdgeKind::kForward) ||
(edge_kind == EdgeKind::kCaseFallThrough)) {
@@ -1340,6 +1378,21 @@
<< ") but it is not a structured header (it has no merge "
"instruction)";
}
+ if ((normal_forward_edges.size() + if_break_edges.size() > 1) &&
+ (src_info->merge_for_header == 0)) {
+ // There is a branch to the merge of an if-selection combined
+ // with an other normal forward branch. Control within the
+ // if-selection needs to be gated by a flow predicate.
+ for (auto if_break_dest : if_break_edges) {
+ auto* head_info =
+ GetBlockInfo(GetBlockInfo(if_break_dest)->header_for_merge);
+ // Generate a guard name, but only once.
+ if (head_info->flow_guard_name.empty()) {
+ const std::string guard = "guard" + std::to_string(head_info->id);
+ head_info->flow_guard_name = namer_.MakeDerivedName(guard);
+ }
+ }
+ }
}
return success();
@@ -1777,9 +1830,21 @@
assert(construct->kind == Construct::kIfSelection);
assert(construct->begin_id == block_info.id);
+ const uint32_t true_head = block_info.true_head;
const uint32_t false_head = block_info.false_head;
const uint32_t premerge_head = block_info.premerge_head;
+ const std::string guard_name = block_info.flow_guard_name;
+ if (!guard_name.empty()) {
+ // Declare the guard variable just before the "if", initialized to true.
+ auto guard_var = std::make_unique<ast::Variable>(
+ guard_name, ast::StorageClass::kFunction, parser_impl_.BoolType());
+ guard_var->set_constructor(MakeTrue());
+ auto guard_decl =
+ std::make_unique<ast::VariableDeclStatement>(std::move(guard_var));
+ AddStatement(std::move(guard_decl));
+ }
+
auto* const if_stmt =
AddStatement(std::make_unique<ast::IfStatement>())->AsIf();
const auto condition_id =
@@ -1857,7 +1922,30 @@
// Blocks for the then-clause appear before blocks for the else-clause.
// So push the else-clause handling onto the stack first. The else-clause
// might be empty, but this works anyway.
+
+ // Handle the premerge, if it exists.
+ if (premerge_head) {
+ // The top of the stack is the statement block that is the parent of the
+ // if-statement. Adding statements now will place them after that 'if'.
+ if (guard_name.empty()) {
+ // We won't have a flow guard for the premerge.
+ // Insert a trivial if(true) { ... } around the blocks from the
+ // premerge head until the end of the if-selection. This is needed
+ // to ensure uniform reconvergence occurs at the end of the if-selection
+ // just like in the original SPIR-V.
+ PushTrueGuard(construct->end_id);
+ } else {
+ // Add a flow guard around the blocks in the premrege area.
+ PushGuard(guard_name, construct->end_id);
+ }
+ }
+
push_else();
+ if (true_head && false_head && !guard_name.empty()) {
+ // There are non-trivial then and else clauses.
+ // We have to guard the start of the else.
+ PushGuard(guard_name, else_end);
+ }
push_then();
}
@@ -2083,11 +2171,20 @@
// Emit an 'if' statement to express the *other* branch as a conditional
// break or continue. Either or both of these could be nullptr.
// (A nullptr is generated for kIfBreak, kForward, or kBack.)
- auto true_branch = MakeBranch(block_info, *true_info);
- auto false_branch = MakeBranch(block_info, *false_info);
+ // Also if one of the branches is an if-break out of an if-selection
+ // requiring a flow guard, then get that flow guard name too. It will
+ // come from at most one of these two branches.
+ std::string flow_guard;
+ auto true_branch =
+ MakeBranchDetailed(block_info, *true_info, false, &flow_guard);
+ auto false_branch =
+ MakeBranchDetailed(block_info, *false_info, false, &flow_guard);
AddStatement(MakeSimpleIf(std::move(cond), std::move(true_branch),
std::move(false_branch)));
+ if (!flow_guard.empty()) {
+ PushGuard(flow_guard, statements_stack_.back().end_id_);
+ }
return true;
}
case SpvOpSwitch:
@@ -2100,10 +2197,11 @@
return success();
}
-std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchInternal(
+std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchDetailed(
const BlockInfo& src_info,
const BlockInfo& dest_info,
- bool forced) const {
+ bool forced,
+ std::string* flow_guard_name_ptr) const {
auto kind = src_info.succ_edge.find(dest_info.id)->second;
switch (kind) {
case EdgeKind::kBack:
@@ -2148,10 +2246,23 @@
}
// Otherwise, emit a regular continue statement.
return std::make_unique<ast::ContinueStatement>();
- case EdgeKind::kIfBreak:
+ case EdgeKind::kIfBreak: {
+ const auto& flow_guard =
+ GetBlockInfo(dest_info.header_for_merge)->flow_guard_name;
+ if (!flow_guard.empty()) {
+ if (flow_guard_name_ptr != nullptr) {
+ *flow_guard_name_ptr = flow_guard;
+ }
+ // Signal an exit from the branch.
+ return std::make_unique<ast::AssignmentStatement>(
+ std::make_unique<ast::IdentifierExpression>(flow_guard),
+ MakeFalse());
+ }
+
// For an unconditional branch, the break out to an if-selection
// merge block is implicit.
break;
+ }
case EdgeKind::kCaseFallThrough:
return std::make_unique<ast::FallthroughStatement>();
case EdgeKind::kForward:
@@ -2686,6 +2797,17 @@
return current_expr;
}
+std::unique_ptr<ast::Expression> FunctionEmitter::MakeTrue() const {
+ return std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::BoolLiteral>(parser_impl_.BoolType(), true));
+}
+
+std::unique_ptr<ast::Expression> FunctionEmitter::MakeFalse() const {
+ ast::type::BoolType bool_type;
+ return std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::BoolLiteral>(parser_impl_.BoolType(), false));
+}
+
} // namespace spirv
} // namespace reader
} // namespace tint
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 686c924..c321f88 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -138,16 +138,23 @@
/// construct for an OpBranchConditional instruction.
/// If not 0, then this block is an if-selection header, and |true_head| is
- /// the target id of the true branch on the OpBranchConditional.
+ /// the target id of the true branch on the OpBranchConditional, and that
+ /// target is inside the if-selection.
uint32_t true_head = 0;
/// If not 0, then this block is an if-selection header, and |false_head|
- /// is the target id of the false branch on the OpBranchConditional.
+ /// is the target id of the false branch on the OpBranchConditional, and
+ /// that target is inside the if-selection.
uint32_t false_head = 0;
/// If not 0, then this block is an if-selection header, and when following
/// the flow via the true and false branches, control first reconverges at
/// the block with ID |premerge_head|, and |premerge_head| is still inside
/// the if-selection.
uint32_t premerge_head = 0;
+ /// If non-empty, then this block is an if-selection header, and control flow
+ /// in the body must be guarded by a boolean flow variable with this name.
+ /// This occurs when a block in this selection has both an if-break edge, and
+ /// also a different normal forward edge but without a merge instruction.
+ std::string flow_guard_name = "";
};
inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
@@ -156,7 +163,6 @@
<< " merge_for_header: " << bi.merge_for_header
<< " continue_for_header: " << bi.continue_for_header
<< " header_for_merge: " << bi.header_for_merge
- << " header_for_merge: " << bi.header_for_merge
<< " single_block_loop: " << int(bi.is_single_block_loop) << "}";
return o;
}
@@ -338,7 +344,7 @@
/// @returns the new statement, or a null statement
std::unique_ptr<ast::Statement> MakeBranch(const BlockInfo& src_info,
const BlockInfo& dest_info) const {
- return MakeBranchInternal(src_info, dest_info, false);
+ return MakeBranchDetailed(src_info, dest_info, false, nullptr);
}
/// Returns a new statement to represent the given branch representing a
@@ -350,7 +356,7 @@
std::unique_ptr<ast::Statement> MakeForcedBranch(
const BlockInfo& src_info,
const BlockInfo& dest_info) const {
- return MakeBranchInternal(src_info, dest_info, true);
+ return MakeBranchDetailed(src_info, dest_info, true, nullptr);
}
/// Returns a new statement to represent the given branch representing a
@@ -359,13 +365,19 @@
/// is false, this method tries to avoid emitting a 'break' statement when
/// that would be redundant in WGSL due to implicit breaking out of a switch.
/// When |forced| is true, the method won't try to avoid emitting that break.
+ /// If the control flow edge is an if-break for an if-selection with a
+ /// control flow guard, then return that guard name via |flow_guard_name_ptr|
+ /// when that parameter is not null.
/// @param src_info the source block
/// @param dest_info the destination block
/// @param forced if true, always emit the branch (if it exists in WGSL)
+ /// @param flow_guard_name_ptr return parameter for control flow guard name
/// @returns the new statement, or a null statement
- std::unique_ptr<ast::Statement> MakeBranchInternal(const BlockInfo& src_info,
- const BlockInfo& dest_info,
- bool forced) const;
+ std::unique_ptr<ast::Statement> MakeBranchDetailed(
+ const BlockInfo& src_info,
+ const BlockInfo& dest_info,
+ bool forced,
+ std::string* flow_guard_name_ptr) const;
/// Returns a new if statement with the given statements as the then-clause
/// and the else-clause. Either or both clauses might be nullptr. If both
@@ -530,6 +542,25 @@
uint32_t end_id,
CompletionAction action);
+ /// Emits an if-statement whose condition is the given flow guard
+ /// variable, and pushes onto the statement stack the corresponding
+ /// statement block ending (and not including) the given block.
+ /// @param flow_guard name of the flow guard variable
+ /// @param end_id first block after the if construct.
+ void PushGuard(const std::string& flow_guard, uint32_t end_id);
+
+ /// Emits an if-statement with 'true' condition, and pushes onto the
+ /// statement stack the corresponding statement block ending (and not
+ /// including) the given block.
+ /// @param end_id first block after the if construct.
+ void PushTrueGuard(uint32_t end_id);
+
+ /// @returns a boolean true expression.
+ std::unique_ptr<ast::Expression> MakeTrue() const;
+
+ /// @returns a boolean false expression.
+ std::unique_ptr<ast::Expression> MakeFalse() const;
+
ParserImpl& parser_impl_;
ast::Module& ast_module_;
spvtools::opt::IRContext& ir_context_;
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index ab1bf0f..6d11200 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -6991,11 +6991,7 @@
}
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen) {
- // Arguably SPIR-V allows this configuration. We're debating whether to ban
- // it.
- // TODO(dneto): We can make this case work, if we injected
- // if (!cond2) { rest-of-then-body }
- // at block 30
+ // SPIR-V allows this unusual configuration.
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7016,7 +7012,7 @@
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
- EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+ EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
auto* bi20 = fe.GetBlockInfo(20);
@@ -7026,17 +7022,11 @@
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
- EXPECT_THAT(p->error(),
- Eq("Control flow diverges at block 20 (to 99, 80) but it is not "
- "a structured header (it has no merge instruction)"));
+ EXPECT_THAT(p->error(), Eq(""));
}
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse) {
- // Arguably SPIR-V allows this configuration. We're debating whether to ban
- // it.
- // TODO(dneto): We can make this case work, if we injected
- // if (!cond2) { rest-of-else-body }
- // at block 30
+ // SPIR-V allows this unusual configuration.
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7060,7 +7050,7 @@
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
- EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+ EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
auto* bi30 = fe.GetBlockInfo(30);
@@ -7070,12 +7060,10 @@
EXPECT_EQ(bi30->succ_edge.count(99), 1u);
EXPECT_EQ(bi30->succ_edge[99], EdgeKind::kIfBreak);
- EXPECT_THAT(p->error(),
- Eq("Control flow diverges at block 30 (to 99, 80) but it is not "
- "a structured header (it has no merge instruction)"));
+ EXPECT_THAT(p->error(), Eq(""));
}
-TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge_IsError) {
+TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7084,7 +7072,7 @@
OpBranchConditional %cond %20 %30
%20 = OpLabel ; then
- OpBranchConditional %cond2 %99 %80 ; break with forward to premerge is error
+ OpBranchConditional %cond2 %99 %80 ; break with forward to premerge
%30 = OpLabel ; else
OpBranch %80
@@ -7099,7 +7087,7 @@
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
- EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+ EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
auto* bi20 = fe.GetBlockInfo(20);
@@ -7109,9 +7097,7 @@
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
- EXPECT_THAT(p->error(),
- Eq("Control flow diverges at block 20 (to 99, 80) but it is not "
- "a structured header (it has no merge instruction)"));
+ EXPECT_THAT(p->error(), Eq(""));
}
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_DomViolation_Merge_CantBeTrueHeader) {
@@ -7204,10 +7190,9 @@
EXPECT_THAT(p->error(), Eq("Block 70 is the merge block for 50 but has alternate paths reaching it, starting from blocks 20 and 50 which are the true and false branches for the if-selection header block 10 (violates dominance rule)"));
}
-TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromThen_ForwardWithinThen) {
- // TODO(dneto): We can make this case work, if we injected
- // if (!cond2) { rest-of-then-body }
- // at block 30
+TEST_F(SpvParserTest, EmitBody_IfBreak_FromThen_ForwardWithinThen) {
+ // Exercises the hard case where we a single OpBranchConditional has both
+ // IfBreak and Forward edges, within the true-branch clause.
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7237,15 +7222,87 @@
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"(something
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+VariableDeclStatement{
+ Variable{
+ guard10
+ function
+ __bool
+ {
+ ScalarConstructor{true}
+ }
+ }
+}
+If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ }
+}
+Else{
+ {
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
Return{}
)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromElse_ForwardWithinElse) {
- // TODO(dneto): We can make this case work, if we injected
- // if (!cond2) { rest-of-else-body }
- // at block 80
+TEST_F(SpvParserTest, EmitBody_IfBreak_FromElse_ForwardWithinElse) {
+ // Exercises the hard case where we a single OpBranchConditional has both
+ // IfBreak and Forward edges, within the false-branch clause.
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7275,16 +7332,90 @@
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"(something
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+VariableDeclStatement{
+ Variable{
+ guard10
+ function
+ __bool
+ {
+ ScalarConstructor{true}
+ }
+ }
+}
+If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+}
+Else{
+ {
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ }
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
Return{}
)")) << ToString(fe.ast_body());
}
-TEST_F(
- SpvParserTest,
- DISABLED_Codegen_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge) {
- // This is a combination of the previous two, but also adding a premrge and
- //
+TEST_F(SpvParserTest,
+ EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge) {
+ // This is a combination of the previous two, but also adding a premerge.
+ // We have IfBreak and Forward edges from the same OpBranchConditional, and
+ // this occurs in the true-branch clause, the false-branch clause, and within
+ // the premerge clause. Flow guards have to be sprinkled in lots of places.
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7298,27 +7429,27 @@
OpBranchConditional %cond2 %21 %99 ; kForward and kIfBreak
%21 = OpLabel ; still in then clause
- OpStore %var %uint_2
+ OpStore %var %uint_3
OpBranch %80 ; to premerge
%50 = OpLabel ; else clause
- OpStore %var %uint_3
+ OpStore %var %uint_4
OpBranchConditional %cond2 %99 %51 ; kIfBreak with kForward
%51 = OpLabel ; still in else clause
- OpStore %var %uint_4
+ OpStore %var %uint_5
OpBranch %80 ; to premerge
%80 = OpLabel ; premerge
- OpStore %var %uint_5
+ OpStore %var %uint_6
OpBranchConditional %cond3 %81 %99
%81 = OpLabel ; premerge
- OpStore %var %uint_6
+ OpStore %var %uint_7
OpBranch %99
%99 = OpLabel
- OpStore %var %uint_7
+ OpStore %var %uint_8
OpReturn
OpFunctionEnd
)";
@@ -7326,7 +7457,139 @@
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
- EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(something
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+VariableDeclStatement{
+ Variable{
+ guard10
+ function
+ __bool
+ {
+ ScalarConstructor{true}
+ }
+ }
+}
+If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ }
+ }
+ }
+}
+Else{
+ {
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+ }
+ }
+ }
+ }
+ }
+ }
+}
+If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ If{
+ (
+ Identifier{guard10}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+ }
+ Assignment{
+ Identifier{guard10}
+ ScalarConstructor{false}
+ }
+ }
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{8}
+}
Return{}
)")) << ToString(fe.ast_body());
}
@@ -7605,16 +7868,23 @@
}
}
}
-Assignment{
- Identifier{var}
- ScalarConstructor{3}
+If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ }
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
Return{}
-)"));
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_If_Then_Premerge) {
@@ -7660,16 +7930,23 @@
}
}
}
-Assignment{
- Identifier{var}
- ScalarConstructor{3}
+If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ }
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
Return{}
-)"));
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_If_Else_Premerge) {
@@ -7719,16 +7996,23 @@
}
}
}
-Assignment{
- Identifier{var}
- ScalarConstructor{3}
+If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ }
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
Return{}
-)"));
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_If_Nest_If) {
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index f778c61..870bd79 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -192,6 +192,7 @@
: Reader(ctx),
spv_binary_(spv_binary),
fail_stream_(&success_, &errors_),
+ bool_type_(ctx->type_mgr().Get(std::make_unique<ast::type::BoolType>())),
namer_(fail_stream_),
enum_converter_(fail_stream_),
tools_context_(kTargetEnv),
@@ -282,7 +283,7 @@
case spvtools::opt::analysis::Type::kVoid:
return save(ctx_.type_mgr().Get(std::make_unique<ast::type::VoidType>()));
case spvtools::opt::analysis::Type::kBool:
- return save(ctx_.type_mgr().Get(std::make_unique<ast::type::BoolType>()));
+ return save(bool_type_);
case spvtools::opt::analysis::Type::kInteger:
return save(ConvertType(spirv_type->AsInteger()));
case spvtools::opt::analysis::Type::kFloat:
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index f7330a8..49003e7 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -280,6 +280,9 @@
ast::type::Type* ForcedResultType(SpvOp op,
ast::type::Type* first_operand_type);
+ /// @returns the registered boolean type.
+ ast::type::Type* BoolType() const { return bool_type_; }
+
private:
/// Converts a specific SPIR-V type to a Tint type. Integer case
ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
@@ -313,6 +316,9 @@
FailStream fail_stream_;
spvtools::MessageConsumer message_consumer_;
+ // The registered boolean type.
+ ast::type::Type* bool_type_;
+
// An object used to store and generate names for SPIR-V objects.
Namer namer_;
// An object used to convert SPIR-V enums to Tint enums