From b6150e517055fb08bf24119391971a7508eb824c Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Thu, 17 Jan 2019 10:39:12 -0500 Subject: [PATCH] Add WebGPU specific RTA validation rules (#2287) Fixes #2066 --- source/val/validate_memory.cpp | 128 ++++++++++++------- source/val/validate_type.cpp | 43 ++++--- test/val/val_data_test.cpp | 62 ++++++++++ test/val/val_memory_test.cpp | 216 +++++++++++++++++++++++++++++++++ 4 files changed, 385 insertions(+), 64 deletions(-) diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp index 2b0db56a1..3b104be8d 100644 --- a/source/val/validate_memory.cpp +++ b/source/val/validate_memory.cpp @@ -276,6 +276,16 @@ uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) { return scope_id; } +bool DoesStructContainRTA(const ValidationState_t& _, const Instruction* inst) { + for (size_t member_index = 1; member_index < inst->operands().size(); + ++member_index) { + const auto member_id = inst->GetOperandAs(member_index); + const auto member_type = _.FindDef(member_id); + if (member_type->opcode() == SpvOpTypeRuntimeArray) return true; + } + return false; +} + spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, uint32_t index) { SpvStorageClass dst_sc, src_sc; @@ -283,9 +293,9 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, if (inst->operands().size() <= index) { if (src_sc == SpvStorageClassPhysicalStorageBufferEXT || dst_sc == SpvStorageClassPhysicalStorageBufferEXT) { - return _.diag(SPV_ERROR_INVALID_ID, inst) << "Memory accesses with " - "PhysicalStorageBufferEXT " - "must use Aligned."; + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Memory accesses with PhysicalStorageBufferEXT must use " + "Aligned."; } return SPV_SUCCESS; } @@ -318,7 +328,7 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, if (!(mask & SpvMemoryAccessNonPrivatePointerKHRMask)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "NonPrivatePointerKHR must be specified if " - "MakePointerVisibleKHR is specified."; + << "MakePointerVisibleKHR is specified."; } // Check the associated scope for MakeVisibleKHR. @@ -335,8 +345,8 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, dst_sc != SpvStorageClassPhysicalStorageBufferEXT) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "NonPrivatePointerKHR requires a pointer in Uniform, " - "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " - "storage classes."; + << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " + << "storage classes."; } if (src_sc != SpvStorageClassMax && src_sc != SpvStorageClassUniform && src_sc != SpvStorageClassWorkgroup && @@ -346,17 +356,17 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, src_sc != SpvStorageClassPhysicalStorageBufferEXT) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "NonPrivatePointerKHR requires a pointer in Uniform, " - "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " - "storage classes."; + << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " + << "storage classes."; } } if (!(mask & SpvMemoryAccessAlignedMask)) { if (src_sc == SpvStorageClassPhysicalStorageBufferEXT || dst_sc == SpvStorageClassPhysicalStorageBufferEXT) { - return _.diag(SPV_ERROR_INVALID_ID, inst) << "Memory accesses with " - "PhysicalStorageBufferEXT " - "must use Aligned."; + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Memory accesses with PhysicalStorageBufferEXT must use " + "Aligned."; } } @@ -442,15 +452,15 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { // VariablePointersStorageBuffer is implied by VariablePointers. if (pointee->opcode() == SpvOpTypePointer) { if (!_.HasCapability(SpvCapabilityVariablePointersStorageBuffer)) { - return _.diag(SPV_ERROR_INVALID_ID, inst) << "In Logical addressing, " - "variables may not " - "allocate a pointer type"; + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "In Logical addressing, variables may not allocate a pointer " + << "type"; } else if (storage_class != SpvStorageClassFunction && storage_class != SpvStorageClassPrivate) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "In Logical addressing with variable pointers, variables " - "that allocate pointers must be in Function or Private " - "storage classes"; + << "that allocate pointers must be in Function or Private " + << "storage classes"; } } } @@ -493,10 +503,9 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { << "' has illegal type.\n" << "From Vulkan spec, section 14.5.2:\n" << "Variables identified with the Uniform storage class are " - "used " - << "to access transparent buffer backed resources. Such " - "variables " - << "must be typed as OpTypeStruct, or an array of this type"; + << "used to access transparent buffer backed resources. Such " + << "variables must be typed as OpTypeStruct, or an array of " + << "this type"; } } } @@ -564,13 +573,13 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpVariable " << inst->id() << ": expected AliasedPointerEXT or RestrictPointerEXT for " - "PhysicalStorageBufferEXT pointer."; + << "PhysicalStorageBufferEXT pointer."; } if (foundAliased && foundRestrict) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpVariable " << inst->id() << ": can't specify both AliasedPointerEXT and " - "RestrictPointerEXT for PhysicalStorageBufferEXT pointer."; + << "RestrictPointerEXT for PhysicalStorageBufferEXT pointer."; } } } @@ -588,9 +597,9 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpVariable, '" << _.getIdName(inst->id()) << "', is attempting to create memory for an illegal type, " - "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only " - "appear as the final member of an OpTypeStruct, thus cannot " - "be instantiated via OpVariable"; + << "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only " + << "appear as the final member of an OpTypeStruct, thus cannot " + << "be instantiated via OpVariable"; } else { // A bare variable OpTypeRuntimeArray is allowed in this context, but // still need to check the storage class. @@ -599,8 +608,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { storage_class != SpvStorageClassUniformConstant) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan with RuntimeDescriptorArrayEXT, a variable " - "containing OpTypeRuntimeArray must have storage class of " - "StorageBuffer, Uniform, or UniformConstant."; + << "containing OpTypeRuntimeArray must have storage class of " + << "StorageBuffer, Uniform, or UniformConstant."; } } } @@ -610,39 +619,64 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { // with Block, or it must be in the Uniform storage class and be decorated // as BufferBlock. if (value_type && value_type->opcode() == SpvOpTypeStruct) { - bool contains_RTA = false; - for (size_t member_type_index = 1; - member_type_index < value_type->operands().size(); - ++member_type_index) { - const auto member_type_id = - value_type->GetOperandAs(member_type_index); - const auto member_type = _.FindDef(member_type_id); - if (member_type->opcode() == SpvOpTypeRuntimeArray) { - contains_RTA = true; - break; - } - } - - if (contains_RTA) { + if (DoesStructContainRTA(_, value_type)) { if (storage_class == SpvStorageClassStorageBuffer) { if (!_.HasDecoration(value_id, SpvDecorationBlock)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan, an OpTypeStruct variable containing an " - "OpTypeRuntimeArray must be decorated with Block if it " - "has storage class StorageBuffer."; + << "OpTypeRuntimeArray must be decorated with Block if it " + << "has storage class StorageBuffer."; } } else if (storage_class == SpvStorageClassUniform) { if (!_.HasDecoration(value_id, SpvDecorationBufferBlock)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan, an OpTypeStruct variable containing an " - "OpTypeRuntimeArray must be decorated with BufferBlock " - "if it has storage class Uniform."; + << "OpTypeRuntimeArray must be decorated with BufferBlock " + << "if it has storage class Uniform."; } } else { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan, OpTypeStruct variables containing " - "OpTypeRuntimeArray must have storage class of " - "StorageBuffer or Uniform."; + << "OpTypeRuntimeArray must have storage class of " + << "StorageBuffer or Uniform."; + } + } + } + } + + // WebGPU specific validation rules for OpTypeRuntimeArray + if (spvIsWebGPUEnv(_.context()->target_env)) { + const auto type_index = 2; + const auto value_id = result_type->GetOperandAs(type_index); + auto value_type = _.FindDef(value_id); + // OpTypeRuntimeArray should only ever be in an OpTypeStruct, + // so should never appear as a bare variable. + if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpVariable, '" << _.getIdName(inst->id()) + << "', is attempting to create memory for an illegal type, " + << "OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray can only " + << "appear as the final member of an OpTypeStruct, thus cannot " + << "be instantiated via OpVariable"; + } + + // If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it + // must have the storage class StorageBuffer and be decorated + // with Block. + if (value_type && value_type->opcode() == SpvOpTypeStruct) { + if (DoesStructContainRTA(_, value_type)) { + if (storage_class == SpvStorageClassStorageBuffer) { + if (!_.HasDecoration(value_id, SpvDecorationBlock)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "For WebGPU, an OpTypeStruct variable containing an " + << "OpTypeRuntimeArray must be decorated with Block if it " + << "has storage class StorageBuffer."; + } + } else { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "For WebGPU, OpTypeStruct variables containing " + << "OpTypeRuntimeArray must have storage class of " + << "StorageBuffer"; } } } diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp index 6dbca3946..94ea66034 100644 --- a/source/val/validate_type.cpp +++ b/source/val/validate_type.cpp @@ -107,11 +107,14 @@ spv_result_t ValidateTypeArray(ValidationState_t& _, const Instruction* inst) { << "' is a void type."; } - if (spvIsVulkanEnv(_.context()->target_env) && + if ((spvIsVulkanEnv(_.context()->target_env) || + spvIsWebGPUEnv(_.context()->target_env)) && element_type->opcode() == SpvOpTypeRuntimeArray) { + const char* env_text = + spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU"; return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Element Type '" << _.getIdName(element_type_id) - << "' is not valid in Vulkan environment."; + << "' is not valid in " << env_text << " environment."; } const auto length_index = 2; @@ -169,12 +172,15 @@ spv_result_t ValidateTypeRuntimeArray(ValidationState_t& _, << _.getIdName(element_id) << "' is a void type."; } - if (spvIsVulkanEnv(_.context()->target_env) && + if ((spvIsVulkanEnv(_.context()->target_env) || + spvIsWebGPUEnv(_.context()->target_env)) && element_type->opcode() == SpvOpTypeRuntimeArray) { + const char* env_text = + spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU"; return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeRuntimeArray Element Type '" - << _.getIdName(element_id) - << "' is not valid in Vulkan environment."; + << _.getIdName(element_id) << "' is not valid in " << env_text + << " environment."; } return SPV_SUCCESS; @@ -200,11 +206,11 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Structure " << _.getIdName(member_type_id) << " contains members with BuiltIn decoration. Therefore this " - "structure may not be contained as a member of another " - "structure " - "type. Structure " - << _.getIdName(struct_id) << " contains structure " - << _.getIdName(member_type_id) << "."; + << "structure may not be contained as a member of another " + << "structure " + << "type. Structure " << _.getIdName(struct_id) + << " contains structure " << _.getIdName(member_type_id) + << "."; } if (_.IsForwardPointer(member_type_id)) { // If we're dealing with a forward pointer: @@ -223,14 +229,17 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { } } - if (spvIsVulkanEnv(_.context()->target_env) && + if ((spvIsVulkanEnv(_.context()->target_env) || + spvIsWebGPUEnv(_.context()->target_env)) && member_type->opcode() == SpvOpTypeRuntimeArray) { const bool is_last_member = member_type_index == inst->operands().size() - 1; if (!is_last_member) { + const char* env_text = + spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU"; return _.diag(SPV_ERROR_INVALID_ID, inst) - << "In Vulkan, OpTypeRuntimeArray must only be used for the " - "last member of an OpTypeStruct"; + << "In " << env_text << ", OpTypeRuntimeArray must only be used " + << "for the last member of an OpTypeStruct"; } } } @@ -247,9 +256,9 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { if (num_builtin_members > 0 && num_builtin_members != num_struct_members) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "When BuiltIn decoration is applied to a structure-type member, " - "all members of that structure type must also be decorated with " - "BuiltIn (No allowed mixing of built-in variables and " - "non-built-in variables within a single structure). Structure id " + << "all members of that structure type must also be decorated with " + << "BuiltIn (No allowed mixing of built-in variables and " + << "non-built-in variables within a single structure). Structure id " << struct_id << " does not meet this requirement."; } if (num_builtin_members > 0) { @@ -332,7 +341,7 @@ spv_result_t ValidateTypeForwardPointer(ValidationState_t& _, pointer_type_inst->GetOperandAs(1)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Storage class in OpTypeForwardPointer does not match the " - "pointer definition."; + << "pointer definition."; } return SPV_SUCCESS; diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp index 48a484612..0aded92b5 100644 --- a/test/val/val_data_test.cpp +++ b/test/val/val_data_test.cpp @@ -872,6 +872,68 @@ TEST_F(ValidateData, vulkan_RTA_not_at_end_of_struct) { "OpTypeStruct %_runtimearr_uint %uint\n")); } +TEST_F(ValidateData, webgpu_RTA_array_at_end_of_struct) { + std::string str = R"( + OpCapability Shader + OpCapability VulkanMemoryModelKHR + OpExtension "SPV_KHR_vulkan_memory_model" + OpMemoryModel Logical VulkanKHR + OpEntryPoint Fragment %func "func" + OpExecutionMode %func OriginUpperLeft + OpDecorate %array_t ArrayStride 4 + OpMemberDecorate %struct_t 0 Offset 0 + OpMemberDecorate %struct_t 1 Offset 4 + OpDecorate %struct_t Block + %uint_t = OpTypeInt 32 0 + %array_t = OpTypeRuntimeArray %uint_t + %struct_t = OpTypeStruct %uint_t %array_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t + %2 = OpVariable %struct_ptr StorageBuffer + %void = OpTypeVoid + %func_t = OpTypeFunction %void + %func = OpFunction %void None %func_t + %1 = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + +TEST_F(ValidateData, webgpu_RTA_not_at_end_of_struct) { + std::string str = R"( + OpCapability Shader + OpCapability VulkanMemoryModelKHR + OpExtension "SPV_KHR_vulkan_memory_model" + OpMemoryModel Logical VulkanKHR + OpEntryPoint Fragment %func "func" + OpExecutionMode %func OriginUpperLeft + OpDecorate %array_t ArrayStride 4 + OpMemberDecorate %struct_t 0 Offset 0 + OpMemberDecorate %struct_t 1 Offset 4 + OpDecorate %struct_t Block + %uint_t = OpTypeInt 32 0 + %array_t = OpTypeRuntimeArray %uint_t + %struct_t = OpTypeStruct %array_t %uint_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t + %2 = OpVariable %struct_ptr StorageBuffer + %void = OpTypeVoid + %func_t = OpTypeFunction %void + %func = OpFunction %void None %func_t + %1 = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("In WebGPU, OpTypeRuntimeArray must only be used for " + "the last member of an OpTypeStruct\n %_struct_3 = " + "OpTypeStruct %_runtimearr_uint %uint\n")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp index 8ad1db46d..097477c31 100644 --- a/test/val/val_memory_test.cpp +++ b/test/val/val_memory_test.cpp @@ -1806,6 +1806,38 @@ OpFunctionEnd "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n")); } +TEST_F(ValidateMemory, WebGPURTAOutsideOfStructBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%sampler_t = OpTypeSampler +%array_t = OpTypeRuntimeArray %sampler_t +%array_ptr = OpTypePointer UniformConstant %array_t +%2 = OpVariable %array_ptr UniformConstant +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpVariable, '5[%5]', is attempting to create memory for an " + "illegal type, OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray " + "can only appear as the final member of an OpTypeStruct, thus cannot " + "be instantiated via OpVariable\n %5 = OpVariable " + "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n")); +} + TEST_F(ValidateMemory, VulkanRTAOutsideOfStructWithRuntimeDescriptorArrayGood) { std::string spirv = R"( OpCapability Shader @@ -1890,6 +1922,34 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); } +TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructGood) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +OpDecorate %array_t ArrayStride 4 +OpMemberDecorate %struct_t 0 Offset 0 +OpDecorate %struct_t Block +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t +%2 = OpVariable %struct_ptr StorageBuffer +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + TEST_F(ValidateMemory, VulkanRTAInsideWrongStorageClassStructBad) { std::string spirv = R"( OpCapability Shader @@ -1919,6 +1979,36 @@ OpFunctionEnd "OpVariable %_ptr_Workgroup__struct_4 Workgroup\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideWrongStorageClassStructBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer Workgroup %struct_t +%2 = OpVariable %struct_ptr Workgroup +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("For WebGPU, OpTypeStruct variables containing " + "OpTypeRuntimeArray must have storage class of StorageBuffer\n " + " %6 = OpVariable %_ptr_Workgroup__struct_4 Workgroup\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructWithoutBlockBad) { std::string spirv = R"( OpCapability Shader @@ -1947,6 +2037,36 @@ OpFunctionEnd "%_ptr_StorageBuffer__struct_4 StorageBuffer\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructWithoutBlockBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t +%2 = OpVariable %struct_ptr StorageBuffer +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("For WebGPU, an OpTypeStruct variable containing an " + "OpTypeRuntimeArray must be decorated with Block if it " + "has storage class StorageBuffer.\n %6 = OpVariable " + "%_ptr_StorageBuffer__struct_4 StorageBuffer\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideUniformStructGood) { std::string spirv = R"( OpCapability Shader @@ -1973,6 +2093,39 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); } +TEST_F(ValidateMemory, WebGPURTAInsideUniformStructBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +OpDecorate %array_t ArrayStride 4 +OpMemberDecorate %struct_t 0 Offset 0 +OpDecorate %struct_t BufferBlock +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer Uniform %struct_t +%2 = OpVariable %struct_ptr Uniform +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("For WebGPU, OpTypeStruct variables containing " + "OpTypeRuntimeArray must have storage class of StorageBuffer\n " + " %6 = OpVariable %_ptr_Uniform__struct_3 Uniform\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideUniformStructWithoutBufferBlockBad) { std::string spirv = R"( OpCapability Shader @@ -2030,6 +2183,37 @@ OpFunctionEnd "OpTypeRuntimeArray %_runtimearr_2\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideRTABad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%sampler_t = OpTypeSampler +%inner_array_t = OpTypeRuntimeArray %sampler_t +%array_t = OpTypeRuntimeArray %inner_array_t +%array_ptr = OpTypePointer UniformConstant %array_t +%2 = OpVariable %array_ptr UniformConstant +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpTypeRuntimeArray Element Type '3[%_runtimearr_2]' is not " + "valid in WebGPU environment.\n %_runtimearr__runtimearr_2 = " + "OpTypeRuntimeArray %_runtimearr_2\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideRTAWithRuntimeDescriptorArrayBad) { std::string spirv = R"( OpCapability RuntimeDescriptorArrayEXT @@ -2190,6 +2374,38 @@ OpFunctionEnd "OpTypeArray %_runtimearr_4 %uint_1\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideArrayBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%uint_t = OpTypeInt 32 0 +%dim = OpConstant %uint_t 1 +%sampler_t = OpTypeSampler +%inner_array_t = OpTypeRuntimeArray %sampler_t +%array_t = OpTypeArray %inner_array_t %dim +%array_ptr = OpTypePointer UniformConstant %array_t +%2 = OpVariable %array_ptr UniformConstant +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeArray Element Type '5[%_runtimearr_4]' is not " + "valid in WebGPU environment.\n %_arr__runtimearr_4_uint_1 = " + "OpTypeArray %_runtimearr_4 %uint_1\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideArrayWithRuntimeDescriptorArrayBad) { std::string spirv = R"( OpCapability RuntimeDescriptorArrayEXT