mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-30 06:50:06 +00:00
6993fc413d
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.
418 lines
12 KiB
C++
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
|