// 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& 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 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(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( generated_by_block_.at(immediate_dominator).size()) + num_available_at_block_entry_.at(immediate_dominator)}); } // Now consider each instruction in the block. std::vector 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(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