Validate location assignments (#3308)
* Add validation that input/output locations are assigned correctly
* Account for component assignment
* Account for 4 components per location and track the combined
coordinate
* Account for multiple output indexes
* handle specifically arrayed variables
* Arrayed variables that specify a component get locations determined
per index of the array for the sub type
* Added tests that check locations and components can be assigned
to interleave an array's locations and components
* Fix up which interfaces are allowed to be arrayed for various shader
stages based on glslang
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index ce09e18..d381276 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -1540,6 +1540,21 @@
return SPV_SUCCESS;
}
+spv_result_t CheckLocationDecoration(ValidationState_t& vstate,
+ const Instruction& inst,
+ const Decoration& decoration) {
+ if (inst.opcode() == SpvOpVariable) return SPV_SUCCESS;
+
+ if (decoration.struct_member_index() != Decoration::kInvalidMember &&
+ inst.opcode() == SpvOpTypeStruct) {
+ return SPV_SUCCESS;
+ }
+
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Location decoration can only be applied to a variable or member "
+ "of a structure type";
+}
+
#define PASS_OR_BAIL_AT_LINE(X, LINE) \
{ \
spv_result_t e##LINE = (X); \
@@ -1590,6 +1605,9 @@
case SpvDecorationBufferBlock:
PASS_OR_BAIL(CheckBlockDecoration(vstate, *inst, decoration));
break;
+ case SpvDecorationLocation:
+ PASS_OR_BAIL(CheckLocationDecoration(vstate, *inst, decoration));
+ break;
default:
break;
}
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index c85b673..ed8cb9c 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -102,6 +102,373 @@
return SPV_SUCCESS;
}
+// This function assumes a base location has been determined already. As such
+// any further location decorations are invalid.
+// TODO: if this code turns out to be slow, there is an opportunity to cache
+// the result for a given type id.
+spv_result_t NumConsumedLocations(ValidationState_t& _, const Instruction* type,
+ uint32_t* num_locations) {
+ *num_locations = 0;
+ switch (type->opcode()) {
+ case SpvOpTypeInt:
+ case SpvOpTypeFloat:
+ // Scalars always consume a single location.
+ *num_locations = 1;
+ break;
+ case SpvOpTypeVector:
+ // 3- and 4-component 64-bit vectors consume two locations.
+ if ((_.ContainsSizedIntOrFloatType(type->id(), SpvOpTypeInt, 64) ||
+ _.ContainsSizedIntOrFloatType(type->id(), SpvOpTypeFloat, 64)) &&
+ (type->GetOperandAs<uint32_t>(2) > 2)) {
+ *num_locations = 2;
+ } else {
+ *num_locations = 1;
+ }
+ break;
+ case SpvOpTypeMatrix:
+ // Matrices consume locations equal to the underlying vector type for
+ // each column.
+ NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
+ num_locations);
+ *num_locations *= type->GetOperandAs<uint32_t>(2);
+ break;
+ case SpvOpTypeArray: {
+ // Arrays consume locations equal to the underlying type times the number
+ // of elements in the vector.
+ NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
+ num_locations);
+ bool is_int = false;
+ bool is_const = false;
+ uint32_t value = 0;
+ // Attempt to evaluate the number of array elements.
+ std::tie(is_int, is_const, value) =
+ _.EvalInt32IfConst(type->GetOperandAs<uint32_t>(2));
+ if (is_int && is_const) *num_locations *= value;
+ break;
+ }
+ case SpvOpTypeStruct: {
+ // Members cannot have location decorations at this point.
+ if (_.HasDecoration(type->id(), SpvDecorationLocation)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, type)
+ << "Members cannot be assigned a location";
+ }
+
+ // Structs consume locations equal to the sum of the locations consumed
+ // by the members.
+ for (uint32_t i = 1; i < type->operands().size(); ++i) {
+ uint32_t member_locations = 0;
+ if (auto error = NumConsumedLocations(
+ _, _.FindDef(type->GetOperandAs<uint32_t>(i)),
+ &member_locations)) {
+ return error;
+ }
+ *num_locations += member_locations;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return SPV_SUCCESS;
+}
+
+// Returns the number of components consumed by types that support a component
+// decoration.
+uint32_t NumConsumedComponents(ValidationState_t& _, const Instruction* type) {
+ uint32_t num_components = 0;
+ switch (type->opcode()) {
+ case SpvOpTypeInt:
+ case SpvOpTypeFloat:
+ // 64-bit types consume two components.
+ if (type->GetOperandAs<uint32_t>(1) == 64) {
+ num_components = 2;
+ } else {
+ num_components = 1;
+ }
+ break;
+ case SpvOpTypeVector:
+ // Vectors consume components equal to the underlying type's consumption
+ // times the number of elements in the vector. Note that 3- and 4-element
+ // vectors cannot have a component decoration (i.e. assumed to be zero).
+ num_components =
+ NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
+ num_components *= type->GetOperandAs<uint32_t>(2);
+ break;
+ default:
+ // This is an error that is validated elsewhere.
+ break;
+ }
+
+ return num_components;
+}
+
+// Populates |locations| (and/or |output_index1_locations|) with the use
+// location and component coordinates for |variable|. Indices are calculated as
+// 4 * location + component.
+spv_result_t GetLocationsForVariable(
+ ValidationState_t& _, const Instruction* entry_point,
+ const Instruction* variable, std::vector<bool>* locations,
+ std::vector<bool>* output_index1_locations) {
+ const bool is_fragment = entry_point->GetOperandAs<SpvExecutionModel>(0) ==
+ SpvExecutionModelFragment;
+ const bool is_output =
+ variable->GetOperandAs<SpvStorageClass>(2) == SpvStorageClassOutput;
+ auto ptr_type_id = variable->GetOperandAs<uint32_t>(0);
+ auto ptr_type = _.FindDef(ptr_type_id);
+ auto type_id = ptr_type->GetOperandAs<uint32_t>(2);
+ auto type = _.FindDef(type_id);
+
+ // Check for Location, Component and Index decorations on the variable. The
+ // validator allows duplicate decorations if the location/component/index are
+ // equal. Also track Patch and PerTaskNV decorations.
+ bool has_location = false;
+ uint32_t location = 0;
+ bool has_component = false;
+ uint32_t component = 0;
+ bool has_index = false;
+ uint32_t index = 0;
+ bool has_patch = false;
+ bool has_per_task_nv = false;
+ bool has_per_vertex_nv = false;
+ for (auto& dec : _.id_decorations(variable->id())) {
+ if (dec.dec_type() == SpvDecorationLocation) {
+ if (has_location && dec.params()[0] != location) {
+ return _.diag(SPV_ERROR_INVALID_DATA, variable)
+ << "Variable has conflicting location decorations";
+ }
+ has_location = true;
+ location = dec.params()[0];
+ } else if (dec.dec_type() == SpvDecorationComponent) {
+ if (has_component && dec.params()[0] != component) {
+ return _.diag(SPV_ERROR_INVALID_DATA, variable)
+ << "Variable has conflicting component decorations";
+ }
+ has_component = true;
+ component = dec.params()[0];
+ } else if (dec.dec_type() == SpvDecorationIndex) {
+ if (!is_output || !is_fragment) {
+ return _.diag(SPV_ERROR_INVALID_DATA, variable)
+ << "Index can only be applied to Fragment output variables";
+ }
+ if (has_index && dec.params()[0] != index) {
+ return _.diag(SPV_ERROR_INVALID_DATA, variable)
+ << "Variable has conflicting index decorations";
+ }
+ has_index = true;
+ index = dec.params()[0];
+ } else if (dec.dec_type() == SpvDecorationBuiltIn) {
+ // Don't check built-ins.
+ return SPV_SUCCESS;
+ } else if (dec.dec_type() == SpvDecorationPatch) {
+ has_patch = true;
+ } else if (dec.dec_type() == SpvDecorationPerTaskNV) {
+ has_per_task_nv = true;
+ } else if (dec.dec_type() == SpvDecorationPerVertexNV) {
+ has_per_vertex_nv = true;
+ }
+ }
+
+ // Vulkan 14.1.3: Tessellation control and mesh per-vertex outputs and
+ // tessellation control, evaluation and geometry per-vertex inputs have a
+ // layer of arraying that is not included in interface matching.
+ bool is_arrayed = false;
+ switch (entry_point->GetOperandAs<SpvExecutionModel>(0)) {
+ case SpvExecutionModelTessellationControl:
+ if (!has_patch) {
+ is_arrayed = true;
+ }
+ break;
+ case SpvExecutionModelTessellationEvaluation:
+ if (!is_output && !has_patch) {
+ is_arrayed = true;
+ }
+ break;
+ case SpvExecutionModelGeometry:
+ if (!is_output) {
+ is_arrayed = true;
+ }
+ break;
+ case SpvExecutionModelFragment:
+ if (!is_output && has_per_vertex_nv) {
+ is_arrayed = true;
+ }
+ break;
+ case SpvExecutionModelMeshNV:
+ if (is_output && !has_per_task_nv) {
+ is_arrayed = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Unpack arrayness.
+ if (is_arrayed && (type->opcode() == SpvOpTypeArray ||
+ type->opcode() == SpvOpTypeRuntimeArray)) {
+ type_id = type->GetOperandAs<uint32_t>(1);
+ type = _.FindDef(type_id);
+ }
+
+ if (type->opcode() == SpvOpTypeStruct) {
+ // Don't check built-ins.
+ if (_.HasDecoration(type_id, SpvDecorationBuiltIn)) return SPV_SUCCESS;
+ }
+
+ // Only block-decorated structs don't need a location on the variable.
+ const bool is_block = _.HasDecoration(type_id, SpvDecorationBlock);
+ if (!has_location && !is_block) {
+ return _.diag(SPV_ERROR_INVALID_DATA, variable)
+ << "Variable must be decorated with a location";
+ }
+
+ const std::string storage_class = is_output ? "output" : "input";
+ if (has_location) {
+ auto sub_type = type;
+ bool is_int = false;
+ bool is_const = false;
+ uint32_t array_size = 1;
+ // If the variable is still arrayed, mark the locations/components per
+ // index.
+ if (type->opcode() == SpvOpTypeArray) {
+ // Determine the array size if possible and get the element type.
+ std::tie(is_int, is_const, array_size) =
+ _.EvalInt32IfConst(type->GetOperandAs<uint32_t>(2));
+ if (!is_int || !is_const) array_size = 1;
+ auto sub_type_id = type->GetOperandAs<uint32_t>(1);
+ sub_type = _.FindDef(sub_type_id);
+ }
+
+ for (uint32_t array_idx = 0; array_idx < array_size; ++array_idx) {
+ uint32_t num_locations = 0;
+ if (auto error = NumConsumedLocations(_, sub_type, &num_locations))
+ return error;
+
+ uint32_t num_components = NumConsumedComponents(_, sub_type);
+ uint32_t array_location = location + (num_locations * array_idx);
+ uint32_t start = array_location * 4;
+ uint32_t end = (array_location + num_locations) * 4;
+ if (num_components != 0) {
+ start += component;
+ end = array_location * 4 + component + num_components;
+ }
+
+ auto locs = locations;
+ if (has_index && index == 1) locs = output_index1_locations;
+
+ if (end > locs->size()) {
+ locs->resize(end, false);
+ }
+ for (uint32_t i = start; i < end; ++i) {
+ if (locs->at(i)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
+ << "Entry-point has conflicting " << storage_class
+ << " location assignment at location " << i / 4
+ << ", component " << i % 4;
+ }
+ (*locs)[i] = true;
+ }
+ }
+ } else {
+ // For Block-decorated structs with no location assigned to the variable,
+ // each member of the block must be assigned a location. Also record any
+ // member component assignments. The validator allows duplicate decorations
+ // if they agree on the location/component.
+ std::unordered_map<uint32_t, uint32_t> member_locations;
+ std::unordered_map<uint32_t, uint32_t> member_components;
+ for (auto& dec : _.id_decorations(type_id)) {
+ if (dec.dec_type() == SpvDecorationLocation) {
+ auto where = member_locations.find(dec.struct_member_index());
+ if (where == member_locations.end()) {
+ member_locations[dec.struct_member_index()] = dec.params()[0];
+ } else if (where->second != dec.params()[0]) {
+ return _.diag(SPV_ERROR_INVALID_DATA, type)
+ << "Member index " << dec.struct_member_index()
+ << " has conflicting location assignments";
+ }
+ } else if (dec.dec_type() == SpvDecorationComponent) {
+ auto where = member_components.find(dec.struct_member_index());
+ if (where == member_components.end()) {
+ member_components[dec.struct_member_index()] = dec.params()[0];
+ } else if (where->second != dec.params()[0]) {
+ return _.diag(SPV_ERROR_INVALID_DATA, type)
+ << "Member index " << dec.struct_member_index()
+ << " has conflicting component assignments";
+ }
+ }
+ }
+
+ for (uint32_t i = 1; i < type->operands().size(); ++i) {
+ auto where = member_locations.find(i - 1);
+ if (where == member_locations.end()) {
+ return _.diag(SPV_ERROR_INVALID_DATA, type)
+ << "Member index " << i - 1
+ << " is missing a location assignment";
+ }
+
+ location = where->second;
+ auto member = _.FindDef(type->GetOperandAs<uint32_t>(i));
+ uint32_t num_locations = 0;
+ if (auto error = NumConsumedLocations(_, member, &num_locations))
+ return error;
+
+ // If the component is not specified, it is assumed to be zero.
+ uint32_t num_components = NumConsumedComponents(_, member);
+ component = 0;
+ if (member_components.count(i - 1)) {
+ component = member_components[i - 1];
+ }
+
+ uint32_t start = location * 4;
+ uint32_t end = (location + num_locations) * 4;
+ if (num_components != 0) {
+ start += component;
+ end = location * 4 + component + num_components;
+ }
+ if (end > locations->size()) {
+ locations->resize(end, false);
+ }
+ for (uint32_t l = start; l < end; ++l) {
+ if (locations->at(l)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
+ << "Entry-point has conflicting " << storage_class
+ << " location assignment at location " << l / 4
+ << ", component " << l % 4;
+ }
+ (*locations)[l] = true;
+ }
+ }
+ }
+
+ return SPV_SUCCESS;
+}
+
+spv_result_t ValidateLocations(ValidationState_t& _,
+ const Instruction* entry_point) {
+ // Reserve space for 16 locations with 4 components each.
+ std::vector<bool> input_locations(16 * 4, false);
+ std::vector<bool> output_locations_index0(16 * 4, false);
+ std::vector<bool> output_locations_index1(16 * 4, false);
+ for (uint32_t i = 3; i < entry_point->operands().size(); ++i) {
+ auto interface_id = entry_point->GetOperandAs<uint32_t>(i);
+ auto interface_var = _.FindDef(interface_id);
+ auto storage_class = interface_var->GetOperandAs<SpvStorageClass>(2);
+ if (storage_class != SpvStorageClassInput &&
+ storage_class != SpvStorageClassOutput) {
+ continue;
+ }
+
+ auto locations = (storage_class == SpvStorageClassInput)
+ ? &input_locations
+ : &output_locations_index0;
+ if (auto error = GetLocationsForVariable(
+ _, entry_point, interface_var, locations, &output_locations_index1))
+ return error;
+ }
+
+ return SPV_SUCCESS;
+}
+
} // namespace
spv_result_t ValidateInterfaces(ValidationState_t& _) {
@@ -114,6 +481,17 @@
}
}
+ if (spvIsVulkanEnv(_.context()->target_env)) {
+ for (auto& inst : _.ordered_instructions()) {
+ if (inst.opcode() == SpvOpEntryPoint) {
+ if (auto error = ValidateLocations(_, &inst)) {
+ return error;
+ }
+ }
+ if (inst.opcode() == SpvOpTypeVoid) break;
+ }
+ }
+
return SPV_SUCCESS;
}
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index 730986c..b1ab0c9 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -2532,7 +2532,9 @@
CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
generator.before_types_ = R"(
+OpDecorate %input_type Block
OpMemberDecorate %input_type 0 BuiltIn Position
+OpDecorate %output_type Block
OpMemberDecorate %output_type 0 BuiltIn Position
)";
@@ -2543,8 +2545,7 @@
%input = OpVariable %input_ptr Input
%input_f32vec4_ptr = OpTypePointer Input %f32vec4
%output_type = OpTypeStruct %f32vec4
-%arrayed_output_type = OpTypeArray %output_type %u32_3
-%output_ptr = OpTypePointer Output %arrayed_output_type
+%output_ptr = OpTypePointer Output %output_type
%output = OpVariable %output_ptr Output
%output_f32vec4_ptr = OpTypePointer Output %f32vec4
)";
@@ -2555,7 +2556,7 @@
entry_point.interfaces = "%input %output";
entry_point.body = R"(
%input_pos = OpAccessChain %input_f32vec4_ptr %input %u32_0 %u32_0
-%output_pos = OpAccessChain %output_f32vec4_ptr %output %u32_0 %u32_0
+%output_pos = OpAccessChain %output_f32vec4_ptr %output %u32_0
%pos = OpLoad %f32vec4 %input_pos
OpStore %output_pos %pos
)";
@@ -3062,6 +3063,8 @@
OpEntryPoint Fragment %4 "PSMa" %12 %17
OpExecutionMode %4 OriginUpperLeft
OpDecorate %gl_PointCoord BuiltIn PointCoord
+ OpDecorate %12 Location 0
+ OpDecorate %17 Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index 8580818..756f762 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -1367,8 +1367,9 @@
std::vector<std::string>{"GeometryStreams"}),
std::make_pair(std::string(kOpenCLMemoryModel) +
"OpEntryPoint Kernel %func \"compute\" \n"
- "OpDecorate %intt Location 0\n"
- "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+ "OpMemberDecorate %struct 0 Location 0\n"
+ "%intt = OpTypeInt 32 0\n"
+ "%struct = OpTypeStruct %intt\n" + std::string(kVoidFVoid),
ShaderDependencies()),
std::make_pair(std::string(kOpenCLMemoryModel) +
"OpEntryPoint Kernel %func \"compute\" \n"
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index b50c4ad..e646162 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -7097,6 +7097,68 @@
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
+TEST_F(ValidateDecorations, LocationVariableGood) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %in_var Location 0
+%float = OpTypeFloat 32
+%ptr_input_float = OpTypePointer Input %float
+%in_var = OpVariable %ptr_input_float Input
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, LocationStructMemberGood) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %struct 0 Location 0
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, LocationStructBad) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %struct Location 0
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Location decoration can only be applied to a variable "
+ "or member of a structure type"));
+}
+
+TEST_F(ValidateDecorations, LocationFloatBad) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %float Location 0
+%float = OpTypeFloat 32
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Location decoration can only be applied to a variable "
+ "or member of a structure type"));
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index 3410616..c274fec 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -399,6 +399,963 @@
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
+TEST_F(ValidateInterfacesTest, VulkanLocationsDoubleAssignmentVariable) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var Location 0
+OpDecorate %var Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%ptr_input_float = OpTypePointer Input %float
+%var = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Variable has conflicting location decorations"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsVariableAndMemberAssigned) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var Location 0
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Members cannot be assigned a location"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsMemberAndSubMemberAssigned) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %outer Block
+OpMemberDecorate %outer 0 Location 0
+OpMemberDecorate %struct 0 Location 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float
+%outer = OpTypeStruct %struct
+%ptr_input_outer = OpTypePointer Input %outer
+%var = OpVariable %ptr_input_outer Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Members cannot be assigned a location"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsDoubleAssignmentStructMember) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 1 Location 0
+OpMemberDecorate %struct 1 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Member index 1 has conflicting location assignments"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsMissingAssignmentStructMember) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 1 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Member index 0 is missing a location assignment"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsMissingAssignmentNonBlockStruct) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Variable must be decorated with a location"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsVariableConflictInput) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var1 = OpVariable %ptr_input_struct Input
+%var2 = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 0"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsVariableConflictOutput) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 1
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_output_struct = OpTypePointer Output %struct
+%var1 = OpVariable %ptr_output_struct Output
+%var2 = OpVariable %ptr_output_struct Output
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting output location assignment "
+ "at location 1"));
+}
+
+TEST_F(ValidateInterfacesTest,
+ VulkanLocationsSameLocationInputAndOutputNoConflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 1
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%ptr_output_struct = OpTypePointer Output %struct
+%var1 = OpVariable %ptr_input_struct Input
+%var2 = OpVariable %ptr_output_struct Output
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsVariableInGap) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+OpMemberDecorate %struct 1 Location 2
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%ptr_input_float = OpTypePointer Input %float
+%var1 = OpVariable %ptr_input_struct Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsLargeFloatVectorConflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%vector = OpTypeVector %double 3
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_vector = OpTypePointer Input %vector
+%var1 = OpVariable %ptr_input_vector Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 1"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsLargeIntVectorConflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Int64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%long = OpTypeInt 64 0
+%vector = OpTypeVector %long 4
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_vector = OpTypePointer Input %vector
+%var1 = OpVariable %ptr_input_vector Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 1"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsMatrix2x2Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%vector = OpTypeVector %float 2
+%matrix = OpTypeMatrix %vector 2
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_matrix = OpTypePointer Input %matrix
+%var1 = OpVariable %ptr_input_matrix Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 1"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsMatrix3x3Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 2
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%vector = OpTypeVector %float 3
+%matrix = OpTypeMatrix %vector 3
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_matrix = OpTypePointer Input %matrix
+%var1 = OpVariable %ptr_input_matrix Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 2"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsMatrix4x4Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 3
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%vector = OpTypeVector %float 4
+%matrix = OpTypeMatrix %vector 4
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_matrix = OpTypePointer Input %matrix
+%var1 = OpVariable %ptr_input_matrix Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 3"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsLargeMatrix2x2Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%vector = OpTypeVector %double 2
+%matrix = OpTypeMatrix %vector 2
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_matrix = OpTypePointer Input %matrix
+%var1 = OpVariable %ptr_input_matrix Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 1"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsLargeMatrix3x3Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 5
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%vector = OpTypeVector %double 3
+%matrix = OpTypeMatrix %vector 3
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_matrix = OpTypePointer Input %matrix
+%var1 = OpVariable %ptr_input_matrix Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 5"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsLargeMatrix4x4Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 7
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%vector = OpTypeVector %double 4
+%matrix = OpTypeMatrix %vector 4
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_matrix = OpTypePointer Input %matrix
+%var1 = OpVariable %ptr_input_matrix Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 7"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsArray2Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%array = OpTypeArray %int %int_2
+%struct = OpTypeStruct %array
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var1 = OpVariable %ptr_input_struct Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 1"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsArray4Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 3
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array = OpTypeArray %int %int_4
+%struct = OpTypeStruct %array
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var1 = OpVariable %ptr_input_struct Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 3"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsMatrix4x4Array4Conflict) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 0
+OpDecorate %var2 Location 15
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%vector = OpTypeVector %float 4
+%matrix = OpTypeMatrix %vector 4
+%array = OpTypeArray %matrix %int_4
+%struct = OpTypeStruct %array
+%ptr_input_float = OpTypePointer Input %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var1 = OpVariable %ptr_input_struct Input
+%var2 = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 15"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsComponentDisambiguates) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+OpMemberDecorate %struct 0 Component 0
+OpMemberDecorate %struct 1 Location 0
+OpMemberDecorate %struct 1 Component 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var1 = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsComponentIn64BitVec3) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+OpMemberDecorate %struct 1 Location 1
+OpMemberDecorate %struct 1 Component 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%double3 = OpTypeVector %double 3
+%struct = OpTypeStruct %double3 %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location assignment "
+ "at location 1, component 1"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsComponentAfter64BitVec3) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+OpMemberDecorate %struct 1 Location 1
+OpMemberDecorate %struct 1 Component 2
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%double3 = OpTypeVector %double 3
+%struct = OpTypeStruct %double3 %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsConflictingComponentVariable) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var Location 0
+OpDecorate %var Component 0
+OpDecorate %var Component 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%ptr_input_float = OpTypePointer Input %float
+%var = OpVariable %ptr_input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Variable has conflicting component decorations"));
+}
+
+TEST_F(ValidateInterfacesTest,
+ VulkanLocationsConflictingComponentStructMember) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+OpMemberDecorate %struct 0 Component 0
+OpMemberDecorate %struct 0 Component 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Member index 0 has conflicting component assignments"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsVariableConflictOutputIndex1) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 1
+OpDecorate %var1 Index 1
+OpDecorate %var2 Location 1
+OpDecorate %var2 Index 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_output_struct = OpTypePointer Output %struct
+%var1 = OpVariable %ptr_output_struct Output
+%var2 = OpVariable %ptr_output_struct Output
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting output location assignment "
+ "at location 1"));
+}
+
+TEST_F(ValidateInterfacesTest,
+ VulkanLocationsVariableNoConflictDifferentIndex) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1 %var2
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 1
+OpDecorate %var1 Index 0
+OpDecorate %var2 Location 1
+OpDecorate %var2 Index 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_output_struct = OpTypePointer Output %struct
+%var1 = OpVariable %ptr_output_struct Output
+%var2 = OpVariable %ptr_output_struct Output
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsIndexGLCompute) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var1
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %var1 Location 1
+OpDecorate %var1 Index 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_output_struct = OpTypePointer Output %struct
+%var1 = OpVariable %ptr_output_struct Output
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Index can only be applied to Fragment output variables"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsIndexInput) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var1
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var1 Location 1
+OpDecorate %var1 Index 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct = OpTypeStruct %float %float
+%ptr_input_struct = OpTypePointer Input %struct
+%var1 = OpVariable %ptr_input_struct Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Index can only be applied to Fragment output variables"));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsArrayWithComponent) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main" %11 %18 %28 %36 %40
+OpExecutionMode %4 OriginUpperLeft
+OpDecorate %11 Location 0
+OpDecorate %18 Component 0
+OpDecorate %18 Location 0
+OpDecorate %28 Component 1
+OpDecorate %28 Location 0
+OpDecorate %36 Location 1
+OpDecorate %40 Component 0
+OpDecorate %40 Location 1
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%11 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_float = OpTypePointer Output %float
+%18 = OpVariable %_ptr_Output_float Output
+%uint = OpTypeInt 32 0
+%v3float = OpTypeVector %float 3
+%uint_2 = OpConstant %uint 2
+%_arr_v3float_uint_2 = OpTypeArray %v3float %uint_2
+%_ptr_Output__arr_v3float_uint_2 = OpTypePointer Output %_arr_v3float_uint_2
+%28 = OpVariable %_ptr_Output__arr_v3float_uint_2 Output
+%_ptr_Output_v3float = OpTypePointer Output %v3float
+%36 = OpVariable %_ptr_Input_v4float Input
+%40 = OpVariable %_ptr_Output_float Output
+%4 = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationsArrayWithComponentBad) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main" %11 %18 %28 %36 %40
+OpExecutionMode %4 OriginUpperLeft
+OpDecorate %11 Location 0
+OpDecorate %18 Component 0
+OpDecorate %18 Location 0
+OpDecorate %28 Component 1
+OpDecorate %28 Location 0
+OpDecorate %36 Location 1
+OpDecorate %40 Component 1
+OpDecorate %40 Location 1
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%11 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_float = OpTypePointer Output %float
+%18 = OpVariable %_ptr_Output_float Output
+%uint = OpTypeInt 32 0
+%v3float = OpTypeVector %float 3
+%uint_2 = OpConstant %uint 2
+%_arr_v3float_uint_2 = OpTypeArray %v3float %uint_2
+%_ptr_Output__arr_v3float_uint_2 = OpTypePointer Output %_arr_v3float_uint_2
+%28 = OpVariable %_ptr_Output__arr_v3float_uint_2 Output
+%_ptr_Output_v3float = OpTypePointer Output %v3float
+%36 = OpVariable %_ptr_Input_v4float Input
+%40 = OpVariable %_ptr_Output_float Output
+%4 = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting output location "
+ "assignment at location 1, component 1"));
+}
+
} // namespace
} // namespace val
} // namespace spvtools