Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2026-08-31 04:48:00 +00:00
committed by Branimir Karadžić
parent 1e2e22d983
commit 31ca235db8
75 changed files with 13718 additions and 10621 deletions
+1 -1
View File
@@ -1 +1 @@
"v2026.2", "SPIRV-Tools v2026.2 v2026.2-55-g18e99045"
"v2026.4", "SPIRV-Tools v2026.4 v2026.3-49-g2736d1f8"
File diff suppressed because it is too large Load Diff
@@ -68,6 +68,7 @@ enum Extension : uint32_t {
kSPV_ARM_graph,
kSPV_ARM_tensors,
kSPV_EXT_arithmetic_fence,
kSPV_EXT_cooperative_matrix_maintenance1,
kSPV_EXT_demote_to_helper_invocation,
kSPV_EXT_descriptor_heap,
kSPV_EXT_descriptor_indexing,
@@ -77,6 +78,7 @@ enum Extension : uint32_t {
kSPV_EXT_fragment_shader_interlock,
kSPV_EXT_long_vector,
kSPV_EXT_mesh_shader,
kSPV_EXT_ocp_microscaling_types,
kSPV_EXT_opacity_micromap,
kSPV_EXT_optnone,
kSPV_EXT_physical_storage_buffer,
@@ -221,10 +223,15 @@ enum Extension : uint32_t {
kSPV_NV_stereo_view_rendering,
kSPV_NV_tensor_addressing,
kSPV_NV_viewport_array2,
kSPV_QCOM_bfloat16_muladd,
kSPV_QCOM_cooperative_matrix_conversion,
kSPV_QCOM_image_processing,
kSPV_QCOM_image_processing2,
kSPV_QCOM_image_processing3,
kSPV_QCOM_multiple_wait_queues,
kSPV_QCOM_subgroup_size,
kSPV_QCOM_tile_shading,
kSPV_SAMSUNG_intrinsic,
kSPV_VALIDATOR_ignore_type_decl_unique,
kSPV_VALVE_mixed_float_dot_product,
};
+3 -2
View File
@@ -14,7 +14,7 @@
{13, "Google", "Shaderc over Glslang", "Google Shaderc over Glslang"},
{14, "Google", "spiregg", "Google spiregg"},
{15, "Google", "rspirv", "Google rspirv"},
{16, "X-LEGEND", "Mesa-IR/SPIR-V Translator", "X-LEGEND Mesa-IR/SPIR-V Translator"},
{16, "X-LEGEND", "Mesa3D GLSL-IR/SPIR-V Translator", "X-LEGEND Mesa3D GLSL-IR/SPIR-V Translator"},
{17, "Khronos", "SPIR-V Tools Linker", "Khronos SPIR-V Tools Linker"},
{18, "Wine", "VKD3D Shader Compiler", "Wine VKD3D Shader Compiler"},
{19, "Tellusim", "Clay Shader Compiler", "Tellusim Clay Shader Compiler"},
@@ -48,4 +48,5 @@
{47, "ARM", "", "ARM"},
{48, "Goopax", "", "Goopax"},
{49, "Icyllis Milica", "Arc3D Shader Compiler", "Icyllis Milica Arc3D Shader Compiler"},
{50, "Isochron", "Pred", "Isochron Pred"},
{50, "Isochron", "Pred", "Isochron Pred"},
{51, "apilatosba", "Apila Jai Compiler", "apilatosba Apila Jai Compiler"},
+28 -2
View File
@@ -338,6 +338,9 @@ typedef enum spv_operand_type_t {
SPV_OPERAND_TYPE_OPTIONAL_CAPABILITY,
SPV_OPERAND_TYPE_VARIABLE_CAPABILITY,
// SPV_QCOM_image_processing3
SPV_OPERAND_TYPE_GATHER_MODES,
// This is a sentinel value, and does not represent an operand type.
// It should come last.
SPV_OPERAND_TYPE_NUM_OPERAND_TYPES,
@@ -399,6 +402,11 @@ typedef enum spv_fp_encoding_t {
SPV_FP_ENCODING_BFLOAT16,
SPV_FP_ENCODING_FLOAT8_E4M3,
SPV_FP_ENCODING_FLOAT8_E5M2,
SPV_FP_ENCODING_FLOAT6_E2M3,
SPV_FP_ENCODING_FLOAT6_E3M2,
SPV_FP_ENCODING_FLOAT4_E2M1,
SPV_FP_ENCODING_FLOAT8_UNSIGNED_E8M0,
SPV_FP_ENCODING_MXINT8,
} spv_fp_encoding_t;
typedef enum spv_text_to_binary_options_t {
@@ -781,6 +789,24 @@ SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowVulkan32BitBitwise(
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetFriendlyNames(
spv_validator_options options, bool val);
// Sets custom size and alignment for buffer and acceleration structure
// descriptor heap resources.
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetBufferDescriptorLayout(
spv_validator_options options, uint32_t size, uint32_t alignment);
// Sets custom size and alignment for image and sampled image descriptor heap
// resources.
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetImageDescriptorLayout(
spv_validator_options options, uint32_t size, uint32_t alignment);
// Sets custom size and alignment for sampler descriptor heap resources.
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetSamplerDescriptorLayout(
spv_validator_options options, uint32_t size, uint32_t alignment);
// Sets custom size and alignment for tensor descriptor heap resources.
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetTensorDescriptorLayout(
spv_validator_options options, uint32_t size, uint32_t alignment);
// Creates an optimizer options object with default options. Returns a valid
// options object. The object remains valid until it is passed into
// |spvOptimizerOptionsDestroy|.
@@ -1010,8 +1036,8 @@ SPIRV_TOOLS_EXPORT spv_result_t spvBinaryParse(
// The optimizer interface.
// A pointer to a function that accepts a log message from an optimizer.
typedef void (*spv_message_consumer)(
spv_message_level_t, const char*, const spv_position_t*, const char*);
typedef void (*spv_message_consumer)(spv_message_level_t, const char*,
const spv_position_t*, const char*);
// Creates and returns an optimizer object. This object must be passed to
// optimizer APIs below and is valid until passed to spvOptimizerDestroy.
+22
View File
@@ -138,6 +138,28 @@ class SPIRV_TOOLS_EXPORT ValidatorOptions {
spvValidatorOptionsSetAllowVulkan32BitBitwise(options_, val);
}
// Sets custom size and alignment for buffer and acceleration structure
// descriptor heap resources.
void SetBufferDescriptorLayout(uint32_t size, uint32_t alignment) {
spvValidatorOptionsSetBufferDescriptorLayout(options_, size, alignment);
}
// Sets custom size and alignment for image and sampled image descriptor heap
// resources.
void SetImageDescriptorLayout(uint32_t size, uint32_t alignment) {
spvValidatorOptionsSetImageDescriptorLayout(options_, size, alignment);
}
// Sets custom size and alignment for sampler descriptor heap resources.
void SetSamplerDescriptorLayout(uint32_t size, uint32_t alignment) {
spvValidatorOptionsSetSamplerDescriptorLayout(options_, size, alignment);
}
// Sets custom size and alignment for tensor descriptor heap resources.
void SetTensorDescriptorLayout(uint32_t size, uint32_t alignment) {
spvValidatorOptionsSetTensorDescriptorLayout(options_, size, alignment);
}
// Records whether or not the validator should relax the rules on pointer
// usage in logical addressing mode.
//
@@ -722,6 +722,8 @@ Optimizer::PassToken CreateSSARewritePass();
// redundancy elimination and DCE.
Optimizer::PassToken CreateConvertRelaxedToHalfPass();
Optimizer::PassToken CreateConvertToUntypedPass();
// Create relax float ops pass.
// This pass decorates all float32 result instructions with RelaxedPrecision
// if not already so decorated.
+6 -2
View File
@@ -153,12 +153,16 @@ const SpecConstantOpcodeEntry kOpSpecConstantOpcodes[] = {
CASE(InBoundsAccessChain),
CASE(PtrAccessChain),
CASE(InBoundsPtrAccessChain),
CASE(UntypedAccessChainKHR),
CASE(UntypedInBoundsAccessChainKHR),
CASE(UntypedPtrAccessChainKHR),
CASE(UntypedInBoundsPtrAccessChainKHR),
CASE(CooperativeMatrixLengthNV),
CASE(CooperativeMatrixLengthKHR)
};
// The 60 is determined by counting the opcodes listed in the spec.
static_assert(61 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
// The count is determined by counting the opcodes listed in the spec.
static_assert(65 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
"OpSpecConstantOp opcode table is incomplete");
#undef CASE
// clang-format on
+3 -1
View File
@@ -489,6 +489,7 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
// Assume non-numeric values. This will be updated for literal numbers.
parsed_operand.number_kind = SPV_NUMBER_NONE;
parsed_operand.number_bit_width = 0;
parsed_operand.fp_encoding = SPV_FP_ENCODING_UNKNOWN;
if (_.word_index >= _.num_words)
return exhaustedInputDiagnostic(inst_offset, opcode, type);
@@ -744,7 +745,8 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
case SPV_OPERAND_TYPE_HOST_ACCESS_QUALIFIER:
case SPV_OPERAND_TYPE_LOAD_CACHE_CONTROL:
case SPV_OPERAND_TYPE_STORE_CACHE_CONTROL:
case SPV_OPERAND_TYPE_NAMED_MAXIMUM_NUMBER_OF_REGISTERS: {
case SPV_OPERAND_TYPE_NAMED_MAXIMUM_NUMBER_OF_REGISTERS:
case SPV_OPERAND_TYPE_GATHER_MODES: {
// A single word that is a plain enum value.
// Map an optional operand type to its corresponding concrete type.
+4 -3
View File
@@ -599,7 +599,7 @@ spv_result_t DisassembleTargetInstruction(
return SPV_SUCCESS;
}
uint32_t GetLineLengthWithoutColor(const std::string line) {
uint32_t GetLineLengthWithoutColor(const std::string& line) {
// Currently, every added color is in the form \x1b...m, so instead of doing a
// lot of string comparisons with spvtools::clr::* strings, we just ignore
// those ranges.
@@ -804,11 +804,12 @@ void InstructionDisassembler::EmitInstructionImpl(
comment_separator = ", ";
}
stream_ << line.str();
const std::string line_str = line.str();
stream_ << line_str;
if (!comments.str().empty()) {
// Align the comments
const uint32_t line_length = GetLineLengthWithoutColor(line.str());
const uint32_t line_length = GetLineLengthWithoutColor(line_str);
uint32_t align = std::max(
{line_length + 2, last_instruction_comment_alignment_, kCommentColumn});
// Round up the alignment to a multiple of 4 for more niceness.
+23 -8
View File
@@ -119,6 +119,7 @@ spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
const DefUseManager& def_use_manager,
const DecorationManager& decoration_manager,
bool allow_partial_linkage,
bool allow_multiple_exports,
LinkageTable* linkings_to_do);
// Checks that for each pair of import and export, the import and export have
@@ -416,6 +417,7 @@ spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
const DefUseManager& def_use_manager,
const DecorationManager& decoration_manager,
bool allow_partial_linkage,
bool allow_multiple_exports,
LinkageTable* linkings_to_do) {
spv_position_t position = {};
@@ -504,6 +506,23 @@ spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
<< possible_export.second.name << "\".";
}
// Detect multiple strong definitions of the same symbol.
// The import-matching loop below only visits symbols that are imported, so it
// would silently accept multiple exports when nothing imports them. Check
// all exports here so that duplicates are always rejected.
//
// The function-variants (multitarget) flow intentionally keeps several
// exports of the same name (one per variant), so skip the check in that case.
if (!allow_multiple_exports) {
for (const auto& exp : exports) {
if (exp.second.size() > 1u)
return DiagnosticStream(position, consumer, "",
SPV_ERROR_INVALID_BINARY)
<< "Too many external references, " << exp.second.size()
<< ", were found for \"" << exp.first << "\".";
}
}
// Find the import/export pairs
for (const auto& import : imports) {
std::vector<LinkageSymbolInfo> possible_exports;
@@ -512,10 +531,6 @@ spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
if (possible_exports.empty() && !allow_partial_linkage)
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
<< "Unresolved external reference to \"" << import.name << "\".";
else if (possible_exports.size() > 1u)
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
<< "Too many external references, " << possible_exports.size()
<< ", were found for \"" << import.name << "\".";
if (!possible_exports.empty())
linkings_to_do->emplace_back(import, possible_exports.front());
@@ -910,10 +925,10 @@ spv_result_t Link(const Context& context, const uint32_t* const* binaries,
// Phase 5: Find the import/export pairs
LinkageTable linkings_to_do;
res = GetImportExportPairs(consumer, linked_context,
*linked_context.get_def_use_mgr(),
*linked_context.get_decoration_mgr(),
options.GetAllowPartialLinkage(), &linkings_to_do);
res = GetImportExportPairs(
consumer, linked_context, *linked_context.get_def_use_mgr(),
*linked_context.get_decoration_mgr(), options.GetAllowPartialLinkage(),
make_multitarget, &linkings_to_do);
if (res != SPV_SUCCESS) return res;
// Phase 6: Ensure the import and export have the same types and decorations.
+33 -4
View File
@@ -78,13 +78,21 @@ void FriendlyNameMapper::SaveName(uint32_t id,
const std::string& suggested_name) {
if (name_for_id_.find(id) != name_for_id_.end()) return;
const std::string sanitized_suggested_name = Sanitize(suggested_name);
std::string name = sanitized_suggested_name;
std::string base_name;
// Limit the size of the name to limit memory usage.
const uint32_t kMaxSize = 256;
if (suggested_name.size() > kMaxSize) {
base_name = to_string(id);
} else {
base_name = Sanitize(suggested_name);
}
std::string name = base_name;
auto inserted = used_names_.insert(name);
if (!inserted.second) {
const std::string base_name = sanitized_suggested_name + "_";
const std::string base_name_prefix = base_name + "_";
for (uint32_t index = 0; !inserted.second; ++index) {
name = base_name + to_string(index);
name = base_name_prefix + to_string(index);
inserted = used_names_.insert(name);
}
}
@@ -227,6 +235,27 @@ spv_result_t FriendlyNameMapper::ParseInstruction(
SaveName(result_id, "fp8e5m2");
break;
}
if (spv::FPEncoding(inst.words[3]) == spv::FPEncoding::Float6E2M3EXT) {
SaveName(result_id, "fp6e2m3");
break;
}
if (spv::FPEncoding(inst.words[3]) == spv::FPEncoding::Float6E3M2EXT) {
SaveName(result_id, "fp6e3m2");
break;
}
if (spv::FPEncoding(inst.words[3]) == spv::FPEncoding::Float4E2M1EXT) {
SaveName(result_id, "fp4e2m1");
break;
}
if (spv::FPEncoding(inst.words[3]) ==
spv::FPEncoding::Float8UnsignedE8M0EXT) {
SaveName(result_id, "fp8e8m0");
break;
}
if (spv::FPEncoding(inst.words[3]) == spv::FPEncoding::MXInt8EXT) {
SaveName(result_id, "mxint8");
break;
}
}
switch (bit_width) {
case 16:
+19 -2
View File
@@ -225,6 +225,8 @@ const char* spvOperandTypeStr(spv_operand_type_t type) {
return "NonSemantic.Shader.DebugInfo.100 debug operation";
case SPV_OPERAND_TYPE_SHDEBUG100_DEBUG_TYPE_QUALIFIER:
return "NonSemantic.Shader.DebugInfo.100 debug type qualifier";
case SPV_OPERAND_TYPE_GATHER_MODES:
return "gather modes";
case SPV_OPERAND_TYPE_NONE:
return "NONE";
@@ -347,6 +349,7 @@ bool spvOperandIsConcrete(spv_operand_type_t type) {
case SPV_OPERAND_TYPE_SHDEBUG100_DEBUG_INFO_FLAGS:
case SPV_OPERAND_TYPE_SHDEBUG100_DEBUG_OPERATION:
case SPV_OPERAND_TYPE_SHDEBUG100_DEBUG_TYPE_QUALIFIER:
case SPV_OPERAND_TYPE_GATHER_MODES:
return true;
default:
break;
@@ -577,10 +580,10 @@ std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
case spv::Op::OpTypeArray:
out = [](unsigned index) { return index == 1; };
break;
case spv::Op::OpCooperativeMatrixPerElementOpNV:
case spv::Op::OpCooperativeMatrixPerElementOpEXT:
out = [](unsigned index) { return index == 3; };
break;
case spv::Op::OpCooperativeMatrixReduceNV:
case spv::Op::OpCooperativeMatrixReduceEXT:
out = [](unsigned index) { return index == 4; };
break;
case spv::Op::OpCooperativeMatrixLoadTensorNV:
@@ -590,6 +593,10 @@ std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
case spv::Op::OpGraphEntryPointARM:
out = [](unsigned index) { return index == 0; };
break;
case spv::Op::OpExtInstWithForwardRefsKHR:
// Any operand may be a forward reference.
out = [](unsigned) { return true; };
break;
default:
out = [](unsigned) { return false; };
break;
@@ -647,6 +654,16 @@ spv_fp_encoding_t spvFPEncodingFromOperandFPEncoding(spv::FPEncoding encoding) {
return SPV_FP_ENCODING_FLOAT8_E4M3;
case spv::FPEncoding::Float8E5M2EXT:
return SPV_FP_ENCODING_FLOAT8_E5M2;
case spv::FPEncoding::Float6E2M3EXT:
return SPV_FP_ENCODING_FLOAT6_E2M3;
case spv::FPEncoding::Float6E3M2EXT:
return SPV_FP_ENCODING_FLOAT6_E3M2;
case spv::FPEncoding::Float4E2M1EXT:
return SPV_FP_ENCODING_FLOAT4_E2M1;
case spv::FPEncoding::Float8UnsignedE8M0EXT:
return SPV_FP_ENCODING_FLOAT8_UNSIGNED_E8M0;
case spv::FPEncoding::MXInt8EXT:
return SPV_FP_ENCODING_MXINT8;
case spv::FPEncoding::Max:
break;
}
@@ -310,28 +310,31 @@ Pass::Status AggressiveDCEPass::ProcessDebugInformation(
// DebugDeclare Variable is not live. Find the value that was being
// stored to this variable. If it's live then create a new DebugValue
// with this value. Otherwise let it die in peace.
get_def_use_mgr()->ForEachUser(var_id, [this,
var_id](Instruction* user) {
if (user->opcode() == spv::Op::OpStore) {
uint32_t stored_value_id = 0;
const uint32_t kStoreValueInIdx = 1;
stored_value_id = user->GetSingleWordInOperand(kStoreValueInIdx);
if (!IsLive(get_def_use_mgr()->GetDef(stored_value_id))) {
return true;
}
bool user_succeeded = get_def_use_mgr()->WhileEachUser(
var_id, [this, var_id](Instruction* user) {
if (user->opcode() == spv::Op::OpStore) {
uint32_t stored_value_id = 0;
const uint32_t kStoreValueInIdx = 1;
stored_value_id =
user->GetSingleWordInOperand(kStoreValueInIdx);
if (!IsLive(get_def_use_mgr()->GetDef(stored_value_id))) {
return true;
}
// value being stored is still live
Instruction* next_inst = user->NextNode();
bool added =
context()->get_debug_info_mgr()->AddDebugValueForVariable(
user, var_id, stored_value_id, user);
if (added && next_inst) {
auto new_debug_value = next_inst->PreviousNode();
AddToWorklist(new_debug_value);
}
}
return true;
});
// value being stored is still live
Instruction* next_inst = user->NextNode();
bool added =
context()->get_debug_info_mgr()->AddDebugValueForVariable(
user, var_id, stored_value_id, user);
if (context()->id_overflow()) return false;
if (added && next_inst) {
auto new_debug_value = next_inst->PreviousNode();
AddToWorklist(new_debug_value);
}
}
return true;
});
if (!user_succeeded) return false;
} else if (inst->GetShaderDebugOpcode() ==
NonSemanticShaderDebugInfoDebugValue) {
uint32_t var_operand_idx = kDebugValueValueInIdx;
@@ -1162,6 +1165,7 @@ void AggressiveDCEPass::InitExtensions() {
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
"SPV_EXT_opacity_micromap",
"SPV_KHR_opacity_micromap",
"SPV_EXT_shader_invocation_reorder",
"SPV_EXT_shader_atomic_float16_add",
"SPV_KHR_abort",
+5 -3
View File
@@ -1110,8 +1110,9 @@ const analysis::Constant* GetConstantUniformValue(
if (!uniform_inst) return nullptr;
uint32_t uniform_id = uniform_inst->result_id();
uint32_t count = type->AsVector()->element_count();
uniform =
const_mgr->GetConstant(type, std::vector<uint32_t>(4, uniform_id));
const_mgr->GetConstant(type, std::vector<uint32_t>(count, uniform_id));
}
return uniform;
@@ -1558,8 +1559,9 @@ ConstantFoldingRule FoldFMix() {
Instruction* one_inst = const_mgr->GetDefiningInstruction(one);
if (one_inst == nullptr) return nullptr;
uint32_t one_id = one_inst->result_id();
one =
const_mgr->GetConstant(result_type, std::vector<uint32_t>(4, one_id));
uint32_t count = result_type->AsVector()->element_count();
one = const_mgr->GetConstant(result_type,
std::vector<uint32_t>(count, one_id));
}
const analysis::Constant* temp1 = FoldFPBinaryOp(
@@ -36,7 +36,8 @@ using utils::ParseNumber;
// Returns true if the given char is ':', '\0' or considered as blank space
// (i.e.: '\n', '\r', '\v', '\t', '\f' and ' ').
bool IsSeparator(char ch) {
return std::strchr(":\0", ch) || std::isspace(ch) != 0;
return std::strchr(":\0", ch) ||
std::isspace(static_cast<unsigned char>(ch)) != 0;
}
// Reads characters starting from |str| until it meets a separator. Parses a
@@ -436,7 +437,7 @@ ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString(
descriptor_set_binding_pairs->push_back({descriptor_set, binding});
// Skip trailing spaces.
while (std::isspace(*str)) str++;
while (std::isspace(static_cast<unsigned char>(*str))) str++;
}
return descriptor_set_binding_pairs;
+779
View File
@@ -0,0 +1,779 @@
// Copyright (c) 2026 Google LLC
//
// 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 "convert_to_untyped.h"
#include <deque>
#include <limits>
#include <unordered_set>
#include "source/operand.h"
namespace spvtools {
namespace opt {
Pass::Status ConvertToUntyped::Process() {
if (HasUnsupportedFeatures()) {
return Pass::Status::SuccessWithoutChange;
}
CheckWorkgroup();
bool changed = ConvertPointers();
if (changed) {
AddUntypedPointerCapability();
}
if (context()->id_overflow()) {
return Pass::Status::Failure;
}
if (changed) {
return Pass::Status::SuccessWithChange;
}
return Pass::Status::SuccessWithoutChange;
}
bool ConvertToUntyped::HasUnsupportedFeatures() {
// Only support converting shaders.
const auto& caps = context()->get_feature_mgr()->GetCapabilities();
if (!caps.contains(spv::Capability::Shader)) {
return true;
}
// Unsupported
CapabilitySet unsupported{
spv::Capability::RawAccessChainsNV,
spv::Capability::PredicatedIOINTEL,
spv::Capability::GraphARM,
spv::Capability::CooperativeMatrixReductionsNV,
spv::Capability::CooperativeMatrixConversionsNV,
spv::Capability::CooperativeMatrixPerElementOperationsNV,
spv::Capability::CooperativeMatrixTensorAddressingNV,
spv::Capability::CooperativeMatrixBlockLoadsNV,
spv::Capability::CooperativeVectorNV,
spv::Capability::CooperativeVectorTrainingNV};
return caps.HasAnyOf(unsupported);
}
void ConvertToUntyped::CheckWorkgroup() {
if (!context()->get_feature_mgr()->GetCapabilities().contains(
spv::Capability::WorkgroupMemoryExplicitLayoutKHR)) {
return;
}
// The module could have a mix of explicit and non-explicit layout for
// workgroup memory (though in different entry points). This transform
// requires all explicit to work.
for (auto& inst : context()->types_values()) {
if (inst.opcode() == spv::Op::OpVariable) {
auto pointee_ty = context()
->get_type_mgr()
->GetType(inst.type_id())
->AsPointer()
->pointee_type();
if (!pointee_ty->AsStruct()) {
return;
}
bool has_block = false;
for (auto& dec : pointee_ty->decorations()) {
if (dec[0] == uint32_t(spv::Decoration::Block)) {
has_block = true;
break;
}
}
if (!has_block) {
return;
}
} else if (inst.opcode() == spv::Op::OpUntypedVariableKHR) {
auto data_ty =
context()->get_type_mgr()->GetType(inst.GetSingleWordInOperand(1));
if (!data_ty->AsStruct()) {
return;
}
bool has_block = false;
for (auto& dec : data_ty->decorations()) {
if (dec[0] == uint32_t(spv::Decoration::Block)) {
has_block = true;
break;
}
}
if (!has_block) {
return;
}
}
}
// All workgroup variables were block-decorated.
support_workgroup_ = true;
}
void ConvertToUntyped::AddUntypedPointerCapability() {
bool needs_ext = true;
for (auto& ei : get_module()->extensions()) {
const std::string name = ei.GetInOperand(0).AsString();
if (name == "SPV_KHR_untyped_pointers") {
needs_ext = false;
break;
}
}
if (needs_ext) {
std::vector<uint32_t> words =
spvtools::utils::MakeVector("SPV_KHR_untyped_pointers");
context()->AddExtension(
MakeUnique<Instruction>(context(), spv::Op::OpExtension, 0, 0,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_LITERAL_STRING, words}}));
}
if (!context()->get_feature_mgr()->HasCapability(
spv::Capability::UntypedPointersKHR)) {
context()->AddCapability(MakeUnique<Instruction>(
context(), spv::Op::OpCapability, 0, 0,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_CAPABILITY,
{uint32_t(spv::Capability::UntypedPointersKHR)}}}));
}
}
uint32_t ConvertToUntyped::NextId() {
uint32_t id = context()->TakeNextId();
return id;
}
bool ConvertToUntyped::SupportedStorageClass(spv::StorageClass sc) {
switch (sc) {
case spv::StorageClass::StorageBuffer:
case spv::StorageClass::Uniform:
case spv::StorageClass::PushConstant:
case spv::StorageClass::PhysicalStorageBuffer:
return true;
case spv::StorageClass::Workgroup:
return support_workgroup_;
default:
return false;
}
}
bool ConvertToUntyped::SupportedStorageClass(uint32_t sc) {
return SupportedStorageClass(static_cast<spv::StorageClass>(sc));
}
bool ConvertToUntyped::ShouldConvert(const Instruction* inst) {
switch (inst->opcode()) {
case spv::Op::OpTypePointer:
return SupportedStorageClass(inst->GetSingleWordInOperand(0));
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpPtrAccessChain:
case spv::Op::OpInBoundsPtrAccessChain:
case spv::Op::OpVariable:
return SupportedStorageClass(context()
->get_type_mgr()
->GetType(inst->type_id())
->AsPointer()
->storage_class());
case spv::Op::OpCopyMemory: {
const auto* dst =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
const auto* dst_ptr =
context()->get_type_mgr()->GetType(dst->type_id())->AsPointer();
const auto* src =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(1));
const auto* src_ptr =
context()->get_type_mgr()->GetType(src->type_id())->AsPointer();
return SupportedStorageClass(dst_ptr->storage_class()) ||
SupportedStorageClass(src_ptr->storage_class());
}
case spv::Op::OpArrayLength: {
const auto* obj =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
const auto* ptr =
context()->get_type_mgr()->GetType(obj->type_id())->AsPointer();
return SupportedStorageClass(ptr->storage_class());
}
case spv::Op::OpCooperativeMatrixLoadKHR:
case spv::Op::OpCooperativeMatrixStoreKHR: {
const auto* ptr =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
const auto* ptr_ty =
context()->get_type_mgr()->GetType(ptr->type_id())->AsPointer();
return SupportedStorageClass(ptr_ty->storage_class());
}
default:
return false;
}
}
std::pair<bool, uint32_t> ConvertToUntyped::MatrixStride(Instruction* inst) {
std::deque<uint32_t> indices;
bool keep_going = true;
while (keep_going) {
keep_going = false;
switch (inst->opcode()) {
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpUntypedAccessChainKHR: {
uint32_t base_idx =
inst->opcode() == spv::Op::OpUntypedAccessChainKHR ? 1 : 0;
for (uint32_t i = inst->NumInOperands() - 1; i > base_idx; i--) {
if (auto* constant =
context()->get_constant_mgr()->FindDeclaredConstant(
inst->GetSingleWordInOperand(i))) {
uint64_t ext = constant->GetZeroExtendedValue();
if (ext > std::numeric_limits<uint32_t>::max()) {
return std::make_pair(false, 0);
}
indices.push_front(static_cast<uint32_t>(ext));
} else {
// Assume we aren't at the start to accumulate a stride.
indices.push_front(1);
}
}
inst = context()->get_def_use_mgr()->GetDef(
inst->GetSingleWordInOperand(base_idx));
keep_going = true;
break;
}
case spv::Op::OpCopyObject:
inst = context()->get_def_use_mgr()->GetDef(
inst->GetSingleWordInOperand(0));
break;
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
break;
default:
break;
}
}
bool row_major = false;
uint32_t mat_stride = 0;
auto type = context()
->get_type_mgr()
->GetType(inst->type_id())
->AsPointer()
->pointee_type();
for (uint32_t i = 0; i < indices.size(); i++) {
switch (type->kind()) {
case analysis::Type::kStruct: {
auto where = type->AsStruct()->element_decorations().find(indices[i]);
if (where != type->AsStruct()->element_decorations().end()) {
const auto& decs = where->second;
for (auto dec : decs) {
if (dec[0] == uint32_t(spv::Decoration::MatrixStride)) {
mat_stride = dec[1];
}
if (dec[0] == uint32_t(spv::Decoration::RowMajor)) {
row_major = true;
}
}
}
type = type->AsStruct()->element_types()[indices[i]];
break;
}
case analysis::Type::kArray:
type = type->AsArray()->element_type();
break;
case analysis::Type::kRuntimeArray:
type = type->AsRuntimeArray()->element_type();
break;
case analysis::Type::kMatrix:
return std::make_pair(row_major, mat_stride);
// No other types could hold a matrix
default:
return std::make_pair(false, 0);
}
}
if (type->AsMatrix()) {
return std::make_pair(row_major, mat_stride);
}
return std::make_pair(false, 0);
}
Instruction* ConvertToUntyped::ConvertPointerType(Instruction* inst) {
Instruction* replacement = nullptr;
auto sc = inst->GetSingleWordInOperand(0);
auto decs = context()->get_decoration_mgr()->GetDecorationsFor(
inst->result_id(), false);
// De-duplicate undecorated pointers.
if (!decs.empty() || !base_ptrs_.count(sc)) {
// Rewrite inst in place. This maintains dominance ordering.
inst->SetOpcode(spv::Op::OpTypeUntypedPointerKHR);
inst->SetInOperands(
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_STORAGE_CLASS, {sc}}});
if (decs.empty()) {
base_ptrs_[sc] = inst;
}
} else {
replacement = base_ptrs_[sc];
}
return replacement;
}
void ConvertToUntyped::ConvertVariable(Instruction* inst) {
auto ptr_ty =
context()->get_type_mgr()->GetType(inst->type_id())->AsPointer();
auto data =
context()->get_type_mgr()->GetTypeInstruction(ptr_ty->pointee_type());
// Rewrite inst in place. This maintains dominance ordering.
inst->SetOpcode(spv::Op::OpUntypedVariableKHR);
inst->SetInOperands(std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(ptr_ty->storage_class())}},
{SPV_OPERAND_TYPE_ID, {data}}});
}
void ConvertToUntyped::ConvertAccessChain(Instruction* inst) {
spv::Op converted_op = inst->opcode();
switch (converted_op) {
case spv::Op::OpAccessChain:
converted_op = spv::Op::OpUntypedAccessChainKHR;
break;
case spv::Op::OpInBoundsAccessChain:
converted_op = spv::Op::OpUntypedInBoundsAccessChainKHR;
break;
case spv::Op::OpPtrAccessChain:
converted_op = spv::Op::OpUntypedPtrAccessChainKHR;
break;
case spv::Op::OpInBoundsPtrAccessChain:
converted_op = spv::Op::OpUntypedInBoundsAccessChainKHR;
break;
default:
assert(false && "unhandled opcode");
break;
}
auto base =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
auto ptr_ty =
context()->get_type_mgr()->GetType(base->type_id())->AsPointer();
auto data =
context()->get_type_mgr()->GetTypeInstruction(ptr_ty->pointee_type());
// Add the base type, but then reuse all the other operands.
std::vector<Operand> operands;
operands.reserve(inst->NumInOperands() + 1);
operands.push_back({SPV_OPERAND_TYPE_ID, {data}});
for (uint32_t i = 0; i < inst->NumInOperands(); i++) {
operands.push_back(inst->GetInOperand(i));
}
inst->SetOpcode(converted_op);
inst->SetInOperands(std::move(operands));
}
void ConvertToUntyped::ConvertArrayLength(Instruction* inst) {
auto structure =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
auto ptr_ty =
context()->get_type_mgr()->GetType(structure->type_id())->AsPointer();
auto data =
context()->get_type_mgr()->GetTypeInstruction(ptr_ty->pointee_type());
// Add the structure type, but then reuse all the other operands.
std::vector<Operand> operands;
operands.reserve(inst->NumInOperands() + 1);
operands.push_back({SPV_OPERAND_TYPE_ID, {data}});
for (uint32_t i = 0; i < inst->NumInOperands(); i++) {
operands.push_back(inst->GetInOperand(i));
}
inst->SetOpcode(spv::Op::OpUntypedArrayLengthKHR);
inst->SetInOperands(std::move(operands));
}
std::vector<Operand> ConvertToUntyped::StoreOperands(
const std::vector<Operand>& operands, uint32_t align) {
// Add placeholders for the pointer and object.
std::vector<Operand> st_operands{{SPV_OPERAND_TYPE_ID, {0}},
{SPV_OPERAND_TYPE_ID, {0}}};
if (operands.empty() && align != 0) {
// Add alignment
st_operands.push_back({SPV_OPERAND_TYPE_MEMORY_ACCESS,
{uint32_t(spv::MemoryAccessMask::Aligned)}});
st_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {align}});
} else if (!operands.empty()) {
// Add/Remove alignment
// Copy MakePointerAvailable scope
// Drop MakePointerVisible (and ignore scope)
uint32_t idx = 0;
st_operands.push_back(operands[idx++]);
if ((operands[0].words[0] & uint32_t(spv::MemoryAccessMask::Aligned)) !=
0) {
idx++;
if (align == 0) {
st_operands[2].words[0] ^= uint32_t(spv::MemoryAccessMask::Aligned);
}
}
if (align != 0) {
st_operands[2].words[0] |= uint32_t(spv::MemoryAccessMask::Aligned);
st_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {align}});
}
if ((operands[0].words[0] &
uint32_t(spv::MemoryAccessMask::MakePointerAvailable)) != 0) {
st_operands.push_back(operands[idx]);
}
st_operands[2].words[0] &=
~uint32_t(spv::MemoryAccessMask::MakePointerVisible);
}
return st_operands;
}
std::vector<Operand> ConvertToUntyped::LoadOperands(
const std::vector<Operand>& operands, uint32_t align) {
// Add placeholder operand for pointer
std::vector<Operand> ld_operands{{SPV_OPERAND_TYPE_ID, {0}}};
if (operands.empty() && align != 0) {
// Add alignment
ld_operands.push_back({SPV_OPERAND_TYPE_MEMORY_ACCESS,
{uint32_t(spv::MemoryAccessMask::Aligned)}});
ld_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {align}});
} else if (!operands.empty()) {
// Add/Remove alignment
// Drop MakePointerAvailable (and ignore scope)
// Copy MakePointerVisible and scope
uint32_t idx = 0;
ld_operands.push_back(operands[idx++]);
if ((operands[0].words[0] & uint32_t(spv::MemoryAccessMask::Aligned)) !=
0) {
idx++;
if (align == 0) {
ld_operands[1].words[0] ^= uint32_t(spv::MemoryAccessMask::Aligned);
}
}
if (align != 0) {
ld_operands[1].words[0] |= uint32_t(spv::MemoryAccessMask::Aligned);
ld_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {align}});
}
if ((operands[0].words[0] &
uint32_t(spv::MemoryAccessMask::MakePointerAvailable)) != 0) {
idx++;
ld_operands[1].words[0] ^=
uint32_t(spv::MemoryAccessMask::MakePointerAvailable);
}
if ((operands[0].words[0] &
uint32_t(spv::MemoryAccessMask::MakePointerVisible)) != 0) {
ld_operands.push_back(operands[idx]);
}
}
return ld_operands;
}
void ConvertToUntyped::ConvertCopyMemory(Instruction* inst) {
auto dst =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
auto dst_type =
context()->get_type_mgr()->GetType(dst->type_id())->AsPointer();
auto dst_sc = dst_type->storage_class();
auto dst_untyped = SupportedStorageClass(dst_sc);
auto src =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(1));
auto src_type =
context()->get_type_mgr()->GetType(src->type_id())->AsPointer();
auto src_sc = src_type->storage_class();
auto src_untyped = SupportedStorageClass(src_sc);
// Get the memory access operands for src and dst.
std::vector<Operand> dst_operands;
std::vector<Operand> src_operands;
if (inst->NumInOperands() > 2) {
uint32_t i = 2;
for (; i < inst->NumInOperands(); i++) {
auto& op = inst->GetInOperand(i);
if (i > 2 && op.type == SPV_OPERAND_TYPE_MEMORY_ACCESS) {
break;
}
dst_operands.push_back(op);
}
if (inst->NumInOperands() > i) {
// The are separate src and dst memory access operands.
for (; i < inst->NumInOperands(); i++) {
src_operands.push_back(inst->GetInOperand(i));
}
} else {
// Shared memory access operands.
src_operands = dst_operands;
}
}
auto [dst_row_major, dst_mat_stride] = MatrixStride(dst);
auto [src_row_major, src_mat_stride] = MatrixStride(src);
// src and dst pointee types must match.
auto pointee_type = dst_type->pointee_type();
// Matrices (and their columns) are a special case.
// The layout information is not on the matrix itself so we could be copying
// matrices with completely different layouts. Just break them down into
// component loads and stores.
if ((dst_mat_stride != 0 || src_mat_stride != 0) &&
!pointee_type->AsFloat()) {
uint32_t num_cols = 1;
uint32_t num_rows = 1;
const analysis::Type* ele_type = nullptr;
if (auto mat_type = pointee_type->AsMatrix()) {
num_cols = mat_type->element_count();
auto vec_type = mat_type->element_type()->AsVector();
num_rows = vec_type->element_count();
ele_type = vec_type->element_type();
} else if (auto vec_type = pointee_type->AsVector()) {
num_rows = vec_type->element_count();
ele_type = vec_type->element_type();
}
uint32_t align = ele_type->AsFloat()->width() / 8;
// Setup operands for the loads and stores.
auto ld_operands = LoadOperands(src_operands, align);
auto st_operands = StoreOperands(dst_operands, align);
// Setup the access operands.
spv::Op src_access_op =
src_untyped ? spv::Op::OpUntypedAccessChainKHR : spv::Op::OpAccessChain;
spv::Op dst_access_op =
dst_untyped ? spv::Op::OpUntypedAccessChainKHR : spv::Op::OpAccessChain;
uint32_t base_type_id = context()->get_type_mgr()->GetId(pointee_type);
uint32_t ele_type_id = context()->get_type_mgr()->GetId(ele_type);
std::vector<Operand> src_access_operands;
if (src_untyped) {
src_access_operands.push_back({SPV_OPERAND_TYPE_ID, {base_type_id}});
}
src_access_operands.push_back({SPV_OPERAND_TYPE_ID, {src->result_id()}});
src_access_operands.push_back({SPV_OPERAND_TYPE_ID, {0}});
std::vector<Operand> dst_access_operands;
if (dst_untyped) {
dst_access_operands.push_back({SPV_OPERAND_TYPE_ID, {base_type_id}});
}
dst_access_operands.push_back({SPV_OPERAND_TYPE_ID, {dst->result_id()}});
dst_access_operands.push_back({SPV_OPERAND_TYPE_ID, {0}});
if (num_cols > 1) {
src_access_operands.push_back({SPV_OPERAND_TYPE_ID, {0}});
dst_access_operands.push_back({SPV_OPERAND_TYPE_ID, {0}});
}
// Finally, unroll the copy as individual loads and stores.
const uint32_t src_access_idx = src_untyped ? 2 : 1;
const uint32_t dst_access_idx = dst_untyped ? 2 : 1;
for (uint32_t c = 0; c < num_cols; c++) {
for (uint32_t r = 0; r < num_rows; r++) {
// Update the access operands
if (num_cols == 1) {
uint32_t row_id = context()->get_constant_mgr()->GetUIntConstId(r);
src_access_operands[src_access_idx].words[0] = row_id;
dst_access_operands[dst_access_idx].words[0] = row_id;
} else {
uint32_t col_id = context()->get_constant_mgr()->GetUIntConstId(c);
uint32_t row_id = context()->get_constant_mgr()->GetUIntConstId(r);
src_access_operands[src_access_idx].words[0] = col_id;
src_access_operands[src_access_idx + 1].words[0] = row_id;
dst_access_operands[dst_access_idx].words[0] = col_id;
dst_access_operands[dst_access_idx + 1].words[0] = row_id;
}
uint32_t src_access_id = NextId();
inst->InsertBefore(MakeUnique<Instruction>(
context(), src_access_op, base_ptrs_[uint32_t(src_sc)]->result_id(),
src_access_id, src_access_operands));
uint32_t src_ld = NextId();
// Update load operand ids
ld_operands[0].words[0] = src_access_id;
inst->InsertBefore(MakeUnique<Instruction>(
context(), spv::Op::OpLoad, ele_type_id, src_ld, ld_operands));
uint32_t dst_access_id = NextId();
inst->InsertBefore(MakeUnique<Instruction>(
context(), dst_access_op, base_ptrs_[uint32_t(dst_sc)]->result_id(),
dst_access_id, dst_access_operands));
// Update store operand ids
st_operands[0].words[0] = dst_access_id;
st_operands[1].words[0] = src_ld;
inst->InsertBefore(MakeUnique<Instruction>(context(), spv::Op::OpStore,
0, 0, st_operands));
}
}
return;
}
// For other cases just load from src and store to dst.
// Don't bother with OpCopyMemorySized, too many shenanigans are necessary for
// that.
//
// We don't need to set an align.
auto ld_operands = LoadOperands(src_operands, 0);
auto st_operands = StoreOperands(dst_operands, 0);
ld_operands[0].words[0] = src->result_id();
auto ld = inst->InsertBefore(MakeUnique<Instruction>(
context(), spv::Op::OpLoad,
context()->get_type_mgr()->GetId(pointee_type), NextId(), ld_operands));
st_operands[0].words[0] = dst->result_id();
st_operands[1].words[0] = ld->result_id();
inst->InsertBefore(
MakeUnique<Instruction>(context(), spv::Op::OpStore, 0, 0, st_operands));
}
void ConvertToUntyped::UpdateCooperativeMatrixLoadStore(Instruction* inst) {
uint32_t stride_idx =
inst->opcode() == spv::Op::OpCooperativeMatrixLoadKHR ? 2 : 3;
// With typed pointers the stride is counted in elements, but with untyped
// pointers the stride is interpreted in bytes. So if there is no stride
// specified, nothing needs done.
if (inst->NumInOperands() <= stride_idx) {
return;
}
auto ptr =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
auto ptr_ty = context()->get_type_mgr()->GetType(ptr->type_id())->AsPointer();
// The pointer must point to a scalar or vector of numeric type so there is a
// very limited number of permutations to consider.
//
// Also the memory is in contiguous locations, so just use the size (e.g.
// don't worry about 3-element vectors).
auto type = ptr_ty->pointee_type();
uint32_t size = 1;
if (auto vec_ty = type->AsVector()) {
size *= vec_ty->element_count();
type = vec_ty->element_type();
} else if (auto long_vec_ty = type->AsCooperativeVectorNV()) {
size *= long_vec_ty->components();
type = long_vec_ty->component_type();
}
if (auto int_type = type->AsInteger()) {
size *= int_type->width() / 8;
} else if (auto float_type = type->AsFloat()) {
size *= float_type->width() / 8;
}
auto mul = inst->InsertBefore(MakeUnique<Instruction>(
context(), spv::Op::OpIMul, context()->get_type_mgr()->GetUIntTypeId(),
NextId(),
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(stride_idx)}},
{SPV_OPERAND_TYPE_ID,
{context()->get_constant_mgr()->GetUIntConstId(size)}}}));
inst->SetInOperand(stride_idx, {mul->result_id()});
}
void ConvertToUntyped::Convert(Instruction* inst) {
Instruction* replacement = nullptr;
switch (inst->opcode()) {
case spv::Op::OpTypePointer:
replacement = ConvertPointerType(inst);
if (replacement != nullptr) {
to_delete_.push_back(inst);
remapped_ids_[inst->result_id()] = replacement->result_id();
}
break;
case spv::Op::OpVariable:
ConvertVariable(inst);
break;
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpPtrAccessChain:
case spv::Op::OpInBoundsPtrAccessChain:
ConvertAccessChain(inst);
break;
case spv::Op::OpArrayLength:
ConvertArrayLength(inst);
break;
case spv::Op::OpCopyMemory:
ConvertCopyMemory(inst);
to_delete_.push_back(inst);
break;
case spv::Op::OpCooperativeMatrixLoadKHR:
case spv::Op::OpCooperativeMatrixStoreKHR:
UpdateCooperativeMatrixLoadStore(inst);
break;
default:
assert(false && "unhandled opcode");
break;
}
}
bool ConvertToUntyped::ConvertPointers() {
if (context()->get_feature_mgr()->HasCapability(
spv::Capability::UntypedPointersKHR)) {
// Untyped pointers capability is already declared, pre-populate base_ptrs_.
// All possibly supported storage classes are listed for extensibility
// purposes.
std::vector<spv::StorageClass> storage_classes{
spv::StorageClass::StorageBuffer,
spv::StorageClass::Uniform,
spv::StorageClass::Workgroup,
spv::StorageClass::PushConstant,
spv::StorageClass::PhysicalStorageBuffer,
spv::StorageClass::UniformConstant};
for (auto sc : storage_classes) {
if (!SupportedStorageClass(sc)) {
continue;
}
const analysis::Pointer ptr_type{nullptr, sc};
auto id = context()->get_type_mgr()->GetId(&ptr_type);
if (id != 0) {
base_ptrs_[uint32_t(sc)] = context()->get_def_use_mgr()->GetDef(id);
}
}
}
// Convert instructions that change opcode with untyped pointers.
std::vector<Instruction*> to_convert;
get_module()->ForEachInst([this, &to_convert](Instruction* inst) {
if (ShouldConvert(inst)) {
to_convert.push_back(inst);
}
});
if (to_convert.empty()) {
return false;
}
for (auto* inst : to_convert) {
Convert(inst);
}
// Update operands of instructions
get_module()->ForEachInst([this](Instruction* inst) {
for (uint32_t i = 0; i < inst->NumOperands(); i++) {
auto& op = inst->GetOperand(i);
if (spvIsIdType(op.type) && op.type != SPV_OPERAND_TYPE_RESULT_ID) {
auto where = remapped_ids_.find(op.words[0]);
if (where != remapped_ids_.end()) {
op.words[0] = where->second;
}
}
}
});
// Delete dead instructions
for (auto* inst : to_delete_) {
context()->KillInst(inst);
}
return true;
}
} // namespace opt
} // namespace spvtools
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) 2026 Google LLC
//
// 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.
#ifndef LIBSPIRV_OPT_CONVERT_TO_UNTYPED_H_
#define LIBSPIRV_OPT_CONVERT_TO_UNTYPED_H_
#include <unordered_map>
#include <utility>
#include "pass.h"
namespace spvtools {
namespace opt {
class ConvertToUntyped : public Pass {
public:
const char* name() const override { return "convert-to-untyped"; }
Status Process() override;
private:
// Adds the untyped pointer extension and capability if they are not present.
void AddUntypedPointerCapability();
// Updates the module by converting instructions and replacing uses.
//
// The TypeManager is not updated as the mutations occur and all instructions
// continue to use old ids (mapped to correctly typed pointers) until after
// conversions. Then all operands are updated (and unneeded instructions
// removed).
//
// Returns true if conversions were made.
bool ConvertPointers();
// Returns true if sc is supports untyped pointers.
bool SupportedStorageClass(spv::StorageClass sc);
bool SupportedStorageClass(uint32_t sc);
// Returns true if inst needs conversion.
bool ShouldConvert(const Instruction* inst);
// Returns true if the module has unsupported features.
bool HasUnsupportedFeatures();
// Determines whether workgroup storage class can be converted.
void CheckWorkgroup();
// Converts or updates inst.
void Convert(Instruction* inst);
// Converts OpTypePointer to OpTypeUntypedPointerKHR
// All undecorated pointers in each storage class are de-duplicated.
Instruction* ConvertPointerType(Instruction* inst);
// Converts OpVariable to OpUntypedVariableKHR
void ConvertVariable(Instruction* inst);
// Converts access chains to equivalent untyped version.
void ConvertAccessChain(Instruction* inst);
// Converts OpArrayLength to OpUntypedArrayLengthKHR.
void ConvertArrayLength(Instruction* inst);
// Converts OpCopyMemory.
// If the copy is a matrix or the column of a matrix, it is converted to loads
// and stores element-wise.
// Otherwise, converts the copy to a load and store.
void ConvertCopyMemory(Instruction* inst);
// Updates cooperative matrix load/store strides to be in bytes.
void UpdateCooperativeMatrixLoadStore(Instruction* inst);
uint32_t NextId();
// Returns (row_major, matrix_stride) for the inst.
std::pair<bool, uint32_t> MatrixStride(Instruction* inst);
// Memory access operand conversion (from OpCopyMemory)
// Converts operands into appropriate memory access operands for OpStore.
std::vector<Operand> StoreOperands(const std::vector<Operand>& operands,
uint32_t align);
// Converts operands into appropriate memory access operands for OpLoad.
std::vector<Operand> LoadOperands(const std::vector<Operand>& operands,
uint32_t align);
// Tracks the de-duplicated pointers per storage class.
std::unordered_map<uint32_t, Instruction*> base_ptrs_{};
// Tracks result id to its replacement result id
std::unordered_map<uint32_t, uint32_t> remapped_ids_{};
// Converted instructions that need deleted.
std::vector<Instruction*> to_delete_{};
bool support_workgroup_ = false;
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_CONVERT_TO_UNTYPED_H_
+6 -1
View File
@@ -480,7 +480,12 @@ Pass::Status DeadBranchElimPass::Process() {
return EliminateDeadBranches(fp);
};
bool modified = context()->ProcessReachableCallTree(pfn);
if (modified) FixBlockOrder();
if (modified) {
context()->InvalidateAnalyses(IRContext::kAnalysisCFG |
IRContext::kAnalysisDominatorAnalysis |
IRContext::kAnalysisStructuredCFG);
FixBlockOrder();
}
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
+23 -6
View File
@@ -153,6 +153,7 @@ void DebugInfoManager::RegisterDbgDeclare(uint32_t var_id,
// such as inlining. Invalidate Constant and DefUse managers if used.
uint32_t AddNewConstInGlobals(IRContext* context, uint32_t const_value) {
uint32_t id = context->TakeNextId();
if (id == 0) return 0;
std::unique_ptr<Instruction> new_const(new Instruction(
context, spv::Op::OpConstant, context->get_type_mgr()->GetUIntTypeId(),
id,
@@ -236,10 +237,12 @@ uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line,
else
line_number =
context()->get_constant_mgr()->GetUIntConstId(line_number);
if (line_number == 0) return kNoInlinedAt;
}
}
uint32_t result_id = context()->TakeNextId();
if (result_id == 0) return kNoInlinedAt;
std::unique_ptr<Instruction> inlined_at(new Instruction(
context(), spv::Op::OpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
result_id,
@@ -417,6 +420,7 @@ Instruction* DebugInfoManager::GetEmptyDebugExpression() {
if (empty_debug_expr_inst_ != nullptr) return empty_debug_expr_inst_;
uint32_t result_id = context()->TakeNextId();
if (result_id == 0) return nullptr;
std::unique_ptr<Instruction> empty_debug_expr(new Instruction(
context(), spv::Op::OpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
result_id,
@@ -450,8 +454,10 @@ Instruction* DebugInfoManager::CloneDebugInlinedAt(uint32_t clone_inlined_at_id,
Instruction* insert_before) {
auto* inlined_at = GetDebugInlinedAt(clone_inlined_at_id);
if (inlined_at == nullptr) return nullptr;
uint32_t result_id = context()->TakeNextId();
if (result_id == 0) return nullptr;
std::unique_ptr<Instruction> new_inlined_at(inlined_at->Clone(context()));
new_inlined_at->SetResultId(context()->TakeNextId());
new_inlined_at->SetResultId(result_id);
RegisterDbgInst(new_inlined_at.get());
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inlined_at.get());
@@ -591,12 +597,18 @@ Instruction* DebugInfoManager::AddDebugValueForDecl(Instruction* dbg_decl,
Instruction* line) {
if (dbg_decl == nullptr || !IsDebugDeclare(dbg_decl)) return nullptr;
Instruction* empty_expr = GetEmptyDebugExpression();
if (empty_expr == nullptr) return nullptr;
uint32_t result_id = context()->TakeNextId();
if (result_id == 0) return nullptr;
std::unique_ptr<Instruction> dbg_val(dbg_decl->Clone(context()));
dbg_val->SetResultId(context()->TakeNextId());
dbg_val->SetResultId(result_id);
dbg_val->SetInOperand(kExtInstInstructionInIdx, {CommonDebugInfoDebugValue});
dbg_val->SetOperand(kDebugDeclareOperandVariableIndex, {value_id});
dbg_val->SetOperand(kDebugValueOperandExpressionIndex,
{GetEmptyDebugExpression()->result_id()});
{empty_expr->result_id()});
dbg_val->UpdateDebugInfoFrom(dbg_decl, line);
auto* added_dbg_val = insert_before->InsertBefore(std::move(dbg_val));
@@ -798,10 +810,16 @@ void DebugInfoManager::ConvertDebugGlobalToLocalVariable(
context()->ForgetUses(dbg_global_var);
context()->AnalyzeUses(dbg_global_var);
Instruction* empty_expr = GetEmptyDebugExpression();
if (empty_expr == nullptr) return;
uint32_t result_id = context()->TakeNextId();
if (result_id == 0) return;
// Create a DebugDeclare
std::unique_ptr<Instruction> new_dbg_decl(new Instruction(
context(), spv::Op::OpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
context()->TakeNextId(),
result_id,
{
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
@@ -809,8 +827,7 @@ void DebugInfoManager::ConvertDebugGlobalToLocalVariable(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{dbg_global_var->result_id()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {local_var->result_id()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{GetEmptyDebugExpression()->result_id()}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {empty_expr->result_id()}},
}));
// Must insert after all OpVariables in block
Instruction* insert_before = local_var;
+5
View File
@@ -116,6 +116,11 @@ class DominatorAnalysisBase {
// If there is no such basic block, nullptr is returned.
BasicBlock* CommonDominator(BasicBlock* b1, BasicBlock* b2) const;
// Returns true if this analysis has the same tree as |other|.
inline bool operator==(const DominatorAnalysisBase& other) const {
return tree_ == other.tree_;
}
protected:
DominatorTree tree_;
};
+19
View File
@@ -381,5 +381,24 @@ void DominatorTree::DumpTreeAsDot(std::ostream& out_stream) const {
out_stream << "}\n";
}
bool DominatorTree::operator==(const DominatorTree& other) const {
if (postdominator_ != other.postdominator_) return false;
if (nodes_.size() != other.nodes_.size()) return false;
for (const auto& pair : nodes_) {
uint32_t id = pair.first;
const DominatorTreeNode& node = pair.second;
const DominatorTreeNode* other_node = other.GetTreeNode(id);
if (!other_node) return false;
if ((node.parent_ == nullptr) != (other_node->parent_ == nullptr))
return false;
if (node.parent_ != nullptr) {
if (node.parent_->id() != other_node->parent_->id()) return false;
}
}
return true;
}
} // namespace opt
} // namespace spvtools
+3
View File
@@ -202,6 +202,9 @@ class DominatorTree {
// Returns true if this tree is a post dominator tree.
bool IsPostDominator() const { return postdominator_; }
// Returns true if this tree has the same nodes and structure as |other|.
bool operator==(const DominatorTree& other) const;
// Clean up the tree.
void ClearTree() {
nodes_.clear();
+107 -7
View File
@@ -14,14 +14,14 @@
#include "source/opt/folding_rules.h"
#include <limits>
#include <memory>
#include <optional>
#include <utility>
#include "ir_builder.h"
#include "source/latest_version_glsl_std_450_header.h"
#include "source/opt/ir_context.h"
#include "source/spirv_constant.h"
#include "source/spirv_target_env.h"
namespace spvtools {
namespace opt {
@@ -2124,6 +2124,84 @@ std::vector<Operand> GetExtractOperandsForElementOfCompositeConstruct(
return {};
}
// If the OpCompositeConstruct that feeds an OpCopyLogical can be retyped to
// the OpCopyLogical's result type, the layout conversion can be expressed at
// constituent granularity instead of at aggregate granularity. This rewrites
// the OpCopyLogical as an OpCompositeConstruct of the result type, using the
// same constituents where their types already match the corresponding
// field/element of the result type, and inserting per-field OpCopyLogical
// instructions only for the fields that genuinely require a layout
// conversion.
bool CompositeConstructFeedingCopyLogical(
IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>&) {
assert(inst->opcode() == spv::Op::OpCopyLogical &&
"Wrong opcode. Should be OpCopyLogical.");
analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
uint32_t src_id = inst->GetSingleWordInOperand(0);
Instruction* src_inst = def_use_mgr->GetDef(src_id);
if (src_inst->opcode() != spv::Op::OpCompositeConstruct) {
return false;
}
Instruction* dst_type_inst = def_use_mgr->GetDef(inst->type_id());
const uint32_t num_constituents = src_inst->NumInOperands();
// Determine the expected type id for each constituent of the destination
// type.
std::vector<uint32_t> expected_type_ids;
expected_type_ids.reserve(num_constituents);
if (dst_type_inst->opcode() == spv::Op::OpTypeStruct) {
if (dst_type_inst->NumInOperands() != num_constituents) {
return false;
}
for (uint32_t i = 0; i < num_constituents; ++i) {
expected_type_ids.push_back(dst_type_inst->GetSingleWordInOperand(i));
}
} else if (dst_type_inst->opcode() == spv::Op::OpTypeArray) {
const uint32_t elem_type_id = dst_type_inst->GetSingleWordInOperand(0);
for (uint32_t i = 0; i < num_constituents; ++i) {
expected_type_ids.push_back(elem_type_id);
}
} else {
return false;
}
// Build the new constituent list, inserting OpCopyLogical instructions for
// the fields whose types differ from the result type.
InstructionBuilder ir_builder(
context, inst,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
std::vector<Operand> operands;
operands.reserve(num_constituents);
for (uint32_t i = 0; i < num_constituents; ++i) {
const uint32_t cid = src_inst->GetSingleWordInOperand(i);
Instruction* cdef = def_use_mgr->GetDef(cid);
if (cdef->type_id() == expected_type_ids[i]) {
operands.push_back({SPV_OPERAND_TYPE_ID, {cid}});
continue;
}
if (def_use_mgr->GetDef(expected_type_ids[i])->opcode() ==
spv::Op::OpTypePointer) {
assert(def_use_mgr->GetDef(expected_type_ids[i])->opcode() !=
spv::Op::OpTypePointer &&
"Unreachable for valid input");
}
Instruction* per_field_copy = ir_builder.AddUnaryOp(
expected_type_ids[i], spv::Op::OpCopyLogical, cid);
if (per_field_copy == nullptr) {
return false;
}
operands.push_back({SPV_OPERAND_TYPE_ID, {per_field_copy->result_id()}});
}
inst->SetOpcode(spv::Op::OpCompositeConstruct);
inst->SetInOperands(std::move(operands));
context->UpdateDefUse(inst);
return true;
}
bool CompositeConstructFeedingExtract(
IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>&) {
@@ -3232,6 +3310,10 @@ FoldingRule MergeBinaryOpSelect(spv::Op opcode) {
return [opcode](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>& constants) {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
analysis::TypeManager* type_mgr = context->get_type_mgr();
analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
const analysis::Constant* const_input = ConstInput(constants);
if (!const_input) {
return false;
@@ -3241,11 +3323,28 @@ FoldingRule MergeBinaryOpSelect(spv::Op opcode) {
return false;
}
std::vector<const analysis::Constant*> select_constants =
context->get_constant_mgr()->GetOperandConstants(non_const);
const_mgr->GetOperandConstants(non_const);
if (!select_constants[1] || !select_constants[2]) {
return false;
}
// The OpSelect that will be created below will use the condition from
// `non_const` and a result type matching `inst`. Before SPIR-V 1.4,
// OpSelect could not have a scalar condition with a vector result.
// We must avoid generating the OpSelect if that would happen.
const analysis::Type* result_type = type_mgr->GetType(inst->type_id());
if (result_type && result_type->AsVector()) {
Instruction* cond_inst =
def_use_mgr->GetDef(non_const->GetSingleWordInOperand(0));
const analysis::Type* cond_type = type_mgr->GetType(cond_inst->type_id());
if (cond_type && !cond_type->AsVector()) {
if (spvVersionForTargetEnv(context->grammar().target_env()) <
SPV_SPIRV_VERSION_WORD(1, 4)) {
return false;
}
}
}
InstructionBuilder ir_builder(
context, inst,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
@@ -3274,15 +3373,13 @@ FoldingRule MergeBinaryOpSelect(spv::Op opcode) {
if (context->get_instruction_folder().FoldInstruction(lhs)) {
context->AnalyzeDefUse(lhs);
while (lhs->opcode() == spv::Op::OpCopyObject) {
lhs =
context->get_def_use_mgr()->GetDef(lhs->GetSingleWordInOperand(0));
lhs = def_use_mgr->GetDef(lhs->GetSingleWordInOperand(0));
}
}
if (context->get_instruction_folder().FoldInstruction(rhs)) {
context->AnalyzeDefUse(rhs);
while (rhs->opcode() == spv::Op::OpCopyObject) {
rhs =
context->get_def_use_mgr()->GetDef(rhs->GetSingleWordInOperand(0));
rhs = def_use_mgr->GetDef(rhs->GetSingleWordInOperand(0));
}
}
inst->SetOpcode(spv::Op::OpSelect);
@@ -4476,6 +4573,9 @@ void FoldingRules::AddFoldingRules() {
rules_[spv::Op::OpCompositeConstruct].push_back(
CompositeExtractFeedingConstruct);
rules_[spv::Op::OpCopyLogical].push_back(
CompositeConstructFeedingCopyLogical);
rules_[spv::Op::OpCompositeExtract].push_back(InsertFeedingExtract());
rules_[spv::Op::OpCompositeExtract].push_back(
CompositeConstructFeedingExtract);
+52 -5
View File
@@ -118,6 +118,12 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
// dominator analysis should be invalidated as well.
if (analyses_to_invalidate & kAnalysisCFG) {
analyses_to_invalidate |= kAnalysisDominatorAnalysis;
analyses_to_invalidate |= kAnalysisStructuredCFG;
}
if (analyses_to_invalidate & kAnalysisLoopAnalysis) {
analyses_to_invalidate |= kAnalysisScalarEvolution;
analyses_to_invalidate |= kAnalysisLiveness;
}
if (analyses_to_invalidate & kAnalysisDefUse) {
@@ -142,6 +148,9 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
dominator_trees_.clear();
post_dominator_trees_.clear();
}
if (analyses_to_invalidate & kAnalysisLoopAnalysis) {
loop_descriptors_.clear();
}
if (analyses_to_invalidate & kAnalysisNameMap) {
id_to_name_.reset(nullptr);
}
@@ -160,6 +169,12 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
if (analyses_to_invalidate & kAnalysisLiveness) {
liveness_mgr_.reset(nullptr);
}
if (analyses_to_invalidate & kAnalysisScalarEvolution) {
scalar_evolution_analysis_.reset(nullptr);
}
if (analyses_to_invalidate & kAnalysisRegisterPressure) {
reg_pressure_.reset(nullptr);
}
if (analyses_to_invalidate & kAnalysisTypes) {
type_mgr_.reset(nullptr);
}
@@ -211,7 +226,8 @@ Instruction* IRContext::KillInst(Instruction* inst) {
if (inst->opcode() == spv::Op::OpCapability ||
inst->opcode() == spv::Op::OpConditionalCapabilityINTEL ||
inst->opcode() == spv::Op::OpExtension ||
inst->opcode() == spv::Op::OpConditionalExtensionINTEL) {
inst->opcode() == spv::Op::OpConditionalExtensionINTEL ||
inst->opcode() == spv::Op::OpExtInstImport) {
// We reset the feature manager, instead of updating it, because it is just
// as much work. We would have to remove all capabilities implied by this
// capability that are not also implied by the remaining OpCapability
@@ -271,7 +287,8 @@ void IRContext::CollectNonSemanticTree(
work_list.pop_back();
get_def_use_mgr()->ForEachUser(
i, [&work_list, to_kill, &seen](Instruction* user) {
if (user->IsNonSemanticInstruction() && seen.insert(user).second) {
if (user->IsNonSemanticInstruction() && !user->IsDebugLineInst() &&
seen.insert(user).second) {
work_list.push_back(user);
to_kill->insert(user);
}
@@ -392,7 +409,6 @@ bool IRContext::IsConsistent() {
}
}
return true;
if (AreAnalysesValid(kAnalysisIdToFuncMapping)) {
for (auto& fn : *module_) {
if (id_to_func_[fn.result_id()] != &fn) {
@@ -437,6 +453,37 @@ bool IRContext::IsConsistent() {
}
}
if (AreAnalysesValid(kAnalysisDominatorAnalysis)) {
for (const auto& it : dominator_trees_) {
const Function* f = it.first;
const DominatorAnalysis& cached_dom = it.second;
DominatorAnalysis new_dom;
new_dom.InitializeTree(*cfg(), f);
if (!(cached_dom == new_dom)) {
return false;
}
}
for (const auto& it : post_dominator_trees_) {
const Function* f = it.first;
const PostDominatorAnalysis& cached_post_dom = it.second;
PostDominatorAnalysis new_post_dom;
new_post_dom.InitializeTree(*cfg(), f);
if (!(cached_post_dom == new_post_dom)) {
return false;
}
}
}
if (AreAnalysesValid(kAnalysisStructuredCFG)) {
StructuredCFGAnalysis new_struct_cfg(this);
StructuredCFGAnalysis* cached_struct_cfg = struct_cfg_analysis_.get();
if (!(*cached_struct_cfg == new_struct_cfg)) {
return false;
}
}
if (feature_mgr_ != nullptr) {
FeatureManager current(grammar_);
current.Analyze(module());
@@ -973,9 +1020,9 @@ void IRContext::AddCalls(const Function* func, std::queue<uint32_t>* todo) {
for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
if (ii->opcode() == spv::Op::OpFunctionCall)
todo->push(ii->GetSingleWordInOperand(0));
if (ii->opcode() == spv::Op::OpCooperativeMatrixPerElementOpNV)
if (ii->opcode() == spv::Op::OpCooperativeMatrixPerElementOpEXT)
todo->push(ii->GetSingleWordInOperand(1));
if (ii->opcode() == spv::Op::OpCooperativeMatrixReduceNV)
if (ii->opcode() == spv::Op::OpCooperativeMatrixReduceEXT)
todo->push(ii->GetSingleWordInOperand(2));
if (ii->opcode() == spv::Op::OpCooperativeMatrixLoadTensorNV) {
const auto memory_operands_index = 3;
@@ -472,6 +472,7 @@ void LocalAccessChainConvertPass::InitExtensions() {
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
"SPV_EXT_opacity_micromap",
"SPV_KHR_opacity_micromap",
"SPV_EXT_shader_invocation_reorder",
"SPV_EXT_shader_atomic_float16_add",
"SPV_KHR_abort",
@@ -309,6 +309,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
"SPV_EXT_opacity_micromap",
"SPV_KHR_opacity_micromap",
"SPV_EXT_shader_invocation_reorder",
"SPV_EXT_shader_atomic_float16_add",
"SPV_KHR_abort",
@@ -161,6 +161,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() {
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
"SPV_EXT_opacity_micromap",
"SPV_KHR_opacity_micromap",
"SPV_EXT_shader_invocation_reorder",
"SPV_EXT_shader_atomic_float16_add",
"SPV_KHR_abort",
+18 -3
View File
@@ -17,10 +17,12 @@
#include <algorithm>
#include <cstring>
#include <ostream>
#include <unordered_set>
#include "source/operand.h"
#include "source/opt/ir_context.h"
#include "source/opt/reflect.h"
#include "source/util/hash_combine.h"
namespace spvtools {
namespace opt {
@@ -149,7 +151,8 @@ void Module::ForEachInst(const std::function<void(const Instruction*)>& f,
#undef DELEGATE
}
void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const {
void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop,
bool filter_duplicate_decorations) const {
binary->push_back(header_.magic_number);
binary->push_back(header_.version);
// TODO(antiagainst): should we change the generator number?
@@ -162,9 +165,21 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const {
const Instruction* last_line_inst = nullptr;
bool between_merge_and_branch = false;
bool between_label_and_phi_var = false;
auto write_inst = [binary, skip_nop, &last_scope, &last_line_inst,
&between_merge_and_branch, &between_label_and_phi_var,
std::unordered_set<std::vector<uint32_t>,
spvtools::utils::VectorHash<uint32_t>>
seen_decorations;
auto write_inst = [binary, skip_nop, filter_duplicate_decorations,
&last_scope, &last_line_inst, &between_merge_and_branch,
&between_label_and_phi_var, &seen_decorations,
this](const Instruction* i) {
if (filter_duplicate_decorations && i->IsDecoration()) {
std::vector<uint32_t> inst_binary;
i->ToBinaryWithoutAttachedDebugInsts(&inst_binary);
if (seen_decorations.count(inst_binary)) {
return;
}
seen_decorations.insert(inst_binary);
}
// Skip emitting line instructions between merge and branch instructions.
auto opcode = i->opcode();
if (between_merge_and_branch && i->IsLineInst()) {
+6 -3
View File
@@ -275,9 +275,12 @@ class Module {
void ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
// Pushes the binary segments for this instruction into the back of *|binary|.
// If |skip_nop| is true and this is a OpNop, do nothing.
void ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const;
// Pushes the binary segments for this module into the back of *`binary`.
// If `skip_nop` is true, OpNop instructions will not be added to binary.
// If `filter_duplicate_decorations` is true, duplicate decorations will not
// be added to the binary.
void ToBinary(std::vector<uint32_t>* binary, bool skip_nop,
bool filter_duplicate_decorations = true) const;
// Returns 1 more than the maximum Id value mentioned in the module.
uint32_t ComputeIdBound() const;
+9 -1
View File
@@ -465,6 +465,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
RegisterPass(CreateReplaceInvalidOpcodePass());
} else if (pass_name == "convert-relaxed-to-half") {
RegisterPass(CreateConvertRelaxedToHalfPass());
} else if (pass_name == "convert-to-untyped") {
RegisterPass(CreateConvertToUntypedPass());
} else if (pass_name == "relax-float-ops") {
RegisterPass(CreateRelaxFloatOpsPass());
} else if (pass_name == "simplify-instructions") {
@@ -719,7 +721,8 @@ bool Optimizer::Run(const uint32_t* original_binary,
!context->module()->ContainsDebugInfo()) {
std::vector<uint32_t> optimized_binary_with_nop;
context->module()->ToBinary(&optimized_binary_with_nop,
/* skip_nop = */ false);
/* skip_nop = */ false,
/* filter_duplicate_decorations = */ false);
assert(optimized_binary_with_nop.size() == original_binary_size &&
"Binary size unexpectedly changed despite the optimizer saying "
"there was no change");
@@ -1054,6 +1057,11 @@ Optimizer::PassToken CreateConvertRelaxedToHalfPass() {
MakeUnique<opt::ConvertToHalfPass>());
}
Optimizer::PassToken CreateConvertToUntypedPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::ConvertToUntyped>());
}
Optimizer::PassToken CreateRelaxFloatOpsPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::RelaxFloatOpsPass>());
+1
View File
@@ -29,6 +29,7 @@
#include "source/opt/compact_ids_pass.h"
#include "source/opt/convert_to_half_pass.h"
#include "source/opt/convert_to_sampled_image_pass.h"
#include "source/opt/convert_to_untyped.h"
#include "source/opt/copy_prop_arrays.h"
#include "source/opt/dead_branch_elim_pass.h"
#include "source/opt/dead_insert_elim_pass.h"
@@ -363,7 +363,8 @@ Pass::Status SetSpecConstantDefaultValuePass::Process() {
// Returns true if the given char is ':', '\0' or considered as blank space
// (i.e.: '\n', '\r', '\v', '\t', '\f' and ' ').
bool IsSeparator(char ch) {
return std::strchr(":\0", ch) || std::isspace(ch) != 0;
return std::strchr(":\0", ch) ||
std::isspace(static_cast<unsigned char>(ch)) != 0;
}
std::unique_ptr<SetSpecConstantDefaultValuePass::SpecIdToValueStrMap>
@@ -375,7 +376,8 @@ SetSpecConstantDefaultValuePass::ParseDefaultValuesString(const char* str) {
// The parsing loop, break when points to the end.
while (*str) {
// Find the spec id.
while (std::isspace(*str)) str++; // skip leading spaces.
while (std::isspace(static_cast<unsigned char>(*str)))
str++; // skip leading spaces.
const char* entry_begin = str;
while (!IsSeparator(*str)) str++;
const char* entry_end = str;
@@ -407,7 +409,7 @@ SetSpecConstantDefaultValuePass::ParseDefaultValuesString(const char* str) {
(*spec_id_to_value)[spec_id] = std::string(val_begin, val_end - val_begin);
// Skip trailing spaces.
while (std::isspace(*str)) str++;
while (std::isspace(static_cast<unsigned char>(*str))) str++;
}
return spec_id_to_value;
+8 -2
View File
@@ -95,8 +95,14 @@ Pass::Status StripDebugInfoPass::Process() {
// clear OpLine information
context()->module()->ForEachInst([&modified](Instruction* inst) {
modified |= !inst->dbg_line_insts().empty();
inst->dbg_line_insts().clear();
if (!inst->dbg_line_insts().empty()) {
modified = true;
inst->dbg_line_insts().clear();
}
if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
modified = true;
inst->SetDebugScope({kNoDebugScope, kNoInlinedAt});
}
});
if (!get_module()->trailing_dbg_line_info().empty()) {
+13
View File
@@ -245,5 +245,18 @@ StructuredCFGAnalysis::FindFuncsCalledFromContinue() {
return called_from_continue;
}
bool StructuredCFGAnalysis::operator==(
const StructuredCFGAnalysis& other) const {
if (bb_to_construct_.size() != other.bb_to_construct_.size()) return false;
for (const auto& pair : bb_to_construct_) {
uint32_t bb_id = pair.first;
const ConstructInfo& info = pair.second;
auto it = other.bb_to_construct_.find(bb_id);
if (it == other.bb_to_construct_.end()) return false;
if (!(info == it->second)) return false;
}
return merge_blocks_ == other.merge_blocks_;
}
} // namespace opt
} // namespace spvtools
+11
View File
@@ -115,6 +115,10 @@ class StructuredCFGAnalysis {
// Return true if |bb_id| is the merge block for a construct.
bool IsMergeBlock(uint32_t bb_id);
// Returns true if this analysis has the same constructs and merge blocks as
// |other|.
bool operator==(const StructuredCFGAnalysis& other) const;
// Returns the set of function ids that are called directly or indirectly from
// a continue construct.
std::unordered_set<uint32_t> FindFuncsCalledFromContinue();
@@ -141,6 +145,13 @@ class StructuredCFGAnalysis {
uint32_t containing_loop;
uint32_t containing_switch;
bool in_continue;
bool operator==(const ConstructInfo& other) const {
return containing_construct == other.containing_construct &&
containing_loop == other.containing_loop &&
containing_switch == other.containing_switch &&
in_continue == other.in_continue;
}
};
// Populates |bb_to_construct_| with the innermost containing merge and loop
+13 -5
View File
@@ -248,11 +248,19 @@ uint32_t TypeManager::GetTypeInstruction(const Type* type) {
{(type->AsInteger()->IsSigned() ? 1u : 0u)}}});
break;
case Type::kFloat:
// TODO: Handle FP encoding enums once actually used.
typeInst = MakeUnique<Instruction>(
context(), spv::Op::OpTypeFloat, 0, id,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {type->AsFloat()->width()}}});
if (type->AsFloat()->encoding() == spv::FPEncoding::Max) {
typeInst = MakeUnique<Instruction>(
context(), spv::Op::OpTypeFloat, 0, id,
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_LITERAL_INTEGER,
{type->AsFloat()->width()}}});
} else {
typeInst = MakeUnique<Instruction>(
context(), spv::Op::OpTypeFloat, 0, id,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {type->AsFloat()->width()}},
{SPV_OPERAND_TYPE_FPENCODING,
{static_cast<uint32_t>(type->AsFloat()->encoding())}}});
}
break;
case Type::kVector: {
uint32_t subtype = GetTypeInstruction(type->AsVector()->element_type());
+20
View File
@@ -400,6 +400,18 @@ bool Float::IsSameImpl(const Type* that, IsSameCache*) const {
std::string Float::str() const {
std::ostringstream oss;
switch (encoding_) {
case spv::FPEncoding::Float4E2M1EXT:
assert(width_ == 4);
oss << "fp4e2m1";
break;
case spv::FPEncoding::Float6E2M3EXT:
assert(width_ == 6);
oss << "fp6e2m3";
break;
case spv::FPEncoding::Float6E3M2EXT:
assert(width_ == 6);
oss << "fp6e3m2";
break;
case spv::FPEncoding::BFloat16KHR:
assert(width_ == 16);
oss << "bfloat16";
@@ -412,6 +424,14 @@ std::string Float::str() const {
assert(width_ == 8);
oss << "fp8e5m2";
break;
case spv::FPEncoding::Float8UnsignedE8M0EXT:
assert(width_ == 8);
oss << "fp8e8m0";
break;
case spv::FPEncoding::MXInt8EXT:
assert(width_ == 8);
oss << "mxint8";
break;
default:
oss << "float" << width_;
break;
+20
View File
@@ -51,6 +51,18 @@ void EmitNumericLiteral(std::ostream* out, const spv_parsed_instruction_t& inst,
case SPV_FP_ENCODING_IEEE754_BINARY32:
*out << spvtools::utils::FloatProxy<float>(word);
break;
case SPV_FP_ENCODING_FLOAT4_E2M1:
*out << spvtools::utils::FloatProxy<spvtools::utils::Float4_E2M1>(
uint8_t(word & 0xF));
break;
case SPV_FP_ENCODING_FLOAT6_E2M3:
*out << spvtools::utils::FloatProxy<spvtools::utils::Float6_E2M3>(
uint8_t(word & 0x3F));
break;
case SPV_FP_ENCODING_FLOAT6_E3M2:
*out << spvtools::utils::FloatProxy<spvtools::utils::Float6_E3M2>(
uint8_t(word & 0x3F));
break;
case SPV_FP_ENCODING_FLOAT8_E4M3:
*out << spvtools::utils::FloatProxy<spvtools::utils::Float8_E4M3>(
uint8_t(word & 0xFF));
@@ -59,6 +71,14 @@ void EmitNumericLiteral(std::ostream* out, const spv_parsed_instruction_t& inst,
*out << spvtools::utils::FloatProxy<spvtools::utils::Float8_E5M2>(
uint8_t(word & 0xFF));
break;
case SPV_FP_ENCODING_FLOAT8_UNSIGNED_E8M0:
*out << spvtools::utils::FloatProxy<spvtools::utils::Float8_E8M0>(
uint8_t(word & 0xFF));
break;
case SPV_FP_ENCODING_MXINT8:
*out << spvtools::utils::HexFixedPoint<spvtools::utils::MXInt8>(
uint8_t(word & 0xFF));
break;
case SPV_FP_ENCODING_BFLOAT16:
*out << spvtools::utils::FloatProxy<spvtools::utils::BFloat16>(
uint16_t(word & 0xFFFF));
+3 -2
View File
@@ -226,7 +226,8 @@ bool spvReadEnvironmentFromText(const std::vector<char>& text,
char minor = text[minor_digit_pos];
char next_char =
minor_digit_pos + 1 < text.size() ? text[minor_digit_pos + 1] : 0;
if (std::isdigit(minor) && !std::isdigit(next_char)) {
if (std::isdigit(static_cast<unsigned char>(minor)) &&
!std::isdigit(static_cast<unsigned char>(next_char))) {
const auto index = minor - '0';
assert(index >= 0);
if (static_cast<size_t>(index) < ordered_universal_envs.size()) {
@@ -243,7 +244,7 @@ bool spvReadEnvironmentFromText(const std::vector<char>& text,
for (; i < text.size(); ++i) {
if (text[i] == '\n') break;
}
} else if (!std::isspace(c)) {
} else if (!std::isspace(static_cast<unsigned char>(c))) {
// Allow blanks, but end the search if we find something else.
break;
}
+27
View File
@@ -140,3 +140,30 @@ void spvValidatorOptionsSetFriendlyNames(spv_validator_options options,
bool val) {
options->use_friendly_names = val;
}
void spvValidatorOptionsSetBufferDescriptorLayout(spv_validator_options options,
uint32_t size,
uint32_t alignment) {
options->buffer_descriptor_layout.size = size;
options->buffer_descriptor_layout.alignment = alignment;
}
void spvValidatorOptionsSetImageDescriptorLayout(spv_validator_options options,
uint32_t size,
uint32_t alignment) {
options->image_descriptor_layout.size = size;
options->image_descriptor_layout.alignment = alignment;
}
void spvValidatorOptionsSetSamplerDescriptorLayout(
spv_validator_options options, uint32_t size, uint32_t alignment) {
options->sampler_descriptor_layout.size = size;
options->sampler_descriptor_layout.alignment = alignment;
}
void spvValidatorOptionsSetTensorDescriptorLayout(spv_validator_options options,
uint32_t size,
uint32_t alignment) {
options->tensor_descriptor_layout.size = size;
options->tensor_descriptor_layout.alignment = alignment;
}
+10
View File
@@ -35,6 +35,11 @@ struct validator_universal_limits_t {
uint32_t max_id_bound{0x3FFFFF};
};
struct OpaqueResourceLayout {
uint32_t size{0};
uint32_t alignment{1};
};
// Manages command line options passed to the SPIR-V Validator. New struct
// members may be added for any new option.
struct spv_validator_options_t {
@@ -66,6 +71,11 @@ struct spv_validator_options_t {
bool allow_vulkan_32_bit_bitwise;
bool before_hlsl_legalization;
bool use_friendly_names;
OpaqueResourceLayout buffer_descriptor_layout;
OpaqueResourceLayout image_descriptor_layout;
OpaqueResourceLayout sampler_descriptor_layout;
OpaqueResourceLayout tensor_descriptor_layout;
};
#endif // SOURCE_SPIRV_VALIDATOR_OPTIONS_H_
+1 -1
View File
@@ -45,7 +45,7 @@
#include "spirv-tools/libspirv.h"
bool spvIsValidIDCharacter(const char value) {
return value == '_' || 0 != ::isalnum(value);
return value == '_' || 0 != ::isalnum(static_cast<unsigned char>(value));
}
// Returns true if the given string represents a valid ID name.
+9
View File
@@ -168,8 +168,17 @@ bool validBitWidthForFPEncoding(spv_fp_encoding_t enc, uint32_t width,
case SPV_FP_ENCODING_IEEE754_BINARY64:
*expected = 64;
break;
case SPV_FP_ENCODING_FLOAT4_E2M1:
*expected = 4;
break;
case SPV_FP_ENCODING_FLOAT6_E2M3:
case SPV_FP_ENCODING_FLOAT6_E3M2:
*expected = 6;
break;
case SPV_FP_ENCODING_FLOAT8_E5M2:
case SPV_FP_ENCODING_FLOAT8_E4M3:
case SPV_FP_ENCODING_FLOAT8_UNSIGNED_E8M0:
case SPV_FP_ENCODING_MXINT8:
*expected = 8;
break;
default:
+10
View File
@@ -78,5 +78,15 @@ std::ostream& operator<<(std::ostream& out, const BitVector& bv) {
return out;
}
bool BitVector::operator==(const BitVector& other) const {
size_t max_size = std::max(bits_.size(), other.bits_.size());
for (size_t i = 0; i < max_size; ++i) {
BitContainer b1 = (i < bits_.size()) ? bits_[i] : 0;
BitContainer b2 = (i < other.bits_.size()) ? other.bits_[i] : 0;
if (b1 != b2) return false;
}
return true;
}
} // namespace utils
} // namespace spvtools
+4
View File
@@ -109,6 +109,10 @@ class BitVector {
// |this|. Return true if |this| changed.
bool Or(const BitVector& that);
// Returns true if |this| and |that| have the same bits set.
bool operator==(const BitVector& that) const;
bool operator!=(const BitVector& that) const { return !(*this == that); }
private:
std::vector<BitContainer> bits_;
};
+7
View File
@@ -47,6 +47,13 @@ inline size_t hash_combine(std::size_t hash, const T& val,
return hash_combine(hash_combine(hash, val), args...);
}
template <typename T>
struct VectorHash {
size_t operator()(const std::vector<T>& v) const {
return hash_combine(0, v);
}
};
} // namespace utils
} // namespace spvtools
File diff suppressed because it is too large Load Diff
+61
View File
@@ -159,6 +159,45 @@ EncodeNumberStatus ParseAndEncodeFloatingPointNumber(
const auto bit_width = AssumedBitWidth(type);
switch (DeduceEncoding(type)) {
case SPV_FP_ENCODING_FLOAT4_E2M1: {
HexFloat<FloatProxy<Float4_E2M1>> hVal(0);
if (!ParseNumber(text, &hVal)) {
ErrorMsgStream(error_msg) << "Invalid E2M1 float literal: " << text;
return EncodeNumberStatus::kInvalidText;
}
// getAsFloat will return the Float16 value, and get_value
// will return a uint8_t representing the bits of the float.
// The encoding is therefore correct from the perspective of the SPIR-V
// spec since the top 28 bits will be 0.
emit(static_cast<uint32_t>(hVal.value().getAsFloat().get_value()));
return EncodeNumberStatus::kSuccess;
} break;
case SPV_FP_ENCODING_FLOAT6_E2M3: {
HexFloat<FloatProxy<Float6_E2M3>> hVal(0);
if (!ParseNumber(text, &hVal)) {
ErrorMsgStream(error_msg) << "Invalid E2M3 float literal: " << text;
return EncodeNumberStatus::kInvalidText;
}
// getAsFloat will return the Float16 value, and get_value
// will return a uint8_t representing the bits of the float.
// The encoding is therefore correct from the perspective of the SPIR-V
// spec since the top 26 bits will be 0.
emit(static_cast<uint32_t>(hVal.value().getAsFloat().get_value()));
return EncodeNumberStatus::kSuccess;
} break;
case SPV_FP_ENCODING_FLOAT6_E3M2: {
HexFloat<FloatProxy<Float6_E3M2>> hVal(0);
if (!ParseNumber(text, &hVal)) {
ErrorMsgStream(error_msg) << "Invalid E3M2 float literal: " << text;
return EncodeNumberStatus::kInvalidText;
}
// getAsFloat will return the Float16 value, and get_value
// will return a uint8_t representing the bits of the float.
// The encoding is therefore correct from the perspective of the SPIR-V
// spec since the top 26 bits will be 0.
emit(static_cast<uint32_t>(hVal.value().getAsFloat().get_value()));
return EncodeNumberStatus::kSuccess;
} break;
case SPV_FP_ENCODING_FLOAT8_E4M3: {
HexFloat<FloatProxy<Float8_E4M3>> hVal(0);
if (!ParseNumber(text, &hVal)) {
@@ -185,6 +224,28 @@ EncodeNumberStatus ParseAndEncodeFloatingPointNumber(
emit(static_cast<uint32_t>(hVal.value().getAsFloat().get_value()));
return EncodeNumberStatus::kSuccess;
} break;
case SPV_FP_ENCODING_FLOAT8_UNSIGNED_E8M0: {
HexFloat<FloatProxy<Float8_E8M0>> hVal(0);
if (!ParseNumber(text, &hVal)) {
ErrorMsgStream(error_msg) << "Invalid E8M0 float literal: " << text;
return EncodeNumberStatus::kInvalidText;
}
// getAsFloat will return the Float16 value, and get_value
// will return a uint16_t representing the bits of the float.
// The encoding is therefore correct from the perspective of the SPIR-V
// spec since the top 16 bits will be 0.
emit(static_cast<uint32_t>(hVal.value().getAsFloat().get_value()));
return EncodeNumberStatus::kSuccess;
} break;
case SPV_FP_ENCODING_MXINT8: {
HexFixedPoint<MXInt8> hVal;
if (!ParseNumber(text, &hVal)) {
ErrorMsgStream(error_msg) << "Invalid MXInt8 float literal: " << text;
return EncodeNumberStatus::kInvalidText;
}
emit(static_cast<uint32_t>(hVal.value()));
return EncodeNumberStatus::kSuccess;
} break;
case SPV_FP_ENCODING_BFLOAT16: {
HexFloat<FloatProxy<BFloat16>> hVal(0);
if (!ParseNumber(text, &hVal)) {
+14 -3
View File
@@ -169,6 +169,16 @@ template <typename T>
struct IsHexFloat<HexFloat<T>> {
static const bool value = true;
};
template <typename T>
struct IsHexFixedPoint {
static const bool value = false;
};
template <typename T>
struct IsHexFixedPoint<HexFixedPoint<T>> {
static const bool value = true;
};
// Parses a numeric value of a given type from the given text. The number
// should take up the entire string, and should be within bounds for the target
// type. On success, returns true and populates the object referenced by
@@ -179,9 +189,10 @@ bool ParseNumber(const char* text, T* value_pointer) {
// with a single-byte type leads to implementation-defined behaviour.
// Similarly for uint8_t.
// HexFloat<T> overloads the operator
static_assert(sizeof(T) > 1 || IsHexFloat<T>::value,
"Single-byte types other than HexFloat<> are not supported in "
"this parse method");
static_assert(
sizeof(T) > 1 || IsHexFloat<T>::value || IsHexFixedPoint<T>::value,
"Single-byte types other than HexFloat<> are not supported in "
"this parse method");
if (!text) return false;
std::istringstream text_stream(text);
+9
View File
@@ -23,6 +23,11 @@
#include <cassert>
#include <iostream>
#if defined(__GNUG__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif
// A macro to call spvtools::utils::PrintTimerDescription(std::ostream*, bool).
// The first argument must be given as std::ostream*. If it is NULL, the
// function does nothing. Otherwise, it prints resource types measured by Timer
@@ -382,6 +387,10 @@ class CumulativeTimer : public Timer {
} // namespace utils
} // namespace spvtools
#if defined(__GNUG__)
#pragma GCC diagnostic pop
#endif
#else // defined(SPIRV_TIMER_ENABLED)
#define SPIRV_TIMER_DESCRIPTION(...)
+9 -1
View File
@@ -18,6 +18,7 @@
#include <iterator>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include "source/binary.h"
@@ -225,6 +226,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
bool has_mask_task_nv = false;
bool has_mask_task_ext = false;
std::vector<Instruction*> visited_entry_points;
std::unordered_set<std::string> graph_entry_point_names;
for (auto& instruction : vstate->ordered_instructions()) {
{
// In order to do this work outside of Process Instruction we need to be
@@ -281,7 +283,12 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
execution_model == spv::ExecutionModel::MeshEXT);
}
if (inst->opcode() == spv::Op::OpGraphEntryPointARM) {
const auto graph = inst->GetOperandAs<uint32_t>(1);
const std::string name = inst->GetOperandAs<std::string>(1);
if (!graph_entry_point_names.insert(name).second) {
return vstate->diag(SPV_ERROR_INVALID_DATA, inst)
<< "Graph entry points cannot share the same name.";
}
const auto graph = inst->GetOperandAs<uint32_t>(0);
vstate->RegisterGraphEntryPoint(graph);
}
if (inst->opcode() == spv::Op::OpFunctionCall) {
@@ -420,6 +427,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
if (auto error = PerformCfgChecks(*vstate)) return error;
if (auto error = CheckIdDefinitionDominateUse(*vstate)) return error;
if (auto error = ValidateDecorations(*vstate)) return error;
if (auto error = ValidateExplicitLayout(*vstate)) return error;
if (auto error = ValidateInterfaces(*vstate)) return error;
// TODO(dsinclair): Restructure ValidateBuiltins so we can move into the
// for() above as it loops over all ordered_instructions internally.
+2
View File
@@ -144,6 +144,8 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst);
/// has been propagated down to the group members.
spv_result_t ValidateDecorations(ValidationState_t& _);
spv_result_t ValidateExplicitLayout(ValidationState_t& _);
/// Performs validation of built-in variables.
spv_result_t ValidateBuiltIns(ValidationState_t& _);
+23
View File
@@ -215,6 +215,29 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
<< fail() << "must be a variable";
}
break;
case spv::Decoration::CooperativeMatrixTransposeEXT:
switch (target->opcode()) {
case spv::Op::OpConvertFToU:
case spv::Op::OpConvertFToS:
case spv::Op::OpConvertSToF:
case spv::Op::OpConvertUToF:
case spv::Op::OpUConvert:
case spv::Op::OpSConvert:
case spv::Op::OpFConvert:
case spv::Op::OpCooperativeMatrixConvertUseEXT:
break;
default:
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< fail()
<< "must be a cooperative matrix conversion instruction";
}
if (!_.IsCooperativeMatrixKHRType(target->type_id()) ||
!_.IsCooperativeMatrixKHRType(_.GetOperandTypeId(target, 2))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< fail()
<< "must be a cooperative matrix conversion instruction";
}
break;
default:
break;
}
+37 -22
View File
@@ -63,8 +63,8 @@ spv_result_t ValidateFloat(ValidationState_t& _, const Instruction* inst,
<< "Expected arithmetic operands to be of Result Type: "
<< spvOpcodeString(opcode) << " operand index " << operand_index;
}
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, type_id, false);
spv_result_t ret = _.CooperativeMatrixShapesMatch(inst, result_type,
type_id, false, false);
if (ret != SPV_SUCCESS) return ret;
} else if (_.GetOperandTypeId(inst, operand_index) != result_type)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -108,8 +108,8 @@ spv_result_t ValidateUnsignedInt(ValidationState_t& _, const Instruction* inst,
<< "Expected arithmetic operands to be of Result Type: "
<< spvOpcodeString(opcode) << " operand index " << operand_index;
}
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, type_id, false);
spv_result_t ret = _.CooperativeMatrixShapesMatch(inst, result_type,
type_id, false, false);
if (ret != SPV_SUCCESS) return ret;
} else if (_.GetOperandTypeId(inst, operand_index) != result_type)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -164,8 +164,8 @@ spv_result_t ValidateSignedInt(ValidationState_t& _, const Instruction* inst,
<< "Expected arithmetic operands to be of Result Type: "
<< spvOpcodeString(opcode) << " operand index " << operand_index;
}
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, type_id, false);
spv_result_t ret = _.CooperativeMatrixShapesMatch(inst, result_type,
type_id, false, false);
if (ret != SPV_SUCCESS) return ret;
}
@@ -715,8 +715,8 @@ spv_result_t ValidateCooperativeMatrixMulAddKHR(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateCooperativeMatrixReduceNV(ValidationState_t& _,
const Instruction* inst) {
spv_result_t ValidateCooperativeMatrixReduceEXT(ValidationState_t& _,
const Instruction* inst) {
const spv::Op opcode = inst->opcode();
const uint32_t result_type = inst->type_id();
if (!_.IsCooperativeMatrixKHRType(result_type)) {
@@ -744,33 +744,48 @@ spv_result_t ValidateCooperativeMatrixReduceNV(ValidationState_t& _,
"type: "
<< spvOpcodeString(opcode);
}
if (_.FindDef(result_type)->GetOperandAs<uint32_t>(2) !=
matrix_type->GetOperandAs<uint32_t>(2)) {
const auto result_scope =
_.EvalInt32IfConst(_.FindDef(result_type)->GetOperandAs<uint32_t>(2));
const auto matrix_scope =
_.EvalInt32IfConst(matrix_type->GetOperandAs<uint32_t>(2));
if (std::get<1>(result_scope) && std::get<1>(matrix_scope) &&
std::get<2>(result_scope) != std::get<2>(matrix_scope)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type and Matrix type must have the same scope: "
<< spvOpcodeString(opcode);
}
if (!_.IsCooperativeMatrixAccType(result_type)) {
const auto result_use =
_.EvalInt32IfConst(_.FindDef(result_type)->GetOperandAs<uint32_t>(5));
if (std::get<1>(result_use) &&
std::get<2>(result_use) !=
uint32_t(spv::CooperativeMatrixUse::MatrixAccumulatorKHR)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must have UseAccumulator: "
<< spvOpcodeString(opcode);
}
if (!_.IsCooperativeMatrixAccType(matrix_type_id)) {
const auto matrix_use =
_.EvalInt32IfConst(matrix_type->GetOperandAs<uint32_t>(5));
if (std::get<1>(matrix_use) &&
std::get<2>(matrix_use) !=
uint32_t(spv::CooperativeMatrixUse::MatrixAccumulatorKHR)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Matrix type must have UseAccumulator: "
<< spvOpcodeString(opcode);
}
const auto reduce_value = inst->GetOperandAs<uint32_t>(3);
const uint32_t reduce_row = uint32_t(spv::CooperativeMatrixReduceMask::Row);
const uint32_t reduce_column =
uint32_t(spv::CooperativeMatrixReduceMask::Column);
const uint32_t reduce_row_column = reduce_row | reduce_column;
const uint32_t reduce_2x2 =
uint32_t(spv::CooperativeMatrixReduceMask::CooperativeMatrixReduce2x2);
if ((reduce_value &
uint32_t(
spv::CooperativeMatrixReduceMask::CooperativeMatrixReduce2x2)) &&
(reduce_value & uint32_t(spv::CooperativeMatrixReduceMask::Row |
spv::CooperativeMatrixReduceMask::Column))) {
if (reduce_value != reduce_row && reduce_value != reduce_column &&
reduce_value != reduce_row_column && reduce_value != reduce_2x2) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Reduce 2x2 must not be used with Row/Column: "
<< "Reduce must be Row, Column, Row|Column, or 2x2: "
<< spvOpcodeString(opcode);
}
@@ -787,8 +802,8 @@ spv_result_t ValidateCooperativeMatrixReduceNV(ValidationState_t& _,
uint32_t(spv::CooperativeMatrixReduceMask::CooperativeMatrixReduce2x2)) {
if (std::get<1>(result_rows) && std::get<1>(result_cols) &&
std::get<1>(matrix_rows) && std::get<1>(matrix_cols) &&
(std::get<2>(result_rows) != std::get<2>(matrix_rows) / 2 ||
std::get<2>(result_cols) != std::get<2>(matrix_cols) / 2)) {
(uint64_t{std::get<2>(result_rows)} * 2 != std::get<2>(matrix_rows) ||
uint64_t{std::get<2>(result_cols)} * 2 != std::get<2>(matrix_cols))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "For Reduce2x2, result rows/cols must be half of matrix "
"rows/cols: "
@@ -884,8 +899,8 @@ spv_result_t ArithmeticsPass(ValidationState_t& _, const Instruction* inst) {
return ValidateCooperativeMatrixMulAddNV(_, inst);
case spv::Op::OpCooperativeMatrixMulAddKHR:
return ValidateCooperativeMatrixMulAddKHR(_, inst);
case spv::Op::OpCooperativeMatrixReduceNV:
return ValidateCooperativeMatrixReduceNV(_, inst);
case spv::Op::OpCooperativeMatrixReduceEXT:
return ValidateCooperativeMatrixReduceEXT(_, inst);
case spv::Op::OpSpecConstantOp: {
switch (inst->GetOperandAs<spv::Op>(2u)) {
+45 -20
View File
@@ -33,27 +33,52 @@ spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
const uint32_t result_type = inst->type_id();
switch (opcode) {
case spv::Op::OpControlBarrier: {
if (_.version() < SPV_SPIRV_VERSION_WORD(1, 3)) {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::TessellationControl &&
model != spv::ExecutionModel::GLCompute &&
model != spv::ExecutionModel::Kernel &&
model != spv::ExecutionModel::TaskNV &&
model != spv::ExecutionModel::MeshNV) {
if (message) {
*message =
"In SPIR-V 1.2 or earlier, OpControlBarrier requires "
"one of the following "
"Execution Models: TessellationControl, GLCompute, "
"Kernel, MeshNV or TaskNV";
}
return false;
case spv::Op::OpControlBarrier:
case spv::Op::OpControlBarrierArriveEXT:
case spv::Op::OpControlBarrierWaitEXT: {
if (opcode == spv::Op::OpControlBarrier) {
if (_.version() < SPV_SPIRV_VERSION_WORD(1, 3)) {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation([](spv::ExecutionModel model,
std::string* message) {
if (model != spv::ExecutionModel::TessellationControl &&
model != spv::ExecutionModel::GLCompute &&
model != spv::ExecutionModel::Kernel &&
model != spv::ExecutionModel::TaskNV &&
model != spv::ExecutionModel::MeshNV) {
if (message) {
*message =
"In SPIR-V 1.2 or earlier, OpControlBarrier requires "
"one of the following "
"Execution Models: TessellationControl, GLCompute, "
"Kernel, MeshNV or TaskNV";
}
return true;
});
return false;
}
return true;
});
}
}
if (opcode == spv::Op::OpControlBarrierArriveEXT ||
opcode == spv::Op::OpControlBarrierWaitEXT) {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation([&_](spv::ExecutionModel model,
std::string* message) {
if (model != spv::ExecutionModel::GLCompute &&
model != spv::ExecutionModel::Kernel) {
if (message) {
*message = _.VkErrorID(13552)
.append(
"The SplitBarrierEXT capability must not "
"be enabled in any stage other than "
"GLCompute or Kernel.")
.c_str();
}
return false;
}
return true;
});
}
const uint32_t execution_scope = inst->word(1);
@@ -147,6 +147,11 @@ bool IsSupportOptionalVulkan_1_0(uint32_t capability) {
case spv::Capability::Int8:
case spv::Capability::BFloat16TypeKHR:
case spv::Capability::Float8EXT:
case spv::Capability::Float4EXT:
case spv::Capability::Float6EXT:
case spv::Capability::Float8UnsignedE8M0EXT:
case spv::Capability::MXInt8EXT:
case spv::Capability::BitcastExtractEXT:
case spv::Capability::PushConstantBanksNV:
return true;
default:
+4
View File
@@ -15,6 +15,7 @@
#include <cassert>
#include <functional>
#include <iostream>
#include <iterator>
#include <map>
#include <string>
#include <tuple>
@@ -345,6 +346,9 @@ spv_result_t ValidateLoopMerge(ValidationState_t& _, const Instruction* inst) {
if ((loop_control >> spv::LoopControlShift::PartialCount) & 0x1) {
++operand;
}
if ((loop_control >> spv::LoopControlShift::MultipleWaitQueuesQCOM) & 0x1) {
++operand;
}
// That the right number of operands is present is checked by the parser. The
// above code tracks operands for expanded validation checking in the future.
@@ -633,6 +633,10 @@ spv_result_t ValidateSpecConstantOp(ValidationState_t& _,
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpPtrAccessChain:
case spv::Op::OpInBoundsPtrAccessChain:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
if (!_.HasCapability(spv::Capability::Kernel)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Specialization constant operation " << spvOpcodeString(op)
+243 -25
View File
@@ -26,6 +26,45 @@
namespace spvtools {
namespace val {
namespace {
bool IsNumericScalarOrVectorType(ValidationState_t& _, uint32_t type_id) {
return _.IsFloatScalarType(type_id) || _.IsFloatVectorType(type_id) ||
_.IsIntScalarType(type_id) || _.IsIntVectorType(type_id);
}
bool IsIEEEOrAlternativeFloatTypeAllowedForOCPMicroscalingFConvert(
ValidationState_t& _, uint32_t type_id) {
const uint32_t component_type = _.GetComponentType(type_id);
const Instruction* inst = _.FindDef(component_type);
if (!inst || inst->opcode() != spv::Op::OpTypeFloat) return false;
if (inst->words().size() <= 3) return true;
const auto encoding = inst->GetOperandAs<spv::FPEncoding>(2);
return encoding == spv::FPEncoding::Float8E4M3EXT ||
encoding == spv::FPEncoding::Float8E5M2EXT ||
encoding == spv::FPEncoding::BFloat16KHR;
}
spv_result_t ValidateVulkanOCPMicroscalingFloatIntConversion(
ValidationState_t& _, const Instruction* inst, uint32_t float_type) {
if (spvIsVulkanEnv(_.context()->target_env) &&
_.ContainsOCPMicroscalingType(float_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(12465) << spvOpcodeString(inst->opcode())
<< " must not consume or produce OCP microscaling types in the "
"Vulkan environment.";
}
return SPV_SUCCESS;
}
bool HasCoopMatTranspose(ValidationState_t& _, uint32_t id) {
return _.HasDecoration(id, spv::Decoration::CooperativeMatrixTransposeEXT);
}
} // namespace
spv_result_t ValidateShaderBitWidth(ValidationState_t& _,
const Instruction* inst) {
if (_.HasCapability(spv::Capability::Shader)) {
@@ -68,7 +107,8 @@ spv_result_t ValidateConvertFToU(ValidationState_t& _, const Instruction* inst,
} else if (_.IsCooperativeMatrixType(result_type) ||
_.IsCooperativeMatrixType(input_type)) {
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true);
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true,
HasCoopMatTranspose(_, inst->id()));
if (ret != SPV_SUCCESS) return ret;
} else {
if (_.GetDimension(result_type) != _.GetDimension(input_type))
@@ -77,6 +117,10 @@ spv_result_t ValidateConvertFToU(ValidationState_t& _, const Instruction* inst,
<< spvOpcodeString(opcode);
}
if (auto error =
ValidateVulkanOCPMicroscalingFloatIntConversion(_, inst, input_type))
return error;
if (auto error = ValidateShaderBitWidth(_, inst)) return error;
return SPV_SUCCESS;
@@ -110,7 +154,8 @@ spv_result_t ValidateConvertFToS(ValidationState_t& _, const Instruction* inst,
} else if (_.IsCooperativeMatrixType(result_type) ||
_.IsCooperativeMatrixType(input_type)) {
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true);
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true,
HasCoopMatTranspose(_, inst->id()));
if (ret != SPV_SUCCESS) return ret;
} else {
if (_.GetDimension(result_type) != _.GetDimension(input_type))
@@ -119,6 +164,10 @@ spv_result_t ValidateConvertFToS(ValidationState_t& _, const Instruction* inst,
<< spvOpcodeString(opcode);
}
if (auto error =
ValidateVulkanOCPMicroscalingFloatIntConversion(_, inst, input_type))
return error;
if (auto error = ValidateShaderBitWidth(_, inst)) return error;
return SPV_SUCCESS;
@@ -153,7 +202,8 @@ spv_result_t ValidateConvertIntToF(ValidationState_t& _,
} else if (_.IsCooperativeMatrixType(result_type) ||
_.IsCooperativeMatrixType(input_type)) {
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true);
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true,
HasCoopMatTranspose(_, inst->id()));
if (ret != SPV_SUCCESS) return ret;
} else {
if (_.GetDimension(result_type) != _.GetDimension(input_type))
@@ -162,6 +212,10 @@ spv_result_t ValidateConvertIntToF(ValidationState_t& _,
<< spvOpcodeString(opcode);
}
if (auto error =
ValidateVulkanOCPMicroscalingFloatIntConversion(_, inst, result_type))
return error;
if (auto error = ValidateShaderBitWidth(_, inst)) return error;
return SPV_SUCCESS;
@@ -196,7 +250,8 @@ spv_result_t ValidateUConvert(ValidationState_t& _, const Instruction* inst,
} else if (_.IsCooperativeMatrixType(result_type) ||
_.IsCooperativeMatrixType(input_type)) {
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true);
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true,
HasCoopMatTranspose(_, inst->id()));
if (ret != SPV_SUCCESS) return ret;
} else {
if (_.GetDimension(result_type) != _.GetDimension(input_type))
@@ -241,7 +296,8 @@ spv_result_t ValidateSConvert(ValidationState_t& _, const Instruction* inst,
} else if (_.IsCooperativeMatrixType(result_type) ||
_.IsCooperativeMatrixType(input_type)) {
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true);
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true,
HasCoopMatTranspose(_, inst->id()));
if (ret != SPV_SUCCESS) return ret;
} else {
if (_.GetDimension(result_type) != _.GetDimension(input_type))
@@ -286,7 +342,8 @@ spv_result_t ValidateFConvert(ValidationState_t& _, const Instruction* inst,
} else if (_.IsCooperativeMatrixType(result_type) ||
_.IsCooperativeMatrixType(input_type)) {
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true);
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, true,
HasCoopMatTranspose(_, inst->id()));
if (ret != SPV_SUCCESS) return ret;
} else {
if (_.GetDimension(result_type) != _.GetDimension(input_type))
@@ -304,6 +361,25 @@ spv_result_t ValidateFConvert(ValidationState_t& _, const Instruction* inst,
"component type of Result Type: "
<< spvOpcodeString(opcode);
}
if (spvIsVulkanEnv(_.context()->target_env)) {
if (_.ContainsOCPMicroscalingType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(12466) << spvOpcodeString(opcode)
<< " must not produce OCP microscaling types in the Vulkan "
"environment.";
}
if (_.ContainsOCPMicroscalingType(input_type) &&
!IsIEEEOrAlternativeFloatTypeAllowedForOCPMicroscalingFConvert(
_, result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(12467) << spvOpcodeString(opcode)
<< " consuming an OCP microscaling type in the Vulkan "
"environment must produce IEEE 754, Float8E4M3EXT, "
"Float8E5M2EXT, or BFloat16KHR.";
}
}
return SPV_SUCCESS;
}
@@ -545,10 +621,12 @@ spv_result_t ValidatePtrCastToGeneric(ValidationState_t& _,
if (input_storage_class != spv::StorageClass::Workgroup &&
input_storage_class != spv::StorageClass::CrossWorkgroup &&
input_storage_class != spv::StorageClass::Function)
input_storage_class != spv::StorageClass::Function &&
input_storage_class != spv::StorageClass::CodeSectionINTEL)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected input to have storage class Workgroup, "
<< "CrossWorkgroup or Function: " << spvOpcodeString(opcode);
<< "CrossWorkgroup, Function or CodeSectionINTEL: "
<< spvOpcodeString(opcode);
if (result_data_type != input_data_type)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -572,10 +650,12 @@ spv_result_t ValidateGenericCastToPtr(ValidationState_t& _,
if (result_storage_class != spv::StorageClass::Workgroup &&
result_storage_class != spv::StorageClass::CrossWorkgroup &&
result_storage_class != spv::StorageClass::Function)
result_storage_class != spv::StorageClass::Function &&
result_storage_class != spv::StorageClass::CodeSectionINTEL)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to have storage class Workgroup, "
<< "CrossWorkgroup or Function: " << spvOpcodeString(opcode);
<< "CrossWorkgroup, Function or CodeSectionINTEL: "
<< spvOpcodeString(opcode);
const uint32_t input_type = _.GetOperandTypeId(inst, operand_index);
spv::StorageClass input_storage_class;
@@ -633,10 +713,12 @@ spv_result_t ValidateGenericCastToPtrExplicit(ValidationState_t& _,
if (target_storage_class != spv::StorageClass::Workgroup &&
target_storage_class != spv::StorageClass::CrossWorkgroup &&
target_storage_class != spv::StorageClass::Function)
target_storage_class != spv::StorageClass::Function &&
target_storage_class != spv::StorageClass::CodeSectionINTEL)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected target storage class to be Workgroup, "
<< "CrossWorkgroup or Function: " << spvOpcodeString(opcode);
<< "CrossWorkgroup, Function or CodeSectionINTEL: "
<< spvOpcodeString(opcode);
return SPV_SUCCESS;
}
@@ -694,8 +776,8 @@ spv_result_t ValidateBitcast(ValidationState_t& _, const Instruction* inst,
}
if (result_is_coopmat) {
spv_result_t ret =
_.CooperativeMatrixShapesMatch(inst, result_type, input_type, false);
spv_result_t ret = _.CooperativeMatrixShapesMatch(inst, result_type,
input_type, false, false);
if (ret != SPV_SUCCESS) return ret;
}
@@ -782,40 +864,174 @@ spv_result_t ValidateConvertUToAccelerationStructure(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateBitcastExtract(ValidationState_t& _,
const Instruction* inst) {
const spv::Op opcode = inst->opcode();
const uint32_t result_type = inst->type_id();
const uint32_t base_type = _.GetOperandTypeId(inst, 2);
const uint32_t offset_type = _.GetOperandTypeId(inst, 3);
if (!IsNumericScalarOrVectorType(_, result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(12468)
<< "Expected Result Type to be a numerical scalar or vector type: "
<< spvOpcodeString(opcode);
}
if (spvIsVulkanEnv(_.context()->target_env) &&
!_.ContainsOCPMicroscalingNonByteType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(12468)
<< "Expected Result Type to be a Float4EXT or Float6EXT type in "
"the Vulkan environment: "
<< spvOpcodeString(opcode);
}
if (!base_type || !IsNumericScalarOrVectorType(_, base_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(12469)
<< "Expected Base to be a numerical scalar or vector type: "
<< spvOpcodeString(opcode);
}
if (spvIsVulkanEnv(_.context()->target_env) &&
!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(12469)
<< "Expected Base to be an integer scalar or vector type in the "
"Vulkan environment: "
<< spvOpcodeString(opcode);
}
if (_.GetDimension(result_type) != _.GetDimension(base_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Base to have the same dimension as Result Type: "
<< spvOpcodeString(opcode);
}
if (_.GetBitWidth(base_type) <= _.GetBitWidth(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Base component bit width to be greater than Result "
"Type component bit width: "
<< spvOpcodeString(opcode);
}
if (!offset_type || !_.IsIntScalarType(offset_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Offset to be a scalar integer type: "
<< spvOpcodeString(opcode);
}
return SPV_SUCCESS;
}
spv_result_t ValidateCooperativeMatrix(ValidationState_t& _,
const Instruction* inst) {
const spv::Op opcode = inst->opcode();
const uint32_t result_type = inst->type_id();
if (!_.IsCooperativeMatrixType(result_type)) {
if (!_.IsCooperativeMatrixKHRType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected cooperative matrix Result Type: "
<< "Expected OpTypeCooperativeMatrixKHR Result Type: "
<< spvOpcodeString(opcode);
}
const uint32_t input_type = _.GetOperandTypeId(inst, 2);
if (!_.IsCooperativeMatrixType(input_type)) {
if (!_.IsCooperativeMatrixKHRType(input_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected cooperative matrix type for Matrix input: "
<< "Expected OpTypeCooperativeMatrixKHR type for Matrix input: "
<< spvOpcodeString(opcode);
}
bool swap_row_col = (opcode == spv::Op::OpCooperativeMatrixTransposeNV);
const bool has_transpose_decoration = HasCoopMatTranspose(_, inst->id());
const bool swap_row_col = opcode == spv::Op::OpCooperativeMatrixTransposeNV ||
has_transpose_decoration;
if (auto error = _.CooperativeMatrixShapesMatch(inst, result_type, input_type,
true, swap_row_col))
return error;
if (opcode == spv::Op::OpCooperativeMatrixConvertNV) {
if (_.FindDef(result_type)->GetOperandAs<uint32_t>(1) !=
_.FindDef(input_type)->GetOperandAs<uint32_t>(1)) {
if (opcode == spv::Op::OpCooperativeMatrixConvertUseEXT) {
auto result_comp_type_id =
_.FindDef(result_type)->GetOperandAs<uint32_t>(1);
auto input_comp_type_id = _.FindDef(input_type)->GetOperandAs<uint32_t>(1);
auto result_comp_type = _.FindDef(result_comp_type_id);
auto input_comp_type = _.FindDef(input_comp_type_id);
const bool same_component_type = result_comp_type_id == input_comp_type_id;
const bool signedness_only_difference =
result_comp_type->opcode() == spv::Op::OpTypeInt &&
input_comp_type->opcode() == spv::Op::OpTypeInt &&
result_comp_type->word(2) == input_comp_type->word(2) &&
result_comp_type->word(3) != input_comp_type->word(3);
if (!same_component_type && !signedness_only_difference) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type and Matrix component types mismatch: "
<< spvOpcodeString(opcode);
}
const auto result_use_id =
_.FindDef(result_type)->GetOperandAs<uint32_t>(5);
const auto input_use_id = _.FindDef(input_type)->GetOperandAs<uint32_t>(5);
const auto result_use_eval = _.EvalInt32IfConst(result_use_id);
const auto input_use_eval = _.EvalInt32IfConst(input_use_id);
const bool result_is_const_int32 = std::get<1>(result_use_eval);
const bool input_is_const_int32 = std::get<1>(input_use_eval);
const uint32_t result_use = std::get<2>(result_use_eval);
const uint32_t input_use = std::get<2>(input_use_eval);
const auto is_accumulator = [](uint32_t use) {
return use == uint32_t(spv::CooperativeMatrixUse::MatrixAccumulatorKHR);
};
const auto is_a_or_b = [](uint32_t use) {
return use == uint32_t(spv::CooperativeMatrixUse::MatrixAKHR) ||
use == uint32_t(spv::CooperativeMatrixUse::MatrixBKHR);
};
bool invalid_use_change = result_use_id == input_use_id;
if (_.HasCapability(spv::Capability::CooperativeMatrixConversionsEXT)) {
if (result_is_const_int32 && input_is_const_int32) {
invalid_use_change |=
!((is_accumulator(input_use) && is_a_or_b(result_use)) ||
(is_a_or_b(input_use) && is_accumulator(result_use)));
} else {
invalid_use_change |=
(result_is_const_int32 && !is_accumulator(result_use) &&
!is_a_or_b(result_use)) ||
(input_is_const_int32 && !is_accumulator(input_use) &&
!is_a_or_b(input_use));
}
} else {
invalid_use_change |=
(result_is_const_int32 && !is_a_or_b(result_use)) ||
(input_is_const_int32 && !is_accumulator(input_use));
}
if (invalid_use_change) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Matrix and Result Type must convert between "
"MatrixAccumulatorKHR and MatrixAKHR or MatrixBKHR: "
<< spvOpcodeString(opcode);
}
}
if (opcode == spv::Op::OpCooperativeMatrixTransposeNV) {
if (!_.IsCooperativeMatrixBType(result_type)) {
const auto result_use_id =
_.FindDef(result_type)->GetOperandAs<uint32_t>(5);
const auto input_use_id = _.FindDef(input_type)->GetOperandAs<uint32_t>(5);
const auto result_use_eval = _.EvalInt32IfConst(result_use_id);
const auto input_use_eval = _.EvalInt32IfConst(input_use_id);
const bool result_is_const_int32 = std::get<1>(result_use_eval);
const bool input_is_const_int32 = std::get<1>(input_use_eval);
const uint32_t result_use = std::get<2>(result_use_eval);
const uint32_t input_use = std::get<2>(input_use_eval);
if (result_use_id == input_use_id ||
(result_is_const_int32 &&
result_use != uint32_t(spv::CooperativeMatrixUse::MatrixBKHR)) ||
(input_is_const_int32 &&
input_use !=
uint32_t(spv::CooperativeMatrixUse::MatrixAccumulatorKHR))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must have UseB: " << spvOpcodeString(opcode);
<< "Result Type must have UseB and Matrix must have "
"UseAccumulator: "
<< spvOpcodeString(opcode);
}
}
return SPV_SUCCESS;
@@ -920,9 +1136,11 @@ spv_result_t ConversionPass(ValidationState_t& _, const Instruction* inst) {
return ValidateGenericCastToPtrExplicit(_, inst);
case spv::Op::OpBitcast:
return ValidateBitcast(_, inst);
case spv::Op::OpBitcastExtractEXT:
return ValidateBitcastExtract(_, inst);
case spv::Op::OpConvertUToAccelerationStructureKHR:
return ValidateConvertUToAccelerationStructure(_, inst);
case spv::Op::OpCooperativeMatrixConvertNV:
case spv::Op::OpCooperativeMatrixConvertUseEXT:
case spv::Op::OpCooperativeMatrixTransposeNV:
return ValidateCooperativeMatrix(_, inst);
case spv::Op::OpBitCastArrayQCOM:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+45
View File
@@ -26,6 +26,7 @@
#include "source/latest_version_glsl_std_450_header.h"
#include "source/latest_version_opencl_std_header.h"
#include "source/opcode.h"
#include "source/operand.h"
#include "source/spirv_constant.h"
#include "source/table2.h"
#include "source/val/instruction.h"
@@ -163,6 +164,38 @@ std::string GetExtInstName(const ValidationState_t& _,
return ss.str();
}
// Rejects an instruction whose result or any operand uses a BFloat16 or FP8
// (E4M3/E5M2) type, i.e. an OpTypeFloat that is not IEEE 754 encoded.
spv_result_t ValidateExtInstFloatEncoding(ValidationState_t& _,
const Instruction* inst) {
auto check = [&](uint32_t type_id) -> spv_result_t {
if (_.IsBfloat16Type(type_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< GetExtInstName(_, inst) << ": doesn't support BFloat16 type.";
}
if (_.IsFP8Type(type_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< GetExtInstName(_, inst)
<< ": doesn't support FP8 E4M3/E5M2 types.";
}
return SPV_SUCCESS;
};
if (spv_result_t result = check(inst->type_id())) return result;
const uint32_t num_operands = static_cast<uint32_t>(inst->operands().size());
for (uint32_t operand_index = 4; operand_index < num_operands;
++operand_index) {
// Some ext instructions take literal operands (e.g. OpenCL.std vloadn's
// component count); only <id> operands have a meaningful result type.
if (!spvIsIdType(inst->operand(operand_index).type)) continue;
if (spv_result_t result = check(_.GetOperandTypeId(inst, operand_index)))
return result;
}
return SPV_SUCCESS;
}
// Returns the declared NSDI version from the OpExtInstImport referenced by
// |inst|. Returns 0 if not a NonSemantic.Shader.DebugInfo import.
uint32_t GetNSDIVersion(const ValidationState_t& _, const Instruction* inst) {
@@ -396,10 +429,14 @@ spv_result_t ValidateOperandDebugType(ValidationState_t& _,
// Check for common types.
std::function<bool(CommonDebugInfoInstructions)> expectation =
[&allow_template_param](CommonDebugInfoInstructions dbg_inst) {
// TODO - Should DebugTypeTemplateParameterPack be allowed?
if (allow_template_param &&
(dbg_inst == CommonDebugInfoDebugTypeTemplateParameter ||
dbg_inst == CommonDebugInfoDebugTypeTemplateTemplateParameter)) {
return true;
} else if (dbg_inst == CommonDebugInfoDebugInfoNone) {
// DebugInfoNone is a safe "null" type that can be used
return true;
}
return CommonDebugInfoDebugTypeBasic <= dbg_inst &&
dbg_inst <= CommonDebugInfoDebugTypeTemplate;
@@ -4416,6 +4453,14 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
const spv_ext_inst_type_t ext_inst_type =
spv_ext_inst_type_t(inst->ext_inst_type());
// GLSL.std.450 and OpenCL.std define a floating-point type as an OpTypeFloat
// using the IEEE 754 encoding, so they don't support BFloat16 or FP8 types.
if (ext_inst_type == SPV_EXT_INST_TYPE_GLSL_STD_450 ||
ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_STD) {
if (spv_result_t result = ValidateExtInstFloatEncoding(_, inst))
return result;
}
if (ext_inst_type == SPV_EXT_INST_TYPE_GLSL_STD_450) {
return ValidateExtInstGlslStd450(_, inst);
} else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_STD) {
+66 -12
View File
@@ -87,8 +87,8 @@ spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) {
spv::Op::OpGetKernelLocalSizeForSubgroupCount,
spv::Op::OpGetKernelMaxNumSubgroups,
spv::Op::OpName,
spv::Op::OpCooperativeMatrixPerElementOpNV,
spv::Op::OpCooperativeMatrixReduceNV,
spv::Op::OpCooperativeMatrixPerElementOpEXT,
spv::Op::OpCooperativeMatrixReduceEXT,
spv::Op::OpCooperativeMatrixLoadTensorNV,
spv::Op::OpConditionalEntryPointINTEL,
spv::Op::OpConstantFunctionPointerINTEL};
@@ -311,7 +311,7 @@ spv_result_t ValidateCooperativeMatrixPerElementOp(ValidationState_t& _,
const auto function = _.FindDef(function_id);
if (!function || spv::Op::OpFunction != function->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV Function <id> "
<< "OpCooperativeMatrixPerElementOpEXT Function <id> "
<< _.getIdName(function_id) << " is not a function.";
}
@@ -320,14 +320,14 @@ spv_result_t ValidateCooperativeMatrixPerElementOp(ValidationState_t& _,
const auto matrix_type_id = matrix->type_id();
if (!_.IsCooperativeMatrixKHRType(matrix_type_id)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV Matrix <id> "
<< "OpCooperativeMatrixPerElementOpEXT Matrix <id> "
<< _.getIdName(matrix_id) << " is not a cooperative matrix.";
}
const auto result_type_id = inst->GetOperandAs<uint32_t>(0);
if (matrix_type_id != result_type_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV Result Type <id> "
<< "OpCooperativeMatrixPerElementOpEXT Result Type <id> "
<< _.getIdName(result_type_id) << " must match matrix type <id> "
<< _.getIdName(matrix_type_id) << ".";
}
@@ -339,7 +339,7 @@ spv_result_t ValidateCooperativeMatrixPerElementOp(ValidationState_t& _,
auto return_type_id = function_type->GetOperandAs<uint32_t>(1);
if (return_type_id != matrix_comp_type_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV function return type <id> "
<< "OpCooperativeMatrixPerElementOpEXT function return type <id> "
<< _.getIdName(return_type_id)
<< " must match matrix component type <id> "
<< _.getIdName(matrix_comp_type_id) << ".";
@@ -347,9 +347,9 @@ spv_result_t ValidateCooperativeMatrixPerElementOp(ValidationState_t& _,
if (function_type->operands().size() < 5) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV function type <id> "
<< "OpCooperativeMatrixPerElementOpEXT function type <id> "
<< _.getIdName(function_type_id)
<< " must have a least three parameters.";
<< " must have at least three parameters.";
}
const auto param0_id = function_type->GetOperandAs<uint32_t>(2);
@@ -357,25 +357,79 @@ spv_result_t ValidateCooperativeMatrixPerElementOp(ValidationState_t& _,
const auto param2_id = function_type->GetOperandAs<uint32_t>(4);
if (!_.IsIntScalarType(param0_id, 32)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV function type first parameter "
<< "OpCooperativeMatrixPerElementOpEXT function type first "
"parameter "
"type <id> "
<< _.getIdName(param0_id) << " must be a 32-bit integer.";
}
if (!_.IsIntScalarType(param1_id, 32)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV function type second "
<< "OpCooperativeMatrixPerElementOpEXT function type second "
"parameter type <id> "
<< _.getIdName(param1_id) << " must be a 32-bit integer.";
}
if (param2_id != matrix_comp_type_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpNV function type third parameter "
<< "OpCooperativeMatrixPerElementOpEXT function type third "
"parameter "
"type <id> "
<< _.getIdName(param2_id) << " must match matrix component type.";
}
const auto optional_operand_count = inst->operands().size() - 4;
const auto expected_function_type_operands = 5 + optional_operand_count;
if (function_type->operands().size() != expected_function_type_operands) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpEXT function type <id> "
<< _.getIdName(function_type_id) << " must have "
<< (3 + optional_operand_count)
<< " parameters to match the instruction operands.";
}
for (uint32_t operand_index = 4; operand_index < inst->operands().size();
++operand_index) {
const auto optional_operand_id =
inst->GetOperandAs<uint32_t>(operand_index);
const auto optional_operand = _.FindDef(optional_operand_id);
const auto optional_operand_type_id =
optional_operand ? optional_operand->type_id() : 0;
const auto param_id =
function_type->GetOperandAs<uint32_t>(operand_index + 1);
if (!_.IsCooperativeMatrixType(optional_operand_type_id)) {
if (param_id != optional_operand_type_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpEXT function type optional "
"parameter type <id> "
<< _.getIdName(param_id)
<< " must match optional operand type <id> "
<< _.getIdName(optional_operand_type_id) << ".";
}
continue;
}
if (optional_operand_type_id != matrix_type_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpEXT optional matrix operand "
"type <id> "
<< _.getIdName(optional_operand_type_id)
<< " must match matrix type <id> " << _.getIdName(matrix_type_id)
<< ".";
}
const auto optional_matrix_comp_type_id =
_.FindDef(optional_operand_type_id)->GetOperandAs<uint32_t>(1);
if (param_id != optional_matrix_comp_type_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpCooperativeMatrixPerElementOpEXT function type optional "
"parameter type <id> "
<< _.getIdName(param_id)
<< " must match optional matrix component type.";
}
}
return SPV_SUCCESS;
}
@@ -392,7 +446,7 @@ spv_result_t FunctionPass(ValidationState_t& _, const Instruction* inst) {
case spv::Op::OpFunctionCall:
if (auto error = ValidateFunctionCall(_, inst)) return error;
break;
case spv::Op::OpCooperativeMatrixPerElementOpNV:
case spv::Op::OpCooperativeMatrixPerElementOpEXT:
if (auto error = ValidateCooperativeMatrixPerElementOp(_, inst))
return error;
break;
+4 -3
View File
@@ -131,14 +131,15 @@ bool InstructionCanHaveTypeOperand(const Instruction* inst) {
const auto opcode = inst->opcode();
bool type_instruction = spvOpcodeGeneratesType(opcode);
bool debug_instruction = spvOpcodeIsDebug(opcode) || inst->IsDebugInfo();
bool coop_matrix_spec_constant_op_length =
bool spec_constant_op_with_type_operand =
(opcode == spv::Op::OpSpecConstantOp) &&
(spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthNV ||
spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthKHR);
spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthKHR ||
spvOpcodeGeneratesUntypedPointer(spv::Op(inst->word(3))));
return type_instruction || debug_instruction || inst->IsNonSemantic() ||
spvOpcodeIsDecoration(opcode) || instruction_allow_set.count(opcode) ||
spvOpcodeGeneratesUntypedPointer(opcode) ||
coop_matrix_spec_constant_op_length;
spec_constant_op_with_type_operand;
}
bool InstructionRequiresTypeOperand(const Instruction* inst) {
+55 -6
View File
@@ -637,7 +637,8 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
if (opcode != spv::Op::OpImageGather &&
opcode != spv::Op::OpImageDrefGather &&
opcode != spv::Op::OpImageSparseGather &&
opcode != spv::Op::OpImageSparseDrefGather) {
opcode != spv::Op::OpImageSparseDrefGather &&
opcode != spv::Op::OpImageGatherQCOM) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(10213)
<< "Image Operand Offset can only be used with "
@@ -652,7 +653,8 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
if (opcode != spv::Op::OpImageGather &&
opcode != spv::Op::OpImageDrefGather &&
opcode != spv::Op::OpImageSparseGather &&
opcode != spv::Op::OpImageSparseDrefGather) {
opcode != spv::Op::OpImageSparseDrefGather &&
opcode != spv::Op::OpImageGatherQCOM) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Image Operand ConstOffsets can only be used with "
"OpImageGather and OpImageDrefGather";
@@ -908,7 +910,9 @@ bool IsSparse(spv::Op opcode) {
return true;
}
default: { return false; }
default: {
return false;
}
}
return false;
@@ -1192,6 +1196,7 @@ bool IsAllowedSampledImageOperand(spv::Op opcode, ValidationState_t& _) {
case spv::Op::OpImageBlockMatchWindowSSDQCOM:
case spv::Op::OpImageBlockMatchGatherSADQCOM:
case spv::Op::OpImageBlockMatchGatherSSDQCOM:
case spv::Op::OpImageGatherQCOM:
case spv::Op::OpImageSampleFootprintNV:
return true;
case spv::Op::OpStore:
@@ -1218,6 +1223,7 @@ spv_result_t ValidateImageCoordinate(ValidationState_t& _,
opcode == spv::Op::OpImageSampleProjDrefImplicitLod ||
opcode == spv::Op::OpImageSampleProjDrefExplicitLod ||
opcode == spv::Op::OpImageGather ||
opcode == spv::Op::OpImageGatherQCOM ||
opcode == spv::Op::OpImageDrefGather ||
opcode == spv::Op::OpImageQueryLod ||
opcode == spv::Op::OpImageSparseSampleImplicitLod ||
@@ -1846,8 +1852,10 @@ spv_result_t ValidateImageGather(ValidationState_t& _,
ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
return result;
unsigned n_additional_operands = 0;
if (opcode == spv::Op::OpImageGather ||
opcode == spv::Op::OpImageSparseGather) {
opcode == spv::Op::OpImageSparseGather ||
opcode == spv::Op::OpImageGatherQCOM) {
const uint32_t component = inst->GetOperandAs<uint32_t>(4);
const uint32_t component_index_type = _.GetTypeId(component);
if (!_.IsIntScalarType(component_index_type, 32)) {
@@ -1862,14 +1870,53 @@ spv_result_t ValidateImageGather(ValidationState_t& _,
"environment";
}
}
if (opcode == spv::Op::OpImageGatherQCOM) {
const uint32_t mode = inst->GetOperandAs<uint32_t>(5);
const uint32_t mode_index_type = _.GetTypeId(mode);
if (!_.IsIntScalarType(mode_index_type) ||
_.GetBitWidth(mode_index_type) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Mode to be 32-bit int scalar";
}
const spv::Op modeOpc = _.GetIdOpcode(mode);
if (spvOpcodeIsConstant(modeOpc) && !spvOpcodeIsSpecConstant(modeOpc)) {
uint64_t m_val = -1;
if (_.EvalConstantValUint64(mode, &m_val)) {
if (m_val == 0) {
if (!_.HasCapability(spv::Capability::ImageGatherLinearQCOM)) {
return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
<< "Mode GatherModesGather4x1QCOM (== 0) requires "
"capability ImageGatherLinearQCOM.";
}
} else if (m_val < 4) {
if (!_.HasCapability(
spv::Capability::ImageGatherExtendedModesQCOM)) {
return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
<< "Mode GatherModesGatherDQCOM (== "
"1)/GatherModesGatherH2QCOM (== 2"
")/GatherModesGatherV2QCOM (== 3)"
" requires capability ImageGatherExtendedModesQCOM.";
}
} else {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "GatherModesGather4x1QCOM (== 0)/"
"GatherModesGatherDQCOM (== 1)/"
"GatherModesGatherH2QCOM (== 2)/"
"GatherModesGatherV2QCOM (== 3) "
"are the only supported modes.";
}
}
}
n_additional_operands += 1;
}
} else {
assert(opcode == spv::Op::OpImageDrefGather ||
opcode == spv::Op::OpImageSparseDrefGather);
if (spv_result_t result = ValidateImageDref(_, inst, info)) return result;
}
if (spv_result_t result =
ValidateImageOperands(_, inst, info, /* word_index = */ 7))
if (spv_result_t result = ValidateImageOperands(
_, inst, info, /* word_index = */ (7 + n_additional_operands)))
return result;
return SPV_SUCCESS;
@@ -2682,6 +2729,7 @@ spv_result_t ImagePass(ValidationState_t& _, const Instruction* inst) {
case spv::Op::OpImageDrefGather:
case spv::Op::OpImageSparseGather:
case spv::Op::OpImageSparseDrefGather:
case spv::Op::OpImageGatherQCOM:
return ValidateImageGather(_, inst);
case spv::Op::OpImageRead:
@@ -2787,6 +2835,7 @@ bool IsImageInstruction(const spv::Op opcode) {
case spv::Op::OpImageBlockMatchWindowSSDQCOM:
case spv::Op::OpImageBlockMatchGatherSADQCOM:
case spv::Op::OpImageBlockMatchGatherSSDQCOM:
case spv::Op::OpImageGatherQCOM:
return true;
default:
break;
@@ -516,6 +516,16 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst) {
<< "OpSamplerImageAddressingModeNV bitwidth should be 64 or 32";
}
_.set_samplerimage_variable_address_mode(bitwidth);
} else if (opcode == spv::Op::OpSourceContinued) {
const auto& instructions = _.ordered_instructions();
size_t idx = inst - &instructions.front();
const Instruction* prev_inst = &instructions[idx - 1];
if (prev_inst->opcode() != spv::Op::OpSource &&
prev_inst->opcode() != spv::Op::OpSourceContinued) {
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
<< "OpSourceContinued must be preceded by OpSource or "
"OpSourceContinued.";
}
}
if (auto error = ReservedCheck(_, inst)) return error;
+9 -2
View File
@@ -740,7 +740,13 @@ spv_result_t ValidateStorageClass(ValidationState_t& _,
if (inst->words().size() > 3) {
auto encoding = inst->GetOperandAs<spv::FPEncoding>(2);
if ((encoding == spv::FPEncoding::Float8E4M3EXT) ||
(encoding == spv::FPEncoding::Float8E5M2EXT)) {
(encoding == spv::FPEncoding::Float8E5M2EXT) ||
(encoding == spv::FPEncoding::Float6E2M3EXT) ||
(encoding == spv::FPEncoding::Float6E3M2EXT) ||
(encoding == spv::FPEncoding::Float4E2M1EXT) ||
(encoding ==
spv::FPEncoding::Float8UnsignedE8M0EXT) ||
(encoding == spv::FPEncoding::MXInt8EXT)) {
return true;
}
}
@@ -748,7 +754,8 @@ spv_result_t ValidateStorageClass(ValidationState_t& _,
return false;
})) {
return _.diag(SPV_ERROR_INVALID_ID, interface_var)
<< _.VkErrorID(10823) << "FP8 E4M3/E5M2 OpVariable <id> "
<< _.VkErrorID(10823)
<< "FP8 or OCP microscaling OpVariable <id> "
<< _.getIdName(interface_var->id()) << " must not be declared "
<< "with a Storage Class of Input or Output.";
}
+30 -2
View File
@@ -56,6 +56,11 @@ bool IsFP8ScalarOrCompositeType(ValidationState_t& _, uint32_t type_id) {
}
}
bool IsOCPMicroscalingScalarOrCompositeType(ValidationState_t& _,
uint32_t type_id) {
return _.ContainsOCPMicroscalingType(type_id);
}
spv_result_t CheckInvalidScalarOrCompositeType(ValidationState_t& _,
const Instruction* inst,
uint32_t type_id) {
@@ -69,6 +74,11 @@ spv_result_t CheckInvalidScalarOrCompositeType(ValidationState_t& _,
<< spvOpcodeString(opcode)
<< " doesn't support FP8 E4M3/E5M2 types.";
}
if (IsOCPMicroscalingScalarOrCompositeType(_, type_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< " doesn't support OCP microscaling types.";
}
return SPV_SUCCESS;
}
@@ -80,8 +90,6 @@ spv_result_t InvalidTypePass(ValidationState_t& _, const Instruction* inst) {
const spv::Op opcode = inst->opcode();
switch (opcode) {
// OpExtInst
case spv::Op::OpExtInst:
// Arithmetic Instructions
case spv::Op::OpFAdd:
case spv::Op::OpFSub:
@@ -156,6 +164,11 @@ spv_result_t InvalidTypePass(ValidationState_t& _, const Instruction* inst) {
<< spvOpcodeString(opcode)
<< " doesn't support FP8 E4M3/E5M2 types.";
}
if (_.ContainsOCPMicroscalingType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< " doesn't support OCP microscaling types.";
}
break;
}
@@ -171,6 +184,11 @@ spv_result_t InvalidTypePass(ValidationState_t& _, const Instruction* inst) {
<< spvOpcodeString(opcode)
<< " doesn't support FP8 E4M3/E5M2 types.";
}
if (_.ContainsOCPMicroscalingType(data_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< " doesn't support OCP microscaling types.";
}
break;
}
// Relational and Logical Instructions
@@ -204,6 +222,11 @@ spv_result_t InvalidTypePass(ValidationState_t& _, const Instruction* inst) {
<< spvOpcodeString(opcode)
<< " doesn't support FP8 E4M3/E5M2 types.";
}
if (_.ContainsOCPMicroscalingType(operand_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< " doesn't support OCP microscaling types.";
}
break;
}
@@ -218,6 +241,11 @@ spv_result_t InvalidTypePass(ValidationState_t& _, const Instruction* inst) {
<< spvOpcodeString(opcode)
<< " doesn't support FP8 E4M3/E5M2 types.";
}
if (_.ContainsOCPMicroscalingType(value_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< " doesn't support OCP microscaling types.";
}
break;
}
+78 -11
View File
@@ -247,6 +247,8 @@ int MemoryAccessNumWords(uint32_t mask) {
if (mask & uint32_t(spv::MemoryAccessMask::Aligned)) ++result;
if (mask & uint32_t(spv::MemoryAccessMask::MakePointerAvailableKHR)) ++result;
if (mask & uint32_t(spv::MemoryAccessMask::MakePointerVisibleKHR)) ++result;
if (mask & uint32_t(spv::MemoryAccessMask::AliasScopeINTELMask)) ++result;
if (mask & uint32_t(spv::MemoryAccessMask::NoAliasINTELMask)) ++result;
return result;
}
@@ -684,6 +686,7 @@ spv_result_t ValidateVariablePointer(ValidationState_t& _,
}
}
}
return SPV_SUCCESS;
}
@@ -1074,6 +1077,25 @@ spv_result_t ValidateVariableShader(ValidationState_t& _,
<< sc_name << " storage class requires an additional capability";
}
}
if (_.ContainsOCPMicroscalingNonByteType(value_id)) {
auto underlying_type = value_type;
auto sc = storage_class;
while (underlying_type &&
underlying_type->opcode() == spv::Op::OpTypePointer) {
sc = underlying_type->GetOperandAs<spv::StorageClass>(1u);
underlying_type = _.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
}
if (sc != spv::StorageClass::Function && sc != spv::StorageClass::Private) {
std::string sc_name = _.grammar().lookupOperandName(
SPV_OPERAND_TYPE_STORAGE_CLASS, uint32_t(sc));
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Cannot allocate a variable containing a Float4EXT or "
"Float6EXT type in "
<< sc_name << " storage class";
}
}
return SPV_SUCCESS;
}
@@ -2413,7 +2435,7 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "Pointer must be an untyped pointer object";
}
} else if (pointer_ty->opcode() != spv::Op::OpTypePointer) {
} else if (!pointer_ty || pointer_ty->opcode() != spv::Op::OpTypePointer) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's type in Op" << spvOpcodeString(opcode)
<< " <id> " << state.getIdName(inst->id())
@@ -2498,6 +2520,41 @@ spv_result_t ValidateCooperativeMatrixLength(ValidationState_t& state,
return SPV_SUCCESS;
}
spv_result_t ValidateCooperativeMatrixGetCoordinateEXT(
ValidationState_t& state, const Instruction* inst) {
std::string instr_name = "OpCooperativeMatrixGetCoordinateEXT";
// Result type must be a uvec2
if (!state.IsIntVectorType(inst->type_id(), 32, 2)) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " Result Type <id> "
<< state.getIdName(inst->type_id())
<< " must be OpTypeVector with two 32-bit integer components.";
}
// Matrix operand must be a cooperative matrix
auto matrix_type_id =
state.FindDef(inst->GetOperandAs<uint32_t>(2))->type_id();
if (!state.IsCooperativeMatrixKHRType(matrix_type_id)) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " Matrix <id> "
<< state.getIdName(inst->GetOperandAs<uint32_t>(2))
<< " must be OpTypeCooperativeMatrixKHR.";
}
// Index operand must be a 32-bit int.
auto index_type_id =
state.FindDef(inst->GetOperandAs<uint32_t>(3))->type_id();
if (!state.IsIntScalarType(index_type_id, 32)) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " Index <id> "
<< state.getIdName(inst->GetOperandAs<uint32_t>(3))
<< " must be OpTypeInt with width 32.";
}
return SPV_SUCCESS;
}
spv_result_t ValidateCooperativeMatrixLoadStoreNV(ValidationState_t& _,
const Instruction* inst) {
uint32_t type_id;
@@ -2802,21 +2859,29 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _,
spv_result_t ValidateBufferPointerEXT(ValidationState_t& _,
const Instruction* inst) {
const auto storage_class_ptr = _.FindDef(inst->GetOperandAs<uint32_t>(0));
const auto storage_class_ptr = _.FindDef(inst->type_id());
if (storage_class_ptr->opcode() != spv::Op::OpTypeUntypedPointerKHR &&
storage_class_ptr->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpBufferPointerEXT's Result Type should be "
<< "a pointer type.";
} else {
// Buffer operand
auto buffer =
_.FindUntypedBaseVariable(_.FindDef(inst->GetOperandAs<uint32_t>(2)));
if (!buffer || !_.IsBuiltin(buffer->id(), spv::BuiltIn::ResourceHeapEXT)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpBufferPointerEXT's buffer must be an untyped pointer"
<< " into a variable declared with the ResourceHeapEXT built-in";
}
}
auto sc = storage_class_ptr->GetOperandAs<spv::StorageClass>(1u);
if (sc != spv::StorageClass::StorageBuffer &&
sc != spv::StorageClass::Uniform) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpBufferPointerEXT Result Type must be a pointer type "
<< "with a Storage Class of Uniform or StorageBuffer.";
}
// Buffer operand
auto buffer =
_.FindUntypedBaseVariable(_.FindDef(inst->GetOperandAs<uint32_t>(2)));
if (!buffer || !_.IsBuiltin(buffer->id(), spv::BuiltIn::ResourceHeapEXT)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpBufferPointerEXT's buffer must be an untyped pointer"
<< " into a variable declared with the ResourceHeapEXT built-in";
}
return SPV_SUCCESS;
}
@@ -3802,6 +3867,8 @@ spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
return ValidateCooperativeMatrixLength(_, inst, true);
case spv::Op::OpCooperativeMatrixLengthNV:
return ValidateCooperativeMatrixLength(_, inst, false);
case spv::Op::OpCooperativeMatrixGetCoordinateEXT:
return ValidateCooperativeMatrixGetCoordinateEXT(_, inst);
case spv::Op::OpCooperativeMatrixLoadKHR:
case spv::Op::OpCooperativeMatrixStoreKHR:
return ValidateCooperativeMatrixLoadStoreKHR(_, inst);
@@ -194,7 +194,7 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
}
if (!spvOpcodeIsAtomicOp(inst->opcode())) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(10874) << spvOpcodeString(opcode)
<< _.VkErrorID(13551) << spvOpcodeString(opcode)
<< ": Memory Semantics with Volatile bit set must not be used "
"with barrier instructions";
}
@@ -286,6 +286,36 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
}
}
if (is_vulkan && (opcode == spv::Op::OpControlBarrierArriveEXT ||
opcode == spv::Op::OpControlBarrierWaitEXT)) {
auto aquire_release_mask = uint32_t(
spv::MemorySemanticsMask::Acquire | spv::MemorySemanticsMask::Release |
spv::MemorySemanticsMask::AcquireRelease);
if (opcode == spv::Op::OpControlBarrierArriveEXT) {
if ((value & aquire_release_mask) !=
uint32_t(spv::MemorySemanticsMask::Release) &&
((value & aquire_release_mask) !=
uint32_t(spv::MemorySemanticsMask::MaskNone))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(13556) << spvOpcodeString(opcode)
<< " Memory Semantics must not have any non-relaxed memory "
"order set other than Release";
}
}
if (opcode == spv::Op::OpControlBarrierWaitEXT) {
if ((value & aquire_release_mask) !=
uint32_t(spv::MemorySemanticsMask::Acquire) &&
((value & aquire_release_mask) !=
uint32_t(spv::MemorySemanticsMask::MaskNone))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(13557) << spvOpcodeString(opcode)
<< " Memory Semantics must not have any non-relaxed memory "
"order set other than Acquire";
}
}
}
// TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
return SPV_SUCCESS;
+19 -2
View File
@@ -139,6 +139,15 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
});
}
if (opcode == spv::Op::OpControlBarrierArriveEXT ||
opcode == spv::Op::OpControlBarrierWaitEXT) {
if (value != spv::Scope::Workgroup && value != spv::Scope::Subgroup) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(13553)
<< "The execution Scope for OpControlBarrierArriveEXT and "
"OpControlBarrierWaitEXT must be Workgroup or Subgroup";
}
}
// Only subset of execution models support Workgroup.
if (value == spv::Scope::Workgroup) {
std::string errorVUID = _.VkErrorID(4637);
@@ -177,11 +186,19 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
// TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
// General SPIRV rules
// Scope for execution must be limited to Workgroup or Subgroup for
// non-uniform operations
// OpGroupNonUniformRotateKHR is the only non-uniform group operation that
// allows Workgroup scope; all others are limited to Subgroup.
if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
opcode != spv::Op::OpGroupNonUniformQuadAllKHR &&
opcode != spv::Op::OpGroupNonUniformQuadAnyKHR &&
opcode != spv::Op::OpGroupNonUniformRotateKHR &&
value != spv::Scope::Subgroup) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode) << ": Execution scope is limited to "
<< "Subgroup";
}
if (opcode == spv::Op::OpGroupNonUniformRotateKHR &&
value != spv::Scope::Subgroup && value != spv::Scope::Workgroup) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
+36 -2
View File
@@ -148,6 +148,37 @@ spv_result_t ValidateTypeFloat(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}
if (num_bits == 4) {
if (!encoding.has_value()) {
// we don't support fp4 without encoding
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "4-bit floating point type requires an encoding.";
}
if (encoding.value() != spv::FPEncoding::Float4E2M1EXT) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Unsupported 4-bit floating point encoding ("
<< static_cast<uint32_t>(encoding.value()) << ").";
}
return SPV_SUCCESS;
}
if (num_bits == 6) {
if (!encoding.has_value()) {
// we don't support fp6 without encoding
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "6-bit floating point type requires an encoding.";
}
const auto enc = encoding.value();
if (enc != spv::FPEncoding::Float6E2M3EXT &&
enc != spv::FPEncoding::Float6E3M2EXT) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Unsupported 6-bit floating point encoding ("
<< static_cast<uint32_t>(enc) << ").";
}
return SPV_SUCCESS;
}
if (num_bits == 16) {
// An absence of FP encoding implies IEEE 754. The Float16 and Float16Buffer
// capabilities only enable IEEE 754 binary 16
@@ -170,12 +201,15 @@ spv_result_t ValidateTypeFloat(ValidationState_t& _, const Instruction* inst) {
if (!_.features().declare_float8_type) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 8-bit floating point "
<< "type requires the Float8EXT capability.";
<< "type requires one of the following capabilities: Float8EXT, "
"Float8UnsignedE8M0EXT, MXInt8EXT.";
}
if (encoding.has_value()) {
const auto enc = encoding.value();
if (enc != spv::FPEncoding::Float8E4M3EXT &&
enc != spv::FPEncoding::Float8E5M2EXT) {
enc != spv::FPEncoding::Float8E5M2EXT &&
enc != spv::FPEncoding::Float8UnsignedE8M0EXT &&
enc != spv::FPEncoding::MXInt8EXT) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Unsupported 8-bit floating point encoding ("
<< static_cast<uint32_t>(enc) << ").";
+182 -16
View File
@@ -441,6 +441,8 @@ void ValidationState_t::RegisterCapability(spv::Capability cap) {
features_.declare_float16_type = true;
break;
case spv::Capability::Float8EXT:
case spv::Capability::Float8UnsignedE8M0EXT:
case spv::Capability::MXInt8EXT:
features_.declare_float8_type = true;
break;
case spv::Capability::StorageUniformBufferBlock16:
@@ -1176,6 +1178,48 @@ bool ValidationState_t::IsFP8Type(uint32_t id) const {
return IsFP8ScalarType(id) || IsFP8VectorType(id) || IsFP8CoopMatType(id);
}
bool ValidationState_t::IsOCPMicroscalingScalarType(uint32_t id) const {
const Instruction* inst = FindDef(id);
if (inst && inst->opcode() == spv::Op::OpTypeFloat &&
inst->words().size() > 3) {
const auto encoding = inst->GetOperandAs<spv::FPEncoding>(2);
return encoding == spv::FPEncoding::Float6E2M3EXT ||
encoding == spv::FPEncoding::Float6E3M2EXT ||
encoding == spv::FPEncoding::Float4E2M1EXT ||
encoding == spv::FPEncoding::Float8UnsignedE8M0EXT ||
encoding == spv::FPEncoding::MXInt8EXT;
}
return false;
}
bool ValidationState_t::IsOCPMicroscalingNonByteScalarType(uint32_t id) const {
const Instruction* inst = FindDef(id);
if (inst && inst->opcode() == spv::Op::OpTypeFloat &&
inst->words().size() > 3) {
const auto encoding = inst->GetOperandAs<spv::FPEncoding>(2);
return encoding == spv::FPEncoding::Float6E2M3EXT ||
encoding == spv::FPEncoding::Float6E3M2EXT ||
encoding == spv::FPEncoding::Float4E2M1EXT;
}
return false;
}
bool ValidationState_t::IsOCPMicroscalingType(uint32_t id) const {
return ContainsOCPMicroscalingType(id);
}
bool ValidationState_t::ContainsOCPMicroscalingType(uint32_t id) const {
return ContainsType(id, [this](const Instruction* inst) {
return IsOCPMicroscalingScalarType(inst->id());
});
}
bool ValidationState_t::ContainsOCPMicroscalingNonByteType(uint32_t id) const {
return ContainsType(id, [this](const Instruction* inst) {
return IsOCPMicroscalingNonByteScalarType(inst->id());
});
}
bool ValidationState_t::IsFloatScalarType(uint32_t id, uint32_t width) const {
const Instruction* inst = FindDef(id);
bool is_float = inst && inst->opcode() == spv::Op::OpTypeFloat;
@@ -1269,6 +1313,20 @@ bool ValidationState_t::IsIntVectorType(uint32_t id) const {
return false;
}
bool ValidationState_t::IsIntVectorType(uint32_t id, uint32_t width,
uint32_t components) const {
if (!IsIntVectorType(id)) {
return false;
}
if (GetBitWidth(id) != width) {
return false;
}
if (GetDimension(id) != components) {
return false;
}
return true;
}
bool ValidationState_t::IsIntScalarOrVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
if (!inst) {
@@ -1743,15 +1801,45 @@ spv_result_t ValidationState_t::CooperativeMatrixShapesMatch(
std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
EvalInt32IfConst(m2_use_id);
if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value &&
// CooperativeMatrixConversionsNV allows conversions from Acc->A/B
!(is_conversion &&
HasCapability(spv::Capability::CooperativeMatrixConversionsNV) &&
m2_value ==
(uint32_t)spv::CooperativeMatrixUse::MatrixAccumulatorKHR)) {
const auto is_accumulator = [](uint32_t use) {
return use == uint32_t(spv::CooperativeMatrixUse::MatrixAccumulatorKHR);
};
const auto is_a_or_b = [](uint32_t use) {
return use == uint32_t(spv::CooperativeMatrixUse::MatrixAKHR) ||
use == uint32_t(spv::CooperativeMatrixUse::MatrixBKHR);
};
if (swap_row_col &&
HasCapability(spv::Capability::CooperativeMatrixConversionsEXT) &&
(m1_use_id == m2_use_id ||
(m1_is_const_int32 && !is_a_or_b(m1_value)) ||
(m2_is_const_int32 && !is_accumulator(m2_value)))) {
return diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Use of Matrix type and Result Type to be "
<< "identical";
<< "A conversion decorated with CooperativeMatrixTransposeEXT "
"must have a MatrixAccumulatorKHR Matrix and a MatrixAKHR or "
"MatrixBKHR Result Type: "
<< spvOpcodeString(inst->opcode());
}
if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
bool allow_conv = false;
// Conversion from Acc->A,B allowed for NV or EXT
if ((HasCapability(spv::Capability::CooperativeMatrixConversionsNV) ||
HasCapability(spv::Capability::CooperativeMatrixConversionsEXT)) &&
is_accumulator(m2_value) && is_a_or_b(m1_value)) {
allow_conv = true;
}
// Conversion from A,B->Acc allowed for EXT
if (HasCapability(spv::Capability::CooperativeMatrixConversionsEXT) &&
is_accumulator(m1_value) && is_a_or_b(m2_value)) {
allow_conv = true;
}
if (!(is_conversion && allow_conv)) {
return diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Use of Matrix type and Result Type to be "
<< "identical";
}
}
}
@@ -1805,16 +1893,39 @@ bool ValidationState_t::EvalConstantValUint64(uint32_t id,
if (inst->opcode() == spv::Op::OpConstantNull) {
*val = 0;
} else if (inst->opcode() == spv::Op::OpConstantSizeOfEXT) {
auto type_op = GetIdOpcode(inst->GetOperandAs<uint32_t>(2u));
*val = 0;
switch (type_op) {
case spv::Op::OpTypeBufferEXT:
case spv::Op::OpTypeAccelerationStructureKHR:
*val = options()->buffer_descriptor_layout.size;
break;
case spv::Op::OpTypeSampler:
*val = options()->sampler_descriptor_layout.size;
break;
case spv::Op::OpTypeImage:
*val = options()->image_descriptor_layout.size;
break;
case spv::Op::OpTypeTensorARM:
*val = options()->tensor_descriptor_layout.size;
break;
default:
break;
}
return *val > 0;
} else if (inst->opcode() != spv::Op::OpConstant) {
// Spec constant values cannot be evaluated so don't consider constant for
// static validation
return false;
} else if (inst->words().size() == 4) {
*val = inst->word(3);
} else {
assert(inst->words().size() == 5);
} else if (inst->words().size() == 5) {
*val = inst->word(3);
*val |= uint64_t(inst->word(4)) << 32;
} else {
// Literal value wider than 64 bits does not fit in a uint64_t.
return false;
}
return true;
}
@@ -1830,17 +1941,40 @@ bool ValidationState_t::EvalConstantValInt64(uint32_t id, int64_t* val) const {
if (inst->opcode() == spv::Op::OpConstantNull) {
*val = 0;
} else if (inst->opcode() == spv::Op::OpConstantSizeOfEXT) {
auto type_op = GetIdOpcode(inst->GetOperandAs<uint32_t>(2u));
*val = 0;
switch (type_op) {
case spv::Op::OpTypeBufferEXT:
case spv::Op::OpTypeAccelerationStructureKHR:
*val = static_cast<int64_t>(options()->buffer_descriptor_layout.size);
break;
case spv::Op::OpTypeSampler:
*val = static_cast<int64_t>(options()->sampler_descriptor_layout.size);
break;
case spv::Op::OpTypeImage:
*val = static_cast<int64_t>(options()->image_descriptor_layout.size);
break;
case spv::Op::OpTypeTensorARM:
*val = static_cast<int64_t>(options()->tensor_descriptor_layout.size);
break;
default:
break;
}
return *val > 0;
} else if (inst->opcode() != spv::Op::OpConstant) {
// Spec constant values cannot be evaluated so don't consider constant for
// static validation
return false;
} else if (inst->words().size() == 4) {
*val = int32_t(inst->word(3));
} else {
assert(inst->words().size() == 5);
} else if (inst->words().size() == 5) {
const uint32_t lo_word = inst->word(3);
const uint32_t hi_word = inst->word(4);
*val = static_cast<int64_t>(uint64_t(lo_word) | uint64_t(hi_word) << 32);
} else {
// Literal value wider than 64 bits does not fit in an int64_t.
return false;
}
return true;
}
@@ -3154,6 +3288,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-PhysicalStorageBuffer64-04710);
case 4711:
return VUID_WRAP(VUID-StandaloneSpirv-OpTypeForwardPointer-04711);
case 4716:
return VUID_WRAP(VUID-StandaloneSpirv-Offset-04716);
case 4734:
return VUID_WRAP(VUID-StandaloneSpirv-OpVariable-04734);
case 4744:
@@ -3422,8 +3558,6 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-MemorySemantics-10872);
case 10873:
return VUID_WRAP(VUID-StandaloneSpirv-MemorySemantics-10873);
case 10874:
return VUID_WRAP(VUID-StandaloneSpirv-MemorySemantics-10874);
case 10875:
return VUID_WRAP(VUID-StandaloneSpirv-UnequalMemorySemantics-10875);
case 10876:
@@ -3451,14 +3585,24 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-Result-11337);
case 11339:
return VUID_WRAP(VUID-StandaloneSpirv-Result-11339);
case 11346:
return VUID_WRAP(VUID-StandaloneSpirv-Result-11346);
case 11347:
return VUID_WRAP(VUID-StandaloneSpirv-OpUntypedVariableKHR-11347);
case 11416:
return VUID_WRAP(VUID-StandaloneSpirv-OpUntypedImageTexelPointerEXT-11416);
case 11417:
return VUID_WRAP(VUID-StandaloneSpirv-OpTypeUntypedPointerKHR-11417);
// These RuntimeSpirv are here because now we can pass in the size/alignment
// which in turns mean we can validate things based on runtime properties
case 11476:
return VUID_WRAP(VUID-RuntimeSpirv-samplerDescriptorAlignment-11476);
case 11477:
return VUID_WRAP(VUID-RuntimeSpirv-imageDescriptorAlignment-11477);
case 11478:
return VUID_WRAP(VUID-RuntimeSpirv-bufferDescriptorAlignment-11478);
case 11479:
return VUID_WRAP(VUID-RuntimeSpirv-bufferDescriptorAlignment-11479);
case 11480:
return VUID_WRAP(VUID-RuntimeSpirv-tensorDescriptorAlignment-11480);
case 11482:
return VUID_WRAP(VUID-StandaloneSpirv-DescriptorHeapEXT-11482);
case 11805:
@@ -3471,6 +3615,28 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-None-12295);
case 12297:
return VUID_WRAP(VUID-StandaloneSpirv-Type-12297);
case 13551:
return VUID_WRAP(VUID-StandaloneSpirv-MemorySemantics-13551);
case 13552:
return VUID_WRAP(VUID-StandaloneSpirv-SplitBarrierEXT-13552);
case 13553:
return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrierArriveEXT-13553);
case 13556:
return VUID_WRAP(VUID-StandaloneSpirv-MemorySemantics-13556);
case 13557:
return VUID_WRAP(VUID-StandaloneSpirv-MemorySemantics-13557);
case 12465:
return VUID_WRAP(VUID-StandaloneSpirv-OpConvertFToU-12465);
case 12466:
return VUID_WRAP(VUID-StandaloneSpirv-OpFConvert-12466);
case 12467:
return VUID_WRAP(VUID-StandaloneSpirv-OpFConvert-12467);
case 12468:
return VUID_WRAP(VUID-StandaloneSpirv-Result-12468);
case 12469:
return VUID_WRAP(VUID-StandaloneSpirv-Base-12469);
case 12509:
return VUID_WRAP(VUID-StandaloneSpirv-Offset-12509);
default:
return ""; // unknown id
}
+7 -1
View File
@@ -713,6 +713,11 @@ class ValidationState_t {
bool IsFP8VectorType(uint32_t id) const;
bool IsFP8CoopMatType(uint32_t id) const;
bool IsFP8Type(uint32_t id) const;
bool IsOCPMicroscalingScalarType(uint32_t id) const;
bool IsOCPMicroscalingNonByteScalarType(uint32_t id) const;
bool IsOCPMicroscalingType(uint32_t id) const;
bool ContainsOCPMicroscalingType(uint32_t id) const;
bool ContainsOCPMicroscalingNonByteType(uint32_t id) const;
bool IsFloatScalarType(uint32_t id, uint32_t width = 0) const;
bool IsFloatArrayType(uint32_t id) const;
bool IsFloatVectorType(uint32_t id) const;
@@ -722,6 +727,7 @@ class ValidationState_t {
bool IsIntScalarType(uint32_t id, uint32_t width = 0) const;
bool IsIntScalarTypeWithSignedness(uint32_t id, uint32_t signedness) const;
bool IsIntVectorType(uint32_t id) const;
bool IsIntVectorType(uint32_t id, uint32_t width, uint32_t components) const;
bool IsIntScalarOrVectorType(uint32_t id) const;
bool IsUnsignedIntScalarType(uint32_t id) const;
bool IsUnsignedIntVectorType(uint32_t id) const;
@@ -956,7 +962,7 @@ class ValidationState_t {
spv_result_t CooperativeMatrixShapesMatch(const Instruction* inst,
uint32_t result_type_id,
uint32_t m2, bool is_conversion,
bool swap_row_col = false);
bool swap_row_col);
spv_result_t CooperativeVectorDimensionsMatch(const Instruction* inst,
uint32_t v1, uint32_t v2);