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