blob: cb131ecf5c42a7880a8ef472a430f8e0ca99fd70 [file] [log] [blame]
/* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-2026 LunarG, Inc.
* Copyright (C) 2015-2026 Google Inc.
* Modifications Copyright (C) 2020-2022,2025-2026 Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2025 RasterGrid Kft.
*
* 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 "sl_spirv.h"
#include "generated/spirv_grammar_helper.h"
#include "chassis/dispatch_object.h"
#include "state_tracker/shader_instruction.h"
#include "state_tracker/shader_module.h"
#include <inttypes.h>
#include <vulkan/vulkan_core.h>
#include <set>
namespace stateless {
// Temporary data of a OpVariable when validating it.
// If found useful in another location, can move out to the header
struct VariableInstInfo {
bool has_8bit = false;
bool has_16bit = false;
};
// easier to use recursion to traverse the OpTypeStruct
static void GetVariableInfo(const spirv::Module &module_state, const spirv::Instruction *insn, VariableInstInfo &info) {
if (!insn) {
return;
} else if (insn->Opcode() == spv::OpTypePointer || insn->Opcode() == spv::OpTypeUntypedPointerKHR) {
return;
} else if (insn->Opcode() == spv::OpTypeFloat || insn->Opcode() == spv::OpTypeInt) {
const uint32_t bit_width = insn->Word(2);
info.has_8bit |= (bit_width == 8);
info.has_16bit |= (bit_width == 16);
} else if (insn->Opcode() == spv::OpTypeStruct) {
for (uint32_t i = 2; i < insn->Length(); i++) {
const spirv::Instruction *member_insn = module_state.FindDef(insn->Word(i));
if (member_insn->StorageClass() == spv::StorageClassPhysicalStorageBuffer) {
continue; // a uint8 pointer is not a 8-bit element
}
const uint32_t base_insn_id = module_state.GetBaseType(member_insn);
const spirv::Instruction *base_insn = module_state.FindDef(base_insn_id);
GetVariableInfo(module_state, base_insn, info);
}
}
}
SpirvValidator::SpirvValidator(DebugReport *debug_report, const vvl::StatelessDeviceData &stateless_device_data, bool disabled)
: Logger(debug_report),
disabled(disabled),
api_version(stateless_device_data.api_version),
extensions(stateless_device_data.extensions),
phys_dev_props(stateless_device_data.phys_dev_props),
phys_dev_props_core11(stateless_device_data.phys_dev_props_core11),
phys_dev_props_core12(stateless_device_data.phys_dev_props_core12),
phys_dev_props_core13(stateless_device_data.phys_dev_props_core13),
phys_dev_props_core14(stateless_device_data.phys_dev_props_core14),
phys_dev_ext_props(stateless_device_data.phys_dev_ext_props),
enabled_features(stateless_device_data.enabled_features),
special_supported(stateless_device_data.special_supported) {}
// stateless spirv == doesn't require pipeline state and/or shader object info
// Originally the goal was to move more validation to vkCreateShaderModule time in case the driver decided to parse an invalid
// SPIR-V here, while that is likely not the case anymore, a bigger reason for checking here is to save on memory. There is a lot of
// state saved in the Module that is only checked once later and could be reduced if not saved.
bool SpirvValidator::Validate(const spirv::Module &module_state, const spirv::StatelessData &stateless_data,
const Location &loc) const {
bool skip = false;
if (!module_state.valid_spirv || disabled) {
return skip;
}
skip |= ValidateShaderClock(module_state, stateless_data, loc);
skip |= ValidateAtomicsTypes(module_state, stateless_data, loc);
skip |= ValidateFma(module_state, stateless_data, loc);
skip |= ValidateVariables(module_state, loc);
skip |= ValidateTransformFeedbackDecorations(module_state, loc);
skip |= ValidateRelaxedExtendedInstruction(module_state, stateless_data, loc);
// The following tries to limit the number of passes through the shader module.
// It save a good amount of memory and complex state tracking to just check these in a 2nd pass
for (const spirv::Instruction &insn : module_state.GetInstructions()) {
skip |= ValidateShaderCapabilitiesAndExtensions(module_state, insn, loc);
skip |= ValidateTexelOffsetLimits(module_state, insn, loc);
skip |= ValidateMemoryScope(module_state, insn, loc);
skip |= ValidateSubgroupRotateClustered(module_state, insn, loc);
}
for (const auto &entry_point : module_state.static_data_.entry_points) {
skip |= ValidateShaderStageGroupNonUniform(module_state, stateless_data, entry_point->stage, loc);
skip |= ValidateShaderStageInputOutputLimits(module_state, *entry_point, stateless_data, loc);
skip |= ValidateShaderStageInterfaceVariables(module_state, *entry_point, stateless_data, loc);
skip |= ValidateShaderFloatControl(module_state, *entry_point, stateless_data, loc);
skip |= ValidateExecutionModes(module_state, *entry_point, stateless_data, loc);
skip |= ValidateConservativeRasterization(module_state, *entry_point, stateless_data, loc);
skip |= ValidateTransformFeedbackEmitStreams(module_state, *entry_point, stateless_data, loc);
skip |= ValidateShaderTensor(module_state, *entry_point, stateless_data, loc);
}
return skip;
}
bool SpirvValidator::ValidateShaderClock(const spirv::Module &module_state, const spirv::StatelessData &stateless_data,
const Location &loc) const {
bool skip = false;
for (const spirv::Instruction *clock_inst : stateless_data.read_clock_inst) {
const spirv::Instruction &insn = *clock_inst;
const spirv::Instruction *scope_id = module_state.FindDef(insn.Word(3));
auto scope_type = scope_id->Word(3);
// if scope isn't Subgroup or Device, spirv-val will catch
if ((scope_type == spv::ScopeSubgroup) && (enabled_features.shaderSubgroupClock == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-shaderSubgroupClock-06267", module_state.handle(), loc,
"SPIR-V uses OpReadClockKHR with a Subgroup scope but shaderSubgroupClock was not enabled.\n%s\n",
module_state.DescribeInstruction(insn).c_str());
} else if ((scope_type == spv::ScopeDevice) && (enabled_features.shaderDeviceClock == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-shaderDeviceClock-06268", module_state.handle(), loc,
"SPIR-V uses OpReadClockKHR with a Device scope but shaderDeviceClock was not enabled.\n%s\n",
module_state.DescribeInstruction(insn).c_str());
}
}
return skip;
}
bool SpirvValidator::ValidateAtomicsTypes(const spirv::Module &module_state, const spirv::StatelessData &stateless_data,
const Location &loc) const {
bool skip = false;
// "If sparseImageInt64Atomics is enabled, shaderImageInt64Atomics must be enabled"
const bool valid_image_64_int = enabled_features.shaderImageInt64Atomics == VK_TRUE;
const bool valid_storage_buffer_float =
((enabled_features.shaderBufferFloat32Atomics == VK_TRUE) || (enabled_features.shaderBufferFloat32AtomicAdd == VK_TRUE) ||
(enabled_features.shaderBufferFloat64Atomics == VK_TRUE) || (enabled_features.shaderBufferFloat64AtomicAdd == VK_TRUE) ||
(enabled_features.shaderBufferFloat16Atomics == VK_TRUE) || (enabled_features.shaderBufferFloat16AtomicAdd == VK_TRUE) ||
(enabled_features.shaderBufferFloat16AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderBufferFloat32AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderBufferFloat64AtomicMinMax == VK_TRUE) || (enabled_features.shaderFloat16VectorAtomics == VK_TRUE));
const bool valid_workgroup_float =
((enabled_features.shaderSharedFloat32Atomics == VK_TRUE) || (enabled_features.shaderSharedFloat32AtomicAdd == VK_TRUE) ||
(enabled_features.shaderSharedFloat64Atomics == VK_TRUE) || (enabled_features.shaderSharedFloat64AtomicAdd == VK_TRUE) ||
(enabled_features.shaderSharedFloat16Atomics == VK_TRUE) || (enabled_features.shaderSharedFloat16AtomicAdd == VK_TRUE) ||
(enabled_features.shaderSharedFloat16AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderSharedFloat32AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderSharedFloat64AtomicMinMax == VK_TRUE) || (enabled_features.shaderFloat16VectorAtomics == VK_TRUE));
const bool valid_image_float =
((enabled_features.shaderImageFloat32Atomics == VK_TRUE) || (enabled_features.shaderImageFloat32AtomicAdd == VK_TRUE) ||
(enabled_features.shaderImageFloat32AtomicMinMax == VK_TRUE) || (enabled_features.shaderFloat16VectorAtomics == VK_TRUE));
const bool valid_16_float =
((enabled_features.shaderBufferFloat16Atomics == VK_TRUE) || (enabled_features.shaderBufferFloat16AtomicAdd == VK_TRUE) ||
(enabled_features.shaderBufferFloat16AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderSharedFloat16Atomics == VK_TRUE) || (enabled_features.shaderSharedFloat16AtomicAdd == VK_TRUE) ||
(enabled_features.shaderSharedFloat16AtomicMinMax == VK_TRUE));
const bool valid_32_float =
((enabled_features.shaderBufferFloat32Atomics == VK_TRUE) || (enabled_features.shaderBufferFloat32AtomicAdd == VK_TRUE) ||
(enabled_features.shaderSharedFloat32Atomics == VK_TRUE) || (enabled_features.shaderSharedFloat32AtomicAdd == VK_TRUE) ||
(enabled_features.shaderImageFloat32Atomics == VK_TRUE) || (enabled_features.shaderImageFloat32AtomicAdd == VK_TRUE) ||
(enabled_features.shaderBufferFloat32AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderSharedFloat32AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderImageFloat32AtomicMinMax == VK_TRUE));
const bool valid_64_float =
((enabled_features.shaderBufferFloat64Atomics == VK_TRUE) || (enabled_features.shaderBufferFloat64AtomicAdd == VK_TRUE) ||
(enabled_features.shaderSharedFloat64Atomics == VK_TRUE) || (enabled_features.shaderSharedFloat64AtomicAdd == VK_TRUE) ||
(enabled_features.shaderBufferFloat64AtomicMinMax == VK_TRUE) ||
(enabled_features.shaderSharedFloat64AtomicMinMax == VK_TRUE));
const bool valid_16_float_vector = (enabled_features.shaderFloat16VectorAtomics == VK_TRUE);
// clang-format on
for (const spirv::Instruction *atomic_def_ptr : stateless_data.atomic_inst) {
const spirv::Instruction &atomic_def = *atomic_def_ptr;
const spirv::AtomicInstructionInfo &atomic = module_state.GetAtomicInfo(atomic_def);
const uint32_t opcode = atomic_def.Opcode();
if (atomic.type == spv::OpTypeFloat && (atomic.vector_size == 2 || atomic.vector_size == 4)) {
if (!valid_16_float_vector) {
skip |=
LogError("VUID-RuntimeSpirv-shaderFloat16VectorAtomics-09581", module_state.handle(), loc,
"SPIR-V is using 16-bit float vector atomics operations with %s storage class, but "
"shaderFloat16VectorAtomics was not enabled.\n%s\n",
string_SpvStorageClass(atomic.storage_class), module_state.DescribeInstruction(atomic_def).c_str());
}
} else if ((atomic.bit_width == 64) && (atomic.type == spv::OpTypeInt)) {
// Validate 64-bit image atomics
if (((atomic.storage_class == spv::StorageClassStorageBuffer) || (atomic.storage_class == spv::StorageClassUniform)) &&
(enabled_features.shaderBufferInt64Atomics == VK_FALSE)) {
skip |=
LogError("VUID-RuntimeSpirv-None-06278", module_state.handle(), loc,
"SPIR-V is using 64-bit int atomics operations with %s storage class, but "
"shaderBufferInt64Atomics was not enabled. \n%s\n",
string_SpvStorageClass(atomic.storage_class), module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.storage_class == spv::StorageClassWorkgroup) &&
(enabled_features.shaderSharedInt64Atomics == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06279", module_state.handle(), loc,
"SPIR-V is using 64-bit int atomics operations with Workgroup storage class, but "
"shaderSharedInt64Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.storage_class == spv::StorageClassImage) && (valid_image_64_int == false)) {
skip |= LogError("VUID-RuntimeSpirv-None-06288", module_state.handle(), loc,
"SPIR-V is using 64-bit int atomics operations with Image storage class, but "
"shaderImageInt64Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
} else if (atomic.type == spv::OpTypeFloat) {
// Validate Floats
if (atomic.storage_class == spv::StorageClassStorageBuffer) {
if (valid_storage_buffer_float == false) {
skip |= LogError("VUID-RuntimeSpirv-None-06284", module_state.handle(), loc,
"SPIR-V is using float atomics operations with StorageBuffer storage class, but none of "
"the required features were enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if (opcode == spv::OpAtomicFAddEXT) {
if ((atomic.bit_width == 16) && (enabled_features.shaderBufferFloat16AtomicAdd == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06337", module_state.handle(), loc,
"SPIR-V is using 16-bit float atomics for add operations with "
"StorageBuffer storage class, but shaderBufferFloat16AtomicAdd was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 32) && (enabled_features.shaderBufferFloat32AtomicAdd == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 32-bit float atomics for add operations with "
"StorageBuffer storage class, but shaderBufferFloat32AtomicAdd was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 64) && (enabled_features.shaderBufferFloat64AtomicAdd == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06339", module_state.handle(), loc,
"SPIR-V is using 64-bit float atomics for add operations with "
"StorageBuffer storage class, but shaderBufferFloat64AtomicAdd was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
} else if (opcode == spv::OpAtomicFMinEXT || opcode == spv::OpAtomicFMaxEXT) {
if ((atomic.bit_width == 16) && (enabled_features.shaderBufferFloat16AtomicMinMax == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06337", module_state.handle(), loc,
"SPIR-V is using 16-bit float atomics for min/max operations with "
"StorageBuffer storage class, but shaderBufferFloat16AtomicMinMax was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 32) && (enabled_features.shaderBufferFloat32AtomicMinMax == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 32-bit float atomics for min/max operations with "
"StorageBuffer storage class, but shaderBufferFloat32AtomicMinMax was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 64) && (enabled_features.shaderBufferFloat64AtomicMinMax == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06339", module_state.handle(), loc,
"SPIR-V is using 64-bit float atomics for min/max operations with "
"StorageBuffer storage class, but shaderBufferFloat64AtomicMinMax was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
} else {
// Assume is valid load/store/exchange (rest of supported atomic operations) or else spirv-val will catch
if ((atomic.bit_width == 16) && (enabled_features.shaderBufferFloat16Atomics == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 16-bit float atomics for load/store/exhange operations with "
"StorageBuffer storage class, but shaderBufferFloat16Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 32) && (enabled_features.shaderBufferFloat32Atomics == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 32-bit float atomics for load/store/exhange operations with "
"StorageBuffer storage class, but shaderBufferFloat32Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 64) && (enabled_features.shaderBufferFloat64Atomics == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06339", module_state.handle(), loc,
"SPIR-V is using 64-bit float atomics for load/store/exhange operations with "
"StorageBuffer storage class, but shaderBufferFloat64Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
}
} else if (atomic.storage_class == spv::StorageClassWorkgroup) {
if (valid_workgroup_float == false) {
skip |= LogError("VUID-RuntimeSpirv-None-06285", module_state.handle(), loc,
"SPIR-V is using float atomics operations with Workgroup storage class, but none of the "
"required features were enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if (opcode == spv::OpAtomicFAddEXT) {
if ((atomic.bit_width == 16) && (enabled_features.shaderSharedFloat16AtomicAdd == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06337", module_state.handle(), loc,
"SPIR-V is using 16-bit float atomics for add operations with Workgroup "
"storage class, but shaderSharedFloat16AtomicAdd was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 32) && (enabled_features.shaderSharedFloat32AtomicAdd == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 32-bit float atomics for add operations with Workgroup "
"storage class, but shaderSharedFloat32AtomicAdd was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 64) && (enabled_features.shaderSharedFloat64AtomicAdd == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06339", module_state.handle(), loc,
"SPIR-V is using 64-bit float atomics for add operations with Workgroup "
"storage class, but shaderSharedFloat64AtomicAdd was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
} else if (opcode == spv::OpAtomicFMinEXT || opcode == spv::OpAtomicFMaxEXT) {
if ((atomic.bit_width == 16) && (enabled_features.shaderSharedFloat16AtomicMinMax == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06337", module_state.handle(), loc,
"SPIR-V is using 16-bit float atomics for min/max operations with "
"Workgroup storage class, but shaderSharedFloat16AtomicMinMax was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 32) && (enabled_features.shaderSharedFloat32AtomicMinMax == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 32-bit float atomics for min/max operations with "
"Workgroup storage class, but shaderSharedFloat32AtomicMinMax was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 64) && (enabled_features.shaderSharedFloat64AtomicMinMax == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06339", module_state.handle(), loc,
"SPIR-V is using 64-bit float atomics for min/max operations with "
"Workgroup storage class, but shaderSharedFloat64AtomicMinMax was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
} else {
// Assume is valid load/store/exchange (rest of supported atomic operations) or else spirv-val will catch
if ((atomic.bit_width == 16) && (enabled_features.shaderSharedFloat16Atomics == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06337", module_state.handle(), loc,
"SPIR-V is using 16-bit float atomics for load/store/exhange operations with Workgroup "
"storage class, but shaderSharedFloat16Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 32) && (enabled_features.shaderSharedFloat32Atomics == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 32-bit float atomics for load/store/exhange operations with Workgroup "
"storage class, but shaderSharedFloat32Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 64) && (enabled_features.shaderSharedFloat64Atomics == VK_FALSE)) {
skip |= LogError("VUID-RuntimeSpirv-None-06339", module_state.handle(), loc,
"SPIR-V is using 64-bit float atomics for load/store/exhange operations with Workgroup "
"storage class, but shaderSharedFloat64Atomics was not enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
}
} else if ((atomic.storage_class == spv::StorageClassImage) && (valid_image_float == false)) {
skip |= LogError("VUID-RuntimeSpirv-None-06286", module_state.handle(), loc,
"SPIR-V is using float atomics operations with Image storage class, but none of the required "
"features were enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 16) && (valid_16_float == false)) {
skip |= LogError(
"VUID-RuntimeSpirv-None-06337", module_state.handle(), loc,
"SPIR-V is using 16-bit float atomics operations but none of the required features were enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 32) && (valid_32_float == false)) {
skip |= LogError(
"VUID-RuntimeSpirv-None-06338", module_state.handle(), loc,
"SPIR-V is using 32-bit float atomics operations but none of the required features were enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
} else if ((atomic.bit_width == 64) && (valid_64_float == false)) {
skip |= LogError(
"VUID-RuntimeSpirv-None-06339", module_state.handle(), loc,
"SPIR-V is using 64-bit float atomics operations but none of the required features were enabled.\n%s\n",
module_state.DescribeInstruction(atomic_def).c_str());
}
}
}
return skip;
}
bool SpirvValidator::ValidateFma(const spirv::Module &module_state, const spirv::StatelessData &stateless_data,
const Location &loc) const {
bool skip = false;
// spirv-val enforces the Result Type must be a scalar or vector of floats
for (const spirv::Instruction *fma_inst : stateless_data.fma_inst) {
const spirv::Instruction* type_insn = module_state.FindDef(fma_inst->TypeId());
const uint32_t bit_width = module_state.GetBaseTypeInstruction(type_insn)->GetBitWidth();
if (bit_width == 16 && !enabled_features.shaderFmaFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderFmaFloat16-10977", module_state.handle(), loc,
"SPIR-V uses OpFmaKHR with 16-bit floats but shaderFmaFloat16 was not enabled.\n%s\n",
module_state.DescribeInstruction(*fma_inst).c_str());
} else if (bit_width == 32 && !enabled_features.shaderFmaFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderFmaFloat32-10978", module_state.handle(), loc,
"SPIR-V uses OpFmaKHR with 32-bit floats but shaderFmaFloat32 was not enabled. (shaderFmaFloat32 is "
"required to be supported if VK_KHR_shader_fma is supported)\n%s\n",
module_state.DescribeInstruction(*fma_inst).c_str());
} else if (bit_width == 64 && !enabled_features.shaderFmaFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderFmaFloat64-10979", module_state.handle(), loc,
"SPIR-V uses OpFmaKHR with 64-bit floats but shaderFmaFloat64 was not enabled.\n%s\n",
module_state.DescribeInstruction(*fma_inst).c_str());
}
}
return skip;
}
bool SpirvValidator::ValidateVariables(const spirv::Module &module_state, const Location &loc) const {
bool skip = false;
for (const spirv::Instruction *insn : module_state.static_data_.explicit_memory_inst) {
const uint32_t opcode = insn->Opcode();
if (opcode == spv::OpVariable || opcode == spv::OpUntypedVariableKHR) {
const uint32_t storage_class = insn->StorageClass();
if (storage_class == spv::StorageClassWorkgroup) {
// If Workgroup variable is initalized, make sure the feature is enabled
const bool untyped = opcode == spv::OpUntypedVariableKHR;
const bool has_initializer = insn->Length() > (untyped ? 5u : 4u);
if (has_initializer && !enabled_features.shaderZeroInitializeWorkgroupMemory) {
skip |= LogError("VUID-RuntimeSpirv-shaderZeroInitializeWorkgroupMemory-06372", module_state.handle(), loc,
"SPIR-V contains an %s with Workgroup Storage Class with an Initializer operand, but "
"shaderZeroInitializeWorkgroupMemory was not enabled.\n%s\n.",
string_SpvOpcode(opcode), insn->Describe().c_str());
}
}
// Checks based off shaderStorageImage(Read|Write)WithoutFormat are
// disabled if VK_KHR_format_feature_flags2 is supported.
//
// https://github.com/KhronosGroup/Vulkan-Docs/blob/6177645341afc/appendices/spirvenv.txt#L553
//
// The other checks need to take into account the format features and so
// we apply that in the descriptor set matching validation code (see
// descriptor_sets.cpp).
if (!special_supported.vk_khr_format_feature_flags2) {
skip |= ValidateShaderStorageImageFormatsVariables(module_state, *insn, loc);
}
}
// These check occur both when we see a OpVariable, but also during an OpLoad/OpStore pointing to an OpUntypedVariableKHR
skip |= Validate8And16BitStorage(module_state, *insn, loc);
}
return skip;
}
// This is to validate the VK_KHR_8bit_storage and VK_KHR_16bit_storage extensions
bool SpirvValidator::Validate8And16BitStorage(const spirv::Module &module_state, const spirv::Instruction &insn,
const Location &loc) const {
bool skip = false;
const bool variable = insn.Opcode() == spv::OpVariable;
const uint32_t storage_class = module_state.StorageClass(insn);
bool untyped_access = false;
VariableInstInfo info;
// When untyped pointers is enabled, storage VUs are based around memory
// access widths and not static declaration types. For example, with
// untyped pointers a variable can contain an 8-bit member so long as it
// isn't accessed. Therefore, variables data types are explicitly not
// checked with untyped pointers.
if (enabled_features.shaderUntypedPointers &&
!(storage_class == spv::StorageClassInput || storage_class == spv::StorageClassOutput)) {
uint32_t type_id = 0;
uint32_t pointer_id = 0;
switch (insn.Opcode()) {
case spv::OpLoad:
case spv::OpAtomicLoad:
case spv::OpAtomicExchange:
case spv::OpAtomicCompareExchange:
case spv::OpAtomicIIncrement:
case spv::OpAtomicIDecrement:
case spv::OpAtomicIAdd:
case spv::OpAtomicISub:
case spv::OpAtomicSMin:
case spv::OpAtomicSMax:
case spv::OpAtomicUMin:
case spv::OpAtomicUMax:
case spv::OpAtomicAnd:
case spv::OpAtomicOr:
case spv::OpAtomicFMinEXT:
case spv::OpAtomicFMaxEXT:
case spv::OpAtomicFAddEXT:
type_id = insn.TypeId();
pointer_id = insn.Word(3);
break;
case spv::OpStore: {
const uint32_t data_id = insn.Word(2);
type_id = module_state.GetTypeId(data_id);
pointer_id = insn.Word(1);
break;
}
case spv::OpAtomicStore: {
const uint32_t data_id = insn.Word(4);
type_id = module_state.GetTypeId(data_id);
pointer_id = insn.Word(1);
break;
}
case spv::OpVariable:
case spv::OpUntypedVariableKHR:
default:
break;
}
if (type_id == 0) {
return skip;
}
if (storage_class == spv::StorageClassWorkgroup) {
// TODO: This doesn't capture explicitly laid out workgroup variables
// that use typed pointers.
const uint32_t ptr_type_id = module_state.GetTypeId(pointer_id);
const spirv::Instruction *ptr_insn = module_state.FindDef(ptr_type_id);
if (ptr_insn->Opcode() != spv::OpTypeUntypedPointerKHR) {
return skip;
}
}
uint32_t byte_size = 0;
const spirv::Instruction *type_insn = module_state.FindDef(type_id);
byte_size = module_state.GetTypeBytesSize(type_insn);
// If 8-bit storage is not enabled, accesses must be at least 16-bit
// multiples (or 32-bit if 16-bit storage is not enabled).
info.has_8bit = byte_size % 2 == 1;
info.has_16bit = !info.has_8bit && byte_size % 4 == 2;
untyped_access = true;
} else if (variable) {
// type will either be a float, int, or struct and if struct need to traverse it
const spirv::Instruction *type = module_state.GetVariablePointerType(insn);
std::shared_ptr<const spirv::TypeStructInfo> struct_info = module_state.GetTypeStructInfo(type);
// Only check block-deocrated workgroup variables.
if (storage_class != spv::StorageClassWorkgroup ||
(struct_info && struct_info->decorations.Has(spirv::DecorationBase::block_bit))) {
GetVariableInfo(module_state, type, info);
}
} else {
return skip; // nothing to check
}
if (info.has_8bit) {
if (!enabled_features.storageBuffer8BitAccess &&
(storage_class == spv::StorageClassStorageBuffer || storage_class == spv::StorageClassShaderRecordBufferKHR ||
storage_class == spv::StorageClassPhysicalStorageBuffer)) {
skip |=
LogError("VUID-RuntimeSpirv-storageBuffer8BitAccess-06328", module_state.handle(), loc,
"SPIR-V contains an 8-bit %s with %s Storage Class, but storageBuffer8BitAccess was not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", string_SpvStorageClass(storage_class), insn.Describe().c_str());
}
if (!enabled_features.uniformAndStorageBuffer8BitAccess && storage_class == spv::StorageClassUniform) {
skip |= LogError("VUID-RuntimeSpirv-uniformAndStorageBuffer8BitAccess-06329", module_state.handle(), loc,
"SPIRV contains an 8-bit %s with Uniform Storage Class, but uniformAndStorageBuffer8BitAccess was not "
"enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", insn.Describe().c_str());
}
if (!enabled_features.storagePushConstant8 && storage_class == spv::StorageClassPushConstant) {
skip |= LogError(
"VUID-RuntimeSpirv-storagePushConstant8-06330", module_state.handle(), loc,
"SPIR-V contains an 8-bit %s with PushConstant Storage Class, but storagePushConstant8 was not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", insn.Describe().c_str());
}
if (!enabled_features.workgroupMemoryExplicitLayout8BitAccess && storage_class == spv::StorageClassWorkgroup) {
skip |= LogError("VUID-RuntimeSpirv-workgroupMemoryExplicitLayout8BitAccess-10756", module_state.handle(), loc,
"SPIRV contains an 8-bit %s with Workgroup Storage Class, but workgroupMemoryExplicitLayout8BitAccess "
"was not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", insn.Describe().c_str());
}
if (storage_class == spv::StorageClassInput || storage_class == spv::StorageClassOutput) {
skip |= LogError(
"VUID-RuntimeSpirv-None-10980", module_state.handle(), loc,
"SPIR-V contains a 8-bit %s with %s Storage Class, but 8-bit Input/Output are not allowed in Vulkan.\n%s\n",
untyped_access ? "access" : "OpVariable", string_SpvStorageClass(storage_class), insn.Describe().c_str());
}
}
if (info.has_16bit) {
if (!enabled_features.storageBuffer16BitAccess &&
(storage_class == spv::StorageClassStorageBuffer || storage_class == spv::StorageClassShaderRecordBufferKHR ||
storage_class == spv::StorageClassPhysicalStorageBuffer)) {
skip |=
LogError("VUID-RuntimeSpirv-storageBuffer16BitAccess-11161", module_state.handle(), loc,
"SPIR-V contains a 16-bit %s with %s StorageClass, but storageBuffer16BitAccess was not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", string_SpvStorageClass(storage_class), insn.Describe().c_str());
}
if (!enabled_features.uniformAndStorageBuffer16BitAccess && storage_class == spv::StorageClassUniform) {
skip |= LogError("VUID-RuntimeSpirv-uniformAndStorageBuffer16BitAccess-06332", module_state.handle(), loc,
"SPIR-V contains a 16-bit %s with Uniform Storage Class, but uniformAndStorageBuffer16BitAccess was "
"not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", insn.Describe().c_str());
}
if (!enabled_features.storagePushConstant16 && storage_class == spv::StorageClassPushConstant) {
skip |= LogError(
"VUID-RuntimeSpirv-storagePushConstant16-06333", module_state.handle(), loc,
"SPIR-V contains a 16-bit %s with PushConstant storage class, but storagePushConstant16 was not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", insn.Describe().c_str());
}
if (!enabled_features.storageInputOutput16 &&
(storage_class == spv::StorageClassInput || storage_class == spv::StorageClassOutput)) {
skip |=
LogError("VUID-RuntimeSpirv-storageInputOutput16-11162", module_state.handle(), loc,
"SPIR-V contains a 16-bit %s with %s Storage Class, but storageInputOutput16 was not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", string_SpvStorageClass(storage_class), insn.Describe().c_str());
}
if (!enabled_features.workgroupMemoryExplicitLayout16BitAccess && storage_class == spv::StorageClassWorkgroup) {
skip |= LogError("VUID-RuntimeSpirv-workgroupMemoryExplicitLayout16BitAccess-10757", module_state.handle(), loc,
"SPIR-V contains a 16-bit %s with Workgroup Storage Class, but "
"workgroupMemoryExplicitLayout16BitAccess was not enabled.\n%s\n",
untyped_access ? "access" : "OpVariable", insn.Describe().c_str());
}
}
return skip;
}
bool SpirvValidator::ValidateShaderStorageImageFormatsVariables(const spirv::Module &module_state, const spirv::Instruction &insn,
const Location &loc) const {
bool skip = false;
// Go through all variables for images and check decorations
// Note: Tried to move to ResourceInterfaceVariable but the issue is the variables don't need to be accessed in the entrypoint
// to trigger the error.
assert(insn.Opcode() == spv::OpVariable || insn.Opcode() == spv::OpUntypedVariableKHR);
// spirv-val validates this is an OpTypePointer
const spirv::Instruction *pointer_def = module_state.FindDef(insn.TypeId());
if (pointer_def->Word(2) != spv::StorageClassUniformConstant) {
return skip; // Vulkan Spec says storage image must be UniformConstant
}
const spirv::Instruction *type_def = module_state.FindDef(pointer_def->Word(3));
// Unpack an optional level of arraying
if (type_def && type_def->IsArray()) {
type_def = module_state.FindDef(type_def->Word(2));
}
if (type_def && type_def->Opcode() == spv::OpTypeImage) {
// Only check if the Image Dim operand is not SubpassData
const uint32_t dim = type_def->Word(3);
// Only check storage images
const uint32_t sampled = type_def->Word(7);
const uint32_t image_format = type_def->Word(8);
if ((dim == spv::DimSubpassData) || (sampled != 2) || (image_format != spv::ImageFormatUnknown)) {
return skip;
}
const uint32_t var_id = insn.ResultId();
const auto decorations = module_state.GetDecorationSet(var_id);
if (!enabled_features.shaderStorageImageReadWithoutFormat && !decorations.Has(spirv::DecorationSet::nonreadable_bit)) {
skip |=
LogError("VUID-RuntimeSpirv-apiVersion-07955", module_state.handle(), loc,
"SPIR-V variable\n%s\nhas an Image\n%s\nwith Unknown "
"format and is not decorated with NonReadable, but shaderStorageImageReadWithoutFormat is not supported.",
module_state.FindDef(var_id)->Describe().c_str(), type_def->Describe().c_str());
}
if (!enabled_features.shaderStorageImageWriteWithoutFormat && !decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
skip |= LogError(
"VUID-RuntimeSpirv-apiVersion-07954", module_state.handle(), loc,
"SPIR-V variable\n%s\nhas an Image\n%s\nwith "
"Unknown format and is not decorated with NonWritable, but shaderStorageImageWriteWithoutFormat is not supported.",
module_state.FindDef(var_id)->Describe().c_str(), type_def->Describe().c_str());
}
}
return skip;
}
bool SpirvValidator::ValidateTransformFeedbackDecorations(const spirv::Module &module_state, const Location &loc) const {
bool skip = false;
if (!enabled_features.transformFeedback) {
return skip;
}
std::vector<const spirv::Instruction *> xfb_streams;
std::vector<const spirv::Instruction *> xfb_buffers;
std::vector<const spirv::Instruction *> xfb_offsets;
for (const spirv::Instruction *op_decorate : module_state.static_data_.decoration_inst) {
uint32_t decoration = op_decorate->Word(2);
if (decoration == spv::DecorationXfbStride) {
uint32_t stride = op_decorate->Word(3);
if (stride > phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackBufferDataStride) {
skip |= LogError("VUID-RuntimeSpirv-XfbStride-06313", module_state.handle(), loc,
"SPIR-V uses transform feedback with xfb_stride (%" PRIu32
") greater than maxTransformFeedbackBufferDataStride (%" PRIu32 ").",
stride, phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackBufferDataStride);
}
}
if (decoration == spv::DecorationStream) {
xfb_streams.push_back(op_decorate);
uint32_t stream = op_decorate->Word(3);
if (stream >= phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackStreams) {
skip |= LogError("VUID-RuntimeSpirv-Stream-06312", module_state.handle(), loc,
"SPIR-V uses transform feedback with stream (%" PRIu32
") not less than maxTransformFeedbackStreams (%" PRIu32 ").",
stream, phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackStreams);
}
}
if (decoration == spv::DecorationXfbBuffer) {
xfb_buffers.push_back(op_decorate);
}
if (decoration == spv::DecorationOffset) {
xfb_offsets.push_back(op_decorate);
}
}
// XfbBuffer, buffer data size
std::vector<std::pair<uint32_t, uint32_t>> buffer_data_sizes;
for (const spirv::Instruction *op_decorate : xfb_offsets) {
for (const spirv::Instruction *xfb_buffer : xfb_buffers) {
if (xfb_buffer->Word(1) == op_decorate->Word(1)) {
const auto offset = op_decorate->Word(3);
const spirv::Instruction *def = module_state.FindDef(xfb_buffer->Word(1));
const auto size = module_state.GetTypeBytesSize(def);
const uint32_t buffer_data_size = offset + size;
if (buffer_data_size > phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackBufferDataSize) {
skip |= LogError(
"VUID-RuntimeSpirv-Offset-06308", module_state.handle(), loc,
"SPIR-V uses transform feedback with xfb_offset (%" PRIu32 ") + size of variable (%" PRIu32
") greater than VkPhysicalDeviceTransformFeedbackPropertiesEXT::maxTransformFeedbackBufferDataSize "
"(%" PRIu32 ").",
offset, size, phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackBufferDataSize);
}
bool found = false;
for (auto &bds : buffer_data_sizes) {
if (bds.first == xfb_buffer->Word(1)) {
bds.second = std::max(bds.second, buffer_data_size);
found = true;
break;
}
}
if (!found) {
buffer_data_sizes.emplace_back(xfb_buffer->Word(1), buffer_data_size);
}
break;
}
}
}
vvl::unordered_map<uint32_t, uint32_t> stream_data_size;
for (const spirv::Instruction *xfb_stream : xfb_streams) {
for (const auto &bds : buffer_data_sizes) {
if (xfb_stream->Word(1) == bds.first) {
uint32_t stream = xfb_stream->Word(3);
const auto itr = stream_data_size.find(stream);
if (itr != stream_data_size.end()) {
itr->second += bds.second;
} else {
stream_data_size.insert({stream, bds.second});
}
}
}
}
for (const auto &stream : stream_data_size) {
if (stream.second > phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackStreamDataSize) {
skip |= LogError(
"VUID-RuntimeSpirv-XfbBuffer-06309", module_state.handle(), loc,
"SPIR-V uses transform feedback with stream (%" PRIu32 ") having the sum of buffer data sizes (%" PRIu32
") not less than VkPhysicalDeviceTransformFeedbackPropertiesEXT::maxTransformFeedbackBufferDataSize "
"(%" PRIu32 ").",
stream.first, stream.second, phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackBufferDataSize);
}
}
return skip;
}
bool SpirvValidator::ValidateTransformFeedbackEmitStreams(const spirv::Module &module_state, const spirv::EntryPoint &entrypoint,
const spirv::StatelessData &stateless_data, const Location &loc) const {
bool skip = false;
if (!enabled_features.transformFeedback || entrypoint.stage != VK_SHADER_STAGE_GEOMETRY_BIT) {
return skip; // GeometryStreams are only used in Geomtry Shaders
}
vvl::unordered_set<uint32_t> emitted_streams;
for (const spirv::Instruction *insn : stateless_data.transform_feedback_stream_inst) {
const uint32_t opcode = insn->Opcode();
if (opcode == spv::OpEmitStreamVertex) {
emitted_streams.emplace(module_state.GetConstantValueById(insn->Word(1)));
}
if (opcode == spv::OpEmitStreamVertex || opcode == spv::OpEndStreamPrimitive) {
uint32_t stream = module_state.GetConstantValueById(insn->Word(1));
if (stream >= phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackStreams) {
skip |= LogError("VUID-RuntimeSpirv-OpEmitStreamVertex-06310", module_state.handle(), loc,
"SPIR-V uses transform feedback stream\n%s\nwith index %" PRIu32
", which is not less than maxTransformFeedbackStreams (%" PRIu32 ").",
insn->Describe().c_str(), stream,
phys_dev_ext_props.transform_feedback_props.maxTransformFeedbackStreams);
}
}
}
const bool output_points = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::output_points_bit);
const uint32_t emitted_streams_size = static_cast<uint32_t>(emitted_streams.size());
if (emitted_streams_size > 1 && !output_points &&
phys_dev_ext_props.transform_feedback_props.transformFeedbackStreamsLinesTriangles == VK_FALSE) {
skip |= LogError("VUID-RuntimeSpirv-transformFeedbackStreamsLinesTriangles-06311", module_state.handle(), loc,
"SPIR-V emits to %" PRIu32
" vertex streams and transformFeedbackStreamsLinesTriangles "
"is VK_FALSE, but execution mode is not OutputPoints.",
emitted_streams_size);
}
return skip;
}
bool SpirvValidator::ValidateRelaxedExtendedInstruction(const spirv::Module &module_state,
const spirv::StatelessData &stateless_data, const Location &loc) const {
bool skip = false;
if (!enabled_features.shaderRelaxedExtendedInstruction && stateless_data.has_ext_inst_with_forward_refs) {
skip |= LogError("VUID-RuntimeSpirv-shaderRelaxedExtendedInstruction-10773", module_state.handle(), loc,
"SPIR-V uses OpExtInstWithForwardRefsKHR but shaderRelaxedExtendedInstruction was not enabled.\nWhen "
"using VK_KHR_shader_non_semantic_info (how you can map SPIR-V back to your source language) this "
"instruction is required when dealing with forward reference to a pointer.");
}
return skip;
}
// Checks for both TexelOffset and TexelGatherOffset limits
bool SpirvValidator::ValidateTexelOffsetLimits(const spirv::Module &module_state, const spirv::Instruction &insn,
const Location &loc) const {
bool skip = false;
const uint32_t opcode = insn.Opcode();
const bool is_image_gather = ImageGatherOperation(opcode);
if (!is_image_gather && !ImageSampleOperation(opcode) && !ImageFetchOperation(opcode)) {
return false;
}
const uint32_t image_operand_position = OpcodeImageOperandsPosition(opcode);
// Image operands can be optional
if (image_operand_position == 0 || insn.Length() <= image_operand_position) {
return false;
}
const uint32_t image_operand = insn.Word(image_operand_position);
// Bits we are validating (sample/fetch only check ConstOffset)
uint32_t offset_bits =
is_image_gather ? (spv::ImageOperandsOffsetMask | spv::ImageOperandsConstOffsetMask | spv::ImageOperandsConstOffsetsMask)
: (spv::ImageOperandsConstOffsetMask);
if ((image_operand & offset_bits) == 0) {
return false;
}
// Operand values follow
uint32_t index = image_operand_position + 1;
// Each bit has it's own operand, starts with the smallest set bit and loop to the highest bit among
// ImageOperandsOffsetMask, ImageOperandsConstOffsetMask and ImageOperandsConstOffsetsMask
for (uint32_t i = 1; i < spv::ImageOperandsConstOffsetsMask; i <<= 1) {
if ((image_operand & i) == 0) {
continue;
}
// If the bit is set, consume operand
if (insn.Length() > index && (i & offset_bits)) {
uint32_t constant_id = insn.Word(index);
const spirv::Instruction *constant = module_state.FindDef(constant_id);
const bool is_dynamic_offset = constant == nullptr;
if (!is_dynamic_offset && constant->Opcode() == spv::OpConstantComposite) {
for (uint32_t j = 3; j < constant->Length(); ++j) {
uint32_t comp_id = constant->Word(j);
const spirv::Instruction *comp = module_state.FindDef(comp_id);
const spirv::Instruction *comp_type = module_state.FindDef(comp->Word(1));
// Get operand value
const uint32_t offset = comp->Word(3);
// spec requires minTexelGatherOffset/minTexelOffset to be -8 or less so never can compare if
// unsigned spec requires maxTexelGatherOffset/maxTexelOffset to be 7 or greater so never can
// compare if signed is less than zero
const int32_t signed_offset = static_cast<int32_t>(offset);
const bool use_signed = (comp_type->Opcode() == spv::OpTypeInt && comp_type->Word(3) != 0);
// There are 2 sets of VU being covered where the only main difference is the opcode
if (is_image_gather) {
// min/maxTexelGatherOffset
if (use_signed && (signed_offset < phys_dev_props.limits.minTexelGatherOffset)) {
skip |=
LogError("VUID-RuntimeSpirv-OpImage-06376", module_state.handle(), loc,
"SPIR-V uses %s with offset (%" PRId32
") less than VkPhysicalDeviceLimits::minTexelGatherOffset (%" PRId32 ").\n%s\n",
string_SpvOpcode(insn.Opcode()), signed_offset, phys_dev_props.limits.minTexelGatherOffset,
module_state.DescribeInstruction(insn).c_str());
} else if ((offset > phys_dev_props.limits.maxTexelGatherOffset) &&
(!use_signed || (use_signed && signed_offset > 0))) {
skip |= LogError("VUID-RuntimeSpirv-OpImage-06377", module_state.handle(), loc,
"SPIR-V uses %s with offset (%" PRIu32
") greater than VkPhysicalDeviceLimits::maxTexelGatherOffset (%" PRIu32 ").\n%s\n",
string_SpvOpcode(insn.Opcode()), offset, phys_dev_props.limits.maxTexelGatherOffset,
module_state.DescribeInstruction(insn).c_str());
}
} else {
// min/maxTexelOffset
// TODO - maintenance8 added ability to use Offset, but will need validation on GPU side
if (use_signed && (signed_offset < phys_dev_props.limits.minTexelOffset)) {
skip |= LogError("VUID-RuntimeSpirv-OpImageSample-06435", module_state.handle(), loc,
"SPIR-V uses %s with offset (%" PRId32
") less than VkPhysicalDeviceLimits::minTexelOffset (%" PRId32 ").\n%s\n",
string_SpvOpcode(insn.Opcode()), signed_offset, phys_dev_props.limits.minTexelOffset,
module_state.DescribeInstruction(insn).c_str());
} else if ((offset > phys_dev_props.limits.maxTexelOffset) &&
(!use_signed || (use_signed && signed_offset > 0))) {
skip |= LogError("VUID-RuntimeSpirv-OpImageSample-06436", module_state.handle(), loc,
"SPIR-V uses %s with offset (%" PRIu32
") greater than VkPhysicalDeviceLimits::maxTexelOffset (%" PRIu32 ").\n%s\n",
string_SpvOpcode(insn.Opcode()), offset, phys_dev_props.limits.maxTexelOffset,
module_state.DescribeInstruction(insn).c_str());
}
}
}
}
}
index += ImageOperandsParamCount(i);
}
return skip;
}
bool SpirvValidator::ValidateMemoryScope(const spirv::Module &module_state, const spirv::Instruction &insn,
const Location &loc) const {
bool skip = false;
const auto &entry = OpcodeMemoryScopePosition(insn.Opcode());
if (entry > 0) {
const uint32_t scope_id = insn.Word(entry);
const spirv::Instruction *scope_def = module_state.GetAnyConstantDef(scope_id);
// Very very low chance using spec constant to set memory scope
if (scope_def && scope_def->Opcode() == spv::OpConstant) {
const spv::Scope scope_type = spv::Scope(scope_def->GetConstantValue());
if (enabled_features.vulkanMemoryModel && !enabled_features.vulkanMemoryModelDeviceScope &&
scope_type == spv::Scope::ScopeDevice) {
skip |=
LogError("VUID-RuntimeSpirv-vulkanMemoryModel-06265", module_state.handle(), loc,
"SPIR-V uses Device memory scope, but the vulkanMemoryModelDeviceScope feature was not enabled.\n%s\n",
module_state.DescribeInstruction(insn).c_str());
} else if (!enabled_features.vulkanMemoryModel && scope_type == spv::Scope::ScopeQueueFamily) {
skip |= LogError("VUID-RuntimeSpirv-vulkanMemoryModel-06266", module_state.handle(), loc,
"SPIR-V uses QueueFamily memory scope, but the vulkanMemoryModel feature was not enabled.\n%s\n",
module_state.DescribeInstruction(insn).c_str());
}
}
}
return skip;
}
bool SpirvValidator::ValidateSubgroupRotateClustered(const spirv::Module &module_state, const spirv::Instruction &insn,
const Location &loc) const {
bool skip = false;
if (!enabled_features.shaderSubgroupRotateClustered && insn.Opcode() == spv::OpGroupNonUniformRotateKHR && insn.Length() == 7) {
skip |= LogError("VUID-RuntimeSpirv-shaderSubgroupRotateClustered-09566", module_state.handle(), loc,
"SPIR-V uses ClusterSize operand, but the shaderSubgroupRotateClustered feature was not enabled.\n%s\n",
module_state.DescribeInstruction(insn).c_str());
}
return skip;
}
bool SpirvValidator::ValidateShaderStageGroupNonUniform(const spirv::Module &module_state,
const spirv::StatelessData &stateless_data, VkShaderStageFlagBits stage,
const Location &loc) const {
bool skip = false;
// Check anything using a group operation (which currently is only OpGroupNonUnifrom* operations)
for (const spirv::Instruction *group_inst : stateless_data.group_inst) {
const spirv::Instruction &insn = *group_inst;
// Check the quad operations.
if ((insn.Opcode() == spv::OpGroupNonUniformQuadBroadcast) || (insn.Opcode() == spv::OpGroupNonUniformQuadSwap)) {
if ((stage != VK_SHADER_STAGE_FRAGMENT_BIT) && (stage != VK_SHADER_STAGE_COMPUTE_BIT)) {
if (!phys_dev_props_core11.subgroupQuadOperationsInAllStages) {
skip |= LogError("VUID-RuntimeSpirv-None-06342", module_state.handle(), loc,
"Can't use for stage %s because VkPhysicalDeviceSubgroupProperties::quadOperationsInAllStages "
"is not supported on this VkDevice",
string_VkShaderStageFlagBits(stage));
}
}
}
uint32_t scope_type = spv::ScopeMax;
if (insn.Opcode() == spv::OpGroupNonUniformPartitionNV) {
// OpGroupNonUniformPartitionNV always assumed subgroup as missing operand
scope_type = spv::ScopeSubgroup;
} else {
// "All <id> used for Scope <id> must be of an OpConstant"
const spirv::Instruction *scope_id = module_state.FindDef(insn.Word(3));
scope_type = scope_id->Word(3);
}
if (scope_type == spv::ScopeSubgroup) {
// "Group operations with subgroup scope" must have stage support
const VkSubgroupFeatureFlags supported_stages = phys_dev_props_core11.subgroupSupportedStages;
if ((supported_stages & stage) == 0) {
skip |= LogError("VUID-RuntimeSpirv-None-06343", module_state.handle(), loc,
"%s is not supported in subgroupSupportedStages (%s).", string_VkShaderStageFlagBits(stage),
string_VkShaderStageFlags(supported_stages).c_str());
}
}
if (!enabled_features.shaderSubgroupExtendedTypes) {
const spirv::Instruction *type = module_state.FindDef(insn.Word(1));
if (type->IsVector()) {
// Get the element type
type = module_state.FindDef(type->Word(2));
}
if (type->Opcode() != spv::OpTypeBool) {
// Both OpTypeInt and OpTypeFloat the width is in the 2nd word.
const uint32_t width = type->Word(2);
if ((type->Opcode() == spv::OpTypeFloat && width == 16) ||
(type->Opcode() == spv::OpTypeInt && (width == 8 || width == 16 || width == 64))) {
if (!enabled_features.shaderSubgroupExtendedTypes) {
skip |= LogError(
"VUID-RuntimeSpirv-None-06275", module_state.handle(), loc,
"VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures::shaderSubgroupExtendedTypes was not enabled");
}
}
}
}
}
return skip;
}
bool SpirvValidator::ValidateShaderStageInputOutputLimits(const spirv::Module &module_state, const spirv::EntryPoint &entrypoint,
const spirv::StatelessData &stateless_data, const Location &loc) const {
const VkShaderStageFlagBits stage = entrypoint.stage;
if (stage == VK_SHADER_STAGE_COMPUTE_BIT || stage == VK_SHADER_STAGE_ALL_GRAPHICS || stage == VK_SHADER_STAGE_ALL) {
return false;
}
bool skip = false;
auto const &limits = phys_dev_props.limits;
const uint32_t num_vertices = entrypoint.execution_mode.output_vertices;
const uint32_t num_primitives = entrypoint.execution_mode.output_primitives;
const bool is_iso_lines = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::subdivision_iso_lines_bit);
const bool is_point_mode = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::point_mode_bit);
// The max is a combiniation of both the user defined variables largest values
// and
// The total components used by built ins
const auto max_input_slot = (entrypoint.max_input_slot_variable && entrypoint.max_input_slot)
? *entrypoint.max_input_slot
: spirv::InterfaceSlot(0, 0, 0, 0);
const auto max_output_slot = (entrypoint.max_output_slot_variable && entrypoint.max_output_slot)
? *entrypoint.max_output_slot
: spirv::InterfaceSlot(0, 0, 0, 0);
const uint32_t total_input_components = max_input_slot.slot + entrypoint.built_in_input_components;
const uint32_t total_output_components = max_output_slot.slot + entrypoint.built_in_output_components;
switch (stage) {
case VK_SHADER_STAGE_VERTEX_BIT:
if (total_output_components >= limits.maxVertexOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Vertex stage) output interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxVertexOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), entrypoint.built_in_output_components,
limits.maxVertexOutputComponents);
}
break;
// For tessellation, it is not clear if the built-ins should be part of the total component limits or not
// https://gitlab.khronos.org/vulkan/vulkan/-/issues/3448#note_459088
// But seems that from Zink, that GL allowed it, so likely there is some exceptions for tessellation
case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:
if (max_input_slot.slot >= limits.maxTessellationControlPerVertexInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation control stage) input interface variable (%s) "
"exceeds component limit maxTessellationControlPerVertexInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), limits.maxTessellationControlPerVertexInputComponents);
}
if (entrypoint.max_input_slot_variable) {
if (entrypoint.max_input_slot_variable->is_patch &&
max_output_slot.slot >= limits.maxTessellationControlPerPatchOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation control stage) output interface variable (%s) "
"exceeds component limit maxTessellationControlPerPatchOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), limits.maxTessellationControlPerPatchOutputComponents);
}
if (!entrypoint.max_input_slot_variable->is_patch &&
max_output_slot.slot >= limits.maxTessellationControlPerVertexOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation control stage) output interface variable (%s) "
"exceeds component limit maxTessellationControlPerVertexOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), limits.maxTessellationControlPerVertexOutputComponents);
}
}
break;
case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:
if (max_input_slot.slot >= limits.maxTessellationEvaluationInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation evaluation stage) input interface variable (%s) "
"exceeds component limit maxTessellationEvaluationInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), limits.maxTessellationEvaluationInputComponents);
}
if (max_output_slot.slot >= limits.maxTessellationEvaluationOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation evaluation stage) output interface variable (%s) "
"exceeds component limit maxTessellationEvaluationOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), limits.maxTessellationEvaluationOutputComponents);
}
// Portability validation
if (IsExtEnabled(extensions.vk_khr_portability_subset)) {
if (is_iso_lines && (VK_FALSE == enabled_features.tessellationIsolines)) {
skip |= LogError("VUID-RuntimeSpirv-tessellationShader-06326", module_state.handle(), loc,
"(portability error) SPIR-V (Tessellation evaluation stage)"
" is using abstract patch type IsoLines, but this is not supported on this platform.");
}
if (is_point_mode && (VK_FALSE == enabled_features.tessellationPointMode)) {
skip |= LogError("VUID-RuntimeSpirv-tessellationShader-06327", module_state.handle(), loc,
"(portability error) SPIR-V (Tessellation evaluation stage)"
" is using abstract patch type PointMode, but this is not supported on this platform.");
}
}
break;
case VK_SHADER_STAGE_GEOMETRY_BIT:
if (total_input_components >= limits.maxGeometryInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Geometry stage) input interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxGeometryInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), entrypoint.built_in_input_components,
limits.maxGeometryInputComponents);
}
if (total_output_components >= limits.maxGeometryOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Geometry stage) output interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxGeometryOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), entrypoint.built_in_output_components,
limits.maxGeometryOutputComponents);
}
break;
case VK_SHADER_STAGE_FRAGMENT_BIT:
if (total_input_components >= limits.maxFragmentInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Fragment stage) input interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxFragmentInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), entrypoint.built_in_input_components,
limits.maxFragmentInputComponents);
}
// Fragment output doesn't have built ins
// 1 Location == 1 color attachment
if (max_output_slot.Location() >= limits.maxFragmentOutputAttachments) {
skip |=
LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Fragment stage) output interface variable at Location %" PRIu32
" "
"exceeds the limit maxFragmentOutputAttachments (%" PRIu32 ") (note: Location are zero index based).",
max_output_slot.Location(), limits.maxFragmentOutputAttachments);
}
break;
case VK_SHADER_STAGE_RAYGEN_BIT_KHR:
case VK_SHADER_STAGE_ANY_HIT_BIT_KHR:
case VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR:
case VK_SHADER_STAGE_MISS_BIT_KHR:
case VK_SHADER_STAGE_INTERSECTION_BIT_KHR:
case VK_SHADER_STAGE_CALLABLE_BIT_KHR:
case VK_SHADER_STAGE_TASK_BIT_EXT:
break;
// Shader stage is an alias, but the ExecutionModel is not
case VK_SHADER_STAGE_MESH_BIT_EXT:
if (entrypoint.execution_model == spv::ExecutionModelMeshNV) {
if (num_vertices > phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputVertices) {
skip |= LogError("VUID-RuntimeSpirv-MeshNV-07113", module_state.handle(), loc,
"SPIR-V (Mesh stage) output vertices count exceeds the "
"maxMeshOutputVertices of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputVertices,
num_vertices - phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputVertices);
}
if (num_primitives > phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputPrimitives) {
skip |= LogError("VUID-RuntimeSpirv-MeshNV-07114", module_state.handle(), loc,
"SPIR-V (Mesh stage) output primitives count exceeds the "
"maxMeshOutputPrimitives of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputPrimitives,
num_primitives - phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputPrimitives);
}
} else if (entrypoint.execution_model == spv::ExecutionModelMeshEXT) {
if (num_vertices > phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputVertices) {
skip |= LogError("VUID-RuntimeSpirv-MeshEXT-07115", module_state.handle(), loc,
"SPIR-V (Mesh stage) output vertices count exceeds the "
"maxMeshOutputVertices of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputVertices,
num_vertices - phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputVertices);
}
if (num_primitives > phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputPrimitives) {
skip |= LogError("VUID-RuntimeSpirv-MeshEXT-07116", module_state.handle(), loc,
"SPIR-V (Mesh stage) output primitives count exceeds the "
"maxMeshOutputPrimitives of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputPrimitives,
num_primitives - phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputPrimitives);
}
}
break;
default:
assert(false); // This should never happen
}
// maxFragmentCombinedOutputResources
//
// This limit was created from Vulkan 1.0, with the move to bindless, this limit has slowly become less relevant, if using
// descriptor indexing, the limit should basically be UINT32_MAX
if (stage == VK_SHADER_STAGE_FRAGMENT_BIT && !IsExtEnabled(extensions.vk_ext_descriptor_indexing)) {
// Variables can be aliased, so use Location to mark things as unique
vvl::unordered_set<uint32_t> color_attachments;
for (const auto *variable : entrypoint.user_defined_interface_variables) {
if (variable->storage_class == spv::StorageClassOutput && variable->decorations.location != spirv::kInvalidValue) {
// even if using an array of attachments in the shader, each used variable of the array is represented by a single
// variable
color_attachments.insert(variable->decorations.location);
}
}
// unordered_set requires to define hashing, and these should be very small and cheap as is
std::set<std::pair<uint32_t, uint32_t>> storage_buffers;
std::set<std::pair<uint32_t, uint32_t>> storage_images;
for (const auto &variable : entrypoint.resource_interface_variables) {
if (!variable.IsAccessed()) continue;
if (variable.is_storage_buffer) {
storage_buffers.insert(std::make_pair(variable.decorations.set, variable.decorations.binding));
} else if (variable.is_storage_image || variable.is_storage_texel_buffer) {
storage_images.insert(std::make_pair(variable.decorations.set, variable.decorations.binding));
}
}
const uint32_t total_output = (uint32_t)(color_attachments.size() + storage_buffers.size() + storage_images.size());
if (total_output > limits.maxFragmentCombinedOutputResources) {
skip |= LogError("VUID-RuntimeSpirv-Location-06428", module_state.handle(), loc,
"SPIR-V (Fragment stage) output contains %zu storage buffer bindings, %zu storage image bindings, and "
"%zu color attachments which together is %" PRIu32
" which exceeds the limit maxFragmentCombinedOutputResources (%" PRIu32 ").",
storage_buffers.size(), storage_images.size(), color_attachments.size(), total_output,
limits.maxFragmentCombinedOutputResources);
}
}
return skip;
}
bool SpirvValidator::ValidateShaderStageInterfaceVariables(const spirv::Module &module_state, const spirv::EntryPoint &entrypoint,
const spirv::StatelessData &stateless_data, const Location &loc) const {
bool skip = false;
if (entrypoint.stage == VK_SHADER_STAGE_MESH_BIT_EXT && !enabled_features.primitiveFragmentShadingRateMeshShader &&
entrypoint.HasBuiltIn(spv::BuiltInPrimitiveShadingRateKHR)) {
skip |= LogError("VUID-PrimitiveShadingRateKHR-PrimitiveShadingRateKHR-12275", module_state.handle(), loc,
"SPIR-V (Mesh stage) declared PrimitiveShadingRateKHR, but the primitiveFragmentShadingRateMeshShader "
"feature was not enabled.");
}
return skip;
}
bool SpirvValidator::ValidateShaderFloatControl(const spirv::Module &module_state, const spirv::EntryPoint &entrypoint,
const spirv::StatelessData &stateless_data, const Location &loc) const {
bool skip = false;
// Need to wrap otherwise phys_dev_props_core12 can be junk
if (!IsExtEnabled(extensions.vk_khr_shader_float_controls)) {
return skip;
}
if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::signed_zero_inf_nan_preserve_width_16) &&
!phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat16-06293", module_state.handle(), loc,
"SPIR-V requires SignedZeroInfNanPreserve for bit width 16 but it is not enabled on the device.");
} else if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::signed_zero_inf_nan_preserve_width_32) &&
!phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat32-06294", module_state.handle(), loc,
"SPIR-V requires SignedZeroInfNanPreserve for bit width 32 but it is not enabled on the device.");
} else if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::signed_zero_inf_nan_preserve_width_64) &&
!phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat64-06295", module_state.handle(), loc,
"SPIR-V requires SignedZeroInfNanPreserve for bit width 64 but it is not enabled on the device.");
}
const bool has_denorm_preserve_width_16 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::denorm_preserve_width_16);
const bool has_denorm_preserve_width_32 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::denorm_preserve_width_32);
const bool has_denorm_preserve_width_64 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::denorm_preserve_width_64);
if (has_denorm_preserve_width_16 && !phys_dev_props_core12.shaderDenormPreserveFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderDenormPreserveFloat16-06296", module_state.handle(), loc,
"SPIR-V requires DenormPreserve for bit width 16 but it is not enabled on the device.");
} else if (has_denorm_preserve_width_32 && !phys_dev_props_core12.shaderDenormPreserveFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderDenormPreserveFloat32-06297", module_state.handle(), loc,
"SPIR-V requires DenormPreserve for bit width 32 but it is not enabled on the device.");
} else if (has_denorm_preserve_width_64 && !phys_dev_props_core12.shaderDenormPreserveFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderDenormPreserveFloat64-06298", module_state.handle(), loc,
"SPIR-V requires DenormPreserve for bit width 64 but it is not enabled on the device.");
}
const bool has_denorm_flush_to_zero_width_16 =
entrypoint.execution_mode.Has(spirv::ExecutionModeSet::denorm_flush_to_zero_width_16);
const bool has_denorm_flush_to_zero_width_32 =
entrypoint.execution_mode.Has(spirv::ExecutionModeSet::denorm_flush_to_zero_width_32);
const bool has_denorm_flush_to_zero_width_64 =
entrypoint.execution_mode.Has(spirv::ExecutionModeSet::denorm_flush_to_zero_width_64);
if (has_denorm_flush_to_zero_width_16 && !phys_dev_props_core12.shaderDenormFlushToZeroFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderDenormFlushToZeroFloat16-06299", module_state.handle(), loc,
"SPIR-V requires DenormFlushToZero for bit width 16 but it is not enabled on the device.");
} else if (has_denorm_flush_to_zero_width_32 && !phys_dev_props_core12.shaderDenormFlushToZeroFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderDenormFlushToZeroFloat32-06300", module_state.handle(), loc,
"SPIR-V requires DenormFlushToZero for bit width 32 but it is not enabled on the device.");
} else if (has_denorm_flush_to_zero_width_64 && !phys_dev_props_core12.shaderDenormFlushToZeroFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderDenormFlushToZeroFloat64-06301", module_state.handle(), loc,
"SPIR-V requires DenormFlushToZero for bit width 64 but it is not enabled on the device.");
}
const bool has_rounding_mode_rte_width_16 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::rounding_mode_rte_width_16);
const bool has_rounding_mode_rte_width_32 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::rounding_mode_rte_width_32);
const bool has_rounding_mode_rte_width_64 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::rounding_mode_rte_width_64);
if (has_rounding_mode_rte_width_16 && !phys_dev_props_core12.shaderRoundingModeRTEFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderRoundingModeRTEFloat16-06302", module_state.handle(), loc,
"SPIR-V requires RoundingModeRTE for bit width 16 but it is not enabled on the device.");
} else if (has_rounding_mode_rte_width_32 && !phys_dev_props_core12.shaderRoundingModeRTEFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderRoundingModeRTEFloat32-06303", module_state.handle(), loc,
"SPIR-V requires RoundingModeRTE for bit width 32 but it is not enabled on the device.");
} else if (has_rounding_mode_rte_width_64 && !phys_dev_props_core12.shaderRoundingModeRTEFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderRoundingModeRTEFloat64-06304", module_state.handle(), loc,
"SPIR-V requires RoundingModeRTE for bit width 64 but it is not enabled on the device.");
}
const bool has_rounding_mode_rtz_width_16 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::rounding_mode_rtz_width_16);
const bool has_rounding_mode_rtz_width_32 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::rounding_mode_rtz_width_32);
const bool has_rounding_mode_rtz_width_64 = entrypoint.execution_mode.Has(spirv::ExecutionModeSet::rounding_mode_rtz_width_64);
if (has_rounding_mode_rtz_width_16 && !phys_dev_props_core12.shaderRoundingModeRTZFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderRoundingModeRTZFloat16-06305", module_state.handle(), loc,
"SPIR-V requires RoundingModeRTZ for bit width 16 but it is not enabled on the device.");
} else if (has_rounding_mode_rtz_width_32 && !phys_dev_props_core12.shaderRoundingModeRTZFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderRoundingModeRTZFloat32-06306", module_state.handle(), loc,
"SPIR-V requires RoundingModeRTZ for bit width 32 but it is not enabled on the device.");
} else if (has_rounding_mode_rtz_width_64 && !phys_dev_props_core12.shaderRoundingModeRTZFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderRoundingModeRTZFloat64-06307", module_state.handle(), loc,
"SPIR-V requires RoundingModeRTZ for bit width 64 but it is not enabled on the device.");
}
auto get_float_width = [&module_state](uint32_t id) {
const auto *insn = module_state.FindDef(id);
if (!insn || insn->Opcode() != spv::OpTypeFloat) {
return 0u;
} else {
return insn->Word(2);
}
};
const uint32_t mask = spv::FPFastMathModeNotNaNMask | spv::FPFastMathModeNotInfMask | spv::FPFastMathModeNSZMask;
if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::fp_fast_math_default)) {
for (const spirv::Instruction *insn : stateless_data.execution_mode_id_inst) {
const uint32_t mode = insn->Word(2);
if (mode != spv::ExecutionModeFPFastMathDefault) {
continue;
}
// spirv-val will catch if this is not a constant
const auto *fast_math_mode = module_state.FindDef(insn->Word(4));
const bool has_mask = (fast_math_mode->GetConstantValue() & mask) == mask;
if (has_mask) {
continue; // nothing to validate
}
const uint32_t bit_width = get_float_width(insn->Word(3));
if (bit_width == 16 && !phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat16-09559", module_state.handle(), loc,
"shaderSignedZeroInfNanPreserveFloat16 is false, but FPFastMathDefault is setting 16-bit floats "
"with modes 0x%" PRIx32 ".",
fast_math_mode->GetConstantValue());
} else if (bit_width == 32 && !phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat32-09561", module_state.handle(), loc,
"shaderSignedZeroInfNanPreserveFloat32 is false, but FPFastMathDefault is setting 32-bit floats "
"with modes 0x%" PRIx32 ".",
fast_math_mode->GetConstantValue());
} else if (bit_width == 64 && !phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat64-09563", module_state.handle(), loc,
"shaderSignedZeroInfNanPreserveFloat64 is false, but FPFastMathDefault is setting 64-bit floats "
"with modes 0x%" PRIx32 ".",
fast_math_mode->GetConstantValue());
}
}
}
for (const spirv::Instruction *insn : module_state.static_data_.decoration_inst) {
uint32_t decoration = insn->Word(2);
if (decoration != spv::DecorationFPFastMathMode) {
continue;
}
uint32_t modes = insn->Word(3);
const bool has_mask = (modes & mask) == mask;
if (has_mask) {
continue; // nothing to validate
}
// See if target instruction uses any floats of various bit widths
bool float_16 = false;
bool float_32 = false;
bool float_64 = false;
uint32_t operand_index = 2; // if using OpDecoration, this instruction must have a ResultId
const auto *target_insn = module_state.FindDef(insn->Word(1));
if (target_insn->TypeId() != 0) {
operand_index++;
const uint32_t bit_width = get_float_width(target_insn->TypeId());
float_16 |= bit_width == 16;
float_32 |= bit_width == 32;
float_64 |= bit_width == 64;
}
for (; operand_index < target_insn->Length(); operand_index++) {
const uint32_t bit_width = get_float_width(target_insn->Word(operand_index));
float_16 |= bit_width == 16;
float_32 |= bit_width == 32;
float_64 |= bit_width == 64;
}
if (float_16 && !phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat16) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat16-09560", module_state.handle(), loc,
"shaderSignedZeroInfNanPreserveFloat16 is false, FPFastMathMode has modes 0x%" PRIx32
" but the target instruction is using 16-bit floats.\n%s\n",
modes, module_state.DescribeInstruction(*target_insn).c_str());
} else if (float_32 && !phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat32) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat32-09562", module_state.handle(), loc,
"shaderSignedZeroInfNanPreserveFloat32 is false, FPFastMathMode has modes 0x%" PRIx32
" but the target instruction is using 32-bit floats.\n%s\n",
modes, module_state.DescribeInstruction(*target_insn).c_str());
} else if (float_64 && !phys_dev_props_core12.shaderSignedZeroInfNanPreserveFloat64) {
skip |= LogError("VUID-RuntimeSpirv-shaderSignedZeroInfNanPreserveFloat64-09564", module_state.handle(), loc,
"shaderSignedZeroInfNanPreserveFloat64 is false, FPFastMathMode has modes 0x%" PRIx32
" but the target instruction is using 64-bit floats.\n%s\n",
modes, module_state.DescribeInstruction(*target_insn).c_str());
}
}
return skip;
}
bool SpirvValidator::ValidateExecutionModes(const spirv::Module &module_state, const spirv::EntryPoint &entrypoint,
const spirv::StatelessData &stateless_data, const Location &loc) const {
bool skip = false;
const VkShaderStageFlagBits stage = entrypoint.stage;
if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::local_size_id_bit)) {
// Special case to print error by extension and feature bit
if (!enabled_features.maintenance4) {
skip |= LogError("VUID-RuntimeSpirv-LocalSizeId-06434", module_state.handle(), loc,
"SPIR-V OpExecutionMode LocalSizeId is used but maintenance4 feature was not enabled.");
}
if (!IsExtEnabled(extensions.vk_khr_maintenance4)) {
skip |= LogError("VUID-RuntimeSpirv-LocalSizeId-06434", module_state.handle(), loc,
"SPIR-V OpExecutionMode LocalSizeId is used but maintenance4 extension is not enabled and used "
"Vulkan api version is 1.2 or less.");
}
}
if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::subgroup_uniform_control_flow_bit)) {
if (!enabled_features.shaderSubgroupUniformControlFlow ||
(phys_dev_ext_props.subgroup_props.supportedStages & stage) == 0 || stateless_data.has_invocation_repack_instruction) {
std::ostringstream msg;
if (!enabled_features.shaderSubgroupUniformControlFlow) {
msg << "shaderSubgroupUniformControlFlow feature must be enabled";
} else if ((phys_dev_ext_props.subgroup_props.supportedStages & stage) == 0) {
msg << "stage" << string_VkShaderStageFlagBits(stage)
<< " must be in VkPhysicalDeviceSubgroupProperties::supportedStages("
<< string_VkShaderStageFlags(phys_dev_ext_props.subgroup_props.supportedStages) << ")";
} else {
msg << "the shader must not use any invocation repack instructions";
}
skip |= LogError("VUID-RuntimeSpirv-SubgroupUniformControlFlowKHR-06379", module_state.handle(), loc,
"SPIR-V uses ExecutionModeSubgroupUniformControlFlowKHR, but %s.", msg.str().c_str());
}
}
if ((stage == VK_SHADER_STAGE_MESH_BIT_EXT || stage == VK_SHADER_STAGE_TASK_BIT_EXT) &&
!phys_dev_ext_props.compute_shader_derivatives_props.meshAndTaskShaderDerivatives) {
if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::derivative_group_linear)) {
skip |=
LogError("VUID-RuntimeSpirv-meshAndTaskShaderDerivatives-10153", module_state.handle(), loc,
"SPIR-V uses DerivativeGroupLinearKHR in a %s shader, but meshAndTaskShaderDerivatives is not supported.",
string_VkShaderStageFlagBits(stage));
} else if (entrypoint.execution_mode.Has(spirv::ExecutionModeSet::derivative_group_quads)) {
skip |=
LogError("VUID-RuntimeSpirv-meshAndTaskShaderDerivatives-10153", module_state.handle(), loc,
"SPIR-V uses DerivativeGroupQuadsKHR in a %s shader, but meshAndTaskShaderDerivatives is not supported.",
string_VkShaderStageFlagBits(stage));
}
}
return skip;
}
bool SpirvValidator::ValidateConservativeRasterization(const spirv::Module &module_state, const spirv::EntryPoint &entrypoint,
const spirv::StatelessData &stateless_data, const Location &loc) const {
bool skip = false;
// only new to validate if property is not enabled
if (phys_dev_ext_props.conservative_rasterization_props.conservativeRasterizationPostDepthCoverage) {
return skip;
}
if (stateless_data.has_built_in_fully_covered &&
entrypoint.execution_mode.Has(spirv::ExecutionModeSet::post_depth_coverage_bit)) {
skip |= LogError("VUID-FullyCoveredEXT-conservativeRasterizationPostDepthCoverage-04235", module_state.handle(), loc,
"SPIR-V (Fragment stage) has a\nOpExecutionMode EarlyFragmentTests\nOpDecorate BuiltIn "
"FullyCoveredEXT\nbut conservativeRasterizationPostDepthCoverage was not enabled.");
}
return skip;
}
bool SpirvValidator::ValidateShaderTensor(const spirv::Module &module_state, const spirv::EntryPoint &entrypoint, const spirv::StatelessData &stateless_data, const Location &loc) const {
bool skip = false;
// check only shaders
if (entrypoint.is_data_graph) {
return skip;
}
if (module_state.HasCapability(spv::CapabilityGraphARM)) {
skip |= LogError("VUID-RuntimeSpirv-GraphARM-09922", module_state.handle(), loc,
"%s, stage %s, includes 'OpCapability GraphARM'.", FormatHandle(module_state.handle()).c_str(),
string_VkShaderStageFlagBits(entrypoint.stage));
}
for (auto &instruction : stateless_data.tensor_inst) {
if ((entrypoint.stage & phys_dev_ext_props.tensor_properties.shaderTensorSupportedStages) == 0) {
skip |= LogError("VUID-RuntimeSpirv-shaderTensorSupportedStages-09901", module_state.handle(), loc,
"'%s' is a tensor instruction but not supported in %s; supported stages are %s.",
instruction->Describe().c_str(), string_VkShaderStageFlagBits(entrypoint.stage),
string_VkShaderStageFlags(phys_dev_ext_props.tensor_properties.shaderTensorSupportedStages).c_str());
}
if (instruction->Opcode() == spv::OpTypeTensorARM) {
// to be legal in a shader, a tensor must have rank and no shape, i.e. exactly 4 words
auto nWords = instruction->Length();
if (nWords > 4) {
skip |= LogError("VUID-RuntimeSpirv-OpTypeTensorARM-09902", module_state.handle(), loc,
"'%s' defines a tensor with a shape.", instruction->Describe().c_str());
} else if (nWords < 4) {
skip |= LogError("VUID-RuntimeSpirv-OpTypeTensorARM-09907", module_state.handle(), loc,
"'%s' defines a tensor with no rank.", instruction->Describe().c_str());
}
}
if (instruction->Opcode() == spv::OpTensorReadARM || instruction->Opcode() == spv::OpTensorWriteARM) {
// for read, the size to copy is defined as a type in the OpTensorReadARM instruction itself;
// for write it's defined with an object, whose type is defined in a previous instruction
const spirv::Instruction *copy_definition_instr =
(instruction->Opcode() == spv::OpTensorReadARM) ? instruction : module_state.FindDef(instruction->Word(3));
const spirv::Instruction *copy_object_type = module_state.FindDef(copy_definition_instr->Word(1));
const spirv::Instruction *element_type = module_state.FindDef(module_state.GetBaseType(copy_object_type));
// the type can be an OpTypeArray or a scalar type: computing array_elements this way works for both cases
uint32_t copy_object_type_bytes = module_state.GetTypeBytesSize(copy_object_type);
uint32_t element_bytes = module_state.GetTypeBytesSize(element_type);
uint32_t array_elements = copy_object_type_bytes / element_bytes;
if (array_elements > phys_dev_ext_props.tensor_properties.maxTensorShaderAccessArrayLength) {
skip |= LogError("VUID-RuntimeSpirv-maxTensorShaderAccessArrayLength-09903", module_state.handle(), loc,
"'%s' accesses %" PRIu32 " elements, more than maxTensorShaderAccessArrayLength (%" PRIu32 ").",
instruction->Describe().c_str(), array_elements,
phys_dev_ext_props.tensor_properties.maxTensorShaderAccessArrayLength);
}
if (copy_object_type_bytes > phys_dev_ext_props.tensor_properties.maxTensorShaderAccessSize) {
skip |= LogError("VUID-RuntimeSpirv-maxTensorShaderAccessSize-09904", module_state.handle(), loc,
"'%s' accesses %" PRIu32 " bytes, more than maxTensorShaderAccessSize (%" PRIu32 ").",
instruction->Describe().c_str(), copy_object_type_bytes,
phys_dev_ext_props.tensor_properties.maxTensorShaderAccessSize);
}
}
}
return skip;
}
} // namespace stateless