AddConstruct({ConstructType::kContinue, &continue_target_block});
continue_construct.set_corresponding_constructs({&loop_construct});
loop_construct.set_corresponding_constructs({&continue_construct});
+ merge_block_header_[&merge_block] = current_block_;
return SPV_SUCCESS;
}
BasicBlock& merge_block = blocks_.at(merge_id);
current_block_->set_type(kBlockTypeHeader);
merge_block.set_type(kBlockTypeMerge);
+ merge_block_header_[&merge_block] = current_block_;
AddConstruct({ConstructType::kSelection, current_block(), &merge_block});
return *construct_ptr;
}
+int Function::GetBlockDepth(BasicBlock* bb) {
+ // Guard against nullptr.
+ if (!bb) {
+ return 0;
+ }
+ // Only calculate the depth if it's not already calculated.
+ // This function uses memoization to avoid duplicate CFG depth calculations.
+ if (block_depth_.find(bb) != block_depth_.end()) {
+ return block_depth_[bb];
+ }
+
+ BasicBlock* bb_dom = bb->immediate_dominator();
+ if (!bb_dom || bb == bb_dom) {
+ // This block has no dominator, so it's at depth 0.
+ block_depth_[bb] = 0;
+ } else if (bb->is_type(kBlockTypeMerge)) {
+ // If this is a merge block, its depth is equal to the block before
+ // branching.
+ BasicBlock* header = merge_block_header_[bb];
+ assert(header);
+ block_depth_[bb] = GetBlockDepth(header);
+ } else if (bb->is_type(kBlockTypeContinue)) {
+ // The depth of the continue block entry point is 1 + loop header depth.
+ Construct* continue_construct = entry_block_to_construct_[bb];
+ assert(continue_construct);
+ // Continue construct has only 1 corresponding construct (loop header).
+ Construct* loop_construct =
+ continue_construct->corresponding_constructs()[0];
+ assert(loop_construct);
+ BasicBlock* loop_header = loop_construct->entry_block();
+ // The continue target may be the loop itself (while 1).
+ // In such cases, the depth of the continue block is: 1 + depth of the
+ // loop's dominator block.
+ if (loop_header == bb) {
+ block_depth_[bb] = 1 + GetBlockDepth(bb_dom);
+ } else {
+ block_depth_[bb] = 1 + GetBlockDepth(loop_header);
+ }
+ } else if (bb_dom->is_type(kBlockTypeHeader) ||
+ bb_dom->is_type(kBlockTypeLoop)) {
+ // The dominator of the given block is a header block. So, the nesting
+ // depth of this block is: 1 + nesting depth of the header.
+ block_depth_[bb] = 1 + GetBlockDepth(bb_dom);
+ } else {
+ block_depth_[bb] = GetBlockDepth(bb_dom);
+ }
+
+ return block_depth_[bb];
+}
+
} /// namespace libspirv
/// Returns the block predecessors function for the augmented CFG.
GetBlocksFunction AugmentedCFGPredecessorsFunction() const;
+ /// Returns the control flow nesting depth of the given basic block.
+ /// This function only works when you have structured control flow.
+ /// This function should only be called after the control flow constructs have
+ /// been identified and dominators have been computed.
+ int GetBlockDepth(BasicBlock* bb);
+
/// Prints a GraphViz digraph of the CFG of the current funciton
void PrintDotGraph() const;
/// Maps a construct's entry block to the construct.
std::unordered_map<const BasicBlock*, Construct*> entry_block_to_construct_;
+
+ /// This map provides the header block for a given merge block.
+ std::unordered_map<BasicBlock*, BasicBlock*> merge_block_header_;
+
+ /// Stores the control flow nesting depth of a given basic block
+ std::unordered_map<BasicBlock*, int> block_depth_;
};
} /// namespace libspirv
}
UpdateContinueConstructExitBlocks(function, back_edges);
- // Check if the order of blocks in the binary appear before the blocks they
- // dominate
auto& blocks = function.ordered_blocks();
- if (blocks.empty() == false) {
+ if (!blocks.empty()) {
+ // Check if the order of blocks in the binary appear before the blocks
+ // they dominate
for (auto block = begin(blocks) + 1; block != end(blocks); ++block) {
if (auto idom = (*block)->immediate_dominator()) {
if (idom != function.pseudo_entry_block() &&
}
}
}
+ // If we have structed control flow, check that no block has a control
+ // flow nesting depth larger than the limit.
+ if (_.HasCapability(SpvCapabilityShader)) {
+ const int control_flow_nesting_depth_limit = 1023;
+ for (auto block = begin(blocks); block != end(blocks); ++block) {
+ if (function.GetBlockDepth(*block) >
+ control_flow_nesting_depth_limit) {
+ return _.diag(SPV_ERROR_INVALID_CFG)
+ << "Maximum Control Flow nesting depth exceeded.";
+ }
+ }
+ }
}
/// Structured control flow checks are only required for shader capabilities
#include "unit_spirv.h"
#include "val_fixtures.h"
+namespace {
+
using ::testing::HasSubstr;
using ::testing::MatchesRegex;
-
using std::string;
using ValidateLimits = spvtest::ValidateBase<bool>;
OpMemoryModel Logical GLSL450
)";
-TEST_F(ValidateLimits, idLargerThanBoundBad) {
+TEST_F(ValidateLimits, IdLargerThanBoundBad) {
string str = header + R"(
; %i32 has ID 1
%i32 = OpTypeInt 32 1
HasSubstr("Result <id> '64' must be less than the ID bound '3'."));
}
-TEST_F(ValidateLimits, idEqualToBoundBad) {
+TEST_F(ValidateLimits, IdEqualToBoundBad) {
string str = header + R"(
; %i32 has ID 1
%i32 = OpTypeInt 32 1
HasSubstr("Result <id> '64' must be less than the ID bound '64'."));
}
-TEST_F(ValidateLimits, structNumMembersGood) {
+TEST_F(ValidateLimits, StructNumMembersGood) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeInt 32 0
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(ValidateLimits, structNumMembersExceededBad) {
+TEST_F(ValidateLimits, StructNumMembersExceededBad) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeInt 32 0
}
// Valid: Switch statement has 16,383 branches.
-TEST_F(ValidateLimits, switchNumBranchesGood) {
+TEST_F(ValidateLimits, SwitchNumBranchesGood) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeVoid
}
// Invalid: Switch statement has 16,384 branches.
-TEST_F(ValidateLimits, switchNumBranchesBad) {
+TEST_F(ValidateLimits, SwitchNumBranchesBad) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeVoid
HasSubstr(
"Structure Nesting Depth may not be larger than 255. Found 256."));
}
+
+// clang-format off
+// Generates an SPIRV program with the given control flow nesting depth
+void GenerateSpirvProgramWithCfgNestingDepth(std::string& str, int depth) {
+ std::ostringstream spirv;
+ spirv << header << R"(
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %bool = OpTypeBool
+ %12 = OpConstantTrue %bool
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpLoopMerge %8 %9 None
+ OpBranch %10
+ %10 = OpLabel
+ OpBranchConditional %12 %7 %8
+ %7 = OpLabel
+ )";
+ int first_id = 13;
+ int last_id = 14;
+ // We already have 1 level of nesting due to the Loop.
+ int num_if_conditions = depth-1;
+ int largest_index = first_id + 2*num_if_conditions - 2;
+ for (int i = first_id; i <= largest_index; i = i + 2) {
+ spirv << "OpSelectionMerge %" << i+1 << " None" << "\n";
+ spirv << "OpBranchConditional %12 " << "%" << i << " %" << i+1 << "\n";
+ spirv << "%" << i << " = OpLabel" << "\n";
+ }
+ spirv << "OpBranch %9" << "\n";
+
+ for (int i = largest_index+1; i > last_id; i = i - 2) {
+ spirv << "%" << i << " = OpLabel" << "\n";
+ spirv << "OpBranch %" << i-2 << "\n";
+ }
+ spirv << "%" << last_id << " = OpLabel" << "\n";
+ spirv << "OpBranch %9" << "\n";
+ spirv << R"(
+ %9 = OpLabel
+ OpBranch %6
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ str = spirv.str();
+}
+// clang-format on
+
+// Valid: Control Flow Nesting depth is 1023.
+TEST_F(ValidateLimits, ControlFlowDepthGood) {
+ std::string spirv;
+ GenerateSpirvProgramWithCfgNestingDepth(spirv, 1023);
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+// Invalid: Control Flow Nesting depth is 1024. (limit is 1023).
+TEST_F(ValidateLimits, ControlFlowDepthBad) {
+ std::string spirv;
+ GenerateSpirvProgramWithCfgNestingDepth(spirv, 1024);
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Maximum Control Flow nesting depth exceeded."));
+}
+
+// Valid. The purpose here is to test the CFG depth calculation code when a loop
+// continue target is the loop iteself. It also exercises the case where a loop
+// is unreachable.
+TEST_F(ValidateLimits, ControlFlowNoEntryToLoopGood) {
+ string str = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpName %entry "entry"
+ OpName %loop "loop"
+ OpName %exit "exit"
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%main = OpFunction %voidt None %funct
+%entry = OpLabel
+ OpBranch %exit
+%loop = OpLabel
+ OpLoopMerge %loop %loop None
+ OpBranch %loop
+%exit = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+} // anonymous namespace