1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
// 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
|