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