[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:
parent
ad2f7ccc55
commit
93e39b451b
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
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 @@ 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_;
|
||||
|
|
|
@ -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{
|
||||
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 @@ If{
|
|||
}
|
||||
}
|
||||
}
|
||||
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 @@ Else{
|
|||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue