SPIRV-Tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
Alastair Donaldson 9c4481419e
spirv-fuzz: Allow inapplicable transformations to be ignored (#4407)
spirv-fuzz features transformations that should be applicable by
construction. Assertions are used to detect when such transformations
turn out to be inapplicable. Failures of such assertions indicate bugs
in the fuzzer. However, when using the fuzzer at scale (e.g. in
ClusterFuzz) reports of these assertion failures create noise, and
cause the fuzzer to exit early. This change adds an option whereby
inapplicable transformations can be ignored. This reduces noise and
allows fuzzing to continue even when a transformation that should be
applicable but is not has been erroneously created.
2021-07-28 22:59:37 +01:00

312 lines
11 KiB
C++

// 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() == SpvOpFunction ||
pair.second->opcode() == SpvOpUndef) {
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