SPIRV-Tools/source/opt/liveness.cpp
Nathan Gauër 1a7f71afb4
clean: constexpr-ify and unify anon namespace use (#4991)
Constexpr guaranteed no runtime init in addition to const semantics.
Moving all opt/ to constexpr.
Moving all compile-unit statics to anonymous namespaces to uniformize
the method used (anonymous namespace vs static has the same behavior
here AFAIK).

Signed-off-by: Nathan Gauër <brioche@google.com>
2022-11-17 19:02:50 +01:00

333 lines
13 KiB
C++

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