diff --git a/DEPS b/DEPS index 3762681db..75581c5aa 100644 --- a/DEPS +++ b/DEPS @@ -6,7 +6,7 @@ vars = { 'effcee_revision': '2ec8f8738118cc483b67c04a759fee53496c5659', 'googletest_revision': '3af06fe1664d30f98de1e78c53a7087e842a2547', 're2_revision': 'ca11026a032ce2a3de4b3c389ee53d2bdc8794d6', - 'spirv_headers_revision': 'f027d53ded7e230e008d37c8b47ede7cd308e19d', + 'spirv_headers_revision': 'faa570afbc91ac73d594d787486bcf8f2df1ace0', } deps = { diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h index 201ee58bb..658c4dd5c 100644 --- a/include/spirv-tools/libspirv.h +++ b/include/spirv-tools/libspirv.h @@ -625,6 +625,12 @@ SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetUniformBufferStandardLayout( SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetScalarBlockLayout( spv_validator_options options, bool val); +// Records whether the validator should use "scalar" block layout +// rules (as defined above) for Workgroup blocks. See Vulkan +// extension VK_KHR_workgroup_memory_explicit_layout. +SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetWorkgroupScalarBlockLayout( + spv_validator_options options, bool val); + // Records whether or not the validator should skip validating standard // uniform/storage block layout. SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetSkipBlockLayout( diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp index 2018e4610..e7e7fc7aa 100644 --- a/include/spirv-tools/libspirv.hpp +++ b/include/spirv-tools/libspirv.hpp @@ -104,6 +104,12 @@ class ValidatorOptions { spvValidatorOptionsSetScalarBlockLayout(options_, val); } + // Enables scalar layout when validating Workgroup blocks. See + // VK_KHR_workgroup_memory_explicit_layout. + void SetWorkgroupScalarBlockLayout(bool val) { + spvValidatorOptionsSetWorkgroupScalarBlockLayout(options_, val); + } + // Skips validating standard uniform/storage buffer/push-constant layout. void SetSkipBlockLayout(bool val) { spvValidatorOptionsSetSkipBlockLayout(options_, val); diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp index 01aa79747..2716cca9b 100644 --- a/source/spirv_validator_options.cpp +++ b/source/spirv_validator_options.cpp @@ -111,6 +111,11 @@ void spvValidatorOptionsSetScalarBlockLayout(spv_validator_options options, options->scalar_block_layout = val; } +void spvValidatorOptionsSetWorkgroupScalarBlockLayout(spv_validator_options options, + bool val) { + options->workgroup_scalar_block_layout = val; +} + void spvValidatorOptionsSetSkipBlockLayout(spv_validator_options options, bool val) { options->skip_block_layout = val; diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h index b7da5d8ea..baaa5359e 100644 --- a/source/spirv_validator_options.h +++ b/source/spirv_validator_options.h @@ -45,6 +45,7 @@ struct spv_validator_options_t { relax_block_layout(false), uniform_buffer_standard_layout(false), scalar_block_layout(false), + workgroup_scalar_block_layout(false), skip_block_layout(false), before_hlsl_legalization(false) {} @@ -54,6 +55,7 @@ struct spv_validator_options_t { bool relax_block_layout; bool uniform_buffer_standard_layout; bool scalar_block_layout; + bool workgroup_scalar_block_layout; bool skip_block_layout; bool before_hlsl_legalization; }; diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp index 01b0ecabc..ed336b477 100644 --- a/source/val/validate_decorations.cpp +++ b/source/val/validate_decorations.cpp @@ -379,6 +379,7 @@ bool IsAlignedTo(uint32_t offset, uint32_t alignment) { // or row major-ness. spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str, const char* decoration_str, bool blockRules, + bool scalar_block_layout, uint32_t incoming_offset, MemberConstraints& constraints, ValidationState_t& vstate) { @@ -392,7 +393,6 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str, // For example, relaxed layout is implied by Vulkan 1.1. But scalar layout // is more permissive than relaxed layout. const bool relaxed_block_layout = vstate.IsRelaxedBlockLayout(); - const bool scalar_block_layout = vstate.options()->scalar_block_layout; auto fail = [&vstate, struct_id, storage_class_str, decoration_str, blockRules, relaxed_block_layout, @@ -501,6 +501,7 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str, if (SpvOpTypeStruct == opcode && SPV_SUCCESS != (recursive_status = checkLayout( id, storage_class_str, decoration_str, blockRules, + scalar_block_layout, offset, constraints, vstate))) return recursive_status; // Check matrix stride. @@ -553,7 +554,8 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str, if (SpvOpTypeStruct == element_inst->opcode() && SPV_SUCCESS != (recursive_status = checkLayout( typeId, storage_class_str, decoration_str, - blockRules, next_offset, constraints, vstate))) + blockRules, scalar_block_layout, + next_offset, constraints, vstate))) return recursive_status; // If offsets accumulate up to a 16-byte multiple stop checking since // it will just repeat. @@ -698,6 +700,9 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) { const auto& descs = vstate.entry_point_descriptions(entry_point); int num_builtin_inputs = 0; int num_builtin_outputs = 0; + int num_workgroup_variables = 0; + int num_workgroup_variables_with_block = 0; + int num_workgroup_variables_with_aliased = 0; for (const auto& desc : descs) { std::unordered_set seen_vars; for (auto interface : desc.interfaces) { @@ -754,6 +759,16 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) { if (auto error = CheckBuiltInVariable(interface, vstate)) return error; } + + if (storage_class == SpvStorageClassWorkgroup) { + ++num_workgroup_variables; + if (type_instr && SpvOpTypeStruct == type_instr->opcode()) { + if (hasDecoration(type_id, SpvDecorationBlock, vstate)) + ++num_workgroup_variables_with_block; + if (hasDecoration(var_instr->id(), SpvDecorationAliased, vstate)) + ++num_workgroup_variables_with_aliased; + } + } } if (num_builtin_inputs > 1 || num_builtin_outputs > 1) { return vstate.diag(SPV_ERROR_INVALID_BINARY, @@ -777,6 +792,30 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) { << " because it is targeted by an OpEntryPoint instruction."; } } + + if (vstate.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayoutKHR) && + num_workgroup_variables > 0 && + num_workgroup_variables_with_block > 0) { + if (num_workgroup_variables != num_workgroup_variables_with_block) { + return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point)) + << "When declaring WorkgroupMemoryExplicitLayoutKHR, " + "either all or none of the Workgroup Storage Class variables " + "in the entry point interface must point to struct types " + "decorated with Block. Entry point id " + << entry_point << " does not meet this requirement."; + } + if (num_workgroup_variables_with_block > 1 && + num_workgroup_variables_with_block != + num_workgroup_variables_with_aliased) { + return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point)) + << "When declaring WorkgroupMemoryExplicitLayoutKHR, " + "if more than one Workgroup Storage Class variable in " + "the entry point interface point to a type decorated " + "with Block, all of them must be decorated with Aliased. " + "Entry point id " + << entry_point << " does not meet this requirement."; + } + } } } return SPV_SUCCESS; @@ -942,14 +981,16 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { const bool phys_storage_buffer = storageClass == SpvStorageClassPhysicalStorageBufferEXT; - if (uniform || push_constant || storage_buffer || phys_storage_buffer) { + const bool workgroup = storageClass == SpvStorageClassWorkgroup; + if (uniform || push_constant || storage_buffer || phys_storage_buffer || + workgroup) { const auto ptrInst = vstate.FindDef(words[1]); assert(SpvOpTypePointer == ptrInst->opcode()); auto id = ptrInst->words()[3]; auto id_inst = vstate.FindDef(id); // Jump through one level of arraying. - if (id_inst->opcode() == SpvOpTypeArray || - id_inst->opcode() == SpvOpTypeRuntimeArray) { + if (!workgroup && (id_inst->opcode() == SpvOpTypeArray || + id_inst->opcode() == SpvOpTypeRuntimeArray)) { id = id_inst->GetOperandAs(1u); id_inst = vstate.FindDef(id); } @@ -961,7 +1002,9 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { // Prepare for messages const char* sc_str = uniform ? "Uniform" - : (push_constant ? "PushConstant" : "StorageBuffer"); + : (push_constant ? "PushConstant" + : (workgroup ? "Workgroup" + : "StorageBuffer")); if (spvIsVulkanEnv(vstate.context()->target_env)) { const bool block = hasDecoration(id, SpvDecorationBlock, vstate); @@ -1029,8 +1072,9 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { const bool bufferDeco = SpvDecorationBufferBlock == dec.dec_type(); const bool blockRules = uniform && blockDeco; const bool bufferRules = - (uniform && bufferDeco) || (push_constant && blockDeco) || - ((storage_buffer || phys_storage_buffer) && blockDeco); + (uniform && bufferDeco) || + ((push_constant || storage_buffer || + phys_storage_buffer || workgroup) && blockDeco); if (uniform && blockDeco) { vstate.RegisterPointerToUniformBlock(ptrInst->id()); vstate.RegisterStructForUniformBlock(id); @@ -1044,6 +1088,10 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { if (blockRules || bufferRules) { const char* deco_str = blockDeco ? "Block" : "BufferBlock"; spv_result_t recursive_status = SPV_SUCCESS; + const bool scalar_block_layout = workgroup ? + vstate.options()->workgroup_scalar_block_layout : + vstate.options()->scalar_block_layout; + if (isMissingOffsetInStruct(id, vstate)) { return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id)) << "Structure id " << id << " decorated as " << deco_str @@ -1072,12 +1120,14 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { "decorations."; } else if (blockRules && (SPV_SUCCESS != (recursive_status = checkLayout( - id, sc_str, deco_str, true, 0, + id, sc_str, deco_str, true, + scalar_block_layout, 0, constraints, vstate)))) { return recursive_status; } else if (bufferRules && (SPV_SUCCESS != (recursive_status = checkLayout( - id, sc_str, deco_str, false, 0, + id, sc_str, deco_str, false, + scalar_block_layout, 0, constraints, vstate)))) { return recursive_status; } diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp index af6ae2b7a..dc8c0243a 100644 --- a/source/val/validate_extensions.cpp +++ b/source/val/validate_extensions.cpp @@ -27,6 +27,7 @@ #include "source/latest_version_glsl_std_450_header.h" #include "source/latest_version_opencl_std_header.h" #include "source/opcode.h" +#include "source/spirv_constant.h" #include "source/spirv_target_env.h" #include "source/val/instruction.h" #include "source/val/validate.h" @@ -685,6 +686,20 @@ bool IsDebugVariableWithIntScalarType(ValidationState_t& _, } // anonymous namespace +spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) { + if (_.version() < SPV_SPIRV_VERSION_WORD(1, 4)) { + std::string extension = GetExtensionString(&(inst->c_inst())); + if (extension == + ExtensionToString(kSPV_KHR_workgroup_memory_explicit_layout)) { + return _.diag(SPV_ERROR_WRONG_VERSION, inst) + << "SPV_KHR_workgroup_memory_explicit_layout extension " + "requires SPIR-V version 1.4 or later."; + } + } + + return SPV_SUCCESS; +} + spv_result_t ValidateExtInstImport(ValidationState_t& _, const Instruction* inst) { const auto name_id = 1; @@ -3124,6 +3139,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { spv_result_t ExtensionPass(ValidationState_t& _, const Instruction* inst) { const SpvOp opcode = inst->opcode(); + if (opcode == SpvOpExtension) return ValidateExtension(_, inst); if (opcode == SpvOpExtInstImport) return ValidateExtInstImport(_, inst); if (opcode == SpvOpExtInst) return ValidateExtInst(_, inst); diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp index baa350315..edc2a0cea 100644 --- a/source/val/validate_memory.cpp +++ b/source/val/validate_memory.cpp @@ -759,6 +759,11 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { storage_class_ok = false; } break; + case SpvStorageClassWorkgroup: + if (!_.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayout16BitAccessKHR)) { + storage_class_ok = false; + } + break; default: return _.diag(SPV_ERROR_INVALID_ID, inst) << "Cannot allocate a variable containing a 16-bit type in " @@ -810,6 +815,11 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { storage_class_ok = false; } break; + case SpvStorageClassWorkgroup: + if (!_.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayout8BitAccessKHR)) { + storage_class_ok = false; + } + break; default: return _.diag(SPV_ERROR_INVALID_ID, inst) << "Cannot allocate a variable containing a 8-bit type in " diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index c2ca893ca..3268e424f 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -359,6 +359,7 @@ void ValidationState_t::RegisterCapability(SpvCapability cap) { case SpvCapabilityStorageBuffer8BitAccess: case SpvCapabilityUniformAndStorageBuffer8BitAccess: case SpvCapabilityStoragePushConstant8: + case SpvCapabilityWorkgroupMemoryExplicitLayout8BitAccessKHR: features_.declare_int8_type = true; break; case SpvCapabilityInt16: @@ -372,6 +373,7 @@ void ValidationState_t::RegisterCapability(SpvCapability cap) { case SpvCapabilityStorageUniform16: case SpvCapabilityStoragePushConstant16: case SpvCapabilityStorageInputOutput16: + case SpvCapabilityWorkgroupMemoryExplicitLayout16BitAccessKHR: features_.declare_int16_type = true; features_.declare_float16_type = true; features_.free_fp_rounding_mode = true; diff --git a/source/val/validation_state.h b/source/val/validation_state.h index aeb1ca861..851113914 100644 --- a/source/val/validation_state.h +++ b/source/val/validation_state.h @@ -103,6 +103,10 @@ class ValidationState_t { // Members need not be listed in offset order bool scalar_block_layout = false; + // Use scalar block layout (as defined above) for Workgroup block + // variables. See VK_KHR_workgroup_memory_explicit_layout. + bool workgroup_scalar_block_layout = false; + // SPIR-V 1.4 allows us to select between any two composite values // of the same type. bool select_between_composites = false; diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp index 8fe1aa6c8..cbf6d7c1c 100644 --- a/test/val/val_decoration_test.cpp +++ b/test/val/val_decoration_test.cpp @@ -7183,6 +7183,469 @@ OpDecorate %float Location 0 "or member of a structure type")); } +TEST_F(ValidateDecorations, WorkgroupSingleBlockVariable) { + 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 8 1 1 + OpMemberDecorate %first 0 Offset 0 + OpDecorate %first Block + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %first = OpTypeStruct %int +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateDecorations, WorkgroupBlockVariableRequiresV14) { + 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 8 1 1 + OpMemberDecorate %first 0 Offset 0 + OpDecorate %first Block + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %first = OpTypeStruct %int +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_WRONG_VERSION, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("requires SPIR-V version 1.4 or later")); +} + +TEST_F(ValidateDecorations, WorkgroupSingleNonBlockVariable) { + std::string spirv = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %a + OpExecutionMode %main LocalSize 8 1 1 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %a = OpVariable %_ptr_Workgroup_int Workgroup + %int_2 = OpConstant %int 2 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %a %int_2 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateDecorations, WorkgroupMultiBlockVariable) { + std::string spirv = R"( + OpCapability Shader + OpCapability WorkgroupMemoryExplicitLayoutKHR + OpExtension "SPV_KHR_workgroup_memory_explicit_layout" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %_ %__0 + OpExecutionMode %main LocalSize 8 1 1 + OpMemberDecorate %first 0 Offset 0 + OpDecorate %first Block + OpMemberDecorate %second 0 Offset 0 + OpDecorate %second Block + OpDecorate %_ Aliased + OpDecorate %__0 Aliased + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %first = OpTypeStruct %int +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %second = OpTypeStruct %int +%_ptr_Workgroup_second = OpTypePointer Workgroup %second + %__0 = OpVariable %_ptr_Workgroup_second Workgroup + %int_3 = OpConstant %int 3 + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + %18 = OpAccessChain %_ptr_Workgroup_int %__0 %int_0 + OpStore %18 %int_3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateDecorations, WorkgroupBlockVariableWith8BitType) { + std::string spirv = R"( + OpCapability Shader + OpCapability Int8 + OpCapability WorkgroupMemoryExplicitLayout8BitAccessKHR + OpExtension "SPV_KHR_workgroup_memory_explicit_layout" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %_ + OpExecutionMode %main LocalSize 2 1 1 + OpMemberDecorate %first 0 Offset 0 + OpDecorate %first Block + %void = OpTypeVoid + %3 = OpTypeFunction %void + %char = OpTypeInt 8 1 + %first = OpTypeStruct %char +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %char_2 = OpConstant %char 2 +%_ptr_Workgroup_char = OpTypePointer Workgroup %char + %main = OpFunction %void None %3 + %5 = OpLabel + %14 = OpAccessChain %_ptr_Workgroup_char %_ %int_0 + OpStore %14 %char_2 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateDecorations, WorkgroupMultiNonBlockVariable) { + std::string spirv = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %a %b + OpExecutionMode %main LocalSize 8 1 1 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %a = OpVariable %_ptr_Workgroup_int Workgroup + %int_2 = OpConstant %int 2 + %b = OpVariable %_ptr_Workgroup_int Workgroup + %int_3 = OpConstant %int 3 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %a %int_2 + OpStore %b %int_3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateDecorations, WorkgroupBlockVariableWith16BitType) { + std::string spirv = R"( + OpCapability Shader + OpCapability Float16 + OpCapability Int16 + OpCapability WorkgroupMemoryExplicitLayout16BitAccessKHR + OpExtension "SPV_KHR_workgroup_memory_explicit_layout" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %_ + OpExecutionMode %main LocalSize 2 1 1 + OpMemberDecorate %first 0 Offset 0 + OpMemberDecorate %first 1 Offset 2 + OpDecorate %first Block + %void = OpTypeVoid + %3 = OpTypeFunction %void + %short = OpTypeInt 16 1 + %half = OpTypeFloat 16 + %first = OpTypeStruct %short %half +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %short_3 = OpConstant %short 3 +%_ptr_Workgroup_short = OpTypePointer Workgroup %short + %int_1 = OpConstant %int 1 +%half_0x1_898p_3 = OpConstant %half 0x1.898p+3 +%_ptr_Workgroup_half = OpTypePointer Workgroup %half + %main = OpFunction %void None %3 + %5 = OpLabel + %15 = OpAccessChain %_ptr_Workgroup_short %_ %int_0 + OpStore %15 %short_3 + %19 = OpAccessChain %_ptr_Workgroup_half %_ %int_1 + OpStore %19 %half_0x1_898p_3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateDecorations, WorkgroupBlockVariableScalarLayout) { + std::string spirv = R"( + OpCapability Shader + OpCapability WorkgroupMemoryExplicitLayoutKHR + OpExtension "SPV_KHR_workgroup_memory_explicit_layout" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %B + OpSource GLSL 450 + OpMemberDecorate %S 0 Offset 0 + OpMemberDecorate %S 1 Offset 4 + OpMemberDecorate %S 2 Offset 16 + OpMemberDecorate %S 3 Offset 28 + OpDecorate %S Block + OpDecorate %B Aliased + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v3float = OpTypeVector %float 3 + %S = OpTypeStruct %float %v3float %v3float %v3float +%_ptr_Workgroup_S = OpTypePointer Workgroup %S + %B = OpVariable %_ptr_Workgroup_S Workgroup + %main = OpFunction %void None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + spvValidatorOptionsSetWorkgroupScalarBlockLayout(getValidatorOptions(), true); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)) + << getDiagnosticString(); +} + +TEST_F(ValidateDecorations, WorkgroupMixBlockAndNonBlockBad) { + std::string spirv = R"( + OpCapability Shader + OpCapability WorkgroupMemoryExplicitLayoutKHR + OpExtension "SPV_KHR_workgroup_memory_explicit_layout" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %_ %b + OpExecutionMode %main LocalSize 8 1 1 + OpMemberDecorate %first 0 Offset 0 + OpDecorate %first Block + OpDecorate %_ Aliased + OpDecorate %b Aliased + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %first = OpTypeStruct %int +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %b = OpVariable %_ptr_Workgroup_int Workgroup + %int_3 = OpConstant %int 3 + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + OpStore %b %int_3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("either all or none of the Workgroup Storage Class variables " + "in the entry point interface must point to struct types " + "decorated with Block")); +} + +TEST_F(ValidateDecorations, WorkgroupMultiBlockVariableMissingAliased) { + std::string spirv = R"( + OpCapability Shader + OpCapability WorkgroupMemoryExplicitLayoutKHR + OpExtension "SPV_KHR_workgroup_memory_explicit_layout" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %_ %__0 + OpExecutionMode %main LocalSize 8 1 1 + OpMemberDecorate %first 0 Offset 0 + OpDecorate %first Block + OpMemberDecorate %second 0 Offset 0 + OpDecorate %second Block + OpDecorate %_ Aliased + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %first = OpTypeStruct %int +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %second = OpTypeStruct %int +%_ptr_Workgroup_second = OpTypePointer Workgroup %second + %__0 = OpVariable %_ptr_Workgroup_second Workgroup + %int_3 = OpConstant %int 3 + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + %18 = OpAccessChain %_ptr_Workgroup_int %__0 %int_0 + OpStore %18 %int_3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("more than one Workgroup Storage Class variable in the " + "entry point interface point to a type decorated with Block, " + "all of them must be decorated with Aliased")); +} + +TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableNotAStruct) { + 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 8 1 1 + OpDecorate %first Block + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %int_3 = OpConstant %int 3 + %first = OpTypeArray %int %int_3 +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Block decoration on a non-struct type")); +} + +TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableMissingLayout) { + 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 8 1 1 + OpDecorate %first Block + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %first = OpTypeStruct %int +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Block must be explicitly laid out with Offset decorations")); +} + +TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableBadLayout) { + 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 8 1 1 + OpMemberDecorate %first 0 Offset 1 + OpDecorate %first Block + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %first = OpTypeStruct %int +%_ptr_Workgroup_first = OpTypePointer Workgroup %first + %_ = OpVariable %_ptr_Workgroup_first Workgroup + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 +%_ptr_Workgroup_int = OpTypePointer Workgroup %int + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0 + OpStore %13 %int_2 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Block for variable in Workgroup storage class must follow " + "standard storage buffer layout rules: " + "member 0 at offset 1 is not aligned to 4")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp index 0d97c66b3..7d66dcd4a 100644 --- a/test/val/val_memory_test.cpp +++ b/test/val/val_memory_test.cpp @@ -22,6 +22,16 @@ #include "test/val/val_code_generator.h" #include "test/val/val_fixtures.h" +// For pretty-printing tuples with spv_target_env. +std::ostream& operator<<(std::ostream& stream, spv_target_env target) +{ + switch (target) { + case SPV_ENV_UNIVERSAL_1_3: return stream << "SPV_ENV_UNIVERSAL_1_3"; + case SPV_ENV_UNIVERSAL_1_4: return stream << "SPV_ENV_UNIVERSAL_1_4"; + default: return stream << (unsigned)target; + } +} + namespace spvtools { namespace val { namespace { @@ -3444,9 +3454,10 @@ OpFunctionEnd } using ValidateSizedVariable = - spvtest::ValidateBase>; + spvtest::ValidateBase>; -CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit) { +CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit, bool buffer_block) { CodeGenerator generator; generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n"; generator.extensions_ = @@ -3454,20 +3465,25 @@ CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit) { "\"SPV_KHR_8bit_storage\"\n"; generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; if (is_8bit) { - generator.before_types_ = R"(OpDecorate %char_buffer_block BufferBlock -OpMemberDecorate %char_buffer_block 0 Offset 0 -)"; + generator.before_types_ = "OpMemberDecorate %char_buffer_block 0 Offset 0\n"; + if (buffer_block) + generator.before_types_ += "OpDecorate %char_buffer_block BufferBlock\n"; + generator.types_ = R"(%void = OpTypeVoid %char = OpTypeInt 8 0 %char4 = OpTypeVector %char 4 %char_buffer_block = OpTypeStruct %char )"; } else { - generator.before_types_ = R"(OpDecorate %half_buffer_block BufferBlock -OpDecorate %short_buffer_block BufferBlock -OpMemberDecorate %half_buffer_block 0 Offset 0 -OpMemberDecorate %short_buffer_block 0 Offset 0 -)"; + generator.before_types_ = + "OpMemberDecorate %half_buffer_block 0 Offset 0\n" + "OpMemberDecorate %short_buffer_block 0 Offset 0\n"; + if (buffer_block) { + generator.before_types_ += + "OpDecorate %half_buffer_block BufferBlock\n" + "OpDecorate %short_buffer_block BufferBlock\n"; + } + generator.types_ = R"(%void = OpTypeVoid %short = OpTypeInt 16 0 %half = OpTypeFloat 16 @@ -3490,6 +3506,10 @@ TEST_P(ValidateSizedVariable, Capability) { const std::string storage_class = std::get<0>(GetParam()); const std::string capability = std::get<1>(GetParam()); const std::string var_type = std::get<2>(GetParam()); + const spv_target_env target = std::get<3>(GetParam()); + + ASSERT_TRUE(target == SPV_ENV_UNIVERSAL_1_3 || + target == SPV_ENV_UNIVERSAL_1_4); bool type_8bit = false; if (var_type == "%char" || var_type == "%char4" || @@ -3497,7 +3517,16 @@ TEST_P(ValidateSizedVariable, Capability) { type_8bit = true; } - auto generator = GetSizedVariableCodeGenerator(type_8bit); + const bool buffer_block = var_type.find("buffer_block") != std::string::npos; + + auto generator = GetSizedVariableCodeGenerator(type_8bit, buffer_block); + + if (capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR" || + capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR") { + generator.extensions_ += + "OpExtension \"SPV_KHR_workgroup_memory_explicit_layout\"\n"; + } + generator.types_ += "%ptr_type = OpTypePointer " + storage_class + " " + var_type + "\n%var = OpVariable %ptr_type " + storage_class + "\n"; @@ -3527,7 +3556,6 @@ TEST_P(ValidateSizedVariable, Capability) { } storage_class_ok = true; } else if (storage_class == "Uniform") { - bool buffer_block = var_type.find("buffer_block") != std::string::npos; if (type_8bit) { capability_ok = capability == "UniformAndStorageBuffer8BitAccess" || (capability == "StorageBuffer8BitAccess" && buffer_block); @@ -3537,11 +3565,30 @@ TEST_P(ValidateSizedVariable, Capability) { (capability == "StorageBuffer16BitAccess" && buffer_block); } storage_class_ok = true; + } else if (storage_class == "Workgroup") { + if (type_8bit) { + capability_ok = + capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR"; + } else { + capability_ok = + capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR"; + } + storage_class_ok = true; } - CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); - spv_result_t result = ValidateInstructions(SPV_ENV_UNIVERSAL_1_3); - if (capability_ok) { + CompileSuccessfully(generator.Build(), target); + spv_result_t result = ValidateInstructions(target); + if (target < SPV_ENV_UNIVERSAL_1_4 && + (capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR" || + capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR")) { + EXPECT_EQ(SPV_ERROR_WRONG_VERSION, result); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("requires SPIR-V version 1.4 or later")); + } else if (buffer_block && target > SPV_ENV_UNIVERSAL_1_3) { + EXPECT_EQ(SPV_ERROR_WRONG_VERSION, result); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("requires SPIR-V version 1.3 or earlier")); + } else if (capability_ok) { EXPECT_EQ(SPV_SUCCESS, result); } else { EXPECT_EQ(SPV_ERROR_INVALID_ID, result); @@ -3566,8 +3613,10 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("UniformConstant", "Input", "Output", "Workgroup", "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"), Values("StorageBuffer8BitAccess", - "UniformAndStorageBuffer8BitAccess", "StoragePushConstant8"), - Values("%char", "%char4", "%char_buffer_block"))); + "UniformAndStorageBuffer8BitAccess", "StoragePushConstant8", + "WorkgroupMemoryExplicitLayout8BitAccessKHR"), + Values("%char", "%char4", "%char_buffer_block"), + Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4))); INSTANTIATE_TEST_SUITE_P( Storage16, ValidateSizedVariable, @@ -3575,9 +3624,11 @@ INSTANTIATE_TEST_SUITE_P( "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"), Values("StorageBuffer16BitAccess", "UniformAndStorageBuffer16BitAccess", - "StoragePushConstant16", "StorageInputOutput16"), + "StoragePushConstant16", "StorageInputOutput16", + "WorkgroupMemoryExplicitLayout16BitAccessKHR"), Values("%short", "%half", "%short4", "%half4", "%mat4x4", - "%short_buffer_block", "%half_buffer_block"))); + "%short_buffer_block", "%half_buffer_block"), + Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4))); using ValidateSizedLoadStore = spvtest::ValidateBase>; diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index e7e589989..6999b39a0 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -490,6 +490,10 @@ Options (in lexicographical order):)", avoid triggering those bugs. Current workarounds: Avoid OpUnreachable in loops.)"); printf(R"( + --workgroup-scalar-block-layout + Forwards this option to the validator. See the validator help + for details.)"); + printf(R"( --wrap-opkill Replaces all OpKill instructions in functions that can be called from a continue construct with a function call to a function @@ -741,6 +745,8 @@ OptStatus ParseFlags(int argc, const char** argv, validator_options->SetRelaxBlockLayout(true); } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) { validator_options->SetScalarBlockLayout(true); + } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) { + validator_options->SetWorkgroupScalarBlockLayout(true); } else if (0 == strcmp(cur_arg, "--skip-block-layout")) { validator_options->SetSkipBlockLayout(true); } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { diff --git a/tools/val/val.cpp b/tools/val/val.cpp index da63245a2..5450023a4 100644 --- a/tools/val/val.cpp +++ b/tools/val/val.cpp @@ -58,6 +58,7 @@ Options: uniform, storage buffer, and push constant layouts. Scalar layout rules are more permissive than relaxed block layout so in effect this will override the --relax-block-layout option. + --workgroup-scalar-block-layout Enable scalar block layout when checking Workgroup block layouts. --skip-block-layout Skip checking standard uniform/storage buffer layout. Overrides any --relax-block-layout or --scalar-block-layout option. --relax-struct-store Allow store from one struct type to a @@ -148,6 +149,8 @@ int main(int argc, char** argv) { options.SetUniformBufferStandardLayout(true); } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) { options.SetScalarBlockLayout(true); + } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) { + options.SetWorkgroupScalarBlockLayout(true); } else if (0 == strcmp(cur_arg, "--skip-block-layout")) { options.SetSkipBlockLayout(true); } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {