From 4c071b3a979796a6473a77d89dde431d5f5c61d5 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: Fri, 22 Mar 2019 13:17:07 -0700 Subject: [PATCH] Updated spirv-tools. --- .../include/generated/build-version.inc | 2 +- .../include/spirv-tools/instrument.hpp | 32 +- .../include/spirv-tools/optimizer.hpp | 22 +- .../source/opt/inst_bindless_check_pass.cpp | 388 ++++++++----- .../source/opt/inst_bindless_check_pass.h | 115 +++- .../source/opt/instrument_pass.cpp | 177 ++++-- .../spirv-tools/source/opt/instrument_pass.h | 39 +- 3rdparty/spirv-tools/source/opt/optimizer.cpp | 9 +- .../spirv-tools/source/reduce/CMakeLists.txt | 4 + .../spirv-tools/source/reduce/reducer.cpp | 19 +- 3rdparty/spirv-tools/source/reduce/reducer.h | 6 +- .../remove_block_reduction_opportunity.cpp | 56 ++ .../remove_block_reduction_opportunity.h | 46 ++ ...ove_block_reduction_opportunity_finder.cpp | 96 ++++ ...emove_block_reduction_opportunity_finder.h | 55 ++ .../spirv-tools/source/val/validate_image.cpp | 11 +- .../source/val/validation_state.cpp | 10 +- .../spirv-tools/source/val/validation_state.h | 9 +- .../test/opt/inst_bindless_check_test.cpp | 524 +++++++++++++++--- .../spirv-tools/test/reduce/CMakeLists.txt | 1 + .../spirv-tools/test/reduce/reducer_test.cpp | 18 +- .../test/reduce/remove_block_test.cpp | 358 ++++++++++++ .../validation_during_reduction_test.cpp | 201 ++++++- .../spirv-tools/test/val/val_image_test.cpp | 32 ++ 3rdparty/spirv-tools/tools/reduce/reduce.cpp | 29 +- 25 files changed, 1885 insertions(+), 374 deletions(-) create mode 100644 3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp create mode 100644 3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.h create mode 100644 3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp create mode 100644 3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h create mode 100644 3rdparty/spirv-tools/test/reduce/remove_block_test.cpp diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 87c3414e9..273ee54ba 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2019.2", "SPIRV-Tools v2019.2 45a64efab9d50ebcf9694a3297acc3223502610d" +"v2019.2", "SPIRV-Tools v2019.2 027e592038bb74c2ffe6b07f9cc4e2ab1b3fbb84" diff --git a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp index 01c7c6d1e..9711b9297 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp @@ -110,6 +110,16 @@ static const int kInstValidationOutError = kInstStageOutCnt; // about the validation error. // // 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; + +// A bindless uninitialized error will output the index. +static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1; +static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2; +static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3; + +// DEPRECATED static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1; static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2; static const int kInstBindlessOutCnt = kInstStageOutCnt + 3; @@ -121,6 +131,7 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3; // // These are the possible validation error codes. static const int kInstErrorBindlessBounds = 0; +static const int kInstErrorBindlessUninit = 1; // Direct Input Buffer Offsets // @@ -141,11 +152,8 @@ static const int kDebugInputDataOffset = 0; // and possibly other future validations. static const int kDebugOutputBindingStream = 0; -// The binding for the input buffer for InstBindlessCheckPass. The input -// buffer needs only be created if the shaders being validated contain a -// descriptor array of runtime size, and validation of runtime size descriptor -// arrays have been enabled at the time of the bindless validation pass -// creation. +// The binding for the input buffer read by InstBindlessCheckPass and +// possibly other future validations. static const int kDebugInputBindingBindless = 1; // Bindless Validation Input Buffer Format @@ -153,11 +161,19 @@ static const int kDebugInputBindingBindless = 1; // An input buffer for bindless validation consists of a single array of // unsigned integers we will call Data[]. This array is formatted as follows. // -// At the beginning of the array is a single uint reserved for future use. +// 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: +// Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ] +static const int kDebugInputBindlessInitOffset = 0; + +// DEPRECATED static const int kDebugInputBindlessOffsetReserved = 0; -// Following the reserved uint is some number of uints such that the following -// is true: the number of descriptors at (set=s, binding=b) is: +// At offset kDebugInputBindlessOffsetLengths is some number of uints which +// provide the bindless length data. More specifically, the number of +// descriptors at (set=s, binding=b) is: // Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ] static const int kDebugInputBindlessOffsetLengths = 1; diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp index bbc70041a..08ef5e67a 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -704,10 +704,14 @@ Optimizer::PassToken CreateCombineAccessChainsPass(); // Create a pass to instrument bindless descriptor checking // This pass instruments all bindless references to check that descriptor -// array indices are inbounds. If the reference is invalid, a record is -// written to the debug output buffer (if space allows) and a null value is -// returned. This pass is designed to support bindless validation in the Vulkan -// validation layers. +// array indices are inbounds, and if the descriptor indexing extension is +// enabled, that the descriptor has been initialized. If the reference is +// invalid, a record is written to the debug output buffer (if space allows) +// and a null value is returned. This pass is designed to support bindless +// validation in the Vulkan validation layers. +// +// TODO(greg-lunarg): Add support for buffer references. Currently only does +// checking for image references. // // Dead code elimination should be run after this pass as the original, // potentially invalid code is not removed and could cause undefined behavior, @@ -723,12 +727,12 @@ Optimizer::PassToken CreateCombineAccessChainsPass(); // The instrumentation will read and write buffers in debug // descriptor set |desc_set|. It will write |shader_id| in each output record // to identify the shader module which generated the record. -// |runtime_array_enable| controls instrumentation of runtime arrays which -// require input buffer support. -// -// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing. +// |input_length_enable| controls instrumentation of runtime descriptor array +// references, and |input_init_enable| controls instrumentation of descriptor +// initialization checking, both of which require input buffer support. Optimizer::PassToken CreateInstBindlessCheckPass( - uint32_t desc_set, uint32_t shader_id, bool runtime_array_enable = false); + uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false, + bool input_init_enable = false); // Create a pass to upgrade to the VulkanKHR memory model. // This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR. 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 d9f221f91..47c81347c 100644 --- a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.cpp @@ -40,21 +40,74 @@ uint32_t InstBindlessCheckPass::GenDebugReadLength( uint32_t desc_set_idx = var2desc_set_[var_id] + kDebugInputBindlessOffsetLengths; uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx); - uint32_t desc_set_offset_id = GenDebugDirectRead(desc_set_idx_id, builder); - Instruction* binding_idx_inst = - builder->AddBinaryOp(GetUintId(), SpvOpIAdd, desc_set_offset_id, - builder->GetUintConstantId(var2binding_[var_id])); - return GenDebugDirectRead(binding_idx_inst->result_id(), builder); + uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]); + return GenDebugDirectRead({desc_set_idx_id, binding_idx_id}, builder); } -void InstBindlessCheckPass::GenBindlessCheckCode( - BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, uint32_t instruction_idx, - uint32_t stage_idx, std::vector>* new_blocks) { - // Look for reference through bindless descriptor. If not, return. - std::unique_ptr new_blk_ptr; - uint32_t image_id; - switch (ref_inst_itr->opcode()) { +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); +} + +uint32_t InstBindlessCheckPass::CloneOriginalReference( + ref_analysis* ref, InstructionBuilder* builder) { + // Clone descriptor load + Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id); + Instruction* new_load_inst = + builder->AddLoad(load_inst->type_id(), + load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx)); + uid2offset_[new_load_inst->unique_id()] = uid2offset_[load_inst->unique_id()]; + uint32_t new_load_id = new_load_inst->result_id(); + get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id); + uint32_t new_image_id = new_load_id; + // Clone Image/SampledImage with new load, if needed + if (ref->image_id != 0) { + Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id); + if (image_inst->opcode() == SpvOp::SpvOpSampledImage) { + Instruction* new_image_inst = builder->AddBinaryOp( + image_inst->type_id(), SpvOpSampledImage, new_load_id, + image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx)); + uid2offset_[new_image_inst->unique_id()] = + uid2offset_[image_inst->unique_id()]; + new_image_id = new_image_inst->result_id(); + } else { + assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage"); + Instruction* new_image_inst = + builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id); + uid2offset_[new_image_inst->unique_id()] = + uid2offset_[image_inst->unique_id()]; + new_image_id = new_image_inst->result_id(); + } + get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id); + } + // Clone original reference using new image code + std::unique_ptr new_ref_inst(ref->ref_inst->Clone(context())); + uint32_t ref_result_id = ref->ref_inst->result_id(); + uint32_t new_ref_id = 0; + if (ref_result_id != 0) { + new_ref_id = TakeNextId(); + new_ref_inst->SetResultId(new_ref_id); + } + new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id}); + // Register new reference and add to new block + Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst)); + uid2offset_[added_inst->unique_id()] = + uid2offset_[ref->ref_inst->unique_id()]; + if (new_ref_id != 0) + get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id); + return new_ref_id; +} + +uint32_t InstBindlessCheckPass::GetDescriptorValueId(Instruction* inst) { + switch (inst->opcode()) { case SpvOp::SpvOpImageSampleImplicitLod: case SpvOp::SpvOpImageSampleExplicitLod: case SpvOp::SpvOpImageSampleDrefImplicitLod: @@ -87,162 +140,205 @@ void InstBindlessCheckPass::GenBindlessCheckCode( case SpvOp::SpvOpImageSparseFetch: case SpvOp::SpvOpImageSparseRead: case SpvOp::SpvOpImageWrite: - image_id = - ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx); - break; + return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx); default: - return; + break; } - Instruction* image_inst = get_def_use_mgr()->GetDef(image_id); - uint32_t load_id; - Instruction* load_inst; + return 0; +} + +bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, + ref_analysis* ref) { + ref->image_id = GetDescriptorValueId(ref_inst); + if (ref->image_id == 0) return false; + Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id); if (image_inst->opcode() == SpvOp::SpvOpSampledImage) { - load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx); - load_inst = get_def_use_mgr()->GetDef(load_id); + ref->load_id = + image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx); } else if (image_inst->opcode() == SpvOp::SpvOpImage) { - load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx); - load_inst = get_def_use_mgr()->GetDef(load_id); + ref->load_id = + image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx); } else { - load_id = image_id; - load_inst = image_inst; - image_id = 0; + ref->load_id = ref->image_id; + ref->image_id = 0; } + Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id); if (load_inst->opcode() != SpvOp::SpvOpLoad) { - // TODO(greg-lunarg): Handle additional possibilities - return; + // TODO(greg-lunarg): Handle additional possibilities? + return false; } - uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx); - Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id); - if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return; - if (ptr_inst->NumInOperands() != 2) { - assert(false && "unexpected bindless index number"); - return; + ref->ptr_id = 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->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 = + ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx); + ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx); + Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id); + if (var_inst->opcode() != SpvOpVariable) { + assert(false && "unexpected bindless base"); + return false; + } + } else { + // TODO(greg-lunarg): Handle additional possibilities? + return false; } - uint32_t index_id = - ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx); - ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx); - ptr_inst = get_def_use_mgr()->GetDef(ptr_id); - if (ptr_inst->opcode() != SpvOpVariable) { - assert(false && "unexpected bindless base"); - return; - } - uint32_t var_type_id = ptr_inst->type_id(); - Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id); - uint32_t ptr_type_id = - var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx); - Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); - // If index and bound both compile-time constants and index < bound, - // return without changing - uint32_t length_id = 0; - if (ptr_type_inst->opcode() == SpvOpTypeArray) { - length_id = - ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx); - Instruction* index_inst = get_def_use_mgr()->GetDef(index_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 (!runtime_array_enabled_ || - ptr_type_inst->opcode() != SpvOpTypeRuntimeArray) { - return; - } - // Generate full runtime bounds test code with true branch - // being full reference and false branch being debug output and zero - // for the referenced value. - MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr); + ref->ref_inst = ref_inst; + return true; +} + +void InstBindlessCheckPass::GenCheckCode( + uint32_t check_id, uint32_t error_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( - context(), &*new_blk_ptr, + context(), back_blk_ptr, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds); - // If length id not yet set, descriptor array is runtime size so - // generate load of length from stage's debug input buffer. - if (length_id == 0) { - assert(ptr_type_inst->opcode() == SpvOpTypeRuntimeArray && - "unexpected bindless type"); - length_id = GenDebugReadLength(ptr_id, &builder); - } - Instruction* ult_inst = - builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id); + // Gen conditional branch on check_id. Valid branch generates original + // reference. Invalid generates debug output and zero result (if needed). uint32_t merge_blk_id = TakeNextId(); uint32_t valid_blk_id = TakeNextId(); uint32_t invalid_blk_id = TakeNextId(); std::unique_ptr merge_label(NewLabel(merge_blk_id)); std::unique_ptr valid_label(NewLabel(valid_blk_id)); std::unique_ptr invalid_label(NewLabel(invalid_blk_id)); - (void)builder.AddConditionalBranch(ult_inst->result_id(), valid_blk_id, - invalid_blk_id, merge_blk_id, - SpvSelectionControlMaskNone); - // Close selection block and gen valid reference block - new_blocks->push_back(std::move(new_blk_ptr)); - new_blk_ptr.reset(new BasicBlock(std::move(valid_label))); + (void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id, + merge_blk_id, SpvSelectionControlMaskNone); + // Gen valid bounds branch + std::unique_ptr new_blk_ptr( + new BasicBlock(std::move(valid_label))); builder.SetInsertPoint(&*new_blk_ptr); - // Clone descriptor load - Instruction* new_load_inst = - builder.AddLoad(load_inst->type_id(), - load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx)); - uint32_t new_load_id = new_load_inst->result_id(); - get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id); - uint32_t new_image_id = new_load_id; - // Clone Image/SampledImage with new load, if needed - if (image_id != 0) { - if (image_inst->opcode() == SpvOp::SpvOpSampledImage) { - Instruction* new_image_inst = builder.AddBinaryOp( - image_inst->type_id(), SpvOpSampledImage, new_load_id, - image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx)); - new_image_id = new_image_inst->result_id(); - } else { - assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage"); - Instruction* new_image_inst = - builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id); - new_image_id = new_image_inst->result_id(); - } - get_decoration_mgr()->CloneDecorations(image_id, new_image_id); - } - // Clone original reference using new image code - std::unique_ptr new_ref_inst(ref_inst_itr->Clone(context())); - uint32_t ref_result_id = ref_inst_itr->result_id(); - uint32_t new_ref_id = 0; - if (ref_result_id != 0) { - new_ref_id = TakeNextId(); - new_ref_inst->SetResultId(new_ref_id); - } - new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id}); - // Register new reference and add to new block - builder.AddInstruction(std::move(new_ref_inst)); - if (new_ref_id != 0) - get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id); - // Close valid block and gen invalid block + uint32_t new_ref_id = CloneOriginalReference(ref, &builder); (void)builder.AddBranch(merge_blk_id); new_blocks->push_back(std::move(new_blk_ptr)); + // Gen invalid block new_blk_ptr.reset(new BasicBlock(std::move(invalid_label))); builder.SetInsertPoint(&*new_blk_ptr); - uint32_t u_index_id = GenUintCastCode(index_id, &builder); - GenDebugStreamWrite(instruction_idx, stage_idx, + 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); // Remember last invalid block id uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id(); // Gen zero for invalid reference - uint32_t ref_type_id = ref_inst_itr->type_id(); - // Close invalid block and gen merge block + uint32_t ref_type_id = ref->ref_inst->type_id(); (void)builder.AddBranch(merge_blk_id); new_blocks->push_back(std::move(new_blk_ptr)); + // Gen merge block new_blk_ptr.reset(new BasicBlock(std::move(merge_label))); builder.SetInsertPoint(&*new_blk_ptr); // Gen phi of new reference and zero, if necessary, and replace the // result id of the original reference with that of the Phi. Kill original - // reference and move in remainder of original block. + // reference. if (new_ref_id != 0) { Instruction* phi_inst = builder.AddPhi( ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id), last_invalid_blk_id}); - context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id()); + context()->ReplaceAllUsesWith(ref->ref_inst->result_id(), + phi_inst->result_id()); } - context()->KillInst(&*ref_inst_itr); - MovePostludeCode(ref_block_itr, &new_blk_ptr); - // Add remainder/merge block to new blocks new_blocks->push_back(std::move(new_blk_ptr)); + context()->KillInst(ref->ref_inst); +} + +void InstBindlessCheckPass::GenBoundsCheckCode( + BasicBlock::iterator ref_inst_itr, + UptrVectorIterator ref_block_itr, uint32_t stage_idx, + std::vector>* new_blocks) { + // Look for reference through indexed descriptor. If found, analyze and + // save components. If not, return. + ref_analysis ref; + if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return; + Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id); + if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return; + // 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); + 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); + Instruction* desc_type_inst = get_def_use_mgr()->GetDef(desc_type_id); + 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* 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_ || + desc_type_inst->opcode() != SpvOpTypeRuntimeArray) { + 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); + InstructionBuilder builder( + context(), &*new_blk_ptr, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + new_blocks->push_back(std::move(new_blk_ptr)); + uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds); + // If length id not yet set, descriptor array is runtime size so + // generate load of length from stage's debug input buffer. + if (length_id == 0) { + assert(desc_type_inst->opcode() == SpvOpTypeRuntimeArray && + "unexpected bindless type"); + length_id = GenDebugReadLength(ref.var_id, &builder); + } + // 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, + new_blocks); + // Move original block's remaining code into remainder/merge block and add + // to new blocks + BasicBlock* back_blk_ptr = &*new_blocks->back(); + MovePostludeCode(ref_block_itr, back_blk_ptr); +} + +void InstBindlessCheckPass::GenInitCheckCode( + 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; + // 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); + InstructionBuilder builder( + 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 + // 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 + // 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); + // Move original block's remaining code into remainder/merge block and add + // to new blocks + BasicBlock* back_blk_ptr = &*new_blocks->back(); + MovePostludeCode(ref_block_itr, back_blk_ptr); } void InstBindlessCheckPass::InitializeInstBindlessCheck() { @@ -258,9 +354,10 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() { break; } } - // If descriptor indexing extension and runtime array support enabled, - // create variable to descriptor set mapping. - if (ext_descriptor_indexing_defined_ && runtime_array_enabled_) + // If descriptor indexing extension and runtime array length support enabled, + // create variable mappings. Length support is always enabled if descriptor + // init check is enabled. + if (ext_descriptor_indexing_defined_ && input_length_enabled_) for (auto& anno : get_module()->annotations()) if (anno.opcode() == SpvOpDecorate) { if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet) @@ -273,18 +370,27 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() { } Pass::Status InstBindlessCheckPass::ProcessImpl() { - // Perform instrumentation on each entry point function in module + // Perform bindless bounds check on each entry point function in module InstProcessFunction pfn = [this](BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, - uint32_t instruction_idx, uint32_t stage_idx, + UptrVectorIterator ref_block_itr, uint32_t stage_idx, std::vector>* new_blocks) { - return GenBindlessCheckCode(ref_inst_itr, ref_block_itr, - instruction_idx, stage_idx, new_blocks); + return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx, + new_blocks); }; bool modified = InstProcessEntryPointCallTree(pfn); - // This pass does not update inst->blk info - context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping); + if (ext_descriptor_indexing_defined_ && input_init_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); + }; + modified |= InstProcessEntryPointCallTree(pfn); + } return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } 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 be782cef4..79c34f1fc 100644 --- a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h +++ b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h @@ -31,12 +31,14 @@ class InstBindlessCheckPass : public InstrumentPass { // For test harness only InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless), - runtime_array_enabled_(true) {} + input_length_enabled_(true), + input_init_enabled_(true) {} // For all other interfaces InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, - bool runtime_array_enable) + bool input_length_enable, bool input_init_enable) : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless), - runtime_array_enabled_(runtime_array_enable) {} + input_length_enabled_(input_length_enable), + input_init_enabled_(input_init_enable) {} ~InstBindlessCheckPass() override = default; @@ -46,32 +48,40 @@ class InstBindlessCheckPass : public InstrumentPass { const char* name() const override { return "inst-bindless-check-pass"; } private: - // Generate instructions into |builder| to read length of runtime descriptor - // array |var_id| from debug input buffer and return id of value. - uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder); - - // Initialize state for instrumenting bindless checking - void InitializeInstBindlessCheck(); - - // This function does bindless checking instrumentation on a single - // instruction. It is designed to be passed to + // 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 + // 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. + // + // The functions are designed to be passed to // InstrumentPass::InstProcessEntryPointCallTree(), which applies the // function to each instruction in a module and replaces the instruction // if warranted. // // If |ref_inst_itr| is a bindless reference, return in |new_blocks| the // result of instrumenting it with validation code within its block at - // |ref_block_itr|. Specifically, generate code to check that the index - // into the descriptor array is in-bounds. If the check passes, execute - // the remainder of the reference, otherwise write a record to the debug + // |ref_block_itr|. The validation code first executes a check for the + // specific condition called for. If the check passes, it executes + // the remainder of the reference, otherwise writes a record to the debug // output buffer stream including |function_idx, instruction_idx, stage_idx| - // and replace the reference with the null value of the original type. The + // and replaces the reference with the null value of the original type. The // block at |ref_block_itr| can just be replaced with the blocks in // |new_blocks|, which will contain at least two blocks. The last block will // comprise all instructions following |ref_inst_itr|, // preceded by a phi instruction. // - // This instrumentation pass utilizes GenDebugStreamWrite() to write its + // These instrumentation functions utilize GenDebugDirectRead() to read data + // from the debug input buffer, specifically the lengths of variable length + // descriptor arrays, and the initialization status of each descriptor. + // The format of the debug input buffer is documented in instrument.hpp. + // + // These instrumentation functions utilize GenDebugStreamWrite() to write its // error records. The validation-specific part of the error record will // have the format: // @@ -84,18 +94,77 @@ class InstBindlessCheckPass : public InstrumentPass { // // The Descriptor Array Size is the size of the descriptor array which was // indexed. - void GenBindlessCheckCode( - BasicBlock::iterator ref_inst_itr, - UptrVectorIterator ref_block_itr, uint32_t instruction_idx, - uint32_t stage_idx, std::vector>* new_blocks); + void GenBoundsCheckCode(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); + + // Generate instructions into |builder| to read length of runtime descriptor + // array |var_id| from debug input buffer and return id of value. + uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder); + + // Generate instructions into |builder| to read initialization status of + // descriptor array |image_id| at |index_id| from debug input buffer and + // return id of value. + uint32_t GenDebugReadInit(uint32_t image_id, uint32_t index_id, + InstructionBuilder* builder); + + // Analysis data for descriptor reference components, generated by + // AnalyzeDescriptorReference. It is necessary and sufficient for further + // analysis and regeneration of the reference. + typedef struct ref_analysis { + uint32_t image_id; + uint32_t load_id; + uint32_t ptr_id; + uint32_t var_id; + uint32_t index_id; + Instruction* ref_inst; + } ref_analysis; + + // Clone original original reference encapsulated by |ref| into |builder|. + // This may generate more than one instruction if neccessary. + uint32_t CloneOriginalReference(ref_analysis* ref, + InstructionBuilder* builder); + + // If |inst| references through a descriptor, (ie references into an image + // or buffer), return the id of the value it references. Else return 0. + uint32_t GetDescriptorValueId(Instruction* inst); + + // Analyze descriptor reference |ref_inst| and save components into |ref|. + // Return true if |ref_inst| is a descriptor reference, false otherwise. + bool AnalyzeDescriptorReference(Instruction* ref_inst, ref_analysis* ref); + + // Generate instrumentation code for generic test result |check_id|, starting + // with |builder| of block |new_blk_ptr|, adding new blocks to |new_blocks|. + // Generate conditional branch to a valid or invalid branch. Generate valid + // block which does original reference |ref|. Generate invalid block which + // 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, + 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. Pass::Status ProcessImpl(); // True if VK_EXT_descriptor_indexing is defined bool ext_descriptor_indexing_defined_; - // Enable instrumentation of runtime arrays - bool runtime_array_enabled_; + // Enable instrumentation of runtime array length checking + bool input_length_enabled_; + + // Enable instrumentation of descriptor initialization checking + bool input_init_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 5c26b5c73..668c98036 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp @@ -57,8 +57,7 @@ void InstrumentPass::MovePreludeCode( } void InstrumentPass::MovePostludeCode( - UptrVectorIterator ref_block_itr, - std::unique_ptr* new_blk_ptr) { + UptrVectorIterator ref_block_itr, BasicBlock* new_blk_ptr) { // new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id()))); // Move contents of original ref block. for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end(); @@ -77,7 +76,7 @@ void InstrumentPass::MovePostludeCode( same_block_post_[rid] = rid; } } - (*new_blk_ptr)->AddInstruction(std::move(mv_inst)); + new_blk_ptr->AddInstruction(std::move(mv_inst)); } } @@ -222,16 +221,14 @@ void InstrumentPass::GenDebugStreamWrite( (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args); } -uint32_t InstrumentPass::GenDebugDirectRead(uint32_t idx_id, - InstructionBuilder* builder) { - uint32_t input_buf_id = GetInputBufferId(); - uint32_t buf_uint_ptr_id = GetBufferUintPtrId(); - Instruction* ibuf_ac_inst = builder->AddTernaryOp( - buf_uint_ptr_id, SpvOpAccessChain, input_buf_id, - builder->GetUintConstantId(kDebugInputDataOffset), idx_id); - Instruction* load_inst = - builder->AddUnaryOp(GetUintId(), SpvOpLoad, ibuf_ac_inst->result_id()); - return load_inst->result_id(); +uint32_t InstrumentPass::GenDebugDirectRead( + const std::vector& offset_ids, InstructionBuilder* 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(); } bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const { @@ -242,7 +239,7 @@ void InstrumentPass::CloneSameBlockOps( std::unique_ptr* inst, std::unordered_map* same_blk_post, std::unordered_map* same_blk_pre, - std::unique_ptr* block_ptr) { + BasicBlock* block_ptr) { (*inst)->ForEachInId( [&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) { const auto map_itr = (*same_blk_post).find(*iid); @@ -259,7 +256,7 @@ void InstrumentPass::CloneSameBlockOps( sb_inst->SetResultId(nid); (*same_blk_post)[rid] = nid; *iid = nid; - (*block_ptr)->AddInstruction(std::move(sb_inst)); + block_ptr->AddInstruction(std::move(sb_inst)); } } else { // Reset same-block op operand. @@ -604,6 +601,81 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx, return output_func_id_; } +uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) { + uint32_t func_id = param2input_func_id_[param_cnt]; + if (func_id != 0) return func_id; + // Create input function for param_cnt + func_id = TakeNextId(); + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + std::vector param_types; + for (uint32_t c = 0; c < param_cnt; ++c) + param_types.push_back(type_mgr->GetType(GetUintId())); + analysis::Function func_ty(type_mgr->GetType(GetUintId()), param_types); + analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty); + std::unique_ptr func_inst(new Instruction( + get_module()->context(), SpvOpFunction, GetUintId(), func_id, + {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, + {SpvFunctionControlMaskNone}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {type_mgr->GetTypeInstruction(reg_func_ty)}}})); + get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst); + std::unique_ptr input_func = + MakeUnique(std::move(func_inst)); + // Add parameters + std::vector param_vec; + for (uint32_t c = 0; c < param_cnt; ++c) { + uint32_t pid = TakeNextId(); + param_vec.push_back(pid); + std::unique_ptr param_inst(new Instruction( + get_module()->context(), SpvOpFunctionParameter, GetUintId(), pid, {})); + get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst); + input_func->AddParameter(std::move(param_inst)); + } + // Create block + uint32_t blk_id = TakeNextId(); + std::unique_ptr blk_label(NewLabel(blk_id)); + std::unique_ptr new_blk_ptr = + MakeUnique(std::move(blk_label)); + InstructionBuilder builder( + context(), &*new_blk_ptr, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + // For each offset parameter, generate new offset with parameter, adding last + // loaded value if it exists, and load value from input buffer at new offset. + // Return last loaded value. + uint32_t buf_id = GetInputBufferId(); + uint32_t buf_uint_ptr_id = GetBufferUintPtrId(); + uint32_t last_value_id = 0; + for (uint32_t p = 0; p < param_cnt; ++p) { + uint32_t offset_id; + if (p == 0) { + offset_id = param_vec[0]; + } else { + Instruction* offset_inst = builder.AddBinaryOp( + GetUintId(), SpvOpIAdd, last_value_id, param_vec[p]); + offset_id = offset_inst->result_id(); + } + Instruction* ac_inst = builder.AddTernaryOp( + buf_uint_ptr_id, SpvOpAccessChain, buf_id, + builder.GetUintConstantId(kDebugInputDataOffset), offset_id); + Instruction* load_inst = + builder.AddUnaryOp(GetUintId(), SpvOpLoad, ac_inst->result_id()); + last_value_id = load_inst->result_id(); + } + (void)builder.AddInstruction(MakeUnique( + context(), SpvOpReturnValue, 0, 0, + std::initializer_list{{SPV_OPERAND_TYPE_ID, {last_value_id}}})); + // Close block and function and add function to module + new_blk_ptr->SetParent(&*input_func); + input_func->AddBasicBlock(std::move(new_blk_ptr)); + std::unique_ptr func_end_inst( + new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {})); + get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst); + input_func->SetFunctionEnd(std::move(func_end_inst)); + context()->AddFunction(std::move(input_func)); + param2input_func_id_[param_cnt] = func_id; + return func_id; +} + bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx, InstProcessFunction& pfn) { bool modified = false; @@ -614,23 +686,17 @@ bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx, ++function_idx; } std::vector> new_blks; - // Start count after function and param instructions - uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1; - func->ForEachParam( - [&instruction_idx](const Instruction*) { ++instruction_idx; }, true); // Using block iterators here because of block erasures and insertions. for (auto bi = func->begin(); bi != func->end(); ++bi) { - // Count block's label - ++instruction_idx; - for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) { - // Bump instruction count if debug instructions - instruction_idx += static_cast(ii->dbg_line_insts().size()); + for (auto ii = bi->begin(); ii != bi->end();) { // Generate instrumentation if warranted - pfn(ii, bi, instruction_idx, stage_idx, &new_blks); + pfn(ii, bi, stage_idx, &new_blks); if (new_blks.size() == 0) { ++ii; continue; } + // Add new blocks to label id map + for (auto& blk : new_blks) id2block_[blk->id()] = &*blk; // If there are new blocks we know there will always be two or // more, so update succeeding phis with label of new last block. size_t newBlocksSize = new_blks.size(); @@ -660,6 +726,9 @@ bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn, uint32_t stage_idx) { bool modified = false; std::unordered_set done; + // Don't process input and output functions + for (auto& ifn : param2input_func_id_) done.insert(ifn.second); + if (output_func_id_ != 0) done.insert(output_func_id_); // Process all functions from roots while (!roots->empty()) { const uint32_t fi = roots->front(); @@ -735,72 +804,68 @@ void InstrumentPass::InitializeInstrument() { } } - // Calculate instruction offset of first function - uint32_t pre_func_size = 0; + // Remember original instruction offsets + uint32_t module_offset = 0; Module* module = get_module(); for (auto& i : context()->capabilities()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->extensions()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->ext_inst_imports()) { (void)i; - ++pre_func_size; + ++module_offset; } - ++pre_func_size; // memory_model + ++module_offset; // memory_model for (auto& i : module->entry_points()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->execution_modes()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->debugs1()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->debugs2()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->debugs3()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->annotations()) { (void)i; - ++pre_func_size; + ++module_offset; } for (auto& i : module->types_values()) { - pre_func_size += 1; - pre_func_size += static_cast(i.dbg_line_insts().size()); + module_offset += 1; + module_offset += static_cast(i.dbg_line_insts().size()); } - funcIdx2offset_[0] = pre_func_size; - // Set instruction offsets for all other functions. - uint32_t func_idx = 1; - auto prev_fn = get_module()->begin(); - auto curr_fn = prev_fn; - for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) { - // Count function, end and param instructions - uint32_t func_size = 2; - prev_fn->ForEachParam([&func_size](const Instruction*) { ++func_size; }, - true); - for (auto& blk : *prev_fn) { + auto curr_fn = get_module()->begin(); + for (; curr_fn != get_module()->end(); ++curr_fn) { + // Count function instruction + module_offset += 1; + curr_fn->ForEachParam( + [&module_offset](const Instruction*) { module_offset += 1; }, true); + for (auto& blk : *curr_fn) { // Count label - func_size += 1; + module_offset += 1; for (auto& inst : blk) { - func_size += 1; - func_size += static_cast(inst.dbg_line_insts().size()); + module_offset += static_cast(inst.dbg_line_insts().size()); + uid2offset_[inst.unique_id()] = module_offset; + module_offset += 1; } } - funcIdx2offset_[func_idx] = funcIdx2offset_[func_idx - 1] + func_size; - ++prev_fn; - ++func_idx; + // Count function end instruction + module_offset += 1; } } diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.h b/3rdparty/spirv-tools/source/opt/instrument_pass.h index 003e64742..c4b97d638 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.h +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.h @@ -65,18 +65,16 @@ class InstrumentPass : public Pass { using cbb_ptr = const BasicBlock*; public: - using InstProcessFunction = std::function, uint32_t, uint32_t, - std::vector>*)>; + using InstProcessFunction = + std::function, + uint32_t, std::vector>*)>; ~InstrumentPass() override = default; IRContext::Analysis GetPreservedAnalyses() override { - return IRContext::kAnalysisDefUse | - IRContext::kAnalysisInstrToBlockMapping | - IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators | - IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId | - IRContext::kAnalysisConstants; + return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations | + IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap | + IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants; } protected: @@ -107,7 +105,7 @@ class InstrumentPass : public Pass { // Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr| // to be instrumented into block |new_blk_ptr|. void MovePostludeCode(UptrVectorIterator ref_block_itr, - std::unique_ptr* new_blk_ptr); + BasicBlock* new_blk_ptr); // Generate instructions in |builder| which will atomically fetch and // increment the size of the debug output buffer stream of the current @@ -196,11 +194,15 @@ class InstrumentPass : public Pass { InstructionBuilder* builder); // Generate in |builder| instructions to read the unsigned integer from the - // input buffer at offset |idx_id|. Return the result id. + // input buffer specified by the offsets in |offset_ids|. Given offsets + // o0, o1, ... oN, and input buffer ibuf, return the id for the value: + // + // ibuf[...ibuf[ibuf[o0]+o1]...+oN] // // The binding and the format of the input buffer is determined by each // specific validation, which is specified at the creation of the pass. - uint32_t GenDebugDirectRead(uint32_t idx_id, InstructionBuilder* builder); + uint32_t GenDebugDirectRead(const std::vector& offset_ids, + InstructionBuilder* builder); // Generate code to cast |value_id| to unsigned, if needed. Return // an id to the unsigned equivalent. @@ -247,10 +249,14 @@ class InstrumentPass : public Pass { uint32_t GetVec4UintId(); // Return id for output function. Define if it doesn't exist with - // |val_spec_arg_cnt| validation-specific uint32 arguments. + // |val_spec_param_cnt| validation-specific uint32 parameters. uint32_t GetStreamWriteFunctionId(uint32_t stage_idx, uint32_t val_spec_param_cnt); + // Return id for input function taking |param_cnt| uint32 parameters. Define + // if it doesn't exist. + uint32_t GetDirectReadFunctionId(uint32_t param_cnt); + // 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 @@ -312,7 +318,7 @@ class InstrumentPass : public Pass { std::unique_ptr* inst, std::unordered_map* same_blk_post, std::unordered_map* same_blk_pre, - std::unique_ptr* block_ptr); + BasicBlock* block_ptr); // Update phis in succeeding blocks to point to new last block void UpdateSucceedingPhis( @@ -331,8 +337,8 @@ class InstrumentPass : public Pass { // CFG. It has functionality not present in CFG. Consolidate. std::unordered_map id2block_; - // Map from function's position index to the offset of its first instruction - std::unordered_map funcIdx2offset_; + // Map from instruction's unique id to offset in original file. + std::unordered_map uid2offset_; // result id for OpConstantFalse uint32_t validation_id_; @@ -346,6 +352,9 @@ class InstrumentPass : public Pass { // id for debug output function uint32_t output_func_id_; + // ids for debug input functions + std::unordered_map param2input_func_id_; + // param count for output function uint32_t output_func_param_cnt_; diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp index 5fb487577..d4499a880 100644 --- a/3rdparty/spirv-tools/source/opt/optimizer.cpp +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -387,7 +387,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { } else if (pass_name == "replace-invalid-opcode") { RegisterPass(CreateReplaceInvalidOpcodePass()); } else if (pass_name == "inst-bindless-check") { - RegisterPass(CreateInstBindlessCheckPass(7, 23, true)); + RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true)); RegisterPass(CreateSimplificationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); @@ -807,10 +807,11 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() { Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, - bool runtime_array_enable) { + bool input_length_enable, + bool input_init_enable) { return MakeUnique( - MakeUnique(desc_set, shader_id, - runtime_array_enable)); + MakeUnique( + desc_set, shader_id, input_length_enable, input_init_enable)); } Optimizer::PassToken CreateCodeSinkingPass() { diff --git a/3rdparty/spirv-tools/source/reduce/CMakeLists.txt b/3rdparty/spirv-tools/source/reduce/CMakeLists.txt index 1a6ead479..468023122 100644 --- a/3rdparty/spirv-tools/source/reduce/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/reduce/CMakeLists.txt @@ -24,6 +24,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES reduction_opportunity_finder.h reduction_pass.h reduction_util.h + remove_block_reduction_opportunity.h + remove_block_reduction_opportunity_finder.h remove_instruction_reduction_opportunity.h remove_function_reduction_opportunity.h remove_function_reduction_opportunity_finder.h @@ -43,6 +45,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES reduction_opportunity.cpp reduction_pass.cpp reduction_util.cpp + remove_block_reduction_opportunity.cpp + remove_block_reduction_opportunity_finder.cpp remove_function_reduction_opportunity.cpp remove_function_reduction_opportunity_finder.cpp remove_instruction_reduction_opportunity.cpp diff --git a/3rdparty/spirv-tools/source/reduce/reducer.cpp b/3rdparty/spirv-tools/source/reduce/reducer.cpp index aaef3fb43..29d32688c 100644 --- a/3rdparty/spirv-tools/source/reduce/reducer.cpp +++ b/3rdparty/spirv-tools/source/reduce/reducer.cpp @@ -53,13 +53,25 @@ void Reducer::SetInterestingnessFunction( Reducer::ReductionResultStatus Reducer::Run( std::vector&& binary_in, std::vector* binary_out, - spv_const_reducer_options options) const { - std::vector current_binary = binary_in; + spv_const_reducer_options options, + spv_validator_options validator_options) const { + std::vector current_binary(std::move(binary_in)); + + spvtools::SpirvTools tools(impl_->target_env); + assert(tools.IsValid() && "Failed to create SPIRV-Tools interface"); // Keeps track of how many reduction attempts have been tried. Reduction // bails out if this reaches a given limit. uint32_t reductions_applied = 0; + // Initial state should be valid. + if (!tools.Validate(¤t_binary[0], current_binary.size(), + validator_options)) { + impl_->consumer(SPV_MSG_INFO, nullptr, {}, + "Initial binary is invalid; stopping."); + return Reducer::ReductionResultStatus::kInitialStateInvalid; + } + // Initial state should be interesting. if (!impl_->interestingness_function(current_binary, reductions_applied)) { impl_->consumer(SPV_MSG_INFO, nullptr, {}, @@ -106,7 +118,8 @@ Reducer::ReductionResultStatus Reducer::Run( << reductions_applied << "."; impl_->consumer(SPV_MSG_INFO, nullptr, {}, (stringstream.str().c_str())); - if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) { + if (!tools.Validate(&maybe_result[0], maybe_result.size(), + validator_options)) { // The reduction step went wrong and an invalid binary was produced. // By design, this shouldn't happen; this is a safeguard to stop an // invalid binary from being regarded as interesting. diff --git a/3rdparty/spirv-tools/source/reduce/reducer.h b/3rdparty/spirv-tools/source/reduce/reducer.h index 0b9bfa111..5ff6b5050 100644 --- a/3rdparty/spirv-tools/source/reduce/reducer.h +++ b/3rdparty/spirv-tools/source/reduce/reducer.h @@ -33,7 +33,8 @@ class Reducer { enum ReductionResultStatus { kInitialStateNotInteresting, kReachedStepLimit, - kComplete + kComplete, + kInitialStateInvalid }; // The type for a function that will take a binary and return true if and @@ -84,7 +85,8 @@ class Reducer { // A status is returned. ReductionResultStatus Run(std::vector&& binary_in, std::vector* binary_out, - spv_const_reducer_options options) const; + spv_const_reducer_options options, + spv_validator_options validator_options) const; private: struct Impl; // Opaque struct for holding internal data. diff --git a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp new file mode 100644 index 000000000..9b952c432 --- /dev/null +++ b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2019 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 "remove_block_reduction_opportunity.h" + +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace reduce { + +using namespace opt; + +RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity( + Function* function, BasicBlock* block) + : function_(function), block_(block) { + // precondition: + assert(block_->begin() != block_->end() && + block_->begin()->context()->get_def_use_mgr()->NumUsers( + block_->id()) == 0 && + "RemoveBlockReductionOpportunity block must have 0 references"); +} + +bool RemoveBlockReductionOpportunity::PreconditionHolds() { + // Removing other blocks cannot disable this opportunity. + return true; +} + +void RemoveBlockReductionOpportunity::Apply() { + // We need an iterator pointing to the block, hence the loop. + for (auto bi = function_->begin(); bi != function_->end(); ++bi) { + if (bi->id() == block_->id()) { + bi->KillAllInsts(true); + bi.Erase(); + // Block removal changes the function, but we don't use analyses, so no + // need to invalidate them. + return; + } + } + + assert(false && + "Unreachable: we should have found a block with the desired id."); +} + +} // namespace reduce +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.h b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.h new file mode 100644 index 000000000..45c0c178b --- /dev/null +++ b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity.h @@ -0,0 +1,46 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_ +#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_ + +#include "reduction_opportunity.h" +#include "source/opt/basic_block.h" +#include "source/opt/function.h" + +namespace spvtools { +namespace reduce { + +// An opportunity to remove an unreferenced block. +// See RemoveBlockReductionOpportunityFinder. +class RemoveBlockReductionOpportunity : public ReductionOpportunity { + public: + // Creates the opportunity to remove |block| in |function| in |context|. + RemoveBlockReductionOpportunity(opt::Function* function, + opt::BasicBlock* block); + + bool PreconditionHolds() override; + + protected: + void Apply() override; + + private: + opt::Function* function_; + opt::BasicBlock* block_; +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_ 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 new file mode 100644 index 000000000..a4ca1f234 --- /dev/null +++ b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/reduce/remove_block_reduction_opportunity_finder.h" +#include "source/reduce/remove_block_reduction_opportunity.h" + +namespace spvtools { +namespace reduce { + +using namespace opt; + +std::string RemoveBlockReductionOpportunityFinder::GetName() const { + return "RemoveBlockReductionOpportunityFinder"; +} + +std::vector> +RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities( + opt::IRContext* context) 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)); + } + } + } + return result; +} + +bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity( + 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()) { + return false; + } + + // Don't remove blocks with references. + 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)) { + return false; + } + + return true; +} + +bool RemoveBlockReductionOpportunityFinder:: + BlockInstructionsHaveNoOutsideReferences(opt::IRContext* context, + const Function::iterator& bi) { + // Get all instructions in block. + std::unordered_set instructions_in_block; + for (const Instruction& instruction : *bi) { + instructions_in_block.insert(instruction.unique_id()); + } + + // For each instruction... + for (const 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 { + // 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()) != + instructions_in_block.end(); + }); + + if (!no_uses_outside_block) { + return false; + } + } + + return true; +} + +} // namespace reduce +} // namespace spvtools 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 new file mode 100644 index 000000000..83cd04b57 --- /dev/null +++ b/3rdparty/spirv-tools/source/reduce/remove_block_reduction_opportunity_finder.h @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_ +#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_ + +#include "source/opt/function.h" +#include "source/reduce/reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { + +// A finder of opportunities to remove a block. The optimizer can remove dead +// code. However, the reducer needs to be able to remove at a fine-grained +// level. +class RemoveBlockReductionOpportunityFinder + : public ReductionOpportunityFinder { + public: + RemoveBlockReductionOpportunityFinder() = default; + + ~RemoveBlockReductionOpportunityFinder() override = default; + + std::string GetName() const final; + + std::vector> GetAvailableOpportunities( + opt::IRContext* context) 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); + + // Returns true if the instructions (definitions) in block |bi| have no + // references, except for references from inside the block itself. + static bool BlockInstructionsHaveNoOutsideReferences( + opt::IRContext* context, const opt::Function::iterator& bi); +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_ diff --git a/3rdparty/spirv-tools/source/val/validate_image.cpp b/3rdparty/spirv-tools/source/val/validate_image.cpp index 8a357ff34..72f9c51f8 100644 --- a/3rdparty/spirv-tools/source/val/validate_image.cpp +++ b/3rdparty/spirv-tools/source/val/validate_image.cpp @@ -800,10 +800,9 @@ spv_result_t ValidateSampledImage(ValidationState_t& _, // to OpPhi instructions or OpSelect instructions, or any instructions other // than the image lookup and query instructions specified to take an operand // whose type is OpTypeSampledImage. - std::vector consumers = _.getSampledImageConsumers(inst->id()); + std::vector consumers = _.getSampledImageConsumers(inst->id()); if (!consumers.empty()) { - for (auto consumer_id : consumers) { - const auto consumer_instr = _.FindDef(consumer_id); + for (auto consumer_instr : consumers) { const auto consumer_opcode = consumer_instr->opcode(); if (consumer_instr->block() != inst->block()) { return _.diag(SPV_ERROR_INVALID_ID, inst) @@ -814,7 +813,7 @@ spv_result_t ValidateSampledImage(ValidationState_t& _, << _.getIdName(inst->id()) << "' has a consumer in a different basic " "block. The consumer instruction is '" - << _.getIdName(consumer_id) << "'."; + << _.getIdName(consumer_instr->id()) << "'."; } // TODO: The following check is incomplete. We should also check that the // Sampled Image is not used by instructions that should not take @@ -828,8 +827,8 @@ spv_result_t ValidateSampledImage(ValidationState_t& _, "operands of Op" << spvOpcodeString(static_cast(consumer_opcode)) << "." << " Found result '" << _.getIdName(inst->id()) - << "' as an operand of '" << _.getIdName(consumer_id) - << "'."; + << "' as an operand of '" + << _.getIdName(consumer_instr->id()) << "'."; } } } diff --git a/3rdparty/spirv-tools/source/val/validation_state.cpp b/3rdparty/spirv-tools/source/val/validation_state.cpp index e6e5e2622..e542904ef 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.cpp +++ b/3rdparty/spirv-tools/source/val/validation_state.cpp @@ -540,15 +540,15 @@ void ValidationState_t::RegisterInstruction(Instruction* inst) { const uint32_t operand_word = inst->word(operand.offset); Instruction* operand_inst = FindDef(operand_word); if (operand_inst && SpvOpSampledImage == operand_inst->opcode()) { - RegisterSampledImageConsumer(operand_word, inst->id()); + RegisterSampledImageConsumer(operand_word, inst); } } } } -std::vector ValidationState_t::getSampledImageConsumers( +std::vector ValidationState_t::getSampledImageConsumers( uint32_t sampled_image_id) const { - std::vector result; + std::vector result; auto iter = sampled_image_consumers_.find(sampled_image_id); if (iter != sampled_image_consumers_.end()) { result = iter->second; @@ -557,8 +557,8 @@ std::vector ValidationState_t::getSampledImageConsumers( } void ValidationState_t::RegisterSampledImageConsumer(uint32_t sampled_image_id, - uint32_t consumer_id) { - sampled_image_consumers_[sampled_image_id].push_back(consumer_id); + Instruction* consumer) { + sampled_image_consumers_[sampled_image_id].push_back(consumer); } uint32_t ValidationState_t::getIdBound() const { return id_bound_; } diff --git a/3rdparty/spirv-tools/source/val/validation_state.h b/3rdparty/spirv-tools/source/val/validation_state.h index 94fa9456c..54d513936 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.h +++ b/3rdparty/spirv-tools/source/val/validation_state.h @@ -432,13 +432,13 @@ class ValidationState_t { return all_definitions_; } - /// Returns a vector containing the Ids of instructions that consume the given + /// Returns a vector containing the instructions that consume the given /// SampledImage id. - std::vector getSampledImageConsumers(uint32_t id) const; + std::vector getSampledImageConsumers(uint32_t id) const; /// Records cons_id as a consumer of sampled_image_id. void RegisterSampledImageConsumer(uint32_t sampled_image_id, - uint32_t cons_id); + Instruction* consumer); /// Returns the set of Global Variables. std::unordered_set& global_vars() { return global_vars_; } @@ -684,7 +684,8 @@ class ValidationState_t { /// Stores a vector of instructions that use the result of a given /// OpSampledImage instruction. - std::unordered_map> sampled_image_consumers_; + std::unordered_map> + sampled_image_consumers_; /// A map of operand IDs and their names defined by the OpName instruction std::unordered_map operand_names_; diff --git a/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp b/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp index 65dd7800e..deea1edcd 100644 --- a/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp +++ b/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp @@ -1926,7 +1926,7 @@ OpName %g_sAniso "g_sAniso" OpName %i_vTextureCoords "i.vTextureCoords" OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" OpDecorate %g_tColor DescriptorSet 1 -OpDecorate %g_tColor Binding 1 +OpDecorate %g_tColor Binding 2 OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0 OpDecorate %PerViewConstantBuffer_t Block OpDecorate %g_sAniso DescriptorSet 1 @@ -1980,7 +1980,7 @@ OpName %g_sAniso "g_sAniso" OpName %i_vTextureCoords "i.vTextureCoords" OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" OpDecorate %g_tColor DescriptorSet 1 -OpDecorate %g_tColor Binding 1 +OpDecorate %g_tColor Binding 2 OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0 OpDecorate %PerViewConstantBuffer_t Block OpDecorate %g_sAniso DescriptorSet 1 @@ -1988,15 +1988,15 @@ OpDecorate %g_sAniso Binding 0 OpDecorate %i_vTextureCoords Location 0 OpDecorate %_entryPointOutput_vColor Location 0 OpDecorate %_runtimearr_uint ArrayStride 4 -OpDecorate %_struct_41 Block -OpMemberDecorate %_struct_41 0 Offset 0 -OpDecorate %43 DescriptorSet 7 -OpDecorate %43 Binding 1 -OpDecorate %_struct_65 Block -OpMemberDecorate %_struct_65 0 Offset 0 -OpMemberDecorate %_struct_65 1 Offset 4 -OpDecorate %67 DescriptorSet 7 -OpDecorate %67 Binding 0 +OpDecorate %_struct_46 Block +OpMemberDecorate %_struct_46 0 Offset 0 +OpDecorate %48 DescriptorSet 7 +OpDecorate %48 Binding 1 +OpDecorate %_struct_71 Block +OpMemberDecorate %_struct_71 0 Offset 0 +OpMemberDecorate %_struct_71 1 Offset 4 +OpDecorate %73 DescriptorSet 7 +OpDecorate %73 Binding 0 OpDecorate %gl_FragCoord BuiltIn FragCoord %void = OpTypeVoid %10 = OpTypeFunction %void @@ -2026,16 +2026,17 @@ OpDecorate %gl_FragCoord BuiltIn FragCoord %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output %uint_0 = OpConstant %uint 0 %uint_2 = OpConstant %uint 2 +%41 = OpTypeFunction %uint %uint %uint %_runtimearr_uint = OpTypeRuntimeArray %uint -%_struct_41 = OpTypeStruct %_runtimearr_uint -%_ptr_StorageBuffer__struct_41 = OpTypePointer StorageBuffer %_struct_41 -%43 = OpVariable %_ptr_StorageBuffer__struct_41 StorageBuffer +%_struct_46 = OpTypeStruct %_runtimearr_uint +%_ptr_StorageBuffer__struct_46 = OpTypePointer StorageBuffer %_struct_46 +%48 = OpVariable %_ptr_StorageBuffer__struct_46 StorageBuffer %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint %bool = OpTypeBool -%59 = OpTypeFunction %void %uint %uint %uint %uint -%_struct_65 = OpTypeStruct %uint %_runtimearr_uint -%_ptr_StorageBuffer__struct_65 = OpTypePointer StorageBuffer %_struct_65 -%67 = OpVariable %_ptr_StorageBuffer__struct_65 StorageBuffer +%65 = OpTypeFunction %void %uint %uint %uint %uint +%_struct_71 = OpTypeStruct %uint %_runtimearr_uint +%_ptr_StorageBuffer__struct_71 = OpTypePointer StorageBuffer %_struct_71 +%73 = OpVariable %_ptr_StorageBuffer__struct_71 StorageBuffer %uint_9 = OpConstant %uint 9 %uint_4 = OpConstant %uint 4 %uint_23 = OpConstant %uint 23 @@ -2048,7 +2049,8 @@ OpDecorate %gl_FragCoord BuiltIn FragCoord %uint_7 = OpConstant %uint 7 %uint_8 = OpConstant %uint 8 %uint_59 = OpConstant %uint 59 -%110 = OpConstantNull %v4float +%116 = OpConstantNull %v4float +%119 = OpTypeFunction %uint %uint %uint %uint %uint )"; const std::string func_before = @@ -2077,84 +2079,436 @@ OpFunctionEnd %34 = OpLoad %16 %33 %35 = OpLoad %24 %g_sAniso %36 = OpSampledImage %26 %34 %35 -%45 = OpAccessChain %_ptr_StorageBuffer_uint %43 %uint_0 %uint_2 -%46 = OpLoad %uint %45 -%47 = OpIAdd %uint %46 %uint_1 -%48 = OpAccessChain %_ptr_StorageBuffer_uint %43 %uint_0 %47 -%49 = OpLoad %uint %48 -%51 = OpULessThan %bool %32 %49 -OpSelectionMerge %52 None -OpBranchConditional %51 %53 %54 -%53 = OpLabel -%55 = OpLoad %16 %33 -%56 = OpSampledImage %26 %55 %35 -%57 = OpImageSampleImplicitLod %v4float %56 %30 -OpBranch %52 -%54 = OpLabel -%109 = OpFunctionCall %void %58 %uint_59 %uint_0 %32 %49 -OpBranch %52 -%52 = OpLabel -%111 = OpPhi %v4float %57 %53 %110 %54 -OpStore %_entryPointOutput_vColor %111 +%55 = OpFunctionCall %uint %40 %uint_2 %uint_2 +%57 = OpULessThan %bool %32 %55 +OpSelectionMerge %58 None +OpBranchConditional %57 %59 %60 +%59 = OpLabel +%61 = OpLoad %16 %33 +%62 = OpSampledImage %26 %61 %35 +%136 = OpFunctionCall %uint %118 %uint_0 %uint_1 %uint_2 %32 +%137 = OpINotEqual %bool %136 %uint_0 +OpSelectionMerge %138 None +OpBranchConditional %137 %139 %140 +%139 = OpLabel +%141 = OpLoad %16 %33 +%142 = OpSampledImage %26 %141 %35 +%143 = OpImageSampleImplicitLod %v4float %142 %30 +OpBranch %138 +%140 = OpLabel +%144 = OpFunctionCall %void %64 %uint_59 %uint_1 %32 %uint_0 +OpBranch %138 +%138 = OpLabel +%145 = OpPhi %v4float %143 %139 %116 %140 +OpBranch %58 +%60 = OpLabel +%115 = OpFunctionCall %void %64 %uint_59 %uint_0 %32 %55 +OpBranch %58 +%58 = OpLabel +%117 = OpPhi %v4float %145 %138 %116 %60 +OpStore %_entryPointOutput_vColor %117 OpReturn OpFunctionEnd )"; - const std::string output_func = - R"(%58 = OpFunction %void None %59 -%60 = OpFunctionParameter %uint -%61 = OpFunctionParameter %uint + const std::string new_funcs = + R"(%40 = OpFunction %uint None %41 +%42 = OpFunctionParameter %uint +%43 = OpFunctionParameter %uint +%44 = OpLabel +%50 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %42 +%51 = OpLoad %uint %50 +%52 = OpIAdd %uint %51 %43 +%53 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %52 +%54 = OpLoad %uint %53 +OpReturnValue %54 +OpFunctionEnd +%64 = OpFunction %void None %65 +%66 = OpFunctionParameter %uint +%67 = OpFunctionParameter %uint +%68 = OpFunctionParameter %uint +%69 = OpFunctionParameter %uint +%70 = OpLabel +%74 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_0 +%77 = OpAtomicIAdd %uint %74 %uint_4 %uint_0 %uint_9 +%78 = OpIAdd %uint %77 %uint_9 +%79 = OpArrayLength %uint %73 1 +%80 = OpULessThanEqual %bool %78 %79 +OpSelectionMerge %81 None +OpBranchConditional %80 %82 %81 +%82 = OpLabel +%83 = OpIAdd %uint %77 %uint_0 +%84 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %83 +OpStore %84 %uint_9 +%86 = OpIAdd %uint %77 %uint_1 +%87 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %86 +OpStore %87 %uint_23 +%88 = OpIAdd %uint %77 %uint_2 +%89 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %88 +OpStore %89 %66 +%91 = OpIAdd %uint %77 %uint_3 +%92 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %91 +OpStore %92 %uint_4 +%95 = OpLoad %v4float %gl_FragCoord +%97 = OpBitcast %v4uint %95 +%98 = OpCompositeExtract %uint %97 0 +%99 = OpIAdd %uint %77 %uint_4 +%100 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %99 +OpStore %100 %98 +%101 = OpCompositeExtract %uint %97 1 +%103 = OpIAdd %uint %77 %uint_5 +%104 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %103 +OpStore %104 %101 +%106 = OpIAdd %uint %77 %uint_6 +%107 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %106 +OpStore %107 %67 +%109 = OpIAdd %uint %77 %uint_7 +%110 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %109 +OpStore %110 %68 +%112 = OpIAdd %uint %77 %uint_8 +%113 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %112 +OpStore %113 %69 +OpBranch %81 +%81 = OpLabel +OpReturn +OpFunctionEnd +%118 = OpFunction %uint None %119 +%120 = OpFunctionParameter %uint +%121 = OpFunctionParameter %uint +%122 = OpFunctionParameter %uint +%123 = OpFunctionParameter %uint +%124 = OpLabel +%125 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %120 +%126 = OpLoad %uint %125 +%127 = OpIAdd %uint %126 %121 +%128 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %127 +%129 = OpLoad %uint %128 +%130 = OpIAdd %uint %129 %122 +%131 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %130 +%132 = OpLoad %uint %131 +%133 = OpIAdd %uint %132 %123 +%134 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %133 +%135 = OpLoad %uint %134 +OpReturnValue %135 +OpFunctionEnd +)"; + + // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + defs_before + func_before, defs_after + func_after + new_funcs, true, + true); +} + +TEST_F(InstBindlessTest, NoInstrumentNonBindless) { + // This test verifies that the pass will correctly not instrument vanilla + // texture sample. + // + // Texture2D g_tColor; + // + // SamplerState g_sAniso; + // + // struct PS_INPUT + // { + // float2 vTextureCoords : TEXCOORD2; + // }; + // + // struct PS_OUTPUT + // { + // float4 vColor : SV_Target0; + // }; + // + // PS_OUTPUT MainPs(PS_INPUT i) + // { + // PS_OUTPUT ps_output; + // ps_output.vColor = + // g_tColor.Sample(g_sAniso, i.vTextureCoords.xy); + // return ps_output; + // } + + const std::string whole_file = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor +OpExecutionMode %MainPs OriginUpperLeft +OpSource HLSL 500 +OpName %MainPs "MainPs" +OpName %g_tColor "g_tColor" +OpName %g_sAniso "g_sAniso" +OpName %i_vTextureCoords "i.vTextureCoords" +OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" +OpDecorate %g_tColor DescriptorSet 0 +OpDecorate %g_tColor Binding 0 +OpDecorate %g_sAniso DescriptorSet 0 +OpDecorate %g_sAniso Binding 0 +OpDecorate %i_vTextureCoords Location 0 +OpDecorate %_entryPointOutput_vColor Location 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%12 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 +%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant +%14 = OpTypeSampler +%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14 +%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant +%16 = OpTypeSampledImage %12 +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output +%MainPs = OpFunction %void None %8 +%19 = OpLabel +%20 = OpLoad %v2float %i_vTextureCoords +%21 = OpLoad %12 %g_tColor +%22 = OpLoad %14 %g_sAniso +%23 = OpSampledImage %16 %21 %22 +%24 = OpImageSampleImplicitLod %v4float %23 %20 +OpStore %_entryPointOutput_vColor %24 +OpReturn +OpFunctionEnd +)"; + + // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(whole_file, whole_file, true, + true); +} + +TEST_F(InstBindlessTest, InstrumentInitCheckOnScalarDescriptor) { + // This test verifies that the pass will correctly instrument vanilla + // texture sample on a scalar descriptor with an initialization check if the + // SPV_EXT_descriptor_checking extension is enabled. This is the same shader + // as NoInstrumentNonBindless, but with the extension hacked on in the SPIR-V. + + const std::string defs_before = + R"(OpCapability Shader +OpExtension "SPV_EXT_descriptor_indexing" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor +OpExecutionMode %MainPs OriginUpperLeft +OpSource HLSL 500 +OpName %MainPs "MainPs" +OpName %g_tColor "g_tColor" +OpName %g_sAniso "g_sAniso" +OpName %i_vTextureCoords "i.vTextureCoords" +OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" +OpDecorate %g_tColor DescriptorSet 0 +OpDecorate %g_tColor Binding 0 +OpDecorate %g_sAniso DescriptorSet 0 +OpDecorate %g_sAniso Binding 0 +OpDecorate %i_vTextureCoords Location 0 +OpDecorate %_entryPointOutput_vColor Location 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%12 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 +%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant +%14 = OpTypeSampler +%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14 +%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant +%16 = OpTypeSampledImage %12 +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string defs_after = + R"(OpCapability Shader +OpExtension "SPV_EXT_descriptor_indexing" +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord +OpExecutionMode %MainPs OriginUpperLeft +OpSource HLSL 500 +OpName %MainPs "MainPs" +OpName %g_tColor "g_tColor" +OpName %g_sAniso "g_sAniso" +OpName %i_vTextureCoords "i.vTextureCoords" +OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" +OpDecorate %g_tColor DescriptorSet 0 +OpDecorate %g_tColor Binding 0 +OpDecorate %g_sAniso DescriptorSet 0 +OpDecorate %g_sAniso Binding 0 +OpDecorate %i_vTextureCoords Location 0 +OpDecorate %_entryPointOutput_vColor Location 0 +OpDecorate %_runtimearr_uint ArrayStride 4 +OpDecorate %_struct_35 Block +OpMemberDecorate %_struct_35 0 Offset 0 +OpDecorate %37 DescriptorSet 7 +OpDecorate %37 Binding 1 +OpDecorate %_struct_67 Block +OpMemberDecorate %_struct_67 0 Offset 0 +OpMemberDecorate %_struct_67 1 Offset 4 +OpDecorate %69 DescriptorSet 7 +OpDecorate %69 Binding 0 +OpDecorate %gl_FragCoord BuiltIn FragCoord +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%12 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 +%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant +%14 = OpTypeSampler +%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14 +%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant +%16 = OpTypeSampledImage %12 +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%28 = OpTypeFunction %uint %uint %uint %uint %uint +%_runtimearr_uint = OpTypeRuntimeArray %uint +%_struct_35 = OpTypeStruct %_runtimearr_uint +%_ptr_StorageBuffer__struct_35 = OpTypePointer StorageBuffer %_struct_35 +%37 = OpVariable %_ptr_StorageBuffer__struct_35 StorageBuffer +%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint +%bool = OpTypeBool +%uint_1 = OpConstant %uint 1 +%61 = OpTypeFunction %void %uint %uint %uint %uint +%_struct_67 = OpTypeStruct %uint %_runtimearr_uint +%_ptr_StorageBuffer__struct_67 = OpTypePointer StorageBuffer %_struct_67 +%69 = OpVariable %_ptr_StorageBuffer__struct_67 StorageBuffer +%uint_9 = OpConstant %uint 9 +%uint_4 = OpConstant %uint 4 +%uint_23 = OpConstant %uint 23 +%uint_2 = OpConstant %uint 2 +%uint_3 = OpConstant %uint 3 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%v4uint = OpTypeVector %uint 4 +%uint_5 = OpConstant %uint 5 +%uint_6 = OpConstant %uint 6 +%uint_7 = OpConstant %uint 7 +%uint_8 = OpConstant %uint 8 +%uint_40 = OpConstant %uint 40 +%113 = OpConstantNull %v4float +)"; + + const std::string func_before = + R"(%MainPs = OpFunction %void None %8 +%19 = OpLabel +%20 = OpLoad %v2float %i_vTextureCoords +%21 = OpLoad %12 %g_tColor +%22 = OpLoad %14 %g_sAniso +%23 = OpSampledImage %16 %21 %22 +%24 = OpImageSampleImplicitLod %v4float %23 %20 +OpStore %_entryPointOutput_vColor %24 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%MainPs = OpFunction %void None %8 +%19 = OpLabel +%20 = OpLoad %v2float %i_vTextureCoords +%21 = OpLoad %12 %g_tColor +%22 = OpLoad %14 %g_sAniso +%23 = OpSampledImage %16 %21 %22 +%50 = OpFunctionCall %uint %27 %uint_0 %uint_0 %uint_0 %uint_0 +%52 = OpINotEqual %bool %50 %uint_0 +OpSelectionMerge %54 None +OpBranchConditional %52 %55 %56 +%55 = OpLabel +%57 = OpLoad %12 %g_tColor +%58 = OpSampledImage %16 %57 %22 +%59 = OpImageSampleImplicitLod %v4float %58 %20 +OpBranch %54 +%56 = OpLabel +%112 = OpFunctionCall %void %60 %uint_40 %uint_1 %uint_0 %uint_0 +OpBranch %54 +%54 = OpLabel +%114 = OpPhi %v4float %59 %55 %113 %56 +OpStore %_entryPointOutput_vColor %114 +OpReturn +OpFunctionEnd +)"; + + const std::string new_funcs = + R"(%27 = OpFunction %uint None %28 +%29 = OpFunctionParameter %uint +%30 = OpFunctionParameter %uint +%31 = OpFunctionParameter %uint +%32 = OpFunctionParameter %uint +%33 = OpLabel +%39 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %29 +%40 = OpLoad %uint %39 +%41 = OpIAdd %uint %40 %30 +%42 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %41 +%43 = OpLoad %uint %42 +%44 = OpIAdd %uint %43 %31 +%45 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %44 +%46 = OpLoad %uint %45 +%47 = OpIAdd %uint %46 %32 +%48 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %47 +%49 = OpLoad %uint %48 +OpReturnValue %49 +OpFunctionEnd +%60 = OpFunction %void None %61 %62 = OpFunctionParameter %uint %63 = OpFunctionParameter %uint -%64 = OpLabel -%68 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_0 -%71 = OpAtomicIAdd %uint %68 %uint_4 %uint_0 %uint_9 -%72 = OpIAdd %uint %71 %uint_9 -%73 = OpArrayLength %uint %67 1 -%74 = OpULessThanEqual %bool %72 %73 -OpSelectionMerge %75 None -OpBranchConditional %74 %76 %75 -%76 = OpLabel -%77 = OpIAdd %uint %71 %uint_0 -%78 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %77 -OpStore %78 %uint_9 -%80 = OpIAdd %uint %71 %uint_1 -%81 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %80 -OpStore %81 %uint_23 -%82 = OpIAdd %uint %71 %uint_2 -%83 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %82 -OpStore %83 %60 -%85 = OpIAdd %uint %71 %uint_3 -%86 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %85 -OpStore %86 %uint_4 -%89 = OpLoad %v4float %gl_FragCoord -%91 = OpBitcast %v4uint %89 -%92 = OpCompositeExtract %uint %91 0 -%93 = OpIAdd %uint %71 %uint_4 -%94 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %93 -OpStore %94 %92 -%95 = OpCompositeExtract %uint %91 1 -%97 = OpIAdd %uint %71 %uint_5 -%98 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %97 -OpStore %98 %95 -%100 = OpIAdd %uint %71 %uint_6 -%101 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %100 -OpStore %101 %61 -%103 = OpIAdd %uint %71 %uint_7 -%104 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %103 -OpStore %104 %62 -%106 = OpIAdd %uint %71 %uint_8 -%107 = OpAccessChain %_ptr_StorageBuffer_uint %67 %uint_1 %106 -OpStore %107 %63 -OpBranch %75 -%75 = OpLabel +%64 = OpFunctionParameter %uint +%65 = OpFunctionParameter %uint +%66 = OpLabel +%70 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0 +%73 = OpAtomicIAdd %uint %70 %uint_4 %uint_0 %uint_9 +%74 = OpIAdd %uint %73 %uint_9 +%75 = OpArrayLength %uint %69 1 +%76 = OpULessThanEqual %bool %74 %75 +OpSelectionMerge %77 None +OpBranchConditional %76 %78 %77 +%78 = OpLabel +%79 = OpIAdd %uint %73 %uint_0 +%80 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %79 +OpStore %80 %uint_9 +%82 = OpIAdd %uint %73 %uint_1 +%83 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %82 +OpStore %83 %uint_23 +%85 = OpIAdd %uint %73 %uint_2 +%86 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %85 +OpStore %86 %62 +%88 = OpIAdd %uint %73 %uint_3 +%89 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %88 +OpStore %89 %uint_4 +%92 = OpLoad %v4float %gl_FragCoord +%94 = OpBitcast %v4uint %92 +%95 = OpCompositeExtract %uint %94 0 +%96 = OpIAdd %uint %73 %uint_4 +%97 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %96 +OpStore %97 %95 +%98 = OpCompositeExtract %uint %94 1 +%100 = OpIAdd %uint %73 %uint_5 +%101 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %100 +OpStore %101 %98 +%103 = OpIAdd %uint %73 %uint_6 +%104 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %103 +OpStore %104 %63 +%106 = OpIAdd %uint %73 %uint_7 +%107 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %106 +OpStore %107 %64 +%109 = OpIAdd %uint %73 %uint_8 +%110 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %109 +OpStore %110 %65 +OpBranch %77 +%77 = OpLabel OpReturn OpFunctionEnd )"; // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); SinglePassRunAndCheck( - defs_before + func_before, defs_after + func_after + output_func, true, + defs_before + func_before, defs_after + func_after + new_funcs, true, true); } diff --git a/3rdparty/spirv-tools/test/reduce/CMakeLists.txt b/3rdparty/spirv-tools/test/reduce/CMakeLists.txt index df9542bfd..4acab566d 100644 --- a/3rdparty/spirv-tools/test/reduce/CMakeLists.txt +++ b/3rdparty/spirv-tools/test/reduce/CMakeLists.txt @@ -20,6 +20,7 @@ add_spvtools_unittest(TARGET reduce reduce_test_util.cpp reduce_test_util.h reducer_test.cpp + remove_block_test.cpp remove_function_test.cpp remove_opname_instruction_test.cpp remove_unreferenced_instruction_test.cpp diff --git a/3rdparty/spirv-tools/test/reduce/reducer_test.cpp b/3rdparty/spirv-tools/test/reduce/reducer_test.cpp index 888724c3d..73effc35a 100644 --- a/3rdparty/spirv-tools/test/reduce/reducer_test.cpp +++ b/3rdparty/spirv-tools/test/reduce/reducer_test.cpp @@ -229,8 +229,12 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) { std::vector binary_out; spvtools::ReducerOptions reducer_options; reducer_options.set_step_limit(500); + spvtools::ValidatorOptions validator_options; - reducer.Run(std::move(binary_in), &binary_out, reducer_options); + Reducer::ReductionResultStatus status = reducer.Run( + std::move(binary_in), &binary_out, reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); CheckEqual(env, expected, binary_out); } @@ -255,7 +259,7 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) { %10 = OpLabel %3 = OpVariable %8 Function %4 = OpLoad %7 %3 - OpStore %3 %7 + OpStore %3 %9 OpReturn OpFunctionEnd )"; @@ -280,7 +284,7 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) { spv_target_env env = SPV_ENV_UNIVERSAL_1_3; Reducer reducer(env); - // Make ping-pong interesting very quickly, as there are not much + // Make ping-pong interesting very quickly, as there are not many // opportunities. PingPongInteresting ping_pong_interesting(1); reducer.SetMessageConsumer(NopDiagnostic); @@ -300,12 +304,16 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) { std::vector binary_out; spvtools::ReducerOptions reducer_options; reducer_options.set_step_limit(500); + spvtools::ValidatorOptions validator_options; - reducer.Run(std::move(binary_in), &binary_out, reducer_options); + Reducer::ReductionResultStatus status = reducer.Run( + std::move(binary_in), &binary_out, reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); CheckEqual(env, expected, binary_out); } } // namespace } // namespace reduce -} // namespace spvtools \ No newline at end of file +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/reduce/remove_block_test.cpp b/3rdparty/spirv-tools/test/reduce/remove_block_test.cpp new file mode 100644 index 000000000..71508c894 --- /dev/null +++ b/3rdparty/spirv-tools/test/reduce/remove_block_test.cpp @@ -0,0 +1,358 @@ +// Copyright (c) 2019 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 "reduce_test_util.h" +#include "source/opt/build_module.h" +#include "source/reduce/reduction_opportunity.h" +#include "source/reduce/remove_block_reduction_opportunity.h" +#include "source/reduce/remove_block_reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { +namespace { + +TEST(RemoveBlockReductionPassTest, BasicCheck) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %10 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %14 + %13 = OpLabel ; unreachable + OpStore %8 %9 + OpBranch %14 + %14 = OpLabel + OpStore %8 %10 + OpBranch %16 + %15 = OpLabel ; unreachable + OpStore %8 %11 + OpBranch %16 + %16 = OpLabel + OpStore %8 %12 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(2, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %10 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %14 + %14 = OpLabel + OpStore %8 %10 + OpBranch %16 + %15 = OpLabel + OpStore %8 %11 + OpBranch %16 + %16 = OpLabel + OpStore %8 %12 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_0, context.get()); + + ASSERT_TRUE(ops[1]->PreconditionHolds()); + ops[1]->TryToApply(); + + std::string after_op_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %10 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %14 + %14 = OpLabel + OpStore %8 %10 + OpBranch %16 + %16 = OpLabel + OpStore %8 %12 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_1, context.get()); +} + +TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) { + // Loop with unreachable merge and continue target. There should be no + // opportunities. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %13 + %13 = OpLabel + OpLoopMerge %16 %15 None + OpBranch %14 + %14 = OpLabel + OpReturn + %15 = OpLabel + OpBranch %13 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(0, ops.size()); +} + +TEST(RemoveBlockReductionPassTest, OneBlock) { + // Function with just one block. There should be no opportunities. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(0, ops.size()); +} + +TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) { + // A function with two unreachable blocks A -> B. A defines ID %9 and B uses + // %9. There are no references to A, but removing A would be invalid because + // of B's use of %9, so there should be no opportunities. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + %8 = OpLabel ; A + %9 = OpUndef %4 + OpBranch %10 + %10 = OpLabel ; B + %11 = OpIAdd %4 %6 %9 ; uses %9 from A, so A cannot be removed + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(0, ops.size()); +} + +TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) { + // Similar to the above test. + + // A function with two unreachable blocks A -> B. Both blocks create and use + // IDs, but the uses are contained within each block, so A should be removed. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + %8 = OpLabel ; A + %9 = OpUndef %4 ; define %9 + %10 = OpIAdd %4 %6 %9 ; use %9 + OpBranch %11 + %11 = OpLabel ; B + %12 = OpUndef %4 ; define %12 + %13 = OpIAdd %4 %6 %12 ; use %12 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + + ops[0]->TryToApply(); + + // Same as above, but block A is removed. + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + %11 = OpLabel + %12 = OpUndef %4 + %13 = OpIAdd %4 %6 %12 + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_0, context.get()); + + // Find opportunities again. There are no reference to B. B should now be + // removed. + + ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + + ops[0]->TryToApply(); + + // Same as above, but block B is removed. + std::string after_op_0_again = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_0_again, context.get()); +} + +} // namespace +} // namespace reduce +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp b/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp index b8a0b206e..4fc577159 100644 --- a/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp +++ b/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp @@ -52,6 +52,65 @@ class BlindlyRemoveGlobalValuesReductionOpportunityFinder } }; +// A dumb reduction opportunity that exists at the start of every function whose +// first instruction is an OpVariable instruction. When applied, the OpVariable +// instruction is duplicated (with a fresh result id). This allows each +// reduction step to increase the number of variables to check if the validator +// limits are enforced. +class OpVariableDuplicatorReductionOpportunity : public ReductionOpportunity { + public: + OpVariableDuplicatorReductionOpportunity(Function* function_) + : function_(function_) {} + + bool PreconditionHolds() override { + Instruction* first_instruction = &*function_->begin()[0].begin(); + return first_instruction->opcode() == SpvOpVariable; + } + + protected: + void Apply() override { + // Duplicate the first OpVariable instruction. + + Instruction* first_instruction = &*function_->begin()[0].begin(); + assert(first_instruction->opcode() == SpvOpVariable && + "Expected first instruction to be OpVariable"); + IRContext* context = first_instruction->context(); + Instruction* cloned_instruction = first_instruction->Clone(context); + cloned_instruction->SetResultId(context->TakeNextId()); + cloned_instruction->InsertBefore(first_instruction); + } + + private: + Function* function_; +}; + +// A reduction opportunity finder that finds +// OpVariableDuplicatorReductionOpportunity. +class OpVariableDuplicatorReductionOpportunityFinder + : public ReductionOpportunityFinder { + public: + OpVariableDuplicatorReductionOpportunityFinder() = default; + + ~OpVariableDuplicatorReductionOpportunityFinder() override = default; + + std::string GetName() const final { + return "LocalVariableAdderReductionOpportunityFinder"; + }; + + std::vector> GetAvailableOpportunities( + opt::IRContext* context) const final { + std::vector> result; + for (auto& function : *context->module()) { + Instruction* first_instruction = &*function.begin()[0].begin(); + if (first_instruction->opcode() == SpvOpVariable) { + result.push_back( + MakeUnique(&function)); + } + } + return result; + } +}; + TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) { // A module whose global values are all referenced, so that any application of // MakeModuleInvalidPass will make the module invalid. @@ -160,8 +219,12 @@ TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) { std::vector binary_out; spvtools::ReducerOptions reducer_options; reducer_options.set_step_limit(500); + spvtools::ValidatorOptions validator_options; - reducer.Run(std::move(binary_in), &binary_out, reducer_options); + Reducer::ReductionResultStatus status = reducer.Run( + std::move(binary_in), &binary_out, reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); // The reducer should have no impact. CheckEqual(env, original, binary_out); @@ -365,11 +428,145 @@ TEST(ValidationDuringReductionTest, CheckNotAlwaysInvalidCanMakeProgress) { std::vector binary_out; spvtools::ReducerOptions reducer_options; reducer_options.set_step_limit(500); + spvtools::ValidatorOptions validator_options; + + Reducer::ReductionResultStatus status = reducer.Run( + std::move(binary_in), &binary_out, reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); - reducer.Run(std::move(binary_in), &binary_out, reducer_options); CheckEqual(env, expected, binary_out); } +// Sets up a Reducer for use in the CheckValidationOptions test; avoids +// repetition. +void setupReducerForCheckValidationOptions(Reducer* reducer) { + reducer->SetMessageConsumer(NopDiagnostic); + + // Say that every module is interesting. + reducer->SetInterestingnessFunction( + [](const std::vector&, uint32_t) -> bool { return true; }); + + // Each "reduction" step will duplicate the first OpVariable instruction in + // the function. + reducer->AddReductionPass( + MakeUnique()); +} + +TEST(ValidationDuringReductionTest, CheckValidationOptions) { + // A module that only validates when the "skip-block-layout" validator option + // is used. Also, the entry point's first instruction creates a local + // variable; this instruction will be duplicated on each reduction step. + std::string original = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %2 "Main" %3 + OpSource HLSL 600 + OpDecorate %3 BuiltIn Position + OpDecorate %4 DescriptorSet 0 + OpDecorate %4 Binding 99 + OpDecorate %5 ArrayStride 16 + OpMemberDecorate %6 0 Offset 0 + OpMemberDecorate %6 1 Offset 32 + OpMemberDecorate %6 1 MatrixStride 16 + OpMemberDecorate %6 1 ColMajor + OpMemberDecorate %6 2 Offset 96 + OpMemberDecorate %6 3 Offset 100 + OpMemberDecorate %6 4 Offset 112 + OpMemberDecorate %6 4 MatrixStride 16 + OpMemberDecorate %6 4 ColMajor + OpMemberDecorate %6 5 Offset 176 + OpDecorate %6 Block + %7 = OpTypeFloat 32 + %8 = OpTypeVector %7 4 + %9 = OpTypeMatrix %8 4 + %10 = OpTypeVector %7 2 + %11 = OpTypeInt 32 1 + %12 = OpTypeInt 32 0 + %13 = OpConstant %12 2 + %14 = OpConstant %11 1 + %15 = OpConstant %11 5 + %5 = OpTypeArray %8 %13 + %6 = OpTypeStruct %5 %9 %12 %10 %9 %7 + %16 = OpTypePointer Uniform %6 + %17 = OpTypePointer Output %8 + %18 = OpTypeVoid + %19 = OpTypeFunction %18 + %20 = OpTypePointer Uniform %7 + %4 = OpVariable %16 Uniform + %3 = OpVariable %17 Output + %21 = OpTypePointer Function %11 + %2 = OpFunction %18 None %19 + %22 = OpLabel + %23 = OpVariable %21 Function + %24 = OpAccessChain %20 %4 %15 + %25 = OpLoad %7 %24 + %26 = OpCompositeConstruct %8 %25 %25 %25 %25 + OpStore %3 %26 + OpReturn + OpFunctionEnd + )"; + + spv_target_env env = SPV_ENV_UNIVERSAL_1_3; + std::vector binary_in; + SpirvTools t(env); + + ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption)); + std::vector binary_out; + spvtools::ReducerOptions reducer_options; + spvtools::ValidatorOptions validator_options; + + reducer_options.set_step_limit(3); + + // Reduction should fail because the initial state is invalid without the + // "skip-block-layout" validator option. Note that the interestingness test + // always returns true. + { + Reducer reducer(env); + setupReducerForCheckValidationOptions(&reducer); + + Reducer::ReductionResultStatus status = + reducer.Run(std::vector(binary_in), &binary_out, + reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kInitialStateInvalid); + } + + // Try again with validator option. + validator_options.SetSkipBlockLayout(true); + + // Reduction should hit step limit; module is seen as valid, interestingness + // test always succeeds, and the finder yields infinite opportunities. + { + Reducer reducer(env); + setupReducerForCheckValidationOptions(&reducer); + + Reducer::ReductionResultStatus status = + reducer.Run(std::vector(binary_in), &binary_out, + reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kReachedStepLimit); + } + + // Now set a limit on the number of local variables. + validator_options.SetUniversalLimit(spv_validator_limit_max_local_variables, + 2); + + // Reduction should "complete"; after one step, a local variable is added and + // the module becomes "invalid" given the validator limits. + { + Reducer reducer(env); + setupReducerForCheckValidationOptions(&reducer); + + Reducer::ReductionResultStatus status = + reducer.Run(std::vector(binary_in), &binary_out, + reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); + } +} + } // namespace } // namespace reduce } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_image_test.cpp b/3rdparty/spirv-tools/test/val/val_image_test.cpp index 79aecb25b..2d6fe29b4 100644 --- a/3rdparty/spirv-tools/test/val/val_image_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_image_test.cpp @@ -4452,6 +4452,38 @@ OpExtension "SPV_KHR_vulkan_memory_model" ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); } +// This example used to cause a seg fault on OpReturnValue, verifying it doesn't +// anymore. +TEST_F(ValidateImage, Issue2463NoSegFault) { + const std::string spirv = R"( + OpCapability Linkage + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %6 = OpTypeFunction %void + %float = OpTypeFloat 32 + %8 = OpTypeImage %float 3D 0 0 0 1 Unknown +%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8 + %10 = OpTypeSampler +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %12 = OpTypeSampledImage %8 + %13 = OpTypeFunction %12 %_ptr_UniformConstant_8 %_ptr_UniformConstant_10 + %23 = OpFunction %12 None %13 + %24 = OpFunctionParameter %_ptr_UniformConstant_8 + %25 = OpFunctionParameter %_ptr_UniformConstant_10 + %26 = OpLabel + %27 = OpLoad %8 %24 + %28 = OpLoad %10 %25 + %29 = OpSampledImage %12 %27 %28 + OpReturnValue %29 + OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/tools/reduce/reduce.cpp b/3rdparty/spirv-tools/tools/reduce/reduce.cpp index bbbfd1abc..a25e6d104 100644 --- a/3rdparty/spirv-tools/tools/reduce/reduce.cpp +++ b/3rdparty/spirv-tools/tools/reduce/reduce.cpp @@ -110,6 +110,13 @@ Options (in lexicographical order): reducer will take before giving up. --version Display reducer version information. + +Supported validator options are as follows. See `spirv-val --help` for details. + --relax-logical-pointer + --relax-block-layout + --scalar-block-layout + --skip-block-layout + --relax-struct-store )", program, program); } @@ -127,7 +134,8 @@ void ReduceDiagnostic(spv_message_level_t level, const char* /*source*/, ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file, const char** interestingness_test, - spvtools::ReducerOptions* reducer_options) { + spvtools::ReducerOptions* reducer_options, + spvtools::ValidatorOptions* validator_options) { uint32_t positional_arg_index = 0; for (int argi = 1; argi < argc; ++argi) { @@ -164,6 +172,16 @@ ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file, assert(!*interestingness_test); *interestingness_test = cur_arg; positional_arg_index++; + } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) { + validator_options->SetRelaxLogicalPointer(true); + } else if (0 == strcmp(cur_arg, "--relax-block-layout")) { + validator_options->SetRelaxBlockLayout(true); + } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) { + validator_options->SetScalarBlockLayout(true); + } else if (0 == strcmp(cur_arg, "--skip-block-layout")) { + validator_options->SetSkipBlockLayout(true); + } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { + validator_options->SetRelaxStructStore(true); } else { spvtools::Error(ReduceDiagnostic, nullptr, {}, "Too many positional arguments specified"); @@ -195,9 +213,10 @@ int main(int argc, const char** argv) { spv_target_env target_env = kDefaultEnvironment; spvtools::ReducerOptions reducer_options; + spvtools::ValidatorOptions validator_options; - ReduceStatus status = - ParseFlags(argc, argv, &in_file, &interestingness_test, &reducer_options); + ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test, + &reducer_options, &validator_options); if (status.action == REDUCE_STOP) { return status.code; @@ -255,8 +274,8 @@ int main(int argc, const char** argv) { } std::vector binary_out; - const auto reduction_status = - reducer.Run(std::move(binary_in), &binary_out, reducer_options); + const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out, + reducer_options, validator_options); if (reduction_status == Reducer::ReductionResultStatus::kInitialStateNotInteresting ||