From 01e15a3fec32eec34b4ff0c483d5777acc8bf636 Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Thu, 20 Feb 2025 15:27:05 -0500 Subject: [PATCH] Add validation for invalid layout decoration usage * Checks that Block, BufferBlock, Offset, ArrayStride, and MatrixStride are not used in a Vulkan environment in disallowed storage classes --- source/val/validate_decorations.cpp | 192 ++++++++++++- ...ormation_set_memory_operands_mask_test.cpp | 74 +++-- test/opt/inline_test.cpp | 4 - test/val/val_builtins_test.cpp | 10 - test/val/val_decoration_test.cpp | 267 ++++++++++++++++++ 5 files changed, 503 insertions(+), 44 deletions(-) diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp index fdbc151aec..32ef4bdffd 100644 --- a/source/val/validate_decorations.cpp +++ b/source/val/validate_decorations.cpp @@ -1155,7 +1155,7 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { for (auto ep_id : entry_points) { const bool already_used = !uses_push_constant.insert(ep_id).second; if (already_used) { - return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id)) + return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id)) << vstate.VkErrorID(6674) << "Entry point id '" << ep_id << "' uses more than one PushConstant interface.\n" << "From Vulkan spec:\n" @@ -2083,6 +2083,195 @@ spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) { 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& 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(i), cache); + } + break; + case spv::Op::OpTypeArray: + case spv::Op::OpTypeRuntimeArray: + res = UsesExplicitLayout(vstate, type_inst->GetOperandAs(1), + cache); + break; + case spv::Op::OpTypePointer: { + const auto sc = type_inst->GetOperandAs(1); + if (!AllowsLayout(vstate, sc)) { + res = UsesExplicitLayout( + vstate, type_inst->GetOperandAs(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 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(2); + auto check_id = type_id; + if (inst.opcode() == spv::Op::OpUntypedVariableKHR) { + if (inst.operands().size() > 3) { + check_id = inst.GetOperandAs(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(1); + const auto base_type_id = inst.GetOperandAs(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(3))->type_id(); + const auto ptr_ty = vstate.FindDef(ptr_ty_id); + const auto sc = ptr_ty->GetOperandAs(1); + const auto base_type_id = inst.GetOperandAs(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(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(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(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(1); + const auto data_type_id = vstate.GetOperandTypeId(&inst, 2); + if (!AllowsLayout(vstate, sc) && + UsesExplicitLayout(vstate, data_type_id, cache)) { + fail_id = inst.GetOperandAs(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 +2283,7 @@ spv_result_t ValidateDecorations(ValidationState_t& vstate) { 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 44901f9c7f..d08b98cd1e 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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { MakeUnique(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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { 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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { // 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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { // 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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { 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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { 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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %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 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %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 ef7ac37d18..f017e91f50 100644 --- a/test/opt/inline_test.cpp +++ b/test/opt/inline_test.cpp @@ -4433,10 +4433,6 @@ TEST_F(InlineTest, DecorateReturnVariableWithAliasedPointer) { 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 ee9b68d70d..d2a77e2790 100644 --- a/test/val/val_builtins_test.cpp +++ b/test/val/val_builtins_test.cpp @@ -1422,16 +1422,6 @@ INSTANTIATE_TEST_SUITE_P( Values(TestResult(SPV_ERROR_INVALID_DATA, "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, diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp index 84e9021b41..2682377d53 100644 --- a/test/val/val_decoration_test.cpp +++ b/test/val/val_decoration_test.cpp @@ -10697,6 +10697,273 @@ OpFunctionEnd 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