diff options
-rw-r--r-- | include/spirv-tools/instrument.hpp | 32 | ||||
-rw-r--r-- | include/spirv-tools/optimizer.hpp | 22 | ||||
-rw-r--r-- | source/opt/inst_bindless_check_pass.cpp | 388 | ||||
-rw-r--r-- | source/opt/inst_bindless_check_pass.h | 115 | ||||
-rw-r--r-- | source/opt/instrument_pass.cpp | 181 | ||||
-rw-r--r-- | source/opt/instrument_pass.h | 39 | ||||
-rw-r--r-- | source/opt/optimizer.cpp | 9 | ||||
-rw-r--r-- | test/opt/inst_bindless_check_test.cpp | 522 |
8 files changed, 966 insertions, 342 deletions
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp index 01c7c6d1..9711b929 100644 --- a/include/spirv-tools/instrument.hpp +++ b/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/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index bbc70041..08ef5e67 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/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/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp index d9f221f9..47c81347 100644 --- a/source/opt/inst_bindless_check_pass.cpp +++ b/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<BasicBlock> ref_block_itr, uint32_t instruction_idx, - uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) { - // Look for reference through bindless descriptor. If not, return. - std::unique_ptr<BasicBlock> 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<Instruction> 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; - } - 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; + 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; } - // 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<std::unique_ptr<BasicBlock>>* 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<Instruction> merge_label(NewLabel(merge_blk_id)); std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id)); std::unique_ptr<Instruction> 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<BasicBlock> 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<Instruction> 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<BasicBlock> ref_block_itr, uint32_t stage_idx, + std::vector<std::unique_ptr<BasicBlock>>* 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<BasicBlock> 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<BasicBlock> ref_block_itr, uint32_t stage_idx, + std::vector<std::unique_ptr<BasicBlock>>* 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<BasicBlock> 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<BasicBlock> ref_block_itr, - uint32_t instruction_idx, uint32_t stage_idx, + UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* 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<BasicBlock> ref_block_itr, + uint32_t stage_idx, + std::vector<std::unique_ptr<BasicBlock>>* 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/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h index be782cef..79c34f1f 100644 --- a/source/opt/inst_bindless_check_pass.h +++ b/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<BasicBlock> ref_block_itr, uint32_t instruction_idx, - uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks); + void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr, + UptrVectorIterator<BasicBlock> ref_block_itr, + uint32_t stage_idx, + std::vector<std::unique_ptr<BasicBlock>>* new_blocks); + void GenInitCheckCode(BasicBlock::iterator ref_inst_itr, + UptrVectorIterator<BasicBlock> ref_block_itr, + uint32_t stage_idx, + std::vector<std::unique_ptr<BasicBlock>>* 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<std::unique_ptr<BasicBlock>>* 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<uint32_t, uint32_t> var2desc_set_; diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp index 5c26b5c7..668c9803 100644 --- a/source/opt/instrument_pass.cpp +++ b/source/opt/instrument_pass.cpp @@ -57,8 +57,7 @@ void InstrumentPass::MovePreludeCode( } void InstrumentPass::MovePostludeCode( - UptrVectorIterator<BasicBlock> ref_block_itr, - std::unique_ptr<BasicBlock>* new_blk_ptr) { + UptrVectorIterator<BasicBlock> 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<uint32_t>& offset_ids, InstructionBuilder* builder) { + // Call debug input function. Pass func_idx and offset ids as args. + uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size()); + uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt); + std::vector<uint32_t> 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<Instruction>* inst, std::unordered_map<uint32_t, uint32_t>* same_blk_post, std::unordered_map<uint32_t, Instruction*>* same_blk_pre, - std::unique_ptr<BasicBlock>* 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<const analysis::Type*> 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<Instruction> 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<Function> input_func = + MakeUnique<Function>(std::move(func_inst)); + // Add parameters + std::vector<uint32_t> param_vec; + for (uint32_t c = 0; c < param_cnt; ++c) { + uint32_t pid = TakeNextId(); + param_vec.push_back(pid); + std::unique_ptr<Instruction> 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<Instruction> blk_label(NewLabel(blk_id)); + std::unique_ptr<BasicBlock> new_blk_ptr = + MakeUnique<BasicBlock>(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<Instruction>( + context(), SpvOpReturnValue, 0, 0, + std::initializer_list<Operand>{{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<Instruction> 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<std::unique_ptr<BasicBlock>> 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<uint32_t>(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<uint32_t> 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<uint32_t>(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) { + module_offset += 1; + module_offset += static_cast<uint32_t>(i.dbg_line_insts().size()); + } + + auto curr_fn = get_module()->begin(); + for (; curr_fn != get_module()->end(); ++curr_fn) { + // Count function instruction + module_offset += 1; + curr_fn->ForEachParam( + [&module_offset](const Instruction*) { module_offset += 1; }, true); + for (auto& blk : *curr_fn) { // Count label - func_size += 1; + module_offset += 1; for (auto& inst : blk) { - func_size += 1; - func_size += static_cast<uint32_t>(inst.dbg_line_insts().size()); + module_offset += static_cast<uint32_t>(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/source/opt/instrument_pass.h b/source/opt/instrument_pass.h index 003e6474..c4b97d63 100644 --- a/source/opt/instrument_pass.h +++ b/source/opt/instrument_pass.h @@ -65,18 +65,16 @@ class InstrumentPass : public Pass { using cbb_ptr = const BasicBlock*; public: - using InstProcessFunction = std::function<void( - BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t, - std::vector<std::unique_ptr<BasicBlock>>*)>; + using InstProcessFunction = + std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>, + uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>; ~InstrumentPass() override = default; IRContext::Analysis GetPreservedAnalyses() override { - return IRContext::kAnalysisDefUse | - IRContext::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<BasicBlock> ref_block_itr, - std::unique_ptr<BasicBlock>* 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<uint32_t>& 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<Instruction>* inst, std::unordered_map<uint32_t, uint32_t>* same_blk_post, std::unordered_map<uint32_t, Instruction*>* same_blk_pre, - std::unique_ptr<BasicBlock>* 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<uint32_t, BasicBlock*> id2block_; - // Map from function's position index to the offset of its first instruction - std::unordered_map<uint32_t, uint32_t> funcIdx2offset_; + // Map from instruction's unique id to offset in original file. + std::unordered_map<uint32_t, uint32_t> 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<uint32_t, uint32_t> param2input_func_id_; + // param count for output function uint32_t output_func_param_cnt_; diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 5fb48757..d4499a88 100644 --- a/source/opt/optimizer.cpp +++ b/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<Optimizer::PassToken::Impl>( - MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id, - runtime_array_enable)); + MakeUnique<opt::InstBindlessCheckPass>( + desc_set, shader_id, input_length_enable, input_init_enable)); } Optimizer::PassToken CreateCodeSinkingPass() { diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp index 65dd7800..deea1edc 100644 --- a/test/opt/inst_bindless_check_test.cpp +++ b/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 +%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 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<InstBindlessCheckPass>( + 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<InstBindlessCheckPass>(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 -%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 +%114 = OpPhi %v4float %59 %55 %113 %56 +OpStore %_entryPointOutput_vColor %114 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"(%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<InstBindlessCheckPass>( - defs_before + func_before, defs_after + func_after + output_func, true, + defs_before + func_before, defs_after + func_after + new_funcs, true, true); } |