|  | // 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/liveness.h" | 
|  |  | 
|  | #include "source/opt/ir_context.h" | 
|  |  | 
|  | namespace spvtools { | 
|  | namespace opt { | 
|  | namespace analysis { | 
|  | namespace { | 
|  | constexpr uint32_t kDecorationLocationInIdx = 2; | 
|  | constexpr uint32_t kOpDecorateMemberMemberInIdx = 1; | 
|  | constexpr uint32_t kOpDecorateMemberLocationInIdx = 3; | 
|  | constexpr uint32_t kOpDecorateBuiltInLiteralInIdx = 2; | 
|  | constexpr uint32_t kOpDecorateMemberBuiltInLiteralInIdx = 3; | 
|  | }  // namespace | 
|  |  | 
|  | LivenessManager::LivenessManager(IRContext* ctx) : ctx_(ctx), computed_(false) { | 
|  | // Liveness sets computed when queried | 
|  | } | 
|  |  | 
|  | void LivenessManager::InitializeAnalysis() { | 
|  | live_locs_.clear(); | 
|  | live_builtins_.clear(); | 
|  | // Mark all builtins live for frag shader. | 
|  | if (context()->GetStage() == spv::ExecutionModel::Fragment) { | 
|  | live_builtins_.insert(uint32_t(spv::BuiltIn::PointSize)); | 
|  | live_builtins_.insert(uint32_t(spv::BuiltIn::ClipDistance)); | 
|  | live_builtins_.insert(uint32_t(spv::BuiltIn::CullDistance)); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool LivenessManager::IsAnalyzedBuiltin(uint32_t bi) { | 
|  | // There are only three builtins that can be analyzed and removed between | 
|  | // two stages: PointSize, ClipDistance and CullDistance. All others are | 
|  | // always consumed implicitly by the downstream stage. | 
|  | const auto builtin = spv::BuiltIn(bi); | 
|  | return builtin == spv::BuiltIn::PointSize || | 
|  | builtin == spv::BuiltIn::ClipDistance || | 
|  | builtin == spv::BuiltIn::CullDistance; | 
|  | } | 
|  |  | 
|  | bool LivenessManager::AnalyzeBuiltIn(uint32_t id) { | 
|  | auto deco_mgr = context()->get_decoration_mgr(); | 
|  | bool saw_builtin = false; | 
|  | // Analyze all builtin decorations of |id|. | 
|  | (void)deco_mgr->ForEachDecoration( | 
|  | id, uint32_t(spv::Decoration::BuiltIn), | 
|  | [this, &saw_builtin](const Instruction& deco_inst) { | 
|  | saw_builtin = true; | 
|  | // No need to process builtins in frag shader. All assumed used. | 
|  | if (context()->GetStage() == spv::ExecutionModel::Fragment) return; | 
|  | uint32_t builtin = uint32_t(spv::BuiltIn::Max); | 
|  | if (deco_inst.opcode() == spv::Op::OpDecorate) | 
|  | builtin = | 
|  | deco_inst.GetSingleWordInOperand(kOpDecorateBuiltInLiteralInIdx); | 
|  | else if (deco_inst.opcode() == spv::Op::OpMemberDecorate) | 
|  | builtin = deco_inst.GetSingleWordInOperand( | 
|  | kOpDecorateMemberBuiltInLiteralInIdx); | 
|  | else | 
|  | assert(false && "unexpected decoration"); | 
|  | if (IsAnalyzedBuiltin(builtin)) live_builtins_.insert(builtin); | 
|  | }); | 
|  | return saw_builtin; | 
|  | } | 
|  |  | 
|  | void LivenessManager::MarkLocsLive(uint32_t start, uint32_t count) { | 
|  | auto finish = start + count; | 
|  | for (uint32_t u = start; u < finish; ++u) { | 
|  | live_locs_.insert(u); | 
|  | } | 
|  | } | 
|  |  | 
|  | uint32_t LivenessManager::GetLocSize(const analysis::Type* type) const { | 
|  | auto arr_type = type->AsArray(); | 
|  | if (arr_type) { | 
|  | auto comp_type = arr_type->element_type(); | 
|  | auto len_info = arr_type->length_info(); | 
|  | assert(len_info.words[0] == analysis::Array::LengthInfo::kConstant && | 
|  | "unexpected array length"); | 
|  | auto comp_len = len_info.words[1]; | 
|  | return comp_len * GetLocSize(comp_type); | 
|  | } | 
|  | auto struct_type = type->AsStruct(); | 
|  | if (struct_type) { | 
|  | uint32_t size = 0u; | 
|  | for (auto& el_type : struct_type->element_types()) | 
|  | size += GetLocSize(el_type); | 
|  | return size; | 
|  | } | 
|  | auto mat_type = type->AsMatrix(); | 
|  | if (mat_type) { | 
|  | auto cnt = mat_type->element_count(); | 
|  | auto comp_type = mat_type->element_type(); | 
|  | return cnt * GetLocSize(comp_type); | 
|  | } | 
|  | auto vec_type = type->AsVector(); | 
|  | if (vec_type) { | 
|  | auto comp_type = vec_type->element_type(); | 
|  | if (comp_type->AsInteger()) return 1; | 
|  | auto float_type = comp_type->AsFloat(); | 
|  | assert(float_type && "unexpected vector component type"); | 
|  | auto width = float_type->width(); | 
|  | if (width == 32 || width == 16) return 1; | 
|  | assert(width == 64 && "unexpected float type width"); | 
|  | auto comp_cnt = vec_type->element_count(); | 
|  | return (comp_cnt > 2) ? 2 : 1; | 
|  | } | 
|  | assert((type->AsInteger() || type->AsFloat()) && "unexpected input type"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | const analysis::Type* LivenessManager::GetComponentType( | 
|  | uint32_t index, const analysis::Type* agg_type) const { | 
|  | auto arr_type = agg_type->AsArray(); | 
|  | if (arr_type) return arr_type->element_type(); | 
|  | auto struct_type = agg_type->AsStruct(); | 
|  | if (struct_type) return struct_type->element_types()[index]; | 
|  | auto mat_type = agg_type->AsMatrix(); | 
|  | if (mat_type) return mat_type->element_type(); | 
|  | auto vec_type = agg_type->AsVector(); | 
|  | assert(vec_type && "unexpected non-aggregate type"); | 
|  | return vec_type->element_type(); | 
|  | } | 
|  |  | 
|  | uint32_t LivenessManager::GetLocOffset(uint32_t index, | 
|  | const analysis::Type* agg_type) const { | 
|  | auto arr_type = agg_type->AsArray(); | 
|  | if (arr_type) return index * GetLocSize(arr_type->element_type()); | 
|  | auto struct_type = agg_type->AsStruct(); | 
|  | if (struct_type) { | 
|  | uint32_t offset = 0u; | 
|  | uint32_t cnt = 0u; | 
|  | for (auto& el_type : struct_type->element_types()) { | 
|  | if (cnt == index) break; | 
|  | offset += GetLocSize(el_type); | 
|  | ++cnt; | 
|  | } | 
|  | return offset; | 
|  | } | 
|  | auto mat_type = agg_type->AsMatrix(); | 
|  | if (mat_type) return index * GetLocSize(mat_type->element_type()); | 
|  | auto vec_type = agg_type->AsVector(); | 
|  | assert(vec_type && "unexpected non-aggregate type"); | 
|  | auto comp_type = vec_type->element_type(); | 
|  | auto flt_type = comp_type->AsFloat(); | 
|  | if (flt_type && flt_type->width() == 64u && index >= 2u) return 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void LivenessManager::AnalyzeAccessChainLoc(const Instruction* ac, | 
|  | const analysis::Type** curr_type, | 
|  | uint32_t* offset, bool* no_loc, | 
|  | bool is_patch, bool input) { | 
|  | 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(); | 
|  | // For tesc, tese and geom input variables, and tesc output variables, | 
|  | // first array index does not contribute to offset. | 
|  | auto stage = context()->GetStage(); | 
|  | bool skip_first_index = false; | 
|  | if ((input && (stage == spv::ExecutionModel::TessellationControl || | 
|  | stage == spv::ExecutionModel::TessellationEvaluation || | 
|  | stage == spv::ExecutionModel::Geometry)) || | 
|  | (!input && stage == spv::ExecutionModel::TessellationControl)) | 
|  | skip_first_index = !is_patch; | 
|  | uint32_t ocnt = 0; | 
|  | ac->WhileEachInOperand([this, &ocnt, def_use_mgr, type_mgr, deco_mgr, | 
|  | curr_type, offset, no_loc, | 
|  | skip_first_index](const uint32_t* opnd) { | 
|  | if (ocnt >= 1) { | 
|  | // Skip first index's contribution to offset if indicated | 
|  | if (ocnt == 1 && skip_first_index) { | 
|  | auto arr_type = (*curr_type)->AsArray(); | 
|  | assert(arr_type && "unexpected wrapper type"); | 
|  | *curr_type = arr_type->element_type(); | 
|  | ocnt++; | 
|  | return true; | 
|  | } | 
|  | // If any non-constant index, mark the entire current object and return. | 
|  | auto idx_inst = def_use_mgr->GetDef(*opnd); | 
|  | if (idx_inst->opcode() != spv::Op::OpConstant) return false; | 
|  | // If current type is struct, look for location decoration on member and | 
|  | // reset offset if found. | 
|  | auto index = idx_inst->GetSingleWordInOperand(0); | 
|  | auto str_type = (*curr_type)->AsStruct(); | 
|  | if (str_type) { | 
|  | uint32_t loc = 0; | 
|  | auto str_type_id = type_mgr->GetId(str_type); | 
|  | bool no_mem_loc = deco_mgr->WhileEachDecoration( | 
|  | str_type_id, uint32_t(spv::Decoration::Location), | 
|  | [&loc, index, no_loc](const Instruction& deco) { | 
|  | assert(deco.opcode() == spv::Op::OpMemberDecorate && | 
|  | "unexpected decoration"); | 
|  | if (deco.GetSingleWordInOperand(kOpDecorateMemberMemberInIdx) == | 
|  | index) { | 
|  | loc = | 
|  | deco.GetSingleWordInOperand(kOpDecorateMemberLocationInIdx); | 
|  | *no_loc = false; | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | }); | 
|  | if (!no_mem_loc) { | 
|  | *offset = loc; | 
|  | *curr_type = GetComponentType(index, *curr_type); | 
|  | ocnt++; | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Update offset and current type based on constant index. | 
|  | *offset += GetLocOffset(index, *curr_type); | 
|  | *curr_type = GetComponentType(index, *curr_type); | 
|  | } | 
|  | ocnt++; | 
|  | return true; | 
|  | }); | 
|  | } | 
|  |  | 
|  | void LivenessManager::MarkRefLive(const Instruction* ref, Instruction* var) { | 
|  | analysis::TypeManager* type_mgr = context()->get_type_mgr(); | 
|  | analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr(); | 
|  | // Find variable location if present. | 
|  | uint32_t loc = 0; | 
|  | auto var_id = var->result_id(); | 
|  | bool no_loc = deco_mgr->WhileEachDecoration( | 
|  | var_id, uint32_t(spv::Decoration::Location), | 
|  | [&loc](const Instruction& deco) { | 
|  | assert(deco.opcode() == spv::Op::OpDecorate && "unexpected decoration"); | 
|  | 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; | 
|  | }); | 
|  | // If use is a load, mark all locations of var | 
|  | auto ptr_type = type_mgr->GetType(var->type_id())->AsPointer(); | 
|  | assert(ptr_type && "unexpected var type"); | 
|  | auto var_type = ptr_type->pointee_type(); | 
|  | if (ref->opcode() == spv::Op::OpLoad) { | 
|  | assert(!no_loc && "missing input variable location"); | 
|  | MarkLocsLive(loc, GetLocSize(var_type)); | 
|  | return; | 
|  | } | 
|  | // Mark just those locations indicated by access chain | 
|  | assert((ref->opcode() == spv::Op::OpAccessChain || | 
|  | ref->opcode() == spv::Op::OpInBoundsAccessChain) && | 
|  | "unexpected use of input variable"); | 
|  | // Traverse access chain, compute location offset and type of reference | 
|  | // through constant indices and mark those locs live. Assert if no location | 
|  | // found. | 
|  | uint32_t offset = loc; | 
|  | auto curr_type = var_type; | 
|  | AnalyzeAccessChainLoc(ref, &curr_type, &offset, &no_loc, is_patch); | 
|  | assert(!no_loc && "missing input variable location"); | 
|  | MarkLocsLive(offset, GetLocSize(curr_type)); | 
|  | } | 
|  |  | 
|  | void LivenessManager::ComputeLiveness() { | 
|  | InitializeAnalysis(); | 
|  | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); | 
|  | analysis::TypeManager* type_mgr = context()->get_type_mgr(); | 
|  | // Process all input 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::Input) { | 
|  | continue; | 
|  | } | 
|  | // If var is builtin, mark live if analyzed and continue to next variable | 
|  | auto var_id = var.result_id(); | 
|  | if (AnalyzeBuiltIn(var_id)) continue; | 
|  | // If interface block with builtin members, mark live if analyzed and | 
|  | // continue to next variable. Input interface blocks will only appear | 
|  | // in tesc, tese and geom shaders. Will need to strip off one level of | 
|  | // arrayness to get to block type. | 
|  | auto pte_type = ptr_type->pointee_type(); | 
|  | auto arr_type = pte_type->AsArray(); | 
|  | if (arr_type) { | 
|  | auto elt_type = arr_type->element_type(); | 
|  | auto str_type = elt_type->AsStruct(); | 
|  | if (str_type) { | 
|  | auto str_type_id = type_mgr->GetId(str_type); | 
|  | if (AnalyzeBuiltIn(str_type_id)) continue; | 
|  | } | 
|  | } | 
|  | // Mark all used locations of var live | 
|  | def_use_mgr->ForEachUser(var_id, [this, &var](Instruction* user) { | 
|  | auto op = user->opcode(); | 
|  | if (op == spv::Op::OpEntryPoint || op == spv::Op::OpName || | 
|  | op == spv::Op::OpDecorate || user->IsNonSemanticInstruction()) { | 
|  | return; | 
|  | } | 
|  | MarkRefLive(user, &var); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | void LivenessManager::GetLiveness(std::unordered_set<uint32_t>* live_locs, | 
|  | std::unordered_set<uint32_t>* live_builtins) { | 
|  | if (!computed_) { | 
|  | ComputeLiveness(); | 
|  | computed_ = true; | 
|  | } | 
|  | *live_locs = live_locs_; | 
|  | *live_builtins = live_builtins_; | 
|  | } | 
|  |  | 
|  | }  // namespace analysis | 
|  | }  // namespace opt | 
|  | }  // namespace spvtools |