available_instructions.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // Copyright (c) 2021 Alastair F. Donaldson
  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/available_instructions.h"
  15. #include "source/fuzz/fuzzer_util.h"
  16. namespace spvtools {
  17. namespace fuzz {
  18. AvailableInstructions::AvailableInstructions(
  19. opt::IRContext* ir_context,
  20. const std::function<bool(opt::IRContext*, opt::Instruction*)>& predicate)
  21. : ir_context_(ir_context) {
  22. // Consider all global declarations
  23. for (auto& global : ir_context->module()->types_values()) {
  24. if (predicate(ir_context, &global)) {
  25. available_globals_.push_back(&global);
  26. }
  27. }
  28. // Consider every function
  29. for (auto& function : *ir_context->module()) {
  30. // Identify those function parameters that satisfy the predicate.
  31. std::vector<opt::Instruction*> available_params_for_function;
  32. function.ForEachParam(
  33. [&predicate, ir_context,
  34. &available_params_for_function](opt::Instruction* param) {
  35. if (predicate(ir_context, param)) {
  36. available_params_for_function.push_back(param);
  37. }
  38. });
  39. // Consider every reachable block in the function.
  40. auto dominator_analysis = ir_context->GetDominatorAnalysis(&function);
  41. for (auto& block : function) {
  42. if (!ir_context->IsReachable(block)) {
  43. // The block is not reachable.
  44. continue;
  45. }
  46. if (&block == &*function.begin()) {
  47. // The function entry block is special: only the relevant globals and
  48. // function parameters are available at its entry point.
  49. num_available_at_block_entry_.insert(
  50. {&block,
  51. static_cast<uint32_t>(available_params_for_function.size() +
  52. available_globals_.size())});
  53. } else {
  54. // |block| is not the entry block and is reachable, so it must have an
  55. // immediate dominator. The number of instructions available on entry to
  56. // |block| is thus the number of instructions available on entry to the
  57. // immediate dominator + the number of instructions generated_by_block
  58. // by the immediate dominator.
  59. auto immediate_dominator =
  60. dominator_analysis->ImmediateDominator(&block);
  61. assert(immediate_dominator != nullptr &&
  62. "The block is reachable so should have an immediate dominator.");
  63. assert(generated_by_block_.count(immediate_dominator) != 0 &&
  64. "Immediate dominator should have already been processed.");
  65. assert(num_available_at_block_entry_.count(immediate_dominator) != 0 &&
  66. "Immediate dominator should have already been processed.");
  67. num_available_at_block_entry_.insert(
  68. {&block,
  69. static_cast<uint32_t>(
  70. generated_by_block_.at(immediate_dominator).size()) +
  71. num_available_at_block_entry_.at(immediate_dominator)});
  72. }
  73. // Now consider each instruction in the block.
  74. std::vector<opt::Instruction*> generated_by_block;
  75. for (auto& inst : block) {
  76. assert(num_available_at_block_entry_.count(&block) != 0 &&
  77. "Block should have already been processed.");
  78. // The number of available instructions before |inst| is the number
  79. // available at the start of the block + the number of relevant
  80. // instructions generated by the block so far.
  81. num_available_before_instruction_.insert(
  82. {&inst, num_available_at_block_entry_.at(&block) +
  83. static_cast<uint32_t>(generated_by_block.size())});
  84. if (predicate(ir_context, &inst)) {
  85. // This instruction satisfies the predicate, so note that it is
  86. // generated by |block|.
  87. generated_by_block.push_back(&inst);
  88. }
  89. }
  90. generated_by_block_.emplace(&block, std::move(generated_by_block));
  91. }
  92. available_params_.emplace(&function,
  93. std::move(available_params_for_function));
  94. }
  95. }
  96. AvailableInstructions::AvailableBeforeInstruction
  97. AvailableInstructions::GetAvailableBeforeInstruction(
  98. opt::Instruction* inst) const {
  99. assert(num_available_before_instruction_.count(inst) != 0 &&
  100. "Availability can only be queried for reachable instructions.");
  101. return {*this, inst};
  102. }
  103. AvailableInstructions::AvailableBeforeInstruction::AvailableBeforeInstruction(
  104. const AvailableInstructions& available_instructions, opt::Instruction* inst)
  105. : available_instructions_(available_instructions), inst_(inst) {}
  106. uint32_t AvailableInstructions::AvailableBeforeInstruction::size() const {
  107. return available_instructions_.num_available_before_instruction_.at(inst_);
  108. }
  109. bool AvailableInstructions::AvailableBeforeInstruction::empty() const {
  110. return size() == 0;
  111. }
  112. opt::Instruction* AvailableInstructions::AvailableBeforeInstruction::operator[](
  113. uint32_t index) const {
  114. assert(index < size() && "Index out of bounds.");
  115. // First, check the cache to see whether we can return the available
  116. // instruction in constant time.
  117. auto cached_result = index_cache.find(index);
  118. if (cached_result != index_cache.end()) {
  119. return cached_result->second;
  120. }
  121. // Next check whether the index falls into the global region.
  122. if (index < available_instructions_.available_globals_.size()) {
  123. auto result = available_instructions_.available_globals_[index];
  124. index_cache.insert({index, result});
  125. return result;
  126. }
  127. auto block = available_instructions_.ir_context_->get_instr_block(inst_);
  128. auto function = block->GetParent();
  129. // Next check whether the index falls into the available instructions that
  130. // correspond to function parameters.
  131. if (index <
  132. available_instructions_.available_globals_.size() +
  133. available_instructions_.available_params_.at(function).size()) {
  134. auto result = available_instructions_.available_params_.at(
  135. function)[index - available_instructions_.available_globals_.size()];
  136. index_cache.insert({index, result});
  137. return result;
  138. }
  139. auto dominator_analysis =
  140. available_instructions_.ir_context_->GetDominatorAnalysis(function);
  141. // Now the expensive part (which is why we have the cache): walk the dominator
  142. // tree backwards starting from the block containing |inst_| until we get to
  143. // the block in which the instruction corresponding to |index| exists.
  144. for (auto* ancestor = block; true;
  145. ancestor = dominator_analysis->ImmediateDominator(ancestor)) {
  146. uint32_t num_available_at_ancestor_entry =
  147. available_instructions_.num_available_at_block_entry_.at(ancestor);
  148. if (index_cache.count(num_available_at_ancestor_entry) == 0) {
  149. // This is the first time we have traversed this block, so we populate the
  150. // cache with the index of each instruction, so that if a future index
  151. // query relates to indices associated with this block we can return the
  152. // result in constant time.
  153. auto& generated_by_ancestor =
  154. available_instructions_.generated_by_block_.at(ancestor);
  155. for (uint32_t local_index = 0; local_index < generated_by_ancestor.size();
  156. local_index++) {
  157. index_cache.insert({num_available_at_ancestor_entry + local_index,
  158. generated_by_ancestor[local_index]});
  159. }
  160. }
  161. if (index >= num_available_at_ancestor_entry) {
  162. // This block contains the instruction we want, so by now it will be in
  163. // the cache.
  164. return index_cache.at(index);
  165. }
  166. assert(ancestor != &*function->begin() &&
  167. "By construction we should find a block associated with the index.");
  168. }
  169. assert(false && "Unreachable.");
  170. return nullptr;
  171. }
  172. } // namespace fuzz
  173. } // namespace spvtools