#include "source/lint/divergence_analysis.h"
#include "source/opt/basic_block.h"
#include "source/opt/control_dependence.h"
#include "source/opt/dataflow.h"
#include "source/opt/function.h"
#include "source/opt/instruction.h"
namespace spvtools {
namespace lint {
void DivergenceAnalysis::EnqueueSuccessors(opt::Instruction* inst) {
uint32_t block_id;
if (inst->IsBlockTerminator()) {
block_id = context().get_instr_block(inst)->id();
} else if (inst->opcode() == spv::Op::OpLabel) {
block_id = inst->result_id();
opt::BasicBlock* bb = context().cfg()->block(block_id);
bb->ForEachPhiInst([this](opt::Instruction* phi) { Enqueue(phi); });
} else {
opt::ForwardDataFlowAnalysis::EnqueueUsers(inst);
return;
}
if (!cd_.HasBlock(block_id)) {
return;
}
for (const spvtools::opt::ControlDependence& dep :
cd_.GetDependenceTargets(block_id)) {
opt::Instruction* target_inst =
context().cfg()->block(dep.target_bb_id())->GetLabelInst();
Enqueue(target_inst);
}
}
opt::DataFlowAnalysis::VisitResult DivergenceAnalysis::Visit(
opt::Instruction* inst) {
if (inst->opcode() == spv::Op::OpLabel) {
return VisitBlock(inst->result_id());
} else {
return VisitInstruction(inst);
}
}
opt::DataFlowAnalysis::VisitResult DivergenceAnalysis::VisitBlock(uint32_t id) {
if (!cd_.HasBlock(id)) {
return opt::DataFlowAnalysis::VisitResult::kResultFixed;
}
DivergenceLevel& cur_level = divergence_[id];
if (cur_level == DivergenceLevel::kDivergent) {
return opt::DataFlowAnalysis::VisitResult::kResultFixed;
}
DivergenceLevel orig = cur_level;
for (const spvtools::opt::ControlDependence& dep :
cd_.GetDependenceSources(id)) {
if (divergence_[dep.source_bb_id()] > cur_level) {
cur_level = divergence_[dep.source_bb_id()];
divergence_source_[id] = dep.source_bb_id();
} else if (dep.source_bb_id() != 0) {
uint32_t condition_id = dep.GetConditionID(*context().cfg());
DivergenceLevel dep_level = divergence_[condition_id];
if (follow_unconditional_branches_[dep.branch_target_bb_id()] !=
follow_unconditional_branches_[dep.target_bb_id()]) {
if (dep_level == DivergenceLevel::kPartiallyUniform) {
dep_level = DivergenceLevel::kDivergent;
}
}
if (dep_level > cur_level) {
cur_level = dep_level;
divergence_source_[id] = condition_id;
divergence_dependence_source_[id] = dep.source_bb_id();
}
}
}
return cur_level > orig ? VisitResult::kResultChanged
: VisitResult::kResultFixed;
}
opt::DataFlowAnalysis::VisitResult DivergenceAnalysis::VisitInstruction(
opt::Instruction* inst) {
if (inst->IsBlockTerminator()) {
return VisitResult::kResultChanged;
}
if (!inst->HasResultId()) {
return VisitResult::kResultFixed;
}
uint32_t id = inst->result_id();
DivergenceLevel& cur_level = divergence_[id];
if (cur_level == DivergenceLevel::kDivergent) {
return opt::DataFlowAnalysis::VisitResult::kResultFixed;
}
DivergenceLevel orig = cur_level;
cur_level = ComputeInstructionDivergence(inst);
return cur_level > orig ? VisitResult::kResultChanged
: VisitResult::kResultFixed;
}
DivergenceAnalysis::DivergenceLevel
DivergenceAnalysis::ComputeInstructionDivergence(opt::Instruction* inst) {
uint32_t id = inst->result_id();
if (inst->opcode() == spv::Op::OpFunctionParameter) {
divergence_source_[id] = 0;
return divergence_[id] = DivergenceLevel::kDivergent;
} else if (inst->IsLoad()) {
spvtools::opt::Instruction* var = inst->GetBaseAddress();
if (var->opcode() != spv::Op::OpVariable) {
divergence_source_[id] = 0;
return DivergenceLevel::kDivergent;
}
DivergenceLevel ret = ComputeVariableDivergence(var);
if (ret > DivergenceLevel::kUniform) {
divergence_source_[inst->result_id()] = 0;
}
return divergence_[id] = ret;
}
DivergenceLevel ret = DivergenceLevel::kUniform;
inst->ForEachInId([this, inst, &ret](const uint32_t* op) {
if (!op) return;
if (divergence_[*op] > ret) {
divergence_source_[inst->result_id()] = *op;
ret = divergence_[*op];
}
});
divergence_[inst->result_id()] = ret;
return ret;
}
DivergenceAnalysis::DivergenceLevel
DivergenceAnalysis::ComputeVariableDivergence(opt::Instruction* var) {
uint32_t type_id = var->type_id();
spvtools::opt::analysis::Pointer* type =
context().get_type_mgr()->GetType(type_id)->AsPointer();
assert(type != nullptr);
uint32_t def_id = var->result_id();
DivergenceLevel ret;
switch (type->storage_class()) {
case spv::StorageClass::Function:
case spv::StorageClass::Generic:
case spv::StorageClass::AtomicCounter:
case spv::StorageClass::StorageBuffer:
case spv::StorageClass::PhysicalStorageBuffer:
case spv::StorageClass::Output:
case spv::StorageClass::Workgroup:
case spv::StorageClass::Image:
case spv::StorageClass::Private:
ret = DivergenceLevel::kDivergent;
break;
case spv::StorageClass::Input:
ret = DivergenceLevel::kDivergent;
context().get_decoration_mgr()->WhileEachDecoration(
def_id, static_cast<uint32_t>(spv::Decoration::Flat),
[&ret](const opt::Instruction&) {
ret = DivergenceLevel::kPartiallyUniform;
return false;
});
break;
case spv::StorageClass::UniformConstant:
if (!var->IsVulkanStorageImage() || var->IsReadOnlyPointer()) {
ret = DivergenceLevel::kUniform;
} else {
ret = DivergenceLevel::kDivergent;
}
break;
case spv::StorageClass::Uniform:
case spv::StorageClass::PushConstant:
case spv::StorageClass::CrossWorkgroup:
default:
ret = DivergenceLevel::kUniform;
break;
}
return ret;
}
void DivergenceAnalysis::Setup(opt::Function* function) {
cd_.ComputeControlDependenceGraph(
*context().cfg(), *context().GetPostDominatorAnalysis(function));
context().cfg()->ForEachBlockInPostOrder(
function->entry().get(), [this](const opt::BasicBlock* bb) {
uint32_t id = bb->id();
if (bb->terminator() == nullptr ||
bb->terminator()->opcode() != spv::Op::OpBranch) {
follow_unconditional_branches_[id] = id;
} else {
uint32_t target_id = bb->terminator()->GetSingleWordInOperand(0);
follow_unconditional_branches_[id] =
follow_unconditional_branches_[target_id];
}
});
}
std::ostream& operator<<(std::ostream& os,
DivergenceAnalysis::DivergenceLevel level) {
switch (level) {
case DivergenceAnalysis::DivergenceLevel::kUniform:
return os << "uniform";
case DivergenceAnalysis::DivergenceLevel::kPartiallyUniform:
return os << "partially uniform";
case DivergenceAnalysis::DivergenceLevel::kDivergent:
return os << "divergent";
default:
return os << "<invalid divergence level>";
}
}
}
}