Validate OpPhi predecessors.
Validate OpLoopMerge successors.
Validate OpSelectionMerge successors.
Fix collateral damage to existing tests.
Remove ValidateIdWithMessage.OpSampledImageUsedInOpPhiBad.
source/val/instruction.cpp \
source/val/validation_state.cpp \
source/validate.cpp \
+ source/validate_adjacency.cpp \
source/validate_arithmetics.cpp \
source/validate_atomics.cpp \
source/validate_bitwise.cpp \
${CMAKE_CURRENT_SOURCE_DIR}/text.cpp
${CMAKE_CURRENT_SOURCE_DIR}/text_handler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/validate_adjacency.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate_arithmetics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate_atomics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate_bitwise.cpp
<< id_str.substr(0, id_str.size() - 1);
}
+ // Validate the preconditions involving adjacent instructions. e.g. SpvOpPhi
+ // must only be preceeded by SpvOpLabel, SpvOpPhi, or SpvOpLine.
+ if (auto error = ValidateAdjacency(*vstate)) return error;
+
// CFG checks are performed after the binary has been parsed
// and the CFGPass has collected information about the control flow
if (auto error = PerformCfgChecks(*vstate)) return error;
/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_ID otherwise
spv_result_t CheckIdDefinitionDominateUse(const ValidationState_t& _);
+/// @brief This function checks for preconditions involving the adjacent
+/// instructions.
+///
+/// This function will iterate over all instructions and check for any required
+/// predecessor and/or successor instructions. e.g. SpvOpPhi must only be
+/// preceeded by SpvOpLabel, SpvOpPhi, or SpvOpLine.
+///
+/// @param[in] _ the validation state of the module
+///
+/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_DATA otherwise
+spv_result_t ValidateAdjacency(ValidationState_t& _);
+
/// @brief Updates the immediate dominator for each of the block edges
///
/// Updates the immediate dominator of the blocks for each of the edges
--- /dev/null
+// 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.
+
+// Validates correctness of the intra-block preconditions of SPIR-V
+// instructions.
+
+#include "validate.h"
+
+#include <string>
+
+#include "diagnostic.h"
+#include "opcode.h"
+#include "val/instruction.h"
+#include "val/validation_state.h"
+
+namespace libspirv {
+
+spv_result_t ValidateAdjacency(ValidationState_t& _) {
+ const auto& instructions = _.ordered_instructions();
+ for (auto i = instructions.cbegin(); i != instructions.cend(); ++i) {
+ switch (i->opcode()) {
+ case SpvOpPhi:
+ if (i != instructions.cbegin()) {
+ switch (prev(i)->opcode()) {
+ case SpvOpLabel:
+ case SpvOpPhi:
+ case SpvOpLine:
+ break;
+ default:
+ return _.diag(SPV_ERROR_INVALID_DATA)
+ << "OpPhi must appear before all non-OpPhi instructions "
+ << "(except for OpLine, which can be mixed with OpPhi).";
+ }
+ }
+ break;
+ case SpvOpLoopMerge:
+ if (next(i) != instructions.cend()) {
+ switch (next(i)->opcode()) {
+ case SpvOpBranch:
+ case SpvOpBranchConditional:
+ break;
+ default:
+ return _.diag(SPV_ERROR_INVALID_DATA)
+ << "OpLoopMerge must immediately precede either an "
+ << "OpBranch or OpBranchConditional instruction. "
+ << "OpLoopMerge must be the second-to-last instruction in "
+ << "its block.";
+ }
+ }
+ break;
+ case SpvOpSelectionMerge:
+ if (next(i) != instructions.cend()) {
+ switch (next(i)->opcode()) {
+ case SpvOpBranchConditional:
+ case SpvOpSwitch:
+ break;
+ default:
+ return _.diag(SPV_ERROR_INVALID_DATA)
+ << "OpSelectionMerge must immediately precede either an "
+ << "OpBranchConditional or OpSwitch instruction. "
+ << "OpSelectionMerge must be the second-to-last "
+ << "instruction in its block.";
+ }
+ }
+ default:
+ break;
+ }
+ }
+
+ return SPV_SUCCESS;
+}
+
+} // namespace libspirv
const std::string body_before = R"(%main = OpFunction %void None %6
%14 = OpLabel
-OpSelectionMerge %17 None
OpBranch %18
%19 = OpLabel
%20 = OpLoad %float %inf
const std::string body_after = R"(%main = OpFunction %void None %6
%14 = OpLabel
-OpSelectionMerge %15 None
-OpBranch %16
-%16 = OpLabel
+OpBranch %15
+%15 = OpLabel
%20 = OpLoad %float %inf
%21 = OpFAdd %float %20 %float_n0_5
%22 = OpCompositeConstruct %v4float %21 %21 %21 %21
OpStore %outf4 %22
-OpBranch %15
-%15 = OpLabel
+OpBranch %19
+%19 = OpLabel
OpReturn
OpFunctionEnd
)";
%main = OpFunction %void None %6
%14 = OpLabel
%x = OpVariable %_ptr_Function_float Function
- OpSelectionMerge %17 None
OpBranch %18
%19 = OpLabel
%dead = OpVariable %_ptr_Function_float Function
%main = OpFunction %void None %6
%11 = OpLabel
%x = OpVariable %_ptr_Function_float Function
-OpSelectionMerge %12 None
-OpBranch %13
-%13 = OpLabel
-OpStore %x %float_4
OpBranch %12
%12 = OpLabel
+OpStore %x %float_4
+OpBranch %14
+%14 = OpLabel
OpReturn
OpFunctionEnd
)";
${VAL_TEST_COMMON_SRCS}
LIBS ${SPIRV_TOOLS}
)
+
+add_spvtools_unittest(TARGET val_adjacency
+ SRCS val_adjacency_test.cpp
+ ${VAL_TEST_COMMON_SRCS}
+ LIBS ${SPIRV_TOOLS}
+)
--- /dev/null
+// 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 "unit_spirv.h"
+#include "val_fixtures.h"
+
+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"
+%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"
+%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"
+%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";
+
+ ss << R"(
+%string = OpString ""
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%zero = OpConstant %int 0
+%func = OpTypeFunction %void
+%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 before all non-OpPhi 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"));
+}
+
+} // anonymous namespace
%voidfunc = OpTypeFunction %void
%main = OpFunction %void None %voidfunc
%lmain = OpLabel
-
- OpSelectionMerge %end None
)";
string BranchConditionalTail = R"(
"'23' as an operand of <id> '24'."));
}
-// Invalid: OpSampledImage result <id> is used by OpPhi
-TEST_F(ValidateIdWithMessage, OpSampledImageUsedInOpPhiBad) {
- string spirv = kGLSL450MemoryModel + sampledImageSetup + R"(
-%smpld_img = OpSampledImage %sampled_image_type %image_inst %sampler_inst
-%phi_result = OpPhi %sampled_image_type %smpld_img %label_1
-OpReturn
-OpFunctionEnd)";
- CompileSuccessfully(spirv.c_str());
- EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Result <id> from OpSampledImage instruction must not "
- "appear as operands of OpPhi. Found result <id> '23' "
- "as an operand of <id> '24'."));
-}
-
// Valid: Get a float in a matrix using CompositeExtract.
// Valid: Insert float into a matrix using CompositeInsert.
TEST_F(ValidateIdWithMessage, CompositeExtractInsertGood) {
TEST_F(ValidateIdWithMessage, OpBranchConditionalGood) {
string spirv = BranchConditionalSetup + R"(
%branch_cond = OpINotEqual %bool %i0 %i1
+ OpSelectionMerge %end None
OpBranchConditional %branch_cond %target_t %target_f
)" + BranchConditionalTail;
TEST_F(ValidateIdWithMessage, OpBranchConditionalWithWeightsGood) {
string spirv = BranchConditionalSetup + R"(
%branch_cond = OpINotEqual %bool %i0 %i1
+ OpSelectionMerge %end None
OpBranchConditional %branch_cond %target_t %target_f 1 1
)" + BranchConditionalTail;
TEST_F(ValidateIdWithMessage, OpBranchConditional_CondIsScalarInt) {
string spirv = BranchConditionalSetup + R"(
- OpBranchConditional %i0 %target_t %target_f
+ OpSelectionMerge %end None
+ OpBranchConditional %i0 %target_t %target_f
)" + BranchConditionalTail;
CompileSuccessfully(spirv.c_str());
TEST_F(ValidateIdWithMessage, OpBranchConditional_TrueTargetIsNotLabel) {
string spirv = BranchConditionalSetup + R"(
+ OpSelectionMerge %end None
OpBranchConditional %i0 %i0 %target_f
)" + BranchConditionalTail;
TEST_F(ValidateIdWithMessage, OpBranchConditional_FalseTargetIsNotLabel) {
string spirv = BranchConditionalSetup + R"(
- OpBranchConditional %i0 %target_t %i0
+ OpSelectionMerge %end None
+ OpBranchConditional %i0 %target_t %i0
)" + BranchConditionalTail;
CompileSuccessfully(spirv.c_str());
TEST_F(ValidateIdWithMessage, OpBranchConditional_NotEnoughWeights) {
string spirv = BranchConditionalSetup + R"(
%branch_cond = OpINotEqual %bool %i0 %i1
+ OpSelectionMerge %end None
OpBranchConditional %branch_cond %target_t %target_f 1
)" + BranchConditionalTail;
TEST_F(ValidateIdWithMessage, OpBranchConditional_TooManyWeights) {
string spirv = BranchConditionalSetup + R"(
%branch_cond = OpINotEqual %bool %i0 %i1
+ OpSelectionMerge %end None
OpBranchConditional %branch_cond %target_t %target_f 1 2 3
)" + BranchConditionalTail;