| // Copyright (c) 2021 Alastair F. Donaldson |
| // |
| // 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/available_instructions.h" |
| |
| #include "source/fuzz/fuzzer_util.h" |
| |
| namespace spvtools { |
| namespace fuzz { |
| |
| AvailableInstructions::AvailableInstructions( |
| opt::IRContext* ir_context, |
| const std::function<bool(opt::IRContext*, opt::Instruction*)>& predicate) |
| : ir_context_(ir_context) { |
| // Consider all global declarations |
| for (auto& global : ir_context->module()->types_values()) { |
| if (predicate(ir_context, &global)) { |
| available_globals_.push_back(&global); |
| } |
| } |
| |
| // Consider every function |
| for (auto& function : *ir_context->module()) { |
| // Identify those function parameters that satisfy the predicate. |
| std::vector<opt::Instruction*> available_params_for_function; |
| function.ForEachParam( |
| [&predicate, ir_context, |
| &available_params_for_function](opt::Instruction* param) { |
| if (predicate(ir_context, param)) { |
| available_params_for_function.push_back(param); |
| } |
| }); |
| |
| // Consider every reachable block in the function. |
| auto dominator_analysis = ir_context->GetDominatorAnalysis(&function); |
| for (auto& block : function) { |
| if (!ir_context->IsReachable(block)) { |
| // The block is not reachable. |
| continue; |
| } |
| if (&block == &*function.begin()) { |
| // The function entry block is special: only the relevant globals and |
| // function parameters are available at its entry point. |
| num_available_at_block_entry_.insert( |
| {&block, |
| static_cast<uint32_t>(available_params_for_function.size() + |
| available_globals_.size())}); |
| } else { |
| // |block| is not the entry block and is reachable, so it must have an |
| // immediate dominator. The number of instructions available on entry to |
| // |block| is thus the number of instructions available on entry to the |
| // immediate dominator + the number of instructions generated_by_block |
| // by the immediate dominator. |
| auto immediate_dominator = |
| dominator_analysis->ImmediateDominator(&block); |
| assert(immediate_dominator != nullptr && |
| "The block is reachable so should have an immediate dominator."); |
| assert(generated_by_block_.count(immediate_dominator) != 0 && |
| "Immediate dominator should have already been processed."); |
| assert(num_available_at_block_entry_.count(immediate_dominator) != 0 && |
| "Immediate dominator should have already been processed."); |
| num_available_at_block_entry_.insert( |
| {&block, |
| static_cast<uint32_t>( |
| generated_by_block_.at(immediate_dominator).size()) + |
| num_available_at_block_entry_.at(immediate_dominator)}); |
| } |
| // Now consider each instruction in the block. |
| std::vector<opt::Instruction*> generated_by_block; |
| for (auto& inst : block) { |
| assert(num_available_at_block_entry_.count(&block) != 0 && |
| "Block should have already been processed."); |
| // The number of available instructions before |inst| is the number |
| // available at the start of the block + the number of relevant |
| // instructions generated by the block so far. |
| num_available_before_instruction_.insert( |
| {&inst, num_available_at_block_entry_.at(&block) + |
| static_cast<uint32_t>(generated_by_block.size())}); |
| if (predicate(ir_context, &inst)) { |
| // This instruction satisfies the predicate, so note that it is |
| // generated by |block|. |
| generated_by_block.push_back(&inst); |
| } |
| } |
| generated_by_block_.emplace(&block, std::move(generated_by_block)); |
| } |
| available_params_.emplace(&function, |
| std::move(available_params_for_function)); |
| } |
| } |
| |
| AvailableInstructions::AvailableBeforeInstruction |
| AvailableInstructions::GetAvailableBeforeInstruction( |
| opt::Instruction* inst) const { |
| assert(num_available_before_instruction_.count(inst) != 0 && |
| "Availability can only be queried for reachable instructions."); |
| return {*this, inst}; |
| } |
| |
| AvailableInstructions::AvailableBeforeInstruction::AvailableBeforeInstruction( |
| const AvailableInstructions& available_instructions, opt::Instruction* inst) |
| : available_instructions_(available_instructions), inst_(inst) {} |
| |
| uint32_t AvailableInstructions::AvailableBeforeInstruction::size() const { |
| return available_instructions_.num_available_before_instruction_.at(inst_); |
| } |
| |
| bool AvailableInstructions::AvailableBeforeInstruction::empty() const { |
| return size() == 0; |
| } |
| |
| opt::Instruction* AvailableInstructions::AvailableBeforeInstruction::operator[]( |
| uint32_t index) const { |
| assert(index < size() && "Index out of bounds."); |
| |
| // First, check the cache to see whether we can return the available |
| // instruction in constant time. |
| auto cached_result = index_cache.find(index); |
| if (cached_result != index_cache.end()) { |
| return cached_result->second; |
| } |
| |
| // Next check whether the index falls into the global region. |
| if (index < available_instructions_.available_globals_.size()) { |
| auto result = available_instructions_.available_globals_[index]; |
| index_cache.insert({index, result}); |
| return result; |
| } |
| |
| auto block = available_instructions_.ir_context_->get_instr_block(inst_); |
| auto function = block->GetParent(); |
| |
| // Next check whether the index falls into the available instructions that |
| // correspond to function parameters. |
| if (index < |
| available_instructions_.available_globals_.size() + |
| available_instructions_.available_params_.at(function).size()) { |
| auto result = available_instructions_.available_params_.at( |
| function)[index - available_instructions_.available_globals_.size()]; |
| index_cache.insert({index, result}); |
| return result; |
| } |
| |
| auto dominator_analysis = |
| available_instructions_.ir_context_->GetDominatorAnalysis(function); |
| |
| // Now the expensive part (which is why we have the cache): walk the dominator |
| // tree backwards starting from the block containing |inst_| until we get to |
| // the block in which the instruction corresponding to |index| exists. |
| for (auto* ancestor = block; true; |
| ancestor = dominator_analysis->ImmediateDominator(ancestor)) { |
| uint32_t num_available_at_ancestor_entry = |
| available_instructions_.num_available_at_block_entry_.at(ancestor); |
| if (index_cache.count(num_available_at_ancestor_entry) == 0) { |
| // This is the first time we have traversed this block, so we populate the |
| // cache with the index of each instruction, so that if a future index |
| // query relates to indices associated with this block we can return the |
| // result in constant time. |
| auto& generated_by_ancestor = |
| available_instructions_.generated_by_block_.at(ancestor); |
| for (uint32_t local_index = 0; local_index < generated_by_ancestor.size(); |
| local_index++) { |
| index_cache.insert({num_available_at_ancestor_entry + local_index, |
| generated_by_ancestor[local_index]}); |
| } |
| } |
| if (index >= num_available_at_ancestor_entry) { |
| // This block contains the instruction we want, so by now it will be in |
| // the cache. |
| return index_cache.at(index); |
| } |
| assert(ancestor != &*function->begin() && |
| "By construction we should find a block associated with the index."); |
| } |
| |
| assert(false && "Unreachable."); |
| return nullptr; |
| } |
| |
| } // namespace fuzz |
| } // namespace spvtools |