mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 11:10:05 +00:00
Enforce block layout rules even when relaxed
- Vulkan 1.0 uses strict layout rules - Vulkan 1.0 with relaxed-block-layout validator option enforces all rules except for the relaxation of vector offset. - Vulkan 1.1 and later always supports relaxed block layout Add spot check tests for the relaxed-block-layout scenarios. Fixes #1697
This commit is contained in:
parent
c02d216648
commit
2c6185e6bf
@ -342,7 +342,7 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
|
||||
<< " layout rules: member " << member_idx << " ");
|
||||
return ds;
|
||||
};
|
||||
if (vstate.options()->relax_block_layout) return SPV_SUCCESS;
|
||||
const bool relaxed_block_layout = vstate.IsRelaxedBlockLayout();
|
||||
const auto& members = getStructMembers(struct_id, vstate);
|
||||
|
||||
// To check for member overlaps, we want to traverse the members in
|
||||
@ -396,9 +396,25 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
|
||||
// Check offset.
|
||||
if (offset == 0xffffffff)
|
||||
return fail(memberIdx) << "is missing an Offset decoration";
|
||||
if (!IsAlignedTo(offset, alignment))
|
||||
return fail(memberIdx)
|
||||
<< "at offset " << offset << " is not aligned to " << alignment;
|
||||
if (relaxed_block_layout && opcode == SpvOpTypeVector) {
|
||||
// In relaxed block layout, the vector offset must be aligned to the
|
||||
// vector's scalar element type.
|
||||
const auto componentId = inst->words()[2];
|
||||
const auto scalar_alignment = getBaseAlignment(
|
||||
componentId, blockRules, constraint, constraints, vstate);
|
||||
if (!IsAlignedTo(offset, scalar_alignment)) {
|
||||
return fail(memberIdx)
|
||||
<< "at offset " << offset
|
||||
<< " is not aligned to scalar element size " << scalar_alignment;
|
||||
}
|
||||
} else {
|
||||
// Without relaxed block layout, the offset must be divisible by the
|
||||
// base alignment.
|
||||
if (!IsAlignedTo(offset, alignment)) {
|
||||
return fail(memberIdx)
|
||||
<< "at offset " << offset << " is not aligned to " << alignment;
|
||||
}
|
||||
}
|
||||
// SPIR-V requires struct members to be specified in memory address order,
|
||||
// and they should not overlap. Vulkan relaxes that rule.
|
||||
if (!permit_non_monotonic_member_offsets) {
|
||||
@ -416,11 +432,13 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
|
||||
return fail(memberIdx) << "at offset " << offset
|
||||
<< " overlaps previous member ending at offset "
|
||||
<< nextValidOffset - 1;
|
||||
// Check improper straddle of vectors.
|
||||
if (SpvOpTypeVector == opcode &&
|
||||
hasImproperStraddle(id, offset, constraint, constraints, vstate))
|
||||
return fail(memberIdx)
|
||||
<< "is an improperly straddling vector at offset " << offset;
|
||||
if (relaxed_block_layout) {
|
||||
// Check improper straddle of vectors.
|
||||
if (SpvOpTypeVector == opcode &&
|
||||
hasImproperStraddle(id, offset, constraint, constraints, vstate))
|
||||
return fail(memberIdx)
|
||||
<< "is an improperly straddling vector at offset " << offset;
|
||||
}
|
||||
// Check struct members recursively.
|
||||
spv_result_t recursive_status = SPV_SUCCESS;
|
||||
if (SpvOpTypeStruct == opcode &&
|
||||
|
@ -169,9 +169,18 @@ ValidationState_t::ValidationState_t(const spv_const_context ctx,
|
||||
in_function_(false) {
|
||||
assert(opt && "Validator options may not be Null.");
|
||||
|
||||
features_.non_monotonic_struct_member_offsets =
|
||||
spvIsVulkanEnv(context_->target_env);
|
||||
switch (context_->target_env) {
|
||||
const auto env = context_->target_env;
|
||||
|
||||
if (spvIsVulkanEnv(env)) {
|
||||
features_.non_monotonic_struct_member_offsets = true;
|
||||
|
||||
// Vulkan 1.1 includes VK_KHR_relaxed_block_layout in core.
|
||||
if (env != SPV_ENV_VULKAN_1_0) {
|
||||
features_.env_relaxed_block_layout = true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (env) {
|
||||
case SPV_ENV_WEBGPU_0:
|
||||
features_.bans_op_undef = true;
|
||||
break;
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "latest_version_spirv_header.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
#include "spirv_definition.h"
|
||||
#include "spirv_validator_options.h"
|
||||
#include "val/function.h"
|
||||
#include "val/instruction.h"
|
||||
|
||||
@ -85,6 +86,10 @@ class ValidationState_t {
|
||||
// Allow non-monotonic offsets for struct members?
|
||||
// Vulkan permits this.
|
||||
bool non_monotonic_struct_member_offsets = false;
|
||||
|
||||
// Target environment uses relaxed block layout.
|
||||
// This is true for Vulkan 1.1 or later.
|
||||
bool env_relaxed_block_layout = false;
|
||||
};
|
||||
|
||||
ValidationState_t(const spv_const_context context,
|
||||
@ -379,6 +384,12 @@ class ValidationState_t {
|
||||
/// Inserts a new <id> to the set of Local Variables.
|
||||
void registerLocalVariable(const uint32_t id) { local_vars_.insert(id); }
|
||||
|
||||
// Returns true if using relaxed block layout, equivalent to
|
||||
// VK_KHR_relaxed_block_layout.
|
||||
bool IsRelaxedBlockLayout() const {
|
||||
return features_.env_relaxed_block_layout || options()->relax_block_layout;
|
||||
}
|
||||
|
||||
/// Sets the struct nesting depth for a given struct ID
|
||||
void set_struct_nesting_depth(uint32_t id, uint32_t depth) {
|
||||
struct_nesting_depth_[id] = depth;
|
||||
|
@ -1571,6 +1571,111 @@ TEST_F(ValidateDecorations, BlockLayoutForbidsTightScalarVec3PackingBad) {
|
||||
"rules: member 1 at offset 4 is not aligned to 16"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations,
|
||||
BlockLayoutPermitsTightScalarVec3PackingWithRelaxedLayoutGood) {
|
||||
// Same as previous test, but with explicit option to relax block layout.
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 450
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpMemberDecorate %S 1 Offset 4
|
||||
OpDecorate %S Block
|
||||
OpDecorate %B DescriptorSet 0
|
||||
OpDecorate %B Binding 0
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v3float = OpTypeVector %float 3
|
||||
%S = OpTypeStruct %float %v3float
|
||||
%_ptr_Uniform_S = OpTypePointer Uniform %S
|
||||
%B = OpVariable %_ptr_Uniform_S Uniform
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
spvValidatorOptionsSetRelaxBlockLayout(getValidatorOptions(), true);
|
||||
EXPECT_EQ(SPV_SUCCESS,
|
||||
ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
|
||||
EXPECT_THAT(getDiagnosticString(), Eq(""));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations,
|
||||
BlockLayoutPermitsTightScalarVec3PackingBadOffsetWithRelaxedLayoutBad) {
|
||||
// Same as previous test, but with the vector not aligned to its scalar
|
||||
// element. Use offset 5 instead of a multiple of 4.
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 450
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpMemberDecorate %S 1 Offset 5
|
||||
OpDecorate %S Block
|
||||
OpDecorate %B DescriptorSet 0
|
||||
OpDecorate %B Binding 0
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v3float = OpTypeVector %float 3
|
||||
%S = OpTypeStruct %float %v3float
|
||||
%_ptr_Uniform_S = OpTypePointer Uniform %S
|
||||
%B = OpVariable %_ptr_Uniform_S Uniform
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
spvValidatorOptionsSetRelaxBlockLayout(getValidatorOptions(), true);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID,
|
||||
ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr(
|
||||
"Structure id 2 decorated as Block for variable in Uniform storage "
|
||||
"class must follow standard uniform buffer layout rules: member 1 at "
|
||||
"offset 5 is not aligned to scalar element size 4"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations,
|
||||
BlockLayoutPermitsTightScalarVec3PackingWithVulkan1_1Good) {
|
||||
// Same as previous test, but with Vulkan 1.1. Vulkan 1.1 included
|
||||
// VK_KHR_relaxed_block_layout in core.
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 450
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpMemberDecorate %S 1 Offset 4
|
||||
OpDecorate %S Block
|
||||
OpDecorate %B DescriptorSet 0
|
||||
OpDecorate %B Binding 0
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v3float = OpTypeVector %float 3
|
||||
%S = OpTypeStruct %float %v3float
|
||||
%_ptr_Uniform_S = OpTypePointer Uniform %S
|
||||
%B = OpVariable %_ptr_Uniform_S Uniform
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_SUCCESS,
|
||||
ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
|
||||
EXPECT_THAT(getDiagnosticString(), Eq(""));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, BufferBlock16bitStandardStorageBufferLayout) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
@ -1612,6 +1717,156 @@ TEST_F(ValidateDecorations, BufferBlock16bitStandardStorageBufferLayout) {
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, BlockArrayBaseAlignmentGood) {
|
||||
// For uniform buffer, Array base alignment is 16, and ArrayStride
|
||||
// must be a multiple of 16.
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 450
|
||||
OpDecorate %_arr_float_uint_2 ArrayStride 16
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpMemberDecorate %S 1 Offset 16
|
||||
OpDecorate %S Block
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v2float = OpTypeVector %float 2
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%_arr_float_uint_2 = OpTypeArray %float %uint_2
|
||||
%S = OpTypeStruct %v2float %_arr_float_uint_2
|
||||
%_ptr_PushConstant_S = OpTypePointer PushConstant %S
|
||||
%u = OpVariable %_ptr_PushConstant_S PushConstant
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
|
||||
<< getDiagnosticString();
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, BlockArrayBadAlignmentBad) {
|
||||
// For uniform buffer, Array base alignment is 16.
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 450
|
||||
OpDecorate %_arr_float_uint_2 ArrayStride 16
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpMemberDecorate %S 1 Offset 8
|
||||
OpDecorate %S Block
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v2float = OpTypeVector %float 2
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%_arr_float_uint_2 = OpTypeArray %float %uint_2
|
||||
%S = OpTypeStruct %v2float %_arr_float_uint_2
|
||||
%_ptr_Uniform_S = OpTypePointer Uniform %S
|
||||
%u = OpVariable %_ptr_Uniform_S Uniform
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr(
|
||||
"Structure id 3 decorated as Block for variable in Uniform "
|
||||
"storage class must follow standard uniform buffer layout rules: "
|
||||
"member 1 at offset 8 is not aligned to 16"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithRelaxedLayoutStillBad) {
|
||||
// For uniform buffer, Array base alignment is 16, and ArrayStride
|
||||
// must be a multiple of 16. This case uses relaxed block layout. Relaxed
|
||||
// layout only relaxes rules for vector alignment, not array alignment.
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 450
|
||||
OpDecorate %_arr_float_uint_2 ArrayStride 16
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpMemberDecorate %S 1 Offset 8
|
||||
OpDecorate %S Block
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v2float = OpTypeVector %float 2
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%_arr_float_uint_2 = OpTypeArray %float %uint_2
|
||||
%S = OpTypeStruct %v2float %_arr_float_uint_2
|
||||
%_ptr_Uniform_S = OpTypePointer Uniform %S
|
||||
%u = OpVariable %_ptr_Uniform_S Uniform
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID,
|
||||
ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
|
||||
spvValidatorOptionsSetRelaxBlockLayout(getValidatorOptions(), true);
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr(
|
||||
"Structure id 3 decorated as Block for variable in Uniform "
|
||||
"storage class must follow standard uniform buffer layout rules: "
|
||||
"member 1 at offset 8 is not aligned to 16"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithVulkan1_1StillBad) {
|
||||
// Same as previous test, but with Vulkan 1.1, which includes
|
||||
// VK_KHR_relaxed_block_layout in core.
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 450
|
||||
OpDecorate %_arr_float_uint_2 ArrayStride 16
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpMemberDecorate %S 1 Offset 8
|
||||
OpDecorate %S Block
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v2float = OpTypeVector %float 2
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%_arr_float_uint_2 = OpTypeArray %float %uint_2
|
||||
%S = OpTypeStruct %v2float %_arr_float_uint_2
|
||||
%_ptr_Uniform_S = OpTypePointer Uniform %S
|
||||
%u = OpVariable %_ptr_Uniform_S Uniform
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID,
|
||||
ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr(
|
||||
"Structure id 3 decorated as Block for variable in Uniform "
|
||||
"storage class must follow standard uniform buffer layout rules: "
|
||||
"member 1 at offset 8 is not aligned to 16"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, PushConstantArrayBaseAlignmentGood) {
|
||||
// Tests https://github.com/KhronosGroup/SPIRV-Tools/issues/1664
|
||||
// From GLSL vertex shader:
|
||||
|
Loading…
Reference in New Issue
Block a user