// 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) : FuzzerPass(ir_context, transformation_context, fuzzer_context, 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*> 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 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* 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 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 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> FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() { std::vector> id_equivalence_classes; // Keep track of all the ids that have already be assigned to a class. std::set 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() == SpvOpFunction || pair.second->opcode() == SpvOpUndef) { continue; } // We need a new equivalence class for this id. std::set 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& 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 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 FuzzerPassAddOpPhiSynonyms::GetSuitableIds( const std::set& ids, uint32_t pred_id) { // Initialise an empty vector of suitable ids. std::vector 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* FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly( const std::vector*>& 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