Add validation for invalid layout decoration usage (#6012)
* Checks that Block, BufferBlock, Offset, ArrayStride, and MatrixStride
are not used in a Vulkan environment in disallowed storage classes
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index fdbc151..d5d0c92 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -2083,6 +2083,197 @@
return SPV_SUCCESS;
}
+bool AllowsLayout(ValidationState_t& vstate, const spv::StorageClass sc) {
+ switch (sc) {
+ case spv::StorageClass::StorageBuffer:
+ case spv::StorageClass::Uniform:
+ case spv::StorageClass::PhysicalStorageBuffer:
+ case spv::StorageClass::PushConstant:
+ // Always explicitly laid out.
+ return true;
+ case spv::StorageClass::UniformConstant:
+ return false;
+ case spv::StorageClass::Workgroup:
+ return vstate.HasCapability(
+ spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
+ case spv::StorageClass::Function:
+ case spv::StorageClass::Private:
+ return vstate.version() < SPV_SPIRV_VERSION_WORD(1, 4);
+ case spv::StorageClass::Input:
+ case spv::StorageClass::Output:
+ // Block is used generally and mesh shaders use Offset.
+ return true;
+ default:
+ // TODO: Some storage classes in ray tracing use explicit layout
+ // decorations, but it is not well documented which. For now treat other
+ // storage classes as allowed to be laid out. See Vulkan internal issue
+ // 4192.
+ return true;
+ }
+}
+
+bool UsesExplicitLayout(ValidationState_t& vstate, uint32_t type_id,
+ std::unordered_map<uint32_t, bool>& cache) {
+ if (type_id == 0) {
+ return false;
+ }
+
+ if (cache.count(type_id)) {
+ return cache[type_id];
+ }
+
+ bool res = false;
+ const auto type_inst = vstate.FindDef(type_id);
+ if (type_inst->opcode() == spv::Op::OpTypeStruct ||
+ type_inst->opcode() == spv::Op::OpTypeArray ||
+ type_inst->opcode() == spv::Op::OpTypeRuntimeArray ||
+ type_inst->opcode() == spv::Op::OpTypePointer ||
+ type_inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
+ const auto& id_decs = vstate.id_decorations();
+ const auto iter = id_decs.find(type_id);
+ if (iter != id_decs.end()) {
+ res = std::any_of(iter->second.begin(), iter->second.end(),
+ [](const Decoration& d) {
+ return d.dec_type() == spv::Decoration::Block ||
+ d.dec_type() == spv::Decoration::BufferBlock ||
+ d.dec_type() == spv::Decoration::Offset ||
+ d.dec_type() == spv::Decoration::ArrayStride ||
+ d.dec_type() == spv::Decoration::MatrixStride;
+ });
+ }
+
+ if (!res) {
+ switch (type_inst->opcode()) {
+ case spv::Op::OpTypeStruct:
+ for (uint32_t i = 1; !res && i < type_inst->operands().size(); i++) {
+ res = UsesExplicitLayout(
+ vstate, type_inst->GetOperandAs<uint32_t>(i), cache);
+ }
+ break;
+ case spv::Op::OpTypeArray:
+ case spv::Op::OpTypeRuntimeArray:
+ res = UsesExplicitLayout(vstate, type_inst->GetOperandAs<uint32_t>(1),
+ cache);
+ break;
+ case spv::Op::OpTypePointer: {
+ const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
+ if (!AllowsLayout(vstate, sc)) {
+ res = UsesExplicitLayout(
+ vstate, type_inst->GetOperandAs<uint32_t>(2), cache);
+ }
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ cache[type_id] = res;
+ return res;
+}
+
+spv_result_t CheckInvalidVulkanExplicitLayout(ValidationState_t& vstate) {
+ if (!spvIsVulkanEnv(vstate.context()->target_env)) {
+ return SPV_SUCCESS;
+ }
+
+ std::unordered_map<uint32_t, bool> cache;
+ for (const auto& inst : vstate.ordered_instructions()) {
+ const auto type_id = inst.type_id();
+ const auto type_inst = vstate.FindDef(type_id);
+ uint32_t fail_id = 0;
+ // Variables are the main place to check for improper decorations, but some
+ // untyped pointer instructions must also be checked since those types may
+ // never be instantiated by a variable. Unlike verifying a valid layout,
+ // physical storage buffer does not need checked here since it is always
+ // explicitly laid out.
+ switch (inst.opcode()) {
+ case spv::Op::OpVariable:
+ case spv::Op::OpUntypedVariableKHR: {
+ const auto sc = inst.GetOperandAs<spv::StorageClass>(2);
+ auto check_id = type_id;
+ if (inst.opcode() == spv::Op::OpUntypedVariableKHR) {
+ if (inst.operands().size() > 3) {
+ check_id = inst.GetOperandAs<uint32_t>(3);
+ }
+ }
+ if (!AllowsLayout(vstate, sc) &&
+ UsesExplicitLayout(vstate, check_id, cache)) {
+ fail_id = check_id;
+ }
+ break;
+ }
+ case spv::Op::OpUntypedAccessChainKHR:
+ case spv::Op::OpUntypedInBoundsAccessChainKHR:
+ case spv::Op::OpUntypedPtrAccessChainKHR:
+ case spv::Op::OpUntypedInBoundsPtrAccessChainKHR: {
+ // Check both the base type and return type. The return type may have an
+ // invalid array stride.
+ const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
+ const auto base_type_id = inst.GetOperandAs<uint32_t>(2);
+ if (!AllowsLayout(vstate, sc)) {
+ if (UsesExplicitLayout(vstate, base_type_id, cache)) {
+ fail_id = base_type_id;
+ } else if (UsesExplicitLayout(vstate, type_id, cache)) {
+ fail_id = type_id;
+ }
+ }
+ break;
+ }
+ case spv::Op::OpUntypedArrayLengthKHR: {
+ // Check the data type.
+ const auto ptr_ty_id =
+ vstate.FindDef(inst.GetOperandAs<uint32_t>(3))->type_id();
+ const auto ptr_ty = vstate.FindDef(ptr_ty_id);
+ const auto sc = ptr_ty->GetOperandAs<spv::StorageClass>(1);
+ const auto base_type_id = inst.GetOperandAs<uint32_t>(2);
+ if (!AllowsLayout(vstate, sc) &&
+ UsesExplicitLayout(vstate, base_type_id, cache)) {
+ fail_id = base_type_id;
+ }
+ break;
+ }
+ case spv::Op::OpLoad: {
+ const auto ptr_id = inst.GetOperandAs<uint32_t>(2);
+ const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id());
+ if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
+ // For untyped pointers check the return type for an invalid layout.
+ const auto sc = ptr_type->GetOperandAs<spv::StorageClass>(1);
+ if (!AllowsLayout(vstate, sc) &&
+ UsesExplicitLayout(vstate, type_id, cache)) {
+ fail_id = type_id;
+ }
+ }
+ break;
+ }
+ case spv::Op::OpStore: {
+ const auto ptr_id = inst.GetOperandAs<uint32_t>(1);
+ const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id());
+ if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
+ // For untyped pointers, check the type of the data operand for an
+ // invalid layout.
+ const auto sc = ptr_type->GetOperandAs<spv::StorageClass>(1);
+ const auto data_type_id = vstate.GetOperandTypeId(&inst, 2);
+ if (!AllowsLayout(vstate, sc) &&
+ UsesExplicitLayout(vstate, data_type_id, cache)) {
+ fail_id = inst.GetOperandAs<uint32_t>(2);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ if (fail_id != 0) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Invalid explicit layout decorations on type for operand "
+ << vstate.getIdName(fail_id);
+ }
+ }
+
+ return SPV_SUCCESS;
+}
+
} // namespace
spv_result_t ValidateDecorations(ValidationState_t& vstate) {
@@ -2094,6 +2285,7 @@
if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
return error;
if (auto error = CheckDecorationsFromDecoration(vstate)) return error;
+ if (auto error = CheckInvalidVulkanExplicitLayout(vstate)) return error;
return SPV_SUCCESS;
}
diff --git a/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
index 44901f9..d08b98c 100644
--- a/test/fuzz/transformation_set_memory_operands_mask_test.cpp
+++ b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
@@ -333,10 +333,12 @@
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeStruct %6 %6 %6
+ %1001 = OpTypeStruct %6 %6 %6
%8 = OpTypeInt 32 0
%9 = OpConstant %8 12
%10 = OpTypeArray %7 %9
- %11 = OpTypePointer Private %10
+ %1002 = OpTypeArray %1001 %9
+ %11 = OpTypePointer Private %1002
%12 = OpVariable %11 Private
%15 = OpTypeStruct %10 %7
%16 = OpTypePointer Uniform %15
@@ -344,26 +346,32 @@
%18 = OpTypeInt 32 1
%19 = OpConstant %18 0
%20 = OpTypePointer Uniform %10
- %24 = OpTypePointer Private %7
+ %24 = OpTypePointer Private %1001
%27 = OpTypePointer Private %6
%30 = OpConstant %18 1
- %132 = OpTypePointer Function %10
+ %132 = OpTypePointer Function %1002
%135 = OpTypePointer Uniform %7
- %145 = OpTypePointer Function %7
+ %145 = OpTypePointer Function %1001
%4 = OpFunction %2 None %3
%5 = OpLabel
%133 = OpVariable %132 Function
%21 = OpAccessChain %20 %17 %19
- OpCopyMemory %12 %21 Aligned 16 Nontemporal|Aligned 16
+ %1003 = OpLoad %10 %21 Nontemporal|Aligned 16
+ %1004 = OpCopyLogical %1002 %1003
+ OpStore %12 %1004 Aligned 16
OpCopyMemory %133 %12 Volatile
OpCopyMemory %133 %12
OpCopyMemory %133 %12
%136 = OpAccessChain %135 %17 %30
%138 = OpAccessChain %24 %12 %19
- OpCopyMemory %138 %136 None Aligned 16
- OpCopyMemory %138 %136 Aligned 16
+ %1005 = OpLoad %7 %136 Aligned 16
+ %1006 = OpCopyLogical %1001 %1005
+ OpStore %138 %1006
+ %1007 = OpLoad %7 %136
+ %1008 = OpCopyLogical %1001 %1007
+ OpStore %138 %1008 Aligned 16
%146 = OpAccessChain %145 %133 %30
- %147 = OpLoad %7 %146 Volatile|Nontemporal|Aligned 16
+ %147 = OpLoad %1001 %146 Volatile|Nontemporal|Aligned 16
%148 = OpAccessChain %24 %12 %19
OpStore %148 %147 Nontemporal
OpReturn
@@ -382,14 +390,14 @@
MakeUnique<FactManager>(context.get()), validator_options);
{
TransformationSetMemoryOperandsMask transformation(
- MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
+ MakeInstructionDescriptor(21, spv::Op::OpLoad, 0),
(uint32_t)spv::MemoryAccessMask::Aligned |
(uint32_t)spv::MemoryAccessMask::Volatile,
- 1);
+ 0);
// Bad: cannot remove aligned
ASSERT_FALSE(TransformationSetMemoryOperandsMask(
- MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
- (uint32_t)spv::MemoryAccessMask::Volatile, 1)
+ MakeInstructionDescriptor(21, spv::Op::OpLoad, 0),
+ (uint32_t)spv::MemoryAccessMask::Volatile, 0)
.IsApplicable(context.get(), transformation_context));
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
@@ -399,13 +407,13 @@
{
TransformationSetMemoryOperandsMask transformation(
- MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1),
+ MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
(uint32_t)spv::MemoryAccessMask::Nontemporal |
(uint32_t)spv::MemoryAccessMask::Volatile,
1);
// Bad: cannot remove volatile
ASSERT_FALSE(TransformationSetMemoryOperandsMask(
- MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1),
+ MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0),
(uint32_t)spv::MemoryAccessMask::Nontemporal, 0)
.IsApplicable(context.get(), transformation_context));
ASSERT_TRUE(
@@ -417,7 +425,7 @@
{
// Creates the first operand.
TransformationSetMemoryOperandsMask transformation(
- MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 2),
+ MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1),
(uint32_t)spv::MemoryAccessMask::Nontemporal |
(uint32_t)spv::MemoryAccessMask::Volatile,
0);
@@ -430,7 +438,7 @@
{
// Creates both operands.
TransformationSetMemoryOperandsMask transformation(
- MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 3),
+ MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 2),
(uint32_t)spv::MemoryAccessMask::Nontemporal |
(uint32_t)spv::MemoryAccessMask::Volatile,
1);
@@ -442,13 +450,13 @@
{
TransformationSetMemoryOperandsMask transformation(
- MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 0),
+ MakeInstructionDescriptor(138, spv::Op::OpLoad, 0),
(uint32_t)spv::MemoryAccessMask::Aligned |
(uint32_t)spv::MemoryAccessMask::Nontemporal,
- 1);
+ 0);
// Bad: the first mask is None, so Aligned cannot be added to it.
ASSERT_FALSE(TransformationSetMemoryOperandsMask(
- MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 0),
+ MakeInstructionDescriptor(138, spv::Op::OpStore, 0),
(uint32_t)spv::MemoryAccessMask::Aligned |
(uint32_t)spv::MemoryAccessMask::Nontemporal,
0)
@@ -461,8 +469,8 @@
{
TransformationSetMemoryOperandsMask transformation(
- MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 1),
- (uint32_t)spv::MemoryAccessMask::Volatile, 1);
+ MakeInstructionDescriptor(138, spv::Op::OpLoad, 1),
+ (uint32_t)spv::MemoryAccessMask::Volatile, 0);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation, context.get(),
@@ -522,10 +530,12 @@
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeStruct %6 %6 %6
+ %1001 = OpTypeStruct %6 %6 %6
%8 = OpTypeInt 32 0
%9 = OpConstant %8 12
%10 = OpTypeArray %7 %9
- %11 = OpTypePointer Private %10
+ %1002 = OpTypeArray %1001 %9
+ %11 = OpTypePointer Private %1002
%12 = OpVariable %11 Private
%15 = OpTypeStruct %10 %7
%16 = OpTypePointer Uniform %15
@@ -533,26 +543,32 @@
%18 = OpTypeInt 32 1
%19 = OpConstant %18 0
%20 = OpTypePointer Uniform %10
- %24 = OpTypePointer Private %7
+ %24 = OpTypePointer Private %1001
%27 = OpTypePointer Private %6
%30 = OpConstant %18 1
- %132 = OpTypePointer Function %10
+ %132 = OpTypePointer Function %1002
%135 = OpTypePointer Uniform %7
- %145 = OpTypePointer Function %7
+ %145 = OpTypePointer Function %1001
%4 = OpFunction %2 None %3
%5 = OpLabel
%133 = OpVariable %132 Function
%21 = OpAccessChain %20 %17 %19
- OpCopyMemory %12 %21 Aligned 16 Aligned|Volatile 16
+ %1003 = OpLoad %10 %21 Aligned|Volatile 16
+ %1004 = OpCopyLogical %1002 %1003
+ OpStore %12 %1004 Aligned 16
OpCopyMemory %133 %12 Volatile Nontemporal|Volatile
OpCopyMemory %133 %12 Nontemporal|Volatile
OpCopyMemory %133 %12 None Nontemporal|Volatile
%136 = OpAccessChain %135 %17 %30
%138 = OpAccessChain %24 %12 %19
- OpCopyMemory %138 %136 None Aligned|Nontemporal 16
- OpCopyMemory %138 %136 Aligned 16 Volatile
+ %1005 = OpLoad %7 %136 Aligned|Nontemporal 16
+ %1006 = OpCopyLogical %1001 %1005
+ OpStore %138 %1006
+ %1007 = OpLoad %7 %136 Volatile
+ %1008 = OpCopyLogical %1001 %1007
+ OpStore %138 %1008 Aligned 16
%146 = OpAccessChain %145 %133 %30
- %147 = OpLoad %7 %146 Volatile|Aligned 16
+ %147 = OpLoad %1001 %146 Volatile|Aligned 16
%148 = OpAccessChain %24 %12 %19
OpStore %148 %147 None
OpReturn
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index ecfa7c7..272caeb 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -4433,10 +4433,6 @@
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 8 8 1
- OpDecorate %_ptr_PhysicalStorageBuffer__struct_5 ArrayStride 8
- OpMemberDecorate %_struct_3 0 Offset 0
- OpMemberDecorate %_struct_3 1 Offset 8
- OpDecorate %_ptr_PhysicalStorageBuffer_int ArrayStride 4
OpMemberDecorate %_struct_5 0 Offset 0
OpMemberDecorate %_struct_5 1 Offset 4
OpDecorate %6 Aliased
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index ee9b68d..d2a77e2 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -1423,16 +1423,6 @@
"to be used only with Fragment execution model"))));
INSTANTIATE_TEST_SUITE_P(
- SampleMaskWrongStorageClass,
- ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
- Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"),
- Values("%u32arr2"), Values("VUID-SampleMask-SampleMask-04358"),
- Values(TestResult(
- SPV_ERROR_INVALID_DATA,
- "Vulkan spec allows BuiltIn SampleMask to be only used for "
- "variables with Input or Output storage class"))));
-
-INSTANTIATE_TEST_SUITE_P(
SampleMaskNotArray,
ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 84e9021..2682377 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -10697,6 +10697,273 @@
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}
+TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPre1p4) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_function_block = OpTypePointer Function %block
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_block Function
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPost1p4) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_function_block = OpTypePointer Function %block
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_block Function
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePre1p4) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_private_block = OpTypePointer Private %block
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_private_block Private
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePost1p4) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_private_block = OpTypePointer Private %block
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_private_block Private
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroupExplicitLayout) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability WorkgroupMemoryExplicitLayoutKHR
+OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %array ArrayStride 4
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array = OpTypeArray %int %int_4
+%ptr_wg_block = OpTypePointer Workgroup %array
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_wg_block Workgroup
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroup) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %array ArrayStride 4
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array = OpTypeArray %int %int_4
+%ptr_wg_block = OpTypePointer Workgroup %array
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_wg_block Workgroup
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutArrayStrideUniformConstant) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %array ArrayStride 4
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%sampler = OpTypeSampler
+%array = OpTypeArray %sampler %int_4
+%ptr_uc_block = OpTypePointer UniformConstant %array
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_uc_block UniformConstant
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutMatrixStrideFunctionPost1p4) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 MatrixStride 16
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%mat4x4 = OpTypeMatrix %v4float 4
+%block = OpTypeStruct %mat4x4
+%ptr_function_block = OpTypePointer Function %block
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_block Function
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutNestedMatrixStrideFunctionPost1p4) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %block 0 MatrixStride 16
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%mat4x4 = OpTypeMatrix %v4float 4
+%block = OpTypeStruct %mat4x4
+%block2 = OpTypeStruct %block
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%array = OpTypeArray %block2 %int_2
+%ptr_function_array = OpTypePointer Function %array
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_function_array Function
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
+TEST_F(ValidateDecorations, InvalidLayoutBufferBlockWorkgroup) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block BufferBlock
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %int
+%ptr_wg_block = OpTypePointer Workgroup %block
+%void_fn = OpTypeFunction %void
+%var = OpVariable %ptr_wg_block Workgroup
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Invalid explicit layout decorations on type for operand"));
+}
+
} // namespace
} // namespace val
} // namespace spvtools