spirv-fuzz: Compute interprocedural loop nesting depth of blocks (#3753)
This PR extends CallGraph with functions to return: - a list of functions in lexicographical order, with respect to function calls - the maximum loop nesting depth that a function can be called from (computed interprocedurally, e.g. if foo() calls bar() at depth 2 and bar() calls baz() at depth 1, the maximum depth of baz() will be 3).
This commit is contained in:
Родитель
8a0ebd40f8
Коммит
43a5186011
|
@ -20,12 +20,32 @@ namespace spvtools {
|
|||
namespace fuzz {
|
||||
|
||||
CallGraph::CallGraph(opt::IRContext* context) {
|
||||
// Initialize function in-degree and call graph edges to 0 and empty.
|
||||
// Initialize function in-degree, call graph edges and corresponding maximum
|
||||
// loop nesting depth to 0, empty and 0 respectively.
|
||||
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;
|
||||
}
|
||||
|
||||
// Record the maximum loop nesting depth for each edge, by keeping a map from
|
||||
// pairs of function ids, where (A, B) represents a function call from A to B,
|
||||
// to the corresponding maximum depth.
|
||||
std::map<std::pair<uint32_t, uint32_t>, uint32_t> call_to_max_depth;
|
||||
|
||||
// Compute |function_in_degree_|, |call_graph_edges_| and |call_to_max_depth|.
|
||||
BuildGraphAndGetDepthOfFunctionCalls(context, &call_to_max_depth);
|
||||
|
||||
// Compute |functions_in_topological_order_|.
|
||||
ComputeTopologicalOrderOfFunctions();
|
||||
|
||||
// Compute |function_max_loop_nesting_depth_|.
|
||||
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) {
|
||||
// Consider every function.
|
||||
for (auto& function : *context->module()) {
|
||||
// Avoid considering the same callee of this function multiple times by
|
||||
|
@ -39,6 +59,25 @@ CallGraph::CallGraph(opt::IRContext* context) {
|
|||
}
|
||||
// Get the id of the function being called.
|
||||
uint32_t callee = instruction.GetSingleWordInOperand(0);
|
||||
|
||||
// Get the loop nesting depth of this function call.
|
||||
uint32_t loop_nesting_depth =
|
||||
context->GetStructuredCFGAnalysis()->LoopNestingDepth(block.id());
|
||||
// If inside a loop header, consider the function call nested inside the
|
||||
// loop headed by the block.
|
||||
if (block.IsLoopHeader()) {
|
||||
loop_nesting_depth++;
|
||||
}
|
||||
|
||||
// Update the map if we have not seen this pair (caller, callee)
|
||||
// before or if this function call is from a greater 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)) {
|
||||
// We have already considered a call to this function - ignore it.
|
||||
continue;
|
||||
|
@ -53,6 +92,69 @@ CallGraph::CallGraph(opt::IRContext* context) {
|
|||
}
|
||||
}
|
||||
|
||||
void CallGraph::ComputeTopologicalOrderOfFunctions() {
|
||||
// This is an implementation of Kahn’s algorithm for topological sorting.
|
||||
|
||||
// Initialise |functions_in_topological_order_|.
|
||||
functions_in_topological_order_.clear();
|
||||
|
||||
// Get a copy of the initial in-degrees of all functions. The algorithm
|
||||
// involves decrementing these values, hence why we work on a copy.
|
||||
std::map<uint32_t, uint32_t> function_in_degree = GetFunctionInDegree();
|
||||
|
||||
// Populate a queue with all those function ids with in-degree zero.
|
||||
std::queue<uint32_t> queue;
|
||||
for (auto& entry : function_in_degree) {
|
||||
if (entry.second == 0) {
|
||||
queue.push(entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop ids from the queue, adding them to the sorted order and decreasing the
|
||||
// in-degrees of their successors. A successor who's in-degree becomes zero
|
||||
// gets added to the queue.
|
||||
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) {
|
||||
// Find the maximum loop nesting depth that each function can be
|
||||
// called from, by considering them in topological order.
|
||||
for (uint32_t function_id : functions_in_topological_order_) {
|
||||
const auto& callees = call_graph_edges_[function_id];
|
||||
|
||||
// For each callee, update its maximum loop nesting depth, if a call from
|
||||
// |function_id| increases it.
|
||||
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)) {
|
||||
|
|
|
@ -24,6 +24,9 @@ namespace spvtools {
|
|||
namespace fuzz {
|
||||
|
||||
// Represents the acyclic call graph of a SPIR-V module.
|
||||
// The module is assumed to be recursion-free, so there are no cycles in the
|
||||
// graph. This class is immutable, so it will need to be recomputed if the
|
||||
// module changes.
|
||||
class CallGraph {
|
||||
public:
|
||||
// Creates a call graph corresponding to the given SPIR-V module.
|
||||
|
@ -43,7 +46,44 @@ class CallGraph {
|
|||
// invokes.
|
||||
std::set<uint32_t> GetIndirectCallees(uint32_t function_id) const;
|
||||
|
||||
// Returns the ids of all the functions in the graph in a topological order,
|
||||
// in relation to the function calls, which are assumed to be recursion-free.
|
||||
const std::vector<uint32_t>& GetFunctionsInTopologicalOrder() const {
|
||||
return functions_in_topological_order_;
|
||||
}
|
||||
|
||||
// Returns the maximum loop nesting depth from which |function_id| can be
|
||||
// called. This is computed inter-procedurally (i.e. if main calls A from
|
||||
// depth 2 and A calls B from depth 1, the result will be 3 for A).
|
||||
// This is a static analysis, so it's not necessarily true that the depth
|
||||
// returned can actually be reached at runtime.
|
||||
uint32_t GetMaxCallNestingDepth(uint32_t function_id) const {
|
||||
return function_max_loop_nesting_depth_.at(function_id);
|
||||
}
|
||||
|
||||
private:
|
||||
// Computes |call_graph_edges_| and |function_in_degree_|. For each pair (A,
|
||||
// B) of functions such that there is at least a function call from A to B,
|
||||
// adds, to |call_to_max_depth|, a mapping from (A, B) to the maximum loop
|
||||
// nesting depth (within A) of any such function call.
|
||||
void BuildGraphAndGetDepthOfFunctionCalls(
|
||||
opt::IRContext* context,
|
||||
std::map<std::pair<uint32_t, uint32_t>, uint32_t>* call_to_max_depth);
|
||||
|
||||
// Computes a topological order of the functions in the graph, writing the
|
||||
// result to |functions_in_topological_order_|. Assumes that the function
|
||||
// calls are recursion-free and that |function_in_degree_| has been computed.
|
||||
void ComputeTopologicalOrderOfFunctions();
|
||||
|
||||
// Computes |function_max_loop_nesting_depth_| so that each function is mapped
|
||||
// to the maximum loop nesting depth from which it can be called, as described
|
||||
// by the comment to GetMaxCallNestingDepth. Assumes that |call_graph_edges_|
|
||||
// and |functions_in_topological_order_| have been computed, and that
|
||||
// |call_to_max_depth| contains a mapping for each edge in the graph.
|
||||
void ComputeInterproceduralFunctionCallDepths(
|
||||
const std::map<std::pair<uint32_t, uint32_t>, uint32_t>&
|
||||
call_to_max_depth);
|
||||
|
||||
// Pushes the direct callees of |function_id| on to |queue|.
|
||||
void PushDirectCallees(uint32_t function_id,
|
||||
std::queue<uint32_t>* queue) const;
|
||||
|
@ -54,6 +94,14 @@ class CallGraph {
|
|||
// For each function id, stores the number of distinct functions that call
|
||||
// the function.
|
||||
std::map<uint32_t, uint32_t> function_in_degree_;
|
||||
|
||||
// Stores the ids of the functions in a topological order,
|
||||
// in relation to the function calls, which are assumed to be recursion-free.
|
||||
std::vector<uint32_t> functions_in_topological_order_;
|
||||
|
||||
// For each function id, stores the maximum loop nesting depth that the
|
||||
// function can be called from.
|
||||
std::map<uint32_t, uint32_t> function_max_loop_nesting_depth_;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
|
|
|
@ -598,7 +598,7 @@ void FuzzerPassDonateModules::HandleFunctions(
|
|||
// Get the ids of functions in the donor module, topologically sorted
|
||||
// according to the donor's call graph.
|
||||
auto topological_order =
|
||||
GetFunctionsInCallGraphTopologicalOrder(donor_ir_context);
|
||||
CallGraph(donor_ir_context).GetFunctionsInTopologicalOrder();
|
||||
|
||||
// Donate the functions in reverse topological order. This ensures that a
|
||||
// function gets donated before any function that depends on it. This allows
|
||||
|
@ -796,52 +796,6 @@ bool FuzzerPassDonateModules::IsBasicType(
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t>
|
||||
FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder(
|
||||
opt::IRContext* context) {
|
||||
CallGraph call_graph(context);
|
||||
|
||||
// This is an implementation of Kahn’s algorithm for topological sorting.
|
||||
|
||||
// This is the sorted order of function ids that we will eventually return.
|
||||
std::vector<uint32_t> result;
|
||||
|
||||
// Get a copy of the initial in-degrees of all functions. The algorithm
|
||||
// involves decrementing these values, hence why we work on a copy.
|
||||
std::map<uint32_t, uint32_t> function_in_degree =
|
||||
call_graph.GetFunctionInDegree();
|
||||
|
||||
// Populate a queue with all those function ids with in-degree zero.
|
||||
std::queue<uint32_t> queue;
|
||||
for (auto& entry : function_in_degree) {
|
||||
if (entry.second == 0) {
|
||||
queue.push(entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop ids from the queue, adding them to the sorted order and decreasing the
|
||||
// in-degrees of their successors. A successor who's in-degree becomes zero
|
||||
// gets added to the queue.
|
||||
while (!queue.empty()) {
|
||||
auto next = queue.front();
|
||||
queue.pop();
|
||||
result.push_back(next);
|
||||
for (auto successor : call_graph.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(result.size() == function_in_degree.size() &&
|
||||
"Every function should appear in the sort.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void FuzzerPassDonateModules::HandleOpArrayLength(
|
||||
const opt::Instruction& instruction,
|
||||
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
|
||||
|
|
|
@ -154,12 +154,6 @@ class FuzzerPassDonateModules : public FuzzerPass {
|
|||
// array or struct; i.e. it is not an opaque type.
|
||||
bool IsBasicType(const opt::Instruction& instruction) const;
|
||||
|
||||
// Returns the ids of all functions in |context| in a topological order in
|
||||
// relation to the call graph of |context|, which is assumed to be recursion-
|
||||
// free.
|
||||
static std::vector<uint32_t> GetFunctionsInCallGraphTopologicalOrder(
|
||||
opt::IRContext* context);
|
||||
|
||||
// Functions that supply SPIR-V modules
|
||||
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers_;
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ if (${SPIRV_BUILD_FUZZER})
|
|||
set(SOURCES
|
||||
fuzz_test_util.h
|
||||
|
||||
call_graph_test.cpp
|
||||
data_synonym_transformation_test.cpp
|
||||
equivalence_relation_test.cpp
|
||||
fact_manager_test.cpp
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
// Copyright (c) 2020 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/fuzz/call_graph.h"
|
||||
#include "test/fuzz/fuzz_test_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
namespace {
|
||||
|
||||
// The SPIR-V came from this GLSL, slightly modified
|
||||
// (main is %2, A is %35, B is %48, C is %50, D is %61):
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// int A (int x) {
|
||||
// return x + 1;
|
||||
// }
|
||||
//
|
||||
// void D() {
|
||||
// }
|
||||
//
|
||||
// void C() {
|
||||
// int x = 0;
|
||||
// int y = 0;
|
||||
//
|
||||
// while (x < 10) {
|
||||
// while (y < 10) {
|
||||
// y = A(y);
|
||||
// }
|
||||
// x = A(x);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void B () {
|
||||
// int x = 0;
|
||||
// int y = 0;
|
||||
//
|
||||
// while (x < 10) {
|
||||
// D();
|
||||
// while (y < 10) {
|
||||
// y = A(y);
|
||||
// C();
|
||||
// }
|
||||
// x++;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// void main()
|
||||
// {
|
||||
// int x = 0;
|
||||
// int y = 0;
|
||||
// int z = 0;
|
||||
//
|
||||
// while (x < 10) {
|
||||
// while(y < 10) {
|
||||
// y = A(x);
|
||||
// while (z < 10) {
|
||||
// z = A(z);
|
||||
// }
|
||||
// }
|
||||
// x += 2;
|
||||
// }
|
||||
//
|
||||
// B();
|
||||
// C();
|
||||
// }
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%3 = OpTypeVoid
|
||||
%4 = OpTypeFunction %3
|
||||
%5 = OpTypeInt 32 1
|
||||
%6 = OpTypePointer Function %5
|
||||
%7 = OpTypeFunction %5 %6
|
||||
%8 = OpConstant %5 1
|
||||
%9 = OpConstant %5 0
|
||||
%10 = OpConstant %5 10
|
||||
%11 = OpTypeBool
|
||||
%12 = OpConstant %5 2
|
||||
%2 = OpFunction %3 None %4
|
||||
%13 = OpLabel
|
||||
%14 = OpVariable %6 Function
|
||||
%15 = OpVariable %6 Function
|
||||
%16 = OpVariable %6 Function
|
||||
%17 = OpVariable %6 Function
|
||||
%18 = OpVariable %6 Function
|
||||
OpStore %14 %9
|
||||
OpStore %15 %9
|
||||
OpStore %16 %9
|
||||
OpBranch %19
|
||||
%19 = OpLabel
|
||||
OpLoopMerge %20 %21 None
|
||||
OpBranch %22
|
||||
%22 = OpLabel
|
||||
%23 = OpLoad %5 %14
|
||||
%24 = OpSLessThan %11 %23 %10
|
||||
OpBranchConditional %24 %25 %20
|
||||
%25 = OpLabel
|
||||
OpBranch %26
|
||||
%26 = OpLabel
|
||||
OpLoopMerge %27 %28 None
|
||||
OpBranch %29
|
||||
%29 = OpLabel
|
||||
%30 = OpLoad %5 %15
|
||||
%31 = OpSLessThan %11 %30 %10
|
||||
OpBranchConditional %31 %32 %27
|
||||
%32 = OpLabel
|
||||
%33 = OpLoad %5 %14
|
||||
OpStore %17 %33
|
||||
%34 = OpFunctionCall %5 %35 %17
|
||||
OpStore %15 %34
|
||||
OpBranch %36
|
||||
%36 = OpLabel
|
||||
OpLoopMerge %37 %38 None
|
||||
OpBranch %39
|
||||
%39 = OpLabel
|
||||
%40 = OpLoad %5 %16
|
||||
%41 = OpSLessThan %11 %40 %10
|
||||
OpBranchConditional %41 %42 %37
|
||||
%42 = OpLabel
|
||||
%43 = OpLoad %5 %16
|
||||
OpStore %18 %43
|
||||
%44 = OpFunctionCall %5 %35 %18
|
||||
OpStore %16 %44
|
||||
OpBranch %38
|
||||
%38 = OpLabel
|
||||
OpBranch %36
|
||||
%37 = OpLabel
|
||||
OpBranch %28
|
||||
%28 = OpLabel
|
||||
OpBranch %26
|
||||
%27 = OpLabel
|
||||
%45 = OpLoad %5 %14
|
||||
%46 = OpIAdd %5 %45 %12
|
||||
OpStore %14 %46
|
||||
OpBranch %21
|
||||
%21 = OpLabel
|
||||
OpBranch %19
|
||||
%20 = OpLabel
|
||||
%47 = OpFunctionCall %3 %48
|
||||
%49 = OpFunctionCall %3 %50
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%35 = OpFunction %5 None %7
|
||||
%51 = OpFunctionParameter %6
|
||||
%52 = OpLabel
|
||||
%53 = OpLoad %5 %51
|
||||
%54 = OpIAdd %5 %53 %8
|
||||
OpReturnValue %54
|
||||
OpFunctionEnd
|
||||
%48 = OpFunction %3 None %4
|
||||
%55 = OpLabel
|
||||
%56 = OpVariable %6 Function
|
||||
%57 = OpVariable %6 Function
|
||||
%58 = OpVariable %6 Function
|
||||
OpStore %56 %9
|
||||
OpStore %57 %9
|
||||
OpBranch %59
|
||||
%59 = OpLabel
|
||||
%60 = OpFunctionCall %3 %61
|
||||
OpLoopMerge %62 %63 None
|
||||
OpBranch %64
|
||||
%64 = OpLabel
|
||||
OpLoopMerge %65 %66 None
|
||||
OpBranch %67
|
||||
%67 = OpLabel
|
||||
%68 = OpLoad %5 %57
|
||||
%69 = OpSLessThan %11 %68 %10
|
||||
OpBranchConditional %69 %70 %65
|
||||
%70 = OpLabel
|
||||
%71 = OpLoad %5 %57
|
||||
OpStore %58 %71
|
||||
%72 = OpFunctionCall %5 %35 %58
|
||||
OpStore %57 %72
|
||||
%73 = OpFunctionCall %3 %50
|
||||
OpBranch %66
|
||||
%66 = OpLabel
|
||||
OpBranch %64
|
||||
%65 = OpLabel
|
||||
%74 = OpLoad %5 %56
|
||||
%75 = OpIAdd %5 %74 %8
|
||||
OpStore %56 %75
|
||||
OpBranch %63
|
||||
%63 = OpLabel
|
||||
%76 = OpLoad %5 %56
|
||||
%77 = OpSLessThan %11 %76 %10
|
||||
OpBranchConditional %77 %59 %62
|
||||
%62 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%50 = OpFunction %3 None %4
|
||||
%78 = OpLabel
|
||||
%79 = OpVariable %6 Function
|
||||
%80 = OpVariable %6 Function
|
||||
%81 = OpVariable %6 Function
|
||||
%82 = OpVariable %6 Function
|
||||
OpStore %79 %9
|
||||
OpStore %80 %9
|
||||
OpBranch %83
|
||||
%83 = OpLabel
|
||||
OpLoopMerge %84 %85 None
|
||||
OpBranch %86
|
||||
%86 = OpLabel
|
||||
%87 = OpLoad %5 %79
|
||||
%88 = OpSLessThan %11 %87 %10
|
||||
OpBranchConditional %88 %89 %84
|
||||
%89 = OpLabel
|
||||
OpBranch %90
|
||||
%90 = OpLabel
|
||||
OpLoopMerge %91 %92 None
|
||||
OpBranch %93
|
||||
%93 = OpLabel
|
||||
%94 = OpLoad %5 %80
|
||||
%95 = OpSLessThan %11 %94 %10
|
||||
OpBranchConditional %95 %96 %91
|
||||
%96 = OpLabel
|
||||
%97 = OpLoad %5 %80
|
||||
OpStore %81 %97
|
||||
%98 = OpFunctionCall %5 %35 %81
|
||||
OpStore %80 %98
|
||||
OpBranch %92
|
||||
%92 = OpLabel
|
||||
OpBranch %90
|
||||
%91 = OpLabel
|
||||
%99 = OpLoad %5 %79
|
||||
OpStore %82 %99
|
||||
%100 = OpFunctionCall %5 %35 %82
|
||||
OpStore %79 %100
|
||||
OpBranch %85
|
||||
%85 = OpLabel
|
||||
OpBranch %83
|
||||
%84 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%61 = OpFunction %3 None %4
|
||||
%101 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// We have that:
|
||||
// main calls:
|
||||
// - A (maximum loop nesting depth of function call: 3)
|
||||
// - B (0)
|
||||
// - C (0)
|
||||
// A calls nothing.
|
||||
// B calls:
|
||||
// - A (2)
|
||||
// - C (2)
|
||||
// - D (1)
|
||||
// C calls:
|
||||
// - A (2)
|
||||
// D calls nothing.
|
||||
|
||||
TEST(CallGraphTest, FunctionInDegree) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
const auto graph = CallGraph(context.get());
|
||||
|
||||
const auto& function_in_degree = graph.GetFunctionInDegree();
|
||||
// Check the in-degrees of, in order: main, A, B, C, D.
|
||||
ASSERT_EQ(function_in_degree.at(2), 0);
|
||||
ASSERT_EQ(function_in_degree.at(35), 3);
|
||||
ASSERT_EQ(function_in_degree.at(48), 1);
|
||||
ASSERT_EQ(function_in_degree.at(50), 2);
|
||||
ASSERT_EQ(function_in_degree.at(61), 1);
|
||||
}
|
||||
|
||||
TEST(CallGraphTest, DirectCallees) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
const auto graph = CallGraph(context.get());
|
||||
|
||||
// Check the callee sets of, in order: main, A, B, C, D.
|
||||
ASSERT_EQ(graph.GetDirectCallees(2), std::set<uint32_t>({35, 48, 50}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({}));
|
||||
}
|
||||
|
||||
TEST(CallGraphTest, IndirectCallees) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
const auto graph = CallGraph(context.get());
|
||||
|
||||
// Check the callee sets of, in order: main, A, B, C, D.
|
||||
ASSERT_EQ(graph.GetIndirectCallees(2), std::set<uint32_t>({35, 48, 50, 61}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35}));
|
||||
ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({}));
|
||||
}
|
||||
|
||||
TEST(CallGraphTest, TopologicalOrder) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
const auto graph = CallGraph(context.get());
|
||||
|
||||
const auto& topological_ordering = graph.GetFunctionsInTopologicalOrder();
|
||||
|
||||
// The possible topological orderings are:
|
||||
// - main, B, D, C, A
|
||||
// - main, B, C, D, A
|
||||
// - main, B, C, A, D
|
||||
ASSERT_TRUE(
|
||||
topological_ordering == std::vector<uint32_t>({2, 48, 61, 50, 35}) ||
|
||||
topological_ordering == std::vector<uint32_t>({2, 48, 50, 61, 35}) ||
|
||||
topological_ordering == std::vector<uint32_t>({2, 48, 50, 35, 61}));
|
||||
}
|
||||
|
||||
TEST(CallGraphTest, LoopNestingDepth) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
const auto graph = CallGraph(context.get());
|
||||
|
||||
// Check the maximum loop nesting depth for function calls to, in order:
|
||||
// main, A, B, C, D
|
||||
ASSERT_EQ(graph.GetMaxCallNestingDepth(2), 0);
|
||||
ASSERT_EQ(graph.GetMaxCallNestingDepth(35), 4);
|
||||
ASSERT_EQ(graph.GetMaxCallNestingDepth(48), 0);
|
||||
ASSERT_EQ(graph.GetMaxCallNestingDepth(50), 2);
|
||||
ASSERT_EQ(graph.GetMaxCallNestingDepth(61), 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
Загрузка…
Ссылка в новой задаче