Update location/component conflict validation (#5993)
Refs #5989
* Update input/output interface validation to treat arrays and matrices
as consuming all 4 components in each location
* For arrays and matrices make `NumConsumedComponents` more consistent
* calculate per element components and round up to a multiple of 4
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index 23e1857..06bae04 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -164,17 +164,22 @@
}
break;
case spv::Op::OpTypeMatrix:
- // Matrices consume locations equal to the underlying vector type for
- // each column.
- NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
- num_locations);
+ // Matrices consume locations equivalent to arrays of 4-component vectors.
+ if (_.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeInt, 64) ||
+ _.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeFloat, 64)) {
+ *num_locations = 2;
+ } else {
+ *num_locations = 1;
+ }
*num_locations *= type->GetOperandAs<uint32_t>(2);
break;
case spv::Op::OpTypeArray: {
// 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);
+ if (auto error = NumConsumedLocations(
+ _, _.FindDef(type->GetOperandAs<uint32_t>(1)), num_locations)) {
+ return error;
+ }
bool is_int = false;
bool is_const = false;
uint32_t value = 0;
@@ -244,10 +249,31 @@
NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
num_components *= type->GetOperandAs<uint32_t>(2);
break;
- case spv::Op::OpTypeArray:
- // Skip the array.
- return NumConsumedComponents(_,
- _.FindDef(type->GetOperandAs<uint32_t>(1)));
+ case spv::Op::OpTypeMatrix:
+ // Matrices consume all components of the location.
+ // Round up to next multiple of 4.
+ num_components =
+ NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
+ num_components *= type->GetOperandAs<uint32_t>(2);
+ num_components = ((num_components + 3) / 4) * 4;
+ break;
+ case spv::Op::OpTypeArray: {
+ // Arrays consume all components of the location.
+ // Round up to next multiple of 4.
+ num_components =
+ NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
+
+ 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_components *= value;
+
+ num_components = ((num_components + 3) / 4) * 4;
+ return num_components;
+ }
case spv::Op::OpTypePointer:
if (_.addressing_model() ==
spv::AddressingModel::PhysicalStorageBuffer64 &&
@@ -330,9 +356,10 @@
}
}
- // 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.
+ // Vulkan 15.1.3 (Interface Matching): 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<spv::ExecutionModel>(0)) {
case spv::ExecutionModel::TessellationControl:
@@ -386,51 +413,33 @@
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() == spv::Op::OpTypeArray) {
- // 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);
+ uint32_t num_locations = 0;
+ if (auto error = NumConsumedLocations(_, type, &num_locations))
+ return error;
+ uint32_t num_components = NumConsumedComponents(_, type);
+
+ uint32_t start = location * 4;
+ uint32_t end = (location + num_locations) * 4;
+ if (num_components % 4 != 0) {
+ start += component;
+ end = start + num_components;
}
- uint32_t num_locations = 0;
- if (auto error = NumConsumedLocations(_, sub_type, &num_locations))
- return error;
- uint32_t num_components = NumConsumedComponents(_, sub_type);
+ if (kMaxLocations <= start) {
+ // Too many locations, give up.
+ return SPV_SUCCESS;
+ }
- for (uint32_t array_idx = 0; array_idx < array_size; ++array_idx) {
- uint32_t array_location = location + (num_locations * array_idx);
- uint32_t start = array_location * 4;
- if (kMaxLocations <= start) {
- // Too many locations, give up.
- break;
- }
+ auto locs = locations;
+ if (has_index && index == 1) locs = output_index1_locations;
- 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;
-
- for (uint32_t i = start; i < end; ++i) {
- if (!locs->insert(i).second) {
- return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
- << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
- << "Entry-point has conflicting " << storage_class
- << " location assignment at location " << i / 4
- << ", component " << i % 4;
- }
+ for (uint32_t i = start; i < end; ++i) {
+ if (!locs->insert(i).second) {
+ return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
+ << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
+ << "Entry-point has conflicting " << storage_class
+ << " location assignment at location " << i / 4 << ", component "
+ << i % 4;
}
}
} else {
@@ -489,38 +498,19 @@
continue;
}
- if (member->opcode() == spv::Op::OpTypeArray && num_components >= 1 &&
- num_components < 4) {
- // When an array has an element that takes less than a location in
- // size, calculate the used locations in a strided manner.
- for (uint32_t l = location; l < num_locations + location; ++l) {
- for (uint32_t c = component; c < component + num_components; ++c) {
- uint32_t check = 4 * l + c;
- if (!locations->insert(check).second) {
- return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
- << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
- << "Entry-point has conflicting " << storage_class
- << " location assignment at location " << l
- << ", component " << c;
- }
- }
- }
- } else {
- // TODO: There is a hole here is the member is an array of 3- or
- // 4-element vectors of 64-bit types.
- uint32_t end = (location + num_locations) * 4;
- if (num_components != 0) {
- start += component;
- end = location * 4 + component + num_components;
- }
- for (uint32_t l = start; l < end; ++l) {
- if (!locations->insert(l).second) {
- return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
- << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
- << "Entry-point has conflicting " << storage_class
- << " location assignment at location " << l / 4
- << ", component " << l % 4;
- }
+ uint32_t end = (location + num_locations) * 4;
+ if (num_components % 4 != 0) {
+ start += component;
+ end = location * 4 + component + num_components;
+ }
+
+ for (uint32_t l = start; l < end; ++l) {
+ if (!locations->insert(l).second) {
+ return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
+ << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
+ << "Entry-point has conflicting " << storage_class
+ << " location assignment at location " << l / 4
+ << ", component " << l % 4;
}
}
}
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index f5ade33..bc9abf2 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -10394,7 +10394,7 @@
OpDecorate %gl_PerVertex Block
OpDecorate %FOO Component 2
OpDecorate %FOO Location 1
- OpDecorate %FOO0 Location 1
+ OpDecorate %FOO0 Location 4
OpDecorate %FOO0 Component 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index c3a1c7c..2ab887f 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -1415,7 +1415,12 @@
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
- EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08722"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting output location "
+ "assignment at location 0, component 0"));
}
TEST_F(ValidateInterfacesTest, VulkanLocationsArrayWithComponentBad) {
@@ -1428,7 +1433,7 @@
OpDecorate %18 Component 0
OpDecorate %18 Location 0
OpDecorate %28 Component 1
-OpDecorate %28 Location 0
+OpDecorate %28 Location 1
OpDecorate %36 Location 1
OpDecorate %40 Component 1
OpDecorate %40 Location 1
@@ -1543,7 +1548,12 @@
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
- EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08721"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location "
+ "assignment at location 0, component 0"));
}
TEST_F(ValidateInterfacesTest, VulkanLocationArrayWithComponent2) {
@@ -1575,7 +1585,12 @@
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
- EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08721"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry-point has conflicting input location "
+ "assignment at location 0, component 0"));
}
TEST_F(ValidateInterfacesTest, DuplicateInterfaceVariableSuccess) {