mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-02-17 20:52:36 +01:00
329 lines
12 KiB
C++
329 lines
12 KiB
C++
// Copyright (c) 2025 Google LLC
|
|
//
|
|
// 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/resolve_binding_conflicts_pass.h"
|
|
|
|
#include <algorithm>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "source/opt/decoration_manager.h"
|
|
#include "source/opt/def_use_manager.h"
|
|
#include "source/opt/instruction.h"
|
|
#include "source/opt/ir_builder.h"
|
|
#include "source/opt/ir_context.h"
|
|
#include "spirv/unified1/spirv.h"
|
|
|
|
namespace spvtools {
|
|
namespace opt {
|
|
|
|
// A VarBindingInfo contains the binding information for a single resource
|
|
// variable.
|
|
//
|
|
// Exactly one such object is created per resource variable in the
|
|
// module. In particular, when a resource variable is statically used by
|
|
// more than one entry point, those entry points share the same VarBindingInfo
|
|
// object for that variable.
|
|
struct VarBindingInfo {
|
|
const Instruction* const var;
|
|
const uint32_t descriptor_set;
|
|
Instruction* const binding_decoration;
|
|
|
|
// Returns the binding number.
|
|
uint32_t binding() const {
|
|
return binding_decoration->GetSingleWordInOperand(2);
|
|
}
|
|
// Sets the binding number to 'b'.
|
|
void updateBinding(uint32_t b) { binding_decoration->SetOperand(2, {b}); }
|
|
};
|
|
|
|
// The bindings in the same descriptor set that are used by an entry point.
|
|
using BindingList = std::vector<VarBindingInfo*>;
|
|
// A map from descriptor set number to the list of bindings in that descriptor
|
|
// set, as used by a particular entry point.
|
|
using DescriptorSets = std::unordered_map<uint32_t, BindingList>;
|
|
|
|
IRContext::Analysis ResolveBindingConflictsPass::GetPreservedAnalyses() {
|
|
// All analyses are kept up to date.
|
|
// At most this modifies the Binding numbers on variables.
|
|
return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping |
|
|
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
|
|
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
|
|
IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
|
|
IRContext::kAnalysisScalarEvolution |
|
|
IRContext::kAnalysisRegisterPressure |
|
|
IRContext::kAnalysisValueNumberTable |
|
|
IRContext::kAnalysisStructuredCFG | IRContext::kAnalysisBuiltinVarId |
|
|
IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisConstants |
|
|
IRContext::kAnalysisTypes | IRContext::kAnalysisDebugInfo |
|
|
IRContext::kAnalysisLiveness;
|
|
}
|
|
|
|
// Orders variable binding info objects.
|
|
// * The binding number is most signficant;
|
|
// * Then a sampler-like object compares greater than non-sampler like object.
|
|
// * Otherwise compare based on variable ID.
|
|
// This provides a total order among bindings in a descriptor set for a valid
|
|
// Vulkan module.
|
|
bool Less(const VarBindingInfo* const lhs, const VarBindingInfo* const rhs) {
|
|
if (lhs->binding() < rhs->binding()) return true;
|
|
if (lhs->binding() > rhs->binding()) return false;
|
|
|
|
// Examine types.
|
|
// In valid Vulkan the only conflict can occur between
|
|
// images and samplers. We only care about a specific
|
|
// comparison when one is a image-like thing and the other
|
|
// is a sampler-like thing of the same shape. So unwrap
|
|
// types until we hit one of those two.
|
|
|
|
auto* def_use_mgr = lhs->var->context()->get_def_use_mgr();
|
|
|
|
// Returns the type found by iteratively following pointer pointee type,
|
|
// or array element type.
|
|
auto unwrap = [&def_use_mgr](Instruction* ty) {
|
|
bool keep_going = true;
|
|
do {
|
|
switch (ty->opcode()) {
|
|
case spv::Op::OpTypePointer:
|
|
ty = def_use_mgr->GetDef(ty->GetSingleWordInOperand(1));
|
|
break;
|
|
case spv::Op::OpTypeArray:
|
|
case spv::Op::OpTypeRuntimeArray:
|
|
ty = def_use_mgr->GetDef(ty->GetSingleWordInOperand(0));
|
|
break;
|
|
default:
|
|
keep_going = false;
|
|
break;
|
|
}
|
|
} while (keep_going);
|
|
return ty;
|
|
};
|
|
|
|
auto* lhs_ty = unwrap(def_use_mgr->GetDef(lhs->var->type_id()));
|
|
auto* rhs_ty = unwrap(def_use_mgr->GetDef(rhs->var->type_id()));
|
|
if (lhs_ty->opcode() == rhs_ty->opcode()) {
|
|
// Pick based on variable ID.
|
|
return lhs->var->result_id() < rhs->var->result_id();
|
|
}
|
|
// A sampler is always greater than an image.
|
|
if (lhs_ty->opcode() == spv::Op::OpTypeSampler) {
|
|
return false;
|
|
}
|
|
if (rhs_ty->opcode() == spv::Op::OpTypeSampler) {
|
|
return true;
|
|
}
|
|
// Pick based on variable ID.
|
|
return lhs->var->result_id() < rhs->var->result_id();
|
|
}
|
|
|
|
// Summarizes the caller-callee relationships between functions in a module.
|
|
class CallGraph {
|
|
public:
|
|
// Returns the list of all functions statically reachable from entry points,
|
|
// where callees precede callers.
|
|
const std::vector<uint32_t>& CalleesBeforeCallers() const {
|
|
return visit_order_;
|
|
}
|
|
// Returns the list functions called from a given function.
|
|
const std::unordered_set<uint32_t>& Callees(uint32_t caller) {
|
|
return calls_[caller];
|
|
}
|
|
|
|
CallGraph(IRContext& context) {
|
|
// Populate calls_.
|
|
std::queue<uint32_t> callee_queue;
|
|
for (const auto& fn : *context.module()) {
|
|
auto& callees = calls_[fn.result_id()];
|
|
context.AddCalls(&fn, &callee_queue);
|
|
while (!callee_queue.empty()) {
|
|
callees.insert(callee_queue.front());
|
|
callee_queue.pop();
|
|
}
|
|
}
|
|
|
|
// Perform depth-first search, starting from each entry point.
|
|
// Populates visit_order_.
|
|
for (const auto& ep : context.module()->entry_points()) {
|
|
Visit(ep.GetSingleWordInOperand(1));
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Visits a function, recursively visiting its callees. Adds this ID
|
|
// to the visit_order after all callees have been visited.
|
|
void Visit(uint32_t func_id) {
|
|
if (visited_.count(func_id)) {
|
|
return;
|
|
}
|
|
visited_.insert(func_id);
|
|
for (auto callee_id : calls_[func_id]) {
|
|
Visit(callee_id);
|
|
}
|
|
visit_order_.push_back(func_id);
|
|
}
|
|
|
|
// Maps the ID of a function to the IDs of functions it calls.
|
|
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> calls_;
|
|
|
|
// IDs of visited functions;
|
|
std::unordered_set<uint32_t> visited_;
|
|
// IDs of functions, where callees precede callers.
|
|
std::vector<uint32_t> visit_order_;
|
|
};
|
|
|
|
// Returns vector binding info for all resource variables in the module.
|
|
auto GetVarBindings(IRContext& context) {
|
|
std::vector<VarBindingInfo> vars;
|
|
auto* deco_mgr = context.get_decoration_mgr();
|
|
for (auto& inst : context.module()->types_values()) {
|
|
if (inst.opcode() == spv::Op::OpVariable) {
|
|
Instruction* descriptor_set_deco = nullptr;
|
|
Instruction* binding_deco = nullptr;
|
|
for (auto* deco : deco_mgr->GetDecorationsFor(inst.result_id(), false)) {
|
|
switch (static_cast<spv::Decoration>(deco->GetSingleWordInOperand(1))) {
|
|
case spv::Decoration::DescriptorSet:
|
|
assert(!descriptor_set_deco);
|
|
descriptor_set_deco = deco;
|
|
break;
|
|
case spv::Decoration::Binding:
|
|
assert(!binding_deco);
|
|
binding_deco = deco;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (descriptor_set_deco && binding_deco) {
|
|
vars.push_back({&inst, descriptor_set_deco->GetSingleWordInOperand(2),
|
|
binding_deco});
|
|
}
|
|
}
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
// Merges the bindings from source into sink. Maintains order and uniqueness
|
|
// within a list of bindings.
|
|
void Merge(DescriptorSets& sink, const DescriptorSets& source) {
|
|
for (auto index_and_bindings : source) {
|
|
const uint32_t index = index_and_bindings.first;
|
|
const BindingList& src1 = index_and_bindings.second;
|
|
const BindingList& src2 = sink[index];
|
|
BindingList merged;
|
|
merged.resize(src1.size() + src2.size());
|
|
auto merged_end = std::merge(src1.begin(), src1.end(), src2.begin(),
|
|
src2.end(), merged.begin(), Less);
|
|
auto unique_end = std::unique(merged.begin(), merged_end);
|
|
merged.resize(unique_end - merged.begin());
|
|
sink[index] = std::move(merged);
|
|
}
|
|
}
|
|
|
|
// Resolves conflicts within this binding list, so the binding number on an
|
|
// item is at least one more than the binding number on the previous item.
|
|
// When this does not yet hold, increase the binding number on the second
|
|
// item in the pair. Returns true if any changes were applied.
|
|
bool ResolveConflicts(BindingList& bl) {
|
|
bool changed = false;
|
|
for (size_t i = 1; i < bl.size(); i++) {
|
|
const auto prev_num = bl[i - 1]->binding();
|
|
if (prev_num >= bl[i]->binding()) {
|
|
bl[i]->updateBinding(prev_num + 1);
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
Pass::Status ResolveBindingConflictsPass::Process() {
|
|
// Assumes the descriptor set and binding decorations are not provided
|
|
// via decoration groups. Decoration groups were deprecated in SPIR-V 1.3
|
|
// Revision 6. I have not seen any compiler generate them. --dneto
|
|
|
|
auto vars = GetVarBindings(*context());
|
|
|
|
// Maps a function ID to the variables used directly or indirectly by the
|
|
// function, organized into descriptor sets. Each descriptor set
|
|
// consists of a BindingList of distinct variables.
|
|
std::unordered_map<uint32_t, DescriptorSets> used_vars;
|
|
|
|
// Determine variables directly used by functions.
|
|
auto* def_use_mgr = context()->get_def_use_mgr();
|
|
for (auto& var : vars) {
|
|
std::unordered_set<uint32_t> visited_functions_for_var;
|
|
def_use_mgr->ForEachUser(var.var, [&](Instruction* user) {
|
|
if (auto* block = context()->get_instr_block(user)) {
|
|
auto* fn = block->GetParent();
|
|
assert(fn);
|
|
const auto fn_id = fn->result_id();
|
|
if (visited_functions_for_var.insert(fn_id).second) {
|
|
used_vars[fn_id][var.descriptor_set].push_back(&var);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Sort within a descriptor set by binding number.
|
|
for (auto& sets_for_fn : used_vars) {
|
|
for (auto& ds : sets_for_fn.second) {
|
|
BindingList& bindings = ds.second;
|
|
std::stable_sort(bindings.begin(), bindings.end(), Less);
|
|
}
|
|
}
|
|
|
|
// Propagate from callees to callers.
|
|
CallGraph call_graph(*context());
|
|
for (const uint32_t caller : call_graph.CalleesBeforeCallers()) {
|
|
DescriptorSets& caller_ds = used_vars[caller];
|
|
for (const uint32_t callee : call_graph.Callees(caller)) {
|
|
Merge(caller_ds, used_vars[callee]);
|
|
}
|
|
}
|
|
|
|
// At this point, the descriptor sets associated with each entry point
|
|
// capture exactly the set of resource variables statically used
|
|
// by the static call tree of that entry point.
|
|
|
|
// Resolve conflicts.
|
|
// VarBindingInfo objects may be shared between the bindings lists.
|
|
// Updating a binding in one list can require updating another list later.
|
|
// So repeat updates until settling.
|
|
|
|
// The union of BindingLists across all entry points.
|
|
std::vector<BindingList*> ep_bindings;
|
|
|
|
for (auto& ep : context()->module()->entry_points()) {
|
|
for (auto& ds : used_vars[ep.GetSingleWordInOperand(1)]) {
|
|
BindingList& bindings = ds.second;
|
|
ep_bindings.push_back(&bindings);
|
|
}
|
|
}
|
|
bool modified = false;
|
|
bool found_conflict;
|
|
do {
|
|
found_conflict = false;
|
|
for (BindingList* bl : ep_bindings) {
|
|
found_conflict |= ResolveConflicts(*bl);
|
|
}
|
|
modified |= found_conflict;
|
|
} while (found_conflict);
|
|
|
|
return modified ? Pass::Status::SuccessWithChange
|
|
: Pass::Status::SuccessWithoutChange;
|
|
}
|
|
|
|
} // namespace opt
|
|
} // namespace spvtools
|