SPIRV-Tools/test/val/val_limits_test.cpp
Ehsan Nasiri 6993fc413d Validation code for control flow nesting depth.
According to Section 2.17 (Universal Limits) of the SPIR-V Spec, the
control flow nesting depth may not be larger than 1023.

This is checked only when we are required to have structured
control flow.  Otherwise it's not clear how to compute control
flow nesting depth.
2016-12-23 14:14:50 -05:00

418 lines
12 KiB
C++

// Copyright (c) 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Validation tests for Universal Limits. (Section 2.17 of the SPIR-V Spec)
#include <sstream>
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "unit_spirv.h"
#include "val_fixtures.h"
namespace {
using ::testing::HasSubstr;
using ::testing::MatchesRegex;
using std::string;
using ValidateLimits = spvtest::ValidateBase<bool>;
string header = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
)";
TEST_F(ValidateLimits, IdLargerThanBoundBad) {
string str = header + R"(
; %i32 has ID 1
%i32 = OpTypeInt 32 1
%c = OpConstant %i32 100
; Fake an instruction with 64 as the result id.
; !64 = OpConstantNull %i32
!0x3002e !1 !64
)";
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Result <id> '64' must be less than the ID bound '3'."));
}
TEST_F(ValidateLimits, IdEqualToBoundBad) {
string str = header + R"(
; %i32 has ID 1
%i32 = OpTypeInt 32 1
%c = OpConstant %i32 100
; Fake an instruction with 64 as the result id.
; !64 = OpConstantNull %i32
!0x3002e !1 !64
)";
CompileSuccessfully(str);
// The largest ID used in this program is 64. Let's overwrite the ID bound in
// the header to be 64. This should result in an error because all IDs must
// satisfy: 0 < id < bound.
OverwriteAssembledBinary(3, 64);
ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Result <id> '64' must be less than the ID bound '64'."));
}
TEST_F(ValidateLimits, StructNumMembersGood) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeInt 32 0
%2 = OpTypeStruct)";
for (int i = 0; i < 16383; ++i) {
spirv << " %1";
}
CompileSuccessfully(spirv.str());
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateLimits, StructNumMembersExceededBad) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeInt 32 0
%2 = OpTypeStruct)";
for (int i = 0; i < 16384; ++i) {
spirv << " %1";
}
CompileSuccessfully(spirv.str());
ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Number of OpTypeStruct members (16384) has exceeded "
"the limit (16383)."));
}
// Valid: Switch statement has 16,383 branches.
TEST_F(ValidateLimits, SwitchNumBranchesGood) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpTypeInt 32 0
%4 = OpConstant %3 1234
%5 = OpFunction %1 None %2
%7 = OpLabel
%8 = OpIAdd %3 %4 %4
%9 = OpSwitch %4 %10)";
// Now add the (literal, label) pairs
for (int i = 0; i < 16383; ++i) {
spirv << " 1 %10";
}
spirv << R"(
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv.str());
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
// Invalid: Switch statement has 16,384 branches.
TEST_F(ValidateLimits, SwitchNumBranchesBad) {
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpTypeInt 32 0
%4 = OpConstant %3 1234
%5 = OpFunction %1 None %2
%7 = OpLabel
%8 = OpIAdd %3 %4 %4
%9 = OpSwitch %4 %10)";
// Now add the (literal, label) pairs
for (int i = 0; i < 16384; ++i) {
spirv << " 1 %10";
}
spirv << R"(
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv.str());
ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Number of (literal, label) pairs in OpSwitch (16384) "
"exceeds the limit (16383)."));
}
// Valid: OpTypeFunction with 255 arguments.
TEST_F(ValidateLimits, OpTypeFunctionGood) {
int num_args = 255;
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeInt 32 0
%2 = OpTypeFunction %1)";
// add parameters
for (int i = 0; i < num_args; ++i) {
spirv << " %1";
}
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
// Invalid: OpTypeFunction with 256 arguments. (limit is 255 according to the
// spec Universal Limits (2.17).
TEST_F(ValidateLimits, OpTypeFunctionBad) {
int num_args = 256;
std::ostringstream spirv;
spirv << header << R"(
%1 = OpTypeInt 32 0
%2 = OpTypeFunction %1)";
for (int i = 0; i < num_args; ++i) {
spirv << " %1";
}
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpTypeFunction may not take more than 255 arguments. "
"OpTypeFunction <id> '2' has 256 arguments."));
}
// Valid: module has 65,535 global variables.
TEST_F(ValidateLimits, NumGlobalVarsGood) {
int num_globals = 65535;
std::ostringstream spirv;
spirv << header << R"(
%int = OpTypeInt 32 0
%_ptr_int = OpTypePointer Input %int
)";
for (int i = 0; i < num_globals; ++i) {
spirv << "%var_" << i << " = OpVariable %_ptr_int Input\n";
}
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
// Invalid: module has 65,536 global variables (limit is 65,535).
TEST_F(ValidateLimits, NumGlobalVarsBad) {
int num_globals = 65536;
std::ostringstream spirv;
spirv << header << R"(
%int = OpTypeInt 32 0
%_ptr_int = OpTypePointer Input %int
)";
for (int i = 0; i < num_globals; ++i) {
spirv << "%var_" << i << " = OpVariable %_ptr_int Input\n";
}
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Number of Global Variables (Storage Class other than "
"'Function') exceeded the valid limit (65535)."));
}
// Valid: module has 524,287 local variables.
TEST_F(ValidateLimits, NumLocalVarsGood) {
int num_locals = 524287;
std::ostringstream spirv;
spirv << header << R"(
%int = OpTypeInt 32 0
%_ptr_int = OpTypePointer Function %int
%voidt = OpTypeVoid
%funct = OpTypeFunction %voidt
%main = OpFunction %voidt None %funct
%entry = OpLabel
)";
for (int i = 0; i < num_locals; ++i) {
spirv << "%var_" << i << " = OpVariable %_ptr_int Function\n";
}
spirv << R"(
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
// Invalid: module has 524,288 local variables (limit is 524,287).
TEST_F(ValidateLimits, NumLocalVarsBad) {
int num_locals = 524288;
std::ostringstream spirv;
spirv << header << R"(
%int = OpTypeInt 32 0
%_ptr_int = OpTypePointer Function %int
%voidt = OpTypeVoid
%funct = OpTypeFunction %voidt
%main = OpFunction %voidt None %funct
%entry = OpLabel
)";
for (int i = 0; i < num_locals; ++i) {
spirv << "%var_" << i << " = OpVariable %_ptr_int Function\n";
}
spirv << R"(
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Number of local variables ('Function' Storage Class) "
"exceeded the valid limit (524287)."));
}
// Valid: Structure nesting depth of 255.
TEST_F(ValidateLimits, StructNestingDepthGood) {
std::ostringstream spirv;
spirv << header << R"(
%int = OpTypeInt 32 0
%s_depth_1 = OpTypeStruct %int
)";
for(auto i=2; i<=255; ++i) {
spirv << "%s_depth_" << i << " = OpTypeStruct %int %s_depth_" << i-1;
spirv << "\n";
}
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
// Invalid: Structure nesting depth of 256.
TEST_F(ValidateLimits, StructNestingDepthBad) {
std::ostringstream spirv;
spirv << header << R"(
%int = OpTypeInt 32 0
%s_depth_1 = OpTypeStruct %int
)";
for(auto i=2; i<=256; ++i) {
spirv << "%s_depth_" << i << " = OpTypeStruct %int %s_depth_" << i-1;
spirv << "\n";
}
CompileSuccessfully(spirv.str());
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
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