mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-02-17 20:52:36 +01:00
Updated spirv-tools.
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
@@ -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"},
|
||||
@@ -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
@@ -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_
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "spirv-tools/libspirv.h"
|
||||
#include "libspirv.h"
|
||||
|
||||
namespace spvtools {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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|.
|
||||
|
||||
9
3rdparty/spirv-tools/source/binary.cpp
vendored
9
3rdparty/spirv-tools/source/binary.cpp
vendored
@@ -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);
|
||||
|
||||
444
3rdparty/spirv-tools/source/disassemble.cpp
vendored
444
3rdparty/spirv-tools/source/disassemble.cpp
vendored
@@ -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))
|
||||
|
||||
18
3rdparty/spirv-tools/source/disassemble.h
vendored
18
3rdparty/spirv-tools/source/disassemble.h
vendored
@@ -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_;
|
||||
|
||||
|
||||
123
3rdparty/spirv-tools/source/link/linker.cpp
vendored
123
3rdparty/spirv-tools/source/link/linker.cpp
vendored
@@ -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;
|
||||
|
||||
21
3rdparty/spirv-tools/source/name_mapper.cpp
vendored
21
3rdparty/spirv-tools/source/name_mapper.cpp
vendored
@@ -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") +
|
||||
|
||||
64
3rdparty/spirv-tools/source/opcode.cpp
vendored
64
3rdparty/spirv-tools/source/opcode.cpp
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
3
3rdparty/spirv-tools/source/opcode.h
vendored
3
3rdparty/spirv-tools/source/opcode.h
vendored
@@ -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_
|
||||
|
||||
26
3rdparty/spirv-tools/source/operand.cpp
vendored
26
3rdparty/spirv-tools/source/operand.cpp
vendored
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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|.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
22
3rdparty/spirv-tools/source/opt/constants.cpp
vendored
22
3rdparty/spirv-tools/source/opt/constants.cpp
vendored
@@ -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;
|
||||
|
||||
5
3rdparty/spirv-tools/source/opt/constants.h
vendored
5
3rdparty/spirv-tools/source/opt/constants.h
vendored
@@ -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
|
||||
|
||||
102
3rdparty/spirv-tools/source/opt/copy_prop_arrays.cpp
vendored
102
3rdparty/spirv-tools/source/opt/copy_prop_arrays.cpp
vendored
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
14
3rdparty/spirv-tools/source/opt/desc_sroa.h
vendored
14
3rdparty/spirv-tools/source/opt/desc_sroa.h
vendored
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
143
3rdparty/spirv-tools/source/opt/folding_rules.cpp
vendored
143
3rdparty/spirv-tools/source/opt/folding_rules.cpp
vendored
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
803
3rdparty/spirv-tools/source/opt/instrument_pass.cpp
vendored
803
3rdparty/spirv-tools/source/opt/instrument_pass.cpp
vendored
@@ -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
|
||||
326
3rdparty/spirv-tools/source/opt/instrument_pass.h
vendored
326
3rdparty/spirv-tools/source/opt/instrument_pass.h
vendored
@@ -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_
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
3
3rdparty/spirv-tools/source/opt/mem_pass.cpp
vendored
3
3rdparty/spirv-tools/source/opt/mem_pass.cpp
vendored
@@ -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.
|
||||
|
||||
60
3rdparty/spirv-tools/source/opt/optimizer.cpp
vendored
60
3rdparty/spirv-tools/source/opt/optimizer.cpp
vendored
@@ -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));
|
||||
|
||||
105
3rdparty/spirv-tools/source/opt/pass.cpp
vendored
105
3rdparty/spirv-tools/source/opt/pass.cpp
vendored
@@ -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
|
||||
|
||||
3
3rdparty/spirv-tools/source/opt/pass.h
vendored
3
3rdparty/spirv-tools/source/opt/pass.h
vendored
@@ -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);
|
||||
|
||||
|
||||
2
3rdparty/spirv-tools/source/opt/passes.h
vendored
2
3rdparty/spirv-tools/source/opt/passes.h
vendored
@@ -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"
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
}
|
||||
|
||||
482
3rdparty/spirv-tools/source/opt/struct_packing_pass.cpp
vendored
Normal file
482
3rdparty/spirv-tools/source/opt/struct_packing_pass.cpp
vendored
Normal 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
|
||||
81
3rdparty/spirv-tools/source/opt/struct_packing_pass.h
vendored
Normal file
81
3rdparty/spirv-tools/source/opt/struct_packing_pass.h
vendored
Normal 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_
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>{
|
||||
|
||||
4
3rdparty/spirv-tools/source/table.h
vendored
4
3rdparty/spirv-tools/source/table.h
vendored
@@ -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
|
||||
|
||||
3
3rdparty/spirv-tools/source/text_handler.cpp
vendored
3
3rdparty/spirv-tools/source/text_handler.cpp
vendored
@@ -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};
|
||||
|
||||
44
3rdparty/spirv-tools/source/to_string.cpp
vendored
Normal file
44
3rdparty/spirv-tools/source/to_string.cpp
vendored
Normal 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
29
3rdparty/spirv-tools/source/to_string.h
vendored
Normal 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_
|
||||
27
3rdparty/spirv-tools/source/util/bitutils.h
vendored
27
3rdparty/spirv-tools/source/util/bitutils.h
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
37
3rdparty/spirv-tools/source/val/validate_cfg.cpp
vendored
37
3rdparty/spirv-tools/source/val/validate_cfg.cpp
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
117
3rdparty/spirv-tools/source/val/validate_image.cpp
vendored
117
3rdparty/spirv-tools/source/val/validate_image.cpp
vendored
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
557
3rdparty/spirv-tools/source/val/validate_memory.cpp
vendored
557
3rdparty/spirv-tools/source/val/validate_memory.cpp
vendored
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user