| // Copyright (c) 2018 LunarG 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. |
| |
| #include <sstream> |
| #include <string> |
| |
| #include "gmock/gmock.h" |
| #include "test/unit_spirv.h" |
| #include "test/val/val_fixtures.h" |
| |
| namespace spvtools { |
| namespace val { |
| namespace { |
| |
| using ::testing::HasSubstr; |
| using ::testing::Not; |
| |
| using ValidateAdjacency = spvtest::ValidateBase<bool>; |
| |
| TEST_F(ValidateAdjacency, OpPhiBeginsModuleFail) { |
| const std::string module = R"( |
| %result = OpPhi %bool %true %true_label %false %false_label |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %func = OpTypeFunction %void |
| %main = OpFunction %void None %func |
| %main_entry = OpLabel |
| OpBranch %true_label |
| %true_label = OpLabel |
| OpBranch %false_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| CompileSuccessfully(module); |
| EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("ID 1[%bool] has not been defined")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpLoopMergeEndsModuleFail) { |
| const std::string module = R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| %void = OpTypeVoid |
| %func = OpTypeFunction %void |
| %main = OpFunction %void None %func |
| %main_entry = OpLabel |
| OpBranch %loop |
| %loop = OpLabel |
| OpLoopMerge %end %loop None |
| )"; |
| |
| CompileSuccessfully(module); |
| EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("Missing OpFunctionEnd at end of module")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpSelectionMergeEndsModuleFail) { |
| const std::string module = R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| %void = OpTypeVoid |
| %func = OpTypeFunction %void |
| %main = OpFunction %void None %func |
| %main_entry = OpLabel |
| OpBranch %merge |
| %merge = OpLabel |
| OpSelectionMerge %merge None |
| )"; |
| |
| CompileSuccessfully(module); |
| EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("Missing OpFunctionEnd at end of module")); |
| } |
| |
| std::string GenerateShaderCode( |
| const std::string& body, |
| const std::string& capabilities_and_extensions = "OpCapability Shader", |
| const std::string& execution_model = "Fragment") { |
| std::ostringstream ss; |
| ss << capabilities_and_extensions << "\n"; |
| ss << "OpMemoryModel Logical GLSL450\n"; |
| ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; |
| if (execution_model == "Fragment") { |
| ss << "OpExecutionMode %main OriginUpperLeft\n"; |
| } |
| |
| ss << R"( |
| %string = OpString "" |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %int = OpTypeInt 32 0 |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %zero = OpConstant %int 0 |
| %int_1 = OpConstant %int 1 |
| %func = OpTypeFunction %void |
| %func_int = OpTypePointer Function %int |
| %main = OpFunction %void None %func |
| %main_entry = OpLabel |
| )"; |
| |
| ss << body; |
| |
| ss << R"( |
| OpReturn |
| OpFunctionEnd)"; |
| |
| return ss.str(); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiPreceededByOpLabelSuccess) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| %line = OpLine %string 0 0 |
| %result = OpPhi %bool %true %true_label %false %false_label |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiPreceededByOpPhiSuccess) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| %1 = OpPhi %bool %true %true_label %false %false_label |
| %2 = OpPhi %bool %true %true_label %false %false_label |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiPreceededByOpLineSuccess) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| %line = OpLine %string 0 0 |
| %result = OpPhi %bool %true %true_label %false %false_label |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiPreceededByBadOpFail) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| OpNop |
| %result = OpPhi %bool %true %true_label %false %false_label |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("OpPhi must appear within a non-entry block before all " |
| "non-OpPhi instructions")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiPreceededByOpLineAndBadOpFail) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| OpNop |
| OpLine %string 1 1 |
| %result = OpPhi %bool %true %true_label %false %false_label |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("OpPhi must appear within a non-entry block before all " |
| "non-OpPhi instructions")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiFollowedByOpLineGood) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| %result = OpPhi %bool %true %true_label %false %false_label |
| OpLine %string 1 1 |
| OpNop |
| OpNop |
| OpLine %string 2 1 |
| OpNop |
| OpLine %string 3 1 |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiMultipleOpLineAndOpPhiFail) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| OpLine %string 1 1 |
| %value = OpPhi %int %zero %true_label %int_1 %false_label |
| OpNop |
| OpLine %string 2 1 |
| OpNop |
| OpLine %string 3 1 |
| %result = OpPhi %bool %true %true_label %false %false_label |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("OpPhi must appear within a non-entry block before all " |
| "non-OpPhi instructions")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiMultipleOpLineAndOpPhiGood) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| OpLine %string 1 1 |
| %value = OpPhi %int %zero %true_label %int_1 %false_label |
| OpLine %string 2 1 |
| %result = OpPhi %bool %true %true_label %false %false_label |
| OpLine %string 3 1 |
| OpNop |
| OpNop |
| OpLine %string 4 1 |
| OpNop |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpPhiInEntryBlockBad) { |
| const std::string body = R"( |
| OpLine %string 1 1 |
| %value = OpPhi %int |
| OpLine %string 2 1 |
| OpNop |
| OpLine %string 3 1 |
| OpNop |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("OpPhi must appear within a non-entry block before all " |
| "non-OpPhi instructions")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpVariableInFunctionGood) { |
| const std::string body = R"( |
| OpLine %string 1 1 |
| %var = OpVariable %func_int Function |
| OpLine %string 2 1 |
| OpNop |
| OpLine %string 3 1 |
| OpNop |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpVariableInFunctionMultipleGood) { |
| const std::string body = R"( |
| OpLine %string 1 1 |
| %1 = OpVariable %func_int Function |
| OpLine %string 2 1 |
| %2 = OpVariable %func_int Function |
| %3 = OpVariable %func_int Function |
| OpNop |
| OpLine %string 3 1 |
| OpNop |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpVariableInFunctionBad) { |
| const std::string body = R"( |
| %1 = OpUndef %int |
| %2 = OpVariable %func_int Function |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("All OpVariable instructions in a function must be the " |
| "first instructions")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpVariableInFunctionMultipleBad) { |
| const std::string body = R"( |
| OpNop |
| %1 = OpVariable %func_int Function |
| OpLine %string 1 1 |
| %2 = OpVariable %func_int Function |
| OpNop |
| OpNop |
| OpLine %string 2 1 |
| %3 = OpVariable %func_int Function |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("All OpVariable instructions in a function must be the " |
| "first instructions")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpLoopMergePreceedsOpBranchSuccess) { |
| const std::string body = R"( |
| OpBranch %loop |
| %loop = OpLabel |
| OpLoopMerge %end %loop None |
| OpBranch %loop |
| %end = OpLabel |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpLoopMergePreceedsOpBranchConditionalSuccess) { |
| const std::string body = R"( |
| OpBranch %loop |
| %loop = OpLabel |
| OpLoopMerge %end %loop None |
| OpBranchConditional %true %loop %end |
| %end = OpLabel |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpLoopMergePreceedsBadOpFail) { |
| const std::string body = R"( |
| OpBranch %loop |
| %loop = OpLabel |
| OpLoopMerge %end %loop None |
| OpNop |
| OpBranchConditional %true %loop %end |
| %end = OpLabel |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("OpLoopMerge must immediately precede either an " |
| "OpBranch or OpBranchConditional instruction.")); |
| } |
| |
| TEST_F(ValidateAdjacency, OpSelectionMergePreceedsOpBranchConditionalSuccess) { |
| const std::string body = R"( |
| OpSelectionMerge %end_label None |
| OpBranchConditional %true %true_label %false_label |
| %true_label = OpLabel |
| OpBranch %end_label |
| %false_label = OpLabel |
| OpBranch %end_label |
| %end_label = OpLabel |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpSelectionMergePreceedsOpSwitchSuccess) { |
| const std::string body = R"( |
| OpSelectionMerge %merge None |
| OpSwitch %zero %merge 0 %label |
| %label = OpLabel |
| OpBranch %merge |
| %merge = OpLabel |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); |
| } |
| |
| TEST_F(ValidateAdjacency, OpSelectionMergePreceedsBadOpFail) { |
| const std::string body = R"( |
| OpSelectionMerge %merge None |
| OpNop |
| OpSwitch %zero %merge 0 %label |
| %label = OpLabel |
| OpBranch %merge |
| %merge = OpLabel |
| )"; |
| |
| CompileSuccessfully(GenerateShaderCode(body)); |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); |
| EXPECT_THAT(getDiagnosticString(), |
| HasSubstr("OpSelectionMerge must immediately precede either an " |
| "OpBranchConditional or OpSwitch instruction")); |
| } |
| |
| } // namespace |
| } // namespace val |
| } // namespace spvtools |