Validate SPV_KHR_workgroup_memory_explicit_layout (#4128)

* Validate SPV_KHR_workgroup_memory_explicit_layout

* Check if SPIR-V is at least 1.4 to use the extension.

* Check if either only Workgroup Blocks or only Workgroup non-Blocks
  are used.

* Check that if more than one Workgroup Block is used, variables are
  decorated with Aliased.

* Check layout decorations for Workgroup Blocks.

* Implicitly use main capability if the ...8BitAccess or
  ...16BitAccess are used.

* Allow 8-bit and 16-bit types when ...8BitAccess and ...16BitAccess
  are used respectively.

* Update SPIRV-Headers dependency

Bump it to include SPV_KHR_workgroup_memory_explicit_layout.

* Add option to validate Workgroup blocks with scalar layout

Validate the equivalent of scalarBlockLayout for Workgroup storage
class Block variables from SPV_KHR_workgroup_memory_explicit_layout.
Add option to the API and command line tool.
This commit is contained in:
Caio Marcelo de Oliveira Filho 2021-01-27 16:38:38 -08:00 committed by GitHub
parent cc81f53d3d
commit b812fd634e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 654 additions and 30 deletions

2
DEPS
View File

@ -6,7 +6,7 @@ vars = {
'effcee_revision': '2ec8f8738118cc483b67c04a759fee53496c5659',
'googletest_revision': '3af06fe1664d30f98de1e78c53a7087e842a2547',
're2_revision': 'ca11026a032ce2a3de4b3c389ee53d2bdc8794d6',
'spirv_headers_revision': 'f027d53ded7e230e008d37c8b47ede7cd308e19d',
'spirv_headers_revision': 'faa570afbc91ac73d594d787486bcf8f2df1ace0',
}
deps = {

View File

@ -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(

View File

@ -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);

View File

@ -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;

View File

@ -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;
};

View File

@ -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<Instruction*> 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<uint32_t>(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;
}

View File

@ -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);

View File

@ -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 "

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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<std::tuple<std::string, std::string, std::string>>;
spvtest::ValidateBase<std::tuple<std::string, std::string,
std::string, spv_target_env>>;
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<std::tuple<std::string, uint32_t, std::string>>;

View File

@ -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")) {

View File

@ -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")) {