// Copyright (c) 2017 The Khronos Group Inc. // Copyright (c) 2017 Valve Corporation // Copyright (c) 2017 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 "source/opt/local_single_store_elim_pass.h" #include "source/cfa.h" #include "source/util/string_utils.h" namespace spvtools { namespace opt { namespace { constexpr uint32_t kStoreValIdInIdx = 1; constexpr uint32_t kVariableInitIdInIdx = 1; } // namespace bool LocalSingleStoreElimPass::LocalSingleStoreElim(Function* func) { bool modified = false; // Check all function scope variables in |func|. BasicBlock* entry_block = &*func->begin(); for (Instruction& inst : *entry_block) { if (inst.opcode() != spv::Op::OpVariable) { break; } modified |= ProcessVariable(&inst); } return modified; } bool LocalSingleStoreElimPass::AllExtensionsSupported() const { // If any extension not in allowlist, return false for (auto& ei : get_module()->extensions()) { const std::string extName = ei.GetInOperand(0).AsString(); if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise // around unknown extended // instruction sets even if they are non-semantic for (auto& inst : context()->module()->ext_inst_imports()) { assert(inst.opcode() == spv::Op::OpExtInstImport && "Expecting an import of an extension's instruction set."); const std::string extension_name = inst.GetInOperand(0).AsString(); if (spvtools::utils::starts_with(extension_name, "NonSemantic.") && extension_name != "NonSemantic.Shader.DebugInfo.100") { return false; } } return true; } Pass::Status LocalSingleStoreElimPass::ProcessImpl() { // Assumes relaxed logical addressing only (see instruction.h) if (context()->get_feature_mgr()->HasCapability(spv::Capability::Addresses)) return Status::SuccessWithoutChange; // Do not process if any disallowed extensions are enabled if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; // Process all entry point functions ProcessFunction pfn = [this](Function* fp) { return LocalSingleStoreElim(fp); }; bool modified = context()->ProcessReachableCallTree(pfn); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } LocalSingleStoreElimPass::LocalSingleStoreElimPass() = default; Pass::Status LocalSingleStoreElimPass::Process() { InitExtensionAllowList(); return ProcessImpl(); } void LocalSingleStoreElimPass::InitExtensionAllowList() { extensions_allowlist_.insert({"SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_gcn_shader", "SPV_KHR_shader_ballot", "SPV_AMD_shader_ballot", "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", "SPV_NVX_multiview_per_view_attributes", "SPV_NV_viewport_array2", "SPV_NV_stereo_view_rendering", "SPV_NV_sample_mask_override_coverage", "SPV_NV_geometry_shader_passthrough", "SPV_AMD_texture_gather_bias_lod", "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_variable_pointers", "SPV_AMD_gpu_shader_int16", "SPV_KHR_post_depth_coverage", "SPV_KHR_shader_atomic_counter_ops", "SPV_EXT_shader_stencil_export", "SPV_EXT_shader_viewport_index_layer", "SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_fragment_mask", "SPV_EXT_fragment_fully_covered", "SPV_AMD_gpu_shader_half_float_fetch", "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1", "SPV_NV_shader_subgroup_partitioned", "SPV_EXT_descriptor_indexing", "SPV_NV_fragment_shader_barycentric", "SPV_NV_compute_shader_derivatives", "SPV_NV_shader_image_footprint", "SPV_NV_shading_rate", "SPV_NV_mesh_shader", "SPV_EXT_mesh_shader", "SPV_NV_ray_tracing", "SPV_KHR_ray_query", "SPV_EXT_fragment_invocation_density", "SPV_EXT_physical_storage_buffer", "SPV_KHR_physical_storage_buffer", "SPV_KHR_terminate_invocation", "SPV_KHR_subgroup_uniform_control_flow", "SPV_KHR_integer_dot_product", "SPV_EXT_shader_image_int64", "SPV_KHR_non_semantic_info", "SPV_KHR_uniform_group_instructions", "SPV_KHR_fragment_shader_barycentric", "SPV_KHR_vulkan_memory_model", "SPV_NV_bindless_texture", "SPV_EXT_shader_atomic_float_add", "SPV_EXT_fragment_shader_interlock", "SPV_NV_compute_shader_derivatives"}); } bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) { std::vector users; FindUses(var_inst, &users); Instruction* store_inst = FindSingleStoreAndCheckUses(var_inst, users); if (store_inst == nullptr) { return false; } bool all_rewritten; bool modified = RewriteLoads(store_inst, users, &all_rewritten); // If all uses are rewritten and the variable has a DebugDeclare and the // variable is not an aggregate, add a DebugValue after the store and remove // the DebugDeclare. uint32_t var_id = var_inst->result_id(); if (all_rewritten && context()->get_debug_info_mgr()->IsVariableDebugDeclared(var_id)) { const analysis::Type* var_type = context()->get_type_mgr()->GetType(var_inst->type_id()); const analysis::Type* store_type = var_type->AsPointer()->pointee_type(); if (!(store_type->AsStruct() || store_type->AsArray())) { modified |= RewriteDebugDeclares(store_inst, var_id); } } return modified; } bool LocalSingleStoreElimPass::RewriteDebugDeclares(Instruction* store_inst, uint32_t var_id) { uint32_t value_id = store_inst->GetSingleWordInOperand(1); bool modified = context()->get_debug_info_mgr()->AddDebugValueForVariable( store_inst, var_id, value_id, store_inst); modified |= context()->get_debug_info_mgr()->KillDebugDeclares(var_id); return modified; } Instruction* LocalSingleStoreElimPass::FindSingleStoreAndCheckUses( Instruction* var_inst, const std::vector& users) const { // Make sure there is exactly 1 store. Instruction* store_inst = nullptr; // If |var_inst| has an initializer, then that will count as a store. if (var_inst->NumInOperands() > 1) { store_inst = var_inst; } for (Instruction* user : users) { switch (user->opcode()) { case spv::Op::OpStore: // Since we are in the relaxed addressing mode, the use has to be the // base address of the store, and not the value being store. Otherwise, // we would have a pointer to a pointer to function scope memory, which // is not allowed. if (store_inst == nullptr) { store_inst = user; } else { // More than 1 store. return nullptr; } break; case spv::Op::OpAccessChain: case spv::Op::OpInBoundsAccessChain: if (FeedsAStore(user)) { // Has a partial store. Cannot propagate that. return nullptr; } break; case spv::Op::OpLoad: case spv::Op::OpImageTexelPointer: case spv::Op::OpName: case spv::Op::OpCopyObject: break; case spv::Op::OpExtInst: { auto dbg_op = user->GetCommonDebugOpcode(); if (dbg_op == CommonDebugInfoDebugDeclare || dbg_op == CommonDebugInfoDebugValue) { break; } return nullptr; } default: if (!user->IsDecoration()) { // Don't know if this instruction modifies the variable. // Conservatively assume it is a store. return nullptr; } break; } } return store_inst; } void LocalSingleStoreElimPass::FindUses( const Instruction* var_inst, std::vector* users) const { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); def_use_mgr->ForEachUser(var_inst, [users, this](Instruction* user) { users->push_back(user); if (user->opcode() == spv::Op::OpCopyObject) { FindUses(user, users); } }); } bool LocalSingleStoreElimPass::FeedsAStore(Instruction* inst) const { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); return !def_use_mgr->WhileEachUser(inst, [this](Instruction* user) { switch (user->opcode()) { case spv::Op::OpStore: return false; case spv::Op::OpAccessChain: case spv::Op::OpInBoundsAccessChain: case spv::Op::OpCopyObject: return !FeedsAStore(user); case spv::Op::OpLoad: case spv::Op::OpImageTexelPointer: case spv::Op::OpName: return true; default: // Don't know if this instruction modifies the variable. // Conservatively assume it is a store. return user->IsDecoration(); } }); } bool LocalSingleStoreElimPass::RewriteLoads( Instruction* store_inst, const std::vector& uses, bool* all_rewritten) { BasicBlock* store_block = context()->get_instr_block(store_inst); DominatorAnalysis* dominator_analysis = context()->GetDominatorAnalysis(store_block->GetParent()); uint32_t stored_id; if (store_inst->opcode() == spv::Op::OpStore) stored_id = store_inst->GetSingleWordInOperand(kStoreValIdInIdx); else stored_id = store_inst->GetSingleWordInOperand(kVariableInitIdInIdx); *all_rewritten = true; bool modified = false; for (Instruction* use : uses) { if (use->opcode() == spv::Op::OpStore) continue; auto dbg_op = use->GetCommonDebugOpcode(); if (dbg_op == CommonDebugInfoDebugDeclare || dbg_op == CommonDebugInfoDebugValue) continue; if (use->opcode() == spv::Op::OpLoad && dominator_analysis->Dominates(store_inst, use)) { modified = true; context()->KillNamesAndDecorates(use->result_id()); context()->ReplaceAllUsesWith(use->result_id(), stored_id); context()->KillInst(use); } else { *all_rewritten = false; } } return modified; } } // namespace opt } // namespace spvtools