mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-09-07 02:48:22 +00:00
1171 lines
46 KiB
C++
1171 lines
46 KiB
C++
// Copyright (c) 2026 Google Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "source/spirv_constant.h"
|
|
#include "source/spirv_target_env.h"
|
|
#include "source/util/hash_combine.h"
|
|
#include "source/val/validation_state.h"
|
|
|
|
namespace spvtools {
|
|
namespace val {
|
|
namespace {
|
|
|
|
enum class LayoutMode : uint8_t {
|
|
// Vulkan scalar block rules
|
|
kScalar,
|
|
// Vulkan standard alignment rules (i.e. std430)
|
|
kStandard,
|
|
// Vulkan extended alignment rules (i.e. std140)
|
|
kExtended,
|
|
};
|
|
|
|
std::ostream& operator<<(std::ostream& str, const LayoutMode& mode) {
|
|
switch (mode) {
|
|
case LayoutMode::kScalar:
|
|
str << "scalar";
|
|
break;
|
|
case LayoutMode::kStandard:
|
|
str << "standard";
|
|
break;
|
|
case LayoutMode::kExtended:
|
|
str << "extended";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
enum class LayoutRequirement : uint8_t {
|
|
// Must be laid out
|
|
kRequired,
|
|
// Must not be laid out
|
|
kProhibited,
|
|
// Either laid or not
|
|
kAllowed,
|
|
};
|
|
|
|
struct Impl {
|
|
ValidationState_t& vstate;
|
|
|
|
// Relevant information to describe a memory instruction for the purposes of
|
|
// layout validation.
|
|
struct MemoryReference {
|
|
// The data type of the memory instruction (after stripping any descriptor
|
|
// array).
|
|
uint32_t type_id = 0;
|
|
// The descriptor array type id (if there is one).
|
|
uint32_t descriptor_array_id = 0;
|
|
// The storage class for the memory instruction.
|
|
// Only used for error messages
|
|
spv::StorageClass storage_class;
|
|
// The layout mode (only relevant if a layout is required).
|
|
LayoutMode layout;
|
|
// The layout requirement for the instruction.
|
|
LayoutRequirement requirement;
|
|
// Whether it is an untyped pointer base.
|
|
bool untyped = false;
|
|
};
|
|
|
|
// Matrix constraints from a struct member to carry to the actual matrix type.
|
|
struct MatrixConstraints {
|
|
uint32_t stride = 0;
|
|
bool col_major = true;
|
|
};
|
|
|
|
// Cache valid checks for types that should have no layout.
|
|
std::unordered_set<uint32_t> no_layout_cache_;
|
|
|
|
// Struct member info
|
|
struct MemberInfo {
|
|
// Structure member index (note: index + 1 is instruction index).
|
|
uint32_t index;
|
|
// Whether or not an Offset/OffsetIdEXT decoration is present.
|
|
bool has_offset = false;
|
|
// Offset value. Max uint32_t is used for no evaluation (e.g. spec
|
|
// constant).
|
|
uint32_t offset = std::numeric_limits<uint32_t>::max();
|
|
// Whether or not RowMajor or ColMajor decoration is present.
|
|
bool has_matrix = false;
|
|
// Matrix constraints (stride and majorness).
|
|
MatrixConstraints matrix_constraints;
|
|
};
|
|
|
|
// Cache of structure member information.
|
|
std::unordered_map<uint32_t, std::vector<MemberInfo>> struct_members_;
|
|
|
|
struct LayoutKey {
|
|
uint32_t type_id = 0;
|
|
LayoutMode layout;
|
|
uint32_t incoming_offset = 0;
|
|
MatrixConstraints matrix_constraints{};
|
|
|
|
bool operator==(const LayoutKey& other) const {
|
|
return type_id == other.type_id && layout == other.layout &&
|
|
incoming_offset == other.incoming_offset &&
|
|
matrix_constraints.stride == other.matrix_constraints.stride &&
|
|
matrix_constraints.col_major == other.matrix_constraints.col_major;
|
|
}
|
|
};
|
|
struct LayoutKeyHash {
|
|
size_t operator()(const LayoutKey& key) const noexcept {
|
|
return spvtools::utils::hash_combine(
|
|
0, key.type_id, static_cast<uint32_t>(key.layout),
|
|
key.incoming_offset, key.matrix_constraints.stride,
|
|
key.matrix_constraints.col_major);
|
|
}
|
|
};
|
|
|
|
// Caches valid results of CheckLayout calls.
|
|
std::unordered_set<LayoutKey, LayoutKeyHash> layout_cache_;
|
|
|
|
// Returns the layout requirements for `sc`.
|
|
// Workgroup is expected to be explicitly laid out if `is_block` is true.
|
|
// UniformConstant is expected to be explicitly laid out if `descriptor_heap`
|
|
// is true.
|
|
LayoutRequirement GetStorageClassRequirement(spv::StorageClass sc,
|
|
bool is_block,
|
|
bool descriptor_heap) {
|
|
switch (sc) {
|
|
case spv::StorageClass::Workgroup:
|
|
return is_block ? LayoutRequirement::kRequired
|
|
: LayoutRequirement::kProhibited;
|
|
case spv::StorageClass::StorageBuffer:
|
|
case spv::StorageClass::Uniform:
|
|
case spv::StorageClass::PushConstant:
|
|
case spv::StorageClass::PhysicalStorageBuffer:
|
|
return LayoutRequirement::kRequired;
|
|
case spv::StorageClass::UniformConstant:
|
|
return descriptor_heap ? LayoutRequirement::kRequired
|
|
: LayoutRequirement::kProhibited;
|
|
case spv::StorageClass::Function:
|
|
case spv::StorageClass::Private:
|
|
return vstate.version() <= SPV_SPIRV_VERSION_WORD(1, 4)
|
|
? LayoutRequirement::kAllowed
|
|
: LayoutRequirement::kProhibited;
|
|
case spv::StorageClass::Input:
|
|
case spv::StorageClass::Output:
|
|
// Block is used generally and mesh shaders use Offset.
|
|
// TODO: This is a little over permissive.
|
|
return LayoutRequirement::kAllowed;
|
|
default:
|
|
// TODO: Some storage classes in ray tracing use explicit layout
|
|
// decorations, but it is not well documented which. For now treat
|
|
// other storage classes as allowed to be laid out. See Vulkan
|
|
// internal issue 4192.
|
|
return LayoutRequirement::kAllowed;
|
|
}
|
|
}
|
|
|
|
// Returns the layout rules for `sc`.
|
|
LayoutMode GetStorageClassLayout(spv::StorageClass sc, bool is_buffer_block) {
|
|
switch (sc) {
|
|
case spv::StorageClass::Workgroup:
|
|
return vstate.options()->workgroup_scalar_block_layout
|
|
? LayoutMode::kScalar
|
|
: LayoutMode::kStandard;
|
|
break;
|
|
case spv::StorageClass::StorageBuffer:
|
|
case spv::StorageClass::PushConstant:
|
|
case spv::StorageClass::UniformConstant:
|
|
case spv::StorageClass::PhysicalStorageBuffer:
|
|
return vstate.options()->scalar_block_layout ? LayoutMode::kScalar
|
|
: LayoutMode::kStandard;
|
|
break;
|
|
case spv::StorageClass::Uniform:
|
|
return vstate.options()->scalar_block_layout
|
|
? LayoutMode::kScalar
|
|
: ((is_buffer_block ||
|
|
vstate.options()->uniform_buffer_standard_layout)
|
|
? LayoutMode::kStandard
|
|
: LayoutMode::kExtended);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return LayoutMode::kStandard;
|
|
}
|
|
|
|
// Returns true if `inst` is a memory reference instruction
|
|
// Populates `reference` with the necessary information.
|
|
//
|
|
// The following are interesting memory references:
|
|
// For typed pointers:
|
|
// * OpVariable
|
|
// * Memory instructions on PhysicalStorageBuffer
|
|
// * OpBufferPointerEXT
|
|
// For untyped pointers:
|
|
// * All memory instructions
|
|
bool GetMemoryReference(const Instruction* inst, MemoryReference* reference) {
|
|
auto* type_inst = vstate.FindDef(inst->type_id());
|
|
if (inst->opcode() == spv::Op::OpVariable) {
|
|
auto sc = type_inst->GetOperandAs<spv::StorageClass>(1u);
|
|
const bool is_descriptor_heap = vstate.IsDescriptorHeapBaseVariable(inst);
|
|
reference->storage_class = sc;
|
|
reference->type_id = type_inst->GetOperandAs<uint32_t>(2u);
|
|
const bool is_block_decorated =
|
|
vstate.GetIdOpcode(reference->type_id) == spv::Op::OpTypeStruct &&
|
|
vstate.HasDecoration(reference->type_id, spv::Decoration::Block);
|
|
reference->requirement = GetStorageClassRequirement(
|
|
sc, is_block_decorated, is_descriptor_heap);
|
|
|
|
// Unwrap the descriptor array.
|
|
if (sc == spv::StorageClass::StorageBuffer ||
|
|
sc == spv::StorageClass::Uniform ||
|
|
sc == spv::StorageClass::UniformConstant) {
|
|
const auto* data_type = vstate.FindDef(reference->type_id);
|
|
if (data_type->opcode() == spv::Op::OpTypeArray ||
|
|
data_type->opcode() == spv::Op::OpTypeRuntimeArray) {
|
|
reference->descriptor_array_id = reference->type_id;
|
|
reference->type_id = data_type->GetOperandAs<uint32_t>(1u);
|
|
}
|
|
}
|
|
|
|
const bool buffer_block =
|
|
vstate.GetIdOpcode(reference->type_id) == spv::Op::OpTypeStruct &&
|
|
vstate.HasDecoration(reference->type_id,
|
|
spv::Decoration::BufferBlock);
|
|
reference->layout = GetStorageClassLayout(sc, buffer_block);
|
|
|
|
return true;
|
|
} else if (type_inst && type_inst->opcode() == spv::Op::OpTypePointer &&
|
|
type_inst->GetOperandAs<spv::StorageClass>(1u) ==
|
|
spv::StorageClass::PhysicalStorageBuffer) {
|
|
reference->storage_class = spv::StorageClass::PhysicalStorageBuffer;
|
|
reference->type_id = type_inst->GetOperandAs<uint32_t>(2u);
|
|
reference->layout = GetStorageClassLayout(
|
|
spv::StorageClass::PhysicalStorageBuffer, false);
|
|
reference->requirement = LayoutRequirement::kRequired;
|
|
|
|
return true;
|
|
} else if (inst->opcode() == spv::Op::OpBufferPointerEXT &&
|
|
type_inst->opcode() == spv::Op::OpTypePointer) {
|
|
auto sc = type_inst->GetOperandAs<spv::StorageClass>(1u);
|
|
reference->storage_class = sc;
|
|
uint32_t pointee_ty_id = type_inst->GetOperandAs<uint32_t>(2u);
|
|
const bool buffer_block =
|
|
vstate.GetIdOpcode(pointee_ty_id) == spv::Op::OpTypeStruct &&
|
|
vstate.HasDecoration(pointee_ty_id, spv::Decoration::BufferBlock);
|
|
reference->layout = GetStorageClassLayout(sc, buffer_block);
|
|
reference->requirement = LayoutRequirement::kRequired;
|
|
reference->type_id = pointee_ty_id;
|
|
|
|
return true;
|
|
} else if (vstate.HasCapability(spv::Capability::UntypedPointersKHR) &&
|
|
spvIsVulkanEnv(vstate.context()->target_env)) {
|
|
uint32_t ptr_ty_id = 0;
|
|
uint32_t data_ty_id = 0;
|
|
switch (inst->opcode()) {
|
|
case spv::Op::OpUntypedVariableKHR:
|
|
if (inst->operands().size() > 3) {
|
|
ptr_ty_id = inst->type_id();
|
|
data_ty_id = inst->GetOperandAs<uint32_t>(3u);
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
case spv::Op::OpUntypedAccessChainKHR:
|
|
case spv::Op::OpUntypedInBoundsAccessChainKHR:
|
|
case spv::Op::OpUntypedPtrAccessChainKHR:
|
|
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
|
|
ptr_ty_id = inst->type_id();
|
|
data_ty_id = inst->GetOperandAs<uint32_t>(2);
|
|
break;
|
|
case spv::Op::OpLoad:
|
|
if (vstate.GetIdOpcode(vstate.GetOperandTypeId(inst, 2)) ==
|
|
spv::Op::OpTypeUntypedPointerKHR) {
|
|
const auto ptr_id = inst->GetOperandAs<uint32_t>(2);
|
|
ptr_ty_id = vstate.FindDef(ptr_id)->type_id();
|
|
data_ty_id = inst->type_id();
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
case spv::Op::OpStore:
|
|
if (vstate.GetIdOpcode(vstate.GetOperandTypeId(inst, 0)) ==
|
|
spv::Op::OpTypeUntypedPointerKHR) {
|
|
const auto ptr_id = inst->GetOperandAs<uint32_t>(0);
|
|
ptr_ty_id = vstate.FindDef(ptr_id)->type_id();
|
|
data_ty_id = vstate.GetOperandTypeId(inst, 1);
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
case spv::Op::OpUntypedArrayLengthKHR:
|
|
ptr_ty_id =
|
|
vstate.FindDef(inst->GetOperandAs<uint32_t>(3))->type_id();
|
|
data_ty_id = inst->GetOperandAs<uint32_t>(2);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// If the data type is an array that contains a Block- or
|
|
// BufferBlock-decorated struct, then use the struct for layout checks
|
|
// instead of the array. In this case, the array represents a descriptor
|
|
// array which should not have an explicit layout.
|
|
const auto* data_type = vstate.FindDef(data_ty_id);
|
|
if (data_type->opcode() == spv::Op::OpTypeArray ||
|
|
data_type->opcode() == spv::Op::OpTypeRuntimeArray) {
|
|
uint32_t ele_ty_id = data_type->GetOperandAs<uint32_t>(1u);
|
|
if (vstate.HasDecoration(ele_ty_id, spv::Decoration::Block) ||
|
|
vstate.HasDecoration(ele_ty_id, spv::Decoration::BufferBlock)) {
|
|
reference->descriptor_array_id = data_ty_id;
|
|
data_ty_id = ele_ty_id;
|
|
}
|
|
}
|
|
|
|
auto sc = vstate.FindDef(ptr_ty_id)->GetOperandAs<spv::StorageClass>(1u);
|
|
reference->storage_class = sc;
|
|
reference->type_id = data_ty_id;
|
|
reference->requirement = LayoutRequirement::kRequired;
|
|
reference->untyped = true;
|
|
const bool buffer_block =
|
|
vstate.GetIdOpcode(data_ty_id) == spv::Op::OpTypeStruct &&
|
|
vstate.HasDecoration(data_ty_id, spv::Decoration::BufferBlock);
|
|
reference->layout = GetStorageClassLayout(sc, buffer_block);
|
|
|
|
return true;
|
|
} else if (inst->opcode() == spv::Op::OpAbortKHR) {
|
|
reference->type_id = inst->GetOperandAs<uint32_t>(0u);
|
|
// Abort messages doesn't have a storage class
|
|
reference->storage_class = spv::StorageClass::Max;
|
|
// Currently not specified well
|
|
// https://gitlab.khronos.org/spirv/SPIR-V/-/work_items/962
|
|
reference->layout = vstate.options()->scalar_block_layout
|
|
? LayoutMode::kScalar
|
|
: LayoutMode::kStandard;
|
|
reference->requirement = LayoutRequirement::kRequired;
|
|
return true;
|
|
}
|
|
|
|
// Not a memory reference.
|
|
return false;
|
|
}
|
|
|
|
// Checks that no instruction in the type tree of `type_id` has any explicit
|
|
// layout decorations.
|
|
spv_result_t CheckNoLayout(const Instruction* inst, uint32_t type_id,
|
|
spv::StorageClass sc) {
|
|
if (no_layout_cache_.count(type_id)) {
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
if (type_inst->opcode() == spv::Op::OpTypePointer) {
|
|
// PhysicalStorageBuffer and variable pointers can have ArrayStride
|
|
// decorations even if they are stored in a non-laid storage class (e.g.
|
|
// Function).
|
|
auto ptr_sc = type_inst->GetOperandAs<spv::StorageClass>(1u);
|
|
if (GetStorageClassRequirement(ptr_sc, true, false) !=
|
|
LayoutRequirement::kProhibited) {
|
|
return SPV_SUCCESS;
|
|
}
|
|
}
|
|
|
|
const auto& id_decs = vstate.id_decorations();
|
|
const auto iter = id_decs.find(type_id);
|
|
if (iter != id_decs.end()) {
|
|
for (const auto& d : iter->second) {
|
|
const spv::Decoration dec = d.dec_type();
|
|
if (dec == spv::Decoration::Block ||
|
|
dec == spv::Decoration::BufferBlock ||
|
|
dec == spv::Decoration::Offset ||
|
|
dec == spv::Decoration::OffsetIdEXT ||
|
|
dec == spv::Decoration::ArrayStride ||
|
|
dec == spv::Decoration::ArrayStrideIdEXT ||
|
|
dec == spv::Decoration::MatrixStride ||
|
|
dec == spv::Decoration::RowMajor ||
|
|
dec == spv::Decoration::ColMajor) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< vstate.VkErrorID(10684)
|
|
<< "Invalid explicit layout decorations on type "
|
|
<< vstate.getIdName(type_id) << ", the "
|
|
<< spvtools::StorageClassToString(sc)
|
|
<< " storage class has an explicit layout from the "
|
|
<< vstate.SpvDecorationString(dec) << " decoration";
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (type_inst->opcode()) {
|
|
case spv::Op::OpTypeStruct:
|
|
for (uint32_t i = 1; i < type_inst->operands().size(); i++) {
|
|
if (auto error = CheckNoLayout(
|
|
inst, type_inst->GetOperandAs<uint32_t>(i), sc)) {
|
|
return error;
|
|
}
|
|
}
|
|
break;
|
|
case spv::Op::OpTypeRuntimeArray:
|
|
case spv::Op::OpTypeArray:
|
|
if (auto error = CheckNoLayout(
|
|
inst, type_inst->GetOperandAs<uint32_t>(1u), sc)) {
|
|
return error;
|
|
}
|
|
break;
|
|
case spv::Op::OpTypePointer: {
|
|
auto ptr_sc = type_inst->GetOperandAs<spv::StorageClass>(1u);
|
|
if (auto error = CheckNoLayout(
|
|
inst, type_inst->GetOperandAs<uint32_t>(2u), ptr_sc)) {
|
|
return error;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
no_layout_cache_.insert(type_id);
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
// Returns true if type_id contains a matrix. Only looks through arrays.
|
|
bool ContainsMatrix(uint32_t type_id) {
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
switch (type_inst->opcode()) {
|
|
case spv::Op::OpTypeMatrix:
|
|
return true;
|
|
case spv::Op::OpTypeArray:
|
|
case spv::Op::OpTypeRuntimeArray:
|
|
return ContainsMatrix(type_inst->GetOperandAs<uint32_t>(1u));
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Gets the value for an id-based layout decoration (e.g. ArrayStrideIdEXT and
|
|
// OffsetIdEXT). Returns max uint32_t if the value cannot be evaluated (e.g.
|
|
// spec constant).
|
|
uint32_t GetIdDecorationValue(uint32_t id) {
|
|
const auto* inst = vstate.FindDef(id);
|
|
if (!spvOpcodeIsConstant(inst->opcode())) {
|
|
return std::numeric_limits<uint32_t>::max();
|
|
}
|
|
uint64_t value = 0;
|
|
if (vstate.EvalConstantValUint64(id, &value)) {
|
|
return static_cast<uint32_t>(value);
|
|
}
|
|
return std::numeric_limits<uint32_t>::max();
|
|
}
|
|
|
|
// Returns whether an array stride decoration is present on the array and its
|
|
// value.
|
|
std::pair<bool, uint32_t> GetArrayStride(uint32_t array_id) {
|
|
uint32_t stride = std::numeric_limits<uint32_t>::max();
|
|
bool has_stride = false;
|
|
for (auto& d : vstate.id_decorations(array_id)) {
|
|
if (d.dec_type() == spv::Decoration::ArrayStride) {
|
|
stride = d.params()[0];
|
|
has_stride = true;
|
|
break;
|
|
} else if (d.dec_type() == spv::Decoration::ArrayStrideIdEXT) {
|
|
stride = GetIdDecorationValue(d.params()[0]);
|
|
has_stride = true;
|
|
break;
|
|
}
|
|
}
|
|
return std::make_pair(has_stride, stride);
|
|
}
|
|
|
|
// Gets the offset value from an offset decoration.
|
|
uint32_t GetOffset(spv::Decoration dec, uint32_t param) {
|
|
// param is a literal value
|
|
if (dec == spv::Decoration::Offset) {
|
|
return param;
|
|
}
|
|
// param is an id
|
|
return GetIdDecorationValue(param);
|
|
}
|
|
|
|
// Returns true if value is aligned to align.
|
|
bool IsAlignedTo(uint32_t value, uint32_t align) {
|
|
if (align == 0) return value == 0;
|
|
return (value % align) == 0;
|
|
}
|
|
|
|
// Rounds up value to next multiple of align.
|
|
uint32_t AlignTo(uint32_t value, uint32_t align) {
|
|
return (value + align - 1) & ~(align - 1);
|
|
}
|
|
|
|
// A member is defined to improperly straddle if either of the following are
|
|
// true:
|
|
// - It is a vector with total size less than or equal to 16 bytes, and has
|
|
// Offset decorations placing its first byte at F and its last byte at L,
|
|
// where floor(F / 16) != floor(L / 16).
|
|
// - 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 offset, uint32_t size) {
|
|
const auto F = offset;
|
|
const auto L = offset + size - 1;
|
|
if (size <= 16) {
|
|
if ((F >> 4) != (L >> 4)) return true;
|
|
} else {
|
|
if (F % 16 != 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Used to map the option alignment to the Vulkan VUID
|
|
uint32_t GetAlignVkErrorId(spv::Op opcode) {
|
|
if (opcode == spv::Op::OpTypeSampler) {
|
|
return 11476;
|
|
} else if (opcode == spv::Op::OpTypeSampledImage ||
|
|
opcode == spv::Op::OpTypeImage) {
|
|
return 11477;
|
|
} else if (opcode == spv::Op::OpTypeBufferEXT) {
|
|
return 11478;
|
|
} else if (opcode == spv::Op::OpTypeAccelerationStructureKHR) {
|
|
return 11479;
|
|
} else if (opcode == spv::Op::OpTypeTensorARM) {
|
|
return 11480;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Returns the alignment for type_id for the given layout rules.
|
|
uint32_t GetAlign(uint32_t type_id, LayoutMode mode,
|
|
const MatrixConstraints& matrix_constraints,
|
|
bool allow_relaxed = true) {
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
uint32_t align = 1;
|
|
switch (type_inst->opcode()) {
|
|
case spv::Op::OpTypeSampledImage:
|
|
case spv::Op::OpTypeSampler:
|
|
case spv::Op::OpTypeImage:
|
|
if (vstate.HasCapability(spv::Capability::BindlessTextureNV)) {
|
|
return vstate.samplerimage_variable_address_mode() / 8;
|
|
}
|
|
if (type_inst->opcode() == spv::Op::OpTypeSampler) {
|
|
return vstate.options()->sampler_descriptor_layout.alignment;
|
|
}
|
|
if (type_inst->opcode() == spv::Op::OpTypeImage) {
|
|
return vstate.options()->image_descriptor_layout.alignment;
|
|
}
|
|
break;
|
|
case spv::Op::OpTypeBufferEXT:
|
|
case spv::Op::OpTypeAccelerationStructureKHR:
|
|
return vstate.options()->buffer_descriptor_layout.alignment;
|
|
case spv::Op::OpTypeInt:
|
|
case spv::Op::OpTypeFloat:
|
|
return type_inst->GetOperandAs<uint32_t>(1u) / 8;
|
|
case spv::Op::OpTypeVector:
|
|
case spv::Op::OpTypeVectorIdEXT: {
|
|
const auto ele_id = type_inst->GetOperandAs<uint32_t>(1u);
|
|
const auto num_eles = vstate.GetDimension(type_id);
|
|
align = GetAlign(ele_id, mode, {});
|
|
// Relaxed layout only applies to vectors as direct members of structs.
|
|
// Arrays and matrices are not relaxed.
|
|
if (mode == LayoutMode::kScalar ||
|
|
(allow_relaxed && vstate.IsRelaxedBlockLayout())) {
|
|
return align;
|
|
}
|
|
return align * ((num_eles == 3 || num_eles > 4) ? 4 : num_eles);
|
|
}
|
|
case spv::Op::OpTypeMatrix:
|
|
if (mode == LayoutMode::kScalar) {
|
|
const auto* vec_inst =
|
|
vstate.FindDef(type_inst->GetOperandAs<uint32_t>(1u));
|
|
const auto ele_id = vec_inst->GetOperandAs<uint32_t>(1u);
|
|
return GetAlign(ele_id, mode, {});
|
|
}
|
|
|
|
if (matrix_constraints.col_major) {
|
|
align = GetAlign(type_inst->GetOperandAs<uint32_t>(1u), mode, {},
|
|
/* allow_relaxed = */ false);
|
|
} 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_cols = type_inst->GetOperandAs<uint32_t>(2u);
|
|
const auto* col_inst =
|
|
vstate.FindDef(type_inst->GetOperandAs<uint32_t>(1u));
|
|
const auto ele_id = col_inst->GetOperandAs<uint32_t>(1u);
|
|
align = GetAlign(ele_id, mode, {});
|
|
// The equivalent vector may not exist so we replicate the vector rule
|
|
// here.
|
|
if (mode != LayoutMode::kScalar) {
|
|
align = align * (num_cols == 3 ? 4 : num_cols);
|
|
}
|
|
}
|
|
if (mode == LayoutMode::kExtended) {
|
|
align = AlignTo(align, 16u);
|
|
}
|
|
return align;
|
|
case spv::Op::OpTypeArray:
|
|
case spv::Op::OpTypeRuntimeArray:
|
|
align = GetAlign(type_inst->GetOperandAs<uint32_t>(1u), mode,
|
|
matrix_constraints, /* allow_relaxed = */ false);
|
|
if (mode == LayoutMode::kExtended) {
|
|
align = AlignTo(align, 16u);
|
|
}
|
|
return align;
|
|
case spv::Op::OpTypeStruct:
|
|
for (uint32_t i = 1; i < type_inst->operands().size(); i++) {
|
|
const auto member_id = type_inst->GetOperandAs<uint32_t>(i);
|
|
MatrixConstraints mat_constraints;
|
|
GetMatrixConstraints(type_id, i - 1, &mat_constraints);
|
|
align = std::max(align, GetAlign(member_id, mode, mat_constraints));
|
|
if (mode == LayoutMode::kExtended) {
|
|
align = AlignTo(align, 16u);
|
|
}
|
|
}
|
|
return align;
|
|
case spv::Op::OpTypePointer:
|
|
case spv::Op::OpTypeUntypedPointerKHR:
|
|
return vstate.pointer_size_and_alignment();
|
|
default:
|
|
break;
|
|
}
|
|
assert(0 && "unhandled type");
|
|
return 1;
|
|
}
|
|
|
|
// Returns the size of the given type.
|
|
uint32_t GetSize(uint32_t type_id,
|
|
const MatrixConstraints& matrix_constraints) {
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
switch (type_inst->opcode()) {
|
|
case spv::Op::OpTypeSampledImage:
|
|
case spv::Op::OpTypeSampler:
|
|
case spv::Op::OpTypeImage:
|
|
if (vstate.HasCapability(spv::Capability::BindlessTextureNV)) {
|
|
return vstate.samplerimage_variable_address_mode() / 8;
|
|
}
|
|
if (type_inst->opcode() == spv::Op::OpTypeSampler) {
|
|
return vstate.options()->sampler_descriptor_layout.size;
|
|
}
|
|
if (type_inst->opcode() == spv::Op::OpTypeImage) {
|
|
return vstate.options()->image_descriptor_layout.size;
|
|
}
|
|
break;
|
|
case spv::Op::OpTypeBufferEXT:
|
|
case spv::Op::OpTypeAccelerationStructureKHR:
|
|
return vstate.options()->buffer_descriptor_layout.size;
|
|
case spv::Op::OpTypeInt:
|
|
case spv::Op::OpTypeFloat:
|
|
return type_inst->GetOperandAs<uint32_t>(1u) / 8;
|
|
case spv::Op::OpTypeVector:
|
|
case spv::Op::OpTypeVectorIdEXT: {
|
|
const auto ele_id = type_inst->GetOperandAs<uint32_t>(1u);
|
|
const auto num_eles = vstate.GetDimension(type_id);
|
|
return GetSize(ele_id, {}) * num_eles;
|
|
}
|
|
case spv::Op::OpTypeArray: {
|
|
const auto count_id = type_inst->GetOperandAs<uint32_t>(2u);
|
|
uint64_t count = 0;
|
|
if (!vstate.EvalConstantValUint64(count_id, &count)) {
|
|
return 0;
|
|
}
|
|
const auto [has_stride, stride] = GetArrayStride(type_id);
|
|
const auto ele_size =
|
|
GetSize(type_inst->GetOperandAs<uint32_t>(1u), matrix_constraints);
|
|
// uint32 max is a marker for unevaluatable.
|
|
if (stride == std::numeric_limits<uint32_t>::max()) {
|
|
return ele_size;
|
|
}
|
|
return (static_cast<uint32_t>(count) - 1) * stride + ele_size;
|
|
}
|
|
case spv::Op::OpTypeRuntimeArray:
|
|
return 0;
|
|
case spv::Op::OpTypeMatrix: {
|
|
const auto num_cols = type_inst->GetOperandAs<uint32_t>(2u);
|
|
if (matrix_constraints.col_major) {
|
|
return num_cols * matrix_constraints.stride;
|
|
} else {
|
|
const auto* col_inst =
|
|
vstate.FindDef(type_inst->GetOperandAs<uint32_t>(1u));
|
|
const auto ele_id = col_inst->GetOperandAs<uint32_t>(1u);
|
|
const auto num_rows = col_inst->GetOperandAs<uint32_t>(2u);
|
|
return (num_rows - 1) * matrix_constraints.stride +
|
|
num_cols * GetSize(ele_id, {});
|
|
}
|
|
}
|
|
case spv::Op::OpTypeStruct: {
|
|
const auto& members = GetStructMembers(type_id);
|
|
if (members.empty()) return 0;
|
|
const auto& last = members.back();
|
|
if (last.offset == std::numeric_limits<uint32_t>::max()) {
|
|
return 0;
|
|
}
|
|
return last.offset +
|
|
GetSize(type_inst->GetOperandAs<uint32_t>(last.index + 1),
|
|
last.matrix_constraints);
|
|
}
|
|
case spv::Op::OpTypePointer:
|
|
case spv::Op::OpTypeUntypedPointerKHR:
|
|
return vstate.pointer_size_and_alignment();
|
|
default:
|
|
break;
|
|
}
|
|
assert(0 && "unhandled type");
|
|
return 0;
|
|
}
|
|
|
|
// Returns true if type_id has a matrix and populates matrix_constraints.
|
|
bool GetMatrixConstraints(uint32_t type_id, uint32_t index,
|
|
MatrixConstraints* matrix_constraints) {
|
|
bool has_matrix = false;
|
|
auto member_decorations = vstate.id_member_decorations(type_id, index);
|
|
for (auto decoration = member_decorations.begin;
|
|
decoration != member_decorations.end; ++decoration) {
|
|
if (decoration->dec_type() == spv::Decoration::ColMajor ||
|
|
decoration->dec_type() == spv::Decoration::RowMajor) {
|
|
has_matrix = true;
|
|
matrix_constraints->col_major =
|
|
decoration->dec_type() == spv::Decoration::ColMajor;
|
|
}
|
|
if (decoration->dec_type() == spv::Decoration::MatrixStride) {
|
|
matrix_constraints->stride = decoration->params()[0];
|
|
}
|
|
}
|
|
return has_matrix;
|
|
}
|
|
|
|
// Gathers (and caches) structure members and their decorations.
|
|
const std::vector<MemberInfo>& GetStructMembers(uint32_t type_id) {
|
|
if (struct_members_.count(type_id)) {
|
|
return struct_members_[type_id];
|
|
}
|
|
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
std::vector<MemberInfo> member_info;
|
|
member_info.reserve(type_inst->operands().size() - 1);
|
|
for (uint32_t i = 1; i < type_inst->operands().size(); i++) {
|
|
auto member_idx = i - 1;
|
|
auto member_decorations =
|
|
vstate.id_member_decorations(type_id, member_idx);
|
|
member_info.push_back(MemberInfo{member_idx});
|
|
auto& member = member_info.back();
|
|
member.has_matrix =
|
|
GetMatrixConstraints(type_id, member_idx, &member.matrix_constraints);
|
|
for (auto decoration = member_decorations.begin;
|
|
decoration != member_decorations.end; ++decoration) {
|
|
switch (decoration->dec_type()) {
|
|
case spv::Decoration::Offset:
|
|
case spv::Decoration::OffsetIdEXT:
|
|
member.has_offset = true;
|
|
member.offset =
|
|
GetOffset(decoration->dec_type(), decoration->params()[0]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Sort by offset value.
|
|
std::stable_sort(member_info.begin(), member_info.end(),
|
|
[](const MemberInfo& lhs, const MemberInfo& rhs) {
|
|
return lhs.offset < rhs.offset;
|
|
});
|
|
struct_members_[type_id] = std::move(member_info);
|
|
return struct_members_[type_id];
|
|
}
|
|
|
|
// Returns some common messaging to improve diagnostics.
|
|
std::string CommonError(const Instruction* inst,
|
|
spv::StorageClass storage_class, LayoutMode mode) {
|
|
std::string s;
|
|
std::ostringstream str(s);
|
|
str << " Instantiated via " << vstate.getIdName(inst->id());
|
|
// OpAbortKHR is an example where there is not storage class
|
|
if (storage_class != spv::StorageClass::Max) {
|
|
str << " in the " << spvtools::StorageClassToString(storage_class)
|
|
<< " storage class";
|
|
}
|
|
str << " using " << mode << " layout rules.";
|
|
if (mode != LayoutMode::kScalar) {
|
|
if (storage_class == spv::StorageClass::Workgroup) {
|
|
str << vstate.MissingFeature(
|
|
"workgroupMemoryExplicitLayoutScalarBlockLayout feature",
|
|
"--workgroup-scalar-block-layout", true);
|
|
} else if (!vstate.IsRelaxedBlockLayout()) {
|
|
str << vstate.MissingFeature("VK_KHR_relaxed_block_layout extension",
|
|
"--relax-block-layout", true);
|
|
} else if (storage_class == spv::StorageClass::Uniform &&
|
|
mode != LayoutMode::kStandard) {
|
|
str << vstate.MissingFeature("uniformBufferStandardLayout feature",
|
|
"--uniform-buffer-standard-layout", true);
|
|
} else {
|
|
str << vstate.MissingFeature("scalarBlockLayout feature",
|
|
"--scalar-block-layout", true);
|
|
}
|
|
}
|
|
return str.str();
|
|
}
|
|
|
|
// Checks struct layouts
|
|
// * Each member must
|
|
// * Have an offset decoration
|
|
// * If the member is a matrix or array(s) of matrices
|
|
// * Must have majorness and matrix stride decorations
|
|
// * If member is a runtime array, it must be last by offset
|
|
// * Offset must be aligned
|
|
// * Total offset must be aligned
|
|
// * Offset + size < next elements offset
|
|
// * If member is a vector, it must have a valid straddle
|
|
// * The member has a valid layout
|
|
spv_result_t CheckStructLayout(const Instruction* inst, uint32_t type_id,
|
|
spv::StorageClass storage_class,
|
|
LayoutMode mode, uint32_t incoming_offset,
|
|
const MatrixConstraints& matrix_constraints) {
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
const auto& member_info = GetStructMembers(type_id);
|
|
for (auto& member : member_info) {
|
|
const auto member_id =
|
|
type_inst->GetOperandAs<uint32_t>(member.index + 1);
|
|
if (!member.has_offset) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Structure member " << member.index
|
|
<< " must be explicitly laid out with Offset or OffsetIdEXT "
|
|
"decorations."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
if (ContainsMatrix(member_id)) {
|
|
if (!member.has_matrix) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Structure member " << member.index
|
|
<< " containing a matrix must be explicitly laid out "
|
|
"with RowMajor or ColMajor decorations."
|
|
<< CommonError(inst, storage_class, mode);
|
|
} else if (member.matrix_constraints.stride == 0) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Structure member " << member.index
|
|
<< " containing a matrix must be explicitly laid out "
|
|
"with MatrixStride decorations."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t next_offset = 0;
|
|
uint32_t ordered_index = 0;
|
|
for (const auto& member : member_info) {
|
|
auto member_idx = member.index;
|
|
auto offset = member.offset;
|
|
// We have no information
|
|
if (offset == std::numeric_limits<uint32_t>::max()) {
|
|
continue;
|
|
}
|
|
auto member_id = type_inst->GetOperandAs<uint32_t>(member_idx + 1);
|
|
auto member_inst = vstate.FindDef(member_id);
|
|
if (member_inst->opcode() == spv::Op::OpTypeRuntimeArray &&
|
|
ordered_index != member_info.size() - 1) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< vstate.VkErrorID(4680)
|
|
<< "Structure has a runtime array at offset " << offset
|
|
<< ", but other members at larger offsets."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
ordered_index++;
|
|
|
|
MatrixConstraints mat_constraints = matrix_constraints;
|
|
if (member.has_matrix) {
|
|
mat_constraints = member.matrix_constraints;
|
|
}
|
|
uint32_t align = GetAlign(member_id, mode, mat_constraints);
|
|
if (!IsAlignedTo(offset, align)) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< vstate.VkErrorID(GetAlignVkErrorId(member_inst->opcode()))
|
|
<< "Structure member " << member_idx << " at offset " << offset
|
|
<< " is not aligned to " << align << "."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
if (!IsAlignedTo(offset + incoming_offset, align)) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< vstate.VkErrorID(GetAlignVkErrorId(member_inst->opcode()))
|
|
<< "Structure member " << member_idx << " at offset " << offset
|
|
<< " plus incoming offset " << incoming_offset
|
|
<< " is not aligned to " << align << "."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
if (offset < next_offset) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Structure member " << member_idx << " at offset " << offset
|
|
<< " overlaps previous member ending at offset "
|
|
<< next_offset - 1 << "."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
if (mode != LayoutMode::kScalar && vstate.IsRelaxedBlockLayout()) {
|
|
if (member_inst->opcode() == spv::Op::OpTypeVector ||
|
|
member_inst->opcode() == spv::Op::OpTypeVectorIdEXT) {
|
|
uint32_t size = GetSize(member_id, {});
|
|
if (HasImproperStraddle(incoming_offset + offset, size)) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Structure member " << member_idx
|
|
<< ": Vector has improper straddle due to offset "
|
|
<< incoming_offset + offset << "."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
}
|
|
}
|
|
if (auto error = CheckLayout(inst, member_id, storage_class, mode,
|
|
incoming_offset + offset, mat_constraints)) {
|
|
return error;
|
|
}
|
|
|
|
uint32_t size = GetSize(member_id, mat_constraints);
|
|
next_offset = size + offset;
|
|
if (mode != LayoutMode::kScalar &&
|
|
(member_inst->opcode() == spv::Op::OpTypeArray ||
|
|
member_inst->opcode() == spv::Op::OpTypeStruct)) {
|
|
next_offset = AlignTo(next_offset, align);
|
|
}
|
|
}
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
// Checks array layouts
|
|
// * Arrays must have stride decoration
|
|
// * Stride must be non-zero
|
|
// * Stride must be aligned
|
|
// * Stride must be greater or equal to element size
|
|
// * Elements have valid layout
|
|
spv_result_t CheckArrayLayout(const Instruction* inst, uint32_t type_id,
|
|
spv::StorageClass storage_class,
|
|
LayoutMode mode, uint32_t incoming_offset,
|
|
const MatrixConstraints& matrix_constraints) {
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
auto [has_stride, stride] = GetArrayStride(type_id);
|
|
if (!has_stride) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Array must be explicitly laid out with ArrayStride or "
|
|
"ArrayStrideIdEXT decorations."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
uint32_t ele_id = type_inst->GetOperandAs<uint32_t>(1u);
|
|
uint32_t ele_size = GetSize(ele_id, matrix_constraints);
|
|
uint32_t align = GetAlign(type_id, mode, matrix_constraints);
|
|
if (stride == 0) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Array must not have a stride of 0."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
|
|
// uint32 max stride is unevaluatable (e.g. spec constant).
|
|
if (stride == std::numeric_limits<uint32_t>::max()) {
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
if (!IsAlignedTo(stride, align)) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Array stride " << stride << " must satisfy alignment " << align
|
|
<< "." << CommonError(inst, storage_class, mode);
|
|
}
|
|
if (stride != std::numeric_limits<uint32_t>::max() && stride < ele_size) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Array stride " << stride
|
|
<< " is smaller than element type size " << ele_size << "."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
|
|
uint32_t num_elements = 0;
|
|
if (type_inst->opcode() == spv::Op::OpTypeArray) {
|
|
uint64_t count = 0;
|
|
if (vstate.EvalConstantValUint64(type_inst->GetOperandAs<uint32_t>(2u),
|
|
&count)) {
|
|
num_elements = static_cast<uint32_t>(count);
|
|
}
|
|
}
|
|
num_elements = std::max(1u, num_elements);
|
|
std::vector<bool> seen(16, false);
|
|
for (uint32_t i = 0; i < num_elements; ++i) {
|
|
uint32_t next_offset = i * stride + incoming_offset;
|
|
// Stop checking if offsets repeat in terms of 16-byte multiples.
|
|
if (seen[next_offset % 16]) {
|
|
break;
|
|
}
|
|
|
|
if (auto error = CheckLayout(inst, ele_id, storage_class, mode,
|
|
next_offset, matrix_constraints)) {
|
|
return error;
|
|
}
|
|
|
|
seen[next_offset % 16] = true;
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
// Checks the layout matrices
|
|
// * Stride must be a multiple of align
|
|
// * Stride must be greater or equal to minor size
|
|
spv_result_t CheckMatrixLayout(const Instruction* inst, uint32_t type_id,
|
|
spv::StorageClass storage_class,
|
|
LayoutMode mode,
|
|
const MatrixConstraints& matrix_constraints) {
|
|
// We already checked that any struct containing a matrix has a non-zero
|
|
// stride so if we have 0 stride here then it will come from the result
|
|
// of an access chain or other instruction. Other rules are meant to
|
|
// catch any misuse we can skip it here.
|
|
if (matrix_constraints.stride == 0) {
|
|
return SPV_SUCCESS;
|
|
}
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
const auto align = GetAlign(type_id, mode, matrix_constraints);
|
|
if (!IsAlignedTo(matrix_constraints.stride, align)) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Matrix with a stride " << matrix_constraints.stride
|
|
<< " not satisfying alignment to " << align << "."
|
|
<< CommonError(inst, storage_class, mode) << "\n";
|
|
}
|
|
const auto ele_id = type_inst->GetOperandAs<uint32_t>(1u);
|
|
uint32_t size = 0;
|
|
if (matrix_constraints.col_major) {
|
|
size = GetSize(ele_id, {});
|
|
} else {
|
|
// Element size is # cols * ele size.
|
|
const auto* ele_inst = vstate.FindDef(ele_id);
|
|
const auto scalar_id = ele_inst->GetOperandAs<uint32_t>(1u);
|
|
size = GetSize(scalar_id, {}) * type_inst->GetOperandAs<uint32_t>(2u);
|
|
}
|
|
if (matrix_constraints.stride < size) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, type_inst)
|
|
<< "Matrix stride " << matrix_constraints.stride
|
|
<< " is smaller than column size " << size << "."
|
|
<< CommonError(inst, storage_class, mode);
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
// Returns true if type_id satisfies the given layout rules.
|
|
spv_result_t CheckLayout(const Instruction* inst, uint32_t type_id,
|
|
spv::StorageClass storage_class, LayoutMode mode,
|
|
uint32_t incoming_offset,
|
|
const MatrixConstraints& matrix_constraints) {
|
|
if (vstate.options()->skip_block_layout) {
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
LayoutKey key{type_id, mode, incoming_offset, matrix_constraints};
|
|
if (layout_cache_.count(key)) {
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
const auto* type_inst = vstate.FindDef(type_id);
|
|
switch (type_inst->opcode()) {
|
|
case spv::Op::OpTypeStruct:
|
|
if (auto error =
|
|
CheckStructLayout(inst, type_id, storage_class, mode,
|
|
incoming_offset, matrix_constraints)) {
|
|
return error;
|
|
}
|
|
break;
|
|
case spv::Op::OpTypeArray:
|
|
case spv::Op::OpTypeRuntimeArray:
|
|
if (auto error =
|
|
CheckArrayLayout(inst, type_id, storage_class, mode,
|
|
incoming_offset, matrix_constraints)) {
|
|
return error;
|
|
}
|
|
break;
|
|
case spv::Op::OpTypeMatrix:
|
|
if (auto error = CheckMatrixLayout(inst, type_id, storage_class, mode,
|
|
matrix_constraints)) {
|
|
return error;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
layout_cache_.insert(key);
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
// Performs a single pass over the IR and for each memory reference determines
|
|
// what validation is necessary.
|
|
spv_result_t Run() {
|
|
if (!spvIsVulkanEnv(vstate.context()->target_env)) {
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
for (const auto& inst : vstate.ordered_instructions()) {
|
|
MemoryReference reference;
|
|
if (!GetMemoryReference(&inst, &reference)) {
|
|
continue;
|
|
}
|
|
|
|
// Descriptor arrays shouldn't have a stride. Check for that here since
|
|
// most descriptors require a layout.
|
|
if (reference.descriptor_array_id != 0) {
|
|
const bool array_stride = vstate.HasDecoration(
|
|
reference.descriptor_array_id, spv::Decoration::ArrayStride);
|
|
const bool array_stride_id = vstate.HasDecoration(
|
|
reference.descriptor_array_id, spv::Decoration::ArrayStrideIdEXT);
|
|
if (array_stride || array_stride_id) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
|
|
<< vstate.VkErrorID(10684)
|
|
<< "Invalid explicit layout decorations on type "
|
|
<< vstate.getIdName(reference.descriptor_array_id) << ", the "
|
|
<< spvtools::StorageClassToString(reference.storage_class)
|
|
<< " storage class has an explicit layout from the "
|
|
<< vstate.SpvDecorationString(
|
|
array_stride ? spv::Decoration::ArrayStride
|
|
: spv::Decoration::ArrayStrideIdEXT)
|
|
<< " decoration";
|
|
}
|
|
}
|
|
|
|
// Untyped pointers require a layout. Workgroup variables must be blocks
|
|
// to have a layout.
|
|
if (reference.untyped &&
|
|
reference.storage_class == spv::StorageClass::Workgroup &&
|
|
inst.opcode() == spv::Op::OpUntypedVariableKHR &&
|
|
(vstate.GetIdOpcode(reference.type_id) != spv::Op::OpTypeStruct ||
|
|
!vstate.HasDecoration(reference.type_id, spv::Decoration::Block))) {
|
|
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
|
|
<< vstate.VkErrorID(10684)
|
|
<< "Untyped variables in Workgroup storage class must be "
|
|
"block-decorated structs";
|
|
}
|
|
|
|
if (reference.requirement == LayoutRequirement::kRequired) {
|
|
if (auto error =
|
|
CheckLayout(&inst, reference.type_id, reference.storage_class,
|
|
reference.layout, 0, {})) {
|
|
return error;
|
|
}
|
|
} else if (reference.requirement == LayoutRequirement::kProhibited) {
|
|
if (auto error = CheckNoLayout(&inst, reference.type_id,
|
|
reference.storage_class)) {
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
spv_result_t ValidateExplicitLayout(ValidationState_t& vstate) {
|
|
return Impl{vstate}.Run();
|
|
}
|
|
|
|
} // namespace val
|
|
} // namespace spvtools
|