aboutsummaryrefslogtreecommitdiff
path: root/source/fuzz
diff options
context:
space:
mode:
Diffstat (limited to 'source/fuzz')
-rw-r--r--source/fuzz/CMakeLists.txt4
-rw-r--r--source/fuzz/fuzzer.cpp4
-rw-r--r--source/fuzz/fuzzer_context.cpp3
-rw-r--r--source/fuzz/fuzzer_context.h4
-rw-r--r--source/fuzz/fuzzer_pass_add_dead_breaks.cpp6
-rw-r--r--source/fuzz/fuzzer_pass_add_dead_continues.cpp6
-rw-r--r--source/fuzz/fuzzer_pass_apply_id_synonyms.cpp108
-rw-r--r--source/fuzz/fuzzer_pass_apply_id_synonyms.h42
-rw-r--r--source/fuzz/id_use_descriptor.cpp28
-rw-r--r--source/fuzz/id_use_descriptor.h6
-rw-r--r--source/fuzz/protobufs/spvtoolsfuzz.proto48
-rw-r--r--source/fuzz/transformation.cpp4
-rw-r--r--source/fuzz/transformation_replace_id_with_synonym.cpp155
-rw-r--r--source/fuzz/transformation_replace_id_with_synonym.h72
14 files changed, 469 insertions, 21 deletions
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index c0592b34..2262c6d9 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -34,6 +34,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_add_dead_breaks.h
fuzzer_pass_add_dead_continues.h
fuzzer_pass_add_useful_constructs.h
+ fuzzer_pass_apply_id_synonyms.h
fuzzer_pass_copy_objects.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_permute_blocks.h
@@ -58,6 +59,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_move_block_down.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
+ transformation_replace_id_with_synonym.h
transformation_split_block.h
uniform_buffer_element_descriptor.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
@@ -70,6 +72,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_add_dead_breaks.cpp
fuzzer_pass_add_dead_continues.cpp
fuzzer_pass_add_useful_constructs.cpp
+ fuzzer_pass_apply_id_synonyms.cpp
fuzzer_pass_copy_objects.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_permute_blocks.cpp
@@ -93,6 +96,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_move_block_down.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp
+ transformation_replace_id_with_synonym.cpp
transformation_split_block.cpp
uniform_buffer_element_descriptor.cpp
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 9d2a7c74..3b034938 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -22,6 +22,7 @@
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
@@ -111,6 +112,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
FuzzerPassCopyObjects(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
+ FuzzerPassApplyIdSynonyms(ir_context.get(), &fact_manager, &fuzzer_context,
+ transformation_sequence_out)
+ .Apply();
FuzzerPassSplitBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index e9398d5f..8d097b09 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -29,6 +29,7 @@ const uint32_t kDefaultChanceOfAddingDeadContinue = 20;
const uint32_t kDefaultChanceOfCopyingObject = 20;
const uint32_t kDefaultChanceOfMovingBlockDown = 25;
const uint32_t kDefaultChanceOfObfuscatingConstant = 20;
+const uint32_t kDefaultChanceOfReplacingIdWithSynonym = 20;
const uint32_t kDefaultChanceOfSplittingBlock = 20;
// Default functions for controlling how deep to go during recursive
@@ -52,6 +53,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_copying_object_(kDefaultChanceOfCopyingObject),
chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant),
+ chance_of_replacing_id_with_synonym_(
+ kDefaultChanceOfReplacingIdWithSynonym),
chance_of_splitting_block_(kDefaultChanceOfSplittingBlock),
go_deeper_in_constant_obfuscation_(
kDefaultGoDeeperInConstantObfuscation) {}
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index ed6a3551..90d87566 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -66,6 +66,9 @@ class FuzzerContext {
uint32_t GetChanceOfObfuscatingConstant() {
return chance_of_obfuscating_constant_;
}
+ uint32_t GetChanceOfReplacingIdWithSynonym() {
+ return chance_of_replacing_id_with_synonym_;
+ }
uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
// Functions to control how deeply to recurse.
@@ -87,6 +90,7 @@ class FuzzerContext {
uint32_t chance_of_copying_object_;
uint32_t chance_of_moving_block_down_;
uint32_t chance_of_obfuscating_constant_;
+ uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_splitting_block_;
// Functions to determine with what probability to go deeper when generating
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
index d8380d3e..fa6b0988 100644
--- a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
@@ -47,9 +47,9 @@ void FuzzerPassAddDeadBreaks::Apply() {
// ones that turn out to be no good.
for (auto& block : function) {
for (auto merge_block_id : merge_block_ids) {
- // TODO(afd): right now we completely ignore OpPhi instructions at
- // merge blocks. This will lead to interesting opportunities being
- // missed.
+ // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2856): right
+ // now we completely ignore OpPhi instructions at merge blocks. This
+ // will lead to interesting opportunities being missed.
auto candidate_transformation = TransformationAddDeadBreak(
block.id(), merge_block_id, GetFuzzerContext()->ChooseEven(), {});
if (candidate_transformation.IsApplicable(GetIRContext(),
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
index ac4c72f7..51bcb91e 100644
--- a/source/fuzz/fuzzer_pass_add_dead_continues.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
@@ -36,9 +36,9 @@ void FuzzerPassAddDeadContinues::Apply() {
// node turns out to be inappropriate (e.g. by not being in a loop) the
// precondition for the transformation will fail and it will be ignored.
//
- // TODO(afd): right now we completely ignore OpPhi instructions at
- // merge blocks. This will lead to interesting opportunities being
- // missed.
+ // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2856): right
+ // now we completely ignore OpPhi instructions at continue targets.
+ // This will lead to interesting opportunities being missed.
auto candidate_transformation = TransformationAddDeadContinue(
block.id(), GetFuzzerContext()->ChooseEven(), {});
// Probabilistically decide whether to apply the transformation in the
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
new file mode 100644
index 00000000..703f6f27
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
@@ -0,0 +1,108 @@
+// Copyright (c) 2019 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_apply_id_synonyms.h"
+
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/transformation_replace_id_with_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassApplyIdSynonyms::FuzzerPassApplyIdSynonyms(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassApplyIdSynonyms::~FuzzerPassApplyIdSynonyms() = default;
+
+void FuzzerPassApplyIdSynonyms::Apply() {
+ std::vector<TransformationReplaceIdWithSynonym> transformations_to_apply;
+
+ for (auto id_with_known_synonyms :
+ GetFactManager()->GetIdsForWhichSynonymsAreKnown()) {
+ GetIRContext()->get_def_use_mgr()->ForEachUse(
+ id_with_known_synonyms,
+ [this, id_with_known_synonyms, &transformations_to_apply](
+ opt::Instruction* use_inst, uint32_t use_index) -> void {
+ auto block_containing_use = GetIRContext()->get_instr_block(use_inst);
+ // The use might not be in a block; e.g. it could be a decoration.
+ if (!block_containing_use) {
+ return;
+ }
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfReplacingIdWithSynonym())) {
+ return;
+ }
+
+ std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
+ for (auto& data_descriptor :
+ GetFactManager()->GetSynonymsForId(id_with_known_synonyms)) {
+ synonyms_to_try.push_back(&data_descriptor);
+ }
+ while (!synonyms_to_try.empty()) {
+ auto synonym_index =
+ GetFuzzerContext()->RandomIndex(synonyms_to_try);
+ auto synonym_to_try = synonyms_to_try[synonym_index];
+ synonyms_to_try.erase(synonyms_to_try.begin() + synonym_index);
+ assert(synonym_to_try->index().empty() &&
+ "Right now we only support id == id synonyms; supporting "
+ "e.g. id == index-into-vector will come later");
+
+ if (!TransformationReplaceIdWithSynonym::
+ ReplacingUseWithSynonymIsOk(GetIRContext(), use_inst,
+ use_index, *synonym_to_try)) {
+ continue;
+ }
+
+ // |use_index| is the absolute index of the operand. We require
+ // the index of the operand restricted to input operands only, so
+ // we subtract the number of non-input operands from |use_index|.
+ uint32_t number_of_non_input_operands =
+ use_inst->NumOperands() - use_inst->NumInOperands();
+ TransformationReplaceIdWithSynonym replace_id_transformation(
+ transformation::MakeIdUseDescriptorFromUse(
+ GetIRContext(), use_inst,
+ use_index - number_of_non_input_operands),
+ *synonym_to_try, 0);
+ // The transformation should be applicable by construction.
+ assert(replace_id_transformation.IsApplicable(GetIRContext(),
+ *GetFactManager()));
+ // We cannot actually apply the transformation here, as this would
+ // change the analysis results that are being depended on for usage
+ // iteration. We instead store them up and apply them at the end
+ // of the method.
+ transformations_to_apply.push_back(replace_id_transformation);
+ break;
+ }
+ });
+ }
+
+ for (auto& replace_id_transformation : transformations_to_apply) {
+ // Even though replacing id uses with synonyms may lead to new instructions
+ // (to compute indices into composites), as these instructions will generate
+ // ids, their presence should not affect the id use descriptors that were
+ // computed during the creation of transformations. Thus transformations
+ // should not disable one another.
+ assert(replace_id_transformation.IsApplicable(GetIRContext(),
+ *GetFactManager()));
+ replace_id_transformation.Apply(GetIRContext(), GetFactManager());
+ *GetTransformations()->add_transformation() =
+ replace_id_transformation.ToMessage();
+ }
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.h b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
new file mode 100644
index 00000000..1a0748eb
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 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_APPLY_ID_SYNONYMS_
+#define SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A pass that replaces ids with other ids, or accesses into structures, that
+// are known to hold the same values.
+class FuzzerPassApplyIdSynonyms : public FuzzerPass {
+ public:
+ FuzzerPassApplyIdSynonyms(opt::IRContext* ir_context,
+ FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassApplyIdSynonyms() override;
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_
diff --git a/source/fuzz/id_use_descriptor.cpp b/source/fuzz/id_use_descriptor.cpp
index 33d2ca08..8693a7d1 100644
--- a/source/fuzz/id_use_descriptor.cpp
+++ b/source/fuzz/id_use_descriptor.cpp
@@ -78,5 +78,33 @@ protobufs::IdUseDescriptor transformation::MakeIdUseDescriptor(
return result;
}
+protobufs::IdUseDescriptor transformation::MakeIdUseDescriptorFromUse(
+ opt::IRContext* context, opt::Instruction* inst,
+ uint32_t in_operand_index) {
+ auto in_operand = inst->GetInOperand(in_operand_index);
+ assert(in_operand.type == SPV_OPERAND_TYPE_ID);
+ auto id_of_interest = in_operand.words[0];
+
+ auto block = context->get_instr_block(inst);
+ uint32_t base_instruction_result_id = block->id();
+ uint32_t num_opcodes_to_ignore = 0;
+ for (auto& inst_in_block : *block) {
+ if (inst_in_block.HasResultId()) {
+ base_instruction_result_id = inst_in_block.result_id();
+ num_opcodes_to_ignore = 0;
+ }
+ if (&inst_in_block == inst) {
+ return MakeIdUseDescriptor(id_of_interest, inst->opcode(),
+ in_operand_index, base_instruction_result_id,
+ num_opcodes_to_ignore);
+ }
+ if (inst_in_block.opcode() == inst->opcode()) {
+ num_opcodes_to_ignore++;
+ }
+ }
+ assert(false && "No matching instruction was found.");
+ return protobufs::IdUseDescriptor();
+}
+
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/id_use_descriptor.h b/source/fuzz/id_use_descriptor.h
index 63016c5d..1657cb92 100644
--- a/source/fuzz/id_use_descriptor.h
+++ b/source/fuzz/id_use_descriptor.h
@@ -35,6 +35,12 @@ protobufs::IdUseDescriptor MakeIdUseDescriptor(
uint32_t in_operand_index, uint32_t base_instruction_result_id,
uint32_t num_opcodes_to_ignore);
+// Given an id use, represented by the instruction |inst| that uses the id, and
+// the input operand index |in_operand_index| associated with the usage, returns
+// an IdUseDescriptor that represents the use.
+protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse(
+ opt::IRContext* context, opt::Instruction* inst, uint32_t in_operand_index);
+
} // namespace transformation
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 4e8dcac3..7ee81afd 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -172,6 +172,7 @@ message Transformation {
TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
TransformationAddDeadContinue add_dead_continue = 12;
TransformationCopyObject copy_object = 13;
+ TransformationReplaceIdWithSynonym replace_id_with_synonym = 14;
// Add additional option using the next available number.
}
}
@@ -325,6 +326,28 @@ message TransformationMoveBlockDown {
uint32 block_id = 1;
}
+message TransformationReplaceBooleanConstantWithConstantBinary {
+
+ // A transformation to capture replacing a use of a boolean constant with
+ // binary operation on two constant values
+
+ // A descriptor for the boolean constant id we would like to replace
+ IdUseDescriptor id_use_descriptor = 1;
+
+ // Id for the constant to be used on the LHS of the comparision
+ uint32 lhs_id = 2;
+
+ // Id for the constant to be used on the RHS of the comparision
+ uint32 rhs_id = 3;
+
+ // Opcode for binary operator
+ uint32 opcode = 4;
+
+ // Id that will store the result of the binary operation instruction
+ uint32 fresh_id_for_binary_operation = 5;
+
+}
+
message TransformationReplaceConstantWithUniform {
// Replaces a use of a constant id with the the result of a load from an
@@ -344,26 +367,21 @@ message TransformationReplaceConstantWithUniform {
}
-message TransformationReplaceBooleanConstantWithConstantBinary {
+message TransformationReplaceIdWithSynonym {
- // A transformation to capture replacing a use of a boolean constant with
- // binary operation on two constant values
+ // Replaces an id use with something known to be synonymous with that id use,
+ // e.g. because it was obtained via applying OpCopyObject
- // A descriptor for the boolean constant id we would like to replace
+ // Identifies the id use that is to be replaced
IdUseDescriptor id_use_descriptor = 1;
- // Id for the constant to be used on the LHS of the comparision
- uint32 lhs_id = 2;
-
- // Id for the constant to be used on the RHS of the comparision
- uint32 rhs_id = 3;
-
- // Opcode for binary operator
- uint32 opcode = 4;
-
- // Id that will store the result of the binary operation instruction
- uint32 fresh_id_for_binary_operation = 5;
+ // Identifies the data with which the id use is expected to be synonymous
+ DataDescriptor data_descriptor = 2;
+ // In the case that a temporary is required to express the synonym (e.g. to
+ // obtain an element of a vector, provides a fresh id for the temporary;
+ // should be set to 0 if no temporary is required
+ uint32 fresh_id_for_temporary = 3;
}
message TransformationSplitBlock {
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index bf2cdcd0..65966f35 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -29,6 +29,7 @@
#include "transformation_move_block_down.h"
#include "transformation_replace_boolean_constant_with_constant_binary.h"
#include "transformation_replace_constant_with_uniform.h"
+#include "transformation_replace_id_with_synonym.h"
#include "transformation_split_block.h"
namespace spvtools {
@@ -72,6 +73,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kReplaceConstantWithUniform:
return MakeUnique<TransformationReplaceConstantWithUniform>(
message.replace_constant_with_uniform());
+ case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
+ return MakeUnique<TransformationReplaceIdWithSynonym>(
+ message.replace_id_with_synonym());
case protobufs::Transformation::TransformationCase::kSplitBlock:
return MakeUnique<TransformationSplitBlock>(message.split_block());
case protobufs::Transformation::TRANSFORMATION_NOT_SET:
diff --git a/source/fuzz/transformation_replace_id_with_synonym.cpp b/source/fuzz/transformation_replace_id_with_synonym.cpp
new file mode 100644
index 00000000..a8874808
--- /dev/null
+++ b/source/fuzz/transformation_replace_id_with_synonym.cpp
@@ -0,0 +1,155 @@
+// Copyright (c) 2019 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_replace_id_with_synonym.h"
+
+#include <algorithm>
+
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
+ const spvtools::fuzz::protobufs::TransformationReplaceIdWithSynonym&
+ message)
+ : message_(message) {}
+
+TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
+ protobufs::IdUseDescriptor id_use_descriptor,
+ protobufs::DataDescriptor data_descriptor,
+ uint32_t fresh_id_for_temporary) {
+ assert(fresh_id_for_temporary == 0 && data_descriptor.index().size() == 0 &&
+ "At present we do not support making an id that is synonymous with an "
+ "index into a composite.");
+ *message_.mutable_id_use_descriptor() = std::move(id_use_descriptor);
+ *message_.mutable_data_descriptor() = std::move(data_descriptor);
+ message_.set_fresh_id_for_temporary(fresh_id_for_temporary);
+}
+
+bool TransformationReplaceIdWithSynonym::IsApplicable(
+ spvtools::opt::IRContext* context,
+ const spvtools::fuzz::FactManager& fact_manager) const {
+ auto id_of_interest = message_.id_use_descriptor().id_of_interest();
+
+ // Does the fact manager know about the synonym?
+ if (fact_manager.GetIdsForWhichSynonymsAreKnown().count(id_of_interest) ==
+ 0) {
+ return false;
+ }
+
+ auto available_synonyms = fact_manager.GetSynonymsForId(id_of_interest);
+ if (std::find_if(available_synonyms.begin(), available_synonyms.end(),
+ [this](protobufs::DataDescriptor dd) -> bool {
+ return DataDescriptorEquals()(&dd,
+ &message_.data_descriptor());
+ }) == available_synonyms.end()) {
+ return false;
+ }
+
+ auto use_instruction =
+ transformation::FindInstruction(message_.id_use_descriptor(), context);
+ if (!use_instruction) {
+ return false;
+ }
+
+ if (!ReplacingUseWithSynonymIsOk(
+ context, use_instruction,
+ message_.id_use_descriptor().in_operand_index(),
+ message_.data_descriptor())) {
+ return false;
+ }
+
+ assert(message_.fresh_id_for_temporary() == 0);
+ assert(message_.data_descriptor().index().empty());
+
+ return true;
+}
+
+void TransformationReplaceIdWithSynonym::Apply(
+ spvtools::opt::IRContext* context,
+ spvtools::fuzz::FactManager* /*unused*/) const {
+ assert(message_.data_descriptor().index().empty());
+ auto instruction_to_change =
+ transformation::FindInstruction(message_.id_use_descriptor(), context);
+ instruction_to_change->SetInOperand(
+ message_.id_use_descriptor().in_operand_index(),
+ {message_.data_descriptor().object()});
+ context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage()
+ const {
+ protobufs::Transformation result;
+ *result.mutable_replace_id_with_synonym() = message_;
+ return result;
+}
+
+bool TransformationReplaceIdWithSynonym::ReplacingUseWithSynonymIsOk(
+ opt::IRContext* context, opt::Instruction* use_instruction,
+ uint32_t use_in_operand_index, const protobufs::DataDescriptor& synonym) {
+ auto defining_instruction =
+ context->get_def_use_mgr()->GetDef(synonym.object());
+
+ if (use_instruction == defining_instruction) {
+ // If we have an instruction:
+ // %a = OpCopyObject %t %b
+ // then we know %a and %b are synonymous, but we do *not* want to turn
+ // this into:
+ // %a = OpCopyObject %t %a
+ // We require this special case because an instruction dominates itself.
+ return false;
+ }
+
+ if (use_instruction->opcode() == SpvOpAccessChain &&
+ use_in_operand_index > 0) {
+ // This is an access chain index. If the object being accessed has
+ // pointer-to-struct type then we cannot replace the use with a synonym, as
+ // the use needs to be an OpConstant.
+ auto object_being_accessed = context->get_def_use_mgr()->GetDef(
+ use_instruction->GetSingleWordInOperand(0));
+ auto pointer_type =
+ context->get_type_mgr()->GetType(object_being_accessed->type_id());
+ assert(pointer_type->AsPointer());
+ if (pointer_type->AsPointer()->pointee_type()->AsStruct()) {
+ return false;
+ }
+ }
+
+ // We now need to check that replacing the use with the synonym will respect
+ // dominance rules - i.e. the synonym needs to dominate the use.
+ auto dominator_analysis = context->GetDominatorAnalysis(
+ context->get_instr_block(use_instruction)->GetParent());
+ if (use_instruction->opcode() == SpvOpPhi) {
+ // In the case where the use is an operand to OpPhi, it is actually the
+ // *parent* block associated with the operand that must be dominated by the
+ // synonym.
+ auto parent_block =
+ use_instruction->GetSingleWordInOperand(use_in_operand_index + 1);
+ if (!dominator_analysis->Dominates(
+ context->get_instr_block(defining_instruction)->id(),
+ parent_block)) {
+ return false;
+ }
+ } else if (!dominator_analysis->Dominates(defining_instruction,
+ use_instruction)) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_replace_id_with_synonym.h b/source/fuzz/transformation_replace_id_with_synonym.h
new file mode 100644
index 00000000..d0f233e3
--- /dev/null
+++ b/source/fuzz/transformation_replace_id_with_synonym.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2019 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_REPLACE_ID_WITH_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_ID_WITH_SYNONYM_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceIdWithSynonym : public Transformation {
+ public:
+ explicit TransformationReplaceIdWithSynonym(
+ const protobufs::TransformationReplaceIdWithSynonym& message);
+
+ TransformationReplaceIdWithSynonym(
+ protobufs::IdUseDescriptor id_use_descriptor,
+ protobufs::DataDescriptor data_descriptor,
+ uint32_t fresh_id_for_temporary);
+
+ // - The fact manager must know that the id identified by
+ // |message_.id_use_descriptor| is synonomous with
+ // |message_.data_descriptor|.
+ // - Replacing the id in |message_.id_use_descriptor| by the synonym in
+ // |message_.data_descriptor| must respect SPIR-V's rules about uses being
+ // dominated by their definitions.
+ // - The id must not be an index into an access chain whose base object has
+ // struct type, as such indices must be constants.
+ // - |fresh_id_for_temporary| must be 0.
+ // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2855): the
+ // motivation for the temporary is to support the case where an id is
+ // synonymous with an element of a composite. Until support for that is
+ // implemented, 0 records that no temporary is needed.
+ bool IsApplicable(opt::IRContext* context,
+ const FactManager& fact_manager) const override;
+
+ // Replaces the use identified by |message_.id_use_descriptor| with the
+ // synonymous id identified by |message_.data_descriptor|.
+ // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2855): in due
+ // course it will also be necessary to add an additional instruction to pull
+ // the synonym out of a composite.
+ void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ static bool ReplacingUseWithSynonymIsOk(
+ opt::IRContext* context, opt::Instruction* use_instruction,
+ uint32_t use_in_operand_index, const protobufs::DataDescriptor& synonym);
+
+ private:
+ protobufs::TransformationReplaceIdWithSynonym message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_ID_WITH_SYNONYM_H_