From a711c594b8cc36a808ac5358b868db568be1aaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Karpi=C5=84ski?= Date: Wed, 19 Aug 2020 12:56:03 +0000 Subject: [PATCH] spirv-fuzz: add FuzzerPassAddCompositeInserts (#3606) Adds FuzzerPassAddCompositeInserts, which randomly adds new OpCompositeInsert instructions. Each OpCompositeInsert instruction yields a copy of an original composite with one subcomponent replaced with an existing or newly added object. Synonym facts are added for the unchanged components in the original and added composite, and for the replaced subcomponent and the object, if possible. Fixes #2859 --- source/fuzz/CMakeLists.txt | 4 + source/fuzz/fuzzer.cpp | 24 + source/fuzz/fuzzer_context.cpp | 9 +- source/fuzz/fuzzer_context.h | 11 + source/fuzz/fuzzer_pass.cpp | 20 + source/fuzz/fuzzer_pass.h | 3 + .../fuzzer_pass_add_composite_inserts.cpp | 236 +++++ .../fuzz/fuzzer_pass_add_composite_inserts.h | 45 + ..._adds_subs_muls_with_carrying_extended.cpp | 35 +- ...replace_copy_objects_with_stores_loads.cpp | 9 +- source/fuzz/fuzzer_util.cpp | 1 - source/fuzz/fuzzer_util.h | 1 - source/fuzz/protobufs/spvtoolsfuzz.proto | 24 + source/fuzz/transformation.cpp | 4 + .../fuzz/transformation_composite_insert.cpp | 226 +++++ source/fuzz/transformation_composite_insert.h | 72 ++ test/fuzz/CMakeLists.txt | 1 + .../transformation_composite_insert_test.cpp | 814 ++++++++++++++++++ 18 files changed, 1519 insertions(+), 20 deletions(-) create mode 100644 source/fuzz/fuzzer_pass_add_composite_inserts.cpp create mode 100644 source/fuzz/fuzzer_pass_add_composite_inserts.h create mode 100644 source/fuzz/transformation_composite_insert.cpp create mode 100644 source/fuzz/transformation_composite_insert.h create mode 100644 test/fuzz/transformation_composite_insert_test.cpp diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 0a4df4ef8..db32902fa 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -38,6 +38,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_context.h fuzzer_pass.h fuzzer_pass_add_access_chains.h + fuzzer_pass_add_composite_inserts.h fuzzer_pass_add_composite_types.h fuzzer_pass_add_copy_memory.h fuzzer_pass_add_dead_blocks.h @@ -131,6 +132,7 @@ if(SPIRV_BUILD_FUZZER) transformation_adjust_branch_weights.h transformation_composite_construct.h transformation_composite_extract.h + transformation_composite_insert.h transformation_compute_data_synonym_fact_closure.h transformation_context.h transformation_equation_instruction.h @@ -178,6 +180,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_context.cpp fuzzer_pass.cpp fuzzer_pass_add_access_chains.cpp + fuzzer_pass_add_composite_inserts.cpp fuzzer_pass_add_composite_types.cpp fuzzer_pass_add_copy_memory.cpp fuzzer_pass_add_dead_blocks.cpp @@ -270,6 +273,7 @@ if(SPIRV_BUILD_FUZZER) transformation_adjust_branch_weights.cpp transformation_composite_construct.cpp transformation_composite_extract.cpp + transformation_composite_insert.cpp transformation_compute_data_synonym_fact_closure.cpp transformation_context.cpp transformation_equation_instruction.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 77ef8e2a9..651db36f7 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -21,6 +21,7 @@ #include "source/fuzz/fact_manager.h" #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass_add_access_chains.h" +#include "source/fuzz/fuzzer_pass_add_composite_inserts.h" #include "source/fuzz/fuzzer_pass_add_composite_types.h" #include "source/fuzz/fuzzer_pass_add_copy_memory.h" #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" @@ -35,6 +36,7 @@ #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_parameters.h" +#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" #include "source/fuzz/fuzzer_pass_add_stores.h" #include "source/fuzz/fuzzer_pass_add_synonyms.h" #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h" @@ -60,7 +62,11 @@ #include "source/fuzz/fuzzer_pass_permute_phi_operands.h" #include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" +#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" +#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" +#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" +#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" #include "source/fuzz/fuzzer_pass_split_blocks.h" @@ -210,6 +216,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); @@ -249,6 +258,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); @@ -300,6 +312,18 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &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 ced25a5fb..a187daa45 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -27,6 +27,7 @@ const std::pair kChanceOfAddingAccessChain = {5, 50}; const std::pair kChanceOfAddingAnotherStructField = {20, 90}; const std::pair kChanceOfAddingArrayOrStructType = {20, 90}; +const std::pair kChanceOfAddingCompositeInsert = {20, 50}; const std::pair kChanceOfAddingCopyMemory = {20, 50}; const std::pair kChanceOfAddingDeadBlock = {20, 90}; const std::pair kChanceOfAddingDeadBreak = {5, 80}; @@ -64,6 +65,8 @@ const std::pair kChanceOfChoosingWorkgroupStorageClass = { const std::pair kChanceOfConstructingComposite = {20, 50}; const std::pair kChanceOfCopyingObject = {20, 50}; const std::pair kChanceOfDonatingAdditionalModule = {5, 50}; +const std::pair kChanceOfGoingDeeperToInsertInComposite = { + 30, 70}; const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = {50, 95}; const std::pair kChanceOfInterchangingZeroLikeConstants = { @@ -86,7 +89,7 @@ const std::pair kChanceOfPropagatingInstructionsUp = {20, 70}; const std::pair kChanceOfPushingIdThroughVariable = {5, 50}; const std::pair - kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 90}; + kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 70}; const std::pair kChanceOfReplacingCopyMemoryWithLoadStore = {20, 90}; const std::pair kChanceOfReplacingCopyObjectWithStoreLoad = @@ -153,6 +156,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField); chance_of_adding_array_or_struct_type_ = ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType); + chance_of_adding_composite_insert_ = + ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert); chance_of_adding_copy_memory_ = ChooseBetweenMinAndMax(kChanceOfAddingCopyMemory); chance_of_adding_dead_block_ = @@ -207,6 +212,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject); chance_of_donating_additional_module_ = ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); + chance_of_going_deeper_to_insert_in_composite_ = + ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite); chance_of_going_deeper_when_making_access_chain_ = ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); chance_of_interchanging_signedness_of_integer_operands_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 947a834aa..c50161406 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -115,6 +115,9 @@ class FuzzerContext { uint32_t GetChanceOfAddingArrayOrStructType() { return chance_of_adding_array_or_struct_type_; } + uint32_t GetChanceOfAddingCompositeInsert() { + return chance_of_adding_composite_insert_; + } uint32_t GetChanceOfAddingCopyMemory() { return chance_of_adding_copy_memory_; } @@ -186,6 +189,9 @@ class FuzzerContext { uint32_t GetChanceOfDonatingAdditionalModule() { return chance_of_donating_additional_module_; } + uint32_t GetChanceOfGoingDeeperToInsertInComposite() { + return chance_of_going_deeper_to_insert_in_composite_; + } uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { return chance_of_going_deeper_when_making_access_chain_; } @@ -296,6 +302,9 @@ class FuzzerContext { uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) { return random_generator_->RandomUint32(composite_size_bound); } + uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) { + return random_generator_->RandomUint32(number_of_components); + } uint32_t GetRandomLoopControlPartialCount() { return random_generator_->RandomUint32(max_loop_control_partial_count_); } @@ -342,6 +351,7 @@ class FuzzerContext { uint32_t chance_of_adding_access_chain_; uint32_t chance_of_adding_another_struct_field_; uint32_t chance_of_adding_array_or_struct_type_; + uint32_t chance_of_adding_composite_insert_; uint32_t chance_of_adding_copy_memory_; uint32_t chance_of_adding_dead_block_; uint32_t chance_of_adding_dead_break_; @@ -371,6 +381,7 @@ class FuzzerContext { uint32_t chance_of_constructing_composite_; uint32_t chance_of_copying_object_; uint32_t chance_of_donating_additional_module_; + uint32_t chance_of_going_deeper_to_insert_in_composite_; uint32_t chance_of_going_deeper_when_making_access_chain_; uint32_t chance_of_interchanging_signedness_of_integer_operands_; uint32_t chance_of_interchanging_zero_like_constants_; diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp index 3c8c65b1a..3097bc707 100644 --- a/source/fuzz/fuzzer_pass.cpp +++ b/source/fuzz/fuzzer_pass.cpp @@ -518,6 +518,26 @@ uint32_t FuzzerPass::FindOrCreateZeroConstant( } } +bool FuzzerPass::CanFindOrCreateZeroConstant(const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kBool: + case opt::analysis::Type::kInteger: + case opt::analysis::Type::kFloat: + case opt::analysis::Type::kArray: + case opt::analysis::Type::kMatrix: + case opt::analysis::Type::kVector: + return true; + case opt::analysis::Type::kStruct: + return std::all_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [this](const opt::analysis::Type* element_type) { + return CanFindOrCreateZeroConstant(*element_type); + }); + default: + return false; + } +} + void FuzzerPass::MaybeAddUseToReplace( opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id, std::vector>* diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h index 423b44373..1e6992a6d 100644 --- a/source/fuzz/fuzzer_pass.h +++ b/source/fuzz/fuzzer_pass.h @@ -273,6 +273,9 @@ class FuzzerPass { uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id, bool is_irrelevant); + // Checks if FindOrCreateZeroConstant can be called on this type. + bool CanFindOrCreateZeroConstant(const opt::analysis::Type& type); + // Adds a pair (id_use_descriptor, |replacement_id|) to the vector // |uses_to_replace|, where id_use_descriptor is the id use descriptor // representing the usage of an id in the |use_inst| instruction, at operand diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp new file mode 100644 index 000000000..092a1d5bd --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp @@ -0,0 +1,236 @@ +// 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_composite_inserts.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "source/fuzz/transformation_composite_insert.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddCompositeInserts::FuzzerPassAddCompositeInserts( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddCompositeInserts::~FuzzerPassAddCompositeInserts() = default; + +void FuzzerPassAddCompositeInserts::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator instruction_iterator, + const protobufs::InstructionDescriptor& instruction_descriptor) + -> void { + assert(instruction_iterator->opcode() == + instruction_descriptor.target_instruction_opcode() && + "The opcode of the instruction we might insert before must be " + "the same as the opcode in the descriptor for the instruction"); + + // Randomly decide whether to try adding an OpCompositeInsert + // instruction. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingCompositeInsert())) { + return; + } + + // It must be possible to insert an OpCompositeInsert instruction + // before |instruction_iterator|. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction( + SpvOpCompositeInsert, instruction_iterator)) { + return; + } + + // Look for available values that have composite type. + std::vector available_composites = + FindAvailableInstructions( + function, block, instruction_iterator, + [instruction_descriptor]( + opt::IRContext* ir_context, + opt::Instruction* instruction) -> bool { + // |instruction| must be a supported instruction of composite + // type. + if (!TransformationCompositeInsert:: + IsCompositeInstructionSupported(ir_context, + instruction)) { + return false; + } + + auto instruction_type = ir_context->get_type_mgr()->GetType( + instruction->type_id()); + + // No components of the composite can have type + // OpTypeRuntimeArray. + if (ContainsRuntimeArray(*instruction_type)) { + return false; + } + + // No components of the composite can be pointers. + // TODO: + // (https://github.com/KhronosGroup/SPIRV-Tools/issues/3658) + // Structs can have components of pointer type. + // FindOrCreateZeroConstant cannot be called on a + // pointer. We ignore pointers for now. Consider adding + // support for pointer types. + if (ContainsPointer(*instruction_type)) { + return false; + } + + return true; + }); + + // If there are no available values, then return. + if (available_composites.empty()) { + return; + } + + // Choose randomly one available composite value. + auto available_composite = + available_composites[GetFuzzerContext()->RandomIndex( + available_composites)]; + + // Take a random component of the chosen composite value. If the chosen + // component is itself a composite, then randomly decide whether to take + // its component and repeat. + uint32_t current_node_type_id = available_composite->type_id(); + std::vector path_to_replaced; + while (true) { + auto current_node_type_inst = + GetIRContext()->get_def_use_mgr()->GetDef(current_node_type_id); + uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex( + *current_node_type_inst, GetIRContext()); + + // If the composite is empty, then end the iteration. + if (num_of_components == 0) { + break; + } + uint32_t one_selected_index = + GetFuzzerContext()->GetRandomIndexForCompositeInsert( + num_of_components); + + // Construct a final index by appending the current index. + path_to_replaced.push_back(one_selected_index); + current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + GetIRContext(), current_node_type_id, one_selected_index); + + // If the component is not a composite then end the iteration. + if (!fuzzerutil::IsCompositeType( + GetIRContext()->get_type_mgr()->GetType( + current_node_type_id))) { + break; + } + + // If the component is a composite, but we decide not to go deeper, + // then end the iteration. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfGoingDeeperToInsertInComposite())) { + break; + } + } + + // Look for available objects that have the type id + // |current_node_type_id| and can be inserted. + std::vector available_objects = + FindAvailableInstructions( + function, block, instruction_iterator, + [instruction_descriptor, current_node_type_id]( + opt::IRContext* /*unused*/, + opt::Instruction* instruction) -> bool { + if (instruction->result_id() == 0 || + instruction->type_id() == 0) { + return false; + } + if (instruction->type_id() != current_node_type_id) { + return false; + } + return true; + }); + + // If there are no objects of the specific type available, check if + // FindOrCreateZeroConstant can be called and create a zero constant of + // this type. + uint32_t available_object_id; + if (available_objects.empty()) { + auto current_node_type = + GetIRContext()->get_type_mgr()->GetType(current_node_type_id); + if (!CanFindOrCreateZeroConstant(*current_node_type)) { + return; + } + available_object_id = + FindOrCreateZeroConstant(current_node_type_id, false); + } else { + available_object_id = + available_objects[GetFuzzerContext()->RandomIndex( + available_objects)] + ->result_id(); + } + auto new_result_id = GetFuzzerContext()->GetFreshId(); + + // Insert an OpCompositeInsert instruction which copies + // |available_composite| and in the copy inserts the object + // of type |available_object_id| at index |index_to_replace|. + ApplyTransformation(TransformationCompositeInsert( + instruction_descriptor, new_result_id, + available_composite->result_id(), available_object_id, + path_to_replaced)); + }); +} + +bool FuzzerPassAddCompositeInserts::ContainsPointer( + const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kPointer: + return true; + case opt::analysis::Type::kArray: + return ContainsPointer(*type.AsArray()->element_type()); + case opt::analysis::Type::kMatrix: + return ContainsPointer(*type.AsMatrix()->element_type()); + case opt::analysis::Type::kVector: + return ContainsPointer(*type.AsVector()->element_type()); + case opt::analysis::Type::kStruct: + return std::any_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [](const opt::analysis::Type* element_type) { + return ContainsPointer(*element_type); + }); + default: + return false; + } +} + +bool FuzzerPassAddCompositeInserts::ContainsRuntimeArray( + const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kRuntimeArray: + return true; + case opt::analysis::Type::kStruct: + // If any component of a struct is of type OpTypeRuntimeArray, return + // true. + return std::any_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [](const opt::analysis::Type* element_type) { + return ContainsRuntimeArray(*element_type); + }); + default: + return false; + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.h b/source/fuzz/fuzzer_pass_add_composite_inserts.h new file mode 100644 index 000000000..665efc958 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_composite_inserts.h @@ -0,0 +1,45 @@ +// 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 SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H +#define SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that randomly adds new OpCompositeInsert instructions to +// available values that have the composite type. +class FuzzerPassAddCompositeInserts : public FuzzerPass { + public: + FuzzerPassAddCompositeInserts( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddCompositeInserts(); + void Apply() override; + + // Checks if any component of a composite is a pointer. + static bool ContainsPointer(const opt::analysis::Type& type); + + // Checks if any component of a composite has type OpTypeRuntimeArray. + static bool ContainsRuntimeArray(const opt::analysis::Type& type); +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp index d506de6b4..139dc6e2f 100644 --- a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp +++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp @@ -37,6 +37,7 @@ FuzzerPassReplaceAddsSubsMulsWithCarryingExtended:: ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default; void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() { + std::vector instructions_for_transformation; for (auto& function : *GetIRContext()->module()) { for (auto& block : function) { for (auto& instruction : block) { @@ -52,25 +53,27 @@ void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() { IsInstructionSuitable(GetIRContext(), instruction)) { continue; } - - // Get the operand type id. We know that both operands have the same - // type. - uint32_t operand_type_id = - GetIRContext() - ->get_def_use_mgr() - ->GetDef(instruction.GetSingleWordInOperand( - kArithmeticInstructionIndexLeftInOperand)) - ->type_id(); - - // Ensure the required struct type exists. The struct type is based on - // the operand type. - FindOrCreateStructType({operand_type_id, operand_type_id}); - - ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended( - GetFuzzerContext()->GetFreshId(), instruction.result_id())); + instructions_for_transformation.push_back(instruction); } } } + for (auto& instruction : instructions_for_transformation) { + // Get the operand type id. We know that both operands have the same + // type. + uint32_t operand_type_id = + GetIRContext() + ->get_def_use_mgr() + ->GetDef(instruction.GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + // Ensure the required struct type exists. The struct type is based on + // the operand type. + FindOrCreateStructType({operand_type_id, operand_type_id}); + + ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended( + GetFuzzerContext()->GetFreshId(), instruction.result_id())); + } } } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp index e21ea5ed7..4c455a749 100644 --- a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp +++ b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp @@ -66,7 +66,14 @@ void FuzzerPassReplaceCopyObjectsWithStoresLoads::Apply() { ? SpvStorageClassPrivate : SpvStorageClassFunction; - // Find or create a constant to initialize the variable from. + // Find or create a constant to initialize the variable from. The type of + // |instruction| must be such that the function FindOrCreateConstant can be + // called. + auto instruction_type = + GetIRContext()->get_type_mgr()->GetType(instruction->type_id()); + if (!CanFindOrCreateZeroConstant(*instruction_type)) { + return; + } auto variable_initializer_id = FindOrCreateZeroConstant(instruction->type_id(), false); diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index 9e5b30ef6..3e523c3a9 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -1353,6 +1353,5 @@ opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context, } } // namespace fuzzerutil - } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h index d86e13866..af709f889 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -498,7 +498,6 @@ opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context, SpvOp opcode); } // namespace fuzzerutil - } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 2aa113f08..225aa4fe7 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -412,6 +412,7 @@ message Transformation { TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65; TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66; TransformationPropagateInstructionUp propagate_instruction_up = 67; + TransformationCompositeInsert composite_insert = 68; // Add additional option using the next available number. } } @@ -1021,6 +1022,29 @@ message TransformationCompositeExtract { } +message TransformationCompositeInsert { + + // A transformation that adds an instruction OpCompositeInsert which creates + // a new composite from an existing composite, with an element inserted. + + // A descriptor for an instruction before which the new instruction + // OpCompositeInsert should be inserted. + InstructionDescriptor instruction_to_insert_before = 1; + + // Result id of the inserted OpCompositeInsert instruction. + uint32 fresh_id = 2; + + // Id of the composite used as the basis for the insertion. + uint32 composite_id = 3; + + // Id of the object to be inserted. + uint32 object_id = 4; + + // Indices that indicate which part of the composite should be inserted into. + repeated uint32 index = 5; + +} + message TransformationComputeDataSynonymFactClosure { // A transformation that impacts the fact manager only, forcing a computation diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index c3f3befc9..ec765b3f6 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -49,6 +49,7 @@ #include "source/fuzz/transformation_adjust_branch_weights.h" #include "source/fuzz/transformation_composite_construct.h" #include "source/fuzz/transformation_composite_extract.h" +#include "source/fuzz/transformation_composite_insert.h" #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h" #include "source/fuzz/transformation_equation_instruction.h" #include "source/fuzz/transformation_function_call.h" @@ -179,6 +180,9 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kCompositeExtract: return MakeUnique( message.composite_extract()); + case protobufs::Transformation::TransformationCase::kCompositeInsert: + return MakeUnique( + message.composite_insert()); case protobufs::Transformation::TransformationCase:: kComputeDataSynonymFactClosure: return MakeUnique( diff --git a/source/fuzz/transformation_composite_insert.cpp b/source/fuzz/transformation_composite_insert.cpp new file mode 100644 index 000000000..75eebd434 --- /dev/null +++ b/source/fuzz/transformation_composite_insert.cpp @@ -0,0 +1,226 @@ +// 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 "transformation_composite_insert.h" + +#include "source/fuzz/fuzzer_pass_add_composite_inserts.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationCompositeInsert::TransformationCompositeInsert( + const spvtools::fuzz::protobufs::TransformationCompositeInsert& message) + : message_(message) {} + +TransformationCompositeInsert::TransformationCompositeInsert( + const protobufs::InstructionDescriptor& instruction_to_insert_before, + uint32_t fresh_id, uint32_t composite_id, uint32_t object_id, + const std::vector& index) { + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; + message_.set_fresh_id(fresh_id); + message_.set_composite_id(composite_id); + message_.set_object_id(object_id); + for (auto an_index : index) { + message_.add_index(an_index); + } +} + +bool TransformationCompositeInsert::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |message_.fresh_id| must be fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { + return false; + } + + // |message_.composite_id| must refer to an existing composite value. + auto composite = + ir_context->get_def_use_mgr()->GetDef(message_.composite_id()); + + if (!IsCompositeInstructionSupported(ir_context, composite)) { + return false; + } + + // The indices in |message_.index| must be suitable for indexing into + // |composite->type_id()|. + auto component_to_be_replaced_type_id = fuzzerutil::WalkCompositeTypeIndices( + ir_context, composite->type_id(), message_.index()); + if (component_to_be_replaced_type_id == 0) { + return false; + } + + // The instruction having the id of |message_.object_id| must be defined. + auto object_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.object_id()); + if (object_instruction == nullptr || object_instruction->type_id() == 0) { + return false; + } + + // We ignore pointers for now. + auto object_instruction_type = + ir_context->get_type_mgr()->GetType(object_instruction->type_id()); + if (object_instruction_type->AsPointer() != nullptr) { + return false; + } + + // The type id of the object having |message_.object_id| and the type id of + // the component of the composite at index |message_.index| must be the same. + if (component_to_be_replaced_type_id != object_instruction->type_id()) { + return false; + } + + // |message_.instruction_to_insert_before| must be a defined instruction. + auto instruction_to_insert_before = + FindInstruction(message_.instruction_to_insert_before(), ir_context); + if (instruction_to_insert_before == nullptr) { + return false; + } + + // |message_.composite_id| and |message_.object_id| must be available before + // the |message_.instruction_to_insert_before|. + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, instruction_to_insert_before, message_.composite_id())) { + return false; + } + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, instruction_to_insert_before, message_.object_id())) { + return false; + } + + // It must be possible to insert an OpCompositeInsert before this + // instruction. + return fuzzerutil::CanInsertOpcodeBeforeInstruction( + SpvOpCompositeInsert, instruction_to_insert_before); +} + +void TransformationCompositeInsert::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + // |message_.struct_fresh_id| must be fresh. + assert(fuzzerutil::IsFreshId(ir_context, message_.fresh_id()) && + "|message_.fresh_id| must be fresh"); + + std::vector index = + fuzzerutil::RepeatedFieldToVector(message_.index()); + opt::Instruction::OperandList in_operands; + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.object_id()}}); + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.composite_id()}}); + for (auto i : index) { + in_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}); + } + auto composite_type_id = + fuzzerutil::GetTypeId(ir_context, message_.composite_id()); + + FindInstruction(message_.instruction_to_insert_before(), ir_context) + ->InsertBefore(MakeUnique( + ir_context, SpvOpCompositeInsert, composite_type_id, + message_.fresh_id(), std::move(in_operands))); + + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + + // We have modified the module so most analyzes are now invalid. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // Add facts about synonyms. Every element which hasn't been changed in + // the copy is synonymous to the corresponding element in the original + // composite which has id |message_.composite_id|. For every index that is a + // prefix of |index|, the components different from the one that + // contains the inserted object are synonymous with corresponding + // elements in the original composite. + + // If |composite_id| is irrelevant then don't add any synonyms. + if (transformation_context->GetFactManager()->IdIsIrrelevant( + message_.composite_id())) { + return; + } + uint32_t current_node_type_id = composite_type_id; + std::vector current_index; + + for (uint32_t current_level = 0; current_level < index.size(); + current_level++) { + auto current_node_type_inst = + ir_context->get_def_use_mgr()->GetDef(current_node_type_id); + uint32_t index_to_skip = index[current_level]; + uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex( + *current_node_type_inst, ir_context); + + // Update the current_node_type_id. + current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + ir_context, current_node_type_id, index_to_skip); + + for (uint32_t i = 0; i < num_of_components; i++) { + if (i == index_to_skip) { + continue; + } + current_index.push_back(i); + // TODO: (https://github.com/KhronosGroup/SPIRV-Tools/issues/3659) + // Google C++ guide restricts the use of r-value references. + // https://google.github.io/styleguide/cppguide.html#Rvalue_references + // Consider changing the signature of MakeDataDescriptor() + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.fresh_id(), + std::vector(current_index)), + MakeDataDescriptor(message_.composite_id(), + std::vector(current_index)), + ir_context); + current_index.pop_back(); + } + // Store the prefix of the |index|. + current_index.push_back(index[current_level]); + } + // The element which has been changed is synonymous to the found object + // itself. Add this fact only if |object_id| is not irrelevant. + if (!transformation_context->GetFactManager()->IdIsIrrelevant( + message_.object_id())) { + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.object_id(), {}), + MakeDataDescriptor(message_.fresh_id(), std::vector(index)), + ir_context); + } +} + +protobufs::Transformation TransformationCompositeInsert::ToMessage() const { + protobufs::Transformation result; + *result.mutable_composite_insert() = message_; + return result; +} + +bool TransformationCompositeInsert::IsCompositeInstructionSupported( + opt::IRContext* ir_context, opt::Instruction* instruction) { + if (instruction == nullptr) { + return false; + } + if (instruction->result_id() == 0 || instruction->type_id() == 0) { + return false; + } + auto composite_type = + ir_context->get_type_mgr()->GetType(instruction->type_id()); + if (!fuzzerutil::IsCompositeType(composite_type)) { + return false; + } + + // Empty composites are not supported. + auto instruction_type_inst = + ir_context->get_def_use_mgr()->GetDef(instruction->type_id()); + if (fuzzerutil::GetBoundForCompositeIndex(*instruction_type_inst, + ir_context) == 0) { + return false; + } + return true; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_composite_insert.h b/source/fuzz/transformation_composite_insert.h new file mode 100644 index 000000000..c4ea9e4c9 --- /dev/null +++ b/source/fuzz/transformation_composite_insert.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 SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H +#define SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationCompositeInsert : public Transformation { + public: + explicit TransformationCompositeInsert( + const protobufs::TransformationCompositeInsert& message); + + TransformationCompositeInsert( + const protobufs::InstructionDescriptor& instruction_to_insert_before, + uint32_t fresh_id, uint32_t composite_id, uint32_t object_id, + const std::vector& index); + + // - |message_.fresh_id| must be fresh. + // - |message_.composite_id| must refer to an existing composite value. + // - |message_.index| must refer to a correct index in the composite. + // - The type id of the object and the type id of the component of the + // composite at index |message_.index| must be the same. + // - |message_.instruction_to_insert_before| must refer to a defined + // instruction. + // - It must be possible to insert OpCompositeInsert before + // |instruction_to_insert_before|. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Adds an instruction OpCompositeInsert before + // |instruction_to_insert_before|, which creates a new composite from + // |composite_id| by inserting |object_id| at the specified |index|. + // Synonyms are created between those components which are identical in the + // original and the modified composite and between the inserted object and its + // copy in the modified composite. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Checks if |instruction| is a instruction of a composite type supported by + // this transformation. + static bool IsCompositeInstructionSupported(opt::IRContext* ir_context, + opt::Instruction* instruction); + + private: + protobufs::TransformationCompositeInsert message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 7a28a575b..3b64567cd 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -57,6 +57,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_adjust_branch_weights_test.cpp transformation_composite_construct_test.cpp transformation_composite_extract_test.cpp + transformation_composite_insert_test.cpp transformation_compute_data_synonym_fact_closure_test.cpp transformation_equation_instruction_test.cpp transformation_function_call_test.cpp diff --git a/test/fuzz/transformation_composite_insert_test.cpp b/test/fuzz/transformation_composite_insert_test.cpp new file mode 100644 index 000000000..de0cbe355 --- /dev/null +++ b/test/fuzz/transformation_composite_insert_test.cpp @@ -0,0 +1,814 @@ +// 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_composite_insert.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationCompositeInsertTest, NotApplicableScenarios) { + // This test handles cases where IsApplicable() returns false. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpName %20 "l1" + OpName %24 "level_2" + OpMemberName %24 0 "c1" + OpMemberName %24 1 "c2" + OpName %26 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 + %19 = OpTypePointer Function %18 + %24 = OpTypeStruct %18 %18 + %25 = OpTypePointer Function %24 + %30 = OpTypeBool + %31 = OpConstantTrue %30 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %26 = OpVariable %25 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpCompositeConstruct %18 %21 %22 + OpStore %20 %23 + %27 = OpLoad %18 %20 + %28 = OpLoad %18 %20 + %29 = OpCompositeConstruct %24 %27 %28 + OpStore %26 %29 + OpSelectionMerge %33 None + OpBranchConditional %31 %32 %33 + %32 = OpLabel + OpBranch %33 + %33 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + 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); + + // Bad: |fresh_id| is not fresh. + auto transformation_bad_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 20, 29, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: |composite_id| does not refer to a existing instruction. + auto transformation_bad_2 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 40, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: |composite_id| does not refer to a composite value. + auto transformation_bad_3 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 9, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: |object_id| does not refer to a defined instruction. + auto transformation_bad_4 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 40, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); + + // Bad: |object_id| cannot refer to a pointer. + auto transformation_bad_5 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 8, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_5.IsApplicable(context.get(), transformation_context)); + + // Bad: |index| is not a correct index. + auto transformation_bad_6 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {2, 0, 0}); + ASSERT_FALSE( + transformation_bad_6.IsApplicable(context.get(), transformation_context)); + + // Bad: Type id of the object to be inserted and the type id of the + // component at |index| are not the same. + auto transformation_bad_7 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {1, 0}); + ASSERT_FALSE( + transformation_bad_7.IsApplicable(context.get(), transformation_context)); + + // Bad: |instruction_to_insert_before| does not refer to a defined + // instruction. + auto transformation_bad_8 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpIMul, 0), 50, 29, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_8.IsApplicable(context.get(), transformation_context)); + + // Bad: OpCompositeInsert cannot be inserted before OpBranchConditional with + // OpSelectionMerge above it. + auto transformation_bad_9 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpBranchConditional, 0), 50, 29, 11, + {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_9.IsApplicable(context.get(), transformation_context)); + + // Bad: |composite_id| does not have a type_id. + auto transformation_bad_10 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 1, 11, {1, 0, 0}); + ASSERT_FALSE(transformation_bad_10.IsApplicable(context.get(), + transformation_context)); +} + +TEST(TransformationCompositeInsertTest, EmptyCompositeScenarios) { + // This test handles cases where either the composite is empty or the + // composite contains an empty composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + %2 = OpTypeVoid + %60 = OpTypeStruct + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %61 = OpConstantComposite %60 + %62 = OpConstantComposite %60 + %12 = OpTypeStruct %6 %6 + %63 = OpTypeStruct %6 %60 + %13 = OpTypePointer Function %12 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + %64 = OpCompositeConstruct %63 %15 %61 + OpStore %14 %17 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + 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); + + // Bad: The composite with |composite_id| cannot be empty. + auto transformation_bad_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 61, 62, {1}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Good: It is possible to insert into a composite an element which is an + // empty composite. + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 64, 62, {1}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + %2 = OpTypeVoid + %60 = OpTypeStruct + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %61 = OpConstantComposite %60 + %62 = OpConstantComposite %60 + %12 = OpTypeStruct %6 %6 + %63 = OpTypeStruct %6 %60 + %13 = OpTypePointer Function %12 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + %64 = OpCompositeConstruct %63 %15 %61 + %50 = OpCompositeInsert %63 %62 %64 1 + OpStore %14 %17 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationCompositeInsertTest, IrrelevantCompositeNoSynonyms) { + // This test handles cases where either |composite| is irrelevant. + // The transformation shouldn't create any synonyms. + // The member composite has a different number of elements than the parent + // composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + 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); + + // Add fact that the composite is irrelevant. + fact_manager.AddFactIdIsIrrelevant(30); + + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // No synonyms should have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}), + MakeDataDescriptor(50, {0}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}), + MakeDataDescriptor(50, {1, 1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}), + MakeDataDescriptor(50, {1, 2}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}), + MakeDataDescriptor(50, {1, 0, 1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}), + MakeDataDescriptor(11, {}))); +} +TEST(TransformationCompositeInsertTest, IrrelevantObjectSomeSynonyms) { + // This test handles cases where |object| is irrelevant. + // The transformation should create some synonyms. It shouldn't create a + // synonym related to |object|. The member composite has a different number of + // elements than the parent composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + 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); + + // Add fact that the object is irrelevant. + fact_manager.AddFactIdIsIrrelevant(11); + + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // These synonyms should have been added. + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}), + MakeDataDescriptor(50, {0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}), + MakeDataDescriptor(50, {1, 1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}), + MakeDataDescriptor(50, {1, 2}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}), + MakeDataDescriptor(50, {1, 0, 1}))); + // This synonym shouldn't have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}), + MakeDataDescriptor(11, {}))); +} + +TEST(TransformationCompositeInsertTest, ApplicableCreatedSynonyms) { + // This test handles cases where neither |composite| nor |object| is + // irrelevant. The transformation should create synonyms. + // The member composite has a different number of elements than the parent + // composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + 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); + + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // These synonyms should have been added. + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}), + MakeDataDescriptor(50, {0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}), + MakeDataDescriptor(50, {1, 1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}), + MakeDataDescriptor(50, {1, 2}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}), + MakeDataDescriptor(50, {1, 0, 1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}), + MakeDataDescriptor(11, {}))); + + // These synonyms should not have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}), + MakeDataDescriptor(50, {1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0}), + MakeDataDescriptor(50, {1, 0}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 0}), + MakeDataDescriptor(50, {1, 0, 0}))); + + auto transformation_good_2 = TransformationCompositeInsert( + MakeInstructionDescriptor(50, SpvOpStore, 0), 51, 50, 11, {0, 1, 1}); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // These synonyms should have been added. + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1}), + MakeDataDescriptor(51, {1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 0}), + MakeDataDescriptor(51, {0, 0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 2}), + MakeDataDescriptor(51, {0, 2}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1, 0}), + MakeDataDescriptor(51, {0, 1, 0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(51, {0, 1, 1}), + MakeDataDescriptor(11, {}))); + + // These synonyms should not have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0}), + MakeDataDescriptor(51, {0}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1}), + MakeDataDescriptor(51, {0, 1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1, 1}), + MakeDataDescriptor(51, {0, 1, 1}))); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + %50 = OpCompositeInsert %25 %11 %30 1 0 0 + %51 = OpCompositeInsert %25 %11 %50 0 1 1 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationCompositeInsertTest, IdNotAvailableScenarios) { + // This test handles cases where either the composite or the object is not + // available before the |instruction_to_insert_before|. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b1" + OpName %18 "b2" + OpName %22 "lvl1" + OpMemberName %22 0 "b1" + OpMemberName %22 1 "b2" + OpName %24 "l1" + OpName %28 "i3" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %22 = OpTypeStruct %12 %12 + %23 = OpTypePointer Function %22 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %18 = OpVariable %13 Function + %24 = OpVariable %23 Function + %28 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %19 = OpLoad %6 %10 + %20 = OpLoad %6 %8 + %21 = OpCompositeConstruct %12 %19 %20 + OpStore %18 %21 + %25 = OpLoad %12 %14 + %26 = OpLoad %12 %18 + %27 = OpCompositeConstruct %22 %25 %26 + OpStore %24 %27 + %29 = OpLoad %6 %8 + %30 = OpLoad %6 %10 + %31 = OpIMul %6 %29 %30 + OpStore %28 %31 + %60 = OpCompositeConstruct %12 %20 %19 + %61 = OpCompositeConstruct %22 %26 %25 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + 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); + + // Bad: The object with |object_id| is not available at + // |instruction_to_insert_before|. + auto transformation_bad_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 27, 60, {1}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: The composite with |composite_id| is not available at + // |instruction_to_insert_before|. + auto transformation_bad_2 = TransformationCompositeInsert( + MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 61, 21, {1}); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: The |instruction_to_insert_before| is the composite itself and is + // available. + auto transformation_bad_3 = TransformationCompositeInsert( + MakeInstructionDescriptor(61, SpvOpCompositeConstruct, 0), 50, 61, 21, + {1}); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: The |instruction_to_insert_before| is the object itself and is not + // available. + auto transformation_bad_4 = TransformationCompositeInsert( + MakeInstructionDescriptor(60, SpvOpCompositeConstruct, 0), 50, 27, 60, + {1}); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); +} +} // namespace +} // namespace fuzz +} // namespace spvtools