[spirv-reader] Classify CFG edges
Classify CFG edges: - loop backedge - a structured exit: - loop break - loop continue - selection break - fallthrough - forward (any of the rest) Also error out when there should have been a merge instruction. (More than one unique fallthrough or forward edge). Includes lots of tests. Bug: tint:3 Change-Id: I70f27680bdf098213056522abf04ac58a6b478ab Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/20481 Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
parent
32b41b79da
commit
054927d7eb
|
@ -443,6 +443,11 @@ bool FunctionEmitter::EmitBody() {
|
||||||
if (!FindSwitchCaseHeaders()) {
|
if (!FindSwitchCaseHeaders()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!ClassifyCFGEdges()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO(dneto): FindIfSelectionHeaders
|
||||||
|
// TODO(dneto): FindInvalidNestingBetweenSelections
|
||||||
|
|
||||||
if (!EmitFunctionVariables()) {
|
if (!EmitFunctionVariables()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -915,6 +920,237 @@ bool FunctionEmitter::FindSwitchCaseHeaders() {
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlockInfo* FunctionEmitter::HeaderForLoopOrContinue(const Construct* c) {
|
||||||
|
if (c->kind == Construct::kLoop) {
|
||||||
|
return GetBlockInfo(c->begin_id);
|
||||||
|
}
|
||||||
|
if (c->kind == Construct::kContinue) {
|
||||||
|
auto* continue_block = GetBlockInfo(c->begin_id);
|
||||||
|
return GetBlockInfo(continue_block->header_for_continue);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FunctionEmitter::ClassifyCFGEdges() {
|
||||||
|
if (failed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks validity of CFG edges leaving each basic block. This implicitly
|
||||||
|
// checks dominance rules for headers and continue constructs.
|
||||||
|
//
|
||||||
|
// For each branch encountered, classify each edge (S,T) as:
|
||||||
|
// - a back-edge
|
||||||
|
// - a structured exit (specific ways of branching to enclosing construct)
|
||||||
|
// - a normal (forward) edge
|
||||||
|
//
|
||||||
|
// If more than one block is targeted by a normal edge, then S must be a
|
||||||
|
// structured header.
|
||||||
|
//
|
||||||
|
// Term: NEC(B) is the nearest enclosing construct for B.
|
||||||
|
//
|
||||||
|
// If edge (S,T) is a normal edge, and NEC(S) != NEC(T), then
|
||||||
|
// T is the header block of its NEC(T), and
|
||||||
|
// NEC(S) is the parent of NEC(T).
|
||||||
|
|
||||||
|
for (const auto src : block_order_) {
|
||||||
|
assert(src > 0);
|
||||||
|
auto* src_info = GetBlockInfo(src);
|
||||||
|
assert(src_info);
|
||||||
|
const auto src_pos = src_info->pos;
|
||||||
|
const auto& src_construct = *(src_info->construct);
|
||||||
|
|
||||||
|
// Compute the ordered list of unique successors.
|
||||||
|
std::vector<uint32_t> successors;
|
||||||
|
{
|
||||||
|
std::unordered_set<uint32_t> visited;
|
||||||
|
src_info->basic_block->ForEachSuccessorLabel(
|
||||||
|
[&successors, &visited](const uint32_t succ) {
|
||||||
|
if (visited.count(succ) == 0) {
|
||||||
|
successors.push_back(succ);
|
||||||
|
visited.insert(succ);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// There should only be one backedge per backedge block.
|
||||||
|
uint32_t num_backedges = 0;
|
||||||
|
|
||||||
|
// Track destinations for normal forward edges.
|
||||||
|
std::vector<uint32_t> normal_forward_edges;
|
||||||
|
|
||||||
|
if (successors.empty() && src_construct.enclosing_continue) {
|
||||||
|
// Kill and return are not allowed in a continue construct.
|
||||||
|
return Fail() << "Invalid function exit at block " << src
|
||||||
|
<< " from continue construct starting at "
|
||||||
|
<< src_construct.enclosing_continue->begin_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto dest : successors) {
|
||||||
|
const auto* dest_info = GetBlockInfo(dest);
|
||||||
|
// We've already checked terminators are sane.
|
||||||
|
assert(dest_info);
|
||||||
|
const auto dest_pos = dest_info->pos;
|
||||||
|
|
||||||
|
// Insert the edge kind entry and keep a handle to update
|
||||||
|
// its classification.
|
||||||
|
EdgeKind& edge_kind = src_info->succ_edge[dest];
|
||||||
|
|
||||||
|
if (src_pos >= dest_pos) {
|
||||||
|
// This is a backedge.
|
||||||
|
edge_kind = EdgeKind::kBack;
|
||||||
|
num_backedges++;
|
||||||
|
const auto* continue_construct = src_construct.enclosing_continue;
|
||||||
|
if (!continue_construct) {
|
||||||
|
return Fail() << "Invalid backedge (" << src << "->" << dest
|
||||||
|
<< "): " << src << " is not in a continue construct";
|
||||||
|
}
|
||||||
|
if (src_pos != continue_construct->end_pos - 1) {
|
||||||
|
return Fail() << "Invalid exit (" << src << "->" << dest
|
||||||
|
<< ") from continue construct: " << src
|
||||||
|
<< " is not the last block in the continue construct "
|
||||||
|
"starting at "
|
||||||
|
<< src_construct.begin_id
|
||||||
|
<< " (violates post-dominance rule)";
|
||||||
|
}
|
||||||
|
const auto* ct_info = GetBlockInfo(continue_construct->begin_id);
|
||||||
|
assert(ct_info);
|
||||||
|
if (ct_info->header_for_continue != dest) {
|
||||||
|
return Fail()
|
||||||
|
<< "Invalid backedge (" << src << "->" << dest
|
||||||
|
<< "): does not branch to the corresponding loop header, "
|
||||||
|
"expected "
|
||||||
|
<< ct_info->header_for_continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a forward edge.
|
||||||
|
// For now, classify it that way, but we might update it.
|
||||||
|
edge_kind = EdgeKind::kForward;
|
||||||
|
|
||||||
|
// Exit from a continue construct can only be from the last block.
|
||||||
|
const auto* continue_construct = src_construct.enclosing_continue;
|
||||||
|
if (continue_construct != nullptr) {
|
||||||
|
if (continue_construct->ContainsPos(src_pos) &&
|
||||||
|
!continue_construct->ContainsPos(dest_pos) &&
|
||||||
|
(src_pos != continue_construct->end_pos - 1)) {
|
||||||
|
return Fail() << "Invalid exit (" << src << "->" << dest
|
||||||
|
<< ") from continue construct: " << src
|
||||||
|
<< " is not the last block in the continue construct "
|
||||||
|
"starting at "
|
||||||
|
<< continue_construct->begin_id
|
||||||
|
<< " (violates post-dominance rule)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check valid structured exit cases.
|
||||||
|
|
||||||
|
const auto& header_info = *GetBlockInfo(src_construct.begin_id);
|
||||||
|
if (dest == header_info.merge_for_header) {
|
||||||
|
// Branch to construct's merge block.
|
||||||
|
const bool src_is_loop_header = header_info.continue_for_header != 0;
|
||||||
|
const bool src_is_continue_header =
|
||||||
|
header_info.header_for_continue != 0;
|
||||||
|
edge_kind = (src_is_loop_header || src_is_continue_header)
|
||||||
|
? EdgeKind::kLoopBreak
|
||||||
|
: EdgeKind::kToMerge;
|
||||||
|
} else {
|
||||||
|
const auto* loop_or_continue_construct =
|
||||||
|
src_construct.enclosing_loop_or_continue;
|
||||||
|
if (loop_or_continue_construct != nullptr) {
|
||||||
|
// Check for break block or continue block.
|
||||||
|
const auto* loop_header_info =
|
||||||
|
HeaderForLoopOrContinue(loop_or_continue_construct);
|
||||||
|
if (loop_header_info == nullptr) {
|
||||||
|
return Fail() << "internal error: invalid construction of loop "
|
||||||
|
"related to block "
|
||||||
|
<< loop_or_continue_construct->begin_id;
|
||||||
|
}
|
||||||
|
if (dest == loop_header_info->merge_for_header) {
|
||||||
|
// It's a break block for the innermost loop.
|
||||||
|
edge_kind = EdgeKind::kLoopBreak;
|
||||||
|
} else if (dest == loop_header_info->continue_for_header) {
|
||||||
|
// It's a continue block for the innermost loop construct.
|
||||||
|
// In this case loop_or_continue_construct can't be a continue
|
||||||
|
// construct, because then the branch to the continue target is
|
||||||
|
// a backedge, and this code is only looking at forward edges.
|
||||||
|
edge_kind = EdgeKind::kLoopContinue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A forward edge into a case construct that comes from something
|
||||||
|
// other than the OpSwitch is actually a fallthrough.
|
||||||
|
if (edge_kind == EdgeKind::kForward) {
|
||||||
|
const auto* switch_construct =
|
||||||
|
(dest_info->case_head_for ? dest_info->case_head_for
|
||||||
|
: dest_info->default_head_for);
|
||||||
|
if (switch_construct != nullptr) {
|
||||||
|
if (src != switch_construct->begin_id) {
|
||||||
|
edge_kind = EdgeKind::kCaseFallThrough;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((edge_kind == EdgeKind::kForward) ||
|
||||||
|
(edge_kind == EdgeKind::kCaseFallThrough)) {
|
||||||
|
// Check for an invalid forward exit out of this construct.
|
||||||
|
if (dest_info->pos >= src_construct.end_pos) {
|
||||||
|
return Fail()
|
||||||
|
<< "Branch from block " << src << " to block " << dest
|
||||||
|
<< " is an invalid exit from construct starting at block "
|
||||||
|
<< src_construct.begin_id << "; branch bypasses block "
|
||||||
|
<< src_construct.end_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
normal_forward_edges.push_back(dest);
|
||||||
|
|
||||||
|
// Check dominance.
|
||||||
|
|
||||||
|
// Look for edges that violate the dominance condition: a branch
|
||||||
|
// from X to Y where:
|
||||||
|
// If Y is in a nearest enclosing continue construct headed by
|
||||||
|
// CT:
|
||||||
|
// Y is not CT, and
|
||||||
|
// In the structured order, X appears before CT order or
|
||||||
|
// after CT's backedge block.
|
||||||
|
// Otherwise, if Y is in a nearest enclosing construct
|
||||||
|
// headed by H:
|
||||||
|
// Y is not H, and
|
||||||
|
// In the structured order, X appears before H or after H's
|
||||||
|
// merge block.
|
||||||
|
|
||||||
|
const auto& dest_construct = *(dest_info->construct);
|
||||||
|
if (dest != dest_construct.begin_id &&
|
||||||
|
!dest_construct.ContainsPos(src_pos)) {
|
||||||
|
return Fail() << "Branch from " << src << " to " << dest
|
||||||
|
<< " bypasses "
|
||||||
|
<< (dest_construct.kind == Construct::kContinue
|
||||||
|
? "continue target "
|
||||||
|
: "header ")
|
||||||
|
<< dest_construct.begin_id
|
||||||
|
<< " (dominance rule violated)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end forward edge
|
||||||
|
} // end successor
|
||||||
|
|
||||||
|
if (num_backedges > 1) {
|
||||||
|
return Fail() << "Block " << src
|
||||||
|
<< " has too many backedges: " << num_backedges;
|
||||||
|
}
|
||||||
|
if ((normal_forward_edges.size() > 1) &&
|
||||||
|
(src_info->merge_for_header == 0)) {
|
||||||
|
return Fail() << "Control flow diverges at block " << src << " (to "
|
||||||
|
<< normal_forward_edges[0] << ", "
|
||||||
|
<< normal_forward_edges[1]
|
||||||
|
<< ") but it is not a structured header (it has no merge "
|
||||||
|
"instruction)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
bool FunctionEmitter::EmitFunctionVariables() {
|
bool FunctionEmitter::EmitFunctionVariables() {
|
||||||
if (failed()) {
|
if (failed()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -38,6 +38,31 @@ namespace tint {
|
||||||
namespace reader {
|
namespace reader {
|
||||||
namespace spirv {
|
namespace spirv {
|
||||||
|
|
||||||
|
/// Kinds of CFG edges.
|
||||||
|
enum class EdgeKind {
|
||||||
|
// A back-edge: An edge from a node to one of its ancestors in a depth-first
|
||||||
|
// search from the entry block.
|
||||||
|
kBack,
|
||||||
|
// An edge from a node to the merge block of the nearest enclosing structured
|
||||||
|
// construct. We write "ToMerge" to make it clear the destination block is
|
||||||
|
// the merge block, not the source block.
|
||||||
|
kToMerge,
|
||||||
|
// An edge from a node to the merge block of the nearest enclosing loop.
|
||||||
|
// The source block is a "break block" as defined by SPIR-V.
|
||||||
|
kLoopBreak,
|
||||||
|
// An edge from a node in a loop body to the associated continue target, where
|
||||||
|
// there are no other intervening loops or switches.
|
||||||
|
// The source block is a "continue block" as defined by SPIR-V.
|
||||||
|
kLoopContinue,
|
||||||
|
// An edge from one switch case to the next sibling switch case.
|
||||||
|
kCaseFallThrough,
|
||||||
|
// None of the above. By structured control flow rules, there can be at most
|
||||||
|
// one forward edge leaving a basic block. Otherwise there must have been a
|
||||||
|
// merge instruction declaring the divergence and associated reconvergence
|
||||||
|
// point.
|
||||||
|
kForward
|
||||||
|
};
|
||||||
|
|
||||||
/// Bookkeeping info for a basic block.
|
/// Bookkeeping info for a basic block.
|
||||||
struct BlockInfo {
|
struct BlockInfo {
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
@ -87,6 +112,9 @@ struct BlockInfo {
|
||||||
bool default_is_merge = false;
|
bool default_is_merge = false;
|
||||||
/// The list of switch values that cause a branch to this block.
|
/// The list of switch values that cause a branch to this block.
|
||||||
std::unique_ptr<std::vector<uint64_t>> case_values;
|
std::unique_ptr<std::vector<uint64_t>> case_values;
|
||||||
|
|
||||||
|
/// Maps the ID of a successor block (in the CFG) to its edge classification.
|
||||||
|
std::unordered_map<uint32_t, EdgeKind> succ_edge;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
|
inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
|
||||||
|
@ -127,6 +155,9 @@ class FunctionEmitter {
|
||||||
/// @returns a FailStream on which to emit diagnostics.
|
/// @returns a FailStream on which to emit diagnostics.
|
||||||
FailStream& Fail() { return fail_stream_.Fail(); }
|
FailStream& Fail() { return fail_stream_.Fail(); }
|
||||||
|
|
||||||
|
/// @returns the parser implementation
|
||||||
|
ParserImpl* parser() { return &parser_impl_; }
|
||||||
|
|
||||||
/// Emits the declaration, which comprises the name, parameters, and
|
/// Emits the declaration, which comprises the name, parameters, and
|
||||||
/// return type. The function AST node is appended to the module
|
/// return type. The function AST node is appended to the module
|
||||||
/// AST node.
|
/// AST node.
|
||||||
|
@ -184,6 +215,14 @@ class FunctionEmitter {
|
||||||
/// @returns false on failure
|
/// @returns false on failure
|
||||||
bool FindSwitchCaseHeaders();
|
bool FindSwitchCaseHeaders();
|
||||||
|
|
||||||
|
/// Classifies the successor CFG edges for the ordered basic blocks.
|
||||||
|
/// Also checks validity of each edge (populates the |succ_edge| field of
|
||||||
|
/// BlockInfo). Implicitly checks dominance rules for headers and continue
|
||||||
|
/// constructs. Assumes each block has been labeled with its control flow
|
||||||
|
/// construct.
|
||||||
|
/// @returns false on failure
|
||||||
|
bool ClassifyCFGEdges();
|
||||||
|
|
||||||
/// Emits declarations of function variables.
|
/// Emits declarations of function variables.
|
||||||
/// @returns false if emission failed.
|
/// @returns false if emission failed.
|
||||||
bool EmitFunctionVariables();
|
bool EmitFunctionVariables();
|
||||||
|
@ -243,6 +282,13 @@ class FunctionEmitter {
|
||||||
ast::type::Type* GetVariableStoreType(
|
ast::type::Type* GetVariableStoreType(
|
||||||
const spvtools::opt::Instruction& var_decl_inst);
|
const spvtools::opt::Instruction& var_decl_inst);
|
||||||
|
|
||||||
|
/// Finds the loop header block for a loop construct or continue construct.
|
||||||
|
/// The loop header block is the block with the corresponding OpLoopMerge
|
||||||
|
/// instruction.
|
||||||
|
/// @param c the loop or continue construct
|
||||||
|
/// @returns the block info for the loop header.
|
||||||
|
BlockInfo* HeaderForLoopOrContinue(const Construct* c);
|
||||||
|
|
||||||
ParserImpl& parser_impl_;
|
ParserImpl& parser_impl_;
|
||||||
ast::Module& ast_module_;
|
ast::Module& ast_module_;
|
||||||
spvtools::opt::IRContext& ir_context_;
|
spvtools::opt::IRContext& ir_context_;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue