From 88fdc62d2bc0bc5d482cabf93295cb6e726a8e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Sat, 19 Sep 2020 20:12:57 -0700 Subject: [PATCH] Updated spirv-tools. --- 3rdparty/spirv-tools/README.md | 5 +- .../include/generated/build-version.inc | 2 +- .../include/generated/generators.inc | 3 +- .../include/spirv-tools/instrument.hpp | 26 +- .../include/spirv-tools/libspirv.h | 12 + .../include/spirv-tools/libspirv.hpp | 8 + .../include/spirv-tools/optimizer.hpp | 2 +- 3rdparty/spirv-tools/source/opcode.cpp | 17 + 3rdparty/spirv-tools/source/opcode.h | 9 +- .../opt/aggressive_dead_code_elim_pass.cpp | 1 + 3rdparty/spirv-tools/source/opt/ccp_pass.cpp | 2 +- .../opt/graphics_robust_access_pass.cpp | 4 + .../source/opt/inst_bindless_check_pass.cpp | 283 ++++++++++++++--- .../source/opt/inst_bindless_check_pass.h | 79 +++-- .../source/opt/instrument_pass.cpp | 81 ++++- .../spirv-tools/source/opt/instrument_pass.h | 41 ++- .../opt/local_access_chain_convert_pass.cpp | 1 + .../opt/local_single_block_elim_pass.cpp | 1 + .../opt/local_single_store_elim_pass.cpp | 1 + .../spirv-tools/source/opt/loop_peeling.cpp | 2 +- 3rdparty/spirv-tools/source/opt/optimizer.cpp | 16 +- .../source/opt/struct_cfg_analysis.cpp | 13 + .../source/opt/struct_cfg_analysis.h | 5 + .../spirv-tools/source/reduce/CMakeLists.txt | 1 + ..._conditional_branch_opportunity_finder.cpp | 14 +- ...le_conditional_branch_opportunity_finder.h | 2 +- ...nditional_branch_reduction_opportunity.cpp | 10 +- .../merge_blocks_reduction_opportunity.cpp | 12 +- ...ge_blocks_reduction_opportunity_finder.cpp | 10 +- ...erge_blocks_reduction_opportunity_finder.h | 2 +- ..._to_const_reduction_opportunity_finder.cpp | 8 +- ...nd_to_const_reduction_opportunity_finder.h | 2 +- ...nating_id_reduction_opportunity_finder.cpp | 25 +- ...minating_id_reduction_opportunity_finder.h | 2 +- ..._to_undef_reduction_opportunity_finder.cpp | 8 +- ...nd_to_undef_reduction_opportunity_finder.h | 2 +- .../spirv-tools/source/reduce/reducer.cpp | 3 +- .../reduce/reduction_opportunity_finder.cpp | 34 ++ .../reduce/reduction_opportunity_finder.h | 19 +- .../source/reduce/reduction_pass.cpp | 4 +- .../source/reduce/reduction_pass.h | 7 +- .../source/reduce/reduction_util.cpp | 74 ++++- .../source/reduce/reduction_util.h | 10 + .../remove_block_reduction_opportunity.cpp | 5 +- ...ove_block_reduction_opportunity_finder.cpp | 39 ++- ...emove_block_reduction_opportunity_finder.h | 6 +- ..._function_reduction_opportunity_finder.cpp | 9 +- ...ve_function_reduction_opportunity_finder.h | 2 +- ...selection_reduction_opportunity_finder.cpp | 16 +- ...e_selection_reduction_opportunity_finder.h | 2 +- ...struction_reduction_opportunity_finder.cpp | 111 ++++--- ...instruction_reduction_opportunity_finder.h | 2 +- ...ct_member_reduction_opportunity_finder.cpp | 9 +- ...ruct_member_reduction_opportunity_finder.h | 2 +- ...al_branch_to_branch_opportunity_finder.cpp | 11 +- ...onal_branch_to_branch_opportunity_finder.h | 2 +- ...branch_to_branch_reduction_opportunity.cpp | 4 +- ...oop_to_selection_reduction_opportunity.cpp | 93 ++---- ..._loop_to_selection_reduction_opportunity.h | 14 - ...selection_reduction_opportunity_finder.cpp | 20 +- ...o_selection_reduction_opportunity_finder.h | 2 +- .../source/spirv_fuzzer_options.cpp | 8 +- .../spirv-tools/source/spirv_fuzzer_options.h | 3 + .../source/spirv_reducer_options.cpp | 9 +- .../source/spirv_reducer_options.h | 3 + .../source/val/validate_builtins.cpp | 299 +++++++++++++++++- .../source/val/validation_state.cpp | 28 ++ 67 files changed, 1165 insertions(+), 397 deletions(-) create mode 100644 3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp diff --git a/3rdparty/spirv-tools/README.md b/3rdparty/spirv-tools/README.md index c82ca192f..44f582fb8 100644 --- a/3rdparty/spirv-tools/README.md +++ b/3rdparty/spirv-tools/README.md @@ -349,10 +349,7 @@ option, like so: ```sh # In (the SPIRV-Tools repo root): -git clone https://github.com/protocolbuffers/protobuf external/protobuf -pushd external/protobuf -git checkout v3.7.1 -popd +git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf # In your build directory: cmake [-G ] -DSPIRV_BUILD_FUZZER=ON diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 594ae7810..42d06e005 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2020.5", "SPIRV-Tools v2020.5 86402fec24134b6d4103b6ad1e689fb5840f720f" +"v2020.5", "SPIRV-Tools v2020.5 b2259d7c11768aa4bcafa242d63802e9ca427764" diff --git a/3rdparty/spirv-tools/include/generated/generators.inc b/3rdparty/spirv-tools/include/generated/generators.inc index 0c62854b5..b0a58846b 100644 --- a/3rdparty/spirv-tools/include/generated/generators.inc +++ b/3rdparty/spirv-tools/include/generated/generators.inc @@ -21,4 +21,5 @@ {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"}, -{23, "Google", "Tint Compiler", "Google Tint Compiler"}, \ No newline at end of file +{23, "Google", "Tint Compiler", "Google Tint Compiler"}, +{24, "Google", "ANGLE Shader Compiler", "Google ANGLE Shader Compiler"}, \ No newline at end of file diff --git a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp index b4f33554d..9c01cb693 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp @@ -24,6 +24,7 @@ // // CreateInstBindlessCheckPass // CreateInstBuffAddrCheckPass +// CreateInstDebugPrintfPass // // More detailed documentation of these routines can be found in optimizer.hpp @@ -33,7 +34,7 @@ namespace spvtools { // // The following values provide offsets into the output buffer struct // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized -// by InstBindlessCheckPass. +// by InstBindlessCheckPass, InstBuffAddrCheckPass, and InstDebugPrintfPass. // // The first member of the debug output buffer contains the next available word // in the data stream to be written. Shaders will atomically read and update @@ -138,12 +139,21 @@ static const int kInstValidationOutError = kInstStageOutCnt; // A bindless bounds error will output the index and the bound. static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1; static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2; -static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3; +static const int kInstBindlessBoundsOutUnused = kInstStageOutCnt + 3; +static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 4; -// A bindless uninitialized error will output the index. +// A descriptor uninitialized error will output the index. static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1; static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2; -static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3; +static const int kInstBindlessUninitOutUnused2 = kInstStageOutCnt + 3; +static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 4; + +// A buffer out-of-bounds error will output the descriptor +// index, the buffer offset and the buffer size +static const int kInstBindlessBuffOOBOutDescIndex = kInstStageOutCnt + 1; +static const int kInstBindlessBuffOOBOutBuffOff = kInstStageOutCnt + 2; +static const int kInstBindlessBuffOOBOutBuffSize = kInstStageOutCnt + 3; +static const int kInstBindlessBuffOOBOutCnt = kInstStageOutCnt + 4; // A buffer address unalloc error will output the 64-bit pointer in // two 32-bit pieces, lower bits first. @@ -152,7 +162,7 @@ static const int kInstBuffAddrUnallocOutDescPtrHi = kInstStageOutCnt + 2; static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3; // Maximum Output Record Member Count -static const int kInstMaxOutCnt = kInstStageOutCnt + 3; +static const int kInstMaxOutCnt = kInstStageOutCnt + 4; // Validation Error Codes // @@ -160,6 +170,7 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3; static const int kInstErrorBindlessBounds = 0; static const int kInstErrorBindlessUninit = 1; static const int kInstErrorBuffAddrUnallocRef = 2; +static const int kInstErrorBindlessBuffOOB = 3; // Direct Input Buffer Offsets // @@ -197,7 +208,10 @@ static const int kDebugOutputPrintfStream = 3; // At offset kDebugInputBindlessInitOffset in Data[] is a single uint which // gives an offset to the start of the bindless initialization data. More // specifically, if the following value is zero, we know that the descriptor at -// (set = s, binding = b, index = i) is not initialized: +// (set = s, binding = b, index = i) is not initialized; if the value is +// non-zero, and the descriptor points to a buffer, the value is the length of +// the buffer in bytes and can be used to check for out-of-bounds buffer +// references: // Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ] static const int kDebugInputBindlessInitOffset = 0; diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h index 5977f0580..558725b9b 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h @@ -675,6 +675,13 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit( SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError( spv_reducer_options options, bool fail_on_validation_error); +// Sets the function that the reducer should target. If set to zero the reducer +// will target all functions as well as parts of the module that lie outside +// functions. Otherwise the reducer will restrict reduction to the function +// with result id |target_function|, which is required to exist. +SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction( + spv_reducer_options options, uint32_t target_function); + // Creates a fuzzer options object with default options. Returns a valid // options object. The object remains valid until it is passed into // |spvFuzzerOptionsDestroy|. @@ -709,6 +716,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit( SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation( spv_fuzzer_options options); +// Enables all fuzzer passes during a fuzzing run (instead of a random subset +// of passes). +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses( + spv_fuzzer_options options); + // Encodes the given SPIR-V assembly text to its binary representation. The // length parameter specifies the number of bytes for text. Encoded binary will // be stored into *binary. Any error will be written into *diagnostic if diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp index 6b31a07e0..2018e4610 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp @@ -202,6 +202,11 @@ class ReducerOptions { fail_on_validation_error); } + // See spvReducerOptionsSetTargetFunction. + void set_target_function(uint32_t target_function) { + spvReducerOptionsSetTargetFunction(options_, target_function); + } + private: spv_reducer_options options_; }; @@ -242,6 +247,9 @@ class FuzzerOptions { spvFuzzerOptionsEnableFuzzerPassValidation(options_); } + // See spvFuzzerOptionsEnableAllPasses. + void enable_all_passes() { spvFuzzerOptionsEnableAllPasses(options_); } + private: spv_fuzzer_options options_; }; diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp index 741f9476d..d89d3b6fc 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -764,7 +764,7 @@ Optimizer::PassToken CreateCombineAccessChainsPass(); // initialization checking, both of which require input buffer support. Optimizer::PassToken CreateInstBindlessCheckPass( uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false, - bool input_init_enable = false); + bool input_init_enable = false, bool input_buff_oob_enable = false); // Create a pass to instrument physical buffer address checking // This pass instruments all physical buffer address references to check that diff --git a/3rdparty/spirv-tools/source/opcode.cpp b/3rdparty/spirv-tools/source/opcode.cpp index c41e41d6c..8305bcf0a 100644 --- a/3rdparty/spirv-tools/source/opcode.cpp +++ b/3rdparty/spirv-tools/source/opcode.cpp @@ -731,3 +731,20 @@ bool spvOpcodeIsAccessChain(SpvOp opcode) { return false; } } + +bool spvOpcodeIsBit(SpvOp opcode) { + switch (opcode) { + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitReverse: + case SpvOpBitCount: + return true; + default: + return false; + } +} diff --git a/3rdparty/spirv-tools/source/opcode.h b/3rdparty/spirv-tools/source/opcode.h index d79909d26..3702cb35f 100644 --- a/3rdparty/spirv-tools/source/opcode.h +++ b/3rdparty/spirv-tools/source/opcode.h @@ -134,17 +134,20 @@ bool spvOpcodeIsDebug(SpvOp opcode); // where the order of the operands is irrelevant. bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode); -// Returns true for opcodes that represents linear algebra instructions. +// Returns true for opcodes that represent linear algebra instructions. bool spvOpcodeIsLinearAlgebra(SpvOp opcode); -// Returns true for opcodes that represents an image sample instruction. +// Returns true for opcodes that represent image sample instructions. bool spvOpcodeIsImageSample(SpvOp opcode); // Returns a vector containing the indices of the memory semantics // operands for |opcode|. std::vector spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode); -// Returns true for opcodes that represents access chain instructions. +// Returns true for opcodes that represent access chain instructions. bool spvOpcodeIsAccessChain(SpvOp opcode); +// Returns true for opcodes that represent bit instructions. +bool spvOpcodeIsBit(SpvOp opcode); + #endif // SOURCE_OPCODE_H_ diff --git a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp index 71ed022a1..39d468f98 100644 --- a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp @@ -956,6 +956,7 @@ void AggressiveDCEPass::InitExtensions() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", diff --git a/3rdparty/spirv-tools/source/opt/ccp_pass.cpp b/3rdparty/spirv-tools/source/opt/ccp_pass.cpp index b007f2458..2d19bcb38 100644 --- a/3rdparty/spirv-tools/source/opt/ccp_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/ccp_pass.cpp @@ -150,7 +150,7 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) { // that CCP has modified the IR, independently of whether the constant is // actually propagated. See // https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 for details. - if (folded_inst->result_id() == next_id) created_new_constant_ = true; + if (folded_inst->result_id() >= next_id) created_new_constant_ = true; return SSAPropagator::kInteresting; } diff --git a/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp b/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp index db14020db..46483e48b 100644 --- a/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp @@ -320,9 +320,13 @@ void GraphicsRobustAccessPass::ClampIndicesForAccessChain( maxval_width *= 2; } // Determine the type for |maxval|. + uint32_t next_id = context()->module()->IdBound(); analysis::Integer signed_type_for_query(maxval_width, true); auto* maxval_type = type_mgr->GetRegisteredType(&signed_type_for_query)->AsInteger(); + if (next_id != context()->module()->IdBound()) { + module_status_.modified = true; + } // Access chain indices are treated as signed, so limit the maximum value // of the index so it will always be positive for a signed clamp operation. maxval = std::min(maxval, ((uint64_t(1) << (maxval_width - 1)) - 1)); diff --git a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp index 4587343fd..3691414c5 100644 --- a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp @@ -26,13 +26,19 @@ static const int kSpvImageSampledImageIdInIdx = 0; static const int kSpvLoadPtrIdInIdx = 0; static const int kSpvAccessChainBaseIdInIdx = 0; static const int kSpvAccessChainIndex0IdInIdx = 1; -static const int kSpvTypePointerTypeIdInIdx = 1; static const int kSpvTypeArrayLengthIdInIdx = 1; static const int kSpvConstantValueInIdx = 0; static const int kSpvVariableStorageClassInIdx = 0; } // anonymous namespace +// Avoid unused variable warning/error on Linux +#ifndef NDEBUG +#define USE_ASSERT(x) assert(x) +#else +#define USE_ASSERT(x) ((void)(x)) +#endif + namespace spvtools { namespace opt { @@ -48,14 +54,25 @@ uint32_t InstBindlessCheckPass::GenDebugReadLength( uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id, uint32_t desc_idx_id, InstructionBuilder* builder) { - uint32_t desc_set_base_id = - builder->GetUintConstantId(kDebugInputBindlessInitOffset); - uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]); uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]); uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder); - return GenDebugDirectRead( - {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id}, - builder); + // If desc index checking is not enabled, we know the offset of initialization + // entries is 1, so we can avoid loading this value and just add 1 to the + // descriptor set. + if (!desc_idx_enabled_) { + uint32_t desc_set_idx_id = + builder->GetUintConstantId(var2desc_set_[var_id] + 1); + return GenDebugDirectRead({desc_set_idx_id, binding_idx_id, u_desc_idx_id}, + builder); + } else { + uint32_t desc_set_base_id = + builder->GetUintConstantId(kDebugInputBindlessInitOffset); + uint32_t desc_set_idx_id = + builder->GetUintConstantId(var2desc_set_[var_id]); + return GenDebugDirectRead( + {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id}, + builder); + } } uint32_t InstBindlessCheckPass::CloneOriginalReference( @@ -156,13 +173,9 @@ uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) { return 0; } -Instruction* InstBindlessCheckPass::GetDescriptorTypeInst( - Instruction* var_inst) { - uint32_t var_type_id = var_inst->type_id(); - Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id); - uint32_t desc_type_id = - var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx); - return get_def_use_mgr()->GetDef(desc_type_id); +Instruction* InstBindlessCheckPass::GetPointeeTypeInst(Instruction* ptr_inst) { + uint32_t pte_ty_id = GetPointeeTypeId(ptr_inst); + return get_def_use_mgr()->GetDef(pte_ty_id); } bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, @@ -187,7 +200,7 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, return false; break; } - Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst); + Instruction* desc_type_inst = GetPointeeTypeInst(var_inst); switch (desc_type_inst->opcode()) { case SpvOpTypeArray: case SpvOpTypeRuntimeArray: @@ -195,11 +208,11 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, // do not want to instrument loads of descriptors here which are part of // an image-based reference. if (ptr_inst->NumInOperands() < 3) return false; - ref->index_id = + ref->desc_idx_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx); break; default: - ref->index_id = 0; + ref->desc_idx_id = 0; break; } return true; @@ -229,14 +242,14 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx); Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id); if (ptr_inst->opcode() == SpvOp::SpvOpVariable) { - ref->index_id = 0; + ref->desc_idx_id = 0; ref->var_id = ref->ptr_id; } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) { if (ptr_inst->NumInOperands() != 2) { assert(false && "unexpected bindless index number"); return false; } - ref->index_id = + ref->desc_idx_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx); ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx); Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id); @@ -251,9 +264,150 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, return true; } +uint32_t InstBindlessCheckPass::FindStride(uint32_t ty_id, + uint32_t stride_deco) { + uint32_t stride = 0xdeadbeef; + bool found = !get_decoration_mgr()->WhileEachDecoration( + ty_id, stride_deco, [&stride](const Instruction& deco_inst) { + stride = deco_inst.GetSingleWordInOperand(2u); + return false; + }); + USE_ASSERT(found && "stride not found"); + return stride; +} + +uint32_t InstBindlessCheckPass::ByteSize(uint32_t ty_id) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + const analysis::Type* sz_ty = type_mgr->GetType(ty_id); + if (sz_ty->kind() == analysis::Type::kPointer) { + // Assuming PhysicalStorageBuffer pointer + return 8; + } + uint32_t size = 1; + if (sz_ty->kind() == analysis::Type::kMatrix) { + const analysis::Matrix* m_ty = sz_ty->AsMatrix(); + size = m_ty->element_count() * size; + uint32_t stride = FindStride(ty_id, SpvDecorationMatrixStride); + if (stride != 0) return size * stride; + sz_ty = m_ty->element_type(); + } + if (sz_ty->kind() == analysis::Type::kVector) { + const analysis::Vector* v_ty = sz_ty->AsVector(); + size = v_ty->element_count() * size; + sz_ty = v_ty->element_type(); + } + switch (sz_ty->kind()) { + case analysis::Type::kFloat: { + const analysis::Float* f_ty = sz_ty->AsFloat(); + size *= f_ty->width(); + } break; + case analysis::Type::kInteger: { + const analysis::Integer* i_ty = sz_ty->AsInteger(); + size *= i_ty->width(); + } break; + default: { assert(false && "unexpected type"); } break; + } + size /= 8; + return size; +} + +uint32_t InstBindlessCheckPass::GenLastByteIdx(ref_analysis* ref, + InstructionBuilder* builder) { + // Find outermost buffer type and its access chain index + Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id); + Instruction* desc_ty_inst = GetPointeeTypeInst(var_inst); + uint32_t buff_ty_id; + uint32_t ac_in_idx = 1; + switch (desc_ty_inst->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + buff_ty_id = desc_ty_inst->GetSingleWordInOperand(0); + ++ac_in_idx; + break; + default: + assert(desc_ty_inst->opcode() == SpvOpTypeStruct && + "unexpected descriptor type"); + buff_ty_id = desc_ty_inst->result_id(); + break; + } + // Process remaining access chain indices + Instruction* ac_inst = get_def_use_mgr()->GetDef(ref->ptr_id); + uint32_t curr_ty_id = buff_ty_id; + uint32_t sum_id = 0; + while (ac_in_idx < ac_inst->NumInOperands()) { + uint32_t curr_idx_id = ac_inst->GetSingleWordInOperand(ac_in_idx); + Instruction* curr_idx_inst = get_def_use_mgr()->GetDef(curr_idx_id); + Instruction* curr_ty_inst = get_def_use_mgr()->GetDef(curr_ty_id); + uint32_t curr_offset_id = 0; + switch (curr_ty_inst->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeMatrix: { + // Get array/matrix stride and multiply by current index + uint32_t stride_deco = (curr_ty_inst->opcode() == SpvOpTypeMatrix) + ? SpvDecorationMatrixStride + : SpvDecorationArrayStride; + uint32_t arr_stride = FindStride(curr_ty_id, stride_deco); + uint32_t arr_stride_id = builder->GetUintConstantId(arr_stride); + Instruction* curr_offset_inst = builder->AddBinaryOp( + GetUintId(), SpvOpIMul, arr_stride_id, curr_idx_id); + curr_offset_id = curr_offset_inst->result_id(); + // Get element type for next step + curr_ty_id = curr_ty_inst->GetSingleWordInOperand(0); + } break; + case SpvOpTypeVector: { + // Stride is size of component type + uint32_t comp_ty_id = curr_ty_inst->GetSingleWordInOperand(0u); + uint32_t vec_stride = ByteSize(comp_ty_id); + uint32_t vec_stride_id = builder->GetUintConstantId(vec_stride); + Instruction* curr_offset_inst = builder->AddBinaryOp( + GetUintId(), SpvOpIMul, vec_stride_id, curr_idx_id); + curr_offset_id = curr_offset_inst->result_id(); + // Get element type for next step + curr_ty_id = comp_ty_id; + } break; + case SpvOpTypeStruct: { + // Get buffer byte offset for the referenced member + assert(curr_idx_inst->opcode() == SpvOpConstant && + "unexpected struct index"); + uint32_t member_idx = curr_idx_inst->GetSingleWordInOperand(0); + uint32_t member_offset = 0xdeadbeef; + bool found = !get_decoration_mgr()->WhileEachDecoration( + curr_ty_id, SpvDecorationOffset, + [&member_idx, &member_offset](const Instruction& deco_inst) { + if (deco_inst.GetSingleWordInOperand(1u) != member_idx) + return true; + member_offset = deco_inst.GetSingleWordInOperand(3u); + return false; + }); + USE_ASSERT(found && "member offset not found"); + curr_offset_id = builder->GetUintConstantId(member_offset); + // Get element type for next step + curr_ty_id = curr_ty_inst->GetSingleWordInOperand(member_idx); + } break; + default: { assert(false && "unexpected non-composite type"); } break; + } + if (sum_id == 0) + sum_id = curr_offset_id; + else { + Instruction* sum_inst = + builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, curr_offset_id); + sum_id = sum_inst->result_id(); + } + ++ac_in_idx; + } + // Add in offset of last byte of referenced object + uint32_t bsize = ByteSize(curr_ty_id); + uint32_t last = bsize - 1; + uint32_t last_id = builder->GetUintConstantId(last); + Instruction* sum_inst = + builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, last_id); + return sum_inst->result_id(); +} + void InstBindlessCheckPass::GenCheckCode( - uint32_t check_id, uint32_t error_id, uint32_t length_id, - uint32_t stage_idx, ref_analysis* ref, + uint32_t check_id, uint32_t error_id, uint32_t offset_id, + uint32_t length_id, uint32_t stage_idx, ref_analysis* ref, std::vector>* new_blocks) { BasicBlock* back_blk_ptr = &*new_blocks->back(); InstructionBuilder builder( @@ -279,9 +433,19 @@ void InstBindlessCheckPass::GenCheckCode( // Gen invalid block new_blk_ptr.reset(new BasicBlock(std::move(invalid_label))); builder.SetInsertPoint(&*new_blk_ptr); - uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder); - GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx, - {error_id, u_index_id, length_id}, &builder); + uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder); + if (offset_id != 0) + GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx, + {error_id, u_index_id, offset_id, length_id}, &builder); + else if (buffer_bounds_enabled_) + // So all error modes will use same debug stream write function + GenDebugStreamWrite( + uid2offset_[ref->ref_inst->unique_id()], stage_idx, + {error_id, u_index_id, length_id, builder.GetUintConstantId(0)}, + &builder); + else + GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx, + {error_id, u_index_id, length_id}, &builder); // Remember last invalid block id uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id(); // Gen zero for invalid reference @@ -305,7 +469,7 @@ void InstBindlessCheckPass::GenCheckCode( context()->KillInst(ref->ref_inst); } -void InstBindlessCheckPass::GenBoundsCheckCode( +void InstBindlessCheckPass::GenDescIdxCheckCode( BasicBlock::iterator ref_inst_itr, UptrVectorIterator ref_block_itr, uint32_t stage_idx, std::vector>* new_blocks) { @@ -318,19 +482,19 @@ void InstBindlessCheckPass::GenBoundsCheckCode( // If index and bound both compile-time constants and index < bound, // return without changing Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id); - Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst); + Instruction* desc_type_inst = GetPointeeTypeInst(var_inst); uint32_t length_id = 0; if (desc_type_inst->opcode() == SpvOpTypeArray) { length_id = desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx); - Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id); + Instruction* index_inst = get_def_use_mgr()->GetDef(ref.desc_idx_id); Instruction* length_inst = get_def_use_mgr()->GetDef(length_id); if (index_inst->opcode() == SpvOpConstant && length_inst->opcode() == SpvOpConstant && index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) < length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx)) return; - } else if (!input_length_enabled_ || + } else if (!desc_idx_enabled_ || desc_type_inst->opcode() != SpvOpTypeRuntimeArray) { return; } @@ -352,9 +516,9 @@ void InstBindlessCheckPass::GenBoundsCheckCode( // Generate full runtime bounds test code with true branch // being full reference and false branch being debug output and zero // for the referenced value. - Instruction* ult_inst = - builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id); - GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref, + Instruction* ult_inst = builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, + ref.desc_idx_id, length_id); + GenCheckCode(ult_inst->result_id(), error_id, 0u, length_id, stage_idx, &ref, new_blocks); // Move original block's remaining code into remainder/merge block and add // to new blocks @@ -362,13 +526,30 @@ void InstBindlessCheckPass::GenBoundsCheckCode( MovePostludeCode(ref_block_itr, back_blk_ptr); } -void InstBindlessCheckPass::GenInitCheckCode( +void InstBindlessCheckPass::GenDescInitCheckCode( BasicBlock::iterator ref_inst_itr, UptrVectorIterator ref_block_itr, uint32_t stage_idx, std::vector>* new_blocks) { // Look for reference through descriptor. If not, return. ref_analysis ref; if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return; + // Determine if we can only do initialization check + bool init_check = false; + if (ref.desc_load_id != 0 || !buffer_bounds_enabled_) { + init_check = true; + } else { + // For now, only do bounds check for non-aggregate types. Otherwise + // just do descriptor initialization check. + // TODO(greg-lunarg): Do bounds check for aggregate loads and stores + Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id); + Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst); + uint32_t pte_type_op = pte_type_inst->opcode(); + if (pte_type_op == SpvOpTypeArray || pte_type_op == SpvOpTypeRuntimeArray || + pte_type_op == SpvOpTypeStruct) + init_check = true; + } + // If initialization check and not enabled, return + if (init_check && !desc_init_enabled_) return; // Move original block's preceding instructions into first new block std::unique_ptr new_blk_ptr; MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr); @@ -376,19 +557,25 @@ void InstBindlessCheckPass::GenInitCheckCode( context(), &*new_blk_ptr, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); new_blocks->push_back(std::move(new_blk_ptr)); - // Read initialization status from debug input buffer. If index id not yet + // If initialization check, use reference value of zero. + // Else use the index of the last byte referenced. + uint32_t ref_id = init_check ? builder.GetUintConstantId(0u) + : GenLastByteIdx(&ref, &builder); + // Read initialization/bounds from debug input buffer. If index id not yet // set, binding is single descriptor, so set index to constant 0. - uint32_t zero_id = builder.GetUintConstantId(0u); - if (ref.index_id == 0) ref.index_id = zero_id; - uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder); - // Generate full runtime non-zero init test code with true branch + if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u); + uint32_t init_id = GenDebugReadInit(ref.var_id, ref.desc_idx_id, &builder); + // Generate runtime initialization/bounds test code with true branch // being full reference and false branch being debug output and zero // for the referenced value. - Instruction* uneq_inst = - builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id); - uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit); - GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref, - new_blocks); + Instruction* ult_inst = + builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref_id, init_id); + uint32_t error = + init_check ? kInstErrorBindlessUninit : kInstErrorBindlessBuffOOB; + uint32_t error_id = builder.GetUintConstantId(error); + GenCheckCode(ult_inst->result_id(), error_id, init_check ? 0 : ref_id, + init_check ? builder.GetUintConstantId(0u) : init_id, stage_idx, + &ref, new_blocks); // Move original block's remaining code into remainder/merge block and add // to new blocks BasicBlock* back_blk_ptr = &*new_blocks->back(); @@ -400,7 +587,7 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() { InitializeInstrument(); // If runtime array length support enabled, create variable mappings. Length // support is always enabled if descriptor init check is enabled. - if (input_length_enabled_) + if (desc_idx_enabled_ || buffer_bounds_enabled_) for (auto& anno : get_module()->annotations()) if (anno.opcode() == SpvOpDecorate) { if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet) @@ -418,19 +605,19 @@ Pass::Status InstBindlessCheckPass::ProcessImpl() { [this](BasicBlock::iterator ref_inst_itr, UptrVectorIterator ref_block_itr, uint32_t stage_idx, std::vector>* new_blocks) { - return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx, - new_blocks); + return GenDescIdxCheckCode(ref_inst_itr, ref_block_itr, stage_idx, + new_blocks); }; bool modified = InstProcessEntryPointCallTree(pfn); - if (input_init_enabled_) { + if (desc_init_enabled_ || buffer_bounds_enabled_) { // Perform descriptor initialization check on each entry point function in // module pfn = [this](BasicBlock::iterator ref_inst_itr, UptrVectorIterator ref_block_itr, uint32_t stage_idx, std::vector>* new_blocks) { - return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx, - new_blocks); + return GenDescInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx, + new_blocks); }; modified |= InstProcessEntryPointCallTree(pfn); } diff --git a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h index 9335fa5ba..50dfd95d4 100644 --- a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h +++ b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h @@ -28,12 +28,24 @@ namespace opt { // external design may change as the layer evolves. class InstBindlessCheckPass : public InstrumentPass { public: - // Preferred Interface + // Old interface to support testing pre-buffer-overrun capability InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, - bool input_length_enable, bool input_init_enable) - : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless), - input_length_enabled_(input_length_enable), - input_init_enabled_(input_init_enable) {} + bool desc_idx_enable, bool desc_init_enable) + : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, false), + desc_idx_enabled_(desc_idx_enable), + desc_init_enabled_(desc_init_enable), + buffer_bounds_enabled_(false) {} + + // New interface supporting buffer overrun checking + InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, + bool desc_idx_enable, bool desc_init_enable, + bool buffer_bounds_enable) + : InstrumentPass( + desc_set, shader_id, kInstValidationIdBindless, + desc_idx_enable || desc_init_enable || buffer_bounds_enable), + desc_idx_enabled_(desc_idx_enable), + desc_init_enabled_(desc_init_enable), + buffer_bounds_enabled_(buffer_bounds_enable) {} ~InstBindlessCheckPass() override = default; @@ -46,13 +58,11 @@ class InstBindlessCheckPass : public InstrumentPass { // These functions do bindless checking instrumentation on a single // instruction which references through a descriptor (ie references into an // image or buffer). Refer to Vulkan API for further information on - // descriptors. GenBoundsCheckCode checks that an index into a descriptor - // array (array of images or buffers) is in-bounds. GenInitCheckCode + // descriptors. GenDescIdxCheckCode checks that an index into a descriptor + // array (array of images or buffers) is in-bounds. GenDescInitCheckCode // checks that the referenced descriptor has been initialized, if the - // SPV_EXT_descriptor_indexing extension is enabled. - // - // TODO(greg-lunarg): Add support for buffers. Currently only does - // checking of references of images. + // SPV_EXT_descriptor_indexing extension is enabled, and initialized large + // enough to handle the reference, if RobustBufferAccess is disabled. // // The functions are designed to be passed to // InstrumentPass::InstProcessEntryPointCallTree(), which applies the @@ -89,15 +99,15 @@ class InstBindlessCheckPass : public InstrumentPass { // // The Descriptor Array Size is the size of the descriptor array which was // indexed. - void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, - uint32_t stage_idx, - std::vector>* new_blocks); + void GenDescIdxCheckCode( + BasicBlock::iterator ref_inst_itr, + UptrVectorIterator ref_block_itr, uint32_t stage_idx, + std::vector>* new_blocks); - void GenInitCheckCode(BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, - uint32_t stage_idx, - std::vector>* new_blocks); + void GenDescInitCheckCode( + BasicBlock::iterator ref_inst_itr, + UptrVectorIterator ref_block_itr, uint32_t stage_idx, + std::vector>* new_blocks); // Generate instructions into |builder| to read length of runtime descriptor // array |var_id| from debug input buffer and return id of value. @@ -118,10 +128,20 @@ class InstBindlessCheckPass : public InstrumentPass { uint32_t load_id; uint32_t ptr_id; uint32_t var_id; - uint32_t index_id; + uint32_t desc_idx_id; Instruction* ref_inst; } ref_analysis; + // Return size of type |ty_id| in bytes. + uint32_t ByteSize(uint32_t ty_id); + + // Return stride of type |ty_id| with decoration |stride_deco|. Return 0 + // if not found + uint32_t FindStride(uint32_t ty_id, uint32_t stride_deco); + + // Generate index of last byte referenced by buffer reference |ref| + uint32_t GenLastByteIdx(ref_analysis* ref, InstructionBuilder* builder); + // Clone original original reference encapsulated by |ref| into |builder|. // This may generate more than one instruction if neccessary. uint32_t CloneOriginalReference(ref_analysis* ref, @@ -131,8 +151,8 @@ class InstBindlessCheckPass : public InstrumentPass { // references through. Else return 0. uint32_t GetImageId(Instruction* inst); - // Get descriptor type inst of variable |var_inst|. - Instruction* GetDescriptorTypeInst(Instruction* var_inst); + // Get pointee type inst of pointer value |ptr_inst|. + Instruction* GetPointeeTypeInst(Instruction* ptr_inst); // Analyze descriptor reference |ref_inst| and save components into |ref|. // Return true if |ref_inst| is a descriptor reference, false otherwise. @@ -145,22 +165,25 @@ class InstBindlessCheckPass : public InstrumentPass { // writes debug error output utilizing |ref|, |error_id|, |length_id| and // |stage_idx|. Generate merge block for valid and invalid branches. Kill // original reference. - void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id, - uint32_t stage_idx, ref_analysis* ref, + void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t offset_id, + uint32_t length_id, uint32_t stage_idx, ref_analysis* ref, std::vector>* new_blocks); // Initialize state for instrumenting bindless checking void InitializeInstBindlessCheck(); - // Apply GenBoundsCheckCode to every instruction in module. Then apply - // GenInitCheckCode to every instruction in module. + // Apply GenDescIdxCheckCode to every instruction in module. Then apply + // GenDescInitCheckCode to every instruction in module. Pass::Status ProcessImpl(); // Enable instrumentation of runtime array length checking - bool input_length_enabled_; + bool desc_idx_enabled_; // Enable instrumentation of descriptor initialization checking - bool input_init_enabled_; + bool desc_init_enabled_; + + // Enable instrumentation of buffer overrun checking + bool buffer_bounds_enabled_; // Mapping from variable to descriptor set std::unordered_map var2desc_set_; diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp index e6a55a8a4..6ee3cf040 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp @@ -281,14 +281,42 @@ void InstrumentPass::GenDebugStreamWrite( (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args); } +bool InstrumentPass::AllConstant(const std::vector& 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::GenDebugDirectRead( - const std::vector& offset_ids, InstructionBuilder* builder) { + const std::vector& offset_ids, InstructionBuilder* ref_builder) { // Call debug input function. Pass func_idx and offset ids as args. uint32_t off_id_cnt = static_cast(offset_ids.size()); uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt); std::vector args = {input_func_id}; (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end()); - return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id(); + // If optimizing direct reads and the call has already been generated, + // use its result + if (opt_direct_reads_) { + uint32_t res_id = call2id_[args]; + if (res_id != 0) return res_id; + } + // If the offsets 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(offset_ids); + if (insert_in_first_block) { + Instruction* insert_before = &*curr_func_->begin()->tail(); + builder.SetInsertPoint(insert_before); + } + uint32_t res_id = + builder.AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id(); + if (insert_in_first_block) call2id_[args] = res_id; + return res_id; } bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const { @@ -819,21 +847,52 @@ uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) { return func_id; } +void InstrumentPass::SplitBlock( + BasicBlock::iterator inst_itr, UptrVectorIterator block_itr, + std::vector>* 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 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 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 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; - // Compute function index - uint32_t function_idx = 0; - for (auto fii = get_module()->begin(); fii != get_module()->end(); ++fii) { - if (&*fii == func) break; - ++function_idx; - } - std::vector> new_blks; + // Apply instrumentation function to each instruction. // Using block iterators here because of block erasures and insertions. + std::vector> new_blks; for (auto bi = func->begin(); bi != func->end(); ++bi) { for (auto ii = bi->begin(); ii != bi->end();) { - // Generate instrumentation if warranted - pfn(ii, bi, stage_idx, &new_blks); + // 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() != SpvOpVariable) { + 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; diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.h b/3rdparty/spirv-tools/source/opt/instrument_pass.h index f6884d2da..03c99bd9a 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.h +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.h @@ -82,12 +82,15 @@ class InstrumentPass : public Pass { 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. - InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_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, uint32_t validation_id, + bool opt_direct_reads = false) : Pass(), desc_set_(desc_set), shader_id_(shader_id), - validation_id_(validation_id) {} + validation_id_(validation_id), + opt_direct_reads_(opt_direct_reads) {} // Initialize state for instrumentation of module. void InitializeInstrument(); @@ -196,6 +199,9 @@ class InstrumentPass : public Pass { const std::vector& validation_ids, InstructionBuilder* builder); + // Return true if all instructions in |ids| are constants or spec constants. + bool AllConstant(const std::vector& ids); + // Generate in |builder| instructions to read the unsigned integer from the // input buffer specified by the offsets in |offset_ids|. Given offsets // o0, o1, ... oN, and input buffer ibuf, return the id for the value: @@ -284,6 +290,12 @@ class InstrumentPass : public Pass { // if it doesn't exist. uint32_t GetDirectReadFunctionId(uint32_t param_cnt); + // 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 block_itr, + std::vector>* 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 @@ -428,6 +440,29 @@ class InstrumentPass : public Pass { // Post-instrumentation same-block op ids std::unordered_map 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& v) const { + std::size_t hash = v.size(); + for (auto& u : v) { + hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21); + } + return hash; + } + }; + std::unordered_map, uint32_t, vector_hash_> call2id_; + + // Function currently being instrumented + Function* curr_func_; + + // Optimize direct debug input buffer reads. Specifically, move all such + // reads with constant args to first block and reuse them. + bool opt_direct_reads_; }; } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp index 9ff4ec6f0..b57dd29a6 100644 --- a/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp @@ -369,6 +369,7 @@ void LocalAccessChainConvertPass::InitExtensions() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", diff --git a/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp index 511123697..5f35ee1a9 100644 --- a/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp @@ -232,6 +232,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", diff --git a/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp index eb325dd3b..c3479749e 100644 --- a/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp @@ -88,6 +88,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", diff --git a/3rdparty/spirv-tools/source/opt/loop_peeling.cpp b/3rdparty/spirv-tools/source/opt/loop_peeling.cpp index 556442dcd..071c27cb1 100644 --- a/3rdparty/spirv-tools/source/opt/loop_peeling.cpp +++ b/3rdparty/spirv-tools/source/opt/loop_peeling.cpp @@ -1063,7 +1063,7 @@ LoopPeelingPass::LoopPeelingInfo::HandleInequality(CmpOperator cmp_op, } uint32_t cast_iteration = 0; - // coherence check: can we fit |iteration| in a uint32_t ? + // Integrity check: can we fit |iteration| in a uint32_t ? if (static_cast(iteration) < std::numeric_limits::max()) { cast_iteration = static_cast(iteration); } diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp index 403231ab7..b891124e4 100644 --- a/3rdparty/spirv-tools/source/opt/optimizer.cpp +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -427,6 +427,12 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); RegisterPass(CreateAggressiveDCEPass()); + } else if (pass_name == "inst-buff-oob-check") { + RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, true)); + RegisterPass(CreateSimplificationPass()); + RegisterPass(CreateDeadBranchElimPass()); + RegisterPass(CreateBlockMergePass()); + RegisterPass(CreateAggressiveDCEPass()); } else if (pass_name == "inst-buff-addr-check") { RegisterPass(CreateInstBuffAddrCheckPass(7, 23)); RegisterPass(CreateAggressiveDCEPass()); @@ -579,8 +585,8 @@ bool Optimizer::Run(const uint32_t* original_binary, #ifndef NDEBUG // We do not keep the result id of DebugScope in struct DebugScope. - // Instead, we assign random ids for them, which results in coherence - // check failures. We want to skip the coherence check when the module + // Instead, we assign random ids for them, which results in integrity + // check failures. We want to skip the integrity check when the module // contains DebugScope instructions. if (status == opt::Pass::Status::SuccessWithoutChange && !context->module()->ContainsDebugScope()) { @@ -894,10 +900,12 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() { Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, bool input_length_enable, - bool input_init_enable) { + bool input_init_enable, + bool input_buff_oob_enable) { return MakeUnique( MakeUnique( - desc_set, shader_id, input_length_enable, input_init_enable)); + desc_set, shader_id, input_length_enable, input_init_enable, + input_buff_oob_enable)); } Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set, diff --git a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp index 0da1d6012..203db87db 100644 --- a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp +++ b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp @@ -128,6 +128,19 @@ uint32_t StructuredCFGAnalysis::MergeBlock(uint32_t bb_id) { return merge_inst->GetSingleWordInOperand(kMergeNodeIndex); } +uint32_t StructuredCFGAnalysis::NestingDepth(uint32_t bb_id) { + uint32_t result = 0; + + // Find the merge block of the current merge construct as long as the block is + // inside a merge construct, exiting one for each iteration. + for (uint32_t merge_block_id = MergeBlock(bb_id); merge_block_id != 0; + merge_block_id = MergeBlock(merge_block_id)) { + result++; + } + + return result; +} + uint32_t StructuredCFGAnalysis::LoopMergeBlock(uint32_t bb_id) { uint32_t header_id = ContainingLoop(bb_id); if (header_id == 0) { diff --git a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h index 20c202f06..9436b4fb0 100644 --- a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h +++ b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h @@ -53,6 +53,11 @@ class StructuredCFGAnalysis { // merge construct. uint32_t MergeBlock(uint32_t bb_id); + // Returns the nesting depth of the given block, i.e. the number of merge + // constructs containing it. Headers and merge blocks are not considered part + // of the corresponding merge constructs. + uint32_t NestingDepth(uint32_t block_id); + // Returns the id of the header of the innermost loop construct // that contains |bb_id|. Return |0| if |bb_id| is not contained in any loop // construct. diff --git a/3rdparty/spirv-tools/source/reduce/CMakeLists.txt b/3rdparty/spirv-tools/source/reduce/CMakeLists.txt index 9480a43cb..e113ca255 100644 --- a/3rdparty/spirv-tools/source/reduce/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/reduce/CMakeLists.txt @@ -50,6 +50,7 @@ set(SPIRV_TOOLS_REDUCE_SOURCES operand_to_dominating_id_reduction_opportunity_finder.cpp reducer.cpp reduction_opportunity.cpp + reduction_opportunity_finder.cpp reduction_pass.cpp reduction_util.cpp remove_block_reduction_opportunity.cpp diff --git a/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp index 0bd93b9b4..2cd779a26 100644 --- a/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp @@ -20,12 +20,10 @@ namespace spvtools { namespace reduce { -using opt::IRContext; -using opt::Instruction; - std::vector> ConditionalBranchToSimpleConditionalBranchOpportunityFinder:: - GetAvailableOpportunities(IRContext* context) const { + GetAvailableOpportunities(opt::IRContext* context, + uint32_t target_function) const { std::vector> result; // Find the opportunities for redirecting all false targets before the @@ -34,12 +32,12 @@ ConditionalBranchToSimpleConditionalBranchOpportunityFinder:: // reducer is improved by avoiding contiguous opportunities that disable one // another. for (bool redirect_to_true : {true, false}) { - // Consider every function. - for (auto& function : *context->module()) { + // Consider every relevant function. + for (auto* function : GetTargetFunctions(context, target_function)) { // Consider every block in the function. - for (auto& block : function) { + for (auto& block : *function) { // The terminator must be SpvOpBranchConditional. - Instruction* terminator = block.terminator(); + opt::Instruction* terminator = block.terminator(); if (terminator->opcode() != SpvOpBranchConditional) { continue; } diff --git a/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h index c582a889e..17af9b008 100644 --- a/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h @@ -26,7 +26,7 @@ class ConditionalBranchToSimpleConditionalBranchOpportunityFinder : public ReductionOpportunityFinder { public: std::vector> GetAvailableOpportunities( - opt::IRContext* context) const override; + opt::IRContext* context, uint32_t target_function) const override; std::string GetName() const override; }; diff --git a/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp b/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp index d744773bd..8304c30c2 100644 --- a/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp +++ b/3rdparty/spirv-tools/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp @@ -19,13 +19,10 @@ namespace spvtools { namespace reduce { -using opt::IRContext; -using opt::Instruction; - ConditionalBranchToSimpleConditionalBranchReductionOpportunity:: ConditionalBranchToSimpleConditionalBranchReductionOpportunity( - IRContext* context, Instruction* conditional_branch_instruction, - bool redirect_to_true) + opt::IRContext* context, + opt::Instruction* conditional_branch_instruction, bool redirect_to_true) : context_(context), conditional_branch_instruction_(conditional_branch_instruction), redirect_to_true_(redirect_to_true) {} @@ -63,7 +60,8 @@ void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() { context_->cfg()->block(old_successor_block_id)); // We have changed the CFG. - context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); } } // namespace reduce diff --git a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp index 42c784391..a2c3b40a0 100644 --- a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp +++ b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp @@ -20,12 +20,8 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::Function; -using opt::IRContext; - MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity( - IRContext* context, Function* function, BasicBlock* block) { + opt::IRContext* context, opt::Function* function, opt::BasicBlock* block) { // Precondition: the terminator has to be OpBranch. assert(block->terminator()->opcode() == SpvOpBranch); context_ = context; @@ -49,7 +45,8 @@ bool MergeBlocksReductionOpportunity::PreconditionHolds() { "For a successor to be merged into its predecessor, exactly one " "predecessor must be present."); const uint32_t predecessor_id = predecessors[0]; - BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id); + opt::BasicBlock* predecessor_block = + context_->get_instr_block(predecessor_id); return opt::blockmergeutil::CanMergeWithSuccessor(context_, predecessor_block); } @@ -70,7 +67,8 @@ void MergeBlocksReductionOpportunity::Apply() { if (bi->id() == predecessor_id) { opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi); // Block merging changes the control flow graph, so invalidate it. - context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); return; } } diff --git a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.cpp index 89d62632d..ea5e9dac0 100644 --- a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.cpp @@ -19,25 +19,23 @@ namespace spvtools { namespace reduce { -using opt::IRContext; - std::string MergeBlocksReductionOpportunityFinder::GetName() const { return "MergeBlocksReductionOpportunityFinder"; } std::vector> MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; // Consider every block in every function. - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { // See whether it is possible to merge this block with its successor. if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) { // It is, so record an opportunity to do this. result.push_back(spvtools::MakeUnique( - context, &function, &block)); + context, function, &block)); } } } diff --git a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.h index dbf82fec8..df7a8bf1b 100644 --- a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity_finder.h @@ -31,7 +31,7 @@ class MergeBlocksReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.cpp index 3e0a22488..eb7498ad1 100644 --- a/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.cpp @@ -20,11 +20,9 @@ namespace spvtools { namespace reduce { -using opt::IRContext; - std::vector> OperandToConstReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; assert(result.empty()); @@ -37,8 +35,8 @@ OperandToConstReductionOpportunityFinder::GetAvailableOpportunities( // contiguous blocks of opportunities early on, and we want to avoid having a // large block of incompatible opportunities if possible. for (const auto& constant : context->GetConstants()) { - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { // We iterate through the operands using an explicit index (rather // than using a lambda) so that we use said index in the construction diff --git a/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.h index 93c0dcd3f..672674687 100644 --- a/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/operand_to_const_reduction_opportunity_finder.h @@ -33,7 +33,7 @@ class OperandToConstReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp index 1bd9d9d7d..ca3a99e9e 100644 --- a/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp @@ -20,13 +20,9 @@ namespace spvtools { namespace reduce { -using opt::Function; -using opt::IRContext; -using opt::Instruction; - std::vector> OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; // Go through every instruction in every block, considering it as a potential @@ -42,15 +38,15 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( // to prioritise replacing e with its smallest sub-expressions; generalising // this idea to dominating ids this roughly corresponds to more distant // dominators. - for (auto& function : *context->module()) { - for (auto dominating_block = function.begin(); - dominating_block != function.end(); ++dominating_block) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto dominating_block = function->begin(); + dominating_block != function->end(); ++dominating_block) { for (auto& dominating_inst : *dominating_block) { if (dominating_inst.HasResultId() && dominating_inst.type_id()) { // Consider replacing any operand with matching type in a dominated // instruction with the id generated by this instruction. GetOpportunitiesForDominatingInst( - &result, &dominating_inst, dominating_block, &function, context); + &result, &dominating_inst, dominating_block, function, context); } } } @@ -61,9 +57,9 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( void OperandToDominatingIdReductionOpportunityFinder:: GetOpportunitiesForDominatingInst( std::vector>* opportunities, - Instruction* candidate_dominator, - Function::iterator candidate_dominator_block, Function* function, - IRContext* context) const { + opt::Instruction* candidate_dominator, + opt::Function::iterator candidate_dominator_block, + opt::Function* function, opt::IRContext* context) const { assert(candidate_dominator->HasResultId()); assert(candidate_dominator->type_id()); auto dominator_analysis = context->GetDominatorAnalysis(function); @@ -91,9 +87,8 @@ void OperandToDominatingIdReductionOpportunityFinder:: // constant. It is thus not relevant to this pass. continue; } - // Coherence check: we should not get here if the argument is a - // constant. - assert(!context->get_constant_mgr()->GetConstantFromInst(def)); + assert(!context->get_constant_mgr()->GetConstantFromInst(def) && + "We should not get here if the argument is a constant."); if (def->type_id() != candidate_dominator->type_id()) { // The types need to match. continue; diff --git a/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h index 7745ff701..5f3337059 100644 --- a/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h @@ -40,7 +40,7 @@ class OperandToDominatingIdReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: void GetOpportunitiesForDominatingInst( diff --git a/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp index 579b7df67..06bf9550a 100644 --- a/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp @@ -20,15 +20,13 @@ namespace spvtools { namespace reduce { -using opt::IRContext; - std::vector> OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { // Skip instructions that result in a pointer type. auto type_id = inst.type_id(); diff --git a/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.h index 9cdd8cd56..a5c759e93 100644 --- a/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/operand_to_undef_reduction_opportunity_finder.h @@ -32,7 +32,7 @@ class OperandToUndefReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/3rdparty/spirv-tools/source/reduce/reducer.cpp b/3rdparty/spirv-tools/source/reduce/reducer.cpp index 092d40907..18eeaeb66 100644 --- a/3rdparty/spirv-tools/source/reduce/reducer.cpp +++ b/3rdparty/spirv-tools/source/reduce/reducer.cpp @@ -183,7 +183,8 @@ Reducer::ReductionResultStatus Reducer::RunPasses( consumer_(SPV_MSG_INFO, nullptr, {}, ("Trying pass " + pass->GetName() + ".").c_str()); do { - auto maybe_result = pass->TryApplyReduction(*current_binary); + auto maybe_result = + pass->TryApplyReduction(*current_binary, options->target_function); if (maybe_result.empty()) { // For this round, the pass has no more opportunities (chunks) to // apply, so move on to the next pass. diff --git a/3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp new file mode 100644 index 000000000..0bd253b8b --- /dev/null +++ b/3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020 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 "reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { + +std::vector ReductionOpportunityFinder::GetTargetFunctions( + opt::IRContext* ir_context, uint32_t target_function) { + std::vector result; + for (auto& function : *ir_context->module()) { + if (!target_function || function.result_id() == target_function) { + result.push_back(&function); + } + } + assert((!target_function || !result.empty()) && + "Requested target function must exist."); + return result; +} + +} // namespace reduce +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.h index 1837484d7..d95c832b6 100644 --- a/3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.h @@ -15,6 +15,8 @@ #ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_ #define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_ +#include + #include "source/opt/ir_context.h" #include "source/reduce/reduction_opportunity.h" @@ -29,12 +31,25 @@ class ReductionOpportunityFinder { virtual ~ReductionOpportunityFinder() = default; // Finds and returns the reduction opportunities relevant to this pass that - // could be applied to the given SPIR-V module. + // could be applied to SPIR-V module |context|. + // + // If |target_function| is non-zero then the available opportunities will be + // restricted to only those opportunities that modify the function with result + // id |target_function|. virtual std::vector> - GetAvailableOpportunities(opt::IRContext* context) const = 0; + GetAvailableOpportunities(opt::IRContext* context, + uint32_t target_function) const = 0; // Provides a name for the finder. virtual std::string GetName() const = 0; + + protected: + // Requires that |target_function| is zero or the id of a function in + // |ir_context|. If |target_function| is zero, returns all the functions in + // |ir_context|. Otherwise, returns the function with id |target_function|. + // This allows fuzzer passes to restrict attention to a single function. + static std::vector GetTargetFunctions( + opt::IRContext* ir_context, uint32_t target_function); }; } // namespace reduce diff --git a/3rdparty/spirv-tools/source/reduce/reduction_pass.cpp b/3rdparty/spirv-tools/source/reduce/reduction_pass.cpp index 2cb986de9..c6d1ebfd0 100644 --- a/3rdparty/spirv-tools/source/reduce/reduction_pass.cpp +++ b/3rdparty/spirv-tools/source/reduce/reduction_pass.cpp @@ -22,7 +22,7 @@ namespace spvtools { namespace reduce { std::vector ReductionPass::TryApplyReduction( - const std::vector& binary) { + const std::vector& binary, uint32_t target_function) { // We represent modules as binaries because (a) attempts at reduction need to // end up in binary form to be passed on to SPIR-V-consuming tools, and (b) // when we apply a reduction step we need to do it on a fresh version of the @@ -34,7 +34,7 @@ std::vector ReductionPass::TryApplyReduction( assert(context); std::vector> opportunities = - finder_->GetAvailableOpportunities(context.get()); + finder_->GetAvailableOpportunities(context.get(), target_function); // There is no point in having a granularity larger than the number of // opportunities, so reduce the granularity in this case. diff --git a/3rdparty/spirv-tools/source/reduce/reduction_pass.h b/3rdparty/spirv-tools/source/reduce/reduction_pass.h index f2d937ba6..18361824f 100644 --- a/3rdparty/spirv-tools/source/reduce/reduction_pass.h +++ b/3rdparty/spirv-tools/source/reduce/reduction_pass.h @@ -49,7 +49,12 @@ class ReductionPass { // Returns an empty vector if there are no more chunks left to apply; in this // case, the index will be reset and the granularity lowered for the next // round. - std::vector TryApplyReduction(const std::vector& binary); + // + // If |target_function| is non-zero, only reduction opportunities that + // simplify the internals of the function with result id |target_function| + // will be applied. + std::vector TryApplyReduction(const std::vector& binary, + uint32_t target_function); // Notifies the reduction pass whether the binary returned from // TryApplyReduction is interesting, so that the next call to diff --git a/3rdparty/spirv-tools/source/reduce/reduction_util.cpp b/3rdparty/spirv-tools/source/reduce/reduction_util.cpp index 6f128dcb3..511f43236 100644 --- a/3rdparty/spirv-tools/source/reduce/reduction_util.cpp +++ b/3rdparty/spirv-tools/source/reduce/reduction_util.cpp @@ -15,17 +15,73 @@ #include "source/reduce/reduction_util.h" #include "source/opt/ir_context.h" +#include "source/util/make_unique.h" namespace spvtools { namespace reduce { -using opt::IRContext; -using opt::Instruction; - const uint32_t kTrueBranchOperandIndex = 1; const uint32_t kFalseBranchOperandIndex = 2; -uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) { +uint32_t FindOrCreateGlobalVariable(opt::IRContext* context, + uint32_t pointer_type_id) { + for (auto& inst : context->module()->types_values()) { + if (inst.opcode() != SpvOpVariable) { + continue; + } + if (inst.type_id() == pointer_type_id) { + return inst.result_id(); + } + } + const uint32_t variable_id = context->TakeNextId(); + auto variable_inst = MakeUnique( + context, SpvOpVariable, pointer_type_id, variable_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_STORAGE_CLASS, + {static_cast(context->get_type_mgr() + ->GetType(pointer_type_id) + ->AsPointer() + ->storage_class())}}})); + context->module()->AddGlobalValue(std::move(variable_inst)); + return variable_id; +} + +uint32_t FindOrCreateFunctionVariable(opt::IRContext* context, + opt::Function* function, + uint32_t pointer_type_id) { + // The pointer type of a function variable must have Function storage class. + assert(context->get_type_mgr() + ->GetType(pointer_type_id) + ->AsPointer() + ->storage_class() == SpvStorageClassFunction); + + // Go through the instructions in the function's first block until we find a + // suitable variable, or go past all the variables. + opt::BasicBlock::iterator iter = function->begin()->begin(); + for (;; ++iter) { + // We will either find a suitable variable, or find a non-variable + // instruction; we won't exhaust all instructions. + assert(iter != function->begin()->end()); + if (iter->opcode() != SpvOpVariable) { + // If we see a non-variable, we have gone through all the variables. + break; + } + if (iter->type_id() == pointer_type_id) { + return iter->result_id(); + } + } + // At this point, iter refers to the first non-function instruction of the + // function's entry block. + const uint32_t variable_id = context->TakeNextId(); + auto variable_inst = MakeUnique( + context, SpvOpVariable, pointer_type_id, variable_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}})); + iter->InsertBefore(std::move(variable_inst)); + return variable_id; +} + +uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id) { for (auto& inst : context->module()->types_values()) { if (inst.opcode() != SpvOpUndef) { continue; @@ -34,11 +90,9 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) { return inst.result_id(); } } - // TODO(2182): this is adapted from MemPass::Type2Undef. In due course it - // would be good to factor out this duplication. const uint32_t undef_id = context->TakeNextId(); - std::unique_ptr undef_inst( - new Instruction(context, SpvOpUndef, type_id, undef_id, {})); + auto undef_inst = MakeUnique( + context, SpvOpUndef, type_id, undef_id, opt::Instruction::OperandList()); assert(undef_id == undef_inst->result_id()); context->module()->AddGlobalValue(std::move(undef_inst)); return undef_id; @@ -46,8 +100,8 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) { void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, opt::BasicBlock* to_block) { - to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) { - Instruction::OperandList new_in_operands; + to_block->ForEachPhiInst([&from_id](opt::Instruction* phi_inst) { + opt::Instruction::OperandList new_in_operands; // Go through the OpPhi's input operands in (variable, parent) pairs. for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) { // Keep all pairs where the parent is not the block from which the edge diff --git a/3rdparty/spirv-tools/source/reduce/reduction_util.h b/3rdparty/spirv-tools/source/reduce/reduction_util.h index 7e7e153ab..bcdb77cdf 100644 --- a/3rdparty/spirv-tools/source/reduce/reduction_util.h +++ b/3rdparty/spirv-tools/source/reduce/reduction_util.h @@ -26,6 +26,16 @@ namespace reduce { extern const uint32_t kTrueBranchOperandIndex; extern const uint32_t kFalseBranchOperandIndex; +// Returns a global OpVariable of type |pointer_type_id|, adding one if none +// exist. +uint32_t FindOrCreateGlobalVariable(opt::IRContext* context, + uint32_t pointer_type_id); + +// Returns an OpVariable of type |pointer_type_id| declared in |function|, +// adding one if none exist. +uint32_t FindOrCreateFunctionVariable(opt::IRContext* context, opt::Function*, + uint32_t pointer_type_id); + // Returns an OpUndef id from the global value list that is of the given type, // adding one if it does not exist. uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id); diff --git a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp index 3ad7f72cf..aa4810593 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp +++ b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp @@ -19,11 +19,8 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::Function; - RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity( - Function* function, BasicBlock* block) + opt::Function* function, opt::BasicBlock* block) : function_(function), block_(block) { // precondition: assert(block_->begin() != block_->end() && diff --git a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp index a3f873f31..27a4570c1 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp @@ -19,25 +19,21 @@ namespace spvtools { namespace reduce { -using opt::Function; -using opt::IRContext; -using opt::Instruction; - std::string RemoveBlockReductionOpportunityFinder::GetName() const { return "RemoveBlockReductionOpportunityFinder"; } std::vector> RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; - // Consider every block in every function. - for (auto& function : *context->module()) { - for (auto bi = function.begin(); bi != function.end(); ++bi) { - if (IsBlockValidOpportunity(context, function, bi)) { - result.push_back(spvtools::MakeUnique( - &function, &*bi)); + // Consider every block in every relevant function. + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto bi = function->begin(); bi != function->end(); ++bi) { + if (IsBlockValidOpportunity(context, function, &bi)) { + result.push_back( + MakeUnique(function, &*bi)); } } } @@ -45,21 +41,22 @@ RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities( } bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity( - IRContext* context, Function& function, Function::iterator& bi) { - assert(bi != function.end() && "Block iterator was out of bounds"); + opt::IRContext* context, opt::Function* function, + opt::Function::iterator* bi) { + assert(*bi != function->end() && "Block iterator was out of bounds"); // Don't remove first block; we don't want to end up with no blocks. - if (bi == function.begin()) { + if (*bi == function->begin()) { return false; } // Don't remove blocks with references. - if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) { + if (context->get_def_use_mgr()->NumUsers((*bi)->id()) > 0) { return false; } // Don't remove blocks whose instructions have outside references. - if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) { + if (!BlockInstructionsHaveNoOutsideReferences(context, *bi)) { return false; } @@ -67,19 +64,19 @@ bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity( } bool RemoveBlockReductionOpportunityFinder:: - BlockInstructionsHaveNoOutsideReferences(IRContext* context, - const Function::iterator& bi) { + BlockInstructionsHaveNoOutsideReferences( + opt::IRContext* context, const opt::Function::iterator& bi) { // Get all instructions in block. std::unordered_set instructions_in_block; - for (const Instruction& instruction : *bi) { + for (const opt::Instruction& instruction : *bi) { instructions_in_block.insert(instruction.unique_id()); } // For each instruction... - for (const Instruction& instruction : *bi) { + for (const opt::Instruction& instruction : *bi) { // For each use of the instruction... bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser( - &instruction, [&instructions_in_block](Instruction* user) -> bool { + &instruction, [&instructions_in_block](opt::Instruction* user) -> bool { // If the use is in this block, continue (return true). Otherwise, we // found an outside use; return false (and stop). return instructions_in_block.find(user->unique_id()) != diff --git a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h index 83cd04b57..d347bf913 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h @@ -34,14 +34,14 @@ class RemoveBlockReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: // Returns true if the block |bi| in function |function| is a valid // opportunity according to various restrictions. static bool IsBlockValidOpportunity(opt::IRContext* context, - opt::Function& function, - opt::Function::iterator& bi); + opt::Function* function, + opt::Function::iterator* bi); // Returns true if the instructions (definitions) in block |bi| have no // references, except for references from inside the block itself. diff --git a/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp index 1edb97330..1d8d9726e 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.cpp @@ -21,7 +21,14 @@ namespace reduce { std::vector> RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { + if (target_function) { + // If we are targeting a specific function then we are only interested in + // opportunities that simplify the internals of that function; removing + // whole functions does not fit the bill. + return {}; + } + std::vector> result; // Consider each function. for (auto& function : *context->module()) { diff --git a/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.h index 7952a2296..6fcfb7795 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/remove_function_reduction_opportunity_finder.h @@ -31,7 +31,7 @@ class RemoveFunctionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.cpp index 45821e2af..74df1b8db 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.cpp @@ -19,10 +19,6 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::IRContext; -using opt::Instruction; - namespace { const uint32_t kMergeNodeIndex = 0; const uint32_t kContinueNodeIndex = 1; @@ -34,11 +30,11 @@ std::string RemoveSelectionReductionOpportunityFinder::GetName() const { std::vector> RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { // Get all loop merge and continue blocks so we can check for these later. std::unordered_set merge_and_continue_blocks_from_loops; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { if (auto merge_instruction = block.GetMergeInst()) { if (merge_instruction->opcode() == SpvOpLoopMerge) { uint32_t merge_block_id = @@ -73,8 +69,8 @@ RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities( } bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved( - IRContext* context, const BasicBlock& header_block, - Instruction* merge_instruction, + opt::IRContext* context, const opt::BasicBlock& header_block, + opt::Instruction* merge_instruction, std::unordered_set merge_and_continue_blocks_from_loops) { assert(header_block.GetMergeInst() == merge_instruction && "CanOpSelectionMergeBeRemoved(...): header block and merge " @@ -122,7 +118,7 @@ bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved( merge_instruction->GetSingleWordOperand(kMergeNodeIndex); for (uint32_t predecessor_block_id : context->cfg()->preds(merge_block_id)) { - const BasicBlock* predecessor_block = + const opt::BasicBlock* predecessor_block = context->cfg()->block(predecessor_block_id); assert(predecessor_block); bool found_divergent_successor = false; diff --git a/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.h index 848122b8a..1a1749338 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/remove_selection_reduction_opportunity_finder.h @@ -33,7 +33,7 @@ class RemoveSelectionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; // Returns true if the OpSelectionMerge instruction |merge_instruction| in // block |header_block| can be removed. diff --git a/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp index 91ec542c6..d7bb3a82e 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp @@ -28,61 +28,72 @@ RemoveUnusedInstructionReductionOpportunityFinder:: std::vector> RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; - for (auto& inst : context->module()->debugs1()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; + if (!target_function) { + // We are not restricting reduction to a specific function, so we consider + // unused instructions defined outside functions. + + for (auto& inst : context->module()->debugs1()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->debugs2()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->debugs3()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->ext_inst_debuginfo()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->types_values()) { + if (!remove_constants_and_undefs_ && + spvOpcodeIsConstantOrUndef(inst.opcode())) { + continue; + } + if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context, + inst)) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->annotations()) { + if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { + continue; + } + if (!IsIndependentlyRemovableDecoration(inst)) { + continue; + } + result.push_back( + MakeUnique(&inst)); } - result.push_back(MakeUnique(&inst)); } - for (auto& inst : context->module()->debugs2()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->debugs3()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->ext_inst_debuginfo()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->types_values()) { - if (!remove_constants_and_undefs_ && - spvOpcodeIsConstantOrUndef(inst.opcode())) { - continue; - } - if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context, - inst)) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->annotations()) { - if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { - continue; - } - if (!IsIndependentlyRemovableDecoration(inst)) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { if (context->get_def_use_mgr()->NumUses(&inst) > 0) { continue; diff --git a/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h index cbf6a5bd1..032364006 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h @@ -38,7 +38,7 @@ class RemoveUnusedInstructionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: // Returns true if and only if the only uses of |inst| are by decorations that diff --git a/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp index 39ce47f31..e72be625e 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp @@ -24,7 +24,14 @@ namespace reduce { std::vector> RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { + if (target_function) { + // Removing an unused struct member is a global change, as struct types are + // global. We thus do not consider such opportunities if we are targeting + // a specific function. + return {}; + } + std::vector> result; // We track those struct members that are never accessed. We do this by diff --git a/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h index 13f40172b..98f9c019f 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h @@ -32,7 +32,7 @@ class RemoveUnusedStructMemberReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: // A helper method to update |unused_members_to_structs| by removing from it diff --git a/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp index 17a5c7e49..d867c3ad9 100644 --- a/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp @@ -20,20 +20,17 @@ namespace spvtools { namespace reduce { -using opt::IRContext; -using opt::Instruction; - std::vector> SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; // Consider every function. - for (auto& function : *context->module()) { + for (auto* function : GetTargetFunctions(context, target_function)) { // Consider every block in the function. - for (auto& block : function) { + for (auto& block : *function) { // The terminator must be SpvOpBranchConditional. - Instruction* terminator = block.terminator(); + opt::Instruction* terminator = block.terminator(); if (terminator->opcode() != SpvOpBranchConditional) { continue; } diff --git a/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h index 10b9dce4a..8869908b1 100644 --- a/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h @@ -26,7 +26,7 @@ class SimpleConditionalBranchToBranchOpportunityFinder : public ReductionOpportunityFinder { public: std::vector> GetAvailableOpportunities( - opt::IRContext* context) const override; + opt::IRContext* context, uint32_t target_function) const override; std::string GetName() const override; }; diff --git a/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp b/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp index 8968b9627..ca17f9eb1 100644 --- a/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp +++ b/3rdparty/spirv-tools/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp @@ -19,11 +19,9 @@ namespace spvtools { namespace reduce { -using namespace opt; - SimpleConditionalBranchToBranchReductionOpportunity:: SimpleConditionalBranchToBranchReductionOpportunity( - Instruction* conditional_branch_instruction) + opt::Instruction* conditional_branch_instruction) : conditional_branch_instruction_(conditional_branch_instruction) {} bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() { diff --git a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp index 88ea38e7f..0c004439f 100644 --- a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp +++ b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp @@ -21,11 +21,6 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::IRContext; -using opt::Instruction; -using opt::Operand; - namespace { const uint32_t kMergeNodeIndex = 0; } // namespace @@ -58,14 +53,16 @@ void StructuredLoopToSelectionReductionOpportunity::Apply() { // We have made control flow changes that do not preserve the analyses that // were performed. - context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); // (4) By changing CFG edges we may have created scenarios where ids are used // without being dominated; we fix instances of this. FixNonDominatedIdUses(); // Invalidate the analyses we just used. - context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); } void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock( @@ -168,13 +165,14 @@ void StructuredLoopToSelectionReductionOpportunity::RedirectEdge( } void StructuredLoopToSelectionReductionOpportunity:: - AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) { - to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) { + AdaptPhiInstructionsForAddedEdge(uint32_t from_id, + opt::BasicBlock* to_block) { + to_block->ForEachPhiInst([this, &from_id](opt::Instruction* phi_inst) { // Add to the phi operand an (undef, from_id) pair to reflect the added // edge. auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id()); - phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id})); - phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id})); + phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {undef_id})); + phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {from_id})); }); } @@ -227,7 +225,7 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() { continue; } context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def]( - Instruction* use, + opt::Instruction* use, uint32_t index) { // Ignore uses outside of blocks, such as in OpDecorate. if (context_->get_instr_block(use) == nullptr) { @@ -245,17 +243,20 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() { case SpvStorageClassFunction: use->SetOperand( index, {FindOrCreateFunctionVariable( + context_, enclosing_function_, context_->get_type_mgr()->GetId(pointer_type))}); break; default: // TODO(2183) Need to think carefully about whether it makes - // sense to add new variables for all storage classes; it's fine - // for Private but might not be OK for input/output storage - // classes for example. + // sense to add new variables for all storage classes; it's + // fine for Private but might not be OK for input/output + // storage classes for example. use->SetOperand( index, {FindOrCreateGlobalVariable( + context_, context_->get_type_mgr()->GetId(pointer_type))}); break; + break; } } else { use->SetOperand(index, @@ -268,9 +269,10 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() { } bool StructuredLoopToSelectionReductionOpportunity:: - DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use, + DefinitionSufficientlyDominatesUse(opt::Instruction* def, + opt::Instruction* use, uint32_t use_index, - BasicBlock& def_block) { + opt::BasicBlock& def_block) { if (use->opcode() == SpvOpPhi) { // A use in a phi doesn't need to be dominated by its definition, but the // associated parent block does need to be dominated by the definition. @@ -282,62 +284,5 @@ bool StructuredLoopToSelectionReductionOpportunity:: ->Dominates(def, use); } -uint32_t -StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable( - uint32_t pointer_type_id) { - for (auto& inst : context_->module()->types_values()) { - if (inst.opcode() != SpvOpVariable) { - continue; - } - if (inst.type_id() == pointer_type_id) { - return inst.result_id(); - } - } - const uint32_t variable_id = context_->TakeNextId(); - std::unique_ptr variable_inst( - new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, - {(uint32_t)context_->get_type_mgr() - ->GetType(pointer_type_id) - ->AsPointer() - ->storage_class()}}})); - context_->module()->AddGlobalValue(std::move(variable_inst)); - return variable_id; -} - -uint32_t -StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable( - uint32_t pointer_type_id) { - // The pointer type of a function variable must have Function storage class. - assert(context_->get_type_mgr() - ->GetType(pointer_type_id) - ->AsPointer() - ->storage_class() == SpvStorageClassFunction); - - // Go through the instructions in the function's first block until we find a - // suitable variable, or go past all the variables. - BasicBlock::iterator iter = enclosing_function_->begin()->begin(); - for (;; ++iter) { - // We will either find a suitable variable, or find a non-variable - // instruction; we won't exhaust all instructions. - assert(iter != enclosing_function_->begin()->end()); - if (iter->opcode() != SpvOpVariable) { - // If we see a non-variable, we have gone through all the variables. - break; - } - if (iter->type_id() == pointer_type_id) { - return iter->result_id(); - } - } - // At this point, iter refers to the first non-function instruction of the - // function's entry block. - const uint32_t variable_id = context_->TakeNextId(); - std::unique_ptr variable_inst(new Instruction( - context_, SpvOpVariable, pointer_type_id, variable_id, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}})); - iter->InsertBefore(std::move(variable_inst)); - return variable_id; -} - } // namespace reduce } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.h index 962cf77a3..4c576196e 100644 --- a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.h +++ b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity.h @@ -86,20 +86,6 @@ class StructuredLoopToSelectionReductionOpportunity uint32_t use_index, opt::BasicBlock& def_block); - // Checks whether the global value list has an OpVariable of the given pointer - // type, adding one if not, and returns the id of such an OpVariable. - // - // TODO(2184): This will likely be used by other reduction passes, so should - // be factored out in due course. - uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id); - - // Checks whether the enclosing function has an OpVariable of the given - // pointer type, adding one if not, and returns the id of such an OpVariable. - // - // TODO(2184): This will likely be used by other reduction passes, so should - // be factored out in due course. - uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id); - opt::IRContext* context_; opt::BasicBlock* loop_construct_header_; opt::Function* enclosing_function_; diff --git a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp index 085b26722..fdf3ab043 100644 --- a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp @@ -19,8 +19,6 @@ namespace spvtools { namespace reduce { -using opt::IRContext; - namespace { const uint32_t kMergeNodeIndex = 0; const uint32_t kContinueNodeIndex = 1; @@ -28,12 +26,12 @@ const uint32_t kContinueNodeIndex = 1; std::vector> StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; std::set merge_block_ids; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { auto merge_block_id = block.MergeBlockIdIfAny(); if (merge_block_id) { merge_block_ids.insert(merge_block_id); @@ -42,8 +40,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( } // Consider each loop construct header in the module. - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { auto loop_merge_inst = block.GetLoopMergeInst(); if (!loop_merge_inst) { // This is not a loop construct header. @@ -71,8 +69,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // so we cautiously do not consider applying a transformation. auto merge_block_id = loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex); - if (!context->GetDominatorAnalysis(&function)->Dominates( - block.id(), merge_block_id)) { + if (!context->GetDominatorAnalysis(function)->Dominates(block.id(), + merge_block_id)) { continue; } @@ -80,7 +78,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // construct header. If not (e.g. because the loop contains OpReturn, // OpKill or OpUnreachable), we cautiously do not consider applying // a transformation. - if (!context->GetPostDominatorAnalysis(&function)->Dominates( + if (!context->GetPostDominatorAnalysis(function)->Dominates( merge_block_id, block.id())) { continue; } @@ -89,7 +87,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // opportunity to do so. result.push_back( MakeUnique( - context, &block, &function)); + context, &block, function)); } } return result; diff --git a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h index d63d43408..6166af380 100644 --- a/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h +++ b/3rdparty/spirv-tools/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h @@ -46,7 +46,7 @@ class StructuredLoopToSelectionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp b/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp index 64fefbc28..3f62e0e0f 100644 --- a/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp +++ b/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp @@ -25,7 +25,8 @@ spv_fuzzer_options_t::spv_fuzzer_options_t() replay_range(0), replay_validation_enabled(false), shrinker_step_limit(kDefaultStepLimit), - fuzzer_pass_validation_enabled(false) {} + fuzzer_pass_validation_enabled(false), + all_passes_enabled(false) {} SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() { return new spv_fuzzer_options_t(); @@ -60,3 +61,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation( spv_fuzzer_options options) { options->fuzzer_pass_validation_enabled = true; } + +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses( + spv_fuzzer_options options) { + options->all_passes_enabled = true; +} diff --git a/3rdparty/spirv-tools/source/spirv_fuzzer_options.h b/3rdparty/spirv-tools/source/spirv_fuzzer_options.h index 0db16a306..bb8d91037 100644 --- a/3rdparty/spirv-tools/source/spirv_fuzzer_options.h +++ b/3rdparty/spirv-tools/source/spirv_fuzzer_options.h @@ -40,6 +40,9 @@ struct spv_fuzzer_options_t { // See spvFuzzerOptionsValidateAfterEveryPass. bool fuzzer_pass_validation_enabled; + + // See spvFuzzerOptionsEnableAllPasses. + bool all_passes_enabled; }; #endif // SOURCE_SPIRV_FUZZER_OPTIONS_H_ diff --git a/3rdparty/spirv-tools/source/spirv_reducer_options.cpp b/3rdparty/spirv-tools/source/spirv_reducer_options.cpp index e80787538..9086433e5 100644 --- a/3rdparty/spirv-tools/source/spirv_reducer_options.cpp +++ b/3rdparty/spirv-tools/source/spirv_reducer_options.cpp @@ -23,7 +23,9 @@ const uint32_t kDefaultStepLimit = 2500; } // namespace spv_reducer_options_t::spv_reducer_options_t() - : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {} + : step_limit(kDefaultStepLimit), + fail_on_validation_error(false), + target_function(0) {} SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() { return new spv_reducer_options_t(); @@ -42,3 +44,8 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError( spv_reducer_options options, bool fail_on_validation_error) { options->fail_on_validation_error = fail_on_validation_error; } + +SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction( + spv_reducer_options options, uint32_t target_function) { + options->target_function = target_function; +} diff --git a/3rdparty/spirv-tools/source/spirv_reducer_options.h b/3rdparty/spirv-tools/source/spirv_reducer_options.h index 1a431cce2..911747dd2 100644 --- a/3rdparty/spirv-tools/source/spirv_reducer_options.h +++ b/3rdparty/spirv-tools/source/spirv_reducer_options.h @@ -30,6 +30,9 @@ struct spv_reducer_options_t { // See spvReducerOptionsSetFailOnValidationError. bool fail_on_validation_error; + + // See spvReducerOptionsSetTargetFunction. + uint32_t target_function; }; #endif // SOURCE_SPIRV_REDUCER_OPTIONS_H_ diff --git a/3rdparty/spirv-tools/source/val/validate_builtins.cpp b/3rdparty/spirv-tools/source/val/validate_builtins.cpp index ab232c850..1d7017d11 100644 --- a/3rdparty/spirv-tools/source/val/validate_builtins.cpp +++ b/3rdparty/spirv-tools/source/val/validate_builtins.cpp @@ -205,6 +205,14 @@ class BuiltInsValidator { const Decoration& decoration, const Instruction& inst); spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration, const Instruction& inst); + spv_result_t ValidateBaseInstanceOrVertexAtDefinition( + const Decoration& decoration, const Instruction& inst); + spv_result_t ValidateDrawIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateViewIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateDeviceIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId. spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition( const Decoration& decoration, const Instruction& inst); @@ -339,6 +347,26 @@ class BuiltInsValidator { const Instruction& referenced_inst, const Instruction& referenced_from_inst); + spv_result_t ValidateBaseInstanceOrVertexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateDrawIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateViewIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateDeviceIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId. spv_result_t ValidateComputeShaderI32Vec3InputAtReference( const Decoration& decoration, const Instruction& built_in_inst, @@ -2978,6 +3006,259 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference( return SPV_SUCCESS; } +spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + uint32_t vuid = (decoration.params()[0] == SpvBuiltInBaseInstance) + ? 4183 + : 4186; + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(vuid) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateBaseInstanceOrVertexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4182 : 4185; + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelVertex) { + uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4181 : 4184; + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be used only with Vertex execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back( + std::bind(&BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference, + this, decoration, built_in_inst, referenced_from_inst, + std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateDrawIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4209) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateDrawIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateDrawIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4208) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelVertex && + execution_model != SpvExecutionModelMeshNV && + execution_model != SpvExecutionModelTaskNV) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4207) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be used only with Vertex, MeshNV, or TaskNV execution " + "model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateDrawIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateViewIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4403) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateViewIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateViewIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4402) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model == SpvExecutionModelGLCompute) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4401) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be not be used with GLCompute execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateViewIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateDeviceIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4206) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateDeviceIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateDeviceIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4205) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateDeviceIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition( const Decoration& decoration, const Instruction& inst) { if (spvIsVulkanEnv(_.context()->target_env)) { @@ -3168,6 +3449,19 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition( case SpvBuiltInSMIDNV: { return ValidateSMBuiltinsAtDefinition(decoration, inst); } + case SpvBuiltInBaseInstance: + case SpvBuiltInBaseVertex: { + return ValidateBaseInstanceOrVertexAtDefinition(decoration, inst); + } + case SpvBuiltInDrawIndex: { + return ValidateDrawIndexAtDefinition(decoration, inst); + } + case SpvBuiltInViewIndex: { + return ValidateViewIndexAtDefinition(decoration, inst); + } + case SpvBuiltInDeviceIndex: { + return ValidateDeviceIndexAtDefinition(decoration, inst); + } case SpvBuiltInWorkDim: case SpvBuiltInGlobalSize: case SpvBuiltInEnqueuedWorkgroupSize: @@ -3175,11 +3469,6 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition( case SpvBuiltInGlobalLinearId: case SpvBuiltInSubgroupMaxSize: case SpvBuiltInNumEnqueuedSubgroups: - case SpvBuiltInBaseVertex: - case SpvBuiltInBaseInstance: - case SpvBuiltInDrawIndex: - case SpvBuiltInDeviceIndex: - case SpvBuiltInViewIndex: case SpvBuiltInBaryCoordNoPerspAMD: case SpvBuiltInBaryCoordNoPerspCentroidAMD: case SpvBuiltInBaryCoordNoPerspSampleAMD: diff --git a/3rdparty/spirv-tools/source/val/validation_state.cpp b/3rdparty/spirv-tools/source/val/validation_state.cpp index 1d09066b7..72793ecfc 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.cpp +++ b/3rdparty/spirv-tools/source/val/validation_state.cpp @@ -1335,6 +1335,18 @@ std::string ValidationState_t::VkErrorID(uint32_t id, // Clang format adds spaces between hyphens // clang-format off switch (id) { + case 4181: + return VUID_WRAP(VUID-BaseInstance-BaseInstance-04181); + case 4182: + return VUID_WRAP(VUID-BaseInstance-BaseInstance-04182); + case 4183: + return VUID_WRAP(VUID-BaseInstance-BaseInstance-04183); + case 4184: + return VUID_WRAP(VUID-BaseVertex-BaseVertex-04184); + case 4185: + return VUID_WRAP(VUID-BaseVertex-BaseVertex-04185); + case 4186: + return VUID_WRAP(VUID-BaseVertex-BaseVertex-04186); case 4187: return VUID_WRAP(VUID-ClipDistance-ClipDistance-04187); case 4191: @@ -1343,6 +1355,16 @@ std::string ValidationState_t::VkErrorID(uint32_t id, return VUID_WRAP(VUID-CullDistance-CullDistance-04196); case 4200: return VUID_WRAP(VUID-CullDistance-CullDistance-04200); + case 4205: + return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04205); + case 4206: + return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04206); + case 4207: + return VUID_WRAP(VUID-DrawIndex-DrawIndex-04207); + case 4208: + return VUID_WRAP(VUID-DrawIndex-DrawIndex-04208); + case 4209: + return VUID_WRAP(VUID-DrawIndex-DrawIndex-04209); case 4210: return VUID_WRAP(VUID-FragCoord-FragCoord-04210); case 4211: @@ -1473,6 +1495,12 @@ std::string ValidationState_t::VkErrorID(uint32_t id, return VUID_WRAP(VUID-VertexIndex-VertexIndex-04399); case 4400: return VUID_WRAP(VUID-VertexIndex-VertexIndex-04400); + case 4401: + return VUID_WRAP(VUID-ViewIndex-ViewIndex-04401); + case 4402: + return VUID_WRAP(VUID-ViewIndex-ViewIndex-04402); + case 4403: + return VUID_WRAP(VUID-ViewIndex-ViewIndex-04403); case 4404: return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04404); case 4408: