| // Copyright (c) 2022 The Khronos Group Inc. |
| // Copyright (c) 2022 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/eliminate_dead_output_stores_pass.h" |
| |
| #include "source/opt/instruction.h" |
| #include "source/opt/ir_context.h" |
| |
| namespace spvtools { |
| namespace opt { |
| namespace { |
| constexpr uint32_t kDecorationLocationInIdx = 2; |
| constexpr uint32_t kOpDecorateMemberMemberInIdx = 1; |
| constexpr uint32_t kOpDecorateBuiltInLiteralInIdx = 2; |
| constexpr uint32_t kOpDecorateMemberBuiltInLiteralInIdx = 3; |
| constexpr uint32_t kOpAccessChainIdx0InIdx = 1; |
| constexpr uint32_t kOpConstantValueInIdx = 0; |
| } // namespace |
| |
| Pass::Status EliminateDeadOutputStoresPass::Process() { |
| // Current functionality assumes shader capability |
| if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader)) |
| return Status::SuccessWithoutChange; |
| Pass::Status status = DoDeadOutputStoreElimination(); |
| return status; |
| } |
| |
| void EliminateDeadOutputStoresPass::InitializeElimination() { |
| kill_list_.clear(); |
| } |
| |
| bool EliminateDeadOutputStoresPass::IsLiveBuiltin(uint32_t bi) { |
| return live_builtins_->find(bi) != live_builtins_->end(); |
| } |
| |
| bool EliminateDeadOutputStoresPass::AnyLocsAreLive(uint32_t start, |
| uint32_t count) { |
| auto finish = start + count; |
| for (uint32_t u = start; u < finish; ++u) { |
| if (live_locs_->find(u) != live_locs_->end()) return true; |
| } |
| return false; |
| } |
| |
| void EliminateDeadOutputStoresPass::KillAllStoresOfRef(Instruction* ref) { |
| analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| if (ref->opcode() == spv::Op::OpStore) { |
| kill_list_.push_back(ref); |
| return; |
| } |
| assert((ref->opcode() == spv::Op::OpAccessChain || |
| ref->opcode() == spv::Op::OpInBoundsAccessChain) && |
| "unexpected use of output variable"); |
| def_use_mgr->ForEachUser(ref, [this](Instruction* user) { |
| if (user->opcode() == spv::Op::OpStore) kill_list_.push_back(user); |
| }); |
| } |
| |
| void EliminateDeadOutputStoresPass::KillAllDeadStoresOfLocRef( |
| Instruction* ref, Instruction* var) { |
| analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr(); |
| analysis::LivenessManager* live_mgr = context()->get_liveness_mgr(); |
| // Find variable location if present. |
| uint32_t start_loc = 0; |
| auto var_id = var->result_id(); |
| bool no_loc = deco_mgr->WhileEachDecoration( |
| var_id, uint32_t(spv::Decoration::Location), |
| [&start_loc](const Instruction& deco) { |
| assert(deco.opcode() == spv::Op::OpDecorate && "unexpected decoration"); |
| start_loc = deco.GetSingleWordInOperand(kDecorationLocationInIdx); |
| return false; |
| }); |
| // Find patch decoration if present |
| bool is_patch = !deco_mgr->WhileEachDecoration( |
| var_id, uint32_t(spv::Decoration::Patch), [](const Instruction& deco) { |
| if (deco.opcode() != spv::Op::OpDecorate) |
| assert(false && "unexpected decoration"); |
| return false; |
| }); |
| // Compute offset and final type of reference. If no location found |
| // or any stored locations are live, return without removing stores. |
| auto ptr_type = type_mgr->GetType(var->type_id())->AsPointer(); |
| assert(ptr_type && "unexpected var type"); |
| auto var_type = ptr_type->pointee_type(); |
| uint32_t ref_loc = start_loc; |
| auto curr_type = var_type; |
| if (ref->opcode() == spv::Op::OpAccessChain || |
| ref->opcode() == spv::Op::OpInBoundsAccessChain) { |
| live_mgr->AnalyzeAccessChainLoc(ref, &curr_type, &ref_loc, &no_loc, |
| is_patch, /* input */ false); |
| } |
| if (no_loc || AnyLocsAreLive(ref_loc, live_mgr->GetLocSize(curr_type))) |
| return; |
| // Kill all stores based on this reference |
| KillAllStoresOfRef(ref); |
| } |
| |
| void EliminateDeadOutputStoresPass::KillAllDeadStoresOfBuiltinRef( |
| Instruction* ref, Instruction* var) { |
| auto deco_mgr = context()->get_decoration_mgr(); |
| auto def_use_mgr = context()->get_def_use_mgr(); |
| auto type_mgr = context()->get_type_mgr(); |
| auto live_mgr = context()->get_liveness_mgr(); |
| // Search for builtin decoration of base variable |
| uint32_t builtin = uint32_t(spv::BuiltIn::Max); |
| auto var_id = var->result_id(); |
| (void)deco_mgr->WhileEachDecoration( |
| var_id, uint32_t(spv::Decoration::BuiltIn), |
| [&builtin](const Instruction& deco) { |
| assert(deco.opcode() == spv::Op::OpDecorate && "unexpected decoration"); |
| builtin = deco.GetSingleWordInOperand(kOpDecorateBuiltInLiteralInIdx); |
| return false; |
| }); |
| // If analyzed builtin and not live, kill stores. |
| if (builtin != uint32_t(spv::BuiltIn::Max)) { |
| if (live_mgr->IsAnalyzedBuiltin(builtin) && !IsLiveBuiltin(builtin)) |
| KillAllStoresOfRef(ref); |
| return; |
| } |
| // Search for builtin decoration on indexed member |
| auto ref_op = ref->opcode(); |
| if (ref_op != spv::Op::OpAccessChain && |
| ref_op != spv::Op::OpInBoundsAccessChain) { |
| return; |
| } |
| uint32_t in_idx = kOpAccessChainIdx0InIdx; |
| analysis::Type* var_type = type_mgr->GetType(var->type_id()); |
| analysis::Pointer* ptr_type = var_type->AsPointer(); |
| auto curr_type = ptr_type->pointee_type(); |
| auto arr_type = curr_type->AsArray(); |
| if (arr_type) { |
| curr_type = arr_type->element_type(); |
| ++in_idx; |
| } |
| auto str_type = curr_type->AsStruct(); |
| auto str_type_id = type_mgr->GetId(str_type); |
| auto member_idx_id = ref->GetSingleWordInOperand(in_idx); |
| auto member_idx_inst = def_use_mgr->GetDef(member_idx_id); |
| assert(member_idx_inst->opcode() == spv::Op::OpConstant && |
| "unexpected non-constant index"); |
| auto ac_idx = member_idx_inst->GetSingleWordInOperand(kOpConstantValueInIdx); |
| (void)deco_mgr->WhileEachDecoration( |
| str_type_id, uint32_t(spv::Decoration::BuiltIn), |
| [ac_idx, &builtin](const Instruction& deco) { |
| assert(deco.opcode() == spv::Op::OpMemberDecorate && |
| "unexpected decoration"); |
| auto deco_idx = |
| deco.GetSingleWordInOperand(kOpDecorateMemberMemberInIdx); |
| if (deco_idx == ac_idx) { |
| builtin = |
| deco.GetSingleWordInOperand(kOpDecorateMemberBuiltInLiteralInIdx); |
| return false; |
| } |
| return true; |
| }); |
| assert(builtin != uint32_t(spv::BuiltIn::Max) && "builtin not found"); |
| // If analyzed builtin and not live, kill stores. |
| if (live_mgr->IsAnalyzedBuiltin(builtin) && !IsLiveBuiltin(builtin)) |
| KillAllStoresOfRef(ref); |
| } |
| |
| Pass::Status EliminateDeadOutputStoresPass::DoDeadOutputStoreElimination() { |
| // Current implementation only supports vert, tesc, tese, geom shaders |
| auto stage = context()->GetStage(); |
| if (stage != spv::ExecutionModel::Vertex && |
| stage != spv::ExecutionModel::TessellationControl && |
| stage != spv::ExecutionModel::TessellationEvaluation && |
| stage != spv::ExecutionModel::Geometry) |
| return Status::Failure; |
| InitializeElimination(); |
| analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr(); |
| // Process all output variables |
| for (auto& var : context()->types_values()) { |
| if (var.opcode() != spv::Op::OpVariable) { |
| continue; |
| } |
| analysis::Type* var_type = type_mgr->GetType(var.type_id()); |
| analysis::Pointer* ptr_type = var_type->AsPointer(); |
| if (ptr_type->storage_class() != spv::StorageClass::Output) { |
| continue; |
| } |
| // If builtin decoration on variable, process as builtin. |
| auto var_id = var.result_id(); |
| bool is_builtin = false; |
| if (deco_mgr->HasDecoration(var_id, uint32_t(spv::Decoration::BuiltIn))) { |
| is_builtin = true; |
| } else { |
| // If interface block with builtin members, process as builtin. |
| // Strip off outer array type if present. |
| auto curr_type = ptr_type->pointee_type(); |
| auto arr_type = curr_type->AsArray(); |
| if (arr_type) curr_type = arr_type->element_type(); |
| auto str_type = curr_type->AsStruct(); |
| if (str_type) { |
| auto str_type_id = type_mgr->GetId(str_type); |
| if (deco_mgr->HasDecoration(str_type_id, |
| uint32_t(spv::Decoration::BuiltIn))) |
| is_builtin = true; |
| } |
| } |
| // For each store or access chain using var, if dead builtin or all its |
| // locations are dead, kill store or all access chain's stores |
| def_use_mgr->ForEachUser( |
| var_id, [this, &var, is_builtin](Instruction* user) { |
| auto op = user->opcode(); |
| if (op == spv::Op::OpEntryPoint || op == spv::Op::OpName || |
| op == spv::Op::OpDecorate || user->IsNonSemanticInstruction()) |
| return; |
| if (is_builtin) |
| KillAllDeadStoresOfBuiltinRef(user, &var); |
| else |
| KillAllDeadStoresOfLocRef(user, &var); |
| }); |
| } |
| for (auto& kinst : kill_list_) context()->KillInst(kinst); |
| |
| return kill_list_.empty() ? Status::SuccessWithoutChange |
| : Status::SuccessWithChange; |
| } |
| |
| } // namespace opt |
| } // namespace spvtools |