| // 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 |