| // Copyright (c) 2020 Google LLC |
| // |
| // 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/fuzzer_pass_add_opphi_synonyms.h" |
| |
| #include "source/fuzz/fuzzer_util.h" |
| #include "source/fuzz/transformation_add_opphi_synonym.h" |
| |
| namespace spvtools { |
| namespace fuzz { |
| |
| FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms( |
| opt::IRContext* ir_context, TransformationContext* transformation_context, |
| FuzzerContext* fuzzer_context, |
| protobufs::TransformationSequence* transformations, |
| bool ignore_inapplicable_transformations) |
| : FuzzerPass(ir_context, transformation_context, fuzzer_context, |
| transformations, ignore_inapplicable_transformations) {} |
| |
| void FuzzerPassAddOpPhiSynonyms::Apply() { |
| // Get a list of synonymous ids with the same type that can be used in the |
| // same OpPhi instruction. |
| auto equivalence_classes = GetIdEquivalenceClasses(); |
| |
| // Make a list of references, to avoid copying sets unnecessarily. |
| std::vector<std::set<uint32_t>*> equivalence_class_pointers; |
| for (auto& set : equivalence_classes) { |
| equivalence_class_pointers.push_back(&set); |
| } |
| |
| // Keep a list of transformations to apply at the end. |
| std::vector<TransformationAddOpPhiSynonym> transformations_to_apply; |
| |
| for (auto& function : *GetIRContext()->module()) { |
| for (auto& block : function) { |
| // Randomly decide whether to consider this block. |
| if (!GetFuzzerContext()->ChoosePercentage( |
| GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) { |
| continue; |
| } |
| |
| // The block must not be dead. |
| if (GetTransformationContext()->GetFactManager()->BlockIsDead( |
| block.id())) { |
| continue; |
| } |
| |
| // The block must have at least one predecessor. |
| size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size(); |
| if (num_preds == 0) { |
| continue; |
| } |
| |
| std::set<uint32_t>* chosen_equivalence_class = nullptr; |
| |
| if (num_preds > 1) { |
| // If the block has more than one predecessor, prioritise sets with at |
| // least 2 ids available at some predecessor. |
| chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly( |
| equivalence_class_pointers, block.id(), 2); |
| } |
| |
| // If a set was not already chosen, choose one with at least one available |
| // id. |
| if (!chosen_equivalence_class) { |
| chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly( |
| equivalence_class_pointers, block.id(), 1); |
| } |
| |
| // If no suitable set was found, we cannot apply the transformation to |
| // this block. |
| if (!chosen_equivalence_class) { |
| continue; |
| } |
| |
| // Initialise the map from predecessor labels to ids. |
| std::map<uint32_t, uint32_t> preds_to_ids; |
| |
| // Keep track of the ids used and of the id of a predecessor with at least |
| // two ids to choose from. This is to ensure that, if possible, at least |
| // two distinct ids will be used. |
| std::set<uint32_t> ids_chosen; |
| uint32_t pred_with_alternatives = 0; |
| |
| // Choose an id for each predecessor. |
| for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) { |
| auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id); |
| assert(!suitable_ids.empty() && |
| "We must be able to find at least one suitable id because the " |
| "equivalence class was chosen among suitable ones."); |
| |
| // If this predecessor has more than one id to choose from and it is the |
| // first one of this kind that we found, remember its id. |
| if (suitable_ids.size() > 1 && !pred_with_alternatives) { |
| pred_with_alternatives = pred_id; |
| } |
| |
| uint32_t chosen_id = |
| suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)]; |
| |
| // Add this id to the set of ids chosen. |
| ids_chosen.emplace(chosen_id); |
| |
| // Add the pair (predecessor, chosen id) to the map. |
| preds_to_ids[pred_id] = chosen_id; |
| } |
| |
| // If: |
| // - the block has more than one predecessor |
| // - at least one predecessor has more than one alternative |
| // - the same id has been chosen by all the predecessors |
| // then choose another one for the predecessor with more than one |
| // alternative. |
| if (num_preds > 1 && pred_with_alternatives != 0 && |
| ids_chosen.size() == 1) { |
| auto suitable_ids = |
| GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives); |
| uint32_t chosen_id = |
| GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids); |
| if (chosen_id == preds_to_ids[pred_with_alternatives]) { |
| chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids); |
| } |
| |
| preds_to_ids[pred_with_alternatives] = chosen_id; |
| } |
| |
| // Add the transformation to the list of transformations to apply. |
| transformations_to_apply.emplace_back(block.id(), preds_to_ids, |
| GetFuzzerContext()->GetFreshId()); |
| } |
| } |
| |
| // Apply the transformations. |
| for (const auto& transformation : transformations_to_apply) { |
| ApplyTransformation(transformation); |
| } |
| } |
| |
| std::vector<std::set<uint32_t>> |
| FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() { |
| std::vector<std::set<uint32_t>> id_equivalence_classes; |
| |
| // Keep track of all the ids that have already be assigned to a class. |
| std::set<uint32_t> already_in_a_class; |
| |
| for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) { |
| // Exclude ids that have already been assigned to a class. |
| if (already_in_a_class.count(pair.first)) { |
| continue; |
| } |
| |
| // Exclude irrelevant ids. |
| if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant( |
| pair.first)) { |
| continue; |
| } |
| |
| // Exclude ids having a type that is not allowed by the transformation. |
| if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed( |
| GetIRContext(), pair.second->type_id())) { |
| continue; |
| } |
| |
| // Exclude OpFunction and OpUndef instructions, because: |
| // - OpFunction does not yield a value; |
| // - OpUndef yields an undefined value at each use, so it should never be a |
| // synonym of another id. |
| if (pair.second->opcode() == spv::Op::OpFunction || |
| pair.second->opcode() == spv::Op::OpUndef) { |
| continue; |
| } |
| |
| // We need a new equivalence class for this id. |
| std::set<uint32_t> new_equivalence_class; |
| |
| // Add this id to the class. |
| new_equivalence_class.emplace(pair.first); |
| already_in_a_class.emplace(pair.first); |
| |
| // Add all the synonyms with the same type to this class. |
| for (auto synonym : |
| GetTransformationContext()->GetFactManager()->GetSynonymsForId( |
| pair.first)) { |
| // The synonym must be a plain id - it cannot be an indexed access into a |
| // composite. |
| if (synonym->index_size() > 0) { |
| continue; |
| } |
| |
| // The synonym must not be irrelevant. |
| if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant( |
| synonym->object())) { |
| continue; |
| } |
| |
| auto synonym_def = |
| GetIRContext()->get_def_use_mgr()->GetDef(synonym->object()); |
| // The synonym must exist and have the same type as the id we are |
| // considering. |
| if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) { |
| continue; |
| } |
| |
| // We can add this synonym to the new equivalence class. |
| new_equivalence_class.emplace(synonym->object()); |
| already_in_a_class.emplace(synonym->object()); |
| } |
| |
| // Add the new equivalence class to the list of equivalence classes. |
| id_equivalence_classes.emplace_back(std::move(new_equivalence_class)); |
| } |
| |
| return id_equivalence_classes; |
| } |
| |
| bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock( |
| const std::set<uint32_t>& equivalence_class, uint32_t block_id, |
| uint32_t distinct_ids_required) { |
| bool at_least_one_id_for_each_pred = true; |
| |
| // Keep a set of the suitable ids found. |
| std::set<uint32_t> suitable_ids_found; |
| |
| // Loop through all the predecessors of the block. |
| for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) { |
| // Find the last instruction in the predecessor block. |
| auto last_instruction = |
| GetIRContext()->get_instr_block(pred_id)->terminator(); |
| |
| // Initially assume that there is not a suitable id for this predecessor. |
| bool at_least_one_suitable_id_found = false; |
| for (uint32_t id : equivalence_class) { |
| if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(), |
| last_instruction, id)) { |
| // We have found a suitable id. |
| at_least_one_suitable_id_found = true; |
| suitable_ids_found.emplace(id); |
| |
| // If we have already found enough distinct suitable ids, we don't need |
| // to check the remaining ones for this predecessor. |
| if (suitable_ids_found.size() >= distinct_ids_required) { |
| break; |
| } |
| } |
| } |
| // If no suitable id was found for this predecessor, this equivalence class |
| // is not suitable and we don't need to check the other predecessors. |
| if (!at_least_one_suitable_id_found) { |
| at_least_one_id_for_each_pred = false; |
| break; |
| } |
| } |
| |
| // The equivalence class is suitable if at least one suitable id was found for |
| // each predecessor and we have found at least |distinct_ids_required| |
| // distinct suitable ids in general. |
| return at_least_one_id_for_each_pred && |
| suitable_ids_found.size() >= distinct_ids_required; |
| } |
| |
| std::vector<uint32_t> FuzzerPassAddOpPhiSynonyms::GetSuitableIds( |
| const std::set<uint32_t>& ids, uint32_t pred_id) { |
| // Initialise an empty vector of suitable ids. |
| std::vector<uint32_t> suitable_ids; |
| |
| // Get the predecessor block. |
| auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id); |
| |
| // Loop through the ids to find the suitable ones. |
| for (uint32_t id : ids) { |
| if (fuzzerutil::IdIsAvailableBeforeInstruction( |
| GetIRContext(), predecessor->terminator(), id)) { |
| suitable_ids.push_back(id); |
| } |
| } |
| |
| return suitable_ids; |
| } |
| |
| std::set<uint32_t>* |
| FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly( |
| const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id, |
| uint32_t distinct_ids_required) { |
| auto remaining_candidates = candidates; |
| while (!remaining_candidates.empty()) { |
| // Choose one set randomly and return it if it is suitable. |
| auto chosen = |
| GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates); |
| if (EquivalenceClassIsSuitableForBlock(*chosen, block_id, |
| distinct_ids_required)) { |
| return chosen; |
| } |
| } |
| |
| // No suitable sets were found. |
| return nullptr; |
| } |
| |
| } // namespace fuzz |
| } // namespace spvtools |