[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>
This commit is contained in:
David Neto 2020-06-16 01:03:39 +00:00 committed by dan sinclair
parent ad2f7ccc55
commit 93e39b451b
5 changed files with 521 additions and 77 deletions

View File

@ -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 @@ void FunctionEmitter::PushNewStatementBlock(const Construct* construct,
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 @@ bool FunctionEmitter::ClassifyCFGEdges() {
// 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 @@ bool FunctionEmitter::ClassifyCFGEdges() {
// 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 @@ bool FunctionEmitter::ClassifyCFGEdges() {
<< ") 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 @@ bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
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 @@ bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
// 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 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
// 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 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
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 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchInternal(
}
// 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 @@ TypedExpression FunctionEmitter::MakeCompositeExtract(
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

View File

@ -138,16 +138,23 @@ struct BlockInfo {
/// 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 @@ inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
<< " 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 @@ class FunctionEmitter {
/// @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 @@ class FunctionEmitter {
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 @@ class FunctionEmitter {
/// 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,
std::unique_ptr<ast::Statement> MakeBranchDetailed(
const BlockInfo& src_info,
const BlockInfo& dest_info,
bool forced) const;
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 @@ class FunctionEmitter {
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_;

View File

@ -6991,11 +6991,7 @@ TEST_F(SpvParserTest,
}
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 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen) {
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 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen) {
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 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse) {
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 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse) {
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 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge_IsError) {
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 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge_IsError) {
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 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge_IsError) {
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 @@ TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_DomViolation_Merge_CantBePr
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 @@ TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromThen_ForwardWithinThen) {
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 @@ TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromElse_ForwardWithinElse) {
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 @@ TEST_F(
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 @@ TEST_F(
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 @@ Else{
}
}
}
Assignment{
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 @@ If{
}
}
}
Assignment{
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 @@ Else{
}
}
}
Assignment{
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) {

View File

@ -192,6 +192,7 @@ ParserImpl::ParserImpl(Context* ctx, const std::vector<uint32_t>& spv_binary)
: 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 @@ ast::type::Type* ParserImpl::ConvertType(uint32_t type_id) {
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:

View File

@ -280,6 +280,9 @@ class ParserImpl : Reader {
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 @@ class ParserImpl : Reader {
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