diff options
Diffstat (limited to 'source/fuzz')
-rw-r--r-- | source/fuzz/CMakeLists.txt | 4 | ||||
-rw-r--r-- | source/fuzz/fuzzer.cpp | 4 | ||||
-rw-r--r-- | source/fuzz/fuzzer_context.cpp | 3 | ||||
-rw-r--r-- | source/fuzz/fuzzer_context.h | 4 | ||||
-rw-r--r-- | source/fuzz/fuzzer_pass_add_dead_breaks.cpp | 6 | ||||
-rw-r--r-- | source/fuzz/fuzzer_pass_add_dead_continues.cpp | 6 | ||||
-rw-r--r-- | source/fuzz/fuzzer_pass_apply_id_synonyms.cpp | 108 | ||||
-rw-r--r-- | source/fuzz/fuzzer_pass_apply_id_synonyms.h | 42 | ||||
-rw-r--r-- | source/fuzz/id_use_descriptor.cpp | 28 | ||||
-rw-r--r-- | source/fuzz/id_use_descriptor.h | 6 | ||||
-rw-r--r-- | source/fuzz/protobufs/spvtoolsfuzz.proto | 48 | ||||
-rw-r--r-- | source/fuzz/transformation.cpp | 4 | ||||
-rw-r--r-- | source/fuzz/transformation_replace_id_with_synonym.cpp | 155 | ||||
-rw-r--r-- | source/fuzz/transformation_replace_id_with_synonym.h | 72 |
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_ |