| // 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 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 |