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