call_graph.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // Copyright (c) 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #include "source/fuzz/call_graph.h"
  15. #include <queue>
  16. namespace spvtools {
  17. namespace fuzz {
  18. CallGraph::CallGraph(opt::IRContext* context) {
  19. // Initialize function in-degree, call graph edges and corresponding maximum
  20. // loop nesting depth to 0, empty and 0 respectively.
  21. for (auto& function : *context->module()) {
  22. function_in_degree_[function.result_id()] = 0;
  23. call_graph_edges_[function.result_id()] = std::set<uint32_t>();
  24. function_max_loop_nesting_depth_[function.result_id()] = 0;
  25. }
  26. // Record the maximum loop nesting depth for each edge, by keeping a map from
  27. // pairs of function ids, where (A, B) represents a function call from A to B,
  28. // to the corresponding maximum depth.
  29. std::map<std::pair<uint32_t, uint32_t>, uint32_t> call_to_max_depth;
  30. // Compute |function_in_degree_|, |call_graph_edges_| and |call_to_max_depth|.
  31. BuildGraphAndGetDepthOfFunctionCalls(context, &call_to_max_depth);
  32. // Compute |functions_in_topological_order_|.
  33. ComputeTopologicalOrderOfFunctions();
  34. // Compute |function_max_loop_nesting_depth_|.
  35. ComputeInterproceduralFunctionCallDepths(call_to_max_depth);
  36. }
  37. void CallGraph::BuildGraphAndGetDepthOfFunctionCalls(
  38. opt::IRContext* context,
  39. std::map<std::pair<uint32_t, uint32_t>, uint32_t>* call_to_max_depth) {
  40. // Consider every function.
  41. for (auto& function : *context->module()) {
  42. // Avoid considering the same callee of this function multiple times by
  43. // recording known callees.
  44. std::set<uint32_t> known_callees;
  45. // Consider every function call instruction in every block.
  46. for (auto& block : function) {
  47. for (auto& instruction : block) {
  48. if (instruction.opcode() != spv::Op::OpFunctionCall) {
  49. continue;
  50. }
  51. // Get the id of the function being called.
  52. uint32_t callee = instruction.GetSingleWordInOperand(0);
  53. // Get the loop nesting depth of this function call.
  54. uint32_t loop_nesting_depth =
  55. context->GetStructuredCFGAnalysis()->LoopNestingDepth(block.id());
  56. // If inside a loop header, consider the function call nested inside the
  57. // loop headed by the block.
  58. if (block.IsLoopHeader()) {
  59. loop_nesting_depth++;
  60. }
  61. // Update the map if we have not seen this pair (caller, callee)
  62. // before or if this function call is from a greater depth.
  63. if (!known_callees.count(callee) ||
  64. call_to_max_depth->at({function.result_id(), callee}) <
  65. loop_nesting_depth) {
  66. call_to_max_depth->insert(
  67. {{function.result_id(), callee}, loop_nesting_depth});
  68. }
  69. if (known_callees.count(callee)) {
  70. // We have already considered a call to this function - ignore it.
  71. continue;
  72. }
  73. // Increase the callee's in-degree and add an edge to the call graph.
  74. function_in_degree_[callee]++;
  75. call_graph_edges_[function.result_id()].insert(callee);
  76. // Mark the callee as 'known'.
  77. known_callees.insert(callee);
  78. }
  79. }
  80. }
  81. }
  82. void CallGraph::ComputeTopologicalOrderOfFunctions() {
  83. // This is an implementation of Kahn’s algorithm for topological sorting.
  84. // Initialise |functions_in_topological_order_|.
  85. functions_in_topological_order_.clear();
  86. // Get a copy of the initial in-degrees of all functions. The algorithm
  87. // involves decrementing these values, hence why we work on a copy.
  88. std::map<uint32_t, uint32_t> function_in_degree = GetFunctionInDegree();
  89. // Populate a queue with all those function ids with in-degree zero.
  90. std::queue<uint32_t> queue;
  91. for (auto& entry : function_in_degree) {
  92. if (entry.second == 0) {
  93. queue.push(entry.first);
  94. }
  95. }
  96. // Pop ids from the queue, adding them to the sorted order and decreasing the
  97. // in-degrees of their successors. A successor who's in-degree becomes zero
  98. // gets added to the queue.
  99. while (!queue.empty()) {
  100. auto next = queue.front();
  101. queue.pop();
  102. functions_in_topological_order_.push_back(next);
  103. for (auto successor : GetDirectCallees(next)) {
  104. assert(function_in_degree.at(successor) > 0 &&
  105. "The in-degree cannot be zero if the function is a successor.");
  106. function_in_degree[successor] = function_in_degree.at(successor) - 1;
  107. if (function_in_degree.at(successor) == 0) {
  108. queue.push(successor);
  109. }
  110. }
  111. }
  112. assert(functions_in_topological_order_.size() == function_in_degree.size() &&
  113. "Every function should appear in the sort.");
  114. return;
  115. }
  116. void CallGraph::ComputeInterproceduralFunctionCallDepths(
  117. const std::map<std::pair<uint32_t, uint32_t>, uint32_t>&
  118. call_to_max_depth) {
  119. // Find the maximum loop nesting depth that each function can be
  120. // called from, by considering them in topological order.
  121. for (uint32_t function_id : functions_in_topological_order_) {
  122. const auto& callees = call_graph_edges_[function_id];
  123. // For each callee, update its maximum loop nesting depth, if a call from
  124. // |function_id| increases it.
  125. for (uint32_t callee : callees) {
  126. uint32_t max_depth_from_this_function =
  127. function_max_loop_nesting_depth_[function_id] +
  128. call_to_max_depth.at({function_id, callee});
  129. if (function_max_loop_nesting_depth_[callee] <
  130. max_depth_from_this_function) {
  131. function_max_loop_nesting_depth_[callee] = max_depth_from_this_function;
  132. }
  133. }
  134. }
  135. }
  136. void CallGraph::PushDirectCallees(uint32_t function_id,
  137. std::queue<uint32_t>* queue) const {
  138. for (auto callee : GetDirectCallees(function_id)) {
  139. queue->push(callee);
  140. }
  141. }
  142. std::set<uint32_t> CallGraph::GetIndirectCallees(uint32_t function_id) const {
  143. std::set<uint32_t> result;
  144. std::queue<uint32_t> queue;
  145. PushDirectCallees(function_id, &queue);
  146. while (!queue.empty()) {
  147. auto next = queue.front();
  148. queue.pop();
  149. if (result.count(next)) {
  150. continue;
  151. }
  152. result.insert(next);
  153. PushDirectCallees(next, &queue);
  154. }
  155. return result;
  156. }
  157. } // namespace fuzz
  158. } // namespace spvtools