| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // 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
|