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