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