// 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* live_locs, std::unordered_set* live_builtins) { if (!computed_) { ComputeLiveness(); computed_ = true; } *live_locs = live_locs_; *live_builtins = live_builtins_; } } // namespace analysis } // namespace opt } // namespace spvtools