validator: use RowMajor, ArrayStride, MatrixStride

Implement rules for row-major matrices

Use ArrayStride and MatrixStride to compute sizes

Propagate matrix stride and RowMajor/ColumnMajor through array members of structs.

Fixes #1637
Fixes #1668
This commit is contained in:
David Neto 2018-07-04 15:50:42 -04:00
parent 1a283f41ed
commit 5e0276bdc9
2 changed files with 536 additions and 44 deletions

View File

@ -15,7 +15,10 @@
#include "validate.h"
#include <algorithm>
#include <cassert>
#include <string>
#include <unordered_map>
#include <utility>
#include "diagnostic.h"
#include "opcode.h"
@ -27,10 +30,48 @@ using libspirv::Decoration;
using libspirv::DiagnosticStream;
using libspirv::Instruction;
using libspirv::ValidationState_t;
using std::make_pair;
namespace {
// Returns whether the given variable has a BuiltIn decoration.
// Distinguish between row and column major matrix layouts.
enum MatrixLayout { kRowMajor, kColumnMajor };
// A functor for hashing a pair of integers.
struct PairHash {
std::size_t operator()(const std::pair<uint32_t, uint32_t> pair) const {
const uint32_t a = pair.first;
const uint32_t b = pair.second;
const uint32_t rotated_b = (b >> 2) | ((b & 3) << 30);
return a ^ rotated_b;
}
};
// Struct member layout attributes that are inherited through arrays.
struct LayoutConstraints {
explicit LayoutConstraints(
MatrixLayout the_majorness = MatrixLayout::kColumnMajor,
uint32_t stride = 0)
: majorness(the_majorness), matrix_stride(stride) {}
MatrixLayout majorness;
uint32_t matrix_stride;
};
// A type for mapping (struct id, member id) to layout constraints.
using MemberConstraints = std::unordered_map<std::pair<uint32_t, uint32_t>,
LayoutConstraints, PairHash>;
// Returns the array stride of the given array type.
uint32_t GetArrayStride(uint32_t array_id, ValidationState_t& vstate) {
for (auto& decoration : vstate.id_decorations(array_id)) {
if (SpvDecorationArrayStride == decoration.dec_type()) {
return decoration.params()[0];
}
}
return 0;
}
// Returns true if the given variable has a BuiltIn decoration.
bool isBuiltInVar(uint32_t var_id, ValidationState_t& vstate) {
const auto& decorations = vstate.id_decorations(var_id);
return std::any_of(
@ -38,7 +79,7 @@ bool isBuiltInVar(uint32_t var_id, ValidationState_t& vstate) {
[](const Decoration& d) { return SpvDecorationBuiltIn == d.dec_type(); });
}
// Returns whether the given structure type has any members with BuiltIn
// Returns true if the given structure type has any members with BuiltIn
// decoration.
bool isBuiltInStruct(uint32_t struct_id, ValidationState_t& vstate) {
const auto& decorations = vstate.id_decorations(struct_id);
@ -113,6 +154,8 @@ uint32_t align(uint32_t x, uint32_t alignment) {
// ensure that structs and arrays are aligned at least to a multiple of 16
// bytes.
uint32_t getBaseAlignment(uint32_t member_id, bool roundUp,
const LayoutConstraints& inherited,
MemberConstraints& constraints,
ValidationState_t& vstate) {
const auto inst = vstate.FindDef(member_id);
const auto& words = inst->words();
@ -125,25 +168,48 @@ uint32_t getBaseAlignment(uint32_t member_id, bool roundUp,
case SpvOpTypeVector: {
const auto componentId = words[2];
const auto numComponents = words[3];
const auto componentAlignment =
getBaseAlignment(componentId, roundUp, vstate);
const auto componentAlignment = getBaseAlignment(
componentId, roundUp, inherited, constraints, vstate);
baseAlignment =
componentAlignment * (numComponents == 3 ? 4 : numComponents);
break;
}
case SpvOpTypeMatrix:
case SpvOpTypeMatrix: {
const auto column_type = words[2];
if (inherited.majorness == kColumnMajor) {
baseAlignment = getBaseAlignment(column_type, roundUp, inherited,
constraints, vstate);
} else {
// A row-major matrix of C columns has a base alignment equal to the
// base alignment of a vector of C matrix components.
const auto num_columns = words[3];
const auto component_inst = vstate.FindDef(column_type);
const auto component_id = component_inst->words()[2];
const auto componentAlignment = getBaseAlignment(
component_id, roundUp, inherited, constraints, vstate);
baseAlignment =
componentAlignment * (num_columns == 3 ? 4 : num_columns);
}
} break;
case SpvOpTypeArray:
case SpvOpTypeRuntimeArray:
baseAlignment = getBaseAlignment(words[2], roundUp, vstate);
baseAlignment =
getBaseAlignment(words[2], roundUp, inherited, constraints, vstate);
if (roundUp) baseAlignment = align(baseAlignment, 16u);
break;
case SpvOpTypeStruct:
for (auto id : getStructMembers(member_id, vstate)) {
baseAlignment =
std::max(baseAlignment, getBaseAlignment(id, roundUp, vstate));
case SpvOpTypeStruct: {
const auto members = getStructMembers(member_id, vstate);
for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
memberIdx < numMembers; ++memberIdx) {
const auto id = members[memberIdx];
const auto& constraint = constraints[make_pair(member_id, memberIdx)];
baseAlignment = std::max(
baseAlignment,
getBaseAlignment(id, roundUp, constraint, constraints, vstate));
}
if (roundUp) baseAlignment = align(baseAlignment, 16u);
break;
}
default:
assert(0);
break;
@ -154,35 +220,60 @@ uint32_t getBaseAlignment(uint32_t member_id, bool roundUp,
// Returns size of a struct member. Doesn't include padding at the end of struct
// or array. Assumes that in the struct case, all members have offsets.
uint32_t getSize(uint32_t member_id, bool roundUp, ValidationState_t& vstate) {
uint32_t getSize(uint32_t member_id, bool roundUp,
const LayoutConstraints& inherited,
MemberConstraints& constraints, ValidationState_t& vstate) {
const auto inst = vstate.FindDef(member_id);
const auto& words = inst->words();
const auto baseAlignment = getBaseAlignment(member_id, roundUp, vstate);
switch (inst->opcode()) {
case SpvOpTypeInt:
case SpvOpTypeFloat:
return baseAlignment;
return getBaseAlignment(member_id, roundUp, inherited, constraints,
vstate);
case SpvOpTypeVector: {
const auto componentId = words[2];
const auto numComponents = words[3];
const auto componentSize = getSize(componentId, roundUp, vstate);
return componentSize * numComponents;
const auto componentSize =
getSize(componentId, roundUp, inherited, constraints, vstate);
const auto size = componentSize * numComponents;
return size;
}
case SpvOpTypeArray: {
const auto sizeInst = vstate.FindDef(words[3]);
if (spvOpcodeIsSpecConstant(sizeInst->opcode())) return 0;
assert(SpvOpConstant == sizeInst->opcode());
return (sizeInst->words()[3] - 1) * baseAlignment +
getSize(vstate.FindDef(member_id)->words()[2], roundUp, vstate);
const uint32_t num_elem = sizeInst->words()[3];
const uint32_t elem_type = words[2];
const uint32_t elem_size =
getSize(elem_type, roundUp, inherited, constraints, vstate);
// Account for gaps due to alignments in the first N-1 elements,
// then add the size of the last element.
const auto size =
(num_elem - 1) * GetArrayStride(member_id, vstate) + elem_size;
return size;
}
case SpvOpTypeRuntimeArray:
return 0;
case SpvOpTypeMatrix:
return words[3] * baseAlignment;
case SpvOpTypeMatrix: {
const auto num_columns = words[3];
if (inherited.majorness == kColumnMajor) {
return num_columns * inherited.matrix_stride;
} else {
// Row major case.
const auto column_type = words[2];
const auto component_inst = vstate.FindDef(column_type);
const auto num_rows = component_inst->words()[3];
const auto scalar_elem_type = component_inst->words()[2];
const uint32_t scalar_elem_size =
getSize(scalar_elem_type, roundUp, inherited, constraints, vstate);
return (num_rows - 1) * inherited.matrix_stride +
num_columns * scalar_elem_size;
}
}
case SpvOpTypeStruct: {
const auto& members = getStructMembers(member_id, vstate);
if (members.empty()) return 0;
const auto lastIdx = members.size() - 1;
const auto lastIdx = uint32_t(members.size() - 1);
const auto& lastMember = members.back();
uint32_t offset = 0xffffffff;
// Find the offset of the last element and add the size.
@ -195,7 +286,9 @@ uint32_t getSize(uint32_t member_id, bool roundUp, ValidationState_t& vstate) {
// This check depends on the fact that all members have offsets. This
// has been checked earlier in the flow.
assert(offset != 0xffffffff);
return offset + getSize(lastMember, roundUp, vstate);
const auto& constraint = constraints[make_pair(lastMember, lastIdx)];
return offset +
getSize(lastMember, roundUp, constraint, constraints, vstate);
}
default:
assert(0);
@ -211,8 +304,10 @@ uint32_t getSize(uint32_t member_id, bool roundUp, ValidationState_t& vstate) {
// - It is a vector with total size greater than 16 bytes and has its Offset
// decorations placing its first byte at a non-integer multiple of 16.
bool hasImproperStraddle(uint32_t id, uint32_t offset,
const LayoutConstraints& inherited,
MemberConstraints& constraints,
ValidationState_t& vstate) {
const auto size = getSize(id, false, vstate);
const auto size = getSize(id, false, inherited, constraints, vstate);
const auto F = offset;
const auto L = offset + size - 1;
if (size <= 16) {
@ -232,9 +327,11 @@ bool IsAlignedTo(uint32_t offset, uint32_t alignment) {
// Returns SPV_SUCCESS if the given struct satisfies standard layout rules for
// Block or BufferBlocks in Vulkan. Otherwise emits a diagnostic and returns
// something other than SPV_SUCCESS.
// something other than SPV_SUCCESS. Matrices inherit the specified column
// or row major-ness.
spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
const char* decoration_str, bool blockRules,
MemberConstraints& constraints,
ValidationState_t& vstate) {
auto fail = [&vstate, struct_id, storage_class_str, decoration_str,
blockRules](uint32_t member_idx) -> libspirv::DiagnosticStream {
@ -254,17 +351,25 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
memberIdx < numMembers; memberIdx++) {
auto id = members[memberIdx];
const auto alignment = getBaseAlignment(id, blockRules, vstate);
const auto inst = vstate.FindDef(id);
const auto opcode = inst->opcode();
uint32_t offset = 0xffffffff;
for (auto& decoration : vstate.id_decorations(struct_id)) {
if (SpvDecorationOffset == decoration.dec_type() &&
decoration.struct_member_index() == (int)memberIdx) {
offset = decoration.params()[0];
if (decoration.struct_member_index() == (int)memberIdx) {
switch (decoration.dec_type()) {
case SpvDecorationOffset:
offset = decoration.params()[0];
break;
default:
break;
}
}
}
const auto size = getSize(id, blockRules, vstate);
const LayoutConstraints& constraint =
constraints[make_pair(struct_id, uint32_t(memberIdx))];
const auto alignment =
getBaseAlignment(id, blockRules, constraint, constraints, vstate);
const auto inst = vstate.FindDef(id);
const auto opcode = inst->opcode();
const auto size = getSize(id, blockRules, constraint, constraints, vstate);
// Check offset.
if (offset == 0xffffffff)
return fail(memberIdx) << "is missing an Offset decoration";
@ -282,15 +387,16 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
<< " overlaps previous member ending at offset "
<< nextValidOffset - 1;
// Check improper straddle of vectors.
if (SpvOpTypeVector == opcode && hasImproperStraddle(id, offset, vstate))
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 &&
SPV_SUCCESS !=
(recursive_status = checkLayout(
id, storage_class_str, decoration_str, blockRules, vstate)))
SPV_SUCCESS != (recursive_status =
checkLayout(id, storage_class_str, decoration_str,
blockRules, constraints, vstate)))
return recursive_status;
// Check matrix stride.
if (SpvOpTypeMatrix == opcode) {
@ -307,9 +413,9 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
const auto typeId = inst->words()[2];
const auto arrayInst = vstate.FindDef(typeId);
if (SpvOpTypeStruct == arrayInst->opcode() &&
SPV_SUCCESS != (recursive_status =
checkLayout(typeId, storage_class_str,
decoration_str, blockRules, vstate)))
SPV_SUCCESS != (recursive_status = checkLayout(
typeId, storage_class_str, decoration_str,
blockRules, constraints, vstate)))
return recursive_status;
// Check array stride.
for (auto& decoration : vstate.id_decorations(id)) {
@ -541,6 +647,87 @@ spv_result_t CheckDescriptorSetArrayOfArrays(ValidationState_t& vstate) {
return SPV_SUCCESS;
}
// Load |constraints| with all the member constraints for structs contained
// within the given array type.
void ComputeMemberConstraintsForArray(MemberConstraints* constraints,
uint32_t array_id,
const LayoutConstraints& inherited,
ValidationState_t& vstate);
// Load |constraints| with all the member constraints for the given struct,
// and all its contained structs.
void ComputeMemberConstraintsForStruct(MemberConstraints* constraints,
uint32_t struct_id,
const LayoutConstraints& inherited,
ValidationState_t& vstate) {
assert(constraints);
const auto& members = getStructMembers(struct_id, vstate);
for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
memberIdx < numMembers; memberIdx++) {
LayoutConstraints& constraint =
(*constraints)[make_pair(struct_id, memberIdx)];
constraint = inherited;
for (auto& decoration : vstate.id_decorations(struct_id)) {
if (decoration.struct_member_index() == (int)memberIdx) {
switch (decoration.dec_type()) {
case SpvDecorationRowMajor:
constraint.majorness = kRowMajor;
break;
case SpvDecorationColMajor:
constraint.majorness = kColumnMajor;
break;
case SpvDecorationMatrixStride:
constraint.matrix_stride = decoration.params()[0];
break;
default:
break;
}
}
}
// Now recurse
auto member_type_id = members[memberIdx];
const auto member_type_inst = vstate.FindDef(member_type_id);
const auto opcode = member_type_inst->opcode();
switch (opcode) {
case SpvOpTypeArray:
case SpvOpTypeRuntimeArray:
ComputeMemberConstraintsForArray(constraints, member_type_id, inherited,
vstate);
break;
case SpvOpTypeStruct:
ComputeMemberConstraintsForStruct(constraints, member_type_id,
inherited, vstate);
break;
default:
break;
}
}
}
void ComputeMemberConstraintsForArray(MemberConstraints* constraints,
uint32_t array_id,
const LayoutConstraints& inherited,
ValidationState_t& vstate) {
assert(constraints);
auto elem_type_id = vstate.FindDef(array_id)->words()[2];
const auto elem_type_inst = vstate.FindDef(elem_type_id);
const auto opcode = elem_type_inst->opcode();
switch (opcode) {
case SpvOpTypeArray:
case SpvOpTypeRuntimeArray:
ComputeMemberConstraintsForArray(constraints, elem_type_id, inherited,
vstate);
break;
case SpvOpTypeStruct:
ComputeMemberConstraintsForStruct(constraints, elem_type_id, inherited,
vstate);
break;
default:
break;
}
}
spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
for (const auto& def : vstate.all_definitions()) {
const auto inst = def.second;
@ -557,7 +744,9 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
assert(SpvOpTypePointer == ptrInst->opcode());
const auto id = ptrInst->words()[3];
if (SpvOpTypeStruct != vstate.FindDef(id)->opcode()) continue;
MemberConstraints constraints;
ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
vstate);
// Prepare for messages
const char* sc_str =
uniform ? "Uniform"
@ -598,14 +787,14 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
<< " must be explicitly laid out with MatrixStride "
"decorations.";
} else if (blockRules &&
(SPV_SUCCESS !=
(recursive_status = checkLayout(id, sc_str, deco_str,
true, vstate)))) {
(SPV_SUCCESS != (recursive_status = checkLayout(
id, sc_str, deco_str, true,
constraints, vstate)))) {
return recursive_status;
} else if (bufferRules &&
(SPV_SUCCESS !=
(recursive_status = checkLayout(id, sc_str, deco_str,
false, vstate)))) {
(SPV_SUCCESS != (recursive_status = checkLayout(
id, sc_str, deco_str, false,
constraints, vstate)))) {
return recursive_status;
}
}

View File

@ -2360,4 +2360,307 @@ TEST_F(ValidateDecorations, BufferBlockEmptyStruct) {
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
TEST_F(ValidateDecorations, RowMajorMatrixTightPackingGood) {
// Row major matrix rule:
// A row-major matrix of C columns has a base alignment equal to
// the base alignment of a vector of C matrix components.
// Note: The "matrix component" is the scalar element type.
// The matrix has 3 columns and 2 rows (C=3, R=2).
// So the base alignment of b is the same as a vector of 3 floats, which is 16
// bytes. The matrix consists of two of these, and therefore occupies 2 x 16
// bytes, or 32 bytes.
//
// So the offsets can be:
// a -> 0
// b -> 16
// c -> 48
// d -> 60 ; d fits at bytes 12-15 after offset of c. Tight (vec3;float)
// packing
string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
OpSource GLSL 450
OpMemberDecorate %_struct_2 0 Offset 0
OpMemberDecorate %_struct_2 1 RowMajor
OpMemberDecorate %_struct_2 1 Offset 16
OpMemberDecorate %_struct_2 1 MatrixStride 16
OpMemberDecorate %_struct_2 2 Offset 48
OpMemberDecorate %_struct_2 3 Offset 60
OpDecorate %_struct_2 Block
OpDecorate %3 DescriptorSet 0
OpDecorate %3 Binding 0
%void = OpTypeVoid
%5 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%v2float = OpTypeVector %float 2
%mat3v2float = OpTypeMatrix %v2float 3
%v3float = OpTypeVector %float 3
%_struct_2 = OpTypeStruct %v4float %mat3v2float %v3float %float
%_ptr_Uniform__struct_2 = OpTypePointer Uniform %_struct_2
%3 = OpVariable %_ptr_Uniform__struct_2 Uniform
%1 = OpFunction %void None %5
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
<< getDiagnosticString();
}
TEST_F(ValidateDecorations, ArrayArrayRowMajorMatrixTightPackingGood) {
// Like the previous case, but we have an array of arrays of matrices.
// The RowMajor decoration goes on the struct member (surprisingly).
string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
OpSource GLSL 450
OpMemberDecorate %_struct_2 0 Offset 0
OpMemberDecorate %_struct_2 1 RowMajor
OpMemberDecorate %_struct_2 1 Offset 16
OpMemberDecorate %_struct_2 1 MatrixStride 16
OpMemberDecorate %_struct_2 2 Offset 80
OpMemberDecorate %_struct_2 3 Offset 92
OpDecorate %arr_mat ArrayStride 32
OpDecorate %arr_arr_mat ArrayStride 32
OpDecorate %_struct_2 Block
OpDecorate %3 DescriptorSet 0
OpDecorate %3 Binding 0
%void = OpTypeVoid
%5 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%v2float = OpTypeVector %float 2
%mat3v2float = OpTypeMatrix %v2float 3
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%arr_mat = OpTypeArray %mat3v2float %uint_1
%arr_arr_mat = OpTypeArray %arr_mat %uint_2
%v3float = OpTypeVector %float 3
%_struct_2 = OpTypeStruct %v4float %arr_arr_mat %v3float %float
%_ptr_Uniform__struct_2 = OpTypePointer Uniform %_struct_2
%3 = OpVariable %_ptr_Uniform__struct_2 Uniform
%1 = OpFunction %void None %5
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
<< getDiagnosticString();
}
TEST_F(ValidateDecorations, ArrayArrayRowMajorMatrixNextMemberOverlapsBad) {
// Like the previous case, but the offset of member 2 overlaps the matrix.
string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
OpSource GLSL 450
OpMemberDecorate %_struct_2 0 Offset 0
OpMemberDecorate %_struct_2 1 RowMajor
OpMemberDecorate %_struct_2 1 Offset 16
OpMemberDecorate %_struct_2 1 MatrixStride 16
OpMemberDecorate %_struct_2 2 Offset 64
OpMemberDecorate %_struct_2 3 Offset 92
OpDecorate %arr_mat ArrayStride 32
OpDecorate %arr_arr_mat ArrayStride 32
OpDecorate %_struct_2 Block
OpDecorate %3 DescriptorSet 0
OpDecorate %3 Binding 0
%void = OpTypeVoid
%5 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%v2float = OpTypeVector %float 2
%mat3v2float = OpTypeMatrix %v2float 3
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%arr_mat = OpTypeArray %mat3v2float %uint_1
%arr_arr_mat = OpTypeArray %arr_mat %uint_2
%v3float = OpTypeVector %float 3
%_struct_2 = OpTypeStruct %v4float %arr_arr_mat %v3float %float
%_ptr_Uniform__struct_2 = OpTypePointer Uniform %_struct_2
%3 = OpVariable %_ptr_Uniform__struct_2 Uniform
%1 = OpFunction %void None %5
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Structure id 2 decorated as Block for variable in Uniform storage "
"class must follow standard uniform buffer layout rules: member 2 at "
"offset 64 overlaps previous member ending at offset 79"));
}
TEST_F(ValidateDecorations, StorageBufferArraySizeCalculationPackGood) {
// Original GLSL
// #version 450
// layout (set=0,binding=0) buffer S {
// uvec3 arr[2][2]; // first 3 elements are 16 bytes, last is 12
// uint i; // Can have offset 60 = 3x16 + 12
// } B;
// void main() {}
string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
OpDecorate %_arr_v3uint_uint_2 ArrayStride 16
OpDecorate %_arr__arr_v3uint_uint_2_uint_2 ArrayStride 32
OpMemberDecorate %_struct_4 0 Offset 0
OpMemberDecorate %_struct_4 1 Offset 60
OpDecorate %_struct_4 BufferBlock
OpDecorate %5 DescriptorSet 0
OpDecorate %5 Binding 0
%void = OpTypeVoid
%7 = OpTypeFunction %void
%uint = OpTypeInt 32 0
%v3uint = OpTypeVector %uint 3
%uint_2 = OpConstant %uint 2
%_arr_v3uint_uint_2 = OpTypeArray %v3uint %uint_2
%_arr__arr_v3uint_uint_2_uint_2 = OpTypeArray %_arr_v3uint_uint_2 %uint_2
%_struct_4 = OpTypeStruct %_arr__arr_v3uint_uint_2_uint_2 %uint
%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
%5 = OpVariable %_ptr_Uniform__struct_4 Uniform
%1 = OpFunction %void None %7
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
TEST_F(ValidateDecorations, StorageBufferArraySizeCalculationPackBad) {
// Like previous but, the offset of the second member is too small.
string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
OpDecorate %_arr_v3uint_uint_2 ArrayStride 16
OpDecorate %_arr__arr_v3uint_uint_2_uint_2 ArrayStride 32
OpMemberDecorate %_struct_4 0 Offset 0
OpMemberDecorate %_struct_4 1 Offset 56
OpDecorate %_struct_4 BufferBlock
OpDecorate %5 DescriptorSet 0
OpDecorate %5 Binding 0
%void = OpTypeVoid
%7 = OpTypeFunction %void
%uint = OpTypeInt 32 0
%v3uint = OpTypeVector %uint 3
%uint_2 = OpConstant %uint 2
%_arr_v3uint_uint_2 = OpTypeArray %v3uint %uint_2
%_arr__arr_v3uint_uint_2_uint_2 = OpTypeArray %_arr_v3uint_uint_2 %uint_2
%_struct_4 = OpTypeStruct %_arr__arr_v3uint_uint_2_uint_2 %uint
%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
%5 = OpVariable %_ptr_Uniform__struct_4 Uniform
%1 = OpFunction %void None %7
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Structure id 4 decorated as BufferBlock for variable "
"in Uniform storage class must follow standard storage "
"buffer layout rules: member 1 at offset 56 overlaps "
"previous member ending at offset 59"));
}
TEST_F(ValidateDecorations, UniformBufferArraySizeCalculationPackGood) {
// Like the corresponding buffer block case, but the array padding must
// count for the last element as well, and so the offset of the second
// member must be at least 64.
string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
OpDecorate %_arr_v3uint_uint_2 ArrayStride 16
OpDecorate %_arr__arr_v3uint_uint_2_uint_2 ArrayStride 32
OpMemberDecorate %_struct_4 0 Offset 0
OpMemberDecorate %_struct_4 1 Offset 64
OpDecorate %_struct_4 Block
OpDecorate %5 DescriptorSet 0
OpDecorate %5 Binding 0
%void = OpTypeVoid
%7 = OpTypeFunction %void
%uint = OpTypeInt 32 0
%v3uint = OpTypeVector %uint 3
%uint_2 = OpConstant %uint 2
%_arr_v3uint_uint_2 = OpTypeArray %v3uint %uint_2
%_arr__arr_v3uint_uint_2_uint_2 = OpTypeArray %_arr_v3uint_uint_2 %uint_2
%_struct_4 = OpTypeStruct %_arr__arr_v3uint_uint_2_uint_2 %uint
%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
%5 = OpVariable %_ptr_Uniform__struct_4 Uniform
%1 = OpFunction %void None %7
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
TEST_F(ValidateDecorations, UniformBufferArraySizeCalculationPackBad) {
// Like previous but, the offset of the second member is too small.
string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
OpDecorate %_arr_v3uint_uint_2 ArrayStride 16
OpDecorate %_arr__arr_v3uint_uint_2_uint_2 ArrayStride 32
OpMemberDecorate %_struct_4 0 Offset 0
OpMemberDecorate %_struct_4 1 Offset 60
OpDecorate %_struct_4 Block
OpDecorate %5 DescriptorSet 0
OpDecorate %5 Binding 0
%void = OpTypeVoid
%7 = OpTypeFunction %void
%uint = OpTypeInt 32 0
%v3uint = OpTypeVector %uint 3
%uint_2 = OpConstant %uint 2
%_arr_v3uint_uint_2 = OpTypeArray %v3uint %uint_2
%_arr__arr_v3uint_uint_2_uint_2 = OpTypeArray %_arr_v3uint_uint_2 %uint_2
%_struct_4 = OpTypeStruct %_arr__arr_v3uint_uint_2_uint_2 %uint
%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
%5 = OpVariable %_ptr_Uniform__struct_4 Uniform
%1 = OpFunction %void None %7
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Structure id 4 decorated as Block for variable in Uniform storage "
"class must follow standard uniform buffer layout rules: member 1 at "
"offset 60 overlaps previous member ending at offset 63"));
}
} // anonymous namespace