mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-01 23:40:04 +00:00
d35a78db57
Fixes #4960 * Switches to using enum classes with an underlying type to avoid undefined behaviour
312 lines
11 KiB
C++
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() == 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
|