Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2024-10-18 20:28:32 -07:00
parent 0796d4ffab
commit 4157813255
76 changed files with 4871 additions and 4434 deletions

View File

@@ -1 +1 @@
"v2024.2", "SPIRV-Tools v2024.2 v2024.2.rc1-28-gf2bbb12a"
"v2024.4", "SPIRV-Tools v2024.4 v2024.4.rc1-27-g4a6ce351"

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,7 @@ kSPV_AMD_shader_trinary_minmax,
kSPV_AMD_texture_gather_bias_lod,
kSPV_ARM_cooperative_matrix_layouts,
kSPV_ARM_core_builtins,
kSPV_EXT_arithmetic_fence,
kSPV_EXT_demote_to_helper_invocation,
kSPV_EXT_descriptor_indexing,
kSPV_EXT_fragment_fully_covered,
@@ -19,7 +20,9 @@ kSPV_EXT_fragment_invocation_density,
kSPV_EXT_fragment_shader_interlock,
kSPV_EXT_mesh_shader,
kSPV_EXT_opacity_micromap,
kSPV_EXT_optnone,
kSPV_EXT_physical_storage_buffer,
kSPV_EXT_relaxed_printf_string_address_space,
kSPV_EXT_replicated_composites,
kSPV_EXT_shader_atomic_float16_add,
kSPV_EXT_shader_atomic_float_add,
@@ -68,6 +71,7 @@ kSPV_INTEL_optnone,
kSPV_INTEL_runtime_aligned,
kSPV_INTEL_shader_integer_functions2,
kSPV_INTEL_split_barrier,
kSPV_INTEL_subgroup_buffer_prefetch,
kSPV_INTEL_subgroups,
kSPV_INTEL_unstructured_loop_controls,
kSPV_INTEL_usm_storage_classes,
@@ -76,6 +80,7 @@ kSPV_INTEL_vector_compute,
kSPV_KHR_16bit_storage,
kSPV_KHR_8bit_storage,
kSPV_KHR_bit_instructions,
kSPV_KHR_compute_shader_derivatives,
kSPV_KHR_cooperative_matrix,
kSPV_KHR_device_group,
kSPV_KHR_expect_assume,
@@ -107,6 +112,7 @@ kSPV_KHR_subgroup_uniform_control_flow,
kSPV_KHR_subgroup_vote,
kSPV_KHR_terminate_invocation,
kSPV_KHR_uniform_group_instructions,
kSPV_KHR_untyped_pointers,
kSPV_KHR_variable_pointers,
kSPV_KHR_vulkan_memory_model,
kSPV_KHR_workgroup_memory_explicit_layout,

View File

@@ -20,7 +20,7 @@
{19, "Tellusim", "Clay Shader Compiler", "Tellusim Clay Shader Compiler"},
{20, "W3C WebGPU Group", "WHLSL Shader Translator", "W3C WebGPU Group WHLSL Shader Translator"},
{21, "Google", "Clspv", "Google Clspv"},
{22, "Google", "MLIR SPIR-V Serializer", "Google MLIR SPIR-V Serializer"},
{22, "LLVM", "MLIR SPIR-V Serializer", "LLVM MLIR SPIR-V Serializer"},
{23, "Google", "Tint Compiler", "Google Tint Compiler"},
{24, "Google", "ANGLE Shader Compiler", "Google ANGLE Shader Compiler"},
{25, "Netease Games", "Messiah Shader Compiler", "Netease Games Messiah Shader Compiler"},
@@ -41,4 +41,6 @@
{40, "NVIDIA", "Slang Compiler", "NVIDIA Slang Compiler"},
{41, "Zig Software Foundation", "Zig Compiler", "Zig Software Foundation Zig Compiler"},
{42, "Rendong Liang", "spq", "Rendong Liang spq"},
{43, "LLVM", "LLVM SPIR-V Backend", "LLVM LLVM SPIR-V Backend"},
{43, "LLVM", "LLVM SPIR-V Backend", "LLVM LLVM SPIR-V Backend"},
{44, "Robert Konrad", "Kongruent", "Robert Konrad Kongruent"},
{45, "Kitsunebi Games", "Nuvk SPIR-V Emitter and DLSL compiler", "Kitsunebi Games Nuvk SPIR-V Emitter and DLSL compiler"},

View File

@@ -1,12 +1,12 @@
static const spv_ext_inst_desc_t nonsemantic_vkspreflection_entries[] = {
{"Configuration", 1, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}},
{"StartCounter", 2, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_NONE}},
{"Configuration", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}},
{"StartCounter", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}},
{"StopCounter", 3, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}},
{"PushConstants", 4, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}},
{"SpecializationMapEntry", 5, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}},
{"DescriptorSetBuffer", 6, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}},
{"DescriptorSetImage", 7, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}},
{"DescriptorSetSampler", 8, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_FLOAT, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_FLOAT, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_FLOAT, SPV_OPERAND_TYPE_LITERAL_FLOAT, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}
{"PushConstants", 4, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}},
{"SpecializationMapEntry", 5, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}},
{"DescriptorSetBuffer", 6, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}},
{"DescriptorSetImage", 7, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}},
{"DescriptorSetSampler", 8, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +0,0 @@
// Copyright (c) 2018 The Khronos Group Inc.
// Copyright (c) 2018 Valve Corporation
// Copyright (c) 2018 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
#define INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
// Shader Instrumentation Interface
//
// This file provides an external interface for applications that wish to
// communicate with shaders instrumented by passes created by:
//
// CreateInstDebugPrintfPass
//
// More detailed documentation of these routines can be found in optimizer.hpp
namespace spvtools {
// Stream Output Buffer Offsets
//
// The following values provide offsets into the output buffer struct
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
// by InstDebugPrintfPass.
//
// The 1st member of the debug output buffer contains a set of flags
// controlling the behavior of instrumentation code.
static const int kDebugOutputFlagsOffset = 0;
// The 2nd member of the debug output buffer contains the next available word
// in the data stream to be written. Shaders will atomically read and update
// this value so as not to overwrite each others records. This value must be
// initialized to zero
static const int kDebugOutputSizeOffset = 1;
// The 3rd member of the output buffer is the start of the stream of records
// written by the instrumented shaders. Each record represents a validation
// error. The format of the records is documented below.
static const int kDebugOutputDataOffset = 2;
// Common Stream Record Offsets
//
// The following are offsets to fields which are common to all records written
// to the output stream.
//
// Each record first contains the size of the record in 32-bit words, including
// the size word.
static const int kInstCommonOutSize = 0;
// This is the shader id passed by the layer when the instrumentation pass is
// created.
static const int kInstCommonOutShaderId = 1;
// This is the ordinal position of the instruction within the SPIR-V shader
// which generated the validation error.
static const int kInstCommonOutInstructionIdx = 2;
// Debug Buffer Bindings
//
// These are the bindings for the different buffers which are
// read or written by the instrumentation passes.
//
// This is the output buffer written by InstDebugPrintfPass.
static const int kDebugOutputPrintfStream = 3;
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_

View File

@@ -175,6 +175,7 @@ typedef enum spv_operand_type_t {
SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS, // SPIR-V Sec 3.29
SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO, // SPIR-V Sec 3.30
SPV_OPERAND_TYPE_CAPABILITY, // SPIR-V Sec 3.31
SPV_OPERAND_TYPE_FPENCODING, // SPIR-V Sec 3.51
// NOTE: New concrete enum values should be added at the end.
@@ -236,6 +237,8 @@ typedef enum spv_operand_type_t {
// assemble regardless of where they occur -- literals, IDs, immediate
// integers, etc.
SPV_OPERAND_TYPE_OPTIONAL_CIV,
// An optional floating point encoding enum
SPV_OPERAND_TYPE_OPTIONAL_FPENCODING,
// A variable operand represents zero or more logical operands.
// In an instruction definition, this may only appear at the end of the
@@ -383,6 +386,11 @@ typedef enum spv_binary_to_text_options_t {
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES = SPV_BIT(6),
// Add some comments to the generated assembly
SPV_BINARY_TO_TEXT_OPTION_COMMENT = SPV_BIT(7),
// Use nested indentation for more readable SPIR-V
SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT = SPV_BIT(8),
// Reorder blocks to match the structured control flow of SPIR-V to increase
// readability.
SPV_BINARY_TO_TEXT_OPTION_REORDER_BLOCKS = SPV_BIT(9),
SPV_FORCE_32_BIT_ENUM(spv_binary_to_text_options_t)
} spv_binary_to_text_options_t;

View File

@@ -20,7 +20,7 @@
#include <string>
#include <vector>
#include "spirv-tools/libspirv.h"
#include "libspirv.h"
namespace spvtools {

View File

@@ -16,7 +16,6 @@
#define INCLUDE_SPIRV_TOOLS_LINKER_HPP_
#include <cstdint>
#include <memory>
#include <vector>
@@ -63,11 +62,17 @@ class SPIRV_TOOLS_EXPORT LinkerOptions {
use_highest_version_ = use_highest_vers;
}
bool GetAllowPtrTypeMismatch() const { return allow_ptr_type_mismatch_; }
void SetAllowPtrTypeMismatch(bool allow_ptr_type_mismatch) {
allow_ptr_type_mismatch_ = allow_ptr_type_mismatch;
}
private:
bool create_library_{false};
bool verify_ids_{false};
bool allow_partial_linkage_{false};
bool use_highest_version_{false};
bool allow_ptr_type_mismatch_{false};
};
// Links one or more SPIR-V modules into a new SPIR-V module. That is, combine

View File

@@ -747,18 +747,6 @@ Optimizer::PassToken CreateReduceLoadSizePass(
// them into a single instruction where possible.
Optimizer::PassToken CreateCombineAccessChainsPass();
// Create a pass to instrument OpDebugPrintf instructions.
// This pass replaces all OpDebugPrintf instructions with instructions to write
// a record containing the string id and the all specified values into a special
// printf output buffer (if space allows). This pass is designed to support
// the printf validation in the Vulkan validation layers.
//
// The instrumentation will write buffers in debug descriptor set |desc_set|.
// It will write |shader_id| in each output record to identify the shader
// module which generated the record.
Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
uint32_t shader_id);
// Create a pass to upgrade to the VulkanKHR memory model.
// This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
// Additionally, it modifies memory, image, atomic and barrier operations to
@@ -827,14 +815,19 @@ Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass();
// Create descriptor scalar replacement pass.
// This pass replaces every array variable |desc| that has a DescriptorSet and
// Binding decorations with a new variable for each element of the array.
// Suppose |desc| was bound at binding |b|. Then the variable corresponding to
// |desc[i]| will have binding |b+i|. The descriptor set will be the same. It
// is assumed that no other variable already has a binding that will used by one
// of the new variables. If not, the pass will generate invalid Spir-V. All
// accesses to |desc| must be OpAccessChain instructions with a literal index
// for the first index.
// Binding decorations with a new variable for each element of the
// array/composite. Suppose |desc| was bound at binding |b|. Then the variable
// corresponding to |desc[i]| will have binding |b+i|. The descriptor set will
// be the same. It is assumed that no other variable already has a binding that
// will used by one of the new variables. If not, the pass will generate
// invalid Spir-V. All accesses to |desc| must be OpAccessChain instructions
// with a literal index for the first index. This variant flattens both
// composites and arrays.
Optimizer::PassToken CreateDescriptorScalarReplacementPass();
// This variant flattens only composites.
Optimizer::PassToken CreateDescriptorCompositeScalarReplacementPass();
// This variant flattens only arrays.
Optimizer::PassToken CreateDescriptorArrayScalarReplacementPass();
// Create a pass to replace each OpKill instruction with a function call to a
// function that has a single OpKill. Also replace each OpTerminateInvocation
@@ -951,6 +944,15 @@ Optimizer::PassToken CreateFixFuncCallArgumentsPass();
// the unknown capability interacts with one of the trimmed capabilities.
Optimizer::PassToken CreateTrimCapabilitiesPass();
// Creates a struct-packing pass.
// This pass re-assigns all offset layout decorators to tightly pack
// the struct with OpName matching `structToPack` according to the given packing
// rule. Accepted packing rules are: std140, std140EnhancedLayout, std430,
// std430EnhancedLayout, hlslCbuffer, hlslCbufferPackOffset, scalar,
// scalarEnhancedLayout.
Optimizer::PassToken CreateStructPackingPass(const char* structToPack,
const char* packingRule);
// Creates a switch-descriptorset pass.
// This pass changes any DescriptorSet decorations with the value |ds_from| to
// use the new value |ds_to|.

View File

@@ -671,6 +671,11 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
case SPV_OPERAND_TYPE_OVERFLOW_MODES:
case SPV_OPERAND_TYPE_PACKED_VECTOR_FORMAT:
case SPV_OPERAND_TYPE_OPTIONAL_PACKED_VECTOR_FORMAT:
case SPV_OPERAND_TYPE_FPENCODING:
case SPV_OPERAND_TYPE_OPTIONAL_FPENCODING:
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: {
// A single word that is a plain enum value.
@@ -679,6 +684,8 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
parsed_operand.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER;
if (type == SPV_OPERAND_TYPE_OPTIONAL_PACKED_VECTOR_FORMAT)
parsed_operand.type = SPV_OPERAND_TYPE_PACKED_VECTOR_FORMAT;
if (type == SPV_OPERAND_TYPE_OPTIONAL_FPENCODING)
parsed_operand.type = SPV_OPERAND_TYPE_FPENCODING;
spv_operand_desc entry;
if (grammar_.lookupOperand(type, word, &entry)) {
@@ -699,7 +706,7 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
<< ", if you are creating a new source language please use "
"value 0 "
"(Unknown) and when ready, add your source language to "
"SPRIV-Headers";
"SPIRV-Headers";
}
// Prepare to accept operands to this operand, if needed.
spvPushOperandTypes(entry->operandTypes, expected_operands);

View File

@@ -24,6 +24,8 @@
#include <cstring>
#include <iomanip>
#include <memory>
#include <set>
#include <stack>
#include <unordered_map>
#include <utility>
@@ -43,6 +45,70 @@
namespace spvtools {
namespace {
// Indices to ControlFlowGraph's list of blocks from one block to its successors
struct BlockSuccessors {
// Merge block in OpLoopMerge and OpSelectionMerge
uint32_t merge_block_id = 0;
// The continue block in OpLoopMerge
uint32_t continue_block_id = 0;
// The true and false blocks in OpBranchConditional
uint32_t true_block_id = 0;
uint32_t false_block_id = 0;
// The body block of a loop, as specified by OpBranch after a merge
// instruction
uint32_t body_block_id = 0;
// The same-nesting-level block that follows this one, indicated by an
// OpBranch with no merge instruction.
uint32_t next_block_id = 0;
// The cases (including default) of an OpSwitch
std::vector<uint32_t> case_block_ids;
};
class ParsedInstruction {
public:
ParsedInstruction(const spv_parsed_instruction_t* instruction) {
// Make a copy of the parsed instruction, including stable memory for its
// operands.
instruction_ = *instruction;
operands_ =
std::make_unique<spv_parsed_operand_t[]>(instruction->num_operands);
memcpy(operands_.get(), instruction->operands,
instruction->num_operands * sizeof(*instruction->operands));
instruction_.operands = operands_.get();
}
const spv_parsed_instruction_t* get() const { return &instruction_; }
private:
spv_parsed_instruction_t instruction_;
std::unique_ptr<spv_parsed_operand_t[]> operands_;
};
// One block in the CFG
struct SingleBlock {
// The byte offset in the SPIR-V where the block starts. Used for printing in
// a comment.
size_t byte_offset;
// Block instructions
std::vector<ParsedInstruction> instructions;
// Successors of this block
BlockSuccessors successors;
// The nesting level for this block.
uint32_t nest_level = 0;
bool nest_level_assigned = false;
// Whether the block was reachable
bool reachable = false;
};
// CFG for one function
struct ControlFlowGraph {
std::vector<SingleBlock> blocks;
};
// A Disassembler instance converts a SPIR-V binary to its assembly
// representation.
class Disassembler {
@@ -50,6 +116,10 @@ class Disassembler {
Disassembler(const AssemblyGrammar& grammar, uint32_t options,
NameMapper name_mapper)
: print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
nested_indent_(
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT, options)),
reorder_blocks_(
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_REORDER_BLOCKS, options)),
text_(),
out_(print_ ? out_stream() : out_stream(text_)),
instruction_disassembler_(grammar, out_.get(), options, name_mapper),
@@ -70,7 +140,13 @@ class Disassembler {
spv_result_t SaveTextResult(spv_text* text_result) const;
private:
void EmitCFG();
const bool print_; // Should we also print to the standard output stream?
const bool nested_indent_; // Should the blocks be indented according to the
// control flow structure?
const bool
reorder_blocks_; // Should the blocks be reordered for readability?
spv_endianness_t endian_; // The detected endianness of the binary.
std::stringstream text_; // Captures the text, if not printing.
out_stream out_; // The Output stream. Either to text_ or standard output.
@@ -80,6 +156,9 @@ class Disassembler {
bool inserted_decoration_space_ = false;
bool inserted_debug_space_ = false;
bool inserted_type_space_ = false;
// The CFG for the current function
ControlFlowGraph current_function_cfg_;
};
spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
@@ -106,13 +185,336 @@ spv_result_t Disassembler::HandleInstruction(
inserted_debug_space_,
inserted_type_space_);
instruction_disassembler_.EmitInstruction(inst, byte_offset_);
// When nesting needs to be calculated or when the blocks are reordered, we
// have to have the full picture of the CFG first. Defer processing of the
// instructions until the entire function is visited. This is not done
// without those options (even if simpler) to improve debuggability; for
// example to be able to see whatever is parsed so far even if there is a
// parse error.
if (nested_indent_ || reorder_blocks_) {
switch (static_cast<spv::Op>(inst.opcode)) {
case spv::Op::OpLabel: {
// Add a new block to the CFG
SingleBlock new_block;
new_block.byte_offset = byte_offset_;
new_block.instructions.emplace_back(&inst);
current_function_cfg_.blocks.push_back(std::move(new_block));
break;
}
case spv::Op::OpFunctionEnd:
// Process the CFG and output the instructions
EmitCFG();
// Output OpFunctionEnd itself too
[[fallthrough]];
default:
if (!current_function_cfg_.blocks.empty()) {
// If in a function, stash the instruction for later.
current_function_cfg_.blocks.back().instructions.emplace_back(&inst);
} else {
// Otherwise emit the instruction right away.
instruction_disassembler_.EmitInstruction(inst, byte_offset_);
}
break;
}
} else {
instruction_disassembler_.EmitInstruction(inst, byte_offset_);
}
byte_offset_ += inst.num_words * sizeof(uint32_t);
return SPV_SUCCESS;
}
// Helper to get the operand of an instruction as an id.
uint32_t GetOperand(const spv_parsed_instruction_t* instruction,
uint32_t operand) {
return instruction->words[instruction->operands[operand].offset];
}
std::unordered_map<uint32_t, uint32_t> BuildControlFlowGraph(
ControlFlowGraph& cfg) {
std::unordered_map<uint32_t, uint32_t> id_to_index;
for (size_t index = 0; index < cfg.blocks.size(); ++index) {
SingleBlock& block = cfg.blocks[index];
// For future use, build the ID->index map
assert(static_cast<spv::Op>(block.instructions[0].get()->opcode) ==
spv::Op::OpLabel);
const uint32_t id = block.instructions[0].get()->result_id;
id_to_index[id] = static_cast<uint32_t>(index);
// Look for a merge instruction first. The function of OpBranch depends on
// that.
if (block.instructions.size() >= 3) {
const spv_parsed_instruction_t* maybe_merge =
block.instructions[block.instructions.size() - 2].get();
switch (static_cast<spv::Op>(maybe_merge->opcode)) {
case spv::Op::OpLoopMerge:
block.successors.merge_block_id = GetOperand(maybe_merge, 0);
block.successors.continue_block_id = GetOperand(maybe_merge, 1);
break;
case spv::Op::OpSelectionMerge:
block.successors.merge_block_id = GetOperand(maybe_merge, 0);
break;
default:
break;
}
}
// Then look at the last instruction; it must be a branch
assert(block.instructions.size() >= 2);
const spv_parsed_instruction_t* branch = block.instructions.back().get();
switch (static_cast<spv::Op>(branch->opcode)) {
case spv::Op::OpBranch:
if (block.successors.merge_block_id != 0) {
block.successors.body_block_id = GetOperand(branch, 0);
} else {
block.successors.next_block_id = GetOperand(branch, 0);
}
break;
case spv::Op::OpBranchConditional:
block.successors.true_block_id = GetOperand(branch, 1);
block.successors.false_block_id = GetOperand(branch, 2);
break;
case spv::Op::OpSwitch:
for (uint32_t case_index = 1; case_index < branch->num_operands;
case_index += 2) {
block.successors.case_block_ids.push_back(
GetOperand(branch, case_index));
}
break;
default:
break;
}
}
return id_to_index;
}
// Helper to deal with nesting and non-existing ids / previously-assigned
// levels. It assigns a given nesting level `level` to the block identified by
// `id` (unless that block already has a nesting level assigned).
void Nest(ControlFlowGraph& cfg,
const std::unordered_map<uint32_t, uint32_t>& id_to_index,
uint32_t id, uint32_t level) {
if (id == 0) {
return;
}
const uint32_t block_index = id_to_index.at(id);
SingleBlock& block = cfg.blocks[block_index];
if (!block.nest_level_assigned) {
block.nest_level = level;
block.nest_level_assigned = true;
}
}
// For a given block, assign nesting level to its successors.
void NestSuccessors(ControlFlowGraph& cfg, const SingleBlock& block,
const std::unordered_map<uint32_t, uint32_t>& id_to_index) {
assert(block.nest_level_assigned);
// Nest loops as such:
//
// %loop = OpLabel
// OpLoopMerge %merge %cont ...
// OpBranch %body
// %body = OpLabel
// Op...
// %cont = OpLabel
// Op...
// %merge = OpLabel
// Op...
//
// Nest conditional branches as such:
//
// %header = OpLabel
// OpSelectionMerge %merge ...
// OpBranchConditional ... %true %false
// %true = OpLabel
// Op...
// %false = OpLabel
// Op...
// %merge = OpLabel
// Op...
//
// Nest switch/case as such:
//
// %header = OpLabel
// OpSelectionMerge %merge ...
// OpSwitch ... %default ... %case0 ... %case1 ...
// %default = OpLabel
// Op...
// %case0 = OpLabel
// Op...
// %case1 = OpLabel
// Op...
// ...
// %merge = OpLabel
// Op...
//
// The following can be observed:
//
// - In all cases, the merge block has the same nesting as this block
// - The continue block of loops is nested 1 level deeper
// - The body/branches/cases are nested 2 levels deeper
//
// Back branches to the header block, branches to the merge block, etc
// are correctly handled by processing the header block first (that is
// _this_ block, already processed), then following the above rules
// (in the same order) for any block that is not already processed.
Nest(cfg, id_to_index, block.successors.merge_block_id, block.nest_level);
Nest(cfg, id_to_index, block.successors.continue_block_id,
block.nest_level + 1);
Nest(cfg, id_to_index, block.successors.true_block_id, block.nest_level + 2);
Nest(cfg, id_to_index, block.successors.false_block_id, block.nest_level + 2);
Nest(cfg, id_to_index, block.successors.body_block_id, block.nest_level + 2);
Nest(cfg, id_to_index, block.successors.next_block_id, block.nest_level);
for (uint32_t case_block_id : block.successors.case_block_ids) {
Nest(cfg, id_to_index, case_block_id, block.nest_level + 2);
}
}
struct StackEntry {
// The index of the block (in ControlFlowGraph::blocks) to process.
uint32_t block_index;
// Whether this is the pre or post visit of the block. Because a post-visit
// traversal is needed, the same block is pushed back on the stack on
// pre-visit so it can be visited again on post-visit.
bool post_visit = false;
};
// Helper to deal with DFS traversal and non-existing ids
void VisitSuccesor(std::stack<StackEntry>* dfs_stack,
const std::unordered_map<uint32_t, uint32_t>& id_to_index,
uint32_t id) {
if (id != 0) {
dfs_stack->push({id_to_index.at(id), false});
}
}
// Given the control flow graph, calculates and returns the reverse post-order
// ordering of the blocks. The blocks are then disassembled in that order for
// readability.
std::vector<uint32_t> OrderBlocks(
ControlFlowGraph& cfg,
const std::unordered_map<uint32_t, uint32_t>& id_to_index) {
std::vector<uint32_t> post_order;
// Nest level of a function's first block is 0.
cfg.blocks[0].nest_level = 0;
cfg.blocks[0].nest_level_assigned = true;
// Stack of block indices as they are visited.
std::stack<StackEntry> dfs_stack;
dfs_stack.push({0, false});
std::set<uint32_t> visited;
while (!dfs_stack.empty()) {
const uint32_t block_index = dfs_stack.top().block_index;
const bool post_visit = dfs_stack.top().post_visit;
dfs_stack.pop();
// If this is the second time the block is visited, that's the post-order
// visit.
if (post_visit) {
post_order.push_back(block_index);
continue;
}
// If already visited, another path got to it first (like a case
// fallthrough), avoid reprocessing it.
if (visited.count(block_index) > 0) {
continue;
}
visited.insert(block_index);
// Push it back in the stack for post-order visit
dfs_stack.push({block_index, true});
SingleBlock& block = cfg.blocks[block_index];
// Assign nest levels of successors right away. The successors are either
// nested under this block, or are back or forward edges to blocks outside
// this nesting level (no farther than the merge block), whose nesting
// levels are already assigned before this block is visited.
NestSuccessors(cfg, block, id_to_index);
block.reachable = true;
// The post-order visit yields the order in which the blocks are naturally
// ordered _backwards_. So blocks to be ordered last should be visited
// first. In other words, they should be pushed to the DFS stack last.
VisitSuccesor(&dfs_stack, id_to_index, block.successors.true_block_id);
VisitSuccesor(&dfs_stack, id_to_index, block.successors.false_block_id);
VisitSuccesor(&dfs_stack, id_to_index, block.successors.body_block_id);
VisitSuccesor(&dfs_stack, id_to_index, block.successors.next_block_id);
for (uint32_t case_block_id : block.successors.case_block_ids) {
VisitSuccesor(&dfs_stack, id_to_index, case_block_id);
}
VisitSuccesor(&dfs_stack, id_to_index, block.successors.continue_block_id);
VisitSuccesor(&dfs_stack, id_to_index, block.successors.merge_block_id);
}
std::vector<uint32_t> order(post_order.rbegin(), post_order.rend());
// Finally, dump all unreachable blocks at the end
for (size_t index = 0; index < cfg.blocks.size(); ++index) {
SingleBlock& block = cfg.blocks[index];
if (!block.reachable) {
order.push_back(static_cast<uint32_t>(index));
block.nest_level = 0;
block.nest_level_assigned = true;
}
}
return order;
}
void Disassembler::EmitCFG() {
// Build the CFG edges. At the same time, build an ID->block index map to
// simplify building the CFG edges.
const std::unordered_map<uint32_t, uint32_t> id_to_index =
BuildControlFlowGraph(current_function_cfg_);
// Walk the CFG in reverse post-order to find the best ordering of blocks for
// presentation
std::vector<uint32_t> block_order =
OrderBlocks(current_function_cfg_, id_to_index);
assert(block_order.size() == current_function_cfg_.blocks.size());
// Walk the CFG either in block order or input order based on whether the
// reorder_blocks_ option is given.
for (uint32_t index = 0; index < current_function_cfg_.blocks.size();
++index) {
const uint32_t block_index = reorder_blocks_ ? block_order[index] : index;
const SingleBlock& block = current_function_cfg_.blocks[block_index];
// Emit instructions for this block
size_t byte_offset = block.byte_offset;
assert(block.nest_level_assigned);
for (const ParsedInstruction& inst : block.instructions) {
instruction_disassembler_.EmitInstructionInBlock(*inst.get(), byte_offset,
block.nest_level);
byte_offset += inst.get()->num_words * sizeof(uint32_t);
}
}
current_function_cfg_.blocks.clear();
}
spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
if (!print_) {
size_t length = text_.str().size();
@@ -203,7 +605,7 @@ uint32_t GetLineLengthWithoutColor(const std::string line) {
if (line[i] == '\x1b') {
do {
++i;
} while (line[i] != 'm');
} while (i < line.size() && line[i] != 'm');
continue;
}
@@ -214,6 +616,8 @@ uint32_t GetLineLengthWithoutColor(const std::string line) {
}
constexpr int kStandardIndent = 15;
constexpr int kBlockNestIndent = 2;
constexpr int kBlockBodyIndentOffset = 2;
constexpr uint32_t kCommentColumn = 50;
} // namespace
@@ -229,6 +633,8 @@ InstructionDisassembler::InstructionDisassembler(const AssemblyGrammar& grammar,
indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
? kStandardIndent
: 0),
nested_indent_(
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT, options)),
comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
show_byte_offset_(
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
@@ -265,12 +671,29 @@ void InstructionDisassembler::EmitHeaderSchema(uint32_t schema) {
void InstructionDisassembler::EmitInstruction(
const spv_parsed_instruction_t& inst, size_t inst_byte_offset) {
EmitInstructionImpl(inst, inst_byte_offset, 0, false);
}
void InstructionDisassembler::EmitInstructionInBlock(
const spv_parsed_instruction_t& inst, size_t inst_byte_offset,
uint32_t block_indent) {
EmitInstructionImpl(inst, inst_byte_offset, block_indent, true);
}
void InstructionDisassembler::EmitInstructionImpl(
const spv_parsed_instruction_t& inst, size_t inst_byte_offset,
uint32_t block_indent, bool is_in_block) {
auto opcode = static_cast<spv::Op>(inst.opcode);
// To better align the comments (if any), write the instruction to a line
// first so its length can be readily available.
std::ostringstream line;
if (nested_indent_ && opcode == spv::Op::OpLabel) {
// Separate the blocks by an empty line to make them easier to separate
stream_ << std::endl;
}
if (inst.result_id) {
SetBlue();
const std::string id_name = name_mapper_(inst.result_id);
@@ -283,6 +706,17 @@ void InstructionDisassembler::EmitInstruction(
line << std::string(indent_, ' ');
}
if (nested_indent_ && is_in_block) {
// Output OpLabel at the specified nest level, and instructions inside
// blocks nested a little more.
uint32_t indent = block_indent;
bool body_indent = opcode != spv::Op::OpLabel;
line << std::string(
indent * kBlockNestIndent + (body_indent ? kBlockBodyIndentOffset : 0),
' ');
}
line << "Op" << spvOpcodeString(opcode);
for (uint16_t i = 0; i < inst.num_operands; i++) {
@@ -386,6 +820,11 @@ void InstructionDisassembler::EmitSectionComment(
auto opcode = static_cast<spv::Op>(inst.opcode);
if (comment_ && opcode == spv::Op::OpFunction) {
stream_ << std::endl;
if (nested_indent_) {
// Double the empty lines between Function sections since nested_indent_
// also separates blocks by a blank.
stream_ << std::endl;
}
stream_ << std::string(indent_, ' ');
stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
}
@@ -505,6 +944,7 @@ void InstructionDisassembler::EmitOperand(std::ostream& stream,
case SPV_OPERAND_TYPE_FPDENORM_MODE:
case SPV_OPERAND_TYPE_FPOPERATION_MODE:
case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
case SPV_OPERAND_TYPE_FPENCODING:
case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
spv_operand_desc entry;
if (grammar_.lookupOperand(operand.type, word, &entry))

View File

@@ -58,6 +58,11 @@ class InstructionDisassembler {
// Emits the assembly text for the given instruction.
void EmitInstruction(const spv_parsed_instruction_t& inst,
size_t inst_byte_offset);
// Same as EmitInstruction, but only for block instructions (including
// OpLabel) and useful for nested indentation. If nested indentation is not
// desired, EmitInstruction can still be used for block instructions.
void EmitInstructionInBlock(const spv_parsed_instruction_t& inst,
size_t inst_byte_offset, uint32_t block_indent);
// Emits a comment between different sections of the module.
void EmitSectionComment(const spv_parsed_instruction_t& inst,
@@ -82,6 +87,10 @@ class InstructionDisassembler {
void SetRed(std::ostream& stream) const;
void SetGreen(std::ostream& stream) const;
void EmitInstructionImpl(const spv_parsed_instruction_t& inst,
size_t inst_byte_offset, uint32_t block_indent,
bool is_in_block);
// Emits an operand for the given instruction, where the instruction
// is at offset words from the start of the binary.
void EmitOperand(std::ostream& stream, const spv_parsed_instruction_t& inst,
@@ -97,10 +106,11 @@ class InstructionDisassembler {
const spvtools::AssemblyGrammar& grammar_;
std::ostream& stream_;
const bool print_; // Should we also print to the standard output stream?
const bool color_; // Should we print in colour?
const int indent_; // How much to indent. 0 means don't indent
const int comment_; // Should we comment the source
const bool print_; // Should we also print to the standard output stream?
const bool color_; // Should we print in colour?
const int indent_; // How much to indent. 0 means don't indent
const bool nested_indent_; // Whether indentation should indicate nesting
const int comment_; // Should we comment the source
const bool show_byte_offset_; // Should we print byte offset, in hex?
spvtools::NameMapper name_mapper_;

View File

@@ -31,6 +31,7 @@
#include "source/opt/build_module.h"
#include "source/opt/compact_ids_pass.h"
#include "source/opt/decoration_manager.h"
#include "source/opt/ir_builder.h"
#include "source/opt/ir_loader.h"
#include "source/opt/pass_manager.h"
#include "source/opt/remove_duplicates_pass.h"
@@ -46,12 +47,14 @@ namespace spvtools {
namespace {
using opt::Instruction;
using opt::InstructionBuilder;
using opt::IRContext;
using opt::Module;
using opt::PassManager;
using opt::RemoveDuplicatesPass;
using opt::analysis::DecorationManager;
using opt::analysis::DefUseManager;
using opt::analysis::Function;
using opt::analysis::Type;
using opt::analysis::TypeManager;
@@ -126,6 +129,7 @@ spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
// checked.
spv_result_t CheckImportExportCompatibility(const MessageConsumer& consumer,
const LinkageTable& linkings_to_do,
bool allow_ptr_type_mismatch,
opt::IRContext* context);
// Remove linkage specific instructions, such as prototypes of imported
@@ -502,6 +506,7 @@ spv_result_t GetImportExportPairs(const MessageConsumer& consumer,
spv_result_t CheckImportExportCompatibility(const MessageConsumer& consumer,
const LinkageTable& linkings_to_do,
bool allow_ptr_type_mismatch,
opt::IRContext* context) {
spv_position_t position = {};
@@ -513,7 +518,34 @@ spv_result_t CheckImportExportCompatibility(const MessageConsumer& consumer,
type_manager.GetType(linking_entry.imported_symbol.type_id);
Type* exported_symbol_type =
type_manager.GetType(linking_entry.exported_symbol.type_id);
if (!(*imported_symbol_type == *exported_symbol_type))
if (!(*imported_symbol_type == *exported_symbol_type)) {
Function* imported_symbol_type_func = imported_symbol_type->AsFunction();
Function* exported_symbol_type_func = exported_symbol_type->AsFunction();
if (imported_symbol_type_func && exported_symbol_type_func) {
const auto& imported_params = imported_symbol_type_func->param_types();
const auto& exported_params = exported_symbol_type_func->param_types();
// allow_ptr_type_mismatch allows linking functions where the pointer
// type of arguments doesn't match. Everything else still needs to be
// equal. This is to workaround LLVM-17+ not having typed pointers and
// generated SPIR-Vs not knowing the actual pointer types in some cases.
if (allow_ptr_type_mismatch &&
imported_params.size() == exported_params.size()) {
bool correct = true;
for (size_t i = 0; i < imported_params.size(); i++) {
const auto& imported_param = imported_params[i];
const auto& exported_param = exported_params[i];
if (!imported_param->IsSame(exported_param) &&
(imported_param->kind() != Type::kPointer ||
exported_param->kind() != Type::kPointer)) {
correct = false;
break;
}
}
if (correct) continue;
}
}
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
<< "Type mismatch on symbol \""
<< linking_entry.imported_symbol.name
@@ -521,6 +553,7 @@ spv_result_t CheckImportExportCompatibility(const MessageConsumer& consumer,
<< linking_entry.imported_symbol.id
<< " and exported variable/function %"
<< linking_entry.exported_symbol.id << ".";
}
}
// Ensure the import and export decorations are similar
@@ -696,6 +729,57 @@ spv_result_t VerifyLimits(const MessageConsumer& consumer,
return SPV_SUCCESS;
}
spv_result_t FixFunctionCallTypes(opt::IRContext& context,
const LinkageTable& linkings) {
auto mod = context.module();
const auto type_manager = context.get_type_mgr();
const auto def_use_mgr = context.get_def_use_mgr();
for (auto& func : *mod) {
func.ForEachInst([&](Instruction* inst) {
if (inst->opcode() != spv::Op::OpFunctionCall) return;
opt::Operand& target = inst->GetInOperand(0);
// only fix calls to imported functions
auto linking = std::find_if(
linkings.begin(), linkings.end(), [&](const auto& entry) {
return entry.exported_symbol.id == target.AsId();
});
if (linking == linkings.end()) return;
auto builder = InstructionBuilder(&context, inst);
for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
auto exported_func_param =
def_use_mgr->GetDef(linking->exported_symbol.parameter_ids[i - 1]);
const Type* target_type =
type_manager->GetType(exported_func_param->type_id());
if (target_type->kind() != Type::kPointer) continue;
opt::Operand& arg = inst->GetInOperand(i);
const Type* param_type =
type_manager->GetType(def_use_mgr->GetDef(arg.AsId())->type_id());
// No need to cast if it already matches
if (*param_type == *target_type) continue;
auto new_id = context.TakeNextId();
// cast to the expected pointer type
builder.AddInstruction(MakeUnique<opt::Instruction>(
&context, spv::Op::OpBitcast, exported_func_param->type_id(),
new_id,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {arg.AsId()}}})));
inst->SetInOperand(i, {new_id});
}
});
}
context.InvalidateAnalyses(opt::IRContext::kAnalysisDefUse |
opt::IRContext::kAnalysisInstrToBlockMapping);
return SPV_SUCCESS;
}
} // namespace
spv_result_t Link(const Context& context,
@@ -773,7 +857,14 @@ spv_result_t Link(const Context& context, const uint32_t* const* binaries,
if (res != SPV_SUCCESS) return res;
}
// Phase 4: Find the import/export pairs
// Phase 4: Remove duplicates
PassManager manager;
manager.SetMessageConsumer(consumer);
manager.AddPass<RemoveDuplicatesPass>();
opt::Pass::Status pass_res = manager.Run(&linked_context);
if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
// Phase 5: Find the import/export pairs
LinkageTable linkings_to_do;
res = GetImportExportPairs(consumer, linked_context,
*linked_context.get_def_use_mgr(),
@@ -781,18 +872,12 @@ spv_result_t Link(const Context& context, const uint32_t* const* binaries,
options.GetAllowPartialLinkage(), &linkings_to_do);
if (res != SPV_SUCCESS) return res;
// Phase 5: Ensure the import and export have the same types and decorations.
res =
CheckImportExportCompatibility(consumer, linkings_to_do, &linked_context);
// Phase 6: Ensure the import and export have the same types and decorations.
res = CheckImportExportCompatibility(consumer, linkings_to_do,
options.GetAllowPtrTypeMismatch(),
&linked_context);
if (res != SPV_SUCCESS) return res;
// Phase 6: Remove duplicates
PassManager manager;
manager.SetMessageConsumer(consumer);
manager.AddPass<RemoveDuplicatesPass>();
opt::Pass::Status pass_res = manager.Run(&linked_context);
if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
// Phase 7: Remove all names and decorations of import variables/functions
for (const auto& linking_entry : linkings_to_do) {
linked_context.KillNamesAndDecorates(linking_entry.imported_symbol.id);
@@ -815,21 +900,27 @@ spv_result_t Link(const Context& context, const uint32_t* const* binaries,
&linked_context);
if (res != SPV_SUCCESS) return res;
// Phase 10: Compact the IDs used in the module
// Phase 10: Optionally fix function call types
if (options.GetAllowPtrTypeMismatch()) {
res = FixFunctionCallTypes(linked_context, linkings_to_do);
if (res != SPV_SUCCESS) return res;
}
// Phase 11: Compact the IDs used in the module
manager.AddPass<opt::CompactIdsPass>();
pass_res = manager.Run(&linked_context);
if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
// Phase 11: Recompute EntryPoint variables
// Phase 12: Recompute EntryPoint variables
manager.AddPass<opt::RemoveUnusedInterfaceVariablesPass>();
pass_res = manager.Run(&linked_context);
if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
// Phase 12: Warn if SPIR-V limits were exceeded
// Phase 13: Warn if SPIR-V limits were exceeded
res = VerifyLimits(consumer, linked_context);
if (res != SPV_SUCCESS) return res;
// Phase 13: Output the module
// Phase 14: Output the module
linked_context.module()->ToBinary(linked_binary, true);
return SPV_SUCCESS;

View File

@@ -25,24 +25,15 @@
#include "source/binary.h"
#include "source/latest_version_spirv_header.h"
#include "source/parsed_operand.h"
#include "source/to_string.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
namespace {
// Converts a uint32_t to its string decimal representation.
std::string to_string(uint32_t id) {
// Use stringstream, since some versions of Android compilers lack
// std::to_string.
std::stringstream os;
os << id;
return os.str();
NameMapper GetTrivialNameMapper() {
return [](uint32_t i) { return spvtools::to_string(i); };
}
} // anonymous namespace
NameMapper GetTrivialNameMapper() { return to_string; }
FriendlyNameMapper::FriendlyNameMapper(const spv_const_context context,
const uint32_t* code,
const size_t wordCount)
@@ -218,6 +209,7 @@ spv_result_t FriendlyNameMapper::ParseInstruction(
} break;
case spv::Op::OpTypeFloat: {
const auto bit_width = inst.words[2];
// TODO: Handle optional fpencoding enum once actually used.
switch (bit_width) {
case 16:
SaveName(result_id, "half");
@@ -255,6 +247,11 @@ spv_result_t FriendlyNameMapper::ParseInstruction(
inst.words[2]) +
"_" + NameForId(inst.words[3]));
break;
case spv::Op::OpTypeUntypedPointerKHR:
SaveName(result_id, std::string("_ptr_") +
NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS,
inst.words[2]));
break;
case spv::Op::OpTypePipe:
SaveName(result_id,
std::string("Pipe") +

View File

@@ -102,7 +102,7 @@ spv_result_t spvOpcodeTableNameLookup(spv_target_env env,
const auto version = spvVersionForTargetEnv(env);
for (uint64_t opcodeIndex = 0; opcodeIndex < table->count; ++opcodeIndex) {
const spv_opcode_desc_t& entry = table->entries[opcodeIndex];
// We considers the current opcode as available as long as
// We consider the current opcode as available as long as
// 1. The target environment satisfies the minimal requirement of the
// opcode; or
// 2. There is at least one extension enabling this opcode.
@@ -110,13 +110,34 @@ spv_result_t spvOpcodeTableNameLookup(spv_target_env env,
// Note that the second rule assumes the extension enabling this instruction
// is indeed requested in the SPIR-V code; checking that should be
// validator's work.
if (((version >= entry.minVersion && version <= entry.lastVersion) ||
entry.numExtensions > 0u || entry.numCapabilities > 0u) &&
nameLength == strlen(entry.name) &&
!strncmp(name, entry.name, nameLength)) {
// NOTE: Found out Opcode!
*pEntry = &entry;
return SPV_SUCCESS;
if ((version >= entry.minVersion && version <= entry.lastVersion) ||
entry.numExtensions > 0u || entry.numCapabilities > 0u) {
// Exact match case.
if (nameLength == strlen(entry.name) &&
!strncmp(name, entry.name, nameLength)) {
*pEntry = &entry;
return SPV_SUCCESS;
}
// Lack of binary search really hurts here. There isn't an easy filter to
// apply before checking aliases since we need to handle promotion from
// vendor to KHR/EXT and KHR/EXT to core. It would require a sure-fire way
// of dropping suffices. Fortunately, most lookup are based on token
// value.
//
// If this was a binary search we could iterate between the lower and
// upper bounds.
if (entry.numAliases > 0) {
for (uint32_t aliasIndex = 0; aliasIndex < entry.numAliases;
aliasIndex++) {
// Skip Op prefix. Should this be encoded in the table instead?
const auto alias = entry.aliases[aliasIndex] + 2;
const size_t aliasLength = strlen(alias);
if (nameLength == aliasLength && !strncmp(name, alias, nameLength)) {
*pEntry = &entry;
return SPV_SUCCESS;
}
}
}
}
}
@@ -133,8 +154,8 @@ spv_result_t spvOpcodeTableValueLookup(spv_target_env env,
const auto beg = table->entries;
const auto end = table->entries + table->count;
spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {},
false, false, 0, nullptr, ~0u, ~0u};
spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {}, 0,
{}, false, false, 0, nullptr, ~0u, ~0u};
auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) {
return lhs.opcode < rhs.opcode;
@@ -189,6 +210,7 @@ const char* spvOpcodeString(const uint32_t opcode) {
spv_opcode_desc_t needle = {"", static_cast<spv::Op>(opcode),
0, nullptr,
0, {},
0, {},
false, false,
0, nullptr,
~0u, ~0u};
@@ -276,6 +298,7 @@ int32_t spvOpcodeIsComposite(const spv::Op opcode) {
case spv::Op::OpTypeMatrix:
case spv::Op::OpTypeArray:
case spv::Op::OpTypeStruct:
case spv::Op::OpTypeRuntimeArray:
case spv::Op::OpTypeCooperativeMatrixNV:
case spv::Op::OpTypeCooperativeMatrixKHR:
return true;
@@ -287,8 +310,11 @@ int32_t spvOpcodeIsComposite(const spv::Op opcode) {
bool spvOpcodeReturnsLogicalVariablePointer(const spv::Op opcode) {
switch (opcode) {
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpFunctionParameter:
case spv::Op::OpImageTexelPointer:
case spv::Op::OpCopyObject:
@@ -296,6 +322,7 @@ bool spvOpcodeReturnsLogicalVariablePointer(const spv::Op opcode) {
case spv::Op::OpPhi:
case spv::Op::OpFunctionCall:
case spv::Op::OpPtrAccessChain:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpLoad:
case spv::Op::OpConstantNull:
case spv::Op::OpRawAccessChainNV:
@@ -308,8 +335,11 @@ bool spvOpcodeReturnsLogicalVariablePointer(const spv::Op opcode) {
int32_t spvOpcodeReturnsLogicalPointer(const spv::Op opcode) {
switch (opcode) {
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpFunctionParameter:
case spv::Op::OpImageTexelPointer:
case spv::Op::OpCopyObject:
@@ -351,6 +381,7 @@ int32_t spvOpcodeGeneratesType(spv::Op op) {
// spv::Op::OpTypeAccelerationStructureNV
case spv::Op::OpTypeRayQueryKHR:
case spv::Op::OpTypeHitObjectNV:
case spv::Op::OpTypeUntypedPointerKHR:
return true;
default:
// In particular, OpTypeForwardPointer does not generate a type,
@@ -792,3 +823,16 @@ bool spvOpcodeIsBit(spv::Op opcode) {
return false;
}
}
bool spvOpcodeGeneratesUntypedPointer(spv::Op opcode) {
switch (opcode) {
case spv::Op::OpUntypedVariableKHR:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
return true;
default:
return false;
}
}

View File

@@ -162,4 +162,7 @@ bool spvOpcodeIsBit(spv::Op opcode);
// Gets the name of an instruction, without the "Op" prefix.
const char* spvOpcodeString(const spv::Op opcode);
// Returns true for opcodes that generate an untyped pointer result.
bool spvOpcodeGeneratesUntypedPointer(spv::Op opcode);
#endif // SOURCE_OPCODE_H_

View File

@@ -64,11 +64,29 @@ spv_result_t spvOperandTableNameLookup(spv_target_env,
// We consider the current operand as available as long as
// it is in the grammar. It might not be *valid* to use,
// but that should be checked by the validator, not by parsing.
//
// Exact match case
if (nameLength == strlen(entry.name) &&
!strncmp(entry.name, name, nameLength)) {
*pEntry = &entry;
return SPV_SUCCESS;
}
// Check the aliases. Ideally we would have a version of the table sorted
// by name and then we could iterate between the lower and upper bounds to
// restrict the amount comparisons. Fortunately, name-based lookups are
// mostly restricted to the assembler.
if (entry.numAliases > 0) {
for (uint32_t aliasIndex = 0; aliasIndex < entry.numAliases;
aliasIndex++) {
const auto alias = entry.aliases[aliasIndex];
const size_t aliasLength = strlen(alias);
if (nameLength == aliasLength && !strncmp(name, alias, nameLength)) {
*pEntry = &entry;
return SPV_SUCCESS;
}
}
}
}
}
@@ -83,7 +101,8 @@ spv_result_t spvOperandTableValueLookup(spv_target_env,
if (!table) return SPV_ERROR_INVALID_TABLE;
if (!pEntry) return SPV_ERROR_INVALID_POINTER;
spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr, {}, ~0u, ~0u};
spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr,
0, nullptr, {}, ~0u, ~0u};
auto comp = [](const spv_operand_desc_t& lhs, const spv_operand_desc_t& rhs) {
return lhs.value < rhs.value;
@@ -252,6 +271,9 @@ const char* spvOperandTypeStr(spv_operand_type_t type) {
return "OpenCL.DebugInfo.100 debug operation";
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
return "OpenCL.DebugInfo.100 debug imported entity";
case SPV_OPERAND_TYPE_FPENCODING:
case SPV_OPERAND_TYPE_OPTIONAL_FPENCODING:
return "FP encoding";
// The next values are for values returned from an instruction, not actually
// an operand. So the specific strings don't matter. But let's add them
@@ -366,6 +388,7 @@ bool spvOperandIsConcrete(spv_operand_type_t type) {
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_FPENCODING:
return true;
default:
break;
@@ -407,6 +430,7 @@ bool spvOperandIsOptional(spv_operand_type_t type) {
case SPV_OPERAND_TYPE_OPTIONAL_COOPERATIVE_MATRIX_OPERANDS:
case SPV_OPERAND_TYPE_OPTIONAL_CIV:
case SPV_OPERAND_TYPE_OPTIONAL_RAW_ACCESS_CHAIN_OPERANDS:
case SPV_OPERAND_TYPE_OPTIONAL_FPENCODING:
return true;
default:
break;

View File

@@ -40,6 +40,9 @@ constexpr uint32_t kCopyMemorySourceAddrInIdx = 1;
constexpr uint32_t kLoadSourceAddrInIdx = 0;
constexpr uint32_t kDebugDeclareOperandVariableIndex = 5;
constexpr uint32_t kGlobalVariableVariableIndex = 12;
constexpr uint32_t kExtInstSetInIdx = 0;
constexpr uint32_t kExtInstOpInIdx = 1;
constexpr uint32_t kInterpolantInIdx = 2;
// Sorting functor to present annotation instructions in an easy-to-process
// order. The functor orders by opcode first and falls back on unique id
@@ -134,7 +137,12 @@ void AggressiveDCEPass::AddStores(Function* func, uint32_t ptrId) {
}
break;
// If default, assume it stores e.g. frexp, modf, function call
case spv::Op::OpStore:
case spv::Op::OpStore: {
const uint32_t kStoreTargetAddrInIdx = 0;
if (user->GetSingleWordInOperand(kStoreTargetAddrInIdx) == ptrId)
AddToWorklist(user);
break;
}
default:
AddToWorklist(user);
break;
@@ -262,6 +270,7 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
}
bool AggressiveDCEPass::AggressiveDCE(Function* func) {
if (func->IsDeclaration()) return false;
std::list<BasicBlock*> structured_order;
cfg()->ComputeStructuredOrder(func, &*func->begin(), &structured_order);
live_local_vars_.clear();
@@ -416,6 +425,19 @@ uint32_t AggressiveDCEPass::GetLoadedVariableFromNonFunctionCalls(
case spv::Op::OpCopyMemorySized:
return GetVariableId(
inst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx));
case spv::Op::OpExtInst: {
if (inst->GetSingleWordInOperand(kExtInstSetInIdx) ==
context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450()) {
auto ext_inst = inst->GetSingleWordInOperand(kExtInstOpInIdx);
switch (ext_inst) {
case GLSLstd450InterpolateAtCentroid:
case GLSLstd450InterpolateAtOffset:
case GLSLstd450InterpolateAtSample:
return inst->GetSingleWordInOperand(kInterpolantInIdx);
}
}
break;
}
default:
break;
}
@@ -1004,7 +1026,10 @@ void AggressiveDCEPass::InitExtensions() {
"SPV_NV_bindless_texture",
"SPV_EXT_shader_atomic_float_add",
"SPV_EXT_fragment_shader_interlock",
"SPV_NV_compute_shader_derivatives"
"SPV_KHR_compute_shader_derivatives",
"SPV_NV_cooperative_matrix",
"SPV_KHR_cooperative_matrix",
"SPV_KHR_ray_tracing_position_fetch"
});
// clang-format on
}

View File

@@ -24,7 +24,7 @@
namespace spvtools {
// Builds an Module returns the owning IRContext from the given SPIR-V
// Builds a Module and returns the owning IRContext from the given SPIR-V
// |binary|. |size| specifies number of words in |binary|. The |binary| will be
// decoded according to the given target |env|. Returns nullptr if errors occur
// and sends the errors to |consumer|. When |extra_line_tracking| is true,
@@ -41,7 +41,7 @@ std::unique_ptr<opt::IRContext> BuildModule(spv_target_env env,
const uint32_t* binary,
size_t size);
// Builds an Module and returns the owning IRContext from the given
// Builds a Module and returns the owning IRContext from the given
// SPIR-V assembly |text|. The |text| will be encoded according to the given
// target |env|. Returns nullptr if errors occur and sends the errors to
// |consumer|.

View File

@@ -21,59 +21,6 @@ namespace opt {
namespace {
constexpr uint32_t kExtractCompositeIdInIdx = 0;
// Returns the value obtained by extracting the |number_of_bits| least
// significant bits from |value|, and sign-extending it to 64-bits.
uint64_t SignExtendValue(uint64_t value, uint32_t number_of_bits) {
if (number_of_bits == 64) return value;
uint64_t mask_for_sign_bit = 1ull << (number_of_bits - 1);
uint64_t mask_for_significant_bits = (mask_for_sign_bit << 1) - 1ull;
if (value & mask_for_sign_bit) {
// Set upper bits to 1
value |= ~mask_for_significant_bits;
} else {
// Clear the upper bits
value &= mask_for_significant_bits;
}
return value;
}
// Returns the value obtained by extracting the |number_of_bits| least
// significant bits from |value|, and zero-extending it to 64-bits.
uint64_t ZeroExtendValue(uint64_t value, uint32_t number_of_bits) {
if (number_of_bits == 64) return value;
uint64_t mask_for_first_bit_to_clear = 1ull << (number_of_bits);
uint64_t mask_for_bits_to_keep = mask_for_first_bit_to_clear - 1;
value &= mask_for_bits_to_keep;
return value;
}
// Returns a constant whose value is `value` and type is `type`. This constant
// will be generated by `const_mgr`. The type must be a scalar integer type.
const analysis::Constant* GenerateIntegerConstant(
const analysis::Integer* integer_type, uint64_t result,
analysis::ConstantManager* const_mgr) {
assert(integer_type != nullptr);
std::vector<uint32_t> words;
if (integer_type->width() == 64) {
// In the 64-bit case, two words are needed to represent the value.
words = {static_cast<uint32_t>(result),
static_cast<uint32_t>(result >> 32)};
} else {
// In all other cases, only a single word is needed.
assert(integer_type->width() <= 32);
if (integer_type->IsSigned()) {
result = SignExtendValue(result, integer_type->width());
} else {
result = ZeroExtendValue(result, integer_type->width());
}
words = {static_cast<uint32_t>(result)};
}
return const_mgr->GetConstant(integer_type, words);
}
// Returns a constants with the value NaN of the given type. Only works for
// 32-bit and 64-bit float point types. Returns |nullptr| if an error occurs.
const analysis::Constant* GetNan(const analysis::Type* type,
@@ -1730,7 +1677,7 @@ BinaryScalarFoldingRule FoldBinaryIntegerOperation(uint64_t (*op)(uint64_t,
uint64_t result = op(ia, ib);
const analysis::Constant* result_constant =
GenerateIntegerConstant(integer_type, result, const_mgr);
const_mgr->GenerateIntegerConstant(integer_type, result);
return result_constant;
};
}
@@ -1745,7 +1692,7 @@ const analysis::Constant* FoldScalarSConvert(
const analysis::Integer* integer_type = result_type->AsInteger();
assert(integer_type && "The result type of an SConvert");
int64_t value = a->GetSignExtendedValue();
return GenerateIntegerConstant(integer_type, value, const_mgr);
return const_mgr->GenerateIntegerConstant(integer_type, value);
}
// A scalar folding rule that folds OpUConvert.
@@ -1762,8 +1709,8 @@ const analysis::Constant* FoldScalarUConvert(
// If the operand was an unsigned value with less than 32-bit, it would have
// been sign extended earlier, and we need to clear those bits.
auto* operand_type = a->type()->AsInteger();
value = ZeroExtendValue(value, operand_type->width());
return GenerateIntegerConstant(integer_type, value, const_mgr);
value = utils::ClearHighBits(value, 64 - operand_type->width());
return const_mgr->GenerateIntegerConstant(integer_type, value);
}
} // namespace

View File

@@ -525,6 +525,28 @@ uint32_t ConstantManager::GetNullConstId(const Type* type) {
return GetDefiningInstruction(c)->result_id();
}
const Constant* ConstantManager::GenerateIntegerConstant(
const analysis::Integer* integer_type, uint64_t result) {
assert(integer_type != nullptr);
std::vector<uint32_t> words;
if (integer_type->width() == 64) {
// In the 64-bit case, two words are needed to represent the value.
words = {static_cast<uint32_t>(result),
static_cast<uint32_t>(result >> 32)};
} else {
// In all other cases, only a single word is needed.
assert(integer_type->width() <= 32);
if (integer_type->IsSigned()) {
result = utils::SignExtendValue(result, integer_type->width());
} else {
result = utils::ZeroExtendValue(result, integer_type->width());
}
words = {static_cast<uint32_t>(result)};
}
return GetConstant(integer_type, words);
}
std::vector<const analysis::Constant*> Constant::GetVectorComponents(
analysis::ConstantManager* const_mgr) const {
std::vector<const analysis::Constant*> components;

View File

@@ -671,6 +671,11 @@ class ConstantManager {
// Returns the id of a OpConstantNull with type of |type|.
uint32_t GetNullConstId(const Type* type);
// Returns a constant whose value is `value` and type is `type`. This constant
// will be generated by `const_mgr`. The type must be a scalar integer type.
const Constant* GenerateIntegerConstant(const analysis::Integer* integer_type,
uint64_t result);
private:
// Creates a Constant instance with the given type and a vector of constant
// defining words. Returns a unique pointer to the created Constant instance

View File

@@ -28,6 +28,9 @@ constexpr uint32_t kStoreObjectInOperand = 1;
constexpr uint32_t kCompositeExtractObjectInOperand = 0;
constexpr uint32_t kTypePointerStorageClassInIdx = 0;
constexpr uint32_t kTypePointerPointeeInIdx = 1;
constexpr uint32_t kExtInstSetInIdx = 0;
constexpr uint32_t kExtInstOpInIdx = 1;
constexpr uint32_t kInterpolantInIdx = 2;
bool IsDebugDeclareOrValue(Instruction* di) {
auto dbg_opcode = di->GetCommonDebugOpcode();
@@ -74,28 +77,38 @@ Pass::Status CopyPropagateArrays::Process() {
for (auto var_inst = entry_bb->begin();
var_inst->opcode() == spv::Op::OpVariable; ++var_inst) {
if (!IsPointerToArrayType(var_inst->type_id())) {
worklist_.push(&*var_inst);
}
}
while (!worklist_.empty()) {
Instruction* var_inst = worklist_.front();
worklist_.pop();
// Find the only store to the entire memory location, if it exists.
Instruction* store_inst = FindStoreInstruction(&*var_inst);
if (!store_inst) {
continue;
}
std::unique_ptr<MemoryObject> source_object =
FindSourceObjectIfPossible(&*var_inst, store_inst);
if (source_object != nullptr) {
if (!IsPointerToArrayType(var_inst->type_id()) &&
source_object->GetStorageClass() != spv::StorageClass::Input) {
continue;
}
// Find the only store to the entire memory location, if it exists.
Instruction* store_inst = FindStoreInstruction(&*var_inst);
if (CanUpdateUses(&*var_inst, source_object->GetPointerTypeId(this))) {
modified = true;
if (!store_inst) {
continue;
}
std::unique_ptr<MemoryObject> source_object =
FindSourceObjectIfPossible(&*var_inst, store_inst);
if (source_object != nullptr) {
if (CanUpdateUses(&*var_inst, source_object->GetPointerTypeId(this))) {
modified = true;
PropagateObject(&*var_inst, source_object.get(), store_inst);
}
PropagateObject(&*var_inst, source_object.get(), store_inst);
}
}
}
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
@@ -204,6 +217,8 @@ bool CopyPropagateArrays::HasNoStores(Instruction* ptr_inst) {
return true;
} else if (use->opcode() == spv::Op::OpEntryPoint) {
return true;
} else if (IsInterpolationInstruction(use)) {
return true;
}
// Some other instruction. Be conservative.
return false;
@@ -225,6 +240,13 @@ bool CopyPropagateArrays::HasValidReferencesOnly(Instruction* ptr_inst,
// time to do the multiple traverses can add up. Consider collecting
// those loads and doing a single traversal.
return dominator_analysis->Dominates(store_inst, use);
} else if (IsInterpolationInstruction(use)) {
// GLSL InterpolateAt* instructions work similarly to loads
uint32_t interpolant = use->GetSingleWordInOperand(kInterpolantInIdx);
if (interpolant !=
store_inst->GetSingleWordInOperand(kStorePointerInOperand))
return false;
return dominator_analysis->Dominates(store_inst, use);
} else if (use->opcode() == spv::Op::OpAccessChain) {
return HasValidReferencesOnly(use, store_inst);
} else if (use->IsDecoration() || use->opcode() == spv::Op::OpName) {
@@ -489,6 +511,21 @@ bool CopyPropagateArrays::IsPointerToArrayType(uint32_t type_id) {
return false;
}
bool CopyPropagateArrays::IsInterpolationInstruction(Instruction* inst) {
if (inst->opcode() == spv::Op::OpExtInst &&
inst->GetSingleWordInOperand(kExtInstSetInIdx) ==
context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450()) {
uint32_t ext_inst = inst->GetSingleWordInOperand(kExtInstOpInIdx);
switch (ext_inst) {
case GLSLstd450InterpolateAtCentroid:
case GLSLstd450InterpolateAtOffset:
case GLSLstd450InterpolateAtSample:
return true;
}
}
return false;
}
bool CopyPropagateArrays::CanUpdateUses(Instruction* original_ptr_inst,
uint32_t type_id) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -522,6 +559,11 @@ bool CopyPropagateArrays::CanUpdateUses(Instruction* original_ptr_inst,
}
return true;
}
case spv::Op::OpExtInst:
if (IsInterpolationInstruction(use)) {
return true;
}
return false;
case spv::Op::OpAccessChain: {
analysis::Pointer* pointer_type = type->AsPointer();
const analysis::Type* pointee_type = pointer_type->pointee_type();
@@ -670,6 +712,18 @@ void CopyPropagateArrays::UpdateUses(Instruction* original_ptr_inst,
} else {
context()->AnalyzeUses(use);
}
AddUsesToWorklist(use);
} break;
case spv::Op::OpExtInst: {
if (IsInterpolationInstruction(use)) {
// Replace the actual use.
context()->ForgetUses(use);
use->SetOperand(index, {new_ptr_inst->result_id()});
context()->AnalyzeUses(use);
} else {
assert(false && "Don't know how to rewrite instruction");
}
} break;
case spv::Op::OpAccessChain: {
// Update the actual use.
@@ -751,6 +805,8 @@ void CopyPropagateArrays::UpdateUses(Instruction* original_ptr_inst,
uint32_t pointee_type_id =
pointer_type->GetSingleWordInOperand(kTypePointerPointeeInIdx);
uint32_t copy = GenerateCopy(original_ptr_inst, pointee_type_id, use);
assert(copy != 0 &&
"Should not be updating uses unless we know it can be done.");
context()->ForgetUses(use);
use->SetInOperand(index, {copy});
@@ -797,6 +853,22 @@ uint32_t CopyPropagateArrays::GetMemberTypeId(
return id;
}
void CopyPropagateArrays::AddUsesToWorklist(Instruction* inst) {
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
def_use_mgr->ForEachUse(inst, [this](Instruction* use, uint32_t) {
if (use->opcode() == spv::Op::OpStore) {
uint32_t var_id;
Instruction* target_pointer = GetPtr(use, &var_id);
if (target_pointer->opcode() != spv::Op::OpVariable) {
return;
}
worklist_.push(target_pointer);
}
});
}
void CopyPropagateArrays::MemoryObject::PushIndirection(
const std::vector<AccessChainEntry>& access_chain) {
access_chain_.insert(access_chain_.end(), access_chain.begin(),

View File

@@ -222,6 +222,10 @@ class CopyPropagateArrays : public MemPass {
// Return true if |type_id| is a pointer type whose pointee type is an array.
bool IsPointerToArrayType(uint32_t type_id);
// Return true if |inst| is one of the InterpolateAt* GLSL.std.450 extended
// instructions.
bool IsInterpolationInstruction(Instruction* inst);
// Returns true if there are not stores using |ptr_inst| or something derived
// from it.
bool HasNoStores(Instruction* ptr_inst);
@@ -254,6 +258,14 @@ class CopyPropagateArrays : public MemPass {
// same way the indexes are used in an |OpCompositeExtract| instruction.
uint32_t GetMemberTypeId(uint32_t id,
const std::vector<uint32_t>& access_chain) const;
// If the result of inst is stored to a variable, add that variable to the
// worklist.
void AddUsesToWorklist(Instruction* inst);
// OpVariable worklist. An instruction is added to this list if we would like
// to run copy propagation on it.
std::queue<Instruction*> worklist_;
};
} // namespace opt

View File

@@ -768,15 +768,29 @@ void DebugInfoManager::ConvertDebugGlobalToLocalVariable(
local_var->opcode() == spv::Op::OpFunctionParameter);
// Convert |dbg_global_var| to DebugLocalVariable
// All of the operands up to the scope operand are the same for the type
// instructions. The flag operand needs to move from operand
// kDebugGlobalVariableOperandFlagsIndex to
// kDebugLocalVariableOperandFlagsIndex. No other operands are needed to
// define the DebugLocalVariable.
// Modify the opcode.
dbg_global_var->SetInOperand(kExtInstInstructionInIdx,
{CommonDebugInfoDebugLocalVariable});
// Move the flags operand.
auto flags = dbg_global_var->GetSingleWordOperand(
kDebugGlobalVariableOperandFlagsIndex);
for (uint32_t i = dbg_global_var->NumInOperands() - 1;
i >= kDebugLocalVariableOperandFlagsIndex; --i) {
dbg_global_var->SetOperand(kDebugLocalVariableOperandFlagsIndex, {flags});
// Remove the extra operands. Starting at the end to avoid copying too much
// data.
for (uint32_t i = dbg_global_var->NumOperands() - 1;
i > kDebugLocalVariableOperandFlagsIndex; --i) {
dbg_global_var->RemoveOperand(i);
}
dbg_global_var->SetOperand(kDebugLocalVariableOperandFlagsIndex, {flags});
// Update the def-use manager.
context()->ForgetUses(dbg_global_var);
context()->AnalyzeUses(dbg_global_var);

View File

@@ -31,11 +31,14 @@ bool IsDecorationBinding(Instruction* inst) {
Pass::Status DescriptorScalarReplacement::Process() {
bool modified = false;
std::vector<Instruction*> vars_to_kill;
for (Instruction& var : context()->types_values()) {
if (descsroautil::IsDescriptorArray(context(), &var)) {
bool is_candidate =
flatten_arrays_ && descsroautil::IsDescriptorArray(context(), &var);
is_candidate |= flatten_composites_ &&
descsroautil::IsDescriptorStruct(context(), &var);
if (is_candidate) {
modified = true;
if (!ReplaceCandidate(&var)) {
return Status::Failure;

View File

@@ -32,9 +32,16 @@ namespace opt {
// Documented in optimizer.hpp
class DescriptorScalarReplacement : public Pass {
public:
DescriptorScalarReplacement() {}
DescriptorScalarReplacement(bool flatten_composites, bool flatten_arrays)
: flatten_composites_(flatten_composites),
flatten_arrays_(flatten_arrays) {}
const char* name() const override { return "descriptor-scalar-replacement"; }
const char* name() const override {
if (flatten_composites_ && flatten_arrays_)
return "descriptor-scalar-replacement";
if (flatten_composites_) return "descriptor-compososite-scalar-replacement";
return "descriptor-array-scalar-replacement";
}
Status Process() override;
@@ -141,6 +148,9 @@ class DescriptorScalarReplacement : public Pass {
// array |var|. If the entry is |0|, then the variable has not been
// created yet.
std::map<Instruction*, std::vector<uint32_t>> replacement_variables_;
bool flatten_composites_;
bool flatten_arrays_;
};
} // namespace opt

View File

@@ -29,41 +29,58 @@ uint32_t GetLengthOfArrayType(IRContext* context, Instruction* type) {
return length_const->GetU32();
}
} // namespace
bool HasDescriptorDecorations(IRContext* context, Instruction* var) {
const auto& decoration_mgr = context->get_decoration_mgr();
return decoration_mgr->HasDecoration(
var->result_id(), uint32_t(spv::Decoration::DescriptorSet)) &&
decoration_mgr->HasDecoration(var->result_id(),
uint32_t(spv::Decoration::Binding));
}
namespace descsroautil {
bool IsDescriptorArray(IRContext* context, Instruction* var) {
Instruction* GetVariableType(IRContext* context, Instruction* var) {
if (var->opcode() != spv::Op::OpVariable) {
return false;
return nullptr;
}
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
if (ptr_type_inst->opcode() != spv::Op::OpTypePointer) {
return false;
return nullptr;
}
uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
Instruction* var_type_inst = context->get_def_use_mgr()->GetDef(var_type_id);
if (var_type_inst->opcode() != spv::Op::OpTypeArray &&
var_type_inst->opcode() != spv::Op::OpTypeStruct) {
return false;
return context->get_def_use_mgr()->GetDef(var_type_id);
}
} // namespace
namespace descsroautil {
bool IsDescriptorArray(IRContext* context, Instruction* var) {
Instruction* var_type_inst = GetVariableType(context, var);
if (var_type_inst == nullptr) return false;
return var_type_inst->opcode() == spv::Op::OpTypeArray &&
HasDescriptorDecorations(context, var);
}
bool IsDescriptorStruct(IRContext* context, Instruction* var) {
Instruction* var_type_inst = GetVariableType(context, var);
if (var_type_inst == nullptr) return false;
while (var_type_inst->opcode() == spv::Op::OpTypeArray) {
var_type_inst = context->get_def_use_mgr()->GetDef(
var_type_inst->GetInOperand(0).AsId());
}
if (var_type_inst->opcode() != spv::Op::OpTypeStruct) return false;
// All structures with descriptor assignments must be replaced by variables,
// one for each of their members - with the exceptions of buffers.
if (IsTypeOfStructuredBuffer(context, var_type_inst)) {
return false;
}
if (!context->get_decoration_mgr()->HasDecoration(
var->result_id(), uint32_t(spv::Decoration::DescriptorSet))) {
return false;
}
return context->get_decoration_mgr()->HasDecoration(
var->result_id(), uint32_t(spv::Decoration::Binding));
return HasDescriptorDecorations(context, var);
}
bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type) {

View File

@@ -27,6 +27,10 @@ namespace descsroautil {
// descriptor array.
bool IsDescriptorArray(IRContext* context, Instruction* var);
// Returns true if |var| is an OpVariable instruction that represents a
// struct containing descriptors.
bool IsDescriptorStruct(IRContext* context, Instruction* var);
// Returns true if |type| is a type that could be used for a structured buffer
// as opposed to a type that would be used for a structure of resource
// descriptors.

View File

@@ -141,22 +141,26 @@ bool FixStorageClass::IsPointerResultType(Instruction* inst) {
if (inst->type_id() == 0) {
return false;
}
const analysis::Type* ret_type =
context()->get_type_mgr()->GetType(inst->type_id());
return ret_type->AsPointer() != nullptr;
Instruction* type_def = get_def_use_mgr()->GetDef(inst->type_id());
return type_def->opcode() == spv::Op::OpTypePointer;
}
bool FixStorageClass::IsPointerToStorageClass(Instruction* inst,
spv::StorageClass storage_class) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Type* pType = type_mgr->GetType(inst->type_id());
const analysis::Pointer* result_type = pType->AsPointer();
if (result_type == nullptr) {
if (inst->type_id() == 0) {
return false;
}
return (result_type->storage_class() == storage_class);
Instruction* type_def = get_def_use_mgr()->GetDef(inst->type_id());
if (type_def->opcode() != spv::Op::OpTypePointer) {
return false;
}
const uint32_t kPointerTypeStorageClassIndex = 0;
spv::StorageClass pointer_storage_class = static_cast<spv::StorageClass>(
type_def->GetSingleWordInOperand(kPointerTypeStorageClassIndex));
return pointer_storage_class == storage_class;
}
bool FixStorageClass::ChangeResultType(Instruction* inst,
@@ -233,6 +237,9 @@ bool FixStorageClass::PropagateType(Instruction* inst, uint32_t type_id,
}
uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst);
if (copy_id == 0) {
return false;
}
inst->SetInOperand(1, {copy_id});
context()->UpdateDefUse(inst);
}
@@ -301,9 +308,11 @@ uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
break;
}
Instruction* orig_type_inst = get_def_use_mgr()->GetDef(id);
assert(orig_type_inst->opcode() == spv::Op::OpTypePointer);
id = orig_type_inst->GetSingleWordInOperand(1);
Instruction* id_type_inst = get_def_use_mgr()->GetDef(id);
assert(id_type_inst->opcode() == spv::Op::OpTypePointer);
id = id_type_inst->GetSingleWordInOperand(1);
spv::StorageClass input_storage_class =
static_cast<spv::StorageClass>(id_type_inst->GetSingleWordInOperand(0));
for (uint32_t i = start_idx; i < inst->NumInOperands(); ++i) {
Instruction* type_inst = get_def_use_mgr()->GetDef(id);
@@ -312,6 +321,7 @@ uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
case spv::Op::OpTypeRuntimeArray:
case spv::Op::OpTypeMatrix:
case spv::Op::OpTypeVector:
case spv::Op::OpTypeCooperativeMatrixKHR:
id = type_inst->GetSingleWordInOperand(0);
break;
case spv::Op::OpTypeStruct: {
@@ -335,9 +345,19 @@ uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
"Tried to extract from an object where it cannot be done.");
}
return context()->get_type_mgr()->FindPointerToType(
id, static_cast<spv::StorageClass>(
orig_type_inst->GetSingleWordInOperand(0)));
Instruction* orig_type_inst = get_def_use_mgr()->GetDef(inst->type_id());
spv::StorageClass orig_storage_class =
static_cast<spv::StorageClass>(orig_type_inst->GetSingleWordInOperand(0));
assert(orig_type_inst->opcode() == spv::Op::OpTypePointer);
if (orig_type_inst->GetSingleWordInOperand(1) == id &&
input_storage_class == orig_storage_class) {
// The existing type is correct. Avoid the search for the type. Note that if
// there is a duplicate type, the search below could return a different type
// forcing more changes to the code than necessary.
return inst->type_id();
}
return context()->get_type_mgr()->FindPointerToType(id, input_storage_class);
}
// namespace opt

View File

@@ -247,18 +247,7 @@ utils::SmallVector<uint32_t, 2> EncodeIntegerAsWords(const analysis::Type& type,
// Truncate first_word if the |type| has width less than uint32.
if (bit_width < bits_per_word) {
const uint32_t num_high_bits_to_mask = bits_per_word - bit_width;
const bool is_negative_after_truncation =
result_type_signed &&
utils::IsBitAtPositionSet(first_word, bit_width - 1);
if (is_negative_after_truncation) {
// Truncate and sign-extend |first_word|. No padding words will be
// added and |pad_value| can be left as-is.
first_word = utils::SetHighBits(first_word, num_high_bits_to_mask);
} else {
first_word = utils::ClearHighBits(first_word, num_high_bits_to_mask);
}
first_word = utils::SignExtendValue(first_word, bit_width);
}
utils::SmallVector<uint32_t, 2> words = {first_word};

View File

@@ -112,6 +112,12 @@ bool IsValidResult(T val) {
}
}
// Returns true if `type` is a cooperative matrix.
bool IsCooperativeMatrix(const analysis::Type* type) {
return type->kind() == analysis::Type::kCooperativeMatrixKHR ||
type->kind() == analysis::Type::kCooperativeMatrixNV;
}
const analysis::Constant* ConstInput(
const std::vector<const analysis::Constant*>& constants) {
return constants[0] ? constants[0] : constants[1];
@@ -180,8 +186,14 @@ std::vector<uint32_t> GetWordsFromNumericScalarOrVectorConstant(
const analysis::Constant* ConvertWordsToNumericScalarOrVectorConstant(
analysis::ConstantManager* const_mgr, const std::vector<uint32_t>& words,
const analysis::Type* type) {
if (type->AsInteger() || type->AsFloat())
return const_mgr->GetConstant(type, words);
const spvtools::opt::analysis::Integer* int_type = type->AsInteger();
if (int_type && int_type->width() <= 32) {
assert(words.size() == 1);
return const_mgr->GenerateIntegerConstant(int_type, words[0]);
}
if (int_type || type->AsFloat()) return const_mgr->GetConstant(type, words);
if (const auto* vec_type = type->AsVector())
return const_mgr->GetNumericVectorConstantWithWords(vec_type, words);
return nullptr;
@@ -307,6 +319,11 @@ FoldingRule ReciprocalFDiv() {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
if (!inst->IsFloatingPointFoldingAllowed()) return false;
uint32_t width = ElementWidth(type);
@@ -388,6 +405,11 @@ FoldingRule MergeNegateMulDivArithmetic() {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
return false;
@@ -400,36 +422,37 @@ FoldingRule MergeNegateMulDivArithmetic() {
if (width != 32 && width != 64) return false;
spv::Op opcode = op_inst->opcode();
if (opcode == spv::Op::OpFMul || opcode == spv::Op::OpFDiv ||
opcode == spv::Op::OpIMul || opcode == spv::Op::OpSDiv ||
opcode == spv::Op::OpUDiv) {
std::vector<const analysis::Constant*> op_constants =
const_mgr->GetOperandConstants(op_inst);
// Merge negate into mul or div if one operand is constant.
if (op_constants[0] || op_constants[1]) {
bool zero_is_variable = op_constants[0] == nullptr;
const analysis::Constant* c = ConstInput(op_constants);
uint32_t neg_id = NegateConstant(const_mgr, c);
uint32_t non_const_id = zero_is_variable
? op_inst->GetSingleWordInOperand(0u)
: op_inst->GetSingleWordInOperand(1u);
// Change this instruction to a mul/div.
inst->SetOpcode(op_inst->opcode());
if (opcode == spv::Op::OpFDiv || opcode == spv::Op::OpUDiv ||
opcode == spv::Op::OpSDiv) {
uint32_t op0 = zero_is_variable ? non_const_id : neg_id;
uint32_t op1 = zero_is_variable ? neg_id : non_const_id;
inst->SetInOperands(
{{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}});
} else {
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}},
{SPV_OPERAND_TYPE_ID, {neg_id}}});
}
return true;
}
if (opcode != spv::Op::OpFMul && opcode != spv::Op::OpFDiv &&
opcode != spv::Op::OpIMul && opcode != spv::Op::OpSDiv) {
return false;
}
return false;
std::vector<const analysis::Constant*> op_constants =
const_mgr->GetOperandConstants(op_inst);
// Merge negate into mul or div if one operand is constant.
if (op_constants[0] == nullptr && op_constants[1] == nullptr) {
return false;
}
bool zero_is_variable = op_constants[0] == nullptr;
const analysis::Constant* c = ConstInput(op_constants);
uint32_t neg_id = NegateConstant(const_mgr, c);
uint32_t non_const_id = zero_is_variable
? op_inst->GetSingleWordInOperand(0u)
: op_inst->GetSingleWordInOperand(1u);
// Change this instruction to a mul/div.
inst->SetOpcode(op_inst->opcode());
if (opcode == spv::Op::OpFDiv || opcode == spv::Op::OpUDiv ||
opcode == spv::Op::OpSDiv) {
uint32_t op0 = zero_is_variable ? non_const_id : neg_id;
uint32_t op1 = zero_is_variable ? neg_id : non_const_id;
inst->SetInOperands(
{{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}});
} else {
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}},
{SPV_OPERAND_TYPE_ID, {neg_id}}});
}
return true;
};
}
@@ -449,6 +472,11 @@ FoldingRule MergeNegateAddSubArithmetic() {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
return false;
@@ -680,6 +708,11 @@ FoldingRule MergeMulMulArithmetic() {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
return false;
@@ -734,6 +767,11 @@ FoldingRule MergeMulDivArithmetic() {
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
if (!inst->IsFloatingPointFoldingAllowed()) return false;
uint32_t width = ElementWidth(type);
@@ -807,6 +845,11 @@ FoldingRule MergeMulNegateArithmetic() {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
@@ -847,6 +890,11 @@ FoldingRule MergeDivDivArithmetic() {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
if (!inst->IsFloatingPointFoldingAllowed()) return false;
uint32_t width = ElementWidth(type);
@@ -920,6 +968,11 @@ FoldingRule MergeDivMulArithmetic() {
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
if (!inst->IsFloatingPointFoldingAllowed()) return false;
uint32_t width = ElementWidth(type);
@@ -1062,6 +1115,11 @@ FoldingRule MergeSubNegateArithmetic() {
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
@@ -1110,6 +1168,11 @@ FoldingRule MergeAddAddArithmetic() {
inst->opcode() == spv::Op::OpIAdd);
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
@@ -1158,6 +1221,11 @@ FoldingRule MergeAddSubArithmetic() {
inst->opcode() == spv::Op::OpIAdd);
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
@@ -1218,6 +1286,11 @@ FoldingRule MergeSubAddArithmetic() {
inst->opcode() == spv::Op::OpISub);
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
@@ -1284,6 +1357,11 @@ FoldingRule MergeSubSubArithmetic() {
inst->opcode() == spv::Op::OpISub);
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
@@ -1377,6 +1455,11 @@ FoldingRule MergeGenericAddSubArithmetic() {
inst->opcode() == spv::Op::OpIAdd);
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
if (IsCooperativeMatrix(type)) {
return false;
}
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;

View File

@@ -1,483 +0,0 @@
// Copyright (c) 2020 The Khronos Group Inc.
// Copyright (c) 2020 Valve Corporation
// Copyright (c) 2020 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "inst_debug_printf_pass.h"
#include "source/spirv_constant.h"
#include "source/util/string_utils.h"
#include "spirv/unified1/NonSemanticDebugPrintf.h"
namespace spvtools {
namespace opt {
void InstDebugPrintfPass::GenOutputValues(Instruction* val_inst,
std::vector<uint32_t>* val_ids,
InstructionBuilder* builder) {
uint32_t val_ty_id = val_inst->type_id();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Type* val_ty = type_mgr->GetType(val_ty_id);
switch (val_ty->kind()) {
case analysis::Type::kVector: {
analysis::Vector* v_ty = val_ty->AsVector();
const analysis::Type* c_ty = v_ty->element_type();
uint32_t c_ty_id = type_mgr->GetId(c_ty);
for (uint32_t c = 0; c < v_ty->element_count(); ++c) {
Instruction* c_inst =
builder->AddCompositeExtract(c_ty_id, val_inst->result_id(), {c});
GenOutputValues(c_inst, val_ids, builder);
}
return;
}
case analysis::Type::kBool: {
// Select between uint32 zero or one
uint32_t zero_id = builder->GetUintConstantId(0);
uint32_t one_id = builder->GetUintConstantId(1);
Instruction* sel_inst = builder->AddSelect(
GetUintId(), val_inst->result_id(), one_id, zero_id);
val_ids->push_back(sel_inst->result_id());
return;
}
case analysis::Type::kFloat: {
analysis::Float* f_ty = val_ty->AsFloat();
switch (f_ty->width()) {
case 16: {
// Convert float16 to float32 and recurse
Instruction* f32_inst = builder->AddUnaryOp(
GetFloatId(), spv::Op::OpFConvert, val_inst->result_id());
GenOutputValues(f32_inst, val_ids, builder);
return;
}
case 64: {
// Bitcast float64 to uint64 and recurse
Instruction* ui64_inst = builder->AddUnaryOp(
GetUint64Id(), spv::Op::OpBitcast, val_inst->result_id());
GenOutputValues(ui64_inst, val_ids, builder);
return;
}
case 32: {
// Bitcase float32 to uint32
Instruction* bc_inst = builder->AddUnaryOp(
GetUintId(), spv::Op::OpBitcast, val_inst->result_id());
val_ids->push_back(bc_inst->result_id());
return;
}
default:
assert(false && "unsupported float width");
return;
}
}
case analysis::Type::kInteger: {
analysis::Integer* i_ty = val_ty->AsInteger();
switch (i_ty->width()) {
case 64: {
Instruction* ui64_inst = val_inst;
if (i_ty->IsSigned()) {
// Bitcast sint64 to uint64
ui64_inst = builder->AddUnaryOp(GetUint64Id(), spv::Op::OpBitcast,
val_inst->result_id());
}
// Break uint64 into 2x uint32
Instruction* lo_ui64_inst = builder->AddUnaryOp(
GetUintId(), spv::Op::OpUConvert, ui64_inst->result_id());
Instruction* rshift_ui64_inst = builder->AddBinaryOp(
GetUint64Id(), spv::Op::OpShiftRightLogical,
ui64_inst->result_id(), builder->GetUintConstantId(32));
Instruction* hi_ui64_inst = builder->AddUnaryOp(
GetUintId(), spv::Op::OpUConvert, rshift_ui64_inst->result_id());
val_ids->push_back(lo_ui64_inst->result_id());
val_ids->push_back(hi_ui64_inst->result_id());
return;
}
case 8: {
Instruction* ui8_inst = val_inst;
if (i_ty->IsSigned()) {
// Bitcast sint8 to uint8
ui8_inst = builder->AddUnaryOp(GetUint8Id(), spv::Op::OpBitcast,
val_inst->result_id());
}
// Convert uint8 to uint32
Instruction* ui32_inst = builder->AddUnaryOp(
GetUintId(), spv::Op::OpUConvert, ui8_inst->result_id());
val_ids->push_back(ui32_inst->result_id());
return;
}
case 32: {
Instruction* ui32_inst = val_inst;
if (i_ty->IsSigned()) {
// Bitcast sint32 to uint32
ui32_inst = builder->AddUnaryOp(GetUintId(), spv::Op::OpBitcast,
val_inst->result_id());
}
// uint32 needs no further processing
val_ids->push_back(ui32_inst->result_id());
return;
}
default:
// TODO(greg-lunarg): Support non-32-bit int
assert(false && "unsupported int width");
return;
}
}
default:
assert(false && "unsupported type");
return;
}
}
void InstDebugPrintfPass::GenOutputCode(
Instruction* printf_inst,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
BasicBlock* back_blk_ptr = &*new_blocks->back();
InstructionBuilder builder(
context(), back_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// Gen debug printf record validation-specific values. The format string
// will have its id written. Vectors will need to be broken down into
// component values. float16 will need to be converted to float32. Pointer
// and uint64 will need to be converted to two uint32 values. float32 will
// need to be bitcast to uint32. int32 will need to be bitcast to uint32.
std::vector<uint32_t> val_ids;
bool is_first_operand = false;
printf_inst->ForEachInId(
[&is_first_operand, &val_ids, &builder, this](const uint32_t* iid) {
// skip set operand
if (!is_first_operand) {
is_first_operand = true;
return;
}
Instruction* opnd_inst = get_def_use_mgr()->GetDef(*iid);
if (opnd_inst->opcode() == spv::Op::OpString) {
uint32_t string_id_id = builder.GetUintConstantId(*iid);
val_ids.push_back(string_id_id);
} else {
GenOutputValues(opnd_inst, &val_ids, &builder);
}
});
GenDebugStreamWrite(
builder.GetUintConstantId(shader_id_),
builder.GetUintConstantId(uid2offset_[printf_inst->unique_id()]), val_ids,
&builder);
context()->KillInst(printf_inst);
}
void InstDebugPrintfPass::GenDebugPrintfCode(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
// If not DebugPrintf OpExtInst, return.
Instruction* printf_inst = &*ref_inst_itr;
if (printf_inst->opcode() != spv::Op::OpExtInst) return;
if (printf_inst->GetSingleWordInOperand(0) != ext_inst_printf_id_) return;
if (printf_inst->GetSingleWordInOperand(1) !=
NonSemanticDebugPrintfDebugPrintf)
return;
// Initialize DefUse manager before dismantling module
(void)get_def_use_mgr();
// Move original block's preceding instructions into first new block
std::unique_ptr<BasicBlock> new_blk_ptr;
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
new_blocks->push_back(std::move(new_blk_ptr));
// Generate instructions to output printf args to printf buffer
GenOutputCode(printf_inst, new_blocks);
// Caller expects at least two blocks with last block containing remaining
// code, so end block after instrumentation, create remainder block, and
// branch to it
uint32_t rem_blk_id = TakeNextId();
std::unique_ptr<Instruction> rem_label(NewLabel(rem_blk_id));
BasicBlock* back_blk_ptr = &*new_blocks->back();
InstructionBuilder builder(
context(), back_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
(void)builder.AddBranch(rem_blk_id);
// Gen remainder block
new_blk_ptr.reset(new BasicBlock(std::move(rem_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// Move original block's remaining code into remainder block and add
// to new blocks
MovePostludeCode(ref_block_itr, &*new_blk_ptr);
new_blocks->push_back(std::move(new_blk_ptr));
}
// Return id for output buffer
uint32_t InstDebugPrintfPass::GetOutputBufferId() {
if (output_buffer_id_ == 0) {
// If not created yet, create one
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::RuntimeArray* reg_uint_rarr_ty = GetUintRuntimeArrayType(32);
analysis::Integer* reg_uint_ty = GetInteger(32, false);
analysis::Type* reg_buf_ty =
GetStruct({reg_uint_ty, reg_uint_ty, reg_uint_rarr_ty});
uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
// By the Vulkan spec, a pre-existing struct containing a RuntimeArray
// must be a block, and will therefore be decorated with Block. Therefore
// the undecorated type returned here will not be pre-existing and can
// safely be decorated. Since this type is now decorated, it is out of
// sync with the TypeManager and therefore the TypeManager must be
// invalidated after this pass.
assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
"used struct type returned");
deco_mgr->AddDecoration(obufTyId, uint32_t(spv::Decoration::Block));
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputFlagsOffset,
uint32_t(spv::Decoration::Offset), 0);
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
uint32_t(spv::Decoration::Offset), 4);
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputDataOffset,
uint32_t(spv::Decoration::Offset), 8);
uint32_t obufTyPtrId_ =
type_mgr->FindPointerToType(obufTyId, spv::StorageClass::StorageBuffer);
output_buffer_id_ = TakeNextId();
std::unique_ptr<Instruction> newVarOp(new Instruction(
context(), spv::Op::OpVariable, obufTyPtrId_, output_buffer_id_,
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{uint32_t(spv::StorageClass::StorageBuffer)}}}));
context()->AddGlobalValue(std::move(newVarOp));
context()->AddDebug2Inst(NewGlobalName(obufTyId, "OutputBuffer"));
context()->AddDebug2Inst(NewMemberName(obufTyId, 0, "flags"));
context()->AddDebug2Inst(NewMemberName(obufTyId, 1, "written_count"));
context()->AddDebug2Inst(NewMemberName(obufTyId, 2, "data"));
context()->AddDebug2Inst(NewGlobalName(output_buffer_id_, "output_buffer"));
deco_mgr->AddDecorationVal(
output_buffer_id_, uint32_t(spv::Decoration::DescriptorSet), desc_set_);
deco_mgr->AddDecorationVal(output_buffer_id_,
uint32_t(spv::Decoration::Binding),
GetOutputBufferBinding());
AddStorageBufferExt();
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// Add the new buffer to all entry points.
for (auto& entry : get_module()->entry_points()) {
entry.AddOperand({SPV_OPERAND_TYPE_ID, {output_buffer_id_}});
context()->AnalyzeUses(&entry);
}
}
}
return output_buffer_id_;
}
uint32_t InstDebugPrintfPass::GetOutputBufferPtrId() {
if (output_buffer_ptr_id_ == 0) {
output_buffer_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
GetUintId(), spv::StorageClass::StorageBuffer);
}
return output_buffer_ptr_id_;
}
uint32_t InstDebugPrintfPass::GetOutputBufferBinding() {
return kDebugOutputPrintfStream;
}
void InstDebugPrintfPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
uint32_t field_offset,
uint32_t field_value_id,
InstructionBuilder* builder) {
// Cast value to 32-bit unsigned if necessary
uint32_t val_id = GenUintCastCode(field_value_id, builder);
// Store value
Instruction* data_idx_inst = builder->AddIAdd(
GetUintId(), base_offset_id, builder->GetUintConstantId(field_offset));
uint32_t buf_id = GetOutputBufferId();
uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
Instruction* achain_inst = builder->AddAccessChain(
buf_uint_ptr_id, buf_id,
{builder->GetUintConstantId(kDebugOutputDataOffset),
data_idx_inst->result_id()});
(void)builder->AddStore(achain_inst->result_id(), val_id);
}
uint32_t InstDebugPrintfPass::GetStreamWriteFunctionId(uint32_t param_cnt) {
enum {
kShaderId = 0,
kInstructionIndex = 1,
kFirstParam = 2,
};
// Total param count is common params plus validation-specific
// params
if (param2output_func_id_[param_cnt] == 0) {
// Create function
param2output_func_id_[param_cnt] = TakeNextId();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
const analysis::Type* uint_type = GetInteger(32, false);
std::vector<const analysis::Type*> param_types(kFirstParam + param_cnt,
uint_type);
std::unique_ptr<Function> output_func = StartFunction(
param2output_func_id_[param_cnt], type_mgr->GetVoidType(), param_types);
std::vector<uint32_t> param_ids = AddParameters(*output_func, param_types);
// Create first block
auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
InstructionBuilder builder(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// Gen test if debug output buffer size will not be exceeded.
const uint32_t first_param_offset = kInstCommonOutInstructionIdx + 1;
const uint32_t obuf_record_sz = first_param_offset + param_cnt;
const uint32_t buf_id = GetOutputBufferId();
const uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
Instruction* obuf_curr_sz_ac_inst = builder.AddAccessChain(
buf_uint_ptr_id, buf_id,
{builder.GetUintConstantId(kDebugOutputSizeOffset)});
// Fetch the current debug buffer written size atomically, adding the
// size of the record to be written.
uint32_t obuf_record_sz_id = builder.GetUintConstantId(obuf_record_sz);
uint32_t mask_none_id =
builder.GetUintConstantId(uint32_t(spv::MemoryAccessMask::MaskNone));
uint32_t scope_invok_id =
builder.GetUintConstantId(uint32_t(spv::Scope::Invocation));
Instruction* obuf_curr_sz_inst = builder.AddQuadOp(
GetUintId(), spv::Op::OpAtomicIAdd, obuf_curr_sz_ac_inst->result_id(),
scope_invok_id, mask_none_id, obuf_record_sz_id);
uint32_t obuf_curr_sz_id = obuf_curr_sz_inst->result_id();
// Compute new written size
Instruction* obuf_new_sz_inst =
builder.AddIAdd(GetUintId(), obuf_curr_sz_id,
builder.GetUintConstantId(obuf_record_sz));
// Fetch the data bound
Instruction* obuf_bnd_inst =
builder.AddIdLiteralOp(GetUintId(), spv::Op::OpArrayLength,
GetOutputBufferId(), kDebugOutputDataOffset);
// Test that new written size is less than or equal to debug output
// data bound
Instruction* obuf_safe_inst = builder.AddBinaryOp(
GetBoolId(), spv::Op::OpULessThanEqual, obuf_new_sz_inst->result_id(),
obuf_bnd_inst->result_id());
uint32_t merge_blk_id = TakeNextId();
uint32_t write_blk_id = TakeNextId();
std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
std::unique_ptr<Instruction> write_label(NewLabel(write_blk_id));
(void)builder.AddConditionalBranch(
obuf_safe_inst->result_id(), write_blk_id, merge_blk_id, merge_blk_id,
uint32_t(spv::SelectionControlMask::MaskNone));
// Close safety test block and gen write block
output_func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
builder.SetInsertPoint(&*new_blk_ptr);
// Generate common and stage-specific debug record members
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutSize,
builder.GetUintConstantId(obuf_record_sz),
&builder);
// Store Shader Id
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutShaderId,
param_ids[kShaderId], &builder);
// Store Instruction Idx
GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutInstructionIdx,
param_ids[kInstructionIndex], &builder);
// Gen writes of validation specific data
for (uint32_t i = 0; i < param_cnt; ++i) {
GenDebugOutputFieldCode(obuf_curr_sz_id, first_param_offset + i,
param_ids[kFirstParam + i], &builder);
}
// Close write block and gen merge block
(void)builder.AddBranch(merge_blk_id);
output_func->AddBasicBlock(std::move(new_blk_ptr));
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
builder.SetInsertPoint(&*new_blk_ptr);
// Close merge block and function and add function to module
(void)builder.AddNullaryOp(0, spv::Op::OpReturn);
output_func->AddBasicBlock(std::move(new_blk_ptr));
output_func->SetFunctionEnd(EndFunction());
context()->AddFunction(std::move(output_func));
std::string name("stream_write_");
name += std::to_string(param_cnt);
context()->AddDebug2Inst(
NewGlobalName(param2output_func_id_[param_cnt], name));
}
return param2output_func_id_[param_cnt];
}
void InstDebugPrintfPass::GenDebugStreamWrite(
uint32_t shader_id, uint32_t instruction_idx_id,
const std::vector<uint32_t>& validation_ids, InstructionBuilder* builder) {
// Call debug output function. Pass func_idx, instruction_idx and
// validation ids as args.
uint32_t val_id_cnt = static_cast<uint32_t>(validation_ids.size());
std::vector<uint32_t> args = {shader_id, instruction_idx_id};
(void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
(void)builder->AddFunctionCall(GetVoidId(),
GetStreamWriteFunctionId(val_id_cnt), args);
}
std::unique_ptr<Instruction> InstDebugPrintfPass::NewGlobalName(
uint32_t id, const std::string& name_str) {
std::string prefixed_name{"inst_printf_"};
prefixed_name += name_str;
return NewName(id, prefixed_name);
}
std::unique_ptr<Instruction> InstDebugPrintfPass::NewMemberName(
uint32_t id, uint32_t member_index, const std::string& name_str) {
return MakeUnique<Instruction>(
context(), spv::Op::OpMemberName, 0, 0,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_ID, {id}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index}},
{SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
}
void InstDebugPrintfPass::InitializeInstDebugPrintf() {
// Initialize base class
InitializeInstrument();
output_buffer_id_ = 0;
output_buffer_ptr_id_ = 0;
}
Pass::Status InstDebugPrintfPass::ProcessImpl() {
// Perform printf instrumentation on each entry point function in module
InstProcessFunction pfn =
[this](BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr,
[[maybe_unused]] uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
return GenDebugPrintfCode(ref_inst_itr, ref_block_itr, new_blocks);
};
(void)InstProcessEntryPointCallTree(pfn);
// Remove DebugPrintf OpExtInstImport instruction
Instruction* ext_inst_import_inst =
get_def_use_mgr()->GetDef(ext_inst_printf_id_);
context()->KillInst(ext_inst_import_inst);
// If no remaining non-semantic instruction sets, remove non-semantic debug
// info extension from module and feature manager
bool non_sem_set_seen = false;
for (auto c_itr = context()->module()->ext_inst_import_begin();
c_itr != context()->module()->ext_inst_import_end(); ++c_itr) {
const std::string set_name = c_itr->GetInOperand(0).AsString();
if (spvtools::utils::starts_with(set_name, "NonSemantic.")) {
non_sem_set_seen = true;
break;
}
}
if (!non_sem_set_seen) {
context()->RemoveExtension(kSPV_KHR_non_semantic_info);
}
return Status::SuccessWithChange;
}
Pass::Status InstDebugPrintfPass::Process() {
ext_inst_printf_id_ =
get_module()->GetExtInstImportId("NonSemantic.DebugPrintf");
if (ext_inst_printf_id_ == 0) return Status::SuccessWithoutChange;
InitializeInstDebugPrintf();
return ProcessImpl();
}
} // namespace opt
} // namespace spvtools

View File

@@ -1,198 +0,0 @@
// Copyright (c) 2020 The Khronos Group Inc.
// Copyright (c) 2020 Valve Corporation
// Copyright (c) 2020 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
#define LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_
#include "instrument_pass.h"
namespace spvtools {
namespace opt {
// This class/pass is designed to support the debug printf GPU-assisted layer
// of https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and
// external design may change as the layer evolves.
class InstDebugPrintfPass : public InstrumentPass {
public:
// For test harness only
InstDebugPrintfPass() : InstrumentPass(7, 23, false, false) {}
// For all other interfaces
InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id)
: InstrumentPass(desc_set, shader_id, false, false) {}
~InstDebugPrintfPass() override = default;
// See optimizer.hpp for pass user documentation.
Status Process() override;
const char* name() const override { return "inst-printf-pass"; }
private:
// Gen code into |builder| to write |field_value_id| into debug output
// buffer at |base_offset_id| + |field_offset|.
void GenDebugOutputFieldCode(uint32_t base_offset_id, uint32_t field_offset,
uint32_t field_value_id,
InstructionBuilder* builder);
// Generate instructions in |builder| which will atomically fetch and
// increment the size of the debug output buffer stream of the current
// validation and write a record to the end of the stream, if enough space
// in the buffer remains. The record will contain the index of the function
// and instruction within that function |func_idx, instruction_idx| which
// generated the record. Finally, the record will contain validation-specific
// data contained in |validation_ids| which will identify the validation
// error as well as the values involved in the error.
//
// The output buffer binding written to by the code generated by the function
// is determined by the validation id specified when each specific
// instrumentation pass is created.
//
// The output buffer is a sequence of 32-bit values with the following
// format (where all elements are unsigned 32-bit unless otherwise noted):
//
// Size
// Record0
// Record1
// Record2
// ...
//
// Size is the number of 32-bit values that have been written or
// attempted to be written to the output buffer, excluding the Size. It is
// initialized to 0. If the size of attempts to write the buffer exceeds
// the actual size of the buffer, it is possible that this field can exceed
// the actual size of the buffer.
//
// Each Record* is a variable-length sequence of 32-bit values with the
// following format defined using static const offsets in the .cpp file:
//
// Record Size
// Shader ID
// Instruction Index
// ...
// Validation Error Code
// Validation-specific Word 0
// Validation-specific Word 1
// Validation-specific Word 2
// ...
//
// Each record consists of two subsections: members common across all
// validation and members specific to a
// validation.
//
// The Record Size is the number of 32-bit words in the record, including
// the Record Size word.
//
// Shader ID is a value that identifies which shader has generated the
// validation error. It is passed when the instrumentation pass is created.
//
// The Instruction Index is the position of the instruction within the
// SPIR-V file which is in error.
//
// The Validation Error Code specifies the exact error which has occurred.
// These are enumerated with the kInstError* static consts. This allows
// multiple validation layers to use the same, single output buffer.
//
// The Validation-specific Words are a validation-specific number of 32-bit
// words which give further information on the validation error that
// occurred. These are documented further in each file containing the
// validation-specific class which derives from this base class.
//
// Because the code that is generated checks against the size of the buffer
// before writing, the size of the debug out buffer can be used by the
// validation layer to control the number of error records that are written.
void GenDebugStreamWrite(uint32_t shader_id, uint32_t instruction_idx_id,
const std::vector<uint32_t>& validation_ids,
InstructionBuilder* builder);
// Return id for output function. Define if it doesn't exist with
// |val_spec_param_cnt| validation-specific uint32 parameters.
uint32_t GetStreamWriteFunctionId(uint32_t val_spec_param_cnt);
// Generate instructions for OpDebugPrintf.
//
// If |ref_inst_itr| is an OpDebugPrintf, return in |new_blocks| the result
// of replacing it with buffer write instructions within its block at
// |ref_block_itr|. The instructions write a record to the printf
// output buffer stream including |function_idx, instruction_idx|
// and removes the OpDebugPrintf. The block at |ref_block_itr| can just be
// replaced with the block in |new_blocks|. Besides the buffer writes, this
// block will comprise all instructions preceding and following
// |ref_inst_itr|.
//
// This function is designed to be passed to
// InstrumentPass::InstProcessEntryPointCallTree(), which applies the
// function to each instruction in a module and replaces the instruction
// if warranted.
//
// This instrumentation function utilizes GenDebugStreamWrite() to write its
// error records. The validation-specific part of the error record will
// consist of a uint32 which is the id of the format string plus a sequence
// of uint32s representing the values of the remaining operands of the
// DebugPrintf.
void GenDebugPrintfCode(BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Generate a sequence of uint32 instructions in |builder| (if necessary)
// representing the value of |val_inst|, which must be a buffer pointer, a
// uint64, or a scalar or vector of type uint32, float32 or float16. Append
// the ids of all values to the end of |val_ids|.
void GenOutputValues(Instruction* val_inst, std::vector<uint32_t>* val_ids,
InstructionBuilder* builder);
// Generate instructions to write a record containing the operands of
// |printf_inst| arguments to printf buffer, adding new code to the end of
// the last block in |new_blocks|. Kill OpDebugPrintf instruction.
void GenOutputCode(Instruction* printf_inst,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Set the name for a function or global variable, names will be
// prefixed to identify which instrumentation pass generated them.
std::unique_ptr<Instruction> NewGlobalName(uint32_t id,
const std::string& name_str);
// Set the name for a structure member
std::unique_ptr<Instruction> NewMemberName(uint32_t id, uint32_t member_index,
const std::string& name_str);
// Return id for debug output buffer
uint32_t GetOutputBufferId();
// Return id for buffer uint type
uint32_t GetOutputBufferPtrId();
// Return binding for output buffer for current validation.
uint32_t GetOutputBufferBinding();
// Initialize state for instrumenting bindless checking
void InitializeInstDebugPrintf();
// Apply GenDebugPrintfCode to every instruction in module.
Pass::Status ProcessImpl();
uint32_t ext_inst_printf_id_{0};
// id for output buffer variable
uint32_t output_buffer_id_{0};
// ptr type id for output buffer element
uint32_t output_buffer_ptr_id_{0};
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_

View File

@@ -1,803 +0,0 @@
// Copyright (c) 2018 The Khronos Group Inc.
// Copyright (c) 2018 Valve Corporation
// Copyright (c) 2018 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "instrument_pass.h"
#include "source/cfa.h"
#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {
namespace {
// Indices of operands in SPIR-V instructions
constexpr int kEntryPointFunctionIdInIdx = 1;
} // namespace
void InstrumentPass::MovePreludeCode(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr,
std::unique_ptr<BasicBlock>* new_blk_ptr) {
same_block_pre_.clear();
same_block_post_.clear();
// Initialize new block. Reuse label from original block.
new_blk_ptr->reset(new BasicBlock(std::move(ref_block_itr->GetLabel())));
// Move contents of original ref block up to ref instruction.
for (auto cii = ref_block_itr->begin(); cii != ref_inst_itr;
cii = ref_block_itr->begin()) {
Instruction* inst = &*cii;
inst->RemoveFromList();
std::unique_ptr<Instruction> mv_ptr(inst);
// Remember same-block ops for possible regeneration.
if (IsSameBlockOp(&*mv_ptr)) {
auto* sb_inst_ptr = mv_ptr.get();
same_block_pre_[mv_ptr->result_id()] = sb_inst_ptr;
}
(*new_blk_ptr)->AddInstruction(std::move(mv_ptr));
}
}
void InstrumentPass::MovePostludeCode(
UptrVectorIterator<BasicBlock> ref_block_itr, BasicBlock* new_blk_ptr) {
// Move contents of original ref block.
for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
cii = ref_block_itr->begin()) {
Instruction* inst = &*cii;
inst->RemoveFromList();
std::unique_ptr<Instruction> mv_inst(inst);
// Regenerate any same-block instruction that has not been seen in the
// current block.
if (same_block_pre_.size() > 0) {
CloneSameBlockOps(&mv_inst, &same_block_post_, &same_block_pre_,
new_blk_ptr);
// Remember same-block ops in this block.
if (IsSameBlockOp(&*mv_inst)) {
const uint32_t rid = mv_inst->result_id();
same_block_post_[rid] = rid;
}
}
new_blk_ptr->AddInstruction(std::move(mv_inst));
}
}
std::unique_ptr<Instruction> InstrumentPass::NewLabel(uint32_t label_id) {
auto new_label =
MakeUnique<Instruction>(context(), spv::Op::OpLabel, 0, label_id,
std::initializer_list<Operand>{});
get_def_use_mgr()->AnalyzeInstDefUse(&*new_label);
return new_label;
}
std::unique_ptr<Function> InstrumentPass::StartFunction(
uint32_t func_id, const analysis::Type* return_type,
const std::vector<const analysis::Type*>& param_types) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Function* func_type = GetFunction(return_type, param_types);
const std::vector<Operand> operands{
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{uint32_t(spv::FunctionControlMask::MaskNone)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_mgr->GetId(func_type)}},
};
auto func_inst =
MakeUnique<Instruction>(context(), spv::Op::OpFunction,
type_mgr->GetId(return_type), func_id, operands);
get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
return MakeUnique<Function>(std::move(func_inst));
}
std::unique_ptr<Instruction> InstrumentPass::EndFunction() {
auto end = MakeUnique<Instruction>(context(), spv::Op::OpFunctionEnd, 0, 0,
std::initializer_list<Operand>{});
get_def_use_mgr()->AnalyzeInstDefUse(end.get());
return end;
}
std::vector<uint32_t> InstrumentPass::AddParameters(
Function& func, const std::vector<const analysis::Type*>& param_types) {
std::vector<uint32_t> param_ids;
param_ids.reserve(param_types.size());
for (const analysis::Type* param : param_types) {
uint32_t pid = TakeNextId();
param_ids.push_back(pid);
auto param_inst =
MakeUnique<Instruction>(context(), spv::Op::OpFunctionParameter,
context()->get_type_mgr()->GetId(param), pid,
std::initializer_list<Operand>{});
get_def_use_mgr()->AnalyzeInstDefUse(param_inst.get());
func.AddParameter(std::move(param_inst));
}
return param_ids;
}
std::unique_ptr<Instruction> InstrumentPass::NewName(
uint32_t id, const std::string& name_str) {
return MakeUnique<Instruction>(
context(), spv::Op::OpName, 0, 0,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_ID, {id}},
{SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
}
uint32_t InstrumentPass::Gen32BitCvtCode(uint32_t val_id,
InstructionBuilder* builder) {
// Convert integer value to 32-bit if necessary
analysis::TypeManager* type_mgr = context()->get_type_mgr();
uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_id)->type_id();
analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger();
if (val_ty->width() == 32) return val_id;
bool is_signed = val_ty->IsSigned();
analysis::Integer val_32b_ty(32, is_signed);
analysis::Type* val_32b_reg_ty = type_mgr->GetRegisteredType(&val_32b_ty);
uint32_t val_32b_reg_ty_id = type_mgr->GetId(val_32b_reg_ty);
if (is_signed)
return builder->AddUnaryOp(val_32b_reg_ty_id, spv::Op::OpSConvert, val_id)
->result_id();
else
return builder->AddUnaryOp(val_32b_reg_ty_id, spv::Op::OpUConvert, val_id)
->result_id();
}
uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id,
InstructionBuilder* builder) {
// Convert value to 32-bit if necessary
uint32_t val_32b_id = Gen32BitCvtCode(val_id, builder);
// Cast value to unsigned if necessary
analysis::TypeManager* type_mgr = context()->get_type_mgr();
uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_32b_id)->type_id();
analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger();
if (!val_ty->IsSigned()) return val_32b_id;
return builder->AddUnaryOp(GetUintId(), spv::Op::OpBitcast, val_32b_id)
->result_id();
}
uint32_t InstrumentPass::GenVarLoad(uint32_t var_id,
InstructionBuilder* builder) {
Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
uint32_t type_id = GetPointeeTypeId(var_inst);
Instruction* load_inst = builder->AddLoad(type_id, var_id);
return load_inst->result_id();
}
uint32_t InstrumentPass::GenStageInfo(uint32_t stage_idx,
InstructionBuilder* builder) {
std::vector<uint32_t> ids(4, builder->GetUintConstantId(0));
ids[0] = builder->GetUintConstantId(stage_idx);
// %289 = OpCompositeConstruct %v4uint %uint_0 %285 %288 %uint_0
// TODO(greg-lunarg): Add support for all stages
switch (spv::ExecutionModel(stage_idx)) {
case spv::ExecutionModel::Vertex: {
// Load and store VertexId and InstanceId
uint32_t load_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::VertexIndex)),
builder);
ids[1] = GenUintCastCode(load_id, builder);
load_id = GenVarLoad(context()->GetBuiltinInputVarId(
uint32_t(spv::BuiltIn::InstanceIndex)),
builder);
ids[2] = GenUintCastCode(load_id, builder);
} break;
case spv::ExecutionModel::GLCompute:
case spv::ExecutionModel::TaskNV:
case spv::ExecutionModel::MeshNV:
case spv::ExecutionModel::TaskEXT:
case spv::ExecutionModel::MeshEXT: {
// Load and store GlobalInvocationId.
uint32_t load_id = GenVarLoad(context()->GetBuiltinInputVarId(uint32_t(
spv::BuiltIn::GlobalInvocationId)),
builder);
for (uint32_t u = 0; u < 3u; ++u) {
ids[u + 1] = builder->AddCompositeExtract(GetUintId(), load_id, {u})
->result_id();
}
} break;
case spv::ExecutionModel::Geometry: {
// Load and store PrimitiveId and InvocationId.
uint32_t load_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)),
builder);
ids[1] = load_id;
load_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::InvocationId)),
builder);
ids[2] = GenUintCastCode(load_id, builder);
} break;
case spv::ExecutionModel::TessellationControl: {
// Load and store InvocationId and PrimitiveId
uint32_t load_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::InvocationId)),
builder);
ids[1] = GenUintCastCode(load_id, builder);
load_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)),
builder);
ids[2] = load_id;
} break;
case spv::ExecutionModel::TessellationEvaluation: {
// Load and store PrimitiveId and TessCoord.uv
uint32_t load_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::PrimitiveId)),
builder);
ids[1] = load_id;
load_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::TessCoord)),
builder);
Instruction* uvec3_cast_inst =
builder->AddUnaryOp(GetVec3UintId(), spv::Op::OpBitcast, load_id);
uint32_t uvec3_cast_id = uvec3_cast_inst->result_id();
for (uint32_t u = 0; u < 2u; ++u) {
ids[u + 2] =
builder->AddCompositeExtract(GetUintId(), uvec3_cast_id, {u})
->result_id();
}
} break;
case spv::ExecutionModel::Fragment: {
// Load FragCoord and convert to Uint
Instruction* frag_coord_inst = builder->AddLoad(
GetVec4FloatId(),
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::FragCoord)));
Instruction* uint_frag_coord_inst = builder->AddUnaryOp(
GetVec4UintId(), spv::Op::OpBitcast, frag_coord_inst->result_id());
for (uint32_t u = 0; u < 2u; ++u) {
ids[u + 1] =
builder
->AddCompositeExtract(GetUintId(),
uint_frag_coord_inst->result_id(), {u})
->result_id();
}
} break;
case spv::ExecutionModel::RayGenerationNV:
case spv::ExecutionModel::IntersectionNV:
case spv::ExecutionModel::AnyHitNV:
case spv::ExecutionModel::ClosestHitNV:
case spv::ExecutionModel::MissNV:
case spv::ExecutionModel::CallableNV: {
// Load and store LaunchIdNV.
uint32_t launch_id = GenVarLoad(
context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::LaunchIdNV)),
builder);
for (uint32_t u = 0; u < 3u; ++u) {
ids[u + 1] = builder->AddCompositeExtract(GetUintId(), launch_id, {u})
->result_id();
}
} break;
default: { assert(false && "unsupported stage"); } break;
}
return builder->AddCompositeConstruct(GetVec4UintId(), ids)->result_id();
}
bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
for (auto& id : ids) {
Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id);
if (!spvOpcodeIsConstant(id_inst->opcode())) return false;
}
return true;
}
uint32_t InstrumentPass::GenReadFunctionCall(
uint32_t return_id, uint32_t func_id,
const std::vector<uint32_t>& func_call_args,
InstructionBuilder* ref_builder) {
// If optimizing direct reads and the call has already been generated,
// use its result
if (opt_direct_reads_) {
uint32_t res_id = call2id_[func_call_args];
if (res_id != 0) return res_id;
}
// If the function arguments are all constants, the call can be moved to the
// first block of the function where its result can be reused. One example
// where this is profitable is for uniform buffer references, of which there
// are often many.
InstructionBuilder builder(ref_builder->GetContext(),
&*ref_builder->GetInsertPoint(),
ref_builder->GetPreservedAnalysis());
bool insert_in_first_block = opt_direct_reads_ && AllConstant(func_call_args);
if (insert_in_first_block) {
Instruction* insert_before = &*curr_func_->begin()->tail();
builder.SetInsertPoint(insert_before);
}
uint32_t res_id =
builder.AddFunctionCall(return_id, func_id, func_call_args)->result_id();
if (insert_in_first_block) call2id_[func_call_args] = res_id;
return res_id;
}
bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
return inst->opcode() == spv::Op::OpSampledImage ||
inst->opcode() == spv::Op::OpImage;
}
void InstrumentPass::CloneSameBlockOps(
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
BasicBlock* block_ptr) {
bool changed = false;
(*inst)->ForEachInId([&same_blk_post, &same_blk_pre, &block_ptr, &changed,
this](uint32_t* iid) {
const auto map_itr = (*same_blk_post).find(*iid);
if (map_itr == (*same_blk_post).end()) {
const auto map_itr2 = (*same_blk_pre).find(*iid);
if (map_itr2 != (*same_blk_pre).end()) {
// Clone pre-call same-block ops, map result id.
const Instruction* in_inst = map_itr2->second;
std::unique_ptr<Instruction> sb_inst(in_inst->Clone(context()));
const uint32_t rid = sb_inst->result_id();
const uint32_t nid = this->TakeNextId();
get_decoration_mgr()->CloneDecorations(rid, nid);
sb_inst->SetResultId(nid);
get_def_use_mgr()->AnalyzeInstDefUse(&*sb_inst);
(*same_blk_post)[rid] = nid;
*iid = nid;
changed = true;
CloneSameBlockOps(&sb_inst, same_blk_post, same_blk_pre, block_ptr);
block_ptr->AddInstruction(std::move(sb_inst));
}
} else {
// Reset same-block op operand if necessary
if (*iid != map_itr->second) {
*iid = map_itr->second;
changed = true;
}
}
});
if (changed) get_def_use_mgr()->AnalyzeInstUse(&**inst);
}
void InstrumentPass::UpdateSucceedingPhis(
std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
const auto first_blk = new_blocks.begin();
const auto last_blk = new_blocks.end() - 1;
const uint32_t first_id = (*first_blk)->id();
const uint32_t last_id = (*last_blk)->id();
const BasicBlock& const_last_block = *last_blk->get();
const_last_block.ForEachSuccessorLabel(
[&first_id, &last_id, this](const uint32_t succ) {
BasicBlock* sbp = this->id2block_[succ];
sbp->ForEachPhiInst([&first_id, &last_id, this](Instruction* phi) {
bool changed = false;
phi->ForEachInId([&first_id, &last_id, &changed](uint32_t* id) {
if (*id == first_id) {
*id = last_id;
changed = true;
}
});
if (changed) get_def_use_mgr()->AnalyzeInstUse(phi);
});
});
}
analysis::Integer* InstrumentPass::GetInteger(uint32_t width, bool is_signed) {
analysis::Integer i(width, is_signed);
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&i);
assert(type && type->AsInteger());
return type->AsInteger();
}
analysis::Struct* InstrumentPass::GetStruct(
const std::vector<const analysis::Type*>& fields) {
analysis::Struct s(fields);
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&s);
assert(type && type->AsStruct());
return type->AsStruct();
}
analysis::RuntimeArray* InstrumentPass::GetRuntimeArray(
const analysis::Type* element) {
analysis::RuntimeArray r(element);
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&r);
assert(type && type->AsRuntimeArray());
return type->AsRuntimeArray();
}
analysis::Array* InstrumentPass::GetArray(const analysis::Type* element,
uint32_t length) {
uint32_t length_id = context()->get_constant_mgr()->GetUIntConstId(length);
analysis::Array::LengthInfo length_info{
length_id, {analysis::Array::LengthInfo::Case::kConstant, length}};
analysis::Array r(element, length_info);
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&r);
assert(type && type->AsArray());
return type->AsArray();
}
analysis::Function* InstrumentPass::GetFunction(
const analysis::Type* return_val,
const std::vector<const analysis::Type*>& args) {
analysis::Function func(return_val, args);
analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&func);
assert(type && type->AsFunction());
return type->AsFunction();
}
analysis::RuntimeArray* InstrumentPass::GetUintXRuntimeArrayType(
uint32_t width, analysis::RuntimeArray** rarr_ty) {
if (*rarr_ty == nullptr) {
*rarr_ty = GetRuntimeArray(GetInteger(width, false));
uint32_t uint_arr_ty_id =
context()->get_type_mgr()->GetTypeInstruction(*rarr_ty);
// By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
// a block, and will therefore be decorated with an ArrayStride. Therefore
// the undecorated type returned here will not be pre-existing and can
// safely be decorated. Since this type is now decorated, it is out of
// sync with the TypeManager and therefore the TypeManager must be
// invalidated after this pass.
assert(get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
"used RuntimeArray type returned");
get_decoration_mgr()->AddDecorationVal(
uint_arr_ty_id, uint32_t(spv::Decoration::ArrayStride), width / 8u);
}
return *rarr_ty;
}
analysis::RuntimeArray* InstrumentPass::GetUintRuntimeArrayType(
uint32_t width) {
analysis::RuntimeArray** rarr_ty =
(width == 64) ? &uint64_rarr_ty_ : &uint32_rarr_ty_;
return GetUintXRuntimeArrayType(width, rarr_ty);
}
void InstrumentPass::AddStorageBufferExt() {
if (storage_buffer_ext_defined_) return;
if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
context()->AddExtension("SPV_KHR_storage_buffer_storage_class");
}
storage_buffer_ext_defined_ = true;
}
uint32_t InstrumentPass::GetFloatId() {
if (float_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Float float_ty(32);
analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
float_id_ = type_mgr->GetTypeInstruction(reg_float_ty);
}
return float_id_;
}
uint32_t InstrumentPass::GetVec4FloatId() {
if (v4float_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Float float_ty(32);
analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
analysis::Vector v4float_ty(reg_float_ty, 4);
analysis::Type* reg_v4float_ty = type_mgr->GetRegisteredType(&v4float_ty);
v4float_id_ = type_mgr->GetTypeInstruction(reg_v4float_ty);
}
return v4float_id_;
}
uint32_t InstrumentPass::GetUintId() {
if (uint_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Integer uint_ty(32, false);
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
uint_id_ = type_mgr->GetTypeInstruction(reg_uint_ty);
}
return uint_id_;
}
uint32_t InstrumentPass::GetUint64Id() {
if (uint64_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Integer uint64_ty(64, false);
analysis::Type* reg_uint64_ty = type_mgr->GetRegisteredType(&uint64_ty);
uint64_id_ = type_mgr->GetTypeInstruction(reg_uint64_ty);
}
return uint64_id_;
}
uint32_t InstrumentPass::GetUint8Id() {
if (uint8_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Integer uint8_ty(8, false);
analysis::Type* reg_uint8_ty = type_mgr->GetRegisteredType(&uint8_ty);
uint8_id_ = type_mgr->GetTypeInstruction(reg_uint8_ty);
}
return uint8_id_;
}
uint32_t InstrumentPass::GetVecUintId(uint32_t len) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Integer uint_ty(32, false);
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
analysis::Vector v_uint_ty(reg_uint_ty, len);
analysis::Type* reg_v_uint_ty = type_mgr->GetRegisteredType(&v_uint_ty);
uint32_t v_uint_id = type_mgr->GetTypeInstruction(reg_v_uint_ty);
return v_uint_id;
}
uint32_t InstrumentPass::GetVec4UintId() {
if (v4uint_id_ == 0) v4uint_id_ = GetVecUintId(4u);
return v4uint_id_;
}
uint32_t InstrumentPass::GetVec3UintId() {
if (v3uint_id_ == 0) v3uint_id_ = GetVecUintId(3u);
return v3uint_id_;
}
uint32_t InstrumentPass::GetBoolId() {
if (bool_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Bool bool_ty;
analysis::Type* reg_bool_ty = type_mgr->GetRegisteredType(&bool_ty);
bool_id_ = type_mgr->GetTypeInstruction(reg_bool_ty);
}
return bool_id_;
}
uint32_t InstrumentPass::GetVoidId() {
if (void_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Void void_ty;
analysis::Type* reg_void_ty = type_mgr->GetRegisteredType(&void_ty);
void_id_ = type_mgr->GetTypeInstruction(reg_void_ty);
}
return void_id_;
}
void InstrumentPass::SplitBlock(
BasicBlock::iterator inst_itr, UptrVectorIterator<BasicBlock> block_itr,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
// Make sure def/use analysis is done before we start moving instructions
// out of function
(void)get_def_use_mgr();
// Move original block's preceding instructions into first new block
std::unique_ptr<BasicBlock> first_blk_ptr;
MovePreludeCode(inst_itr, block_itr, &first_blk_ptr);
InstructionBuilder builder(
context(), &*first_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
uint32_t split_blk_id = TakeNextId();
std::unique_ptr<Instruction> split_label(NewLabel(split_blk_id));
(void)builder.AddBranch(split_blk_id);
new_blocks->push_back(std::move(first_blk_ptr));
// Move remaining instructions into split block and add to new blocks
std::unique_ptr<BasicBlock> split_blk_ptr(
new BasicBlock(std::move(split_label)));
MovePostludeCode(block_itr, &*split_blk_ptr);
new_blocks->push_back(std::move(split_blk_ptr));
}
bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
InstProcessFunction& pfn) {
curr_func_ = func;
call2id_.clear();
bool first_block_split = false;
bool modified = false;
// Apply instrumentation function to each instruction.
// Using block iterators here because of block erasures and insertions.
std::vector<std::unique_ptr<BasicBlock>> new_blks;
for (auto bi = func->begin(); bi != func->end(); ++bi) {
for (auto ii = bi->begin(); ii != bi->end();) {
// Split all executable instructions out of first block into a following
// block. This will allow function calls to be inserted into the first
// block without interfering with the instrumentation algorithm.
if (opt_direct_reads_ && !first_block_split) {
if (ii->opcode() != spv::Op::OpVariable) {
SplitBlock(ii, bi, &new_blks);
first_block_split = true;
}
} else {
pfn(ii, bi, stage_idx, &new_blks);
}
// If no new code, continue
if (new_blks.size() == 0) {
++ii;
continue;
}
// Add new blocks to label id map
for (auto& blk : new_blks) id2block_[blk->id()] = &*blk;
// If there are new blocks we know there will always be two or
// more, so update succeeding phis with label of new last block.
size_t newBlocksSize = new_blks.size();
assert(newBlocksSize > 1);
UpdateSucceedingPhis(new_blks);
// Replace original block with new block(s)
bi = bi.Erase();
for (auto& bb : new_blks) {
bb->SetParent(func);
}
bi = bi.InsertBefore(&new_blks);
// Reset block iterator to last new block
for (size_t i = 0; i < newBlocksSize - 1; i++) ++bi;
modified = true;
// Restart instrumenting at beginning of last new block,
// but skip over any new phi or copy instruction.
ii = bi->begin();
if (ii->opcode() == spv::Op::OpPhi ||
ii->opcode() == spv::Op::OpCopyObject)
++ii;
new_blks.clear();
}
}
return modified;
}
bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
std::queue<uint32_t>* roots,
uint32_t stage_idx) {
bool modified = false;
std::unordered_set<uint32_t> done;
// Don't process input and output functions
for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
for (auto& ofn : param2output_func_id_) done.insert(ofn.second);
// Process all functions from roots
while (!roots->empty()) {
const uint32_t fi = roots->front();
roots->pop();
if (done.insert(fi).second) {
Function* fn = id2function_.at(fi);
// Add calls first so we don't add new output function
context()->AddCalls(fn, roots);
modified = InstrumentFunction(fn, stage_idx, pfn) || modified;
}
}
return modified;
}
bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
uint32_t stage_id;
if (use_stage_info_) {
// Make sure all entry points have the same execution model. Do not
// instrument if they do not.
// TODO(greg-lunarg): Handle mixed stages. Technically, a shader module
// can contain entry points with different execution models, although
// such modules will likely be rare as GLSL and HLSL are geared toward
// one model per module. In such cases we will need
// to clone any functions which are in the call trees of entrypoints
// with differing execution models.
spv::ExecutionModel stage = context()->GetStage();
// Check for supported stages
if (stage != spv::ExecutionModel::Vertex &&
stage != spv::ExecutionModel::Fragment &&
stage != spv::ExecutionModel::Geometry &&
stage != spv::ExecutionModel::GLCompute &&
stage != spv::ExecutionModel::TessellationControl &&
stage != spv::ExecutionModel::TessellationEvaluation &&
stage != spv::ExecutionModel::TaskNV &&
stage != spv::ExecutionModel::MeshNV &&
stage != spv::ExecutionModel::RayGenerationNV &&
stage != spv::ExecutionModel::IntersectionNV &&
stage != spv::ExecutionModel::AnyHitNV &&
stage != spv::ExecutionModel::ClosestHitNV &&
stage != spv::ExecutionModel::MissNV &&
stage != spv::ExecutionModel::CallableNV &&
stage != spv::ExecutionModel::TaskEXT &&
stage != spv::ExecutionModel::MeshEXT) {
if (consumer()) {
std::string message = "Stage not supported by instrumentation";
consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str());
}
return false;
}
stage_id = static_cast<uint32_t>(stage);
} else {
stage_id = 0;
}
// Add together the roots of all entry points
std::queue<uint32_t> roots;
for (auto& e : get_module()->entry_points()) {
roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
}
bool modified = InstProcessCallTreeFromRoots(pfn, &roots, stage_id);
return modified;
}
void InstrumentPass::InitializeInstrument() {
float_id_ = 0;
v4float_id_ = 0;
uint_id_ = 0;
uint64_id_ = 0;
uint8_id_ = 0;
v4uint_id_ = 0;
v3uint_id_ = 0;
bool_id_ = 0;
void_id_ = 0;
storage_buffer_ext_defined_ = false;
uint32_rarr_ty_ = nullptr;
uint64_rarr_ty_ = nullptr;
// clear collections
id2function_.clear();
id2block_.clear();
// clear maps
param2input_func_id_.clear();
param2output_func_id_.clear();
// Initialize function and block maps.
for (auto& fn : *get_module()) {
id2function_[fn.result_id()] = &fn;
for (auto& blk : fn) {
id2block_[blk.id()] = &blk;
}
}
// Remember original instruction offsets
uint32_t module_offset = 0;
Module* module = get_module();
for (auto& i : context()->capabilities()) {
(void)i;
++module_offset;
}
for (auto& i : module->extensions()) {
(void)i;
++module_offset;
}
for (auto& i : module->ext_inst_imports()) {
(void)i;
++module_offset;
}
++module_offset; // memory_model
for (auto& i : module->entry_points()) {
(void)i;
++module_offset;
}
for (auto& i : module->execution_modes()) {
(void)i;
++module_offset;
}
for (auto& i : module->debugs1()) {
(void)i;
++module_offset;
}
for (auto& i : module->debugs2()) {
(void)i;
++module_offset;
}
for (auto& i : module->debugs3()) {
(void)i;
++module_offset;
}
for (auto& i : module->ext_inst_debuginfo()) {
(void)i;
++module_offset;
}
for (auto& i : module->annotations()) {
(void)i;
++module_offset;
}
for (auto& i : module->types_values()) {
module_offset += 1;
module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
}
auto curr_fn = get_module()->begin();
for (; curr_fn != get_module()->end(); ++curr_fn) {
// Count function instruction
module_offset += 1;
curr_fn->ForEachParam(
[&module_offset](const Instruction*) { module_offset += 1; }, true);
for (auto& blk : *curr_fn) {
// Count label
module_offset += 1;
for (auto& inst : blk) {
module_offset += static_cast<uint32_t>(inst.dbg_line_insts().size());
uid2offset_[inst.unique_id()] = module_offset;
module_offset += 1;
}
}
// Count function end instruction
module_offset += 1;
}
}
} // namespace opt
} // namespace spvtools

View File

@@ -1,326 +0,0 @@
// Copyright (c) 2018 The Khronos Group Inc.
// Copyright (c) 2018 Valve Corporation
// Copyright (c) 2018 LunarG Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef LIBSPIRV_OPT_INSTRUMENT_PASS_H_
#define LIBSPIRV_OPT_INSTRUMENT_PASS_H_
#include <list>
#include <memory>
#include <vector>
#include "source/opt/ir_builder.h"
#include "source/opt/pass.h"
#include "spirv-tools/instrument.hpp"
// This is a base class to assist in the creation of passes which instrument
// shader modules. More specifically, passes which replace instructions with a
// larger and more capable set of instructions. Commonly, these new
// instructions will add testing of operands and execute different
// instructions depending on the outcome, including outputting of debug
// information into a buffer created especially for that purpose.
//
// This class contains helper functions to create an InstProcessFunction,
// which is the heart of any derived class implementing a specific
// instrumentation pass. It takes an instruction as an argument, decides
// if it should be instrumented, and generates code to replace it. This class
// also supplies function InstProcessEntryPointCallTree which applies the
// InstProcessFunction to every reachable instruction in a module and replaces
// the instruction with new instructions if generated.
//
// Chief among the helper functions are output code generation functions,
// used to generate code in the shader which writes data to output buffers
// associated with that validation. Currently one such function,
// GenDebugStreamWrite, exists. Other such functions may be added in the
// future. Each is accompanied by documentation describing the format of
// its output buffer.
//
// A validation pass may read or write multiple buffers. All such buffers
// are located in a single debug descriptor set whose index is passed at the
// creation of the instrumentation pass. The bindings of the buffers used by
// a validation pass are permanently assigned and fixed and documented by
// the kDebugOutput* static consts.
namespace spvtools {
namespace opt {
class InstrumentPass : public Pass {
using cbb_ptr = const BasicBlock*;
public:
using InstProcessFunction =
std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>,
uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>;
~InstrumentPass() override = default;
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap |
IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants;
}
protected:
// Create instrumentation pass for |validation_id| which utilizes descriptor
// set |desc_set| for debug input and output buffers and writes |shader_id|
// into debug output records. |opt_direct_reads| indicates that the pass
// will see direct input buffer reads and should prepare to optimize them.
InstrumentPass(uint32_t desc_set, uint32_t shader_id, bool opt_direct_reads,
bool use_stage_info)
: Pass(),
desc_set_(desc_set),
shader_id_(shader_id),
opt_direct_reads_(opt_direct_reads),
use_stage_info_(use_stage_info) {}
// Initialize state for instrumentation of module.
void InitializeInstrument();
// Call |pfn| on all instructions in all functions in the call tree of the
// entry points in |module|. If code is generated for an instruction, replace
// the instruction's block with the new blocks that are generated. Continue
// processing at the top of the last new block.
bool InstProcessEntryPointCallTree(InstProcessFunction& pfn);
// Move all code in |ref_block_itr| preceding the instruction |ref_inst_itr|
// to be instrumented into block |new_blk_ptr|.
void MovePreludeCode(BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr,
std::unique_ptr<BasicBlock>* new_blk_ptr);
// Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
// to be instrumented into block |new_blk_ptr|.
void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
BasicBlock* new_blk_ptr);
// Return true if all instructions in |ids| are constants or spec constants.
bool AllConstant(const std::vector<uint32_t>& ids);
uint32_t GenReadFunctionCall(uint32_t return_id, uint32_t func_id,
const std::vector<uint32_t>& args,
InstructionBuilder* builder);
// Generate code to convert integer |value_id| to 32bit, if needed. Return
// an id to the 32bit equivalent.
uint32_t Gen32BitCvtCode(uint32_t value_id, InstructionBuilder* builder);
// Generate code to cast integer |value_id| to 32bit unsigned, if needed.
// Return an id to the Uint equivalent.
uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
std::unique_ptr<Function> StartFunction(
uint32_t func_id, const analysis::Type* return_type,
const std::vector<const analysis::Type*>& param_types);
std::vector<uint32_t> AddParameters(
Function& func, const std::vector<const analysis::Type*>& param_types);
std::unique_ptr<Instruction> EndFunction();
// Return new label.
std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
// Set the name function parameter or local variable
std::unique_ptr<Instruction> NewName(uint32_t id,
const std::string& name_str);
// Return id for 32-bit unsigned type
uint32_t GetUintId();
// Return id for 64-bit unsigned type
uint32_t GetUint64Id();
// Return id for 8-bit unsigned type
uint32_t GetUint8Id();
// Return id for 32-bit unsigned type
uint32_t GetBoolId();
// Return id for void type
uint32_t GetVoidId();
// Get registered type structures
analysis::Integer* GetInteger(uint32_t width, bool is_signed);
analysis::Struct* GetStruct(const std::vector<const analysis::Type*>& fields);
analysis::RuntimeArray* GetRuntimeArray(const analysis::Type* element);
analysis::Array* GetArray(const analysis::Type* element, uint32_t size);
analysis::Function* GetFunction(
const analysis::Type* return_val,
const std::vector<const analysis::Type*>& args);
// Return pointer to type for runtime array of uint
analysis::RuntimeArray* GetUintXRuntimeArrayType(
uint32_t width, analysis::RuntimeArray** rarr_ty);
// Return pointer to type for runtime array of uint
analysis::RuntimeArray* GetUintRuntimeArrayType(uint32_t width);
// Add storage buffer extension if needed
void AddStorageBufferExt();
// Return id for 32-bit float type
uint32_t GetFloatId();
// Return id for v4float type
uint32_t GetVec4FloatId();
// Return id for uint vector type of |length|
uint32_t GetVecUintId(uint32_t length);
// Return id for v4uint type
uint32_t GetVec4UintId();
// Return id for v3uint type
uint32_t GetVec3UintId();
// Split block |block_itr| into two new blocks where the second block
// contains |inst_itr| and place in |new_blocks|.
void SplitBlock(BasicBlock::iterator inst_itr,
UptrVectorIterator<BasicBlock> block_itr,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
// Apply instrumentation function |pfn| to every instruction in |func|.
// If code is generated for an instruction, replace the instruction's
// block with the new blocks that are generated. Continue processing at the
// top of the last new block.
virtual bool InstrumentFunction(Function* func, uint32_t stage_idx,
InstProcessFunction& pfn);
// Call |pfn| on all functions in the call tree of the function
// ids in |roots|.
bool InstProcessCallTreeFromRoots(InstProcessFunction& pfn,
std::queue<uint32_t>* roots,
uint32_t stage_idx);
// Generate instructions into |builder| which will load |var_id| and return
// its result id.
uint32_t GenVarLoad(uint32_t var_id, InstructionBuilder* builder);
uint32_t GenStageInfo(uint32_t stage_idx, InstructionBuilder* builder);
// Return true if instruction must be in the same block that its result
// is used.
bool IsSameBlockOp(const Instruction* inst) const;
// Clone operands which must be in same block as consumer instructions.
// Look in same_blk_pre for instructions that need cloning. Look in
// same_blk_post for instructions already cloned. Add cloned instruction
// to same_blk_post.
void CloneSameBlockOps(
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
BasicBlock* block_ptr);
// Update phis in succeeding blocks to point to new last block
void UpdateSucceedingPhis(
std::vector<std::unique_ptr<BasicBlock>>& new_blocks);
// Debug descriptor set index
uint32_t desc_set_;
// Shader module ID written into output record
uint32_t shader_id_;
// Map from function id to function pointer.
std::unordered_map<uint32_t, Function*> id2function_;
// Map from block's label id to block. TODO(dnovillo): This is superfluous wrt
// CFG. It has functionality not present in CFG. Consolidate.
std::unordered_map<uint32_t, BasicBlock*> id2block_;
// Map from instruction's unique id to offset in original file.
std::unordered_map<uint32_t, uint32_t> uid2offset_;
// id for debug output function
std::unordered_map<uint32_t, uint32_t> param2output_func_id_;
// ids for debug input functions
std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
// id for 32-bit float type
uint32_t float_id_{0};
// id for v4float type
uint32_t v4float_id_{0};
// id for v4uint type
uint32_t v4uint_id_{0};
// id for v3uint type
uint32_t v3uint_id_{0};
// id for 32-bit unsigned type
uint32_t uint_id_{0};
// id for 64-bit unsigned type
uint32_t uint64_id_{0};
// id for 8-bit unsigned type
uint32_t uint8_id_{0};
// id for bool type
uint32_t bool_id_{0};
// id for void type
uint32_t void_id_{0};
// boolean to remember storage buffer extension
bool storage_buffer_ext_defined_{false};
// runtime array of uint type
analysis::RuntimeArray* uint64_rarr_ty_{nullptr};
// runtime array of uint type
analysis::RuntimeArray* uint32_rarr_ty_{nullptr};
// Pre-instrumentation same-block insts
std::unordered_map<uint32_t, Instruction*> same_block_pre_;
// Post-instrumentation same-block op ids
std::unordered_map<uint32_t, uint32_t> same_block_post_;
// Map function calls to result id. Clear for every function.
// This is for debug input reads with constant arguments that
// have been generated into the first block of the function.
// This mechanism is used to avoid multiple identical debug
// input buffer reads.
struct vector_hash_ {
std::size_t operator()(const std::vector<uint32_t>& v) const {
std::size_t hash = v.size();
for (auto& u : v) {
hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21);
}
return hash;
}
};
std::unordered_map<std::vector<uint32_t>, uint32_t, vector_hash_> call2id_;
// Function currently being instrumented
Function* curr_func_{nullptr};
// Optimize direct debug input buffer reads. Specifically, move all such
// reads with constant args to first block and reuse them.
const bool opt_direct_reads_;
// Set true if the instrumentation needs to know the current stage.
// Note that this does not work with multi-stage modules.
const bool use_stage_info_;
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_INSTRUMENT_PASS_H_

View File

@@ -429,7 +429,8 @@ void LocalAccessChainConvertPass::InitExtensions() {
"SPV_KHR_fragment_shader_barycentric", "SPV_KHR_vulkan_memory_model",
"SPV_NV_bindless_texture", "SPV_EXT_shader_atomic_float_add",
"SPV_EXT_fragment_shader_interlock",
"SPV_NV_compute_shader_derivatives"});
"SPV_KHR_compute_shader_derivatives", "SPV_NV_cooperative_matrix",
"SPV_KHR_cooperative_matrix", "SPV_KHR_ray_tracing_position_fetch"});
}
bool LocalAccessChainConvertPass::AnyIndexIsOutOfBounds(

View File

@@ -291,7 +291,10 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
"SPV_NV_bindless_texture",
"SPV_EXT_shader_atomic_float_add",
"SPV_EXT_fragment_shader_interlock",
"SPV_NV_compute_shader_derivatives"});
"SPV_KHR_compute_shader_derivatives",
"SPV_NV_cooperative_matrix",
"SPV_KHR_cooperative_matrix",
"SPV_KHR_ray_tracing_position_fetch"});
}
} // namespace opt

View File

@@ -141,7 +141,10 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() {
"SPV_NV_bindless_texture",
"SPV_EXT_shader_atomic_float_add",
"SPV_EXT_fragment_shader_interlock",
"SPV_NV_compute_shader_derivatives"});
"SPV_KHR_compute_shader_derivatives",
"SPV_NV_cooperative_matrix",
"SPV_KHR_cooperative_matrix",
"SPV_KHR_ray_tracing_position_fetch"});
}
bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
std::vector<Instruction*> users;

View File

@@ -43,6 +43,8 @@ bool MemPass::IsBaseTargetType(const Instruction* typeInst) const {
case spv::Op::OpTypeSampler:
case spv::Op::OpTypeSampledImage:
case spv::Op::OpTypePointer:
case spv::Op::OpTypeCooperativeMatrixNV:
case spv::Op::OpTypeCooperativeMatrixKHR:
return true;
default:
break;
@@ -413,6 +415,7 @@ void MemPass::RemoveBlock(Function::iterator* bi) {
}
bool MemPass::RemoveUnreachableBlocks(Function* func) {
if (func->IsDeclaration()) return false;
bool modified = false;
// Mark reachable all blocks reachable from the function's entry block.

View File

@@ -364,6 +364,10 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
RegisterPass(CreateSpreadVolatileSemanticsPass());
} else if (pass_name == "descriptor-scalar-replacement") {
RegisterPass(CreateDescriptorScalarReplacementPass());
} else if (pass_name == "descriptor-composite-scalar-replacement") {
RegisterPass(CreateDescriptorCompositeScalarReplacementPass());
} else if (pass_name == "descriptor-array-scalar-replacement") {
RegisterPass(CreateDescriptorArrayScalarReplacementPass());
} else if (pass_name == "eliminate-dead-code-aggressive") {
RegisterPass(CreateAggressiveDCEPass(preserve_interface));
} else if (pass_name == "eliminate-insert-extract") {
@@ -458,13 +462,6 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
RegisterPass(CreateConvertRelaxedToHalfPass());
} else if (pass_name == "relax-float-ops") {
RegisterPass(CreateRelaxFloatOpsPass());
} else if (pass_name == "inst-debug-printf") {
// This private option is not for user consumption.
// It is here to assist in debugging and fixing the debug printf
// instrumentation pass.
// For users who wish to utilize debug printf, see the white paper at
// https://www.lunarg.com/wp-content/uploads/2021/08/Using-Debug-Printf-02August2021.pdf
RegisterPass(CreateInstDebugPrintfPass(7, 23));
} else if (pass_name == "simplify-instructions") {
RegisterPass(CreateSimplificationPass());
} else if (pass_name == "ssa-rewrite") {
@@ -567,6 +564,26 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
pass_args.c_str());
return false;
}
} else if (pass_name == "struct-packing") {
if (pass_args.size() == 0) {
Error(consumer(), nullptr, {},
"--struct-packing requires a name:rule argument.");
return false;
}
auto separator_pos = pass_args.find(':');
if (separator_pos == std::string::npos || separator_pos == 0 ||
separator_pos + 1 == pass_args.size()) {
Errorf(consumer(), nullptr, {},
"Invalid argument for --struct-packing: %s", pass_args.c_str());
return false;
}
const std::string struct_name = pass_args.substr(0, separator_pos);
const std::string rule_name = pass_args.substr(separator_pos + 1);
RegisterPass(
CreateStructPackingPass(struct_name.c_str(), rule_name.c_str()));
} else if (pass_name == "switch-descriptorset") {
if (pass_args.size() == 0) {
Error(consumer(), nullptr, {},
@@ -1016,12 +1033,6 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
MakeUnique<opt::UpgradeMemoryModel>());
}
Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
uint32_t shader_id) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstDebugPrintfPass>(desc_set, shader_id));
}
Optimizer::PassToken CreateConvertRelaxedToHalfPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::ConvertToHalfPass>());
@@ -1059,7 +1070,20 @@ Optimizer::PassToken CreateSpreadVolatileSemanticsPass() {
Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::DescriptorScalarReplacement>());
MakeUnique<opt::DescriptorScalarReplacement>(
/* flatten_composites= */ true, /* flatten_arrays= */ true));
}
Optimizer::PassToken CreateDescriptorCompositeScalarReplacementPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::DescriptorScalarReplacement>(
/* flatten_composites= */ true, /* flatten_arrays= */ false));
}
Optimizer::PassToken CreateDescriptorArrayScalarReplacementPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::DescriptorScalarReplacement>(
/* flatten_composites= */ false, /* flatten_arrays= */ true));
}
Optimizer::PassToken CreateWrapOpKillPass() {
@@ -1135,6 +1159,14 @@ Optimizer::PassToken CreateTrimCapabilitiesPass() {
MakeUnique<opt::TrimCapabilitiesPass>());
}
Optimizer::PassToken CreateStructPackingPass(const char* structToPack,
const char* packingRule) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::StructPackingPass>(
structToPack,
opt::StructPackingPass::ParsePackingRuleFromString(packingRule)));
}
Optimizer::PassToken CreateSwitchDescriptorSetPass(uint32_t from, uint32_t to) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::SwitchDescriptorSetPass>(from, to));

View File

@@ -83,7 +83,6 @@ uint32_t Pass::GetNullId(uint32_t type_id) {
uint32_t Pass::GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
Instruction* insertion_position) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
uint32_t original_type_id = object_to_copy->type_id();
@@ -95,57 +94,63 @@ uint32_t Pass::GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
context(), insertion_position,
IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
analysis::Type* original_type = type_mgr->GetType(original_type_id);
analysis::Type* new_type = type_mgr->GetType(new_type_id);
Instruction* original_type = get_def_use_mgr()->GetDef(original_type_id);
Instruction* new_type = get_def_use_mgr()->GetDef(new_type_id);
if (const analysis::Array* original_array_type = original_type->AsArray()) {
uint32_t original_element_type_id =
type_mgr->GetId(original_array_type->element_type());
analysis::Array* new_array_type = new_type->AsArray();
assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
uint32_t new_element_type_id =
type_mgr->GetId(new_array_type->element_type());
std::vector<uint32_t> element_ids;
const analysis::Constant* length_const =
const_mgr->FindDeclaredConstant(original_array_type->LengthId());
assert(length_const->AsIntConstant());
uint32_t array_length = length_const->AsIntConstant()->GetU32();
for (uint32_t i = 0; i < array_length; i++) {
Instruction* extract = ir_builder.AddCompositeExtract(
original_element_type_id, object_to_copy->result_id(), {i});
element_ids.push_back(
GenerateCopy(extract, new_element_type_id, insertion_position));
}
return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
->result_id();
} else if (const analysis::Struct* original_struct_type =
original_type->AsStruct()) {
analysis::Struct* new_struct_type = new_type->AsStruct();
const std::vector<const analysis::Type*>& original_types =
original_struct_type->element_types();
const std::vector<const analysis::Type*>& new_types =
new_struct_type->element_types();
std::vector<uint32_t> element_ids;
for (uint32_t i = 0; i < original_types.size(); i++) {
Instruction* extract = ir_builder.AddCompositeExtract(
type_mgr->GetId(original_types[i]), object_to_copy->result_id(), {i});
element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
insertion_position));
}
return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
->result_id();
} else {
// If we do not have an aggregate type, then we have a problem. Either we
// found multiple instances of the same type, or we are copying to an
// incompatible type. Either way the code is illegal.
assert(false &&
"Don't know how to copy this type. Code is likely illegal.");
if (new_type->opcode() != original_type->opcode()) {
return 0;
}
switch (original_type->opcode()) {
case spv::Op::OpTypeArray: {
uint32_t original_element_type_id =
original_type->GetSingleWordInOperand(0);
uint32_t new_element_type_id = new_type->GetSingleWordInOperand(0);
std::vector<uint32_t> element_ids;
uint32_t length_id = original_type->GetSingleWordInOperand(1);
const analysis::Constant* length_const =
const_mgr->FindDeclaredConstant(length_id);
assert(length_const->AsIntConstant());
uint32_t array_length = length_const->AsIntConstant()->GetU32();
for (uint32_t i = 0; i < array_length; i++) {
Instruction* extract = ir_builder.AddCompositeExtract(
original_element_type_id, object_to_copy->result_id(), {i});
uint32_t new_id =
GenerateCopy(extract, new_element_type_id, insertion_position);
if (new_id == 0) {
return 0;
}
element_ids.push_back(new_id);
}
return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
->result_id();
}
case spv::Op::OpTypeStruct: {
std::vector<uint32_t> element_ids;
for (uint32_t i = 0; i < original_type->NumInOperands(); i++) {
uint32_t orig_member_type_id = original_type->GetSingleWordInOperand(i);
uint32_t new_member_type_id = new_type->GetSingleWordInOperand(i);
Instruction* extract = ir_builder.AddCompositeExtract(
orig_member_type_id, object_to_copy->result_id(), {i});
uint32_t new_id =
GenerateCopy(extract, new_member_type_id, insertion_position);
if (new_id == 0) {
return 0;
}
element_ids.push_back(new_id);
}
return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
->result_id();
}
default:
// If we do not have an aggregate type, then we have a problem. Either we
// found multiple instances of the same type, or we are copying to an
// incompatible type. Either way the code is illegal. Leave the code as
// is and let the caller deal with it.
return 0;
}
return 0;
}
} // namespace opt

View File

@@ -145,7 +145,8 @@ class Pass {
// Returns the id whose value is the same as |object_to_copy| except its type
// is |new_type_id|. Any instructions needed to generate this value will be
// inserted before |insertion_position|.
// inserted before |insertion_position|. Returns 0 if a copy could not be
// done.
uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
Instruction* insertion_position);

View File

@@ -48,7 +48,6 @@
#include "source/opt/if_conversion.h"
#include "source/opt/inline_exhaustive_pass.h"
#include "source/opt/inline_opaque_pass.h"
#include "source/opt/inst_debug_printf_pass.h"
#include "source/opt/interface_var_sroa.h"
#include "source/opt/interp_fixup_pass.h"
#include "source/opt/invocation_interlock_placement_pass.h"
@@ -83,6 +82,7 @@
#include "source/opt/strength_reduction_pass.h"
#include "source/opt/strip_debug_info_pass.h"
#include "source/opt/strip_nonsemantic_info_pass.h"
#include "source/opt/struct_packing_pass.h"
#include "source/opt/switch_descriptorset_pass.h"
#include "source/opt/trim_capabilities_pass.h"
#include "source/opt/unify_const_pass.h"

View File

@@ -21,6 +21,8 @@ class RemoveUnusedInterfaceVariablesContext {
RemoveUnusedInterfaceVariablesPass& parent_;
Instruction& entry_;
std::unordered_set<uint32_t> used_variables_;
std::vector<uint32_t> operands_to_add_;
IRContext::ProcessFunction pfn_ =
std::bind(&RemoveUnusedInterfaceVariablesContext::processFunction, this,
std::placeholders::_1);
@@ -38,8 +40,10 @@ class RemoveUnusedInterfaceVariablesContext {
(parent_.get_module()->version() >=
SPV_SPIRV_VERSION_WORD(1, 4) ||
storage_class == spv::StorageClass::Input ||
storage_class == spv::StorageClass::Output))
storage_class == spv::StorageClass::Output)) {
used_variables_.insert(*id);
operands_to_add_.push_back(*id);
}
});
return false;
}
@@ -71,7 +75,7 @@ class RemoveUnusedInterfaceVariablesContext {
void Modify() {
for (int i = entry_.NumInOperands() - 1; i >= 3; --i)
entry_.RemoveInOperand(i);
for (auto id : used_variables_) {
for (auto id : operands_to_add_) {
entry_.AddOperand(Operand(SPV_OPERAND_TYPE_ID, {id}));
}
}

View File

@@ -0,0 +1,482 @@
// Copyright (c) 2024 Epic Games, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "struct_packing_pass.h"
#include <algorithm>
#include "source/opt/instruction.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
/*
Std140 packing rules from the original GLSL 140 specification (see
https://registry.khronos.org/OpenGL/extensions/ARB/ARB_uniform_buffer_object.txt)
When using the "std140" storage layout, structures will be laid out in
buffer storage with its members stored in monotonically increasing order
based on their location in the declaration. A structure and each
structure member have a base offset and a base alignment, from which an
aligned offset is computed by rounding the base offset up to a multiple of
the base alignment. The base offset of the first member of a structure is
taken from the aligned offset of the structure itself. The base offset of
all other structure members is derived by taking the offset of the last
basic machine unit consumed by the previous member and adding one. Each
structure member is stored in memory at its aligned offset. The members
of a top-level uniform block are laid out in buffer storage by treating
the uniform block as a structure with a base offset of zero.
(1) If the member is a scalar consuming <N> basic machine units, the
base alignment is <N>.
(2) If the member is a two- or four-component vector with components
consuming <N> basic machine units, the base alignment is 2<N> or
4<N>, respectively.
(3) If the member is a three-component vector with components consuming
<N> basic machine units, the base alignment is 4<N>.
(4) If the member is an array of scalars or vectors, the base alignment
and array stride are set to match the base alignment of a single
array element, according to rules (1), (2), and (3), and rounded up
to the base alignment of a vec4. The array may have padding at the
end; the base offset of the member following the array is rounded up
to the next multiple of the base alignment.
(5) If the member is a column-major matrix with <C> columns and <R>
rows, the matrix is stored identically to an array of <C> column
vectors with <R> components each, according to rule (4).
(6) If the member is an array of <S> column-major matrices with <C>
columns and <R> rows, the matrix is stored identically to a row of
<S>*<C> column vectors with <R> components each, according to rule
(4).
(7) If the member is a row-major matrix with <C> columns and <R> rows,
the matrix is stored identically to an array of <R> row vectors
with <C> components each, according to rule (4).
(8) If the member is an array of <S> row-major matrices with <C> columns
and <R> rows, the matrix is stored identically to a row of <S>*<R>
row vectors with <C> components each, according to rule (4).
(9) If the member is a structure, the base alignment of the structure is
<N>, where <N> is the largest base alignment value of any of its
members, and rounded up to the base alignment of a vec4. The
individual members of this sub-structure are then assigned offsets
by applying this set of rules recursively, where the base offset of
the first member of the sub-structure is equal to the aligned offset
of the structure. The structure may have padding at the end; the
base offset of the member following the sub-structure is rounded up
to the next multiple of the base alignment of the structure.
(10) If the member is an array of <S> structures, the <S> elements of
the array are laid out in order, according to rule (9).
*/
static bool isPackingVec4Padded(StructPackingPass::PackingRules rules) {
switch (rules) {
case StructPackingPass::PackingRules::Std140:
case StructPackingPass::PackingRules::Std140EnhancedLayout:
case StructPackingPass::PackingRules::HlslCbuffer:
case StructPackingPass::PackingRules::HlslCbufferPackOffset:
return true;
default:
return false;
}
}
static bool isPackingScalar(StructPackingPass::PackingRules rules) {
switch (rules) {
case StructPackingPass::PackingRules::Scalar:
case StructPackingPass::PackingRules::ScalarEnhancedLayout:
return true;
default:
return false;
}
}
static bool isPackingHlsl(StructPackingPass::PackingRules rules) {
switch (rules) {
case StructPackingPass::PackingRules::HlslCbuffer:
case StructPackingPass::PackingRules::HlslCbufferPackOffset:
return true;
default:
return false;
}
}
static uint32_t getPackedBaseSize(const analysis::Type& type) {
switch (type.kind()) {
case analysis::Type::kBool:
return 1;
case analysis::Type::kInteger:
return type.AsInteger()->width() / 8;
case analysis::Type::kFloat:
return type.AsFloat()->width() / 8;
case analysis::Type::kVector:
return getPackedBaseSize(*type.AsVector()->element_type());
case analysis::Type::kMatrix:
return getPackedBaseSize(*type.AsMatrix()->element_type());
default:
break; // we only expect bool, int, float, vec, and mat here
}
assert(0 && "Unrecognized type to get base size");
return 0;
}
static uint32_t getScalarElementCount(const analysis::Type& type) {
switch (type.kind()) {
case analysis::Type::kVector:
return type.AsVector()->element_count();
case analysis::Type::kMatrix:
return getScalarElementCount(*type.AsMatrix()->element_type());
case analysis::Type::kStruct:
assert(0 && "getScalarElementCount() does not recognized struct types");
return 0;
default:
return 1;
}
}
// Aligns the specified value to a multiple of alignment, whereas the
// alignment must be a power-of-two.
static uint32_t alignPow2(uint32_t value, uint32_t alignment) {
return (value + alignment - 1) & ~(alignment - 1);
}
void StructPackingPass::buildConstantsMap() {
constantsMap_.clear();
for (Instruction* instr : context()->module()->GetConstants()) {
constantsMap_[instr->result_id()] = instr;
}
}
uint32_t StructPackingPass::getPackedAlignment(
const analysis::Type& type) const {
switch (type.kind()) {
case analysis::Type::kArray: {
// Get alignment of base type and round up to minimum alignment
const uint32_t minAlignment = isPackingVec4Padded(packingRules_) ? 16 : 1;
return std::max<uint32_t>(
minAlignment, getPackedAlignment(*type.AsArray()->element_type()));
}
case analysis::Type::kStruct: {
// Rule 9. Struct alignment is maximum alignmnet of its members
uint32_t alignment = 1;
for (const analysis::Type* elementType :
type.AsStruct()->element_types()) {
alignment =
std::max<uint32_t>(alignment, getPackedAlignment(*elementType));
}
if (isPackingVec4Padded(packingRules_))
alignment = std::max<uint32_t>(alignment, 16u);
return alignment;
}
default: {
const uint32_t baseAlignment = getPackedBaseSize(type);
// Scalar block layout always uses alignment for the most basic component
if (isPackingScalar(packingRules_)) return baseAlignment;
if (const analysis::Matrix* matrixType = type.AsMatrix()) {
// Rule 5/7
if (isPackingVec4Padded(packingRules_) ||
matrixType->element_count() == 3)
return baseAlignment * 4;
else
return baseAlignment * matrixType->element_count();
} else if (const analysis::Vector* vectorType = type.AsVector()) {
// Rule 1
if (vectorType->element_count() == 1) return baseAlignment;
// Rule 2
if (vectorType->element_count() == 2 ||
vectorType->element_count() == 4)
return baseAlignment * vectorType->element_count();
// Rule 3
if (vectorType->element_count() == 3) return baseAlignment * 4;
} else {
// Rule 1
return baseAlignment;
}
}
}
assert(0 && "Unrecognized type to get packed alignment");
return 0;
}
static uint32_t getPadAlignment(const analysis::Type& type,
uint32_t packedAlignment) {
// The next member following a struct member is aligned to the base alignment
// of a previous struct member.
return type.kind() == analysis::Type::kStruct ? packedAlignment : 1;
}
uint32_t StructPackingPass::getPackedSize(const analysis::Type& type) const {
switch (type.kind()) {
case analysis::Type::kArray: {
if (const analysis::Array* arrayType = type.AsArray()) {
uint32_t size =
getPackedArrayStride(*arrayType) * getArrayLength(*arrayType);
// For arrays of vector and matrices in HLSL, the last element has a
// size depending on its vector/matrix size to allow packing other
// vectors in the last element.
const analysis::Type* arraySubType = arrayType->element_type();
if (isPackingHlsl(packingRules_) &&
arraySubType->kind() != analysis::Type::kStruct) {
size -= (4 - getScalarElementCount(*arraySubType)) *
getPackedBaseSize(*arraySubType);
}
return size;
}
break;
}
case analysis::Type::kStruct: {
uint32_t size = 0;
uint32_t padAlignment = 1;
for (const analysis::Type* memberType :
type.AsStruct()->element_types()) {
const uint32_t packedAlignment = getPackedAlignment(*memberType);
const uint32_t alignment =
std::max<uint32_t>(packedAlignment, padAlignment);
padAlignment = getPadAlignment(*memberType, packedAlignment);
size = alignPow2(size, alignment);
size += getPackedSize(*memberType);
}
return size;
}
default: {
const uint32_t baseAlignment = getPackedBaseSize(type);
if (isPackingScalar(packingRules_)) {
return getScalarElementCount(type) * baseAlignment;
} else {
uint32_t size = 0;
if (const analysis::Matrix* matrixType = type.AsMatrix()) {
const analysis::Vector* matrixSubType =
matrixType->element_type()->AsVector();
assert(matrixSubType != nullptr &&
"Matrix sub-type is expected to be a vector type");
if (isPackingVec4Padded(packingRules_) ||
matrixType->element_count() == 3)
size = matrixSubType->element_count() * baseAlignment * 4;
else
size = matrixSubType->element_count() * baseAlignment *
matrixType->element_count();
// For matrices in HLSL, the last element has a size depending on its
// vector size to allow packing other vectors in the last element.
if (isPackingHlsl(packingRules_)) {
size -= (4 - matrixSubType->element_count()) *
getPackedBaseSize(*matrixSubType);
}
} else if (const analysis::Vector* vectorType = type.AsVector()) {
size = vectorType->element_count() * baseAlignment;
} else {
size = baseAlignment;
}
return size;
}
}
}
assert(0 && "Unrecognized type to get packed size");
return 0;
}
uint32_t StructPackingPass::getPackedArrayStride(
const analysis::Array& arrayType) const {
// Array stride is equal to aligned size of element type
const uint32_t elementSize = getPackedSize(*arrayType.element_type());
const uint32_t alignment = getPackedAlignment(arrayType);
return alignPow2(elementSize, alignment);
}
uint32_t StructPackingPass::getArrayLength(
const analysis::Array& arrayType) const {
return getConstantInt(arrayType.LengthId());
}
uint32_t StructPackingPass::getConstantInt(spv::Id id) const {
auto it = constantsMap_.find(id);
assert(it != constantsMap_.end() &&
"Failed to map SPIR-V instruction ID to constant value");
[[maybe_unused]] const analysis::Type* constType =
context()->get_type_mgr()->GetType(it->second->type_id());
assert(constType != nullptr &&
"Failed to map SPIR-V instruction result type to definition");
assert(constType->kind() == analysis::Type::kInteger &&
"Failed to map SPIR-V instruction result type to integer type");
return it->second->GetOperand(2).words[0];
}
StructPackingPass::PackingRules StructPackingPass::ParsePackingRuleFromString(
const std::string& s) {
if (s == "std140") return PackingRules::Std140;
if (s == "std140EnhancedLayout") return PackingRules::Std140EnhancedLayout;
if (s == "std430") return PackingRules::Std430;
if (s == "std430EnhancedLayout") return PackingRules::Std430EnhancedLayout;
if (s == "hlslCbuffer") return PackingRules::HlslCbuffer;
if (s == "hlslCbufferPackOffset") return PackingRules::HlslCbufferPackOffset;
if (s == "scalar") return PackingRules::Scalar;
if (s == "scalarEnhancedLayout") return PackingRules::ScalarEnhancedLayout;
return PackingRules::Undefined;
}
StructPackingPass::StructPackingPass(const char* structToPack,
PackingRules rules)
: structToPack_{structToPack != nullptr ? structToPack : ""},
packingRules_{rules} {}
Pass::Status StructPackingPass::Process() {
if (packingRules_ == PackingRules::Undefined) {
if (consumer()) {
consumer()(SPV_MSG_ERROR, "", {0, 0, 0},
"Cannot pack struct with undefined rule");
}
return Status::Failure;
}
// Build Id-to-instruction map for easier access
buildConstantsMap();
// Find structure of interest
const uint32_t structIdToPack = findStructIdByName(structToPack_.c_str());
const Instruction* structDef =
context()->get_def_use_mgr()->GetDef(structIdToPack);
if (structDef == nullptr || structDef->opcode() != spv::Op::OpTypeStruct) {
if (consumer()) {
const std::string message =
"Failed to find struct with name " + structToPack_;
consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
}
return Status::Failure;
}
// Find all struct member types
std::vector<const analysis::Type*> structMemberTypes =
findStructMemberTypes(*structDef);
return assignStructMemberOffsets(structIdToPack, structMemberTypes);
}
uint32_t StructPackingPass::findStructIdByName(const char* structName) const {
for (Instruction& instr : context()->module()->debugs2()) {
if (instr.opcode() == spv::Op::OpName &&
instr.GetOperand(1).AsString() == structName) {
return instr.GetOperand(0).AsId();
}
}
return 0;
}
std::vector<const analysis::Type*> StructPackingPass::findStructMemberTypes(
const Instruction& structDef) const {
// Found struct type to pack, now collect all types of its members
assert(structDef.NumOperands() > 0 &&
"Number of operands in OpTypeStruct instruction must not be zero");
const uint32_t numMembers = structDef.NumOperands() - 1;
std::vector<const analysis::Type*> structMemberTypes;
structMemberTypes.resize(numMembers);
for (uint32_t i = 0; i < numMembers; ++i) {
const spv::Id memberTypeId = structDef.GetOperand(1 + i).AsId();
if (const analysis::Type* memberType =
context()->get_type_mgr()->GetType(memberTypeId)) {
structMemberTypes[i] = memberType;
}
}
return structMemberTypes;
}
Pass::Status StructPackingPass::assignStructMemberOffsets(
uint32_t structIdToPack,
const std::vector<const analysis::Type*>& structMemberTypes) {
// Returns true if the specified instruction is a OpMemberDecorate for the
// struct we're looking for with an offset decoration
auto isMemberOffsetDecoration =
[structIdToPack](const Instruction& instr) -> bool {
return instr.opcode() == spv::Op::OpMemberDecorate &&
instr.GetOperand(0).AsId() == structIdToPack &&
static_cast<spv::Decoration>(instr.GetOperand(2).words[0]) ==
spv::Decoration::Offset;
};
bool modified = false;
// Find and re-assign all member offset decorations
for (auto it = context()->module()->annotation_begin(),
itEnd = context()->module()->annotation_end();
it != itEnd; ++it) {
if (isMemberOffsetDecoration(*it)) {
// Found first member decoration with offset, we expect all other
// offsets right after the first one
uint32_t prevMemberIndex = 0;
uint32_t currentOffset = 0;
uint32_t padAlignment = 1;
do {
const uint32_t memberIndex = it->GetOperand(1).words[0];
if (memberIndex < prevMemberIndex) {
// Failure: we expect all members to appear in consecutive order
return Status::Failure;
}
// Apply alignment rules to current offset
const analysis::Type& memberType = *structMemberTypes[memberIndex];
uint32_t packedAlignment = getPackedAlignment(memberType);
uint32_t packedSize = getPackedSize(memberType);
if (isPackingHlsl(packingRules_)) {
// If a member crosses vec4 boundaries, alignment is size of vec4
if (currentOffset / 16 != (currentOffset + packedSize - 1) / 16)
packedAlignment = std::max<uint32_t>(packedAlignment, 16u);
}
const uint32_t alignment =
std::max<uint32_t>(packedAlignment, padAlignment);
currentOffset = alignPow2(currentOffset, alignment);
padAlignment = getPadAlignment(memberType, packedAlignment);
// Override packed offset in instruction
if (it->GetOperand(3).words[0] < currentOffset) {
// Failure: packing resulted in higher offset for member than
// previously generated
return Status::Failure;
}
it->GetOperand(3).words[0] = currentOffset;
modified = true;
// Move to next member
++it;
prevMemberIndex = memberIndex;
currentOffset += packedSize;
} while (it != itEnd && isMemberOffsetDecoration(*it));
// We're done with all decorations for the struct of interest
break;
}
}
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
} // namespace opt
} // namespace spvtools

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2024 Epic Games, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_OPT_STRUCT_PACKING_PASS_
#define SOURCE_OPT_STRUCT_PACKING_PASS_
#include <unordered_map>
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// This pass re-assigns all field offsets under the specified packing rules.
class StructPackingPass final : public Pass {
public:
enum class PackingRules {
Undefined,
Std140,
Std140EnhancedLayout,
Std430,
Std430EnhancedLayout,
HlslCbuffer,
HlslCbufferPackOffset,
Scalar,
ScalarEnhancedLayout,
};
static PackingRules ParsePackingRuleFromString(const std::string& s);
StructPackingPass(const char* structToPack, PackingRules rules);
const char* name() const override { return "struct-packing"; }
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
IRContext::kAnalysisDominatorAnalysis |
IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
IRContext::kAnalysisScalarEvolution |
IRContext::kAnalysisStructuredCFG | IRContext::kAnalysisConstants |
IRContext::kAnalysisDebugInfo | IRContext::kAnalysisLiveness;
}
private:
void buildConstantsMap();
uint32_t findStructIdByName(const char* structName) const;
std::vector<const analysis::Type*> findStructMemberTypes(
const Instruction& structDef) const;
Status assignStructMemberOffsets(
uint32_t structIdToPack,
const std::vector<const analysis::Type*>& structMemberTypes);
uint32_t getPackedAlignment(const analysis::Type& type) const;
uint32_t getPackedSize(const analysis::Type& type) const;
uint32_t getPackedArrayStride(const analysis::Array& arrayType) const;
uint32_t getArrayLength(const analysis::Array& arrayType) const;
uint32_t getConstantInt(spv::Id id) const;
private:
std::string structToPack_;
PackingRules packingRules_ = PackingRules::Undefined;
std::unordered_map<spv::Id, Instruction*> constantsMap_;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_STRUCT_PACKING_PASS_

View File

@@ -27,6 +27,7 @@
#include "source/enum_set.h"
#include "source/enum_string_mapping.h"
#include "source/ext_inst.h"
#include "source/opt/ir_context.h"
#include "source/opt/reflect.h"
#include "source/spirv_target_env.h"
@@ -49,6 +50,9 @@ constexpr uint32_t kOpTypeImageSampledIndex = kOpTypeImageMSIndex + 1;
constexpr uint32_t kOpTypeImageFormatIndex = kOpTypeImageSampledIndex + 1;
constexpr uint32_t kOpImageReadImageIndex = 0;
constexpr uint32_t kOpImageSparseReadImageIndex = 0;
constexpr uint32_t kOpExtInstSetInIndex = 0;
constexpr uint32_t kOpExtInstInstructionInIndex = 1;
constexpr uint32_t kOpExtInstImportNameInIndex = 0;
// DFS visit of the type defined by `instruction`.
// If `condition` is true, children of the current node are visited.
@@ -514,6 +518,35 @@ void TrimCapabilitiesPass::addInstructionRequirementsForOperand(
}
}
void TrimCapabilitiesPass::addInstructionRequirementsForExtInst(
Instruction* instruction, CapabilitySet* capabilities) const {
assert(instruction->opcode() == spv::Op::OpExtInst &&
"addInstructionRequirementsForExtInst must be passed an OpExtInst "
"instruction");
const auto* def_use_mgr = context()->get_def_use_mgr();
const Instruction* extInstImport = def_use_mgr->GetDef(
instruction->GetSingleWordInOperand(kOpExtInstSetInIndex));
uint32_t extInstruction =
instruction->GetSingleWordInOperand(kOpExtInstInstructionInIndex);
const Operand& extInstSet =
extInstImport->GetInOperand(kOpExtInstImportNameInIndex);
spv_ext_inst_type_t instructionSet =
spvExtInstImportTypeGet(extInstSet.AsString().c_str());
spv_ext_inst_desc desc = {};
auto result =
context()->grammar().lookupExtInst(instructionSet, extInstruction, &desc);
if (result != SPV_SUCCESS) {
return;
}
addSupportedCapabilitiesToSet(desc, capabilities);
}
void TrimCapabilitiesPass::addInstructionRequirements(
Instruction* instruction, CapabilitySet* capabilities,
ExtensionSet* extensions) const {
@@ -523,8 +556,12 @@ void TrimCapabilitiesPass::addInstructionRequirements(
return;
}
addInstructionRequirementsForOpcode(instruction->opcode(), capabilities,
extensions);
if (instruction->opcode() == spv::Op::OpExtInst) {
addInstructionRequirementsForExtInst(instruction, capabilities);
} else {
addInstructionRequirementsForOpcode(instruction->opcode(), capabilities,
extensions);
}
// Second case: one of the opcode operand is gated by a capability.
const uint32_t operandCount = instruction->NumOperands();

View File

@@ -74,8 +74,8 @@ class TrimCapabilitiesPass : public Pass {
// contains unsupported instruction, the pass could yield bad results.
static constexpr std::array kSupportedCapabilities{
// clang-format off
spv::Capability::ComputeDerivativeGroupLinearNV,
spv::Capability::ComputeDerivativeGroupQuadsNV,
spv::Capability::ComputeDerivativeGroupLinearKHR,
spv::Capability::ComputeDerivativeGroupQuadsKHR,
spv::Capability::Float16,
spv::Capability::Float64,
spv::Capability::FragmentShaderPixelInterlockEXT,
@@ -90,6 +90,7 @@ class TrimCapabilitiesPass : public Pass {
spv::Capability::ImageMSArray,
spv::Capability::Int16,
spv::Capability::Int64,
spv::Capability::InterpolationFunction,
spv::Capability::Linkage,
spv::Capability::MinLod,
spv::Capability::PhysicalStorageBufferAddresses,
@@ -160,6 +161,9 @@ class TrimCapabilitiesPass : public Pass {
CapabilitySet* capabilities,
ExtensionSet* extensions) const;
void addInstructionRequirementsForExtInst(Instruction* instruction,
CapabilitySet* capabilities) const;
// Given an `instruction`, determines the capabilities it requires, and output
// them in `capabilities`. The returned capabilities form a subset of
// kSupportedCapabilities.

View File

@@ -245,6 +245,7 @@ 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>{

View File

@@ -22,6 +22,8 @@
typedef struct spv_opcode_desc_t {
const char* name;
const spv::Op opcode;
const uint32_t numAliases;
const char** aliases;
const uint32_t numCapabilities;
const spv::Capability* capabilities;
// operandTypes[0..numTypes-1] describe logical operands for the instruction.
@@ -47,6 +49,8 @@ typedef struct spv_opcode_desc_t {
typedef struct spv_operand_desc_t {
const char* name;
const uint32_t value;
const uint32_t numAliases;
const char** aliases;
const uint32_t numCapabilities;
const spv::Capability* capabilities;
// A set of extensions that enable this feature. If empty then this operand

View File

@@ -329,8 +329,9 @@ spv_result_t AssemblyContext::recordTypeDefinition(
types_[value] = {pInst->words[2], pInst->words[3] != 0,
IdTypeClass::kScalarIntegerType};
} else if (pInst->opcode == spv::Op::OpTypeFloat) {
if (pInst->words.size() != 3)
if ((pInst->words.size() != 3) && (pInst->words.size() != 4))
return diagnostic() << "Invalid OpTypeFloat instruction";
// TODO(kpet) Do we need to record the FP Encoding here?
types_[value] = {pInst->words[2], false, IdTypeClass::kScalarFloatType};
} else {
types_[value] = {0, false, IdTypeClass::kOtherType};

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2024 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 "source/to_string.h"
#include <cassert>
namespace spvtools {
std::string to_string(uint32_t n) {
// This implementation avoids using standard library features that access
// the locale. Using the locale requires taking a mutex which causes
// annoying serialization.
constexpr int max_digits = 10; // max uint has 10 digits
// Contains the resulting digits, with least significant digit in the last
// entry.
char buf[max_digits];
int write_index = max_digits - 1;
if (n == 0) {
buf[write_index] = '0';
} else {
while (n > 0) {
int units = n % 10;
buf[write_index--] = "0123456789"[units];
n = (n - units) / 10;
}
write_index++;
}
assert(write_index >= 0);
return std::string(buf + write_index, max_digits - write_index);
}
} // namespace spvtools

29
3rdparty/spirv-tools/source/to_string.h vendored Normal file
View File

@@ -0,0 +1,29 @@
// Copyright (c) 2024 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 SOURCE_TO_STRING_H_
#define SOURCE_TO_STRING_H_
#include <cstdint>
#include <string>
namespace spvtools {
// Returns the decimal representation of a number as a string,
// without using the locale.
std::string to_string(uint32_t n);
} // namespace spvtools
#endif // SOURCE_TO_STRING_H_

View File

@@ -97,7 +97,7 @@ template <typename T>
size_t CountSetBits(T word) {
static_assert(std::is_integral<T>::value,
"CountSetBits requires integer type");
size_t count = 0;
uint32_t count = 0;
while (word) {
word &= word - 1;
++count;
@@ -181,6 +181,31 @@ T ClearHighBits(T word, size_t num_bits_to_set) {
false);
}
// Returns the value obtained by extracting the |number_of_bits| least
// significant bits from |value|, and sign-extending it to 64-bits.
template <typename T>
T SignExtendValue(T value, uint32_t number_of_bits) {
const uint32_t bit_width = sizeof(value) * 8;
if (number_of_bits == bit_width) return value;
bool is_negative = utils::IsBitAtPositionSet(value, number_of_bits - 1);
if (is_negative) {
value = utils::SetHighBits(value, bit_width - number_of_bits);
} else {
value = utils::ClearHighBits(value, bit_width - number_of_bits);
}
return value;
}
// Returns the value obtained by extracting the |number_of_bits| least
// significant bits from |value|, and zero-extending it to 64-bits.
template <typename T>
T ZeroExtendValue(T value, uint32_t number_of_bits) {
const uint32_t bit_width = sizeof(value) * 8;
if (number_of_bits == bit_width) return value;
return utils::ClearHighBits(value, bit_width - number_of_bits);
}
} // namespace utils
} // namespace spvtools

View File

@@ -117,6 +117,15 @@ spv_result_t ValidateAdjacency(ValidationState_t& _) {
"first instructions in the first block.";
}
break;
case spv::Op::OpUntypedVariableKHR:
if (inst.GetOperandAs<spv::StorageClass>(2) ==
spv::StorageClass::Function &&
adjacency_status != IN_ENTRY_BLOCK) {
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
<< "All OpUntypedVariableKHR instructions in a function must "
"be the first instructions in the first block.";
}
break;
default:
adjacency_status = PHI_AND_VAR_INVALID;
break;

View File

@@ -123,12 +123,14 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
case spv::Decoration::ArrayStride:
if (target->opcode() != spv::Op::OpTypeArray &&
target->opcode() != spv::Op::OpTypeRuntimeArray &&
target->opcode() != spv::Op::OpTypePointer) {
target->opcode() != spv::Op::OpTypePointer &&
target->opcode() != spv::Op::OpTypeUntypedPointerKHR) {
return fail(0) << "must be an array or pointer type";
}
break;
case spv::Decoration::BuiltIn:
if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR &&
!spvOpcodeIsConstant(target->opcode())) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "BuiltIns can only target variables, structure members or "
@@ -139,7 +141,8 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
if (!spvOpcodeIsConstant(target->opcode())) {
return fail(0) << "must be a constant for WorkgroupSize";
}
} else if (target->opcode() != spv::Op::OpVariable) {
} else if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR) {
return fail(0) << "must be a variable";
}
break;
@@ -161,11 +164,12 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
case spv::Decoration::RestrictPointer:
case spv::Decoration::AliasedPointer:
if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR &&
target->opcode() != spv::Op::OpFunctionParameter &&
target->opcode() != spv::Op::OpRawAccessChainNV) {
return fail(0) << "must be a memory object declaration";
}
if (_.GetIdOpcode(target->type_id()) != spv::Op::OpTypePointer) {
if (!_.IsPointerType(target->type_id())) {
return fail(0) << "must be a pointer type";
}
break;
@@ -176,7 +180,8 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
case spv::Decoration::Binding:
case spv::Decoration::DescriptorSet:
case spv::Decoration::InputAttachmentIndex:
if (target->opcode() != spv::Op::OpVariable) {
if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR) {
return fail(0) << "must be a variable";
}
break;

View File

@@ -183,7 +183,44 @@ spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< ": expected Pointer to be of type OpTypePointer";
<< ": expected Pointer to be a pointer type";
}
// If the pointer is an untyped pointer, get the data type elsewhere.
if (data_type == 0) {
switch (opcode) {
case spv::Op::OpAtomicLoad:
case spv::Op::OpAtomicExchange:
case spv::Op::OpAtomicFAddEXT:
case spv::Op::OpAtomicCompareExchange:
case spv::Op::OpAtomicCompareExchangeWeak:
case spv::Op::OpAtomicIIncrement:
case spv::Op::OpAtomicIDecrement:
case spv::Op::OpAtomicIAdd:
case spv::Op::OpAtomicISub:
case spv::Op::OpAtomicSMin:
case spv::Op::OpAtomicUMin:
case spv::Op::OpAtomicFMinEXT:
case spv::Op::OpAtomicSMax:
case spv::Op::OpAtomicUMax:
case spv::Op::OpAtomicFMaxEXT:
case spv::Op::OpAtomicAnd:
case spv::Op::OpAtomicOr:
case spv::Op::OpAtomicXor:
data_type = inst->type_id();
break;
case spv::Op::OpAtomicFlagTestAndSet:
case spv::Op::OpAtomicFlagClear:
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Untyped pointers are not supported by atomic flag "
"instructions";
break;
case spv::Op::OpAtomicStore:
data_type = _.FindDef(inst->GetOperandAs<uint32_t>(3))->type_id();
break;
default:
break;
}
}
// Can't use result_type because OpAtomicStore doesn't have a result

View File

@@ -97,12 +97,16 @@ spv_result_t GetUnderlyingType(ValidationState_t& _,
spv::StorageClass GetStorageClass(const Instruction& inst) {
switch (inst.opcode()) {
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
case spv::Op::OpTypeForwardPointer: {
return spv::StorageClass(inst.word(2));
}
case spv::Op::OpVariable: {
return spv::StorageClass(inst.word(3));
}
case spv::Op::OpUntypedVariableKHR: {
return spv::StorageClass(inst.word(4));
}
case spv::Op::OpGenericCastToPtrExplicit: {
return spv::StorageClass(inst.word(4));
}

View File

@@ -250,7 +250,8 @@ spv_result_t ValidateReturnValue(ValidationState_t& _,
}
if (_.addressing_model() == spv::AddressingModel::Logical &&
spv::Op::OpTypePointer == value_type->opcode() &&
(spv::Op::OpTypePointer == value_type->opcode() ||
spv::Op::OpTypeUntypedPointerKHR == value_type->opcode()) &&
!_.features().variable_pointers && !_.options()->relax_logical_pointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpReturnValue value's type <id> "
@@ -467,13 +468,13 @@ std::string ConstructErrorString(const Construct& construct,
// headed by |target_block| branches to multiple case constructs.
spv_result_t FindCaseFallThrough(
ValidationState_t& _, BasicBlock* target_block, uint32_t* case_fall_through,
const BasicBlock* merge, const std::unordered_set<uint32_t>& case_targets,
Function* function) {
const Construct& switch_construct,
const std::unordered_set<uint32_t>& case_targets) {
const auto* merge = switch_construct.exit_block();
std::vector<BasicBlock*> stack;
stack.push_back(target_block);
std::unordered_set<const BasicBlock*> visited;
bool target_reachable = target_block->structurally_reachable();
int target_depth = function->GetBlockDepth(target_block);
while (!stack.empty()) {
auto block = stack.back();
stack.pop_back();
@@ -491,9 +492,14 @@ spv_result_t FindCaseFallThrough(
} else {
// Exiting the case construct to non-merge block.
if (!case_targets.count(block->id())) {
int depth = function->GetBlockDepth(block);
if ((depth < target_depth) ||
(depth == target_depth && block->is_type(kBlockTypeContinue))) {
// We have already filtered out the following:
// * The switch's merge
// * Other case targets
// * Blocks in the same case construct
//
// So the only remaining valid branches are the structured exits from
// the overall selection construct of the switch.
if (switch_construct.IsStructuredExit(_, block)) {
continue;
}
@@ -525,9 +531,10 @@ spv_result_t FindCaseFallThrough(
}
spv_result_t StructuredSwitchChecks(ValidationState_t& _, Function* function,
const Instruction* switch_inst,
const BasicBlock* header,
const BasicBlock* merge) {
const Construct& switch_construct) {
const auto* header = switch_construct.entry_block();
const auto* merge = switch_construct.exit_block();
const auto* switch_inst = header->terminator();
std::unordered_set<uint32_t> case_targets;
for (uint32_t i = 1; i < switch_inst->operands().size(); i += 2) {
uint32_t target = switch_inst->GetOperandAs<uint32_t>(i);
@@ -545,6 +552,7 @@ spv_result_t StructuredSwitchChecks(ValidationState_t& _, Function* function,
break;
}
}
std::unordered_map<uint32_t, uint32_t> seen_to_fall_through;
for (uint32_t i = 1; i < switch_inst->operands().size(); i += 2) {
uint32_t target = switch_inst->GetOperandAs<uint32_t>(i);
@@ -565,7 +573,7 @@ spv_result_t StructuredSwitchChecks(ValidationState_t& _, Function* function,
}
if (auto error = FindCaseFallThrough(_, target_block, &case_fall_through,
merge, case_targets, function)) {
switch_construct, case_targets)) {
return error;
}
@@ -838,6 +846,9 @@ spv_result_t StructuredControlFlowChecks(
const auto* continue_target = next_inst.block();
if (header->id() != continue_id) {
for (auto pred : *continue_target->predecessors()) {
if (!pred->structurally_reachable()) {
continue;
}
// Ignore back-edges from within the continue construct.
bool is_back_edge = false;
for (auto back_edge : back_edges) {
@@ -862,9 +873,7 @@ spv_result_t StructuredControlFlowChecks(
// Checks rules for case constructs.
if (construct.type() == ConstructType::kSelection &&
header->terminator()->opcode() == spv::Op::OpSwitch) {
const auto terminator = header->terminator();
if (auto error =
StructuredSwitchChecks(_, function, terminator, header, merge)) {
if (auto error = StructuredSwitchChecks(_, function, construct)) {
return error;
}
}

View File

@@ -324,6 +324,7 @@ bool IsTypeNullable(const std::vector<uint32_t>& instruction,
}
return true;
}
case spv::Op::OpTypeUntypedPointerKHR:
case spv::Op::OpTypePointer:
if (spv::StorageClass(instruction[2]) ==
spv::StorageClass::PhysicalStorageBuffer) {

View File

@@ -224,6 +224,7 @@ uint32_t getBaseAlignment(uint32_t member_id, bool roundUp,
break;
}
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
baseAlignment = vstate.pointer_size_and_alignment();
break;
default:
@@ -270,6 +271,7 @@ uint32_t getScalarAlignment(uint32_t type_id, ValidationState_t& vstate) {
return max_member_alignment;
} break;
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
return vstate.pointer_size_and_alignment();
default:
assert(0);
@@ -359,6 +361,7 @@ uint32_t getSize(uint32_t member_id, const LayoutConstraints& inherited,
return offset + getSize(lastMember, constraint, constraints, vstate);
}
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
return vstate.pointer_size_and_alignment();
default:
assert(0);
@@ -432,9 +435,9 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
return ds;
};
// If we are checking physical storage buffer pointers, we may not actually
// have a struct here. Instead, pretend we have a struct with a single member
// at offset 0.
// If we are checking the layout of untyped pointers or physical storage
// buffer pointers, we may not actually have a struct here. Instead, pretend
// we have a struct with a single member at offset 0.
const auto& struct_type = vstate.FindDef(struct_id);
std::vector<uint32_t> members;
if (struct_type->opcode() == spv::Op::OpTypeStruct) {
@@ -451,8 +454,8 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
};
std::vector<MemberOffsetPair> member_offsets;
// With physical storage buffers, we might be checking layouts that do not
// originate from a structure.
// With untyped pointers or physical storage buffers, we might be checking
// layouts that do not originate from a structure.
if (struct_type->opcode() == spv::Op::OpTypeStruct) {
member_offsets.reserve(members.size());
for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
@@ -770,14 +773,19 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
std::unordered_set<spv::BuiltIn> output_var_builtin;
for (auto interface : desc.interfaces) {
Instruction* var_instr = vstate.FindDef(interface);
if (!var_instr || spv::Op::OpVariable != var_instr->opcode()) {
if (!var_instr ||
(spv::Op::OpVariable != var_instr->opcode() &&
spv::Op::OpUntypedVariableKHR != var_instr->opcode())) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "Interfaces passed to OpEntryPoint must be of type "
"OpTypeVariable. Found Op"
<< "Interfaces passed to OpEntryPoint must be variables. "
"Found Op"
<< spvOpcodeString(var_instr->opcode()) << ".";
}
const bool untyped_pointers =
var_instr->opcode() == spv::Op::OpUntypedVariableKHR;
const auto sc_index = 2u;
const spv::StorageClass storage_class =
var_instr->GetOperandAs<spv::StorageClass>(2);
var_instr->GetOperandAs<spv::StorageClass>(sc_index);
if (vstate.version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// Starting in 1.4, OpEntryPoint must list all global variables
// it statically uses and those interfaces must be unique.
@@ -804,12 +812,13 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
}
}
const uint32_t ptr_id = var_instr->word(1);
Instruction* ptr_instr = vstate.FindDef(ptr_id);
// It is guaranteed (by validator ID checks) that ptr_instr is
// OpTypePointer. Word 3 of this instruction is the type being pointed
// to.
const uint32_t type_id = ptr_instr->word(3);
// to. For untyped variables, the pointee type comes from the data type
// operand.
const uint32_t type_id =
untyped_pointers ? var_instr->word(4)
: vstate.FindDef(var_instr->word(1))->word(3);
Instruction* type_instr = vstate.FindDef(type_id);
const bool is_struct =
type_instr && spv::Op::OpTypeStruct == type_instr->opcode();
@@ -874,12 +883,25 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
if (storage_class == spv::StorageClass::Workgroup) {
++num_workgroup_variables;
if (is_struct) {
if (hasDecoration(type_id, spv::Decoration::Block, vstate))
++num_workgroup_variables_with_block;
if (hasDecoration(var_instr->id(), spv::Decoration::Aliased,
vstate))
++num_workgroup_variables_with_aliased;
if (type_instr) {
if (spv::Op::OpTypeStruct == type_instr->opcode()) {
if (hasDecoration(type_id, spv::Decoration::Block, vstate)) {
++num_workgroup_variables_with_block;
} else if (untyped_pointers &&
vstate.HasCapability(spv::Capability::Shader)) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "Untyped workgroup variables in shaders must be "
"block decorated";
}
if (hasDecoration(var_instr->id(), spv::Decoration::Aliased,
vstate))
++num_workgroup_variables_with_aliased;
} else if (untyped_pointers &&
vstate.HasCapability(spv::Capability::Shader)) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "Untyped workgroup variables in shaders must be block "
"decorated structs";
}
}
}
@@ -960,25 +982,33 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
const bool workgroup_blocks_allowed = vstate.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
if (workgroup_blocks_allowed && num_workgroup_variables > 0 &&
if (workgroup_blocks_allowed &&
!vstate.HasCapability(spv::Capability::UntypedPointersKHR) &&
num_workgroup_variables > 0 &&
num_workgroup_variables_with_block > 0) {
if (num_workgroup_variables != num_workgroup_variables_with_block) {
return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
return vstate.diag(SPV_ERROR_INVALID_BINARY,
vstate.FindDef(entry_point))
<< "When declaring WorkgroupMemoryExplicitLayoutKHR, "
"either all or none of the Workgroup Storage Class variables "
"either all or none of the Workgroup Storage Class "
"variables "
"in the entry point interface must point to struct types "
"decorated with Block. Entry point id "
"decorated with Block (unless the "
"UntypedPointersKHR capability is declared). "
"Entry point id "
<< entry_point << " does not meet this requirement.";
}
if (num_workgroup_variables_with_block > 1 &&
num_workgroup_variables_with_block !=
num_workgroup_variables_with_aliased) {
return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
return vstate.diag(SPV_ERROR_INVALID_BINARY,
vstate.FindDef(entry_point))
<< "When declaring WorkgroupMemoryExplicitLayoutKHR, "
"if more than one Workgroup Storage Class variable in "
"the entry point interface point to a type decorated "
"with Block, all of them must be decorated with Aliased. "
"Entry point id "
"with Block, all of them must be decorated with Aliased "
"(unless the UntypedPointerWorkgroupKHR capability is "
"declared). Entry point id "
<< entry_point << " does not meet this requirement.";
}
} else if (!workgroup_blocks_allowed &&
@@ -1084,11 +1114,17 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
const auto& words = inst.words();
auto type_id = inst.type_id();
const Instruction* type_inst = vstate.FindDef(type_id);
if (spv::Op::OpVariable == inst.opcode()) {
bool scalar_block_layout = false;
MemberConstraints constraints;
if (spv::Op::OpVariable == inst.opcode() ||
spv::Op::OpUntypedVariableKHR == inst.opcode()) {
const bool untyped_pointer =
inst.opcode() == spv::Op::OpUntypedVariableKHR;
const auto var_id = inst.id();
// For storage class / decoration combinations, see Vulkan 14.5.4 "Offset
// and Stride Assignment".
const auto storageClass = inst.GetOperandAs<spv::StorageClass>(2);
const auto storageClassVal = words[3];
const auto storageClass = spv::StorageClass(storageClassVal);
const bool uniform = storageClass == spv::StorageClass::Uniform;
const bool uniform_constant =
storageClass == spv::StorageClass::UniformConstant;
@@ -1167,20 +1203,24 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
if (uniform || push_constant || storage_buffer || phys_storage_buffer ||
workgroup) {
const auto ptrInst = vstate.FindDef(words[1]);
assert(spv::Op::OpTypePointer == ptrInst->opcode());
auto id = ptrInst->words()[3];
auto id_inst = vstate.FindDef(id);
// Jump through one level of arraying.
if (!workgroup && (id_inst->opcode() == spv::Op::OpTypeArray ||
id_inst->opcode() == spv::Op::OpTypeRuntimeArray)) {
id = id_inst->GetOperandAs<uint32_t>(1u);
id_inst = vstate.FindDef(id);
assert(spv::Op::OpTypePointer == ptrInst->opcode() ||
spv::Op::OpTypeUntypedPointerKHR == ptrInst->opcode());
auto id = untyped_pointer ? (words.size() > 4 ? words[4] : 0)
: ptrInst->words()[3];
if (id != 0) {
auto id_inst = vstate.FindDef(id);
// Jump through one level of arraying.
if (!workgroup &&
(id_inst->opcode() == spv::Op::OpTypeArray ||
id_inst->opcode() == spv::Op::OpTypeRuntimeArray)) {
id = id_inst->GetOperandAs<uint32_t>(1u);
id_inst = vstate.FindDef(id);
}
// Struct requirement is checked on variables so just move on here.
if (spv::Op::OpTypeStruct != id_inst->opcode()) continue;
ComputeMemberConstraintsForStruct(&constraints, id,
LayoutConstraints(), vstate);
}
// Struct requirement is checked on variables so just move on here.
if (spv::Op::OpTypeStruct != id_inst->opcode()) continue;
MemberConstraints constraints;
ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
vstate);
// Prepare for messages
const char* sc_str =
uniform ? "Uniform"
@@ -1250,88 +1290,91 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
}
}
for (const auto& dec : vstate.id_decorations(id)) {
const bool blockDeco = spv::Decoration::Block == dec.dec_type();
const bool bufferDeco =
spv::Decoration::BufferBlock == dec.dec_type();
const bool blockRules = uniform && blockDeco;
const bool bufferRules =
(uniform && bufferDeco) ||
((push_constant || storage_buffer ||
phys_storage_buffer || workgroup) && blockDeco);
if (uniform && blockDeco) {
vstate.RegisterPointerToUniformBlock(ptrInst->id());
vstate.RegisterStructForUniformBlock(id);
}
if ((uniform && bufferDeco) ||
((storage_buffer || phys_storage_buffer) && blockDeco)) {
vstate.RegisterPointerToStorageBuffer(ptrInst->id());
vstate.RegisterStructForStorageBuffer(id);
}
if (blockRules || bufferRules) {
const char* deco_str = blockDeco ? "Block" : "BufferBlock";
spv_result_t recursive_status = SPV_SUCCESS;
const bool scalar_block_layout = workgroup ?
vstate.options()->workgroup_scalar_block_layout :
vstate.options()->scalar_block_layout;
if (isMissingOffsetInStruct(id, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with Offset "
"decorations.";
if (id != 0) {
for (const auto& dec : vstate.id_decorations(id)) {
const bool blockDeco = spv::Decoration::Block == dec.dec_type();
const bool bufferDeco =
spv::Decoration::BufferBlock == dec.dec_type();
const bool blockRules = uniform && blockDeco;
const bool bufferRules = (uniform && bufferDeco) ||
((push_constant || storage_buffer ||
phys_storage_buffer || workgroup) &&
blockDeco);
if (uniform && blockDeco) {
vstate.RegisterPointerToUniformBlock(ptrInst->id());
vstate.RegisterStructForUniformBlock(id);
}
if ((uniform && bufferDeco) ||
((storage_buffer || phys_storage_buffer) && blockDeco)) {
vstate.RegisterPointerToStorageBuffer(ptrInst->id());
vstate.RegisterStructForStorageBuffer(id);
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::ArrayStride;
},
spv::Op::OpTypeArray, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with ArrayStride "
"decorations.";
}
if (blockRules || bufferRules) {
const char* deco_str = blockDeco ? "Block" : "BufferBlock";
spv_result_t recursive_status = SPV_SUCCESS;
scalar_block_layout =
workgroup ? vstate.options()->workgroup_scalar_block_layout
: vstate.options()->scalar_block_layout;
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::MatrixStride;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with MatrixStride "
"decorations.";
}
if (isMissingOffsetInStruct(id, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with Offset "
"decorations.";
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::RowMajor ||
d == spv::Decoration::ColMajor;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with RowMajor or "
"ColMajor decorations.";
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::ArrayStride;
},
spv::Op::OpTypeArray, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with ArrayStride "
"decorations.";
}
if (spvIsVulkanEnv(vstate.context()->target_env)) {
if (blockRules && (SPV_SUCCESS != (recursive_status = checkLayout(
id, sc_str, deco_str, true,
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::MatrixStride;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with MatrixStride "
"decorations.";
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::RowMajor ||
d == spv::Decoration::ColMajor;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with RowMajor or "
"ColMajor decorations.";
}
if (spvIsVulkanEnv(vstate.context()->target_env)) {
if (blockRules &&
(SPV_SUCCESS !=
(recursive_status = checkLayout(id, sc_str, deco_str, true,
scalar_block_layout, 0,
constraints, vstate)))) {
return recursive_status;
} else if (bufferRules &&
(SPV_SUCCESS !=
(recursive_status = checkLayout(
id, sc_str, deco_str, false, scalar_block_layout,
0, constraints, vstate)))) {
return recursive_status;
return recursive_status;
} else if (bufferRules &&
(SPV_SUCCESS != (recursive_status = checkLayout(
id, sc_str, deco_str, false,
scalar_block_layout, 0,
constraints, vstate)))) {
return recursive_status;
}
}
}
}
@@ -1340,19 +1383,97 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
} else if (type_inst && type_inst->opcode() == spv::Op::OpTypePointer &&
type_inst->GetOperandAs<spv::StorageClass>(1u) ==
spv::StorageClass::PhysicalStorageBuffer) {
const bool scalar_block_layout = vstate.options()->scalar_block_layout;
MemberConstraints constraints;
const bool buffer = true;
const auto data_type_id = type_inst->GetOperandAs<uint32_t>(2u);
const auto* data_type_inst = vstate.FindDef(data_type_id);
const auto pointee_type_id = type_inst->GetOperandAs<uint32_t>(2u);
const auto* data_type_inst = vstate.FindDef(pointee_type_id);
scalar_block_layout = vstate.options()->scalar_block_layout;
if (data_type_inst->opcode() == spv::Op::OpTypeStruct) {
ComputeMemberConstraintsForStruct(&constraints, pointee_type_id,
LayoutConstraints(), vstate);
}
if (auto res = checkLayout(pointee_type_id, "PhysicalStorageBuffer",
"Block", !buffer, scalar_block_layout, 0,
constraints, vstate)) {
return res;
}
} else if (vstate.HasCapability(spv::Capability::UntypedPointersKHR) &&
spvIsVulkanEnv(vstate.context()->target_env)) {
// Untyped variables are checked above. Here we check that instructions
// using an untyped pointer have a valid layout.
uint32_t ptr_ty_id = 0;
uint32_t data_type_id = 0;
switch (inst.opcode()) {
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
ptr_ty_id = inst.type_id();
data_type_id = inst.GetOperandAs<uint32_t>(2);
break;
case spv::Op::OpLoad:
if (vstate.GetIdOpcode(vstate.GetOperandTypeId(&inst, 2)) ==
spv::Op::OpTypeUntypedPointerKHR) {
const auto ptr_id = inst.GetOperandAs<uint32_t>(2);
ptr_ty_id = vstate.FindDef(ptr_id)->type_id();
data_type_id = inst.type_id();
}
break;
case spv::Op::OpStore:
if (vstate.GetIdOpcode(vstate.GetOperandTypeId(&inst, 0)) ==
spv::Op::OpTypeUntypedPointerKHR) {
const auto ptr_id = inst.GetOperandAs<uint32_t>(0);
ptr_ty_id = vstate.FindDef(ptr_id)->type_id();
data_type_id = vstate.GetOperandTypeId(&inst, 1);
}
break;
case spv::Op::OpUntypedArrayLengthKHR:
ptr_ty_id = vstate.FindDef(inst.GetOperandAs<uint32_t>(3))->type_id();
data_type_id = inst.GetOperandAs<uint32_t>(2);
break;
default:
break;
}
if (ptr_ty_id == 0 || data_type_id == 0) {
// Not an untyped pointer.
continue;
}
const auto sc =
vstate.FindDef(ptr_ty_id)->GetOperandAs<spv::StorageClass>(1);
const char* sc_str =
sc == spv::StorageClass::Uniform
? "Uniform"
: (sc == spv::StorageClass::PushConstant
? "PushConstant"
: (sc == spv::StorageClass::Workgroup ? "Workgroup"
: "StorageBuffer"));
const auto data_type = vstate.FindDef(data_type_id);
scalar_block_layout =
sc == spv::StorageClass::Workgroup
? vstate.options()->workgroup_scalar_block_layout
: vstate.options()->scalar_block_layout;
// Assume uniform storage class uses block rules unless we see a
// BufferBlock decorated struct in the data type.
bool bufferRules = sc == spv::StorageClass::Uniform ? false : true;
if (data_type->opcode() == spv::Op::OpTypeStruct) {
if (sc == spv::StorageClass::Uniform) {
bufferRules =
vstate.HasDecoration(data_type_id, spv::Decoration::BufferBlock);
}
ComputeMemberConstraintsForStruct(&constraints, data_type_id,
LayoutConstraints(), vstate);
}
if (auto res = checkLayout(data_type_id, "PhysicalStorageBuffer", "Block",
!buffer, scalar_block_layout, 0, constraints,
vstate)) {
return res;
const char* deco_str =
bufferRules
? (sc == spv::StorageClass::Uniform ? "BufferBlock" : "Block")
: "Block";
if (auto result =
checkLayout(data_type_id, sc_str, deco_str, !bufferRules,
scalar_block_layout, 0, constraints, vstate)) {
return result;
}
}
}
@@ -1585,15 +1706,19 @@ spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
const auto opcode = inst.opcode();
const auto type_id = inst.type_id();
if (opcode != spv::Op::OpVariable &&
opcode != spv::Op::OpUntypedVariableKHR &&
opcode != spv::Op::OpFunctionParameter &&
opcode != spv::Op::OpRawAccessChainNV) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "Target of NonWritable decoration must be a memory object "
"declaration (a variable or a function parameter)";
}
const auto var_storage_class = opcode == spv::Op::OpVariable
? inst.GetOperandAs<spv::StorageClass>(2)
: spv::StorageClass::Max;
const auto var_storage_class =
opcode == spv::Op::OpVariable
? inst.GetOperandAs<spv::StorageClass>(2)
: opcode == spv::Op::OpUntypedVariableKHR
? inst.GetOperandAs<spv::StorageClass>(3)
: spv::StorageClass::Max;
if ((var_storage_class == spv::StorageClass::Function ||
var_storage_class == spv::StorageClass::Private) &&
vstate.features().nonwritable_var_in_function_or_private) {
@@ -1751,7 +1876,7 @@ spv_result_t CheckComponentDecoration(ValidationState_t& vstate,
if (spvIsVulkanEnv(vstate.context()->target_env)) {
// Strip the array, if present.
if (vstate.GetIdOpcode(type_id) == spv::Op::OpTypeArray) {
while (vstate.GetIdOpcode(type_id) == spv::Op::OpTypeArray) {
type_id = vstate.FindDef(type_id)->word(2u);
}

View File

@@ -60,12 +60,14 @@ spv_result_t DerivativesPass(ValidationState_t& _, const Instruction* inst) {
->RegisterExecutionModelLimitation([opcode](spv::ExecutionModel model,
std::string* message) {
if (model != spv::ExecutionModel::Fragment &&
model != spv::ExecutionModel::GLCompute) {
model != spv::ExecutionModel::GLCompute &&
model != spv::ExecutionModel::MeshEXT &&
model != spv::ExecutionModel::TaskEXT) {
if (message) {
*message =
std::string(
"Derivative instructions require Fragment or GLCompute "
"execution model: ") +
"Derivative instructions require Fragment, GLCompute, "
"MeshEXT or TaskEXT execution model: ") +
spvOpcodeString(opcode);
}
return false;
@@ -79,19 +81,23 @@ spv_result_t DerivativesPass(ValidationState_t& _, const Instruction* inst) {
const auto* models = state.GetExecutionModels(entry_point->id());
const auto* modes = state.GetExecutionModes(entry_point->id());
if (models &&
models->find(spv::ExecutionModel::GLCompute) != models->end() &&
(models->find(spv::ExecutionModel::GLCompute) !=
models->end() ||
models->find(spv::ExecutionModel::MeshEXT) != models->end() ||
models->find(spv::ExecutionModel::TaskEXT) != models->end()) &&
(!modes ||
(modes->find(spv::ExecutionMode::DerivativeGroupLinearNV) ==
(modes->find(spv::ExecutionMode::DerivativeGroupLinearKHR) ==
modes->end() &&
modes->find(spv::ExecutionMode::DerivativeGroupQuadsNV) ==
modes->find(spv::ExecutionMode::DerivativeGroupQuadsKHR) ==
modes->end()))) {
if (message) {
*message = std::string(
"Derivative instructions require "
"DerivativeGroupQuadsNV "
"or DerivativeGroupLinearNV execution mode for "
"GLCompute execution model: ") +
spvOpcodeString(opcode);
*message =
std::string(
"Derivative instructions require "
"DerivativeGroupQuadsKHR "
"or DerivativeGroupLinearKHR execution mode for "
"GLCompute, MeshEXT or TaskEXT execution model: ") +
spvOpcodeString(opcode);
}
return false;
}

View File

@@ -1980,7 +1980,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
"CrossWorkgroup, Workgroup or Function";
}
if (result_type != p_data_type) {
if (!_.ContainsUntypedPointer(p_type) && result_type != p_data_type) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected data type of the pointer to be equal to Result "
@@ -2042,15 +2042,17 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
"CrossWorkgroup, Workgroup or Function";
}
if (!_.IsIntScalarOrVectorType(p_data_type) ||
_.GetBitWidth(p_data_type) != 32) {
if ((!_.IsIntScalarOrVectorType(p_data_type) ||
_.GetBitWidth(p_data_type) != 32) &&
!_.ContainsUntypedPointer(p_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected data type of the pointer to be a 32-bit int "
"scalar or vector type";
}
if (_.GetDimension(p_data_type) != num_components) {
if (!_.ContainsUntypedPointer(p_type) &&
_.GetDimension(p_data_type) != num_components) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected data type of the pointer to have the same number "
@@ -2701,8 +2703,9 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
"Generic, CrossWorkgroup, Workgroup or Function";
}
if (!_.IsFloatScalarType(p_data_type) ||
_.GetBitWidth(p_data_type) != 16) {
if ((!_.IsFloatScalarType(p_data_type) ||
_.GetBitWidth(p_data_type) != 16) &&
!_.ContainsUntypedPointer(p_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected operand P data type to be 16-bit float scalar";
@@ -2763,8 +2766,9 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
"Generic, CrossWorkgroup, Workgroup or Function";
}
if (!_.IsFloatScalarType(p_data_type) ||
_.GetBitWidth(p_data_type) != 16) {
if ((!_.IsFloatScalarType(p_data_type) ||
_.GetBitWidth(p_data_type) != 16) &&
!_.ContainsUntypedPointer(p_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected operand P data type to be 16-bit float scalar";
@@ -2855,8 +2859,9 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
"CrossWorkgroup, Workgroup or Function";
}
if (!_.IsFloatScalarType(p_data_type) ||
_.GetBitWidth(p_data_type) != 16) {
if ((!_.IsFloatScalarType(p_data_type) ||
_.GetBitWidth(p_data_type) != 16) &&
!_.ContainsUntypedPointer(p_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected operand P data type to be 16-bit float scalar";
@@ -2962,14 +2967,41 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
<< "expected operand Format to be a pointer";
}
if (format_storage_class != spv::StorageClass::UniformConstant) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected Format storage class to be UniformConstant";
if (_.HasExtension(
Extension::kSPV_EXT_relaxed_printf_string_address_space)) {
if (format_storage_class != spv::StorageClass::UniformConstant &&
// Extension SPV_EXT_relaxed_printf_string_address_space allows
// format strings in Global, Local, Private and Generic address
// spaces
// Global
format_storage_class != spv::StorageClass::CrossWorkgroup &&
// Local
format_storage_class != spv::StorageClass::Workgroup &&
// Private
format_storage_class != spv::StorageClass::Function &&
// Generic
format_storage_class != spv::StorageClass::Generic) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected Format storage class to be UniformConstant, "
"Crossworkgroup, Workgroup, Function, or Generic";
}
} else {
if (format_storage_class != spv::StorageClass::UniformConstant) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected Format storage class to be UniformConstant";
}
}
if (!_.IsIntScalarType(format_data_type) ||
_.GetBitWidth(format_data_type) != 8) {
// If pointer points to an array, get the type of an element
if (_.IsIntArrayType(format_data_type))
format_data_type = _.GetComponentType(format_data_type);
if ((!_.IsIntScalarType(format_data_type) ||
_.GetBitWidth(format_data_type) != 8) &&
!_.ContainsUntypedPointer(format_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected Format data type to be 8-bit int";

View File

@@ -156,7 +156,9 @@ spv_result_t ValidateFunctionParameter(ValidationState_t& _,
param_nonarray_type_id =
_.FindDef(param_nonarray_type_id)->GetOperandAs<uint32_t>(1u);
}
if (_.GetIdOpcode(param_nonarray_type_id) == spv::Op::OpTypePointer) {
if (_.GetIdOpcode(param_nonarray_type_id) == spv::Op::OpTypePointer ||
_.GetIdOpcode(param_nonarray_type_id) ==
spv::Op::OpTypeUntypedPointerKHR) {
auto param_nonarray_type = _.FindDef(param_nonarray_type_id);
if (param_nonarray_type->GetOperandAs<spv::StorageClass>(1u) ==
spv::StorageClass::PhysicalStorageBuffer) {
@@ -185,7 +187,7 @@ spv_result_t ValidateFunctionParameter(ValidationState_t& _,
<< ": can't specify both Aliased and Restrict for "
"PhysicalStorageBuffer pointer.";
}
} else {
} else if (param_nonarray_type->opcode() == spv::Op::OpTypePointer) {
const auto pointee_type_id =
param_nonarray_type->GetOperandAs<uint32_t>(2);
const auto pointee_type = _.FindDef(pointee_type_id);
@@ -288,7 +290,8 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
}
if (_.addressing_model() == spv::AddressingModel::Logical) {
if (parameter_type->opcode() == spv::Op::OpTypePointer &&
if ((parameter_type->opcode() == spv::Op::OpTypePointer ||
parameter_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) &&
!_.options()->relax_logical_pointer) {
spv::StorageClass sc =
parameter_type->GetOperandAs<spv::StorageClass>(1u);
@@ -317,9 +320,11 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
// Validate memory object declaration requirements.
if (argument->opcode() != spv::Op::OpVariable &&
argument->opcode() != spv::Op::OpUntypedVariableKHR &&
argument->opcode() != spv::Op::OpFunctionParameter) {
const bool ssbo_vptr = _.features().variable_pointers &&
sc == spv::StorageClass::StorageBuffer;
const bool ssbo_vptr =
_.HasCapability(spv::Capability::VariablePointersStorageBuffer) &&
sc == spv::StorageClass::StorageBuffer;
const bool wg_vptr =
_.HasCapability(spv::Capability::VariablePointers) &&
sc == spv::StorageClass::Workgroup;

View File

@@ -165,6 +165,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
!spvOpcodeIsDecoration(opcode) && opcode != spv::Op::OpFunction &&
opcode != spv::Op::OpCooperativeMatrixLengthNV &&
opcode != spv::Op::OpCooperativeMatrixLengthKHR &&
!spvOpcodeGeneratesUntypedPointer(opcode) &&
opcode != spv::Op::OpUntypedArrayLengthKHR &&
!(opcode == spv::Op::OpSpecConstantOp &&
(spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthNV ||
@@ -185,6 +187,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
opcode != spv::Op::OpFunction &&
opcode != spv::Op::OpCooperativeMatrixLengthNV &&
opcode != spv::Op::OpCooperativeMatrixLengthKHR &&
!spvOpcodeGeneratesUntypedPointer(opcode) &&
opcode != spv::Op::OpUntypedArrayLengthKHR &&
!(opcode == spv::Op::OpSpecConstantOp &&
(spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthNV ||

View File

@@ -1017,19 +1017,31 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
<< "Expected Image to be of type OpTypeImage.";
}
if (type_inst->GetOperandAs<uint32_t>(1) != image_type) {
// return _.diag(SPV_ERROR_INVALID_DATA, inst)
// << "Expected Image to have the same type as Result Type Image";
}
ImageTypeInfo info;
if (!GetImageTypeInfo(_, image_type, &info)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Corrupt image type definition";
}
// TODO(atgoo@github.com) Check compatibility of result type and received
// image.
// Image operands must match except for depth.
auto sampled_image_id = type_inst->GetOperandAs<uint32_t>(1);
if (sampled_image_id != image_type) {
ImageTypeInfo sampled_info;
if (!GetImageTypeInfo(_, sampled_image_id, &sampled_info)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Corrupt image type definition";
}
if (info.sampled_type != sampled_info.sampled_type ||
info.dim != sampled_info.dim || info.arrayed != sampled_info.arrayed ||
info.multisampled != sampled_info.multisampled ||
info.sampled != sampled_info.sampled ||
info.format != sampled_info.format ||
info.access_qualifier != sampled_info.access_qualifier) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Image operands must match result image operands except for "
"depth";
}
}
if (spvIsVulkanEnv(_.context()->target_env)) {
if (info.sampled != 1) {
@@ -1121,28 +1133,33 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
spv_result_t ValidateImageTexelPointer(ValidationState_t& _,
const Instruction* inst) {
const auto result_type = _.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypePointer) {
if (result_type->opcode() != spv::Op::OpTypePointer &&
result_type->opcode() != spv::Op::OpTypeUntypedPointerKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be OpTypePointer";
<< "Expected Result Type to be a pointer";
}
const auto storage_class = result_type->GetOperandAs<spv::StorageClass>(1);
if (storage_class != spv::StorageClass::Image) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be OpTypePointer whose Storage Class "
<< "Expected Result Type to be a pointer whose Storage Class "
"operand is Image";
}
const auto ptr_type = result_type->GetOperandAs<uint32_t>(2);
const auto ptr_opcode = _.GetIdOpcode(ptr_type);
if (ptr_opcode != spv::Op::OpTypeInt && ptr_opcode != spv::Op::OpTypeFloat &&
ptr_opcode != spv::Op::OpTypeVoid &&
!(ptr_opcode == spv::Op::OpTypeVector &&
_.HasCapability(spv::Capability::AtomicFloat16VectorNV) &&
_.IsFloat16Vector2Or4Type(ptr_type))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be OpTypePointer whose Type operand "
"must be a scalar numerical type or OpTypeVoid";
uint32_t ptr_type = 0;
if (result_type->opcode() == spv::Op::OpTypePointer) {
ptr_type = result_type->GetOperandAs<uint32_t>(2);
const auto ptr_opcode = _.GetIdOpcode(ptr_type);
if (ptr_opcode != spv::Op::OpTypeInt &&
ptr_opcode != spv::Op::OpTypeFloat &&
ptr_opcode != spv::Op::OpTypeVoid &&
!(ptr_opcode == spv::Op::OpTypeVector &&
_.HasCapability(spv::Capability::AtomicFloat16VectorNV) &&
_.IsFloat16Vector2Or4Type(ptr_type))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be a pointer whose Type operand "
"must be a scalar numerical type or OpTypeVoid";
}
}
const auto image_ptr = _.FindDef(_.GetOperandTypeId(inst, 2));
@@ -1163,7 +1180,8 @@ spv_result_t ValidateImageTexelPointer(ValidationState_t& _,
<< "Corrupt image type definition";
}
if (info.sampled_type != ptr_type &&
if (result_type->opcode() == spv::Op::OpTypePointer &&
info.sampled_type != ptr_type &&
!(_.HasCapability(spv::Capability::AtomicFloat16VectorNV) &&
_.IsFloat16Vector2Or4Type(ptr_type) &&
_.GetIdOpcode(info.sampled_type) == spv::Op::OpTypeFloat &&
@@ -2008,11 +2026,13 @@ spv_result_t ValidateImageQueryLod(ValidationState_t& _,
->RegisterExecutionModelLimitation(
[&](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::Fragment &&
model != spv::ExecutionModel::GLCompute) {
model != spv::ExecutionModel::GLCompute &&
model != spv::ExecutionModel::MeshEXT &&
model != spv::ExecutionModel::TaskEXT) {
if (message) {
*message = std::string(
"OpImageQueryLod requires Fragment or GLCompute execution "
"model");
"OpImageQueryLod requires Fragment, GLCompute, MeshEXT or "
"TaskEXT execution model");
}
return false;
}
@@ -2024,16 +2044,20 @@ spv_result_t ValidateImageQueryLod(ValidationState_t& _,
std::string* message) {
const auto* models = state.GetExecutionModels(entry_point->id());
const auto* modes = state.GetExecutionModes(entry_point->id());
if (models->find(spv::ExecutionModel::GLCompute) != models->end() &&
modes->find(spv::ExecutionMode::DerivativeGroupLinearNV) ==
modes->end() &&
modes->find(spv::ExecutionMode::DerivativeGroupQuadsNV) ==
modes->end()) {
if (models &&
(models->find(spv::ExecutionModel::GLCompute) != models->end() ||
models->find(spv::ExecutionModel::MeshEXT) != models->end() ||
models->find(spv::ExecutionModel::TaskEXT) != models->end()) &&
(!modes ||
(modes->find(spv::ExecutionMode::DerivativeGroupLinearKHR) ==
modes->end() &&
modes->find(spv::ExecutionMode::DerivativeGroupQuadsKHR) ==
modes->end()))) {
if (message) {
*message = std::string(
"OpImageQueryLod requires DerivativeGroupQuadsNV "
"or DerivativeGroupLinearNV execution mode for GLCompute "
"execution model");
"OpImageQueryLod requires DerivativeGroupQuadsKHR "
"or DerivativeGroupLinearKHR execution mode for GLCompute, "
"MeshEXT or TaskEXT execution model");
}
return false;
}
@@ -2302,12 +2326,14 @@ spv_result_t ImagePass(ValidationState_t& _, const Instruction* inst) {
->RegisterExecutionModelLimitation([opcode](spv::ExecutionModel model,
std::string* message) {
if (model != spv::ExecutionModel::Fragment &&
model != spv::ExecutionModel::GLCompute) {
model != spv::ExecutionModel::GLCompute &&
model != spv::ExecutionModel::MeshEXT &&
model != spv::ExecutionModel::TaskEXT) {
if (message) {
*message =
std::string(
"ImplicitLod instructions require Fragment or GLCompute "
"execution model: ") +
"ImplicitLod instructions require Fragment, GLCompute, "
"MeshEXT or TaskEXT execution model: ") +
spvOpcodeString(opcode);
}
return false;
@@ -2321,19 +2347,22 @@ spv_result_t ImagePass(ValidationState_t& _, const Instruction* inst) {
const auto* models = state.GetExecutionModels(entry_point->id());
const auto* modes = state.GetExecutionModes(entry_point->id());
if (models &&
models->find(spv::ExecutionModel::GLCompute) != models->end() &&
(models->find(spv::ExecutionModel::GLCompute) != models->end() ||
models->find(spv::ExecutionModel::MeshEXT) != models->end() ||
models->find(spv::ExecutionModel::TaskEXT) != models->end()) &&
(!modes ||
(modes->find(spv::ExecutionMode::DerivativeGroupLinearNV) ==
(modes->find(spv::ExecutionMode::DerivativeGroupLinearKHR) ==
modes->end() &&
modes->find(spv::ExecutionMode::DerivativeGroupQuadsNV) ==
modes->find(spv::ExecutionMode::DerivativeGroupQuadsKHR) ==
modes->end()))) {
if (message) {
*message =
std::string(
"ImplicitLod instructions require DerivativeGroupQuadsNV "
"or DerivativeGroupLinearNV execution mode for GLCompute "
"execution model: ") +
spvOpcodeString(opcode);
*message = std::string(
"ImplicitLod instructions require "
"DerivativeGroupQuadsKHR "
"or DerivativeGroupLinearKHR execution mode for "
"GLCompute, "
"MeshEXT or TaskEXT execution model: ") +
spvOpcodeString(opcode);
}
return false;
}

View File

@@ -34,11 +34,13 @@ const uint32_t kMaxLocations = 4096 * 4;
bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
if (is_spv_1_4) {
// Starting in SPIR-V 1.4, all global variables are interface variables.
return inst->opcode() == spv::Op::OpVariable &&
return (inst->opcode() == spv::Op::OpVariable ||
inst->opcode() == spv::Op::OpUntypedVariableKHR) &&
inst->GetOperandAs<spv::StorageClass>(2u) !=
spv::StorageClass::Function;
} else {
return inst->opcode() == spv::Op::OpVariable &&
return (inst->opcode() == spv::Op::OpVariable ||
inst->opcode() == spv::Op::OpUntypedVariableKHR) &&
(inst->GetOperandAs<spv::StorageClass>(2u) ==
spv::StorageClass::Input ||
inst->GetOperandAs<spv::StorageClass>(2u) ==
@@ -242,8 +244,9 @@ spv_result_t GetLocationsForVariable(
std::unordered_set<uint32_t>* output_index1_locations) {
const bool is_fragment = entry_point->GetOperandAs<spv::ExecutionModel>(0) ==
spv::ExecutionModel::Fragment;
const bool is_output =
variable->GetOperandAs<spv::StorageClass>(2) == spv::StorageClass::Output;
const auto sc_index = 2u;
const bool is_output = variable->GetOperandAs<spv::StorageClass>(sc_index) ==
spv::StorageClass::Output;
auto ptr_type_id = variable->GetOperandAs<uint32_t>(0);
auto ptr_type = _.FindDef(ptr_type_id);
auto type_id = ptr_type->GetOperandAs<uint32_t>(2);
@@ -525,7 +528,9 @@ spv_result_t ValidateLocations(ValidationState_t& _,
for (uint32_t i = 3; i < entry_point->operands().size(); ++i) {
auto interface_id = entry_point->GetOperandAs<uint32_t>(i);
auto interface_var = _.FindDef(interface_id);
auto storage_class = interface_var->GetOperandAs<spv::StorageClass>(2);
const auto sc_index = 2u;
auto storage_class =
interface_var->GetOperandAs<spv::StorageClass>(sc_index);
if (storage_class != spv::StorageClass::Input &&
storage_class != spv::StorageClass::Output) {
continue;

View File

@@ -159,9 +159,11 @@ spv_result_t LogicalsPass(ValidationState_t& _, const Instruction* inst) {
const spv::Op type_opcode = type_inst->opcode();
switch (type_opcode) {
case spv::Op::OpTypeUntypedPointerKHR:
case spv::Op::OpTypePointer: {
if (_.addressing_model() == spv::AddressingModel::Logical &&
!_.features().variable_pointers)
!_.HasCapability(
spv::Capability::VariablePointersStorageBuffer))
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using pointers with OpSelect requires capability "
<< "VariablePointers or VariablePointersStorageBuffer";

View File

@@ -407,42 +407,80 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
}
spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
const bool untyped_pointer = inst->opcode() == spv::Op::OpUntypedVariableKHR;
auto result_type = _.FindDef(inst->type_id());
if (!result_type || result_type->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable Result Type <id> " << _.getIdName(inst->type_id())
<< " is not a pointer type.";
if (untyped_pointer) {
if (!result_type ||
result_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result type must be an untyped pointer";
} else {
if (!result_type || result_type->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable Result Type <id> " << _.getIdName(inst->type_id())
<< " is not a pointer type.";
}
}
const auto type_index = 2;
const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
auto value_type = _.FindDef(value_id);
const auto storage_class_index = 2u;
auto storage_class =
inst->GetOperandAs<spv::StorageClass>(storage_class_index);
uint32_t value_id = 0;
if (untyped_pointer) {
const auto has_data_type = 3u < inst->operands().size();
if (has_data_type) {
value_id = inst->GetOperandAs<uint32_t>(3u);
auto data_type = _.FindDef(value_id);
if (!data_type || !spvOpcodeGeneratesType(data_type->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Data type must be a type instruction";
}
} else {
if (storage_class == spv::StorageClass::Function ||
storage_class == spv::StorageClass::Private ||
storage_class == spv::StorageClass::Workgroup) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Data type must be specified for Function, Private, and "
"Workgroup storage classes";
}
if (spvIsVulkanEnv(_.context()->target_env)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Vulkan requires that data type be specified";
}
}
}
const auto initializer_index = 3;
const auto storage_class_index = 2;
// For OpVariable the data type comes from pointee type of the result type,
// while for OpUntypedVariableKHR the data type comes from the operand.
if (!untyped_pointer) {
value_id = result_type->GetOperandAs<uint32_t>(2);
}
auto value_type = value_id == 0 ? nullptr : _.FindDef(value_id);
const auto initializer_index = untyped_pointer ? 4u : 3u;
if (initializer_index < inst->operands().size()) {
const auto initializer_id = inst->GetOperandAs<uint32_t>(initializer_index);
const auto initializer = _.FindDef(initializer_id);
const auto is_module_scope_var =
initializer && (initializer->opcode() == spv::Op::OpVariable) &&
initializer &&
(initializer->opcode() == spv::Op::OpVariable ||
initializer->opcode() == spv::Op::OpUntypedVariableKHR) &&
(initializer->GetOperandAs<spv::StorageClass>(storage_class_index) !=
spv::StorageClass::Function);
const auto is_constant =
initializer && spvOpcodeIsConstant(initializer->opcode());
if (!initializer || !(is_constant || is_module_scope_var)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable Initializer <id> " << _.getIdName(initializer_id)
<< "Variable Initializer <id> " << _.getIdName(initializer_id)
<< " is not a constant or module-scope variable.";
}
if (initializer->type_id() != value_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Initializer type must match the type pointed to by the Result "
"Type";
<< "Initializer type must match the data type";
}
}
auto storage_class =
inst->GetOperandAs<spv::StorageClass>(storage_class_index);
if (storage_class != spv::StorageClass::Workgroup &&
storage_class != spv::StorageClass::CrossWorkgroup &&
storage_class != spv::StorageClass::Private &&
@@ -466,7 +504,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}
}
if (!builtin &&
if (!builtin && value_type &&
ContainsInvalidBool(_, value_type, storage_input_or_output)) {
if (storage_input_or_output) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -495,7 +533,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
if (storage_class == spv::StorageClass::Generic) {
return _.diag(SPV_ERROR_INVALID_BINARY, inst)
<< "OpVariable storage class cannot be Generic";
<< "Variable storage class cannot be Generic";
}
if (inst->function() && storage_class != spv::StorageClass::Function) {
@@ -517,17 +555,17 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
result_type->GetOperandAs<spv::StorageClass>(result_storage_class_index);
if (storage_class != result_storage_class) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "From SPIR-V spec, section 3.32.8 on OpVariable:\n"
<< "Its Storage Class operand must be the same as the Storage Class "
<< "operand of the result type.";
<< "Storage class must match result type storage class";
}
// Variable pointer related restrictions.
const auto pointee = _.FindDef(result_type->word(3));
const auto pointee = untyped_pointer
? value_id == 0 ? nullptr : _.FindDef(value_id)
: _.FindDef(result_type->word(3));
if (_.addressing_model() == spv::AddressingModel::Logical &&
!_.options()->relax_logical_pointer) {
// VariablePointersStorageBuffer is implied by VariablePointers.
if (pointee->opcode() == spv::Op::OpTypePointer) {
if (pointee && pointee->opcode() == spv::Op::OpTypePointer) {
if (!_.HasCapability(spv::Capability::VariablePointersStorageBuffer)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, variables may not allocate a pointer "
@@ -546,7 +584,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
// Vulkan Push Constant Interface section: Check type of PushConstant
// variables.
if (storage_class == spv::StorageClass::PushConstant) {
if (pointee->opcode() != spv::Op::OpTypeStruct) {
if (pointee && pointee->opcode() != spv::Op::OpTypeStruct) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(6808) << "PushConstant OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@@ -558,11 +596,11 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
// Vulkan Descriptor Set Interface: Check type of UniformConstant and
// Uniform variables.
if (storage_class == spv::StorageClass::UniformConstant) {
if (!IsAllowedTypeOrArrayOfSame(
_, pointee,
{spv::Op::OpTypeImage, spv::Op::OpTypeSampler,
spv::Op::OpTypeSampledImage,
spv::Op::OpTypeAccelerationStructureKHR})) {
if (pointee && !IsAllowedTypeOrArrayOfSame(
_, pointee,
{spv::Op::OpTypeImage, spv::Op::OpTypeSampler,
spv::Op::OpTypeSampledImage,
spv::Op::OpTypeAccelerationStructureKHR})) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(4655) << "UniformConstant OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@@ -575,7 +613,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
if (storage_class == spv::StorageClass::Uniform) {
if (!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
if (pointee &&
!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(6807) << "Uniform OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@@ -588,7 +627,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
if (storage_class == spv::StorageClass::StorageBuffer) {
if (!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
if (pointee &&
!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(6807) << "StorageBuffer OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@@ -621,11 +661,17 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}
}
}
// Initializers in Vulkan are only allowed in some storage clases
if (inst->operands().size() > 3) {
// Vulkan Appendix A: Check that if contains initializer, then
// storage class is Output, Private, or Function.
if (inst->operands().size() > initializer_index &&
storage_class != spv::StorageClass::Output &&
storage_class != spv::StorageClass::Private &&
storage_class != spv::StorageClass::Function) {
if (spvIsVulkanEnv(_.context()->target_env)) {
if (storage_class == spv::StorageClass::Workgroup) {
auto init_id = inst->GetOperandAs<uint32_t>(3);
auto init_id = inst->GetOperandAs<uint32_t>(initializer_index);
auto init = _.FindDef(init_id);
if (init->opcode() != spv::Op::OpConstantNull) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -652,7 +698,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}
if (inst->operands().size() > 3) {
if (initializer_index < inst->operands().size()) {
if (storage_class == spv::StorageClass::TaskPayloadWorkgroupEXT) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable, <id> " << _.getIdName(inst->id())
@@ -676,10 +722,10 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
auto pointee_base = pointee;
while (pointee_base->opcode() == spv::Op::OpTypeArray) {
while (pointee_base && pointee_base->opcode() == spv::Op::OpTypeArray) {
pointee_base = _.FindDef(pointee_base->GetOperandAs<uint32_t>(1u));
}
if (pointee_base->opcode() == spv::Op::OpTypePointer) {
if (pointee_base && pointee_base->opcode() == spv::Op::OpTypePointer) {
if (pointee_base->GetOperandAs<spv::StorageClass>(1u) ==
spv::StorageClass::PhysicalStorageBuffer) {
// check for AliasedPointer/RestrictPointer
@@ -769,7 +815,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
// Cooperative matrix types can only be allocated in Function or Private
if ((storage_class != spv::StorageClass::Function &&
storage_class != spv::StorageClass::Private) &&
ContainsCooperativeMatrix(_, pointee)) {
pointee && ContainsCooperativeMatrix(_, pointee)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Cooperative matrix types (or types containing them) can only be "
"allocated "
@@ -785,7 +831,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
(!_.HasCapability(spv::Capability::Float16) &&
_.ContainsSizedIntOrFloatType(value_id, spv::Op::OpTypeFloat, 16))) {
auto underlying_type = value_type;
while (underlying_type->opcode() == spv::Op::OpTypePointer) {
while (underlying_type &&
underlying_type->opcode() == spv::Op::OpTypePointer) {
storage_class = underlying_type->GetOperandAs<spv::StorageClass>(1u);
underlying_type =
_.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
@@ -801,7 +848,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
break;
case spv::StorageClass::Uniform:
if (!_.HasCapability(
if (underlying_type &&
!_.HasCapability(
spv::Capability::UniformAndStorageBuffer16BitAccess)) {
if (underlying_type->opcode() == spv::Op::OpTypeArray ||
underlying_type->opcode() == spv::Op::OpTypeRuntimeArray) {
@@ -849,7 +897,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
if (!_.HasCapability(spv::Capability::Int8) &&
_.ContainsSizedIntOrFloatType(value_id, spv::Op::OpTypeInt, 8)) {
auto underlying_type = value_type;
while (underlying_type->opcode() == spv::Op::OpTypePointer) {
while (underlying_type &&
underlying_type->opcode() == spv::Op::OpTypePointer) {
storage_class = underlying_type->GetOperandAs<spv::StorageClass>(1u);
underlying_type =
_.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
@@ -865,7 +914,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
break;
case spv::StorageClass::Uniform:
if (!_.HasCapability(
if (underlying_type &&
!_.HasCapability(
spv::Capability::UniformAndStorageBuffer8BitAccess)) {
if (underlying_type->opcode() == spv::Op::OpTypeArray ||
underlying_type->opcode() == spv::Op::OpTypeRuntimeArray) {
@@ -930,21 +980,23 @@ spv_result_t ValidateLoad(ValidationState_t& _, const Instruction* inst) {
}
const auto pointer_type = _.FindDef(pointer->type_id());
if (!pointer_type || pointer_type->opcode() != spv::Op::OpTypePointer) {
if (!pointer_type ||
(pointer_type->opcode() != spv::Op::OpTypePointer &&
pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpLoad type for pointer <id> " << _.getIdName(pointer_id)
<< " is not a pointer type.";
}
uint32_t pointee_data_type;
spv::StorageClass storage_class;
if (!_.GetPointerTypeInfo(pointer_type->id(), &pointee_data_type,
&storage_class) ||
result_type->id() != pointee_data_type) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpLoad Result Type <id> " << _.getIdName(inst->type_id())
<< " does not match Pointer <id> " << _.getIdName(pointer->id())
<< "s type.";
if (pointer_type->opcode() == spv::Op::OpTypePointer) {
const auto pointee_type =
_.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
if (!pointee_type || result_type->id() != pointee_type->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpLoad Result Type <id> " << _.getIdName(inst->type_id())
<< " does not match Pointer <id> " << _.getIdName(pointer->id())
<< "s type.";
}
}
if (!_.options()->before_hlsl_legalization &&
@@ -987,17 +1039,23 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
<< " is not a logical pointer.";
}
const auto pointer_type = _.FindDef(pointer->type_id());
if (!pointer_type || pointer_type->opcode() != spv::Op::OpTypePointer) {
if (!pointer_type ||
(pointer_type->opcode() != spv::Op::OpTypePointer &&
pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpStore type for pointer <id> " << _.getIdName(pointer_id)
<< " is not a pointer type.";
}
const auto type_id = pointer_type->GetOperandAs<uint32_t>(2);
const auto type = _.FindDef(type_id);
if (!type || spv::Op::OpTypeVoid == type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpStore Pointer <id> " << _.getIdName(pointer_id)
<< "s type is void.";
Instruction* type = nullptr;
if (pointer_type->opcode() == spv::Op::OpTypePointer) {
const auto type_id = pointer_type->GetOperandAs<uint32_t>(2);
type = _.FindDef(type_id);
if (!type || spv::Op::OpTypeVoid == type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpStore Pointer <id> " << _.getIdName(pointer_id)
<< "s type is void.";
}
}
// validate storage class
@@ -1074,7 +1132,7 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
<< "s type is void.";
}
if (type->id() != object_type->id()) {
if (type && (type->id() != object_type->id())) {
if (!_.options()->relax_struct_store ||
type->opcode() != spv::Op::OpTypeStruct ||
object_type->opcode() != spv::Op::OpTypeStruct) {
@@ -1107,6 +1165,23 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
}
}
if (spvIsVulkanEnv(_.context()->target_env) &&
!_.options()->before_hlsl_legalization) {
const auto isForbiddenType = [](const Instruction* type_inst) {
auto opcode = type_inst->opcode();
return opcode == spv::Op::OpTypeImage ||
opcode == spv::Op::OpTypeSampler ||
opcode == spv::Op::OpTypeSampledImage ||
opcode == spv::Op::OpTypeAccelerationStructureKHR;
};
if (_.ContainsType(object_type->id(), isForbiddenType)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(6924)
<< "Cannot store to OpTypeImage, OpTypeSampler, "
"OpTypeSampledImage, or OpTypeAccelerationStructureKHR objects";
}
}
return SPV_SUCCESS;
}
@@ -1179,7 +1254,8 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
const auto target_pointer_type = _.FindDef(target->type_id());
if (!target_pointer_type ||
target_pointer_type->opcode() != spv::Op::OpTypePointer) {
(target_pointer_type->opcode() != spv::Op::OpTypePointer &&
target_pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target operand <id> " << _.getIdName(target_id)
<< " is not a pointer.";
@@ -1187,35 +1263,52 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
const auto source_pointer_type = _.FindDef(source->type_id());
if (!source_pointer_type ||
source_pointer_type->opcode() != spv::Op::OpTypePointer) {
(source_pointer_type->opcode() != spv::Op::OpTypePointer &&
source_pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Source operand <id> " << _.getIdName(source_id)
<< " is not a pointer.";
}
if (inst->opcode() == spv::Op::OpCopyMemory) {
const auto target_type =
_.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
if (!target_type || target_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target operand <id> " << _.getIdName(target_id)
<< " cannot be a void pointer.";
const bool target_typed =
target_pointer_type->opcode() == spv::Op::OpTypePointer;
const bool source_typed =
source_pointer_type->opcode() == spv::Op::OpTypePointer;
Instruction* target_type = nullptr;
Instruction* source_type = nullptr;
if (target_typed) {
target_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
if (!target_type || target_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target operand <id> " << _.getIdName(target_id)
<< " cannot be a void pointer.";
}
}
const auto source_type =
_.FindDef(source_pointer_type->GetOperandAs<uint32_t>(2));
if (!source_type || source_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Source operand <id> " << _.getIdName(source_id)
<< " cannot be a void pointer.";
if (source_typed) {
source_type = _.FindDef(source_pointer_type->GetOperandAs<uint32_t>(2));
if (!source_type || source_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Source operand <id> " << _.getIdName(source_id)
<< " cannot be a void pointer.";
}
}
if (target_type->id() != source_type->id()) {
if (target_type && source_type && target_type->id() != source_type->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target <id> " << _.getIdName(source_id)
<< "s type does not match Source <id> "
<< _.getIdName(source_type->id()) << "s type.";
}
if (!target_type && !source_type) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "One of Source or Target must be a typed pointer";
}
if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
} else {
const auto size_id = inst->GetOperandAs<uint32_t>(2);
const auto size = _.FindDef(size_id);
@@ -1231,7 +1324,6 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
<< "Size operand <id> " << _.getIdName(size_id)
<< " must be a scalar integer type.";
}
bool is_zero = true;
switch (size->opcode()) {
case spv::Op::OpConstantNull:
@@ -1258,18 +1350,125 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
// Cannot infer any other opcodes.
break;
}
if (_.HasCapability(spv::Capability::Shader)) {
bool is_int = false;
bool is_const = false;
uint32_t value = 0;
std::tie(is_int, is_const, value) = _.EvalInt32IfConst(size_id);
if (is_const) {
if (value % 4 != 0) {
const auto source_sc =
source_pointer_type->GetOperandAs<spv::StorageClass>(1);
const auto target_sc =
target_pointer_type->GetOperandAs<spv::StorageClass>(1);
const bool int8 = _.HasCapability(spv::Capability::Int8);
const bool ubo_int8 = _.HasCapability(
spv::Capability::UniformAndStorageBuffer8BitAccess);
const bool ssbo_int8 =
_.HasCapability(spv::Capability::StorageBuffer8BitAccess) ||
ubo_int8;
const bool pc_int8 =
_.HasCapability(spv::Capability::StoragePushConstant8);
const bool wg_int8 = _.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR);
const bool int16 = _.HasCapability(spv::Capability::Int16) || int8;
const bool ubo_int16 =
_.HasCapability(
spv::Capability::UniformAndStorageBuffer16BitAccess) ||
ubo_int8;
const bool ssbo_int16 =
_.HasCapability(spv::Capability::StorageBuffer16BitAccess) ||
ubo_int16 || ssbo_int8;
const bool pc_int16 =
_.HasCapability(spv::Capability::StoragePushConstant16) ||
pc_int8;
const bool io_int16 =
_.HasCapability(spv::Capability::StorageInputOutput16);
const bool wg_int16 = _.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR);
bool source_int16_match = false;
bool target_int16_match = false;
bool source_int8_match = false;
bool target_int8_match = false;
switch (source_sc) {
case spv::StorageClass::StorageBuffer:
source_int16_match = ssbo_int16;
source_int8_match = ssbo_int8;
break;
case spv::StorageClass::Uniform:
source_int16_match = ubo_int16;
source_int8_match = ubo_int8;
break;
case spv::StorageClass::PushConstant:
source_int16_match = pc_int16;
source_int8_match = pc_int8;
break;
case spv::StorageClass::Input:
case spv::StorageClass::Output:
source_int16_match = io_int16;
break;
case spv::StorageClass::Workgroup:
source_int16_match = wg_int16;
source_int8_match = wg_int8;
break;
default:
break;
}
switch (target_sc) {
case spv::StorageClass::StorageBuffer:
target_int16_match = ssbo_int16;
target_int8_match = ssbo_int8;
break;
case spv::StorageClass::Uniform:
target_int16_match = ubo_int16;
target_int8_match = ubo_int8;
break;
case spv::StorageClass::PushConstant:
target_int16_match = pc_int16;
target_int8_match = pc_int8;
break;
// Input is read-only so it cannot be the target pointer.
case spv::StorageClass::Output:
target_int16_match = io_int16;
break;
case spv::StorageClass::Workgroup:
target_int16_match = wg_int16;
target_int8_match = wg_int8;
break;
default:
break;
}
if (!int8 && !int16 && !(source_int16_match && target_int16_match)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Size must be a multiple of 4";
}
if (value % 2 != 0) {
if (!int8 && !(source_int8_match && target_int8_match)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Size must be a multiple of 2";
}
}
}
}
}
if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
}
if (auto error = ValidateCopyMemoryMemoryAccess(_, inst)) return error;
// Get past the pointers to avoid checking a pointer copy.
auto sub_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
while (sub_type->opcode() == spv::Op::OpTypePointer) {
sub_type = _.FindDef(sub_type->GetOperandAs<uint32_t>(2));
}
if (_.HasCapability(spv::Capability::Shader) &&
_.ContainsLimitedUseIntOrFloatType(sub_type->id())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Cannot copy memory of objects containing 8- or 16-bit types";
if (target_pointer_type->opcode() == spv::Op::OpTypePointer) {
auto sub_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
while (sub_type->opcode() == spv::Op::OpTypePointer) {
sub_type = _.FindDef(sub_type->GetOperandAs<uint32_t>(2));
}
if (_.HasCapability(spv::Capability::Shader) &&
_.ContainsLimitedUseIntOrFloatType(sub_type->id())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Cannot copy memory of objects containing 8- or 16-bit types";
}
}
return SPV_SUCCESS;
@@ -1280,27 +1479,50 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
std::string instr_name =
"Op" + std::string(spvOpcodeString(static_cast<spv::Op>(inst->opcode())));
// The result type must be OpTypePointer.
const bool untyped_pointer = spvOpcodeGeneratesUntypedPointer(inst->opcode());
// The result type must be OpTypePointer for regular access chains and an
// OpTypeUntypedPointerKHR for untyped access chains.
auto result_type = _.FindDef(inst->type_id());
if (spv::Op::OpTypePointer != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypePointer. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
if (untyped_pointer) {
if (!result_type ||
spv::Op::OpTypeUntypedPointerKHR != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< _.getIdName(inst->id())
<< " must be OpTypeUntypedPointerKHR. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
}
} else {
if (!result_type || spv::Op::OpTypePointer != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypePointer. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
}
}
// Result type is a pointer. Find out what it's pointing to.
// This will be used to make sure the indexing results in the same type.
// OpTypePointer word 3 is the type being pointed to.
const auto result_type_pointee = _.FindDef(result_type->word(3));
if (untyped_pointer) {
// Base type must be a non-pointer type.
const auto base_type = _.FindDef(inst->GetOperandAs<uint32_t>(2));
if (!base_type || !spvOpcodeGeneratesType(base_type->opcode()) ||
base_type->opcode() == spv::Op::OpTypePointer ||
base_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Base type must be a non-pointer type";
}
}
// Base must be a pointer, pointing to the base of a composite object.
const auto base_index = 2;
const auto base_index = untyped_pointer ? 3 : 2;
const auto base_id = inst->GetOperandAs<uint32_t>(base_index);
const auto base = _.FindDef(base_id);
const auto base_type = _.FindDef(base->type_id());
if (!base_type || spv::Op::OpTypePointer != base_type->opcode()) {
if (!base_type || !(spv::Op::OpTypePointer == base_type->opcode() ||
(untyped_pointer && spv::Op::OpTypeUntypedPointerKHR ==
base_type->opcode()))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Base <id> " << _.getIdName(base_id) << " in " << instr_name
<< " instruction must be a pointer.";
@@ -1318,14 +1540,18 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
}
// The type pointed to by OpTypePointer (word 3) must be a composite type.
auto type_pointee = _.FindDef(base_type->word(3));
auto type_pointee = untyped_pointer
? _.FindDef(inst->GetOperandAs<uint32_t>(2))
: _.FindDef(base_type->word(3));
// Check Universal Limit (SPIR-V Spec. Section 2.17).
// The number of indexes passed to OpAccessChain may not exceed 255
// The instruction includes 4 words + N words (for N indexes)
size_t num_indexes = inst->words().size() - 4;
if (inst->opcode() == spv::Op::OpPtrAccessChain ||
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain) {
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain ||
inst->opcode() == spv::Op::OpUntypedPtrAccessChainKHR ||
inst->opcode() == spv::Op::OpUntypedInBoundsPtrAccessChainKHR) {
// In pointer access chains, the element operand is required, but not
// counted as an index.
--num_indexes;
@@ -1344,9 +1570,11 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
// instruction. The second index will apply similarly to that result, and so
// on. Once any non-composite type is reached, there must be no remaining
// (unused) indexes.
auto starting_index = 4;
auto starting_index = untyped_pointer ? 5 : 4;
if (inst->opcode() == spv::Op::OpPtrAccessChain ||
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain) {
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain ||
inst->opcode() == spv::Op::OpUntypedPtrAccessChainKHR ||
inst->opcode() == spv::Op::OpUntypedInBoundsPtrAccessChainKHR) {
++starting_index;
}
for (size_t i = starting_index; i < inst->words().size(); ++i) {
@@ -1411,18 +1639,25 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
}
}
}
// At this point, we have fully walked down from the base using the indices.
// The type being pointed to should be the same as the result type.
if (type_pointee->id() != result_type_pointee->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " result type (Op"
<< spvOpcodeString(
static_cast<spv::Op>(result_type_pointee->opcode()))
<< ") does not match the type that results from indexing into the "
"base "
"<id> (Op"
<< spvOpcodeString(static_cast<spv::Op>(type_pointee->opcode()))
<< ").";
if (!untyped_pointer) {
// Result type is a pointer. Find out what it's pointing to.
// This will be used to make sure the indexing results in the same type.
// OpTypePointer word 3 is the type being pointed to.
const auto result_type_pointee = _.FindDef(result_type->word(3));
// At this point, we have fully walked down from the base using the indeces.
// The type being pointed to should be the same as the result type.
if (type_pointee->id() != result_type_pointee->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " result type (Op"
<< spvOpcodeString(
static_cast<spv::Op>(result_type_pointee->opcode()))
<< ") does not match the type that results from indexing into the "
"base "
"<id> (Op"
<< spvOpcodeString(static_cast<spv::Op>(type_pointee->opcode()))
<< ").";
}
}
return SPV_SUCCESS;
@@ -1550,7 +1785,8 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
const Instruction* inst) {
if (_.addressing_model() == spv::AddressingModel::Logical) {
if (_.addressing_model() == spv::AddressingModel::Logical &&
inst->opcode() == spv::Op::OpPtrAccessChain) {
if (!_.features().variable_pointers) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Generating variable pointers requires capability "
@@ -1561,9 +1797,13 @@ spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
// Need to call first, will make sure Base is a valid ID
if (auto error = ValidateAccessChain(_, inst)) return error;
const bool untyped_pointer = spvOpcodeGeneratesUntypedPointer(inst->opcode());
const auto base_id = inst->GetOperandAs<uint32_t>(2);
const auto base = _.FindDef(base_id);
const auto base_type = _.FindDef(base->type_id());
const auto base_type = untyped_pointer
? _.FindDef(inst->GetOperandAs<uint32_t>(2))
: _.FindDef(base->type_id());
const auto base_type_storage_class =
base_type->GetOperandAs<spv::StorageClass>(1);
@@ -1581,15 +1821,17 @@ spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
}
if (spvIsVulkanEnv(_.context()->target_env)) {
const auto untyped_cap =
untyped_pointer && _.HasCapability(spv::Capability::UntypedPointersKHR);
if (base_type_storage_class == spv::StorageClass::Workgroup) {
if (!_.HasCapability(spv::Capability::VariablePointers)) {
if (!_.HasCapability(spv::Capability::VariablePointers) && !untyped_cap) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(7651)
<< "OpPtrAccessChain Base operand pointing to Workgroup "
"storage class must use VariablePointers capability";
}
} else if (base_type_storage_class == spv::StorageClass::StorageBuffer) {
if (!_.features().variable_pointers) {
if (!_.features().variable_pointers && !untyped_cap) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(7652)
<< "OpPtrAccessChain Base operand pointing to StorageBuffer "
@@ -1597,7 +1839,8 @@ spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
"VariablePointersStorageBuffer capability";
}
} else if (base_type_storage_class !=
spv::StorageClass::PhysicalStorageBuffer) {
spv::StorageClass::PhysicalStorageBuffer &&
!untyped_cap) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(7650)
<< "OpPtrAccessChain Base operand must point to Workgroup, "
@@ -1624,18 +1867,28 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
<< " must be OpTypeInt with width 32 and signedness 0.";
}
// The structure that is passed in must be an pointer to a structure, whose
// last element is a runtime array.
auto pointer = state.FindDef(inst->GetOperandAs<uint32_t>(2));
auto pointer_type = state.FindDef(pointer->type_id());
if (pointer_type->opcode() != spv::Op::OpTypePointer) {
const bool untyped = inst->opcode() == spv::Op::OpUntypedArrayLengthKHR;
auto pointer_ty_id = state.GetOperandTypeId(inst, (untyped ? 3 : 2));
auto pointer_ty = state.FindDef(pointer_ty_id);
if (untyped) {
if (pointer_ty->opcode() != spv::Op::OpTypeUntypedPointerKHR) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "Pointer must be an untyped pointer";
}
} else if (pointer_ty->opcode() != spv::Op::OpTypePointer) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's type in " << instr_name << " <id> "
<< state.getIdName(inst->id())
<< " must be a pointer to an OpTypeStruct.";
}
auto structure_type = state.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
Instruction* structure_type = nullptr;
if (untyped) {
structure_type = state.FindDef(inst->GetOperandAs<uint32_t>(2));
} else {
structure_type = state.FindDef(pointer_ty->GetOperandAs<uint32_t>(2));
}
if (structure_type->opcode() != spv::Op::OpTypeStruct) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's type in " << instr_name << " <id> "
@@ -1654,11 +1907,12 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
// The array member must the index of the last element (the run time
// array).
if (inst->GetOperandAs<uint32_t>(3) != num_of_members - 1) {
const auto index = untyped ? 4 : 3;
if (inst->GetOperandAs<uint32_t>(index) != num_of_members - 1) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The array member in " << instr_name << " <id> "
<< state.getIdName(inst->id())
<< " must be an the last member of the struct.";
<< " must be the last member of the struct.";
}
return SPV_SUCCESS;
}
@@ -1843,12 +2097,16 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _,
const auto pointer_type_id = pointer->type_id();
const auto pointer_type = _.FindDef(pointer_type_id);
if (!pointer_type || pointer_type->opcode() != spv::Op::OpTypePointer) {
if (!pointer_type ||
!(pointer_type->opcode() == spv::Op::OpTypePointer ||
pointer_type->opcode() == spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< opname << " type for pointer <id> " << _.getIdName(pointer_id)
<< " is not a pointer type.";
}
const bool untyped =
pointer_type->opcode() == spv::Op::OpTypeUntypedPointerKHR;
const auto storage_class_index = 1u;
const auto storage_class =
pointer_type->GetOperandAs<spv::StorageClass>(storage_class_index);
@@ -1863,27 +2121,36 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _,
<< " is not Workgroup, StorageBuffer, or PhysicalStorageBuffer.";
}
const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
const auto pointee_type = _.FindDef(pointee_id);
if (!pointee_type || !(_.IsIntScalarOrVectorType(pointee_id) ||
_.IsFloatScalarOrVectorType(pointee_id))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< opname << " Pointer <id> " << _.getIdName(pointer->id())
<< "s Type must be a scalar or vector type.";
if (!untyped) {
const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
const auto pointee_type = _.FindDef(pointee_id);
if (!pointee_type || !(_.IsIntScalarOrVectorType(pointee_id) ||
_.IsFloatScalarOrVectorType(pointee_id))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< opname << " Pointer <id> " << _.getIdName(pointer->id())
<< "s Type must be a scalar or vector type.";
}
}
const auto layout_index =
(inst->opcode() == spv::Op::OpCooperativeMatrixLoadKHR) ? 3u : 2u;
const auto colmajor_id = inst->GetOperandAs<uint32_t>(layout_index);
const auto colmajor = _.FindDef(colmajor_id);
if (!colmajor || !_.IsIntScalarType(colmajor->type_id()) ||
!(spvOpcodeIsConstant(colmajor->opcode()) ||
spvOpcodeIsSpecConstant(colmajor->opcode()))) {
const auto layout_id = inst->GetOperandAs<uint32_t>(layout_index);
const auto layout_inst = _.FindDef(layout_id);
if (!layout_inst || !_.IsIntScalarType(layout_inst->type_id()) ||
!spvOpcodeIsConstant(layout_inst->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "MemoryLayout operand <id> " << _.getIdName(colmajor_id)
<< "MemoryLayout operand <id> " << _.getIdName(layout_id)
<< " must be a 32-bit integer constant instruction.";
}
bool stride_required = false;
uint64_t layout;
if (_.EvalConstantValUint64(layout_id, &layout)) {
stride_required =
(layout == (uint64_t)spv::CooperativeMatrixLayout::RowMajorKHR) ||
(layout == (uint64_t)spv::CooperativeMatrixLayout::ColumnMajorKHR);
}
const auto stride_index =
(inst->opcode() == spv::Op::OpCooperativeMatrixLoadKHR) ? 4u : 3u;
if (inst->operands().size() > stride_index) {
@@ -1894,6 +2161,9 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _,
<< "Stride operand <id> " << _.getIdName(stride_id)
<< " must be a scalar integer type.";
}
} else if (stride_required) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "MemoryLayout " << layout << " requires a Stride.";
}
const auto memory_access_index =
@@ -1935,7 +2205,8 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _,
<< "The types of Operand 1 and Operand 2 must match";
}
const auto op1_type = _.FindDef(op1->type_id());
if (!op1_type || op1_type->opcode() != spv::Op::OpTypePointer) {
if (!op1_type || (op1_type->opcode() != spv::Op::OpTypePointer &&
op1_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand type must be a pointer";
}
@@ -1967,6 +2238,7 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _,
spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
switch (inst->opcode()) {
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
if (auto error = ValidateVariable(_, inst)) return error;
break;
case spv::Op::OpLoad:
@@ -1980,17 +2252,22 @@ spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
if (auto error = ValidateCopyMemory(_, inst)) return error;
break;
case spv::Op::OpPtrAccessChain:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
if (auto error = ValidatePtrAccessChain(_, inst)) return error;
break;
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpInBoundsPtrAccessChain:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
if (auto error = ValidateAccessChain(_, inst)) return error;
break;
case spv::Op::OpRawAccessChainNV:
if (auto error = ValidateRawAccessChain(_, inst)) return error;
break;
case spv::Op::OpArrayLength:
case spv::Op::OpUntypedArrayLengthKHR:
if (auto error = ValidateArrayLength(_, inst)) return error;
break;
case spv::Op::OpCooperativeMatrixLoadNV:

View File

@@ -36,6 +36,7 @@ spv_result_t ValidateUniqueness(ValidationState_t& _, const Instruction* inst) {
const auto opcode = inst->opcode();
if (opcode != spv::Op::OpTypeArray && opcode != spv::Op::OpTypeRuntimeArray &&
opcode != spv::Op::OpTypeStruct && opcode != spv::Op::OpTypePointer &&
opcode != spv::Op::OpTypeUntypedPointerKHR &&
!_.RegisterUniqueTypeDeclaration(inst)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Duplicate non-aggregate type declarations are not allowed. "
@@ -583,6 +584,33 @@ spv_result_t ValidateTypeCooperativeMatrix(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateTypeUntypedPointerKHR(ValidationState_t& _,
const Instruction* inst) {
if (spvIsVulkanEnv(_.context()->target_env)) {
const auto sc = inst->GetOperandAs<spv::StorageClass>(1);
switch (sc) {
case spv::StorageClass::Workgroup:
if (!_.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayoutKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Workgroup storage class untyped pointers in Vulkan "
"require WorkgroupMemoryExplicitLayoutKHR be declared";
}
break;
case spv::StorageClass::StorageBuffer:
case spv::StorageClass::PhysicalStorageBuffer:
case spv::StorageClass::Uniform:
case spv::StorageClass::PushConstant:
break;
default:
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Vulkan, untyped pointers can only be used in an "
"explicitly laid out storage class";
}
}
return SPV_SUCCESS;
}
} // namespace
spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
@@ -628,6 +656,9 @@ spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
case spv::Op::OpTypeCooperativeMatrixKHR:
if (auto error = ValidateTypeCooperativeMatrix(_, inst)) return error;
break;
case spv::Op::OpTypeUntypedPointerKHR:
if (auto error = ValidateTypeUntypedPointerKHR(_, inst)) return error;
break;
default:
break;
}

View File

@@ -73,6 +73,7 @@ ModuleLayoutSection InstructionLayoutSection(
case spv::Op::OpTypeForwardPointer:
return kLayoutTypes;
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
if (current_section == kLayoutTypes) return kLayoutTypes;
return kLayoutFunctionDefinitions;
case spv::Op::OpExtInst:
@@ -869,6 +870,9 @@ uint32_t ValidationState_t::GetComponentType(uint32_t id) const {
case spv::Op::OpTypeBool:
return id;
case spv::Op::OpTypeArray:
return inst->word(2);
case spv::Op::OpTypeVector:
return inst->word(2);
@@ -992,6 +996,19 @@ bool ValidationState_t::IsIntScalarType(uint32_t id) const {
return inst && inst->opcode() == spv::Op::OpTypeInt;
}
bool ValidationState_t::IsIntArrayType(uint32_t id) const {
const Instruction* inst = FindDef(id);
if (!inst) {
return false;
}
if (inst->opcode() == spv::Op::OpTypeArray) {
return IsIntScalarType(GetComponentType(id));
}
return false;
}
bool ValidationState_t::IsIntVectorType(uint32_t id) const {
const Instruction* inst = FindDef(id);
if (!inst) {
@@ -1169,7 +1186,9 @@ bool ValidationState_t::GetStructMemberTypes(
bool ValidationState_t::IsPointerType(uint32_t id) const {
const Instruction* inst = FindDef(id);
return inst && inst->opcode() == spv::Op::OpTypePointer;
assert(inst);
return inst->opcode() == spv::Op::OpTypePointer ||
inst->opcode() == spv::Op::OpTypeUntypedPointerKHR;
}
bool ValidationState_t::GetPointerTypeInfo(
@@ -1179,6 +1198,12 @@ bool ValidationState_t::GetPointerTypeInfo(
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
*storage_class = spv::StorageClass(inst->word(2));
*data_type = 0;
return true;
}
if (inst->opcode() != spv::Op::OpTypePointer) return false;
*storage_class = spv::StorageClass(inst->word(2));
@@ -1689,6 +1714,39 @@ bool ValidationState_t::ContainsRuntimeArray(uint32_t id) const {
return ContainsType(id, f, /* traverse_all_types = */ false);
}
bool ValidationState_t::ContainsUntypedPointer(uint32_t id) const {
const auto inst = FindDef(id);
if (!inst) return false;
if (!spvOpcodeGeneratesType(inst->opcode())) return false;
if (inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) return true;
switch (inst->opcode()) {
case spv::Op::OpTypeArray:
case spv::Op::OpTypeRuntimeArray:
case spv::Op::OpTypeVector:
case spv::Op::OpTypeMatrix:
case spv::Op::OpTypeImage:
case spv::Op::OpTypeSampledImage:
case spv::Op::OpTypeCooperativeMatrixNV:
return ContainsUntypedPointer(inst->GetOperandAs<uint32_t>(1u));
case spv::Op::OpTypePointer:
if (IsForwardPointer(id)) return false;
return ContainsUntypedPointer(inst->GetOperandAs<uint32_t>(2u));
case spv::Op::OpTypeFunction:
case spv::Op::OpTypeStruct: {
for (uint32_t i = 1; i < inst->operands().size(); ++i) {
if (ContainsUntypedPointer(inst->GetOperandAs<uint32_t>(i)))
return true;
}
return false;
}
default:
return false;
}
return false;
}
bool ValidationState_t::IsValidStorageClass(
spv::StorageClass storage_class) const {
if (spvIsVulkanEnv(context()->target_env)) {
@@ -2310,6 +2368,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06807);
case 6808:
return VUID_WRAP(VUID-StandaloneSpirv-PushConstant-06808);
case 6924:
return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-06924);
case 6925:
return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06925);
case 7041:

View File

@@ -606,6 +606,7 @@ class ValidationState_t {
bool IsFloatScalarOrVectorType(uint32_t id) const;
bool IsFloatMatrixType(uint32_t id) const;
bool IsIntScalarType(uint32_t id) const;
bool IsIntArrayType(uint32_t id) const;
bool IsIntVectorType(uint32_t id) const;
bool IsIntScalarOrVectorType(uint32_t id) const;
bool IsUnsignedIntScalarType(uint32_t id) const;
@@ -648,6 +649,9 @@ class ValidationState_t {
const std::function<bool(const Instruction*)>& f,
bool traverse_all_types = true) const;
// Returns true if |id| is type id that contains an untyped pointer.
bool ContainsUntypedPointer(uint32_t id) const;
// Returns type_id if id has type or zero otherwise.
uint32_t GetTypeId(uint32_t id) const;