#include "source/fuzz/call_graph.h"
#include <queue>
namespace spvtools {
namespace fuzz {
CallGraph::CallGraph(opt::IRContext* context) {
for (auto& function : *context->module()) {
function_in_degree_[function.result_id()] = 0;
call_graph_edges_[function.result_id()] = std::set<uint32_t>();
function_max_loop_nesting_depth_[function.result_id()] = 0;
}
std::map<std::pair<uint32_t, uint32_t>, uint32_t> call_to_max_depth;
BuildGraphAndGetDepthOfFunctionCalls(context, &call_to_max_depth);
ComputeTopologicalOrderOfFunctions();
ComputeInterproceduralFunctionCallDepths(call_to_max_depth);
}
void CallGraph::BuildGraphAndGetDepthOfFunctionCalls(
opt::IRContext* context,
std::map<std::pair<uint32_t, uint32_t>, uint32_t>* call_to_max_depth) {
for (auto& function : *context->module()) {
std::set<uint32_t> known_callees;
for (auto& block : function) {
for (auto& instruction : block) {
if (instruction.opcode() != spv::Op::OpFunctionCall) {
continue;
}
uint32_t callee = instruction.GetSingleWordInOperand(0);
uint32_t loop_nesting_depth =
context->GetStructuredCFGAnalysis()->LoopNestingDepth(block.id());
if (block.IsLoopHeader()) {
loop_nesting_depth++;
}
if (!known_callees.count(callee) ||
call_to_max_depth->at({function.result_id(), callee}) <
loop_nesting_depth) {
call_to_max_depth->insert(
{{function.result_id(), callee}, loop_nesting_depth});
}
if (known_callees.count(callee)) {
continue;
}
function_in_degree_[callee]++;
call_graph_edges_[function.result_id()].insert(callee);
known_callees.insert(callee);
}
}
}
}
void CallGraph::ComputeTopologicalOrderOfFunctions() {
functions_in_topological_order_.clear();
std::map<uint32_t, uint32_t> function_in_degree = GetFunctionInDegree();
std::queue<uint32_t> queue;
for (auto& entry : function_in_degree) {
if (entry.second == 0) {
queue.push(entry.first);
}
}
while (!queue.empty()) {
auto next = queue.front();
queue.pop();
functions_in_topological_order_.push_back(next);
for (auto successor : GetDirectCallees(next)) {
assert(function_in_degree.at(successor) > 0 &&
"The in-degree cannot be zero if the function is a successor.");
function_in_degree[successor] = function_in_degree.at(successor) - 1;
if (function_in_degree.at(successor) == 0) {
queue.push(successor);
}
}
}
assert(functions_in_topological_order_.size() == function_in_degree.size() &&
"Every function should appear in the sort.");
return;
}
void CallGraph::ComputeInterproceduralFunctionCallDepths(
const std::map<std::pair<uint32_t, uint32_t>, uint32_t>&
call_to_max_depth) {
for (uint32_t function_id : functions_in_topological_order_) {
const auto& callees = call_graph_edges_[function_id];
for (uint32_t callee : callees) {
uint32_t max_depth_from_this_function =
function_max_loop_nesting_depth_[function_id] +
call_to_max_depth.at({function_id, callee});
if (function_max_loop_nesting_depth_[callee] <
max_depth_from_this_function) {
function_max_loop_nesting_depth_[callee] = max_depth_from_this_function;
}
}
}
}
void CallGraph::PushDirectCallees(uint32_t function_id,
std::queue<uint32_t>* queue) const {
for (auto callee : GetDirectCallees(function_id)) {
queue->push(callee);
}
}
std::set<uint32_t> CallGraph::GetIndirectCallees(uint32_t function_id) const {
std::set<uint32_t> result;
std::queue<uint32_t> queue;
PushDirectCallees(function_id, &queue);
while (!queue.empty()) {
auto next = queue.front();
queue.pop();
if (result.count(next)) {
continue;
}
result.insert(next);
PushDirectCallees(next, &queue);
}
return result;
}
}
}