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 .
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index ab7e985..905d535 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -59,6 +59,7 @@
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 @@
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 @@
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 @@
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
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index fea031c..e35fc8b 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.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 @@
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);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index ee8db86..6f76bbb 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -43,6 +43,7 @@
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 @@
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_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 9f85b13..bc98bc0 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -148,6 +148,9 @@
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 @@
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_;
diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
new file mode 100644
index 0000000..97adfb2
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
@@ -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
diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.h b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
new file mode 100644
index 0000000..ed61c020
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
@@ -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_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index c20fa99..ed3ce0e 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -414,6 +414,7 @@
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 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.
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 0df47d5..3cadac9 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -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 @@
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:
diff --git a/source/fuzz/transformation_add_opphi_synonym.cpp b/source/fuzz/transformation_add_opphi_synonym.cpp
new file mode 100644
index 0000000..3436dd7
--- /dev/null
+++ b/source/fuzz/transformation_add_opphi_synonym.cpp
@@ -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
diff --git a/source/fuzz/transformation_add_opphi_synonym.h b/source/fuzz/transformation_add_opphi_synonym.h
new file mode 100644
index 0000000..dc0e6d9
--- /dev/null
+++ b/source/fuzz/transformation_add_opphi_synonym.h
@@ -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_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 7204f69..1322dad 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -21,6 +21,7 @@
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 @@
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
diff --git a/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
new file mode 100644
index 0000000..9341b56
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
@@ -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
diff --git a/test/fuzz/transformation_add_opphi_synonym_test.cpp b/test/fuzz/transformation_add_opphi_synonym_test.cpp
new file mode 100644
index 0000000..2dad010
--- /dev/null
+++ b/test/fuzz/transformation_add_opphi_synonym_test.cpp
@@ -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