SPIRV-Tools/source/val/validation_state.cpp
sfricke-samsung a61d07a727
spirv-val: Fix SPV_KHR_fragment_shading_rate VUID label (#4014)
in 3943 the new tests should have failed, but didn't due to the AnyVUID
check not handling case with trailing whitespace. Added trimming to handle
it as easily might happen again in future
2020-11-05 13:04:47 -05:00

1514 lines
47 KiB
C++

// Copyright (c) 2015-2016 The Khronos Group 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 "source/val/validation_state.h"
#include <cassert>
#include <stack>
#include <utility>
#include "source/opcode.h"
#include "source/spirv_constant.h"
#include "source/spirv_target_env.h"
#include "source/val/basic_block.h"
#include "source/val/construct.h"
#include "source/val/function.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
namespace val {
namespace {
ModuleLayoutSection InstructionLayoutSection(
ModuleLayoutSection current_section, SpvOp op) {
// See Section 2.4
if (spvOpcodeGeneratesType(op) || spvOpcodeIsConstant(op))
return kLayoutTypes;
switch (op) {
case SpvOpCapability:
return kLayoutCapabilities;
case SpvOpExtension:
return kLayoutExtensions;
case SpvOpExtInstImport:
return kLayoutExtInstImport;
case SpvOpMemoryModel:
return kLayoutMemoryModel;
case SpvOpEntryPoint:
return kLayoutEntryPoint;
case SpvOpExecutionMode:
case SpvOpExecutionModeId:
return kLayoutExecutionMode;
case SpvOpSourceContinued:
case SpvOpSource:
case SpvOpSourceExtension:
case SpvOpString:
return kLayoutDebug1;
case SpvOpName:
case SpvOpMemberName:
return kLayoutDebug2;
case SpvOpModuleProcessed:
return kLayoutDebug3;
case SpvOpDecorate:
case SpvOpMemberDecorate:
case SpvOpGroupDecorate:
case SpvOpGroupMemberDecorate:
case SpvOpDecorationGroup:
case SpvOpDecorateId:
case SpvOpDecorateStringGOOGLE:
case SpvOpMemberDecorateStringGOOGLE:
return kLayoutAnnotations;
case SpvOpTypeForwardPointer:
return kLayoutTypes;
case SpvOpVariable:
if (current_section == kLayoutTypes) return kLayoutTypes;
return kLayoutFunctionDefinitions;
case SpvOpExtInst:
// SpvOpExtInst is only allowed in types section for certain extended
// instruction sets. This will be checked separately.
if (current_section == kLayoutTypes) return kLayoutTypes;
return kLayoutFunctionDefinitions;
case SpvOpLine:
case SpvOpNoLine:
case SpvOpUndef:
if (current_section == kLayoutTypes) return kLayoutTypes;
return kLayoutFunctionDefinitions;
case SpvOpFunction:
case SpvOpFunctionParameter:
case SpvOpFunctionEnd:
if (current_section == kLayoutFunctionDeclarations)
return kLayoutFunctionDeclarations;
return kLayoutFunctionDefinitions;
default:
break;
}
return kLayoutFunctionDefinitions;
}
bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) {
return layout == InstructionLayoutSection(layout, op);
}
// Counts the number of instructions and functions in the file.
spv_result_t CountInstructions(void* user_data,
const spv_parsed_instruction_t* inst) {
ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
if (inst->opcode == SpvOpFunction) _.increment_total_functions();
_.increment_total_instructions();
return SPV_SUCCESS;
}
spv_result_t setHeader(void* user_data, spv_endianness_t, uint32_t,
uint32_t version, uint32_t generator, uint32_t id_bound,
uint32_t) {
ValidationState_t& vstate =
*(reinterpret_cast<ValidationState_t*>(user_data));
vstate.setIdBound(id_bound);
vstate.setGenerator(generator);
vstate.setVersion(version);
return SPV_SUCCESS;
}
// Add features based on SPIR-V core version number.
void UpdateFeaturesBasedOnSpirvVersion(ValidationState_t::Feature* features,
uint32_t version) {
assert(features);
if (version >= SPV_SPIRV_VERSION_WORD(1, 4)) {
features->select_between_composites = true;
features->copy_memory_permits_two_memory_accesses = true;
features->uconvert_spec_constant_op = true;
features->nonwritable_var_in_function_or_private = true;
}
}
} // namespace
ValidationState_t::ValidationState_t(const spv_const_context ctx,
const spv_const_validator_options opt,
const uint32_t* words,
const size_t num_words,
const uint32_t max_warnings)
: context_(ctx),
options_(opt),
words_(words),
num_words_(num_words),
unresolved_forward_ids_{},
operand_names_{},
current_layout_section_(kLayoutCapabilities),
module_functions_(),
module_capabilities_(),
module_extensions_(),
ordered_instructions_(),
all_definitions_(),
global_vars_(),
local_vars_(),
struct_nesting_depth_(),
struct_has_nested_blockorbufferblock_struct_(),
grammar_(ctx),
addressing_model_(SpvAddressingModelMax),
memory_model_(SpvMemoryModelMax),
pointer_size_and_alignment_(0),
in_function_(false),
num_of_warnings_(0),
max_num_of_warnings_(max_warnings) {
assert(opt && "Validator options may not be Null.");
const auto env = context_->target_env;
if (spvIsVulkanEnv(env)) {
// Vulkan 1.1 includes VK_KHR_relaxed_block_layout in core.
if (env != SPV_ENV_VULKAN_1_0) {
features_.env_relaxed_block_layout = true;
}
}
// Only attempt to count if we have words, otherwise let the other validation
// fail and generate an error.
if (num_words > 0) {
// Count the number of instructions in the binary.
// This parse should not produce any error messages. Hijack the context and
// replace the message consumer so that we do not pollute any state in input
// consumer.
spv_context_t hijacked_context = *ctx;
hijacked_context.consumer = [](spv_message_level_t, const char*,
const spv_position_t&, const char*) {};
spvBinaryParse(&hijacked_context, this, words, num_words, setHeader,
CountInstructions,
/* diagnostic = */ nullptr);
preallocateStorage();
}
UpdateFeaturesBasedOnSpirvVersion(&features_, version_);
friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
context_, words_, num_words_);
name_mapper_ = friendly_mapper_->GetNameMapper();
}
void ValidationState_t::preallocateStorage() {
ordered_instructions_.reserve(total_instructions_);
module_functions_.reserve(total_functions_);
}
spv_result_t ValidationState_t::ForwardDeclareId(uint32_t id) {
unresolved_forward_ids_.insert(id);
return SPV_SUCCESS;
}
spv_result_t ValidationState_t::RemoveIfForwardDeclared(uint32_t id) {
unresolved_forward_ids_.erase(id);
return SPV_SUCCESS;
}
spv_result_t ValidationState_t::RegisterForwardPointer(uint32_t id) {
forward_pointer_ids_.insert(id);
return SPV_SUCCESS;
}
bool ValidationState_t::IsForwardPointer(uint32_t id) const {
return (forward_pointer_ids_.find(id) != forward_pointer_ids_.end());
}
void ValidationState_t::AssignNameToId(uint32_t id, std::string name) {
operand_names_[id] = name;
}
std::string ValidationState_t::getIdName(uint32_t id) const {
const std::string id_name = name_mapper_(id);
std::stringstream out;
out << id << "[%" << id_name << "]";
return out.str();
}
size_t ValidationState_t::unresolved_forward_id_count() const {
return unresolved_forward_ids_.size();
}
std::vector<uint32_t> ValidationState_t::UnresolvedForwardIds() const {
std::vector<uint32_t> out(std::begin(unresolved_forward_ids_),
std::end(unresolved_forward_ids_));
return out;
}
bool ValidationState_t::IsDefinedId(uint32_t id) const {
return all_definitions_.find(id) != std::end(all_definitions_);
}
const Instruction* ValidationState_t::FindDef(uint32_t id) const {
auto it = all_definitions_.find(id);
if (it == all_definitions_.end()) return nullptr;
return it->second;
}
Instruction* ValidationState_t::FindDef(uint32_t id) {
auto it = all_definitions_.find(id);
if (it == all_definitions_.end()) return nullptr;
return it->second;
}
ModuleLayoutSection ValidationState_t::current_layout_section() const {
return current_layout_section_;
}
void ValidationState_t::ProgressToNextLayoutSectionOrder() {
// Guard against going past the last element(kLayoutFunctionDefinitions)
if (current_layout_section_ <= kLayoutFunctionDefinitions) {
current_layout_section_ =
static_cast<ModuleLayoutSection>(current_layout_section_ + 1);
}
}
bool ValidationState_t::IsOpcodeInPreviousLayoutSection(SpvOp op) {
ModuleLayoutSection section =
InstructionLayoutSection(current_layout_section_, op);
return section < current_layout_section_;
}
bool ValidationState_t::IsOpcodeInCurrentLayoutSection(SpvOp op) {
return IsInstructionInLayoutSection(current_layout_section_, op);
}
DiagnosticStream ValidationState_t::diag(spv_result_t error_code,
const Instruction* inst) {
if (error_code == SPV_WARNING) {
if (num_of_warnings_ == max_num_of_warnings_) {
DiagnosticStream({0, 0, 0}, context_->consumer, "", error_code)
<< "Other warnings have been suppressed.\n";
}
if (num_of_warnings_ >= max_num_of_warnings_) {
return DiagnosticStream({0, 0, 0}, nullptr, "", error_code);
}
++num_of_warnings_;
}
std::string disassembly;
if (inst) disassembly = Disassemble(*inst);
return DiagnosticStream({0, 0, inst ? inst->LineNum() : 0},
context_->consumer, disassembly, error_code);
}
std::vector<Function>& ValidationState_t::functions() {
return module_functions_;
}
Function& ValidationState_t::current_function() {
assert(in_function_body());
return module_functions_.back();
}
const Function& ValidationState_t::current_function() const {
assert(in_function_body());
return module_functions_.back();
}
const Function* ValidationState_t::function(uint32_t id) const {
const auto it = id_to_function_.find(id);
if (it == id_to_function_.end()) return nullptr;
return it->second;
}
Function* ValidationState_t::function(uint32_t id) {
auto it = id_to_function_.find(id);
if (it == id_to_function_.end()) return nullptr;
return it->second;
}
bool ValidationState_t::in_function_body() const { return in_function_; }
bool ValidationState_t::in_block() const {
return module_functions_.empty() == false &&
module_functions_.back().current_block() != nullptr;
}
void ValidationState_t::RegisterCapability(SpvCapability cap) {
// Avoid redundant work. Otherwise the recursion could induce work
// quadrdatic in the capability dependency depth. (Ok, not much, but
// it's something.)
if (module_capabilities_.Contains(cap)) return;
module_capabilities_.Add(cap);
spv_operand_desc desc;
if (SPV_SUCCESS ==
grammar_.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, cap, &desc)) {
CapabilitySet(desc->numCapabilities, desc->capabilities)
.ForEach([this](SpvCapability c) { RegisterCapability(c); });
}
switch (cap) {
case SpvCapabilityKernel:
features_.group_ops_reduce_and_scans = true;
break;
case SpvCapabilityInt8:
features_.use_int8_type = true;
features_.declare_int8_type = true;
break;
case SpvCapabilityStorageBuffer8BitAccess:
case SpvCapabilityUniformAndStorageBuffer8BitAccess:
case SpvCapabilityStoragePushConstant8:
features_.declare_int8_type = true;
break;
case SpvCapabilityInt16:
features_.declare_int16_type = true;
break;
case SpvCapabilityFloat16:
case SpvCapabilityFloat16Buffer:
features_.declare_float16_type = true;
break;
case SpvCapabilityStorageUniformBufferBlock16:
case SpvCapabilityStorageUniform16:
case SpvCapabilityStoragePushConstant16:
case SpvCapabilityStorageInputOutput16:
features_.declare_int16_type = true;
features_.declare_float16_type = true;
features_.free_fp_rounding_mode = true;
break;
case SpvCapabilityVariablePointers:
features_.variable_pointers = true;
features_.variable_pointers_storage_buffer = true;
break;
case SpvCapabilityVariablePointersStorageBuffer:
features_.variable_pointers_storage_buffer = true;
break;
default:
break;
}
}
void ValidationState_t::RegisterExtension(Extension ext) {
if (module_extensions_.Contains(ext)) return;
module_extensions_.Add(ext);
switch (ext) {
case kSPV_AMD_gpu_shader_half_float:
case kSPV_AMD_gpu_shader_half_float_fetch:
// SPV_AMD_gpu_shader_half_float enables float16 type.
// https://github.com/KhronosGroup/SPIRV-Tools/issues/1375
features_.declare_float16_type = true;
break;
case kSPV_AMD_gpu_shader_int16:
// This is not yet in the extension, but it's recommended for it.
// See https://github.com/KhronosGroup/glslang/issues/848
features_.uconvert_spec_constant_op = true;
break;
case kSPV_AMD_shader_ballot:
// The grammar doesn't encode the fact that SPV_AMD_shader_ballot
// enables the use of group operations Reduce, InclusiveScan,
// and ExclusiveScan. Enable it manually.
// https://github.com/KhronosGroup/SPIRV-Tools/issues/991
features_.group_ops_reduce_and_scans = true;
break;
default:
break;
}
}
bool ValidationState_t::HasAnyOfCapabilities(
const CapabilitySet& capabilities) const {
return module_capabilities_.HasAnyOf(capabilities);
}
bool ValidationState_t::HasAnyOfExtensions(
const ExtensionSet& extensions) const {
return module_extensions_.HasAnyOf(extensions);
}
void ValidationState_t::set_addressing_model(SpvAddressingModel am) {
addressing_model_ = am;
switch (am) {
case SpvAddressingModelPhysical32:
pointer_size_and_alignment_ = 4;
break;
default:
// fall through
case SpvAddressingModelPhysical64:
case SpvAddressingModelPhysicalStorageBuffer64EXT:
pointer_size_and_alignment_ = 8;
break;
}
}
SpvAddressingModel ValidationState_t::addressing_model() const {
return addressing_model_;
}
void ValidationState_t::set_memory_model(SpvMemoryModel mm) {
memory_model_ = mm;
}
SpvMemoryModel ValidationState_t::memory_model() const { return memory_model_; }
spv_result_t ValidationState_t::RegisterFunction(
uint32_t id, uint32_t ret_type_id, SpvFunctionControlMask function_control,
uint32_t function_type_id) {
assert(in_function_body() == false &&
"RegisterFunction can only be called when parsing the binary outside "
"of another function");
in_function_ = true;
module_functions_.emplace_back(id, ret_type_id, function_control,
function_type_id);
id_to_function_.emplace(id, &current_function());
// TODO(umar): validate function type and type_id
return SPV_SUCCESS;
}
spv_result_t ValidationState_t::RegisterFunctionEnd() {
assert(in_function_body() == true &&
"RegisterFunctionEnd can only be called when parsing the binary "
"inside of another function");
assert(in_block() == false &&
"RegisterFunctionParameter can only be called when parsing the binary "
"ouside of a block");
current_function().RegisterFunctionEnd();
in_function_ = false;
return SPV_SUCCESS;
}
Instruction* ValidationState_t::AddOrderedInstruction(
const spv_parsed_instruction_t* inst) {
ordered_instructions_.emplace_back(inst);
ordered_instructions_.back().SetLineNum(ordered_instructions_.size());
return &ordered_instructions_.back();
}
// Improves diagnostic messages by collecting names of IDs
void ValidationState_t::RegisterDebugInstruction(const Instruction* inst) {
switch (inst->opcode()) {
case SpvOpName: {
const auto target = inst->GetOperandAs<uint32_t>(0);
const auto* str = reinterpret_cast<const char*>(inst->words().data() +
inst->operand(1).offset);
AssignNameToId(target, str);
break;
}
case SpvOpMemberName: {
const auto target = inst->GetOperandAs<uint32_t>(0);
const auto* str = reinterpret_cast<const char*>(inst->words().data() +
inst->operand(2).offset);
AssignNameToId(target, str);
break;
}
case SpvOpSourceContinued:
case SpvOpSource:
case SpvOpSourceExtension:
case SpvOpString:
case SpvOpLine:
case SpvOpNoLine:
default:
break;
}
}
void ValidationState_t::RegisterInstruction(Instruction* inst) {
if (inst->id()) all_definitions_.insert(std::make_pair(inst->id(), inst));
// If the instruction is using an OpTypeSampledImage as an operand, it should
// be recorded. The validator will ensure that all usages of an
// OpTypeSampledImage and its definition are in the same basic block.
for (uint16_t i = 0; i < inst->operands().size(); ++i) {
const spv_parsed_operand_t& operand = inst->operand(i);
if (SPV_OPERAND_TYPE_ID == operand.type) {
const uint32_t operand_word = inst->word(operand.offset);
Instruction* operand_inst = FindDef(operand_word);
if (operand_inst && SpvOpSampledImage == operand_inst->opcode()) {
RegisterSampledImageConsumer(operand_word, inst);
}
}
}
}
std::vector<Instruction*> ValidationState_t::getSampledImageConsumers(
uint32_t sampled_image_id) const {
std::vector<Instruction*> result;
auto iter = sampled_image_consumers_.find(sampled_image_id);
if (iter != sampled_image_consumers_.end()) {
result = iter->second;
}
return result;
}
void ValidationState_t::RegisterSampledImageConsumer(uint32_t sampled_image_id,
Instruction* consumer) {
sampled_image_consumers_[sampled_image_id].push_back(consumer);
}
uint32_t ValidationState_t::getIdBound() const { return id_bound_; }
void ValidationState_t::setIdBound(const uint32_t bound) { id_bound_ = bound; }
bool ValidationState_t::RegisterUniqueTypeDeclaration(const Instruction* inst) {
std::vector<uint32_t> key;
key.push_back(static_cast<uint32_t>(inst->opcode()));
for (size_t index = 0; index < inst->operands().size(); ++index) {
const spv_parsed_operand_t& operand = inst->operand(index);
if (operand.type == SPV_OPERAND_TYPE_RESULT_ID) continue;
const int words_begin = operand.offset;
const int words_end = words_begin + operand.num_words;
assert(words_end <= static_cast<int>(inst->words().size()));
key.insert(key.end(), inst->words().begin() + words_begin,
inst->words().begin() + words_end);
}
return unique_type_declarations_.insert(std::move(key)).second;
}
uint32_t ValidationState_t::GetTypeId(uint32_t id) const {
const Instruction* inst = FindDef(id);
return inst ? inst->type_id() : 0;
}
SpvOp ValidationState_t::GetIdOpcode(uint32_t id) const {
const Instruction* inst = FindDef(id);
return inst ? inst->opcode() : SpvOpNop;
}
uint32_t ValidationState_t::GetComponentType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
switch (inst->opcode()) {
case SpvOpTypeFloat:
case SpvOpTypeInt:
case SpvOpTypeBool:
return id;
case SpvOpTypeVector:
return inst->word(2);
case SpvOpTypeMatrix:
return GetComponentType(inst->word(2));
case SpvOpTypeCooperativeMatrixNV:
return inst->word(2);
default:
break;
}
if (inst->type_id()) return GetComponentType(inst->type_id());
assert(0);
return 0;
}
uint32_t ValidationState_t::GetDimension(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
switch (inst->opcode()) {
case SpvOpTypeFloat:
case SpvOpTypeInt:
case SpvOpTypeBool:
return 1;
case SpvOpTypeVector:
case SpvOpTypeMatrix:
return inst->word(3);
case SpvOpTypeCooperativeMatrixNV:
// Actual dimension isn't known, return 0
return 0;
default:
break;
}
if (inst->type_id()) return GetDimension(inst->type_id());
assert(0);
return 0;
}
uint32_t ValidationState_t::GetBitWidth(uint32_t id) const {
const uint32_t component_type_id = GetComponentType(id);
const Instruction* inst = FindDef(component_type_id);
assert(inst);
if (inst->opcode() == SpvOpTypeFloat || inst->opcode() == SpvOpTypeInt)
return inst->word(2);
if (inst->opcode() == SpvOpTypeBool) return 1;
assert(0);
return 0;
}
bool ValidationState_t::IsVoidType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypeVoid;
}
bool ValidationState_t::IsFloatScalarType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypeFloat;
}
bool ValidationState_t::IsFloatVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeVector) {
return IsFloatScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsFloatScalarOrVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeFloat) {
return true;
}
if (inst->opcode() == SpvOpTypeVector) {
return IsFloatScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsIntScalarType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypeInt;
}
bool ValidationState_t::IsIntVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeVector) {
return IsIntScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsIntScalarOrVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeInt) {
return true;
}
if (inst->opcode() == SpvOpTypeVector) {
return IsIntScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsUnsignedIntScalarType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypeInt && inst->word(3) == 0;
}
bool ValidationState_t::IsUnsignedIntVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeVector) {
return IsUnsignedIntScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsSignedIntScalarType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypeInt && inst->word(3) == 1;
}
bool ValidationState_t::IsSignedIntVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeVector) {
return IsSignedIntScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsBoolScalarType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypeBool;
}
bool ValidationState_t::IsBoolVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeVector) {
return IsBoolScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsBoolScalarOrVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeBool) {
return true;
}
if (inst->opcode() == SpvOpTypeVector) {
return IsBoolScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsFloatMatrixType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == SpvOpTypeMatrix) {
return IsFloatScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::GetMatrixTypeInfo(uint32_t id, uint32_t* num_rows,
uint32_t* num_cols,
uint32_t* column_type,
uint32_t* component_type) const {
if (!id) return false;
const Instruction* mat_inst = FindDef(id);
assert(mat_inst);
if (mat_inst->opcode() != SpvOpTypeMatrix) return false;
const uint32_t vec_type = mat_inst->word(2);
const Instruction* vec_inst = FindDef(vec_type);
assert(vec_inst);
if (vec_inst->opcode() != SpvOpTypeVector) {
assert(0);
return false;
}
*num_cols = mat_inst->word(3);
*num_rows = vec_inst->word(3);
*column_type = mat_inst->word(2);
*component_type = vec_inst->word(2);
return true;
}
bool ValidationState_t::GetStructMemberTypes(
uint32_t struct_type_id, std::vector<uint32_t>* member_types) const {
member_types->clear();
if (!struct_type_id) return false;
const Instruction* inst = FindDef(struct_type_id);
assert(inst);
if (inst->opcode() != SpvOpTypeStruct) return false;
*member_types =
std::vector<uint32_t>(inst->words().cbegin() + 2, inst->words().cend());
if (member_types->empty()) return false;
return true;
}
bool ValidationState_t::IsPointerType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypePointer;
}
bool ValidationState_t::GetPointerTypeInfo(uint32_t id, uint32_t* data_type,
uint32_t* storage_class) const {
if (!id) return false;
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() != SpvOpTypePointer) return false;
*storage_class = inst->word(2);
*data_type = inst->word(3);
return true;
}
bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const {
const Instruction* inst = FindDef(id);
assert(inst);
return inst->opcode() == SpvOpTypeCooperativeMatrixNV;
}
bool ValidationState_t::IsFloatCooperativeMatrixType(uint32_t id) const {
if (!IsCooperativeMatrixType(id)) return false;
return IsFloatScalarType(FindDef(id)->word(2));
}
bool ValidationState_t::IsIntCooperativeMatrixType(uint32_t id) const {
if (!IsCooperativeMatrixType(id)) return false;
return IsIntScalarType(FindDef(id)->word(2));
}
bool ValidationState_t::IsUnsignedIntCooperativeMatrixType(uint32_t id) const {
if (!IsCooperativeMatrixType(id)) return false;
return IsUnsignedIntScalarType(FindDef(id)->word(2));
}
spv_result_t ValidationState_t::CooperativeMatrixShapesMatch(
const Instruction* inst, uint32_t m1, uint32_t m2) {
const auto m1_type = FindDef(m1);
const auto m2_type = FindDef(m2);
if (m1_type->opcode() != SpvOpTypeCooperativeMatrixNV ||
m2_type->opcode() != SpvOpTypeCooperativeMatrixNV) {
return diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected cooperative matrix types";
}
uint32_t m1_scope_id = m1_type->GetOperandAs<uint32_t>(2);
uint32_t m1_rows_id = m1_type->GetOperandAs<uint32_t>(3);
uint32_t m1_cols_id = m1_type->GetOperandAs<uint32_t>(4);
uint32_t m2_scope_id = m2_type->GetOperandAs<uint32_t>(2);
uint32_t m2_rows_id = m2_type->GetOperandAs<uint32_t>(3);
uint32_t m2_cols_id = m2_type->GetOperandAs<uint32_t>(4);
bool m1_is_int32 = false, m1_is_const_int32 = false, m2_is_int32 = false,
m2_is_const_int32 = false;
uint32_t m1_value = 0, m2_value = 0;
std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
EvalInt32IfConst(m1_scope_id);
std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
EvalInt32IfConst(m2_scope_id);
if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
return diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected scopes of Matrix and Result Type to be "
<< "identical";
}
std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
EvalInt32IfConst(m1_rows_id);
std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
EvalInt32IfConst(m2_rows_id);
if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
return diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected rows of Matrix type and Result Type to be "
<< "identical";
}
std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
EvalInt32IfConst(m1_cols_id);
std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
EvalInt32IfConst(m2_cols_id);
if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
return diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected columns of Matrix type and Result Type to be "
<< "identical";
}
return SPV_SUCCESS;
}
uint32_t ValidationState_t::GetOperandTypeId(const Instruction* inst,
size_t operand_index) const {
return GetTypeId(inst->GetOperandAs<uint32_t>(operand_index));
}
bool ValidationState_t::GetConstantValUint64(uint32_t id, uint64_t* val) const {
const Instruction* inst = FindDef(id);
if (!inst) {
assert(0 && "Instruction not found");
return false;
}
if (inst->opcode() != SpvOpConstant && inst->opcode() != SpvOpSpecConstant)
return false;
if (!IsIntScalarType(inst->type_id())) return false;
if (inst->words().size() == 4) {
*val = inst->word(3);
} else {
assert(inst->words().size() == 5);
*val = inst->word(3);
*val |= uint64_t(inst->word(4)) << 32;
}
return true;
}
std::tuple<bool, bool, uint32_t> ValidationState_t::EvalInt32IfConst(
uint32_t id) const {
const Instruction* const inst = FindDef(id);
assert(inst);
const uint32_t type = inst->type_id();
if (type == 0 || !IsIntScalarType(type) || GetBitWidth(type) != 32) {
return std::make_tuple(false, false, 0);
}
// Spec constant values cannot be evaluated so don't consider constant for
// the purpose of this method.
if (!spvOpcodeIsConstant(inst->opcode()) ||
spvOpcodeIsSpecConstant(inst->opcode())) {
return std::make_tuple(true, false, 0);
}
if (inst->opcode() == SpvOpConstantNull) {
return std::make_tuple(true, true, 0);
}
assert(inst->words().size() == 4);
return std::make_tuple(true, true, inst->word(3));
}
void ValidationState_t::ComputeFunctionToEntryPointMapping() {
for (const uint32_t entry_point : entry_points()) {
std::stack<uint32_t> call_stack;
std::set<uint32_t> visited;
call_stack.push(entry_point);
while (!call_stack.empty()) {
const uint32_t called_func_id = call_stack.top();
call_stack.pop();
if (!visited.insert(called_func_id).second) continue;
function_to_entry_points_[called_func_id].push_back(entry_point);
const Function* called_func = function(called_func_id);
if (called_func) {
// Other checks should error out on this invalid SPIR-V.
for (const uint32_t new_call : called_func->function_call_targets()) {
call_stack.push(new_call);
}
}
}
}
}
void ValidationState_t::ComputeRecursiveEntryPoints() {
for (const Function& func : functions()) {
std::stack<uint32_t> call_stack;
std::set<uint32_t> visited;
for (const uint32_t new_call : func.function_call_targets()) {
call_stack.push(new_call);
}
while (!call_stack.empty()) {
const uint32_t called_func_id = call_stack.top();
call_stack.pop();
if (!visited.insert(called_func_id).second) continue;
if (called_func_id == func.id()) {
for (const uint32_t entry_point :
function_to_entry_points_[called_func_id])
recursive_entry_points_.insert(entry_point);
break;
}
const Function* called_func = function(called_func_id);
if (called_func) {
// Other checks should error out on this invalid SPIR-V.
for (const uint32_t new_call : called_func->function_call_targets()) {
call_stack.push(new_call);
}
}
}
}
}
const std::vector<uint32_t>& ValidationState_t::FunctionEntryPoints(
uint32_t func) const {
auto iter = function_to_entry_points_.find(func);
if (iter == function_to_entry_points_.end()) {
return empty_ids_;
} else {
return iter->second;
}
}
std::set<uint32_t> ValidationState_t::EntryPointReferences(uint32_t id) const {
std::set<uint32_t> referenced_entry_points;
const auto inst = FindDef(id);
if (!inst) return referenced_entry_points;
std::vector<const Instruction*> stack;
stack.push_back(inst);
while (!stack.empty()) {
const auto current_inst = stack.back();
stack.pop_back();
if (const auto func = current_inst->function()) {
// Instruction lives in a function, we can stop searching.
const auto function_entry_points = FunctionEntryPoints(func->id());
referenced_entry_points.insert(function_entry_points.begin(),
function_entry_points.end());
} else {
// Instruction is in the global scope, keep searching its uses.
for (auto pair : current_inst->uses()) {
const auto next_inst = pair.first;
stack.push_back(next_inst);
}
}
}
return referenced_entry_points;
}
std::string ValidationState_t::Disassemble(const Instruction& inst) const {
const spv_parsed_instruction_t& c_inst(inst.c_inst());
return Disassemble(c_inst.words, c_inst.num_words);
}
std::string ValidationState_t::Disassemble(const uint32_t* words,
uint16_t num_words) const {
uint32_t disassembly_options = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
return spvInstructionBinaryToText(context()->target_env, words, num_words,
words_, num_words_, disassembly_options);
}
bool ValidationState_t::LogicallyMatch(const Instruction* lhs,
const Instruction* rhs,
bool check_decorations) {
if (lhs->opcode() != rhs->opcode()) {
return false;
}
if (check_decorations) {
const auto& dec_a = id_decorations(lhs->id());
const auto& dec_b = id_decorations(rhs->id());
for (const auto& dec : dec_b) {
if (std::find(dec_a.begin(), dec_a.end(), dec) == dec_a.end()) {
return false;
}
}
}
if (lhs->opcode() == SpvOpTypeArray) {
// Size operands must match.
if (lhs->GetOperandAs<uint32_t>(2u) != rhs->GetOperandAs<uint32_t>(2u)) {
return false;
}
// Elements must match or logically match.
const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(1u);
const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(1u);
if (lhs_ele_id == rhs_ele_id) {
return true;
}
const auto lhs_ele = FindDef(lhs_ele_id);
const auto rhs_ele = FindDef(rhs_ele_id);
if (!lhs_ele || !rhs_ele) {
return false;
}
return LogicallyMatch(lhs_ele, rhs_ele, check_decorations);
} else if (lhs->opcode() == SpvOpTypeStruct) {
// Number of elements must match.
if (lhs->operands().size() != rhs->operands().size()) {
return false;
}
for (size_t i = 1u; i < lhs->operands().size(); ++i) {
const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(i);
const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(i);
// Elements must match or logically match.
if (lhs_ele_id == rhs_ele_id) {
continue;
}
const auto lhs_ele = FindDef(lhs_ele_id);
const auto rhs_ele = FindDef(rhs_ele_id);
if (!lhs_ele || !rhs_ele) {
return false;
}
if (!LogicallyMatch(lhs_ele, rhs_ele, check_decorations)) {
return false;
}
}
// All checks passed.
return true;
}
// No other opcodes are acceptable at this point. Arrays and structs are
// caught above and if they're elements are not arrays or structs they are
// required to match exactly.
return false;
}
const Instruction* ValidationState_t::TracePointer(
const Instruction* inst) const {
auto base_ptr = inst;
while (base_ptr->opcode() == SpvOpAccessChain ||
base_ptr->opcode() == SpvOpInBoundsAccessChain ||
base_ptr->opcode() == SpvOpPtrAccessChain ||
base_ptr->opcode() == SpvOpInBoundsPtrAccessChain ||
base_ptr->opcode() == SpvOpCopyObject) {
base_ptr = FindDef(base_ptr->GetOperandAs<uint32_t>(2u));
}
return base_ptr;
}
bool ValidationState_t::ContainsSizedIntOrFloatType(uint32_t id, SpvOp type,
uint32_t width) const {
if (type != SpvOpTypeInt && type != SpvOpTypeFloat) return false;
const auto inst = FindDef(id);
if (!inst) return false;
if (inst->opcode() == type) {
return inst->GetOperandAs<uint32_t>(1u) == width;
}
switch (inst->opcode()) {
case SpvOpTypeArray:
case SpvOpTypeRuntimeArray:
case SpvOpTypeVector:
case SpvOpTypeMatrix:
case SpvOpTypeImage:
case SpvOpTypeSampledImage:
case SpvOpTypeCooperativeMatrixNV:
return ContainsSizedIntOrFloatType(inst->GetOperandAs<uint32_t>(1u), type,
width);
case SpvOpTypePointer:
if (IsForwardPointer(id)) return false;
return ContainsSizedIntOrFloatType(inst->GetOperandAs<uint32_t>(2u), type,
width);
case SpvOpTypeFunction:
case SpvOpTypeStruct: {
for (uint32_t i = 1; i < inst->operands().size(); ++i) {
if (ContainsSizedIntOrFloatType(inst->GetOperandAs<uint32_t>(i), type,
width))
return true;
}
return false;
}
default:
return false;
}
}
bool ValidationState_t::ContainsLimitedUseIntOrFloatType(uint32_t id) const {
if ((!HasCapability(SpvCapabilityInt16) &&
ContainsSizedIntOrFloatType(id, SpvOpTypeInt, 16)) ||
(!HasCapability(SpvCapabilityInt8) &&
ContainsSizedIntOrFloatType(id, SpvOpTypeInt, 8)) ||
(!HasCapability(SpvCapabilityFloat16) &&
ContainsSizedIntOrFloatType(id, SpvOpTypeFloat, 16))) {
return true;
}
return false;
}
bool ValidationState_t::IsValidStorageClass(
SpvStorageClass storage_class) const {
if (spvIsWebGPUEnv(context()->target_env)) {
switch (storage_class) {
case SpvStorageClassUniformConstant:
case SpvStorageClassUniform:
case SpvStorageClassStorageBuffer:
case SpvStorageClassInput:
case SpvStorageClassOutput:
case SpvStorageClassImage:
case SpvStorageClassWorkgroup:
case SpvStorageClassPrivate:
case SpvStorageClassFunction:
return true;
default:
return false;
}
}
if (spvIsVulkanEnv(context()->target_env)) {
switch (storage_class) {
case SpvStorageClassUniformConstant:
case SpvStorageClassUniform:
case SpvStorageClassStorageBuffer:
case SpvStorageClassInput:
case SpvStorageClassOutput:
case SpvStorageClassImage:
case SpvStorageClassWorkgroup:
case SpvStorageClassPrivate:
case SpvStorageClassFunction:
case SpvStorageClassPushConstant:
case SpvStorageClassPhysicalStorageBuffer:
case SpvStorageClassRayPayloadNV:
case SpvStorageClassIncomingRayPayloadNV:
case SpvStorageClassHitAttributeNV:
case SpvStorageClassCallableDataNV:
case SpvStorageClassIncomingCallableDataNV:
case SpvStorageClassShaderRecordBufferNV:
return true;
default:
return false;
}
}
return true;
}
#define VUID_WRAP(vuid) "[" #vuid "] "
// Currently no 2 VUID share the same id, so no need for |reference|
std::string ValidationState_t::VkErrorID(uint32_t id,
const char* /*reference*/) const {
if (!spvIsVulkanEnv(context_->target_env)) {
return "";
}
// This large switch case is only searched when an error has occured.
// If an id is changed, the old case must be modified or removed. Each string
// here is interpreted as being "implemented"
// Clang format adds spaces between hyphens
// clang-format off
switch (id) {
case 4181:
return VUID_WRAP(VUID-BaseInstance-BaseInstance-04181);
case 4182:
return VUID_WRAP(VUID-BaseInstance-BaseInstance-04182);
case 4183:
return VUID_WRAP(VUID-BaseInstance-BaseInstance-04183);
case 4184:
return VUID_WRAP(VUID-BaseVertex-BaseVertex-04184);
case 4185:
return VUID_WRAP(VUID-BaseVertex-BaseVertex-04185);
case 4186:
return VUID_WRAP(VUID-BaseVertex-BaseVertex-04186);
case 4187:
return VUID_WRAP(VUID-ClipDistance-ClipDistance-04187);
case 4191:
return VUID_WRAP(VUID-ClipDistance-ClipDistance-04191);
case 4196:
return VUID_WRAP(VUID-CullDistance-CullDistance-04196);
case 4200:
return VUID_WRAP(VUID-CullDistance-CullDistance-04200);
case 4205:
return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04205);
case 4206:
return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04206);
case 4207:
return VUID_WRAP(VUID-DrawIndex-DrawIndex-04207);
case 4208:
return VUID_WRAP(VUID-DrawIndex-DrawIndex-04208);
case 4209:
return VUID_WRAP(VUID-DrawIndex-DrawIndex-04209);
case 4210:
return VUID_WRAP(VUID-FragCoord-FragCoord-04210);
case 4211:
return VUID_WRAP(VUID-FragCoord-FragCoord-04211);
case 4212:
return VUID_WRAP(VUID-FragCoord-FragCoord-04212);
case 4213:
return VUID_WRAP(VUID-FragDepth-FragDepth-04213);
case 4214:
return VUID_WRAP(VUID-FragDepth-FragDepth-04214);
case 4215:
return VUID_WRAP(VUID-FragDepth-FragDepth-04215);
case 4216:
return VUID_WRAP(VUID-FragDepth-FragDepth-04216);
case 4229:
return VUID_WRAP(VUID-FrontFacing-FrontFacing-04229);
case 4230:
return VUID_WRAP(VUID-FrontFacing-FrontFacing-04230);
case 4231:
return VUID_WRAP(VUID-FrontFacing-FrontFacing-04231);
case 4236:
return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04236);
case 4237:
return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04237);
case 4238:
return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04238);
case 4239:
return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04239);
case 4240:
return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04240);
case 4241:
return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04241);
case 4257:
return VUID_WRAP(VUID-InvocationId-InvocationId-04257);
case 4258:
return VUID_WRAP(VUID-InvocationId-InvocationId-04258);
case 4259:
return VUID_WRAP(VUID-InvocationId-InvocationId-04259);
case 4263:
return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04263);
case 4264:
return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04264);
case 4265:
return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04265);
case 4272:
return VUID_WRAP(VUID-Layer-Layer-04272);
case 4274:
return VUID_WRAP(VUID-Layer-Layer-04274);
case 4275:
return VUID_WRAP(VUID-Layer-Layer-04275);
case 4276:
return VUID_WRAP(VUID-Layer-Layer-04276);
case 4281:
return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04281);
case 4282:
return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04282);
case 4283:
return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04283);
case 4296:
return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04296);
case 4297:
return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04297);
case 4298:
return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04298);
case 4308:
return VUID_WRAP(VUID-PatchVertices-PatchVertices-04308);
case 4309:
return VUID_WRAP(VUID-PatchVertices-PatchVertices-04309);
case 4310:
return VUID_WRAP(VUID-PatchVertices-PatchVertices-04310);
case 4311:
return VUID_WRAP(VUID-PointCoord-PointCoord-04311);
case 4312:
return VUID_WRAP(VUID-PointCoord-PointCoord-04312);
case 4313:
return VUID_WRAP(VUID-PointCoord-PointCoord-04313);
case 4314:
return VUID_WRAP(VUID-PointSize-PointSize-04314);
case 4315:
return VUID_WRAP(VUID-PointSize-PointSize-04315);
case 4316:
return VUID_WRAP(VUID-PointSize-PointSize-04316);
case 4317:
return VUID_WRAP(VUID-PointSize-PointSize-04317);
case 4318:
return VUID_WRAP(VUID-Position-Position-04318);
case 4320:
return VUID_WRAP(VUID-Position-Position-04320);
case 4321:
return VUID_WRAP(VUID-Position-Position-04321);
case 4330:
return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04330);
case 4334:
return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04334);
case 4337:
return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04337);
case 4354:
return VUID_WRAP(VUID-SampleId-SampleId-04354);
case 4355:
return VUID_WRAP(VUID-SampleId-SampleId-04355);
case 4356:
return VUID_WRAP(VUID-SampleId-SampleId-04356);
case 4357:
return VUID_WRAP(VUID-SampleMask-SampleMask-04357);
case 4358:
return VUID_WRAP(VUID-SampleMask-SampleMask-04358);
case 4359:
return VUID_WRAP(VUID-SampleMask-SampleMask-04359);
case 4360:
return VUID_WRAP(VUID-SamplePosition-SamplePosition-04360);
case 4361:
return VUID_WRAP(VUID-SamplePosition-SamplePosition-04361);
case 4362:
return VUID_WRAP(VUID-SamplePosition-SamplePosition-04362);
case 4387:
return VUID_WRAP(VUID-TessCoord-TessCoord-04387);
case 4388:
return VUID_WRAP(VUID-TessCoord-TessCoord-04388);
case 4389:
return VUID_WRAP(VUID-TessCoord-TessCoord-04389);
case 4390:
return VUID_WRAP(VUID-TessLevelOuter-TessLevelOuter-04390);
case 4393:
return VUID_WRAP(VUID-TessLevelOuter-TessLevelOuter-04393);
case 4394:
return VUID_WRAP(VUID-TessLevelInner-TessLevelInner-04394);
case 4397:
return VUID_WRAP(VUID-TessLevelInner-TessLevelInner-04397);
case 4398:
return VUID_WRAP(VUID-VertexIndex-VertexIndex-04398);
case 4399:
return VUID_WRAP(VUID-VertexIndex-VertexIndex-04399);
case 4400:
return VUID_WRAP(VUID-VertexIndex-VertexIndex-04400);
case 4401:
return VUID_WRAP(VUID-ViewIndex-ViewIndex-04401);
case 4402:
return VUID_WRAP(VUID-ViewIndex-ViewIndex-04402);
case 4403:
return VUID_WRAP(VUID-ViewIndex-ViewIndex-04403);
case 4404:
return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04404);
case 4406:
return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04406);
case 4407:
return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04407);
case 4408:
return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04408);
case 4422:
return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04422);
case 4423:
return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04423);
case 4424:
return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04424);
case 4425:
return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04425);
case 4426:
return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04426);
case 4427:
return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04427);
case 4484:
return VUID_WRAP(VUID-PrimitiveShadingRateKHR-PrimitiveShadingRateKHR-04484);
case 4485:
return VUID_WRAP(VUID-PrimitiveShadingRateKHR-PrimitiveShadingRateKHR-04485);
case 4486:
return VUID_WRAP(VUID-PrimitiveShadingRateKHR-PrimitiveShadingRateKHR-04486);
case 4490:
return VUID_WRAP(VUID-ShadingRateKHR-ShadingRateKHR-04490);
case 4491:
return VUID_WRAP(VUID-ShadingRateKHR-ShadingRateKHR-04491);
case 4492:
return VUID_WRAP(VUID-ShadingRateKHR-ShadingRateKHR-04492);
default:
return ""; // unknown id
};
// clang-format on
}
} // namespace val
} // namespace spvtools