#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() {
auto equivalence_classes = GetIdEquivalenceClasses();
std::vector<std::set<uint32_t>*> equivalence_class_pointers;
for (auto& set : equivalence_classes) {
equivalence_class_pointers.push_back(&set);
}
std::vector<TransformationAddOpPhiSynonym> transformations_to_apply;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) {
continue;
}
if (GetTransformationContext()->GetFactManager()->BlockIsDead(
block.id())) {
continue;
}
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) {
chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
equivalence_class_pointers, block.id(), 2);
}
if (!chosen_equivalence_class) {
chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
equivalence_class_pointers, block.id(), 1);
}
if (!chosen_equivalence_class) {
continue;
}
std::map<uint32_t, uint32_t> preds_to_ids;
std::set<uint32_t> ids_chosen;
uint32_t pred_with_alternatives = 0;
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 (suitable_ids.size() > 1 && !pred_with_alternatives) {
pred_with_alternatives = pred_id;
}
uint32_t chosen_id =
suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)];
ids_chosen.emplace(chosen_id);
preds_to_ids[pred_id] = chosen_id;
}
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;
}
transformations_to_apply.emplace_back(block.id(), preds_to_ids,
GetFuzzerContext()->GetFreshId());
}
}
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;
std::set<uint32_t> already_in_a_class;
for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
if (already_in_a_class.count(pair.first)) {
continue;
}
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
pair.first)) {
continue;
}
if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
GetIRContext(), pair.second->type_id())) {
continue;
}
if (pair.second->opcode() == spv::Op::OpFunction ||
pair.second->opcode() == spv::Op::OpUndef) {
continue;
}
std::set<uint32_t> new_equivalence_class;
new_equivalence_class.emplace(pair.first);
already_in_a_class.emplace(pair.first);
for (auto synonym :
GetTransformationContext()->GetFactManager()->GetSynonymsForId(
pair.first)) {
if (synonym->index_size() > 0) {
continue;
}
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
synonym->object())) {
continue;
}
auto synonym_def =
GetIRContext()->get_def_use_mgr()->GetDef(synonym->object());
if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) {
continue;
}
new_equivalence_class.emplace(synonym->object());
already_in_a_class.emplace(synonym->object());
}
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;
std::set<uint32_t> suitable_ids_found;
for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) {
auto last_instruction =
GetIRContext()->get_instr_block(pred_id)->terminator();
bool at_least_one_suitable_id_found = false;
for (uint32_t id : equivalence_class) {
if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(),
last_instruction, id)) {
at_least_one_suitable_id_found = true;
suitable_ids_found.emplace(id);
if (suitable_ids_found.size() >= distinct_ids_required) {
break;
}
}
}
if (!at_least_one_suitable_id_found) {
at_least_one_id_for_each_pred = false;
break;
}
}
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) {
std::vector<uint32_t> suitable_ids;
auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id);
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()) {
auto chosen =
GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates);
if (EquivalenceClassIsSuitableForBlock(*chosen, block_id,
distinct_ids_required)) {
return chosen;
}
}
return nullptr;
}
}
}