mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2025-01-13 18:00:05 +00:00
spirv-fuzz: Create synonym via OpPhi and existing synonyms (#3701)
A transformation that adds new OpPhi instructions to blocks with >=1 predecessors, so that its value depends on previously-defined ids of the right type, which are all synonymous. This instruction is also recorded as synonymous to the others. The related fuzzer pass still needs to be implemented. Fixes #3592 .
This commit is contained in:
parent
7e4948b2a5
commit
08291a3a9e
@ -59,6 +59,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_add_local_variables.h
|
||||
fuzzer_pass_add_loop_preheaders.h
|
||||
fuzzer_pass_add_no_contraction_decorations.h
|
||||
fuzzer_pass_add_opphi_synonyms.h
|
||||
fuzzer_pass_add_parameters.h
|
||||
fuzzer_pass_add_relaxed_decorations.h
|
||||
fuzzer_pass_add_stores.h
|
||||
@ -124,6 +125,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_add_local_variable.h
|
||||
transformation_add_loop_preheader.h
|
||||
transformation_add_no_contraction_decoration.h
|
||||
transformation_add_opphi_synonym.h
|
||||
transformation_add_parameter.h
|
||||
transformation_add_relaxed_decoration.h
|
||||
transformation_add_spec_constant_op.h
|
||||
@ -210,6 +212,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_add_local_variables.cpp
|
||||
fuzzer_pass_add_loop_preheaders.cpp
|
||||
fuzzer_pass_add_no_contraction_decorations.cpp
|
||||
fuzzer_pass_add_opphi_synonyms.cpp
|
||||
fuzzer_pass_add_parameters.cpp
|
||||
fuzzer_pass_add_relaxed_decorations.cpp
|
||||
fuzzer_pass_add_stores.cpp
|
||||
@ -274,6 +277,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_add_local_variable.cpp
|
||||
transformation_add_loop_preheader.cpp
|
||||
transformation_add_no_contraction_decoration.cpp
|
||||
transformation_add_opphi_synonym.cpp
|
||||
transformation_add_parameter.cpp
|
||||
transformation_add_relaxed_decoration.cpp
|
||||
transformation_add_spec_constant_op.cpp
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "source/fuzz/fuzzer_pass_add_local_variables.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_parameters.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_stores.h"
|
||||
@ -256,6 +257,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
||||
MaybeAddPass<FuzzerPassAddLoopPreheaders>(
|
||||
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
MaybeAddPass<FuzzerPassAddOpPhiSynonyms>(
|
||||
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
MaybeAddPass<FuzzerPassAddParameters>(
|
||||
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
|
@ -43,6 +43,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingLoopPreheader = {20, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
|
||||
5, 70};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingOpPhiSynonym = {5, 70};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
|
||||
@ -182,6 +183,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||
ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
|
||||
chance_of_adding_no_contraction_decoration_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
|
||||
chance_of_adding_opphi_synonym_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfAddingOpPhiSynonym);
|
||||
chance_of_adding_parameters =
|
||||
ChooseBetweenMinAndMax(kChanceOfAddingParameters);
|
||||
chance_of_adding_relaxed_decoration_ =
|
||||
|
@ -148,6 +148,9 @@ class FuzzerContext {
|
||||
uint32_t GetChanceOfAddingNoContractionDecoration() {
|
||||
return chance_of_adding_no_contraction_decoration_;
|
||||
}
|
||||
uint32_t GetChanceOfAddingOpPhiSynonym() {
|
||||
return chance_of_adding_opphi_synonym_;
|
||||
}
|
||||
uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; }
|
||||
uint32_t GetChanceOfAddingRelaxedDecoration() {
|
||||
return chance_of_adding_relaxed_decoration_;
|
||||
@ -367,6 +370,7 @@ class FuzzerContext {
|
||||
uint32_t chance_of_adding_loop_preheader_;
|
||||
uint32_t chance_of_adding_matrix_type_;
|
||||
uint32_t chance_of_adding_no_contraction_decoration_;
|
||||
uint32_t chance_of_adding_opphi_synonym_;
|
||||
uint32_t chance_of_adding_parameters;
|
||||
uint32_t chance_of_adding_relaxed_decoration_;
|
||||
uint32_t chance_of_adding_store_;
|
||||
|
297
source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
Normal file
297
source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
// 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)
|
||||
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
|
||||
transformations) {}
|
||||
|
||||
FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
// 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
|
73
source/fuzz/fuzzer_pass_add_opphi_synonyms.h
Normal file
73
source/fuzz/fuzzer_pass_add_opphi_synonyms.h
Normal file
@ -0,0 +1,73 @@
|
||||
// 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.
|
||||
|
||||
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
|
||||
#define SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
|
||||
|
||||
#include "source/fuzz/fuzzer_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// A fuzzer pass to add OpPhi instructions which can take the values of ids that
|
||||
// have been marked as synonymous. This instruction will itself be marked as
|
||||
// synonymous with the others.
|
||||
class FuzzerPassAddOpPhiSynonyms : public FuzzerPass {
|
||||
public:
|
||||
FuzzerPassAddOpPhiSynonyms(
|
||||
opt::IRContext* ir_context, TransformationContext* transformation_context,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations);
|
||||
|
||||
~FuzzerPassAddOpPhiSynonyms() override;
|
||||
|
||||
void Apply() override;
|
||||
|
||||
// Computes the equivalence classes for the non-pointer and non-irrelevant ids
|
||||
// in the module, where two ids are considered equivalent iff they have been
|
||||
// declared synonymous and they have the same type.
|
||||
std::vector<std::set<uint32_t>> GetIdEquivalenceClasses();
|
||||
|
||||
// Returns true iff |equivalence_class| contains at least
|
||||
// |distinct_ids_required| ids so that all of these ids are available at the
|
||||
// end of at least one predecessor of the block with label |block_id|.
|
||||
// Assumes that the block has at least one predecessor.
|
||||
bool EquivalenceClassIsSuitableForBlock(
|
||||
const std::set<uint32_t>& equivalence_class, uint32_t block_id,
|
||||
uint32_t distinct_ids_required);
|
||||
|
||||
// Returns a vector with the ids that are available to use at the end of the
|
||||
// block with id |pred_id|, selected among the given |ids|. Assumes that
|
||||
// |pred_id| is the label of a block and all ids in |ids| exist in the module.
|
||||
std::vector<uint32_t> GetSuitableIds(const std::set<uint32_t>& ids,
|
||||
uint32_t pred_id);
|
||||
|
||||
private:
|
||||
// Randomly chooses one of the equivalence classes in |candidates|, so that it
|
||||
// satisfies all of the following conditions:
|
||||
// - For each of the predecessors of the |block_id| block, there is at least
|
||||
// one id in the chosen equivalence class that is available at the end of
|
||||
// it.
|
||||
// - There are at least |distinct_ids_required| ids available at the end of
|
||||
// some predecessor.
|
||||
// Returns nullptr if no equivalence class in |candidates| satisfies the
|
||||
// requirements.
|
||||
std::set<uint32_t>* MaybeFindSuitableEquivalenceClassRandomly(
|
||||
const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
|
||||
uint32_t distinct_ids_required);
|
||||
};
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
|
@ -414,6 +414,7 @@ message Transformation {
|
||||
TransformationPropagateInstructionUp propagate_instruction_up = 67;
|
||||
TransformationCompositeInsert composite_insert = 68;
|
||||
TransformationInlineFunction inline_function = 69;
|
||||
TransformationAddOpPhiSynonym add_opphi_synonym = 70;
|
||||
// Add additional option using the next available number.
|
||||
}
|
||||
}
|
||||
@ -746,6 +747,24 @@ message TransformationAddNoContractionDecoration {
|
||||
|
||||
}
|
||||
|
||||
message TransformationAddOpPhiSynonym {
|
||||
|
||||
// Adds an OpPhi instruction at the start of a block with n predecessors (pred_1, pred_2, ..., pred_n)
|
||||
// and n related ids (id_1, id_2, ..., id_n) which are pairwise synonymous.
|
||||
// The instruction will be of the form:
|
||||
// %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
|
||||
// and fresh_id will be recorded as being synonymous with all the other ids.
|
||||
|
||||
// Label id of the block
|
||||
uint32 block_id = 1;
|
||||
|
||||
// Pairs (pred_i, id_i)
|
||||
repeated UInt32Pair pred_to_id = 2;
|
||||
|
||||
// Fresh id for the new instruction
|
||||
uint32 fresh_id = 3;
|
||||
}
|
||||
|
||||
message TransformationAddParameter {
|
||||
|
||||
// Adds a new parameter into the function.
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "source/fuzz/transformation_add_local_variable.h"
|
||||
#include "source/fuzz/transformation_add_loop_preheader.h"
|
||||
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
|
||||
#include "source/fuzz/transformation_add_opphi_synonym.h"
|
||||
#include "source/fuzz/transformation_add_parameter.h"
|
||||
#include "source/fuzz/transformation_add_relaxed_decoration.h"
|
||||
#include "source/fuzz/transformation_add_spec_constant_op.h"
|
||||
@ -141,6 +142,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
|
||||
kAddNoContractionDecoration:
|
||||
return MakeUnique<TransformationAddNoContractionDecoration>(
|
||||
message.add_no_contraction_decoration());
|
||||
case protobufs::Transformation::TransformationCase::kAddOpphiSynonym:
|
||||
return MakeUnique<TransformationAddOpPhiSynonym>(
|
||||
message.add_opphi_synonym());
|
||||
case protobufs::Transformation::TransformationCase::kAddParameter:
|
||||
return MakeUnique<TransformationAddParameter>(message.add_parameter());
|
||||
case protobufs::Transformation::TransformationCase::kAddRelaxedDecoration:
|
||||
|
200
source/fuzz/transformation_add_opphi_synonym.cpp
Normal file
200
source/fuzz/transformation_add_opphi_synonym.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
// 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/transformation_add_opphi_synonym.h"
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
|
||||
const protobufs::TransformationAddOpPhiSynonym& message)
|
||||
: message_(message) {}
|
||||
|
||||
TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
|
||||
uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
|
||||
uint32_t fresh_id) {
|
||||
message_.set_block_id(block_id);
|
||||
*message_.mutable_pred_to_id() =
|
||||
fuzzerutil::MapToRepeatedUInt32Pair(preds_to_ids);
|
||||
message_.set_fresh_id(fresh_id);
|
||||
}
|
||||
|
||||
bool TransformationAddOpPhiSynonym::IsApplicable(
|
||||
opt::IRContext* ir_context,
|
||||
const TransformationContext& transformation_context) const {
|
||||
// Check that |message_.block_id| is a block label id.
|
||||
auto block = fuzzerutil::MaybeFindBlock(ir_context, message_.block_id());
|
||||
if (!block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that |message_.fresh_id| is actually fresh.
|
||||
if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that |message_.pred_to_id| contains a mapping for all of the block's
|
||||
// predecessors.
|
||||
std::vector<uint32_t> predecessors = ir_context->cfg()->preds(block->id());
|
||||
|
||||
// There must be at least one predecessor.
|
||||
if (predecessors.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<uint32_t, uint32_t> preds_to_ids =
|
||||
fuzzerutil::RepeatedUInt32PairToMap(message_.pred_to_id());
|
||||
|
||||
// There must not be repeated key values in |message_.pred_to_id|.
|
||||
if (preds_to_ids.size() != static_cast<size_t>(message_.pred_to_id_size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that each predecessor has a corresponding mapping and all of the
|
||||
// corresponding ids exist.
|
||||
for (uint32_t pred : predecessors) {
|
||||
if (preds_to_ids.count(pred) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the id exists in the module.
|
||||
if (!ir_context->get_def_use_mgr()->GetDef(preds_to_ids[pred])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the first id and its type (which should be the same as all the other
|
||||
// ones) and check that the transformation supports this type.
|
||||
uint32_t first_id = preds_to_ids[predecessors[0]];
|
||||
uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
|
||||
if (!CheckTypeIsAllowed(ir_context, type_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the ids corresponding to predecessors are all synonymous, have
|
||||
// the same type and are available to use at the end of the predecessor.
|
||||
for (uint32_t pred : predecessors) {
|
||||
auto id = preds_to_ids[pred];
|
||||
|
||||
// Check that the id has the same type as the other ones.
|
||||
if (ir_context->get_def_use_mgr()->GetDef(id)->type_id() != type_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the id is synonymous with the others by checking that it is
|
||||
// synonymous with the first one (or it is the same id).
|
||||
if (id != first_id &&
|
||||
!transformation_context.GetFactManager()->IsSynonymous(
|
||||
MakeDataDescriptor(id, {}), MakeDataDescriptor(first_id, {}))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the id is available at the end of the corresponding
|
||||
// predecessor block.
|
||||
|
||||
auto pred_block = ir_context->get_instr_block(pred);
|
||||
|
||||
// We should always be able to find the predecessor block, since it is in
|
||||
// the predecessors list of |block|.
|
||||
assert(pred_block && "Could not find one of the predecessor blocks.");
|
||||
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722): This
|
||||
// function always returns false if the block is unreachable, so it may
|
||||
// need to be refactored.
|
||||
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
|
||||
ir_context, pred_block->terminator(), id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TransformationAddOpPhiSynonym::Apply(
|
||||
opt::IRContext* ir_context,
|
||||
TransformationContext* transformation_context) const {
|
||||
// Get the type id from one of the ids.
|
||||
uint32_t first_id = message_.pred_to_id(0).second();
|
||||
uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
|
||||
|
||||
// Define the operand list.
|
||||
opt::Instruction::OperandList operand_list;
|
||||
|
||||
// For each predecessor, add the corresponding operands.
|
||||
for (auto& pair : message_.pred_to_id()) {
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_ID, {pair.second()}});
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_ID, {pair.first()}});
|
||||
}
|
||||
|
||||
// Add a new OpPhi instructions at the beginning of the block.
|
||||
ir_context->get_instr_block(message_.block_id())
|
||||
->begin()
|
||||
.InsertBefore(MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, type_id,
|
||||
message_.fresh_id(),
|
||||
std::move(operand_list)));
|
||||
|
||||
// Update the module id bound.
|
||||
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
|
||||
|
||||
// Invalidate all analyses, since we added an instruction to the module.
|
||||
ir_context->InvalidateAnalysesExceptFor(
|
||||
opt::IRContext::Analysis::kAnalysisNone);
|
||||
|
||||
// Record the fact that the new id is synonym with the other ones by declaring
|
||||
// that it is a synonym of the first one.
|
||||
transformation_context->GetFactManager()->AddFactDataSynonym(
|
||||
MakeDataDescriptor(message_.fresh_id(), {}),
|
||||
MakeDataDescriptor(first_id, {}), ir_context);
|
||||
}
|
||||
|
||||
protobufs::Transformation TransformationAddOpPhiSynonym::ToMessage() const {
|
||||
protobufs::Transformation result;
|
||||
*result.mutable_add_opphi_synonym() = message_;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
|
||||
opt::IRContext* ir_context, uint32_t type_id) {
|
||||
auto type = ir_context->get_type_mgr()->GetType(type_id);
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We allow the following types: Bool, Integer, Float, Vector, Matrix, Array,
|
||||
// Struct.
|
||||
if (type->AsBool() || type->AsInteger() || type->AsFloat() ||
|
||||
type->AsVector() || type->AsMatrix() || type->AsArray() ||
|
||||
type->AsStruct()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We allow pointer types if the VariablePointers capability is enabled and
|
||||
// the pointer has the correct storage class (Workgroup or StorageBuffer).
|
||||
if (type->AsPointer()) {
|
||||
auto storage_class = type->AsPointer()->storage_class();
|
||||
return ir_context->get_feature_mgr()->HasCapability(
|
||||
SpvCapabilityVariablePointers) &&
|
||||
(storage_class == SpvStorageClassWorkgroup ||
|
||||
storage_class == SpvStorageClassStorageBuffer);
|
||||
}
|
||||
|
||||
// We do not allow other types.
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
72
source/fuzz/transformation_add_opphi_synonym.h
Normal file
72
source/fuzz/transformation_add_opphi_synonym.h
Normal file
@ -0,0 +1,72 @@
|
||||
// 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.
|
||||
|
||||
#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
|
||||
|
||||
#include "source/fuzz/transformation.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
class TransformationAddOpPhiSynonym : public Transformation {
|
||||
public:
|
||||
explicit TransformationAddOpPhiSynonym(
|
||||
const protobufs::TransformationAddOpPhiSynonym& message);
|
||||
|
||||
TransformationAddOpPhiSynonym(
|
||||
uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
|
||||
uint32_t fresh_id);
|
||||
|
||||
// - |message_.block_id| is the label of a block with at least one
|
||||
// predecessor.
|
||||
// - |message_.pred_to_id| contains a mapping from each of the predecessors of
|
||||
// the block to an id that is available at the end of the predecessor.
|
||||
// - All the ids corresponding to a predecessor in |message_.pred_to_id|:
|
||||
// - have been recorded as synonymous and all have the same type.
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3726): if a
|
||||
// predecessor is a dead block, any id of the right type could be used,
|
||||
// even if it is not synonym with the others.
|
||||
// - have one of the following types: Bool, Integer, Float, Vector, Matrix,
|
||||
// Array, Struct. Pointer types are also allowed if the VariablePointers
|
||||
// capability is enabled and the storage class is Workgroup or
|
||||
// StorageBuffer.
|
||||
// - |message_.fresh_id| is a fresh id.
|
||||
bool IsApplicable(
|
||||
opt::IRContext* ir_context,
|
||||
const TransformationContext& transformation_context) const override;
|
||||
|
||||
// Given a block with n predecessors, with n >= 1, and n corresponding
|
||||
// synonymous ids of the same type, each available to use at the end of the
|
||||
// corresponding predecessor, adds an OpPhi instruction at the beginning of
|
||||
// the block of the form:
|
||||
// %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
|
||||
// This instruction is then marked as synonymous with the ids.
|
||||
void Apply(opt::IRContext* ir_context,
|
||||
TransformationContext* transformation_context) const override;
|
||||
|
||||
// Returns true if |type_id| is the id of a type in the module, which is one
|
||||
// of the following: Bool, Integer, Float, Vector, Matrix, Array, Struct.
|
||||
// Pointer types are also allowed if the VariablePointers capability is
|
||||
// enabled and the storage class is Workgroup or StorageBuffer.
|
||||
static bool CheckTypeIsAllowed(opt::IRContext* ir_context, uint32_t type_id);
|
||||
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
private:
|
||||
protobufs::TransformationAddOpPhiSynonym message_;
|
||||
};
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
|
@ -21,6 +21,7 @@ if (${SPIRV_BUILD_FUZZER})
|
||||
equivalence_relation_test.cpp
|
||||
fact_manager_test.cpp
|
||||
fuzz_test_util.cpp
|
||||
fuzzer_pass_add_opphi_synonyms_test.cpp
|
||||
fuzzer_pass_construct_composites_test.cpp
|
||||
fuzzer_pass_donate_modules_test.cpp
|
||||
fuzzer_pass_outline_functions_test.cpp
|
||||
@ -42,6 +43,7 @@ if (${SPIRV_BUILD_FUZZER})
|
||||
transformation_add_local_variable_test.cpp
|
||||
transformation_add_loop_preheader_test.cpp
|
||||
transformation_add_no_contraction_decoration_test.cpp
|
||||
transformation_add_opphi_synonym_test.cpp
|
||||
transformation_add_parameter_test.cpp
|
||||
transformation_add_relaxed_decoration_test.cpp
|
||||
transformation_add_synonym_test.cpp
|
||||
|
170
test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
Normal file
170
test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
// 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/pseudo_random_generator.h"
|
||||
#include "test/fuzz/fuzz_test_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
namespace {
|
||||
|
||||
protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) {
|
||||
protobufs::FactDataSynonym data_synonym_fact;
|
||||
*data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {});
|
||||
*data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {});
|
||||
protobufs::Fact result;
|
||||
*result.mutable_data_synonym_fact() = data_synonym_fact;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Adds synonym facts to the fact manager.
|
||||
void SetUpIdSynonyms(FactManager* fact_manager, opt::IRContext* context) {
|
||||
// Synonyms {9, 11, 15, 16, 21, 22}
|
||||
fact_manager->AddFact(MakeSynonymFact(11, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(15, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(16, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(21, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(22, 9), context);
|
||||
|
||||
// Synonyms {10, 23}
|
||||
fact_manager->AddFact(MakeSynonymFact(10, 23), context);
|
||||
|
||||
// Synonyms {14, 27}
|
||||
fact_manager->AddFact(MakeSynonymFact(14, 27), context);
|
||||
|
||||
// Synonyms {24, 26, 30}
|
||||
fact_manager->AddFact(MakeSynonymFact(26, 24), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(30, 24), context);
|
||||
}
|
||||
|
||||
// Returns true if the given lists have the same elements, regardless of their
|
||||
// order.
|
||||
template <typename T>
|
||||
bool ListsHaveTheSameElements(const std::vector<T>& list1,
|
||||
const std::vector<T>& list2) {
|
||||
auto sorted1 = list1;
|
||||
std::sort(sorted1.begin(), sorted1.end());
|
||||
|
||||
auto sorted2 = list2;
|
||||
std::sort(sorted2.begin(), sorted2.end());
|
||||
|
||||
return sorted1 == sorted2;
|
||||
}
|
||||
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
%3 = OpTypeVoid
|
||||
%4 = OpTypeFunction %3
|
||||
%5 = OpTypeBool
|
||||
%6 = OpConstantTrue %5
|
||||
%7 = OpTypeInt 32 1
|
||||
%8 = OpTypeInt 32 0
|
||||
%9 = OpConstant %7 1
|
||||
%10 = OpConstant %7 2
|
||||
%11 = OpConstant %8 1
|
||||
%12 = OpTypePointer Function %7
|
||||
%2 = OpFunction %3 None %4
|
||||
%13 = OpLabel
|
||||
%14 = OpVariable %12 Function
|
||||
%15 = OpCopyObject %7 %9
|
||||
%16 = OpCopyObject %8 %11
|
||||
OpBranch %17
|
||||
%17 = OpLabel
|
||||
OpSelectionMerge %18 None
|
||||
OpBranchConditional %6 %19 %20
|
||||
%19 = OpLabel
|
||||
%21 = OpCopyObject %7 %15
|
||||
%22 = OpCopyObject %8 %16
|
||||
%23 = OpCopyObject %7 %10
|
||||
%24 = OpIAdd %7 %9 %10
|
||||
OpBranch %18
|
||||
%20 = OpLabel
|
||||
OpBranch %18
|
||||
%18 = OpLabel
|
||||
%26 = OpIAdd %7 %15 %10
|
||||
%27 = OpCopyObject %12 %14
|
||||
OpSelectionMerge %28 None
|
||||
OpBranchConditional %6 %29 %28
|
||||
%29 = OpLabel
|
||||
%30 = OpCopyObject %7 %26
|
||||
OpBranch %28
|
||||
%28 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
TEST(FuzzerPassAddOpPhiSynonymsTest, HelperFunctions) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
TransformationContext transformation_context(&fact_manager,
|
||||
validator_options);
|
||||
|
||||
PseudoRandomGenerator prng(0);
|
||||
FuzzerContext fuzzer_context(&prng, 100);
|
||||
protobufs::TransformationSequence transformation_sequence;
|
||||
|
||||
FuzzerPassAddOpPhiSynonyms fuzzer_pass(context.get(), &transformation_context,
|
||||
&fuzzer_context,
|
||||
&transformation_sequence);
|
||||
|
||||
SetUpIdSynonyms(&fact_manager, context.get());
|
||||
|
||||
std::vector<std::set<uint32_t>> expected_equivalence_classes = {
|
||||
{9, 15, 21}, {11, 16, 22}, {10, 23}, {6}, {24, 26, 30}};
|
||||
|
||||
ASSERT_TRUE(ListsHaveTheSameElements<std::set<uint32_t>>(
|
||||
fuzzer_pass.GetIdEquivalenceClasses(), expected_equivalence_classes));
|
||||
|
||||
// The set {24, 26, 30} is not suitable for 18 (none if the ids is available
|
||||
// for predecessor 20).
|
||||
ASSERT_FALSE(
|
||||
fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 18, 1));
|
||||
|
||||
// The set {6} is not suitable for 18 if we require at least 2 distinct
|
||||
// available ids.
|
||||
ASSERT_FALSE(fuzzer_pass.EquivalenceClassIsSuitableForBlock({6}, 18, 2));
|
||||
|
||||
// Only id 26 from the set {24, 26, 30} is available to use for the
|
||||
// transformation at block 29, so the set is not suitable if we want at least
|
||||
// 2 available ids.
|
||||
ASSERT_FALSE(
|
||||
fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 2));
|
||||
|
||||
ASSERT_TRUE(
|
||||
fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 1));
|
||||
|
||||
// %21 is not available at the end of block 20.
|
||||
ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>(
|
||||
fuzzer_pass.GetSuitableIds({9, 15, 21}, 20), {9, 15}));
|
||||
|
||||
// %24 and %30 are not available at the end of block 18.
|
||||
ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>(
|
||||
fuzzer_pass.GetSuitableIds({24, 26, 30}, 18), {26}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
423
test/fuzz/transformation_add_opphi_synonym_test.cpp
Normal file
423
test/fuzz/transformation_add_opphi_synonym_test.cpp
Normal file
@ -0,0 +1,423 @@
|
||||
// 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/transformation_add_opphi_synonym.h"
|
||||
|
||||
#include "test/fuzz/fuzz_test_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
namespace {
|
||||
|
||||
protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) {
|
||||
protobufs::FactDataSynonym data_synonym_fact;
|
||||
*data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {});
|
||||
*data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {});
|
||||
protobufs::Fact result;
|
||||
*result.mutable_data_synonym_fact() = data_synonym_fact;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Adds synonym facts to the fact manager.
|
||||
void SetUpIdSynonyms(FactManager* fact_manager, opt::IRContext* context) {
|
||||
fact_manager->AddFact(MakeSynonymFact(11, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(13, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(14, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(19, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(20, 9), context);
|
||||
fact_manager->AddFact(MakeSynonymFact(10, 21), context);
|
||||
}
|
||||
|
||||
TEST(TransformationAddOpPhiSynonymTest, Inapplicable) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
%3 = OpTypeVoid
|
||||
%4 = OpTypeFunction %3
|
||||
%5 = OpTypeBool
|
||||
%6 = OpConstantTrue %5
|
||||
%7 = OpTypeInt 32 1
|
||||
%8 = OpTypeInt 32 0
|
||||
%22 = OpTypePointer Function %7
|
||||
%9 = OpConstant %7 1
|
||||
%10 = OpConstant %7 2
|
||||
%11 = OpConstant %8 1
|
||||
%2 = OpFunction %3 None %4
|
||||
%12 = OpLabel
|
||||
%23 = OpVariable %22 Function
|
||||
%13 = OpCopyObject %7 %9
|
||||
%14 = OpCopyObject %8 %11
|
||||
OpBranch %15
|
||||
%15 = OpLabel
|
||||
OpSelectionMerge %16 None
|
||||
OpBranchConditional %6 %17 %18
|
||||
%17 = OpLabel
|
||||
%19 = OpCopyObject %7 %13
|
||||
%20 = OpCopyObject %8 %14
|
||||
%21 = OpCopyObject %7 %10
|
||||
OpBranch %16
|
||||
%18 = OpLabel
|
||||
%24 = OpCopyObject %22 %23
|
||||
OpBranch %16
|
||||
%16 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
TransformationContext transformation_context(&fact_manager,
|
||||
validator_options);
|
||||
|
||||
SetUpIdSynonyms(&fact_manager, context.get());
|
||||
fact_manager.AddFact(MakeSynonymFact(23, 24), context.get());
|
||||
|
||||
// %13 is not a block label.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// Block %12 does not have a predecessor.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(12, {{}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// Not all predecessors of %16 (%17 and %18) are considered in the map.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %30 does not exist in the module.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{30, 19}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %20 is not a block label.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{20, 19}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %15 is not the id of one of the predecessors of the block.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{15, 19}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %30 does not exist in the module.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 30}, {18, 13}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %19 and %10 are not synonymous.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 10}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %19 and %14 do not have the same type.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 14}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %19 is not available at the end of %18.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 19}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %21 is not a fresh id.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 9}}}, 21)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// %23 and %24 have pointer id.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 23}, {18, 24}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
}
|
||||
|
||||
TEST(TransformationAddOpPhiSynonymTest, Apply) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
%3 = OpTypeVoid
|
||||
%4 = OpTypeFunction %3
|
||||
%5 = OpTypeBool
|
||||
%6 = OpConstantTrue %5
|
||||
%7 = OpTypeInt 32 1
|
||||
%8 = OpTypeInt 32 0
|
||||
%9 = OpConstant %7 1
|
||||
%10 = OpConstant %7 2
|
||||
%11 = OpConstant %8 1
|
||||
%2 = OpFunction %3 None %4
|
||||
%12 = OpLabel
|
||||
%13 = OpCopyObject %7 %9
|
||||
%14 = OpCopyObject %8 %11
|
||||
OpBranch %15
|
||||
%15 = OpLabel
|
||||
OpSelectionMerge %16 None
|
||||
OpBranchConditional %6 %17 %18
|
||||
%17 = OpLabel
|
||||
%19 = OpCopyObject %7 %13
|
||||
%20 = OpCopyObject %8 %14
|
||||
%21 = OpCopyObject %7 %10
|
||||
OpBranch %16
|
||||
%18 = OpLabel
|
||||
OpBranch %16
|
||||
%16 = OpLabel
|
||||
OpBranch %22
|
||||
%22 = OpLabel
|
||||
OpLoopMerge %23 %24 None
|
||||
OpBranchConditional %6 %25 %23
|
||||
%25 = OpLabel
|
||||
OpSelectionMerge %26 None
|
||||
OpBranchConditional %6 %27 %26
|
||||
%27 = OpLabel
|
||||
%28 = OpCopyObject %7 %13
|
||||
OpBranch %23
|
||||
%26 = OpLabel
|
||||
OpSelectionMerge %29 None
|
||||
OpBranchConditional %6 %29 %24
|
||||
%29 = OpLabel
|
||||
%30 = OpCopyObject %7 %13
|
||||
OpBranch %23
|
||||
%24 = OpLabel
|
||||
OpBranch %22
|
||||
%23 = OpLabel
|
||||
OpSelectionMerge %31 None
|
||||
OpBranchConditional %6 %31 %31
|
||||
%31 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
TransformationContext transformation_context(&fact_manager,
|
||||
validator_options);
|
||||
|
||||
SetUpIdSynonyms(&fact_manager, context.get());
|
||||
|
||||
// Add some further synonym facts.
|
||||
fact_manager.AddFact(MakeSynonymFact(28, 9), context.get());
|
||||
fact_manager.AddFact(MakeSynonymFact(30, 9), context.get());
|
||||
|
||||
auto transformation1 = TransformationAddOpPhiSynonym(17, {{{15, 13}}}, 100);
|
||||
ASSERT_TRUE(
|
||||
transformation1.IsApplicable(context.get(), transformation_context));
|
||||
transformation1.Apply(context.get(), &transformation_context);
|
||||
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(100, {}),
|
||||
MakeDataDescriptor(9, {})));
|
||||
|
||||
auto transformation2 =
|
||||
TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 13}}}, 101);
|
||||
ASSERT_TRUE(
|
||||
transformation2.IsApplicable(context.get(), transformation_context));
|
||||
transformation2.Apply(context.get(), &transformation_context);
|
||||
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(101, {}),
|
||||
MakeDataDescriptor(9, {})));
|
||||
|
||||
auto transformation3 =
|
||||
TransformationAddOpPhiSynonym(23, {{{22, 13}, {27, 28}, {29, 30}}}, 102);
|
||||
ASSERT_TRUE(
|
||||
transformation3.IsApplicable(context.get(), transformation_context));
|
||||
transformation3.Apply(context.get(), &transformation_context);
|
||||
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(102, {}),
|
||||
MakeDataDescriptor(9, {})));
|
||||
|
||||
auto transformation4 = TransformationAddOpPhiSynonym(31, {{{23, 13}}}, 103);
|
||||
ASSERT_TRUE(
|
||||
transformation4.IsApplicable(context.get(), transformation_context));
|
||||
transformation4.Apply(context.get(), &transformation_context);
|
||||
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(103, {}),
|
||||
MakeDataDescriptor(9, {})));
|
||||
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
std::string after_transformations = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
%3 = OpTypeVoid
|
||||
%4 = OpTypeFunction %3
|
||||
%5 = OpTypeBool
|
||||
%6 = OpConstantTrue %5
|
||||
%7 = OpTypeInt 32 1
|
||||
%8 = OpTypeInt 32 0
|
||||
%9 = OpConstant %7 1
|
||||
%10 = OpConstant %7 2
|
||||
%11 = OpConstant %8 1
|
||||
%2 = OpFunction %3 None %4
|
||||
%12 = OpLabel
|
||||
%13 = OpCopyObject %7 %9
|
||||
%14 = OpCopyObject %8 %11
|
||||
OpBranch %15
|
||||
%15 = OpLabel
|
||||
OpSelectionMerge %16 None
|
||||
OpBranchConditional %6 %17 %18
|
||||
%17 = OpLabel
|
||||
%100 = OpPhi %7 %13 %15
|
||||
%19 = OpCopyObject %7 %13
|
||||
%20 = OpCopyObject %8 %14
|
||||
%21 = OpCopyObject %7 %10
|
||||
OpBranch %16
|
||||
%18 = OpLabel
|
||||
OpBranch %16
|
||||
%16 = OpLabel
|
||||
%101 = OpPhi %7 %19 %17 %13 %18
|
||||
OpBranch %22
|
||||
%22 = OpLabel
|
||||
OpLoopMerge %23 %24 None
|
||||
OpBranchConditional %6 %25 %23
|
||||
%25 = OpLabel
|
||||
OpSelectionMerge %26 None
|
||||
OpBranchConditional %6 %27 %26
|
||||
%27 = OpLabel
|
||||
%28 = OpCopyObject %7 %13
|
||||
OpBranch %23
|
||||
%26 = OpLabel
|
||||
OpSelectionMerge %29 None
|
||||
OpBranchConditional %6 %29 %24
|
||||
%29 = OpLabel
|
||||
%30 = OpCopyObject %7 %13
|
||||
OpBranch %23
|
||||
%24 = OpLabel
|
||||
OpBranch %22
|
||||
%23 = OpLabel
|
||||
%102 = OpPhi %7 %13 %22 %28 %27 %30 %29
|
||||
OpSelectionMerge %31 None
|
||||
OpBranchConditional %6 %31 %31
|
||||
%31 = OpLabel
|
||||
%103 = OpPhi %7 %13 %23
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
|
||||
}
|
||||
|
||||
TEST(TransformationAddOpPhiSynonymTest, VariablePointers) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
OpCapability VariablePointers
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main" %3
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%4 = OpTypeVoid
|
||||
%5 = OpTypeFunction %4
|
||||
%6 = OpTypeBool
|
||||
%7 = OpConstantTrue %6
|
||||
%8 = OpTypeInt 32 1
|
||||
%9 = OpTypePointer Function %8
|
||||
%10 = OpTypePointer Workgroup %8
|
||||
%3 = OpVariable %10 Workgroup
|
||||
%2 = OpFunction %4 None %5
|
||||
%11 = OpLabel
|
||||
%12 = OpVariable %9 Function
|
||||
OpSelectionMerge %13 None
|
||||
OpBranchConditional %7 %14 %13
|
||||
%14 = OpLabel
|
||||
%15 = OpCopyObject %10 %3
|
||||
%16 = OpCopyObject %9 %12
|
||||
OpBranch %13
|
||||
%13 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
TransformationContext transformation_context(&fact_manager,
|
||||
validator_options);
|
||||
|
||||
// Declare synonyms
|
||||
fact_manager.AddFact(MakeSynonymFact(3, 15), context.get());
|
||||
fact_manager.AddFact(MakeSynonymFact(12, 16), context.get());
|
||||
|
||||
// Remove the VariablePointers capability.
|
||||
context.get()->get_feature_mgr()->RemoveCapability(
|
||||
SpvCapabilityVariablePointers);
|
||||
|
||||
// The VariablePointers capability is required to add an OpPhi instruction of
|
||||
// pointer type.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// Add the VariablePointers capability back.
|
||||
context.get()->get_feature_mgr()->AddCapability(
|
||||
SpvCapabilityVariablePointers);
|
||||
|
||||
// If the ids have pointer type, the storage class must be Workgroup or
|
||||
// StorageBuffer, but it is Function in this case.
|
||||
ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 12}, {14, 16}}}, 100)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
auto transformation =
|
||||
TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100);
|
||||
ASSERT_TRUE(
|
||||
transformation.IsApplicable(context.get(), transformation_context));
|
||||
transformation.Apply(context.get(), &transformation_context);
|
||||
|
||||
std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
OpCapability VariablePointers
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main" %3
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%4 = OpTypeVoid
|
||||
%5 = OpTypeFunction %4
|
||||
%6 = OpTypeBool
|
||||
%7 = OpConstantTrue %6
|
||||
%8 = OpTypeInt 32 1
|
||||
%9 = OpTypePointer Function %8
|
||||
%10 = OpTypePointer Workgroup %8
|
||||
%3 = OpVariable %10 Workgroup
|
||||
%2 = OpFunction %4 None %5
|
||||
%11 = OpLabel
|
||||
%12 = OpVariable %9 Function
|
||||
OpSelectionMerge %13 None
|
||||
OpBranchConditional %7 %14 %13
|
||||
%14 = OpLabel
|
||||
%15 = OpCopyObject %10 %3
|
||||
%16 = OpCopyObject %9 %12
|
||||
OpBranch %13
|
||||
%13 = OpLabel
|
||||
%100 = OpPhi %10 %3 %11 %15 %14
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
||||
}
|
||||
} // namespace
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
Loading…
Reference in New Issue
Block a user