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