diff options
Diffstat (limited to 'source/opt/liveness.cpp')
-rw-r--r-- | source/opt/liveness.cpp | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/source/opt/liveness.cpp b/source/opt/liveness.cpp new file mode 100644 index 000000000..08b1c6ebc --- /dev/null +++ b/source/opt/liveness.cpp @@ -0,0 +1,330 @@ +// 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 { + +const uint32_t kDecorationLocationInIdx = 2; +const uint32_t kOpDecorateMemberMemberInIdx = 1; +const uint32_t kOpDecorateMemberLocationInIdx = 3; +const uint32_t kOpDecorateBuiltInLiteralInIdx = 2; +const uint32_t kOpDecorateMemberBuiltInLiteralInIdx = 3; + +} // namespace + +namespace spvtools { +namespace opt { +namespace analysis { + +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() == SpvExecutionModelFragment) { + live_builtins_.insert(SpvBuiltInPointSize); + live_builtins_.insert(SpvBuiltInClipDistance); + live_builtins_.insert(SpvBuiltInCullDistance); + } +} + +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. + return bi == SpvBuiltInPointSize || bi == SpvBuiltInClipDistance || + bi == SpvBuiltInCullDistance; +} + +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, SpvDecorationBuiltIn, + [this, &saw_builtin](const Instruction& deco_inst) { + saw_builtin = true; + // No need to process builtins in frag shader. All assumed used. + if (context()->GetStage() == SpvExecutionModelFragment) return; + uint32_t builtin = SpvBuiltInMax; + if (deco_inst.opcode() == SpvOpDecorate) + builtin = + deco_inst.GetSingleWordInOperand(kOpDecorateBuiltInLiteralInIdx); + else if (deco_inst.opcode() == SpvOpMemberDecorate) + 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 == SpvExecutionModelTessellationControl || + stage == SpvExecutionModelTessellationEvaluation || + stage == SpvExecutionModelGeometry)) || + (!input && stage == SpvExecutionModelTessellationControl)) + 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() != SpvOpConstant) 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, SpvDecorationLocation, + [&loc, index, no_loc](const Instruction& deco) { + assert(deco.opcode() == SpvOpMemberDecorate && + "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, SpvDecorationLocation, [&loc](const Instruction& deco) { + assert(deco.opcode() == SpvOpDecorate && "unexpected decoration"); + loc = deco.GetSingleWordInOperand(kDecorationLocationInIdx); + return false; + }); + // Find patch decoration if present + bool is_patch = !deco_mgr->WhileEachDecoration( + var_id, SpvDecorationPatch, [](const Instruction& deco) { + if (deco.opcode() != SpvOpDecorate) + 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() == SpvOpLoad) { + assert(!no_loc && "missing input variable location"); + MarkLocsLive(loc, GetLocSize(var_type)); + return; + } + // Mark just those locations indicated by access chain + assert((ref->opcode() == SpvOpAccessChain || + ref->opcode() == SpvOpInBoundsAccessChain) && + "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() != SpvOpVariable) { + continue; + } + analysis::Type* var_type = type_mgr->GetType(var.type_id()); + analysis::Pointer* ptr_type = var_type->AsPointer(); + if (ptr_type->storage_class() != SpvStorageClassInput) { + 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 == SpvOpEntryPoint || op == SpvOpName || op == SpvOpDecorate) + 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 |