diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 733bc5698..cea05cf30 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -63,15 +63,16 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_function_calls.h fuzzer_pass_add_global_variables.h fuzzer_pass_add_image_sample_unused_components.h - fuzzer_pass_add_synonyms.h fuzzer_pass_add_loads.h fuzzer_pass_add_local_variables.h fuzzer_pass_add_loop_preheaders.h + fuzzer_pass_add_loops_to_create_int_constant_synonyms.h fuzzer_pass_add_no_contraction_decorations.h fuzzer_pass_add_opphi_synonyms.h fuzzer_pass_add_parameters.h fuzzer_pass_add_relaxed_decorations.h fuzzer_pass_add_stores.h + fuzzer_pass_add_synonyms.h fuzzer_pass_add_vector_shuffle_instructions.h fuzzer_pass_adjust_branch_weights.h fuzzer_pass_adjust_function_controls.h @@ -147,6 +148,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_image_sample_unused_components.h transformation_add_local_variable.h transformation_add_loop_preheader.h + transformation_add_loop_to_create_int_constant_synonym.h transformation_add_no_contraction_decoration.h transformation_add_opphi_synonym.h transformation_add_parameter.h @@ -237,15 +239,16 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_function_calls.cpp fuzzer_pass_add_global_variables.cpp fuzzer_pass_add_image_sample_unused_components.cpp - fuzzer_pass_add_synonyms.cpp fuzzer_pass_add_loads.cpp fuzzer_pass_add_local_variables.cpp fuzzer_pass_add_loop_preheaders.cpp + fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp fuzzer_pass_add_no_contraction_decorations.cpp fuzzer_pass_add_opphi_synonyms.cpp fuzzer_pass_add_parameters.cpp fuzzer_pass_add_relaxed_decorations.cpp fuzzer_pass_add_stores.cpp + fuzzer_pass_add_synonyms.cpp fuzzer_pass_add_vector_shuffle_instructions.cpp fuzzer_pass_adjust_branch_weights.cpp fuzzer_pass_adjust_function_controls.cpp @@ -319,6 +322,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_image_sample_unused_components.cpp transformation_add_local_variable.cpp transformation_add_loop_preheader.cpp + transformation_add_loop_to_create_int_constant_synonym.cpp transformation_add_no_contraction_decoration.cpp transformation_add_opphi_synonym.cpp transformation_add_parameter.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 845c8c043..557ee55b3 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -35,6 +35,7 @@ #include "source/fuzz/fuzzer_pass_add_loads.h" #include "source/fuzz/fuzzer_pass_add_local_variables.h" #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" +#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" #include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" #include "source/fuzz/fuzzer_pass_add_parameters.h" @@ -238,6 +239,8 @@ Fuzzer::FuzzerResult Fuzzer::Run() { MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); + MaybeAddRepeatedPass( + &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 416ac7113..9e9e78dd9 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -75,6 +75,8 @@ const std::pair kChanceOfChoosingWorkgroupStorageClass = { 50, 50}; const std::pair kChanceOfConstructingComposite = {20, 50}; const std::pair kChanceOfCopyingObject = {20, 50}; +const std::pair kChanceOfCreatingIntSynonymsUsingLoops = { + 5, 10}; const std::pair kChanceOfDonatingAdditionalModule = {5, 50}; const std::pair kChanceOfDuplicatingRegionWithSelection = { 20, 50}; @@ -84,6 +86,8 @@ const std::pair kChanceOfGoingDeeperToInsertInComposite = { 30, 70}; const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = {50, 95}; +const std::pair + kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym = {50, 80}; const std::pair kChanceOfInliningFunction = {10, 90}; const std::pair kChanceOfInterchangingZeroLikeConstants = { 10, 90}; @@ -244,6 +248,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_constructing_composite_ = ChooseBetweenMinAndMax(kChanceOfConstructingComposite); chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject); + chance_of_creating_int_synonyms_using_loops_ = + ChooseBetweenMinAndMax(kChanceOfCreatingIntSynonymsUsingLoops); chance_of_donating_additional_module_ = ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); chance_of_duplicating_region_with_selection_ = @@ -254,6 +260,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite); chance_of_going_deeper_when_making_access_chain_ = ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); + chance_of_having_two_blocks_in_loop_to_create_int_synonym_ = + ChooseBetweenMinAndMax(kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym); chance_of_inlining_function_ = ChooseBetweenMinAndMax(kChanceOfInliningFunction); chance_of_interchanging_signedness_of_integer_operands_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 79042e7a4..b6f466ffe 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -204,6 +204,9 @@ class FuzzerContext { return chance_of_constructing_composite_; } uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; } + uint32_t GetChanceOfCreatingIntSynonymsUsingLoops() { + return chance_of_creating_int_synonyms_using_loops_; + } uint32_t GetChanceOfDonatingAdditionalModule() { return chance_of_donating_additional_module_; } @@ -219,6 +222,9 @@ class FuzzerContext { uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { return chance_of_going_deeper_when_making_access_chain_; } + uint32_t GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym() { + return chance_of_having_two_blocks_in_loop_to_create_int_synonym_; + } uint32_t GetChanceOfInliningFunction() { return chance_of_inlining_function_; } @@ -342,6 +348,9 @@ class FuzzerContext { uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) { return random_generator_->RandomUint32(number_of_components); } + int64_t GetRandomValueForStepConstantInLoop() { + return random_generator_->RandomUint64(UINT64_MAX); + } uint32_t GetRandomLoopControlPartialCount() { return random_generator_->RandomUint32(max_loop_control_partial_count_); } @@ -351,6 +360,9 @@ class FuzzerContext { uint32_t GetRandomLoopLimit() { return random_generator_->RandomUint32(max_loop_limit_); } + uint32_t GetRandomNumberOfLoopIterations(uint32_t max_num_iterations) { + return ChooseBetweenMinAndMax({1, max_num_iterations}); + } uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) { assert(num_of_params < GetMaximumNumberOfFunctionParameters()); return ChooseBetweenMinAndMax( @@ -423,11 +435,13 @@ class FuzzerContext { uint32_t chance_of_choosing_workgroup_storage_class_; uint32_t chance_of_constructing_composite_; uint32_t chance_of_copying_object_; + uint32_t chance_of_creating_int_synonyms_using_loops_; uint32_t chance_of_donating_additional_module_; uint32_t chance_of_duplicating_region_with_selection_; uint32_t chance_of_flattening_conditional_branch_; uint32_t chance_of_going_deeper_to_insert_in_composite_; uint32_t chance_of_going_deeper_when_making_access_chain_; + uint32_t chance_of_having_two_blocks_in_loop_to_create_int_synonym_; uint32_t chance_of_inlining_function_; 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 96feef9af..b1f39b73a 100644 --- a/source/fuzz/fuzzer_pass.cpp +++ b/source/fuzz/fuzzer_pass.cpp @@ -35,6 +35,7 @@ #include "source/fuzz/transformation_add_type_pointer.h" #include "source/fuzz/transformation_add_type_struct.h" #include "source/fuzz/transformation_add_type_vector.h" +#include "source/fuzz/transformation_split_block.h" namespace spvtools { namespace fuzz { @@ -608,6 +609,29 @@ opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader( return &*function->FindBlock(preheader_id); } +opt::BasicBlock* FuzzerPass::SplitBlockAfterOpPhiOrOpVariable( + uint32_t block_id) { + auto block = fuzzerutil::MaybeFindBlock(GetIRContext(), block_id); + assert(block && "|block_id| must be a block label"); + assert(!block->IsLoopHeader() && "|block_id| cannot be a loop header"); + + // Find the first non-OpPhi and non-OpVariable instruction. + auto non_phi_or_var_inst = &*block->begin(); + while (non_phi_or_var_inst->opcode() == SpvOpPhi || + non_phi_or_var_inst->opcode() == SpvOpVariable) { + non_phi_or_var_inst = non_phi_or_var_inst->NextNode(); + } + + // Split the block. + uint32_t new_block_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst), + new_block_id)); + + // We need to return the newly-created block. + return &*block->GetParent()->FindBlock(new_block_id); +} + uint32_t FuzzerPass::FindOrCreateLocalVariable( uint32_t pointer_type_id, uint32_t function_id, bool pointee_value_is_irrelevant) { diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h index 40471eb43..a9aae0979 100644 --- a/source/fuzz/fuzzer_pass.h +++ b/source/fuzz/fuzzer_pass.h @@ -293,6 +293,11 @@ class FuzzerPass { // reachable in the CFG (and thus has at least 2 predecessors). opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id); + // Returns the second block in the pair obtained by splitting |block_id| just + // after the last OpPhi or OpVariable instruction in it. Assumes that the + // block is not a loop header. + opt::BasicBlock* SplitBlockAfterOpPhiOrOpVariable(uint32_t block_id); + // Returns the id of an available local variable (storage class Function) with // the fact PointeeValueIsIrrelevant set according to // |pointee_value_is_irrelevant|. If there is no such variable, it creates one diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp new file mode 100644 index 000000000..1b286dd15 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp @@ -0,0 +1,247 @@ +// 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_loops_to_create_int_constant_synonyms.h" + +#include "source/fuzz/call_graph.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" + +namespace spvtools { +namespace fuzz { +namespace { +uint32_t kMaxNestingDepth = 4; +} // namespace + +FuzzerPassAddLoopsToCreateIntConstantSynonyms:: + FuzzerPassAddLoopsToCreateIntConstantSynonyms( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddLoopsToCreateIntConstantSynonyms:: + ~FuzzerPassAddLoopsToCreateIntConstantSynonyms() = default; + +void FuzzerPassAddLoopsToCreateIntConstantSynonyms::Apply() { + std::vector constants; + + // Choose the constants for which to create synonyms. + for (auto constant_def : GetIRContext()->GetConstants()) { + // Randomly decide whether to consider this constant. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfCreatingIntSynonymsUsingLoops())) { + continue; + } + + auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant( + constant_def->result_id()); + + // We only consider integer constants (scalar or vector). + if (!constant->AsIntConstant() && + !(constant->AsVectorConstant() && + constant->AsVectorConstant()->component_type()->AsInteger())) { + continue; + } + + constants.push_back(constant_def->result_id()); + } + + std::vector blocks; + + // Get a list of all the blocks before which we can add a loop creating a new + // synonym. We cannot apply the transformation while iterating over the + // module, because we are going to add new blocks. + for (auto& function : *GetIRContext()->module()) { + // Consider all blocks reachable from the first block of the function. + GetIRContext()->cfg()->ForEachBlockInPostOrder( + &*function.begin(), + [&blocks](opt::BasicBlock* block) { blocks.push_back(block->id()); }); + } + + // Make sure that the module has an OpTypeBool instruction, and 32-bit signed + // integer constants 0 and 1, adding them if necessary. + FindOrCreateBoolType(); + FindOrCreateIntegerConstant({0}, 32, true, false); + FindOrCreateIntegerConstant({1}, 32, true, false); + + // Compute the call graph. We can use this for any further computation, since + // we are not adding or removing functions or function calls. + auto call_graph = CallGraph(GetIRContext()); + + // Consider each constant and each block. + for (uint32_t constant_id : constants) { + // Choose one of the blocks. + uint32_t block_id = blocks[GetFuzzerContext()->RandomIndex(blocks)]; + + // Adjust the block so that the transformation can be applied. + auto block = GetIRContext()->get_instr_block(block_id); + + // If the block is a loop header, add a simple preheader. We can do this + // because we have excluded all the non-reachable headers. + if (block->IsLoopHeader()) { + block = GetOrCreateSimpleLoopPreheader(block->id()); + block_id = block->id(); + } + + assert(!block->IsLoopHeader() && + "The block cannot be a loop header at this point."); + + // If the block is a merge block, a continue block or it does not have + // exactly 1 predecessor, split it after any OpPhi or OpVariable + // instructions. + if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id()) || + GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock( + block->id()) || + GetIRContext()->cfg()->preds(block->id()).size() != 1) { + block = SplitBlockAfterOpPhiOrOpVariable(block->id()); + block_id = block->id(); + } + + // Randomly decide the values for the number of iterations and the step + // value, and compute the initial value accordingly. + + // The maximum number of iterations depends on the maximum possible loop + // nesting depth of the block, computed interprocedurally, i.e. also + // considering the possibility that the enclosing function is called inside + // a loop. It is: + // - 1 if the nesting depth is >= kMaxNestingDepth + // - 2^(kMaxNestingDepth - nesting_depth) otherwise + uint32_t max_nesting_depth = + call_graph.GetMaxCallNestingDepth(block->GetParent()->result_id()) + + GetIRContext()->GetStructuredCFGAnalysis()->LoopNestingDepth( + block->id()); + uint32_t num_iterations = + max_nesting_depth >= kMaxNestingDepth + ? 1 + : GetFuzzerContext()->GetRandomNumberOfLoopIterations( + 1u << (kMaxNestingDepth - max_nesting_depth)); + + // Find or create the corresponding constant containing the number of + // iterations. + uint32_t num_iterations_id = + FindOrCreateIntegerConstant({num_iterations}, 32, true, false); + + // Find the other constants. + // We use 64-bit values and then use the bits that we need. We find the + // step value (S) randomly and then compute the initial value (I) using + // the equation I = C + S*N. + uint32_t initial_value_id = 0; + uint32_t step_value_id = 0; + + // Get the content of the existing constant. + const auto constant = + GetIRContext()->get_constant_mgr()->FindDeclaredConstant(constant_id); + const auto constant_type_id = + GetIRContext()->get_def_use_mgr()->GetDef(constant_id)->type_id(); + + if (constant->AsIntConstant()) { + // The constant is a scalar integer. + + std::tie(initial_value_id, step_value_id) = + FindSuitableStepAndInitialValueConstants( + constant->GetZeroExtendedValue(), + constant->type()->AsInteger()->width(), + constant->type()->AsInteger()->IsSigned(), num_iterations); + } else { + // The constant is a vector of integers. + assert(constant->AsVectorConstant() && + constant->AsVectorConstant()->component_type()->AsInteger() && + "If the program got here, the constant should be a vector of " + "integers."); + + // Find a constant for each component of the initial value and the step + // values. + std::vector initial_value_component_ids; + std::vector step_value_component_ids; + + // Get the value, width and signedness of the components. + std::vector component_values; + for (auto component : constant->AsVectorConstant()->GetComponents()) { + component_values.push_back(component->GetZeroExtendedValue()); + } + uint32_t bit_width = + constant->AsVectorConstant()->component_type()->AsInteger()->width(); + uint32_t is_signed = constant->AsVectorConstant() + ->component_type() + ->AsInteger() + ->IsSigned(); + + for (uint64_t component_val : component_values) { + uint32_t initial_val_id; + uint32_t step_val_id; + std::tie(initial_val_id, step_val_id) = + FindSuitableStepAndInitialValueConstants(component_val, bit_width, + is_signed, num_iterations); + initial_value_component_ids.push_back(initial_val_id); + step_value_component_ids.push_back(step_val_id); + } + + // Find or create the vector constants. + initial_value_id = FindOrCreateCompositeConstant( + initial_value_component_ids, constant_type_id, false); + step_value_id = FindOrCreateCompositeConstant(step_value_component_ids, + constant_type_id, false); + } + + assert(initial_value_id && step_value_id && + "|initial_value_id| and |step_value_id| should have been defined."); + + // Randomly decide whether to have two blocks (or just one) in the new + // loop. + uint32_t additional_block_id = + GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym()) + ? GetFuzzerContext()->GetFreshId() + : 0; + + // Add the loop and create the synonym. + ApplyTransformation(TransformationAddLoopToCreateIntConstantSynonym( + constant_id, initial_value_id, step_value_id, num_iterations_id, + block_id, GetFuzzerContext()->GetFreshId(), + GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(), + GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(), + GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(), + additional_block_id)); + } +} + +std::pair FuzzerPassAddLoopsToCreateIntConstantSynonyms:: + FindSuitableStepAndInitialValueConstants(uint64_t constant_val, + uint32_t bit_width, bool is_signed, + uint32_t num_iterations) { + // Choose the step value randomly and compute the initial value accordingly. + // The result of |initial_value| could overflow, but this is OK, since + // the transformation takes overflows into consideration (the equation still + // holds as long as the last |bit_width| bits of C and of (I-S*N) match). + uint64_t step_value = + GetFuzzerContext()->GetRandomValueForStepConstantInLoop(); + uint64_t initial_value = constant_val + step_value * num_iterations; + + uint32_t initial_val_id = FindOrCreateIntegerConstant( + fuzzerutil::IntToWords(initial_value, bit_width, is_signed), bit_width, + is_signed, false); + + uint32_t step_val_id = FindOrCreateIntegerConstant( + fuzzerutil::IntToWords(step_value, bit_width, is_signed), bit_width, + is_signed, false); + + return {initial_val_id, step_val_id}; +} + +} // namespace fuzz +} // namespace spvtools \ No newline at end of file diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h new file mode 100644 index 000000000..ee98c4e7f --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h @@ -0,0 +1,53 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that adds synonyms for integer, scalar or vector, constants, by +// adding loops that compute the same value by subtracting a value S from an +// initial value I, and for N times, so that C = I - S*N. +class FuzzerPassAddLoopsToCreateIntConstantSynonyms : public FuzzerPass { + public: + FuzzerPassAddLoopsToCreateIntConstantSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddLoopsToCreateIntConstantSynonyms(); + + void Apply() override; + + private: + // Returns a pair (initial_val_id, step_val_id) such that both ids are + // integer scalar constants of the same type as the scalar integer constant + // identified by the given |constant_val|, |bit_width| and signedness, and + // such that, if I is the value of initial_val_id, S is the value of + // step_val_id and C is the value of the constant, the equation (C = I - S * + // num_iterations) holds, (only considering the last |bit_width| bits of each + // constant). + std::pair FindSuitableStepAndInitialValueConstants( + uint64_t constant_val, uint32_t bit_width, bool is_signed, + uint32_t num_iterations); +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_ diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index b550a741c..aca958765 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -1306,6 +1306,30 @@ void AddStructType(opt::IRContext* ir_context, uint32_t result_id, UpdateModuleIdBound(ir_context, result_id); } +std::vector IntToWords(uint64_t value, uint32_t width, + bool is_signed) { + assert(width <= 64 && "The bit width should not be more than 64 bits"); + + // Sign-extend or zero-extend the last |width| bits of |value|, depending on + // |is_signed|. + if (is_signed) { + // Sign-extend by shifting left and then shifting right, interpreting the + // integer as signed. + value = static_cast(value << (64 - width)) >> (64 - width); + } else { + // Zero-extend by shifting left and then shifting right, interpreting the + // integer as unsigned. + value = (value << (64 - width)) >> (64 - width); + } + + std::vector result; + result.push_back(static_cast(value)); + if (width > 32) { + result.push_back(static_cast(value >> 32)); + } + return result; +} + bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id, uint32_t type2_id) { if (type1_id == type2_id) { diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h index b4b905750..981d22956 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -474,6 +474,17 @@ void AddVectorType(opt::IRContext* ir_context, uint32_t result_id, void AddStructType(opt::IRContext* ir_context, uint32_t result_id, const std::vector& component_type_ids); +// Returns a vector of words representing the integer |value|, only considering +// the last |width| bits. The last |width| bits are sign-extended if the value +// is signed, zero-extended if it is unsigned. +// |width| must be <= 64. +// If |width| <= 32, returns a vector containing one value. If |width| > 64, +// returns a vector containing two values, with the first one representing the +// lower-order word of the value and the second one representing the +// higher-order word. +std::vector IntToWords(uint64_t value, uint32_t width, + bool is_signed); + // Returns a bit pattern that represents a floating-point |value|. inline uint32_t FloatToWord(float value) { uint32_t result; diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h index fdcb05523..57820a242 100644 --- a/source/fuzz/pass_management/repeated_pass_instances.h +++ b/source/fuzz/pass_management/repeated_pass_instances.h @@ -30,6 +30,7 @@ #include "source/fuzz/fuzzer_pass_add_loads.h" #include "source/fuzz/fuzzer_pass_add_local_variables.h" #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" +#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" #include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" #include "source/fuzz/fuzzer_pass_add_parameters.h" #include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" @@ -118,6 +119,7 @@ class RepeatedPassInstances { REPEATED_PASS_INSTANCE(AddLoads); REPEATED_PASS_INSTANCE(AddLocalVariables); REPEATED_PASS_INSTANCE(AddLoopPreheaders); + REPEATED_PASS_INSTANCE(AddLoopsToCreateIntConstantSynonyms); REPEATED_PASS_INSTANCE(AddOpPhiSynonyms); REPEATED_PASS_INSTANCE(AddParameters); REPEATED_PASS_INSTANCE(AddRelaxedDecorations); diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp index 253504bb5..8c0380786 100644 --- a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp +++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp @@ -122,6 +122,10 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations( {pass_instances_->GetDuplicateRegionsWithSelections(), pass_instances_->GetOutlineFunctions()}); } + if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) { + // - New synonyms can be applied + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } if (&pass == pass_instances_->GetAddOpPhiSynonyms()) { // - New synonyms can be applied // - If OpPhi synonyms are introduced for blocks with dead predecessors, the diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index c6169438a..d052ccc5b 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -497,6 +497,7 @@ message Transformation { TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75; TransformationFlattenConditionalBranch flatten_conditional_branch = 76; TransformationAddBitInstructionSynonym add_bit_instruction_synonym = 77; + TransformationAddLoopToCreateIntConstantSynonym add_loop_to_create_int_constant_synonym = 78; // Add additional option using the next available number. } } @@ -847,6 +848,97 @@ message TransformationAddLoopPreheader { } +message TransformationAddLoopToCreateIntConstantSynonym { + // A transformation that uses a loop to create a synonym for an integer + // constant C (scalar or vector) using an initial value I, a step value S and + // a number of iterations N such that C = I - N * S. For each iteration, S is + // added to the total. + // The loop can be made up of one or two blocks, and it is inserted before a + // block with a single predecessor. In the one-block case, it is of the form: + // + // %loop_id = OpLabel + // %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id + // %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id + // %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id + // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1 + // %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id + // OpLoopMerge %block_after_loop_id %loop_id %none + // OpBranchConditional %cond_id %loop_id %block_after_loop_id + // + // A new OpPhi instruction is then added to %block_after_loop_id, as follows: + // + // %block_after_loop_id = OpLabel + // %syn_id = OpPhi %type_of_I %eventual_syn_id %loop_id + // + // This can be translated, assuming that N > 0, to: + // int syn = I; + // for (int ctr = 0; ctr < N; ctr++) syn = syn - S; + // + // All existing OpPhi instructions in %block_after_loop_id are also updated + // to reflect the fact that its predecessor is now %loop_id. + + // The following are existing ids. + + // The id of the integer constant C that we want a synonym of. + uint32 constant_id = 1; + + // The id of the initial value integer constant I. + uint32 initial_val_id = 2; + + // The id of the step value integer constant S. + uint32 step_val_id = 3; + + // The id of the integer scalar constant, its value being the number of + // iterations N. + uint32 num_iterations_id = 4; + + // The label id of the block before which the loop must be inserted. + uint32 block_after_loop_id = 5; + + + // The following are fresh ids. + + // A fresh id for the synonym. + uint32 syn_id = 6; + + // A fresh id for the label of the loop, + uint32 loop_id = 7; + + // A fresh id for the counter. + uint32 ctr_id = 8; + + // A fresh id taking the value I - S * ctr at the ctr-th iteration. + uint32 temp_id = 9; + + // A fresh id taking the value I - S * (ctr + 1) at the ctr-th iteration, and + // thus I - S * N at the last iteration. + uint32 eventual_syn_id = 10; + + // A fresh id for the incremented counter. + uint32 incremented_ctr_id = 11; + + // A fresh id for the loop condition. + uint32 cond_id = 12; + + // The instructions in the loop can also be laid out in two basic blocks, as follows: + // + // %loop_id = OpLabel + // %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id + // %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id + // OpLoopMerge %block_after_loop_id %additional_block_id None + // OpBranch %additional_block_id + // + // %additional_block_id = OpLabel + // %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id + // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1 + // %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id + // OpBranchConditional %cond_id %loop_id %block_after_loop_id + + // A fresh id for the additional block. If this is 0, it means that only one + // block is to be created. + uint32 additional_block_id = 13; +} + message TransformationAddNoContractionDecoration { // Applies OpDecorate NoContraction to the given result id diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index 0b253ff5a..7301a8941 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -33,6 +33,7 @@ #include "source/fuzz/transformation_add_image_sample_unused_components.h" #include "source/fuzz/transformation_add_local_variable.h" #include "source/fuzz/transformation_add_loop_preheader.h" +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" #include "source/fuzz/transformation_add_no_contraction_decoration.h" #include "source/fuzz/transformation_add_opphi_synonym.h" #include "source/fuzz/transformation_add_parameter.h" @@ -149,6 +150,10 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kAddLoopPreheader: return MakeUnique( message.add_loop_preheader()); + case protobufs::Transformation::TransformationCase:: + kAddLoopToCreateIntConstantSynonym: + return MakeUnique( + message.add_loop_to_create_int_constant_synonym()); case protobufs::Transformation::TransformationCase:: kAddNoContractionDecoration: return MakeUnique( diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp new file mode 100644 index 000000000..fccfc34d0 --- /dev/null +++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp @@ -0,0 +1,427 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { +namespace { +uint32_t kMaxNumOfIterations = 32; +} + +TransformationAddLoopToCreateIntConstantSynonym:: + TransformationAddLoopToCreateIntConstantSynonym( + const protobufs::TransformationAddLoopToCreateIntConstantSynonym& + message) + : message_(message) {} + +TransformationAddLoopToCreateIntConstantSynonym:: + TransformationAddLoopToCreateIntConstantSynonym( + uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id, + uint32_t num_iterations_id, uint32_t block_after_loop_id, + uint32_t syn_id, uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id, + uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id, + uint32_t additional_block_id) { + message_.set_constant_id(constant_id); + message_.set_initial_val_id(initial_val_id); + message_.set_step_val_id(step_val_id); + message_.set_num_iterations_id(num_iterations_id); + message_.set_block_after_loop_id(block_after_loop_id); + message_.set_syn_id(syn_id); + message_.set_loop_id(loop_id); + message_.set_ctr_id(ctr_id); + message_.set_temp_id(temp_id); + message_.set_eventual_syn_id(eventual_syn_id); + message_.set_incremented_ctr_id(incremented_ctr_id); + message_.set_cond_id(cond_id); + message_.set_additional_block_id(additional_block_id); +} + +bool TransformationAddLoopToCreateIntConstantSynonym::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // Check that |message_.constant_id|, |message_.initial_val_id| and + // |message_.step_val_id| are existing constants. + + auto constant = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.constant_id()); + auto initial_val = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.initial_val_id()); + auto step_val = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.step_val_id()); + + if (!constant || !initial_val || !step_val) { + return false; + } + + // Check that the type of |constant| is integer scalar or vector with integer + // components. + if (!constant->AsIntConstant() && + (!constant->AsVectorConstant() || + !constant->type()->AsVector()->element_type()->AsInteger())) { + return false; + } + + // Check that the component bit width of |constant| is <= 64. + // Consider the width of the constant if it is an integer, of a single + // component if it is a vector. + uint32_t bit_width = + constant->AsIntConstant() + ? constant->type()->AsInteger()->width() + : constant->type()->AsVector()->element_type()->AsInteger()->width(); + if (bit_width > 64) { + return false; + } + + auto constant_def = + ir_context->get_def_use_mgr()->GetDef(message_.constant_id()); + auto initial_val_def = + ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id()); + auto step_val_def = + ir_context->get_def_use_mgr()->GetDef(message_.step_val_id()); + + // Check that |constant|, |initial_val| and |step_val| have the same type, + // with possibly different signedness. + if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(), + initial_val_def->type_id()) || + !fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(), + step_val_def->type_id())) { + return false; + } + + // |message_.num_iterations_id| is an integer constant with bit width 32. + auto num_iterations = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.num_iterations_id()); + + if (!num_iterations || !num_iterations->AsIntConstant() || + num_iterations->type()->AsInteger()->width() != 32) { + return false; + } + + // Check that the number of iterations is > 0 and <= 32. + uint32_t num_iterations_value = + num_iterations->AsIntConstant()->GetU32BitValue(); + + if (num_iterations_value == 0 || num_iterations_value > kMaxNumOfIterations) { + return false; + } + + // Check that the module contains 32-bit signed integer scalar constants of + // value 0 and 1. + if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context, + {0}, 32, true, false)) { + return false; + } + + if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context, + {1}, 32, true, false)) { + return false; + } + + // Check that the module contains the Bool type. + if (!fuzzerutil::MaybeGetBoolType(ir_context)) { + return false; + } + + // Check that the equation C = I - S * N is satisfied. + + // Collect the components in vectors (if the constants are scalars, these + // vectors will contain the constants themselves). + std::vector c_components; + std::vector i_components; + std::vector s_components; + if (constant->AsIntConstant()) { + c_components.emplace_back(constant); + i_components.emplace_back(initial_val); + s_components.emplace_back(step_val); + } else { + // It is a vector: get all the components. + c_components = constant->AsVectorConstant()->GetComponents(); + i_components = initial_val->AsVectorConstant()->GetComponents(); + s_components = step_val->AsVectorConstant()->GetComponents(); + } + + // Check the value of the components satisfy the equation. + for (uint32_t i = 0; i < c_components.size(); i++) { + // Use 64-bits integers to be able to handle constants of any width <= 64. + uint64_t c_value = c_components[i]->AsIntConstant()->GetZeroExtendedValue(); + uint64_t i_value = i_components[i]->AsIntConstant()->GetZeroExtendedValue(); + uint64_t s_value = s_components[i]->AsIntConstant()->GetZeroExtendedValue(); + + uint64_t result = i_value - s_value * num_iterations_value; + + // Use bit shifts to ignore the first bits in excess (if there are any). By + // shifting left, we discard the first |64 - bit_width| bits. By shifting + // right, we move the bits back to their correct position. + result = (result << (64 - bit_width)) >> (64 - bit_width); + + if (c_value != result) { + return false; + } + } + + // Check that |message_.block_after_loop_id| is the label of a block. + auto block = + fuzzerutil::MaybeFindBlock(ir_context, message_.block_after_loop_id()); + + // Check that the block exists and has a single predecessor. + if (!block || ir_context->cfg()->preds(block->id()).size() != 1) { + return false; + } + + // Check that the block is not a merge block. + if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) { + return false; + } + + // Check that the block is not a continue block. + if (ir_context->GetStructuredCFGAnalysis()->IsContinueBlock(block->id())) { + return false; + } + + // Check that the block is not a loop header. + if (block->IsLoopHeader()) { + return false; + } + + // Check all the fresh ids. + std::set fresh_ids_used; + for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(), + message_.temp_id(), message_.eventual_syn_id(), + message_.incremented_ctr_id(), message_.cond_id()}) { + if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context, + &fresh_ids_used)) { + return false; + } + } + + // Check the additional block id if it is non-zero. + return !message_.additional_block_id() || + CheckIdIsFreshAndNotUsedByThisTransformation( + message_.additional_block_id(), ir_context, &fresh_ids_used); +} + +void TransformationAddLoopToCreateIntConstantSynonym::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + // Find 32-bit signed integer constants 0 and 1. + uint32_t const_0_id = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {0}, 32, true, false); + auto const_0_def = ir_context->get_def_use_mgr()->GetDef(const_0_id); + uint32_t const_1_id = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {1}, 32, true, false); + + // Retrieve the instruction defining the initial value constant. + auto initial_val_def = + ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id()); + + // Retrieve the block before which we want to insert the loop. + auto block_after_loop = + ir_context->get_instr_block(message_.block_after_loop_id()); + + // Find the predecessor of the block. + uint32_t pred_id = + ir_context->cfg()->preds(message_.block_after_loop_id())[0]; + + // Get the id for the last block in the new loop. It will be + // |message_.additional_block_id| if this is non_zero, |message_.loop_id| + // otherwise. + uint32_t last_loop_block_id = message_.additional_block_id() + ? message_.additional_block_id() + : message_.loop_id(); + + // Create the loop header block. + std::unique_ptr loop_block = + MakeUnique(MakeUnique( + ir_context, SpvOpLabel, 0, message_.loop_id(), + opt::Instruction::OperandList{})); + + // Add OpPhi instructions to retrieve the current value of the counter and of + // the temporary variable that will be decreased at each operation. + loop_block->AddInstruction(MakeUnique( + ir_context, SpvOpPhi, const_0_def->type_id(), message_.ctr_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {const_0_id}}, + {SPV_OPERAND_TYPE_ID, {pred_id}}, + {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}})); + + loop_block->AddInstruction(MakeUnique( + ir_context, SpvOpPhi, initial_val_def->type_id(), message_.temp_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.initial_val_id()}}, + {SPV_OPERAND_TYPE_ID, {pred_id}}, + {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}})); + + // Collect the other instructions in a list. These will be added to an + // additional block if |message_.additional_block_id| is defined, to the loop + // header otherwise. + std::vector> other_instructions; + + // Add an instruction to subtract the step value from the temporary value. + // The value of this id will converge to the constant in the last iteration. + other_instructions.push_back(MakeUnique( + ir_context, SpvOpISub, initial_val_def->type_id(), + message_.eventual_syn_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.temp_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.step_val_id()}}})); + + // Add an instruction to increment the counter. + other_instructions.push_back(MakeUnique( + ir_context, SpvOpIAdd, const_0_def->type_id(), + message_.incremented_ctr_id(), + opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {message_.ctr_id()}}, + {SPV_OPERAND_TYPE_ID, {const_1_id}}})); + + // Add an instruction to decide whether the condition holds. + other_instructions.push_back(MakeUnique( + ir_context, SpvOpSLessThan, fuzzerutil::MaybeGetBoolType(ir_context), + message_.cond_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.num_iterations_id()}}})); + + // Define the OpLoopMerge instruction for the loop header. The merge block is + // the existing block, the continue block is the last block in the loop + // (either the loop itself or the additional block). + std::unique_ptr merge_inst = MakeUnique( + ir_context, SpvOpLoopMerge, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}, + {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}); + + // Define a conditional branch instruction, branching to the loop header if + // the condition holds, and to the existing block otherwise. This instruction + // will be added to the last block in the loop. + std::unique_ptr conditional_branch = + MakeUnique( + ir_context, SpvOpBranchConditional, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.cond_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.loop_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}}}); + + if (message_.additional_block_id()) { + // If an id for the additional block is specified, create an additional + // block, containing the instructions in the list and a branching + // instruction. + + std::unique_ptr additional_block = + MakeUnique(MakeUnique( + ir_context, SpvOpLabel, 0, message_.additional_block_id(), + opt::Instruction::OperandList{})); + + for (auto& instruction : other_instructions) { + additional_block->AddInstruction(std::move(instruction)); + } + + additional_block->AddInstruction(std::move(conditional_branch)); + + // Add the merge instruction to the header. + loop_block->AddInstruction(std::move(merge_inst)); + + // Add an unconditional branch from the header to the additional block. + loop_block->AddInstruction(MakeUnique( + ir_context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.additional_block_id()}}})); + + // Insert the two loop blocks before the existing block. + block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block), + block_after_loop); + block_after_loop->GetParent()->InsertBasicBlockBefore( + std::move(additional_block), block_after_loop); + } else { + // If no id for an additional block is specified, the loop will only be made + // up of one block, so we need to add all the instructions to it. + + for (auto& instruction : other_instructions) { + loop_block->AddInstruction(std::move(instruction)); + } + + // Add the merge and conditional branch instructions. + loop_block->AddInstruction(std::move(merge_inst)); + loop_block->AddInstruction(std::move(conditional_branch)); + + // Insert the header before the existing block. + block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block), + block_after_loop); + } + + // Update the branching instructions leading to this block. + ir_context->get_def_use_mgr()->ForEachUse( + message_.block_after_loop_id(), + [this](opt::Instruction* instruction, uint32_t operand_index) { + assert(instruction->opcode() != SpvOpLoopMerge && + instruction->opcode() != SpvOpSelectionMerge && + instruction->opcode() != SpvOpSwitch && + "The block should not be referenced by OpLoopMerge, " + "OpSelectionMerge or OpSwitch instructions, by construction."); + // Replace all uses of the label inside branch instructions. + if (instruction->opcode() == SpvOpBranch || + instruction->opcode() == SpvOpBranchConditional) { + instruction->SetOperand(operand_index, {message_.loop_id()}); + } + }); + + // Update all the OpPhi instructions in the block after the loop: its + // predecessor is now the last block in the loop. + block_after_loop->ForEachPhiInst( + [last_loop_block_id](opt::Instruction* phi_inst) { + // Since the block only had one predecessor, the id of the predecessor + // is input operand 1. + phi_inst->SetInOperand(1, {last_loop_block_id}); + }); + + // Add a new OpPhi instruction at the beginning of the block after the loop, + // defining the synonym of the constant. The type id will be the same as + // |message_.initial_value_id|, since this is the value that is decremented in + // the loop. + block_after_loop->begin()->InsertBefore(MakeUnique( + ir_context, SpvOpPhi, initial_val_def->type_id(), message_.syn_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}})); + + // Update the module id bound with all the fresh ids used. + for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(), + message_.temp_id(), message_.eventual_syn_id(), + message_.incremented_ctr_id(), message_.cond_id(), + message_.cond_id(), message_.additional_block_id()}) { + fuzzerutil::UpdateModuleIdBound(ir_context, id); + } + + // Since we changed the structure of the module, we need to invalidate all the + // analyses. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // Record that |message_.syn_id| is synonymous with |message_.constant_id|. + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.syn_id(), {}), + MakeDataDescriptor(message_.constant_id(), {}), ir_context); +} + +protobufs::Transformation +TransformationAddLoopToCreateIntConstantSynonym::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_loop_to_create_int_constant_synonym() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h new file mode 100644 index 000000000..291402988 --- /dev/null +++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h @@ -0,0 +1,69 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_ + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { +class TransformationAddLoopToCreateIntConstantSynonym : public Transformation { + public: + explicit TransformationAddLoopToCreateIntConstantSynonym( + const protobufs::TransformationAddLoopToCreateIntConstantSynonym& + message); + + TransformationAddLoopToCreateIntConstantSynonym( + uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id, + uint32_t num_iterations_id, uint32_t block_after_loop_id, uint32_t syn_id, + uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id, + uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id, + uint32_t additional_block_id); + + // - |message_.constant_id|, |message_.initial_value_id|, + // |message_.step_val_id| are integer constants (scalar or vectors) with the + // same type (with possibly different signedness, but same bit width, which + // must be <= 64). Let their value be C, I, S respectively. + // - |message_.num_iterations_id| is a 32-bit integer scalar constant, with + // value N > 0 and N <= 32. + // - The module contains 32-bit signed integer scalar constants of values 0 + // and 1. + // - The module contains the boolean type. + // - C = I - S * N + // - |message_.block_after_loop_id| is the label of a block which has a single + // predecessor and which is not a merge block, a continue block or a loop + // header. + // - |message_.additional_block_id| is either 0 or a valid fresh id, distinct + // from the other fresh ids. + // - All of the other parameters are valid fresh ids. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Adds a loop to the module, defining a synonym of an integer (scalar or + // vector) constant. This id is marked as synonym with the original constant. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddLoopToCreateIntConstantSynonym message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_ diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 66299f77a..a918b24f9 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -46,6 +46,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_add_image_sample_unused_components_test.cpp transformation_add_local_variable_test.cpp transformation_add_loop_preheader_test.cpp + transformation_add_loop_to_create_int_constant_synonym_test.cpp transformation_add_no_contraction_decoration_test.cpp transformation_add_opphi_synonym_test.cpp transformation_add_parameter_test.cpp diff --git a/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp new file mode 100644 index 000000000..d88ffae8f --- /dev/null +++ b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp @@ -0,0 +1,957 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, + ConstantsNotSuitable) { + std::string shader = R"( + OpCapability Shader + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %36 = OpTypeBool + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 -1 + %7 = OpConstant %5 0 + %8 = OpConstant %5 1 + %9 = OpConstant %5 2 + %10 = OpConstant %5 5 + %11 = OpConstant %5 10 + %12 = OpConstant %5 20 + %13 = OpConstant %5 33 + %14 = OpTypeVector %5 2 + %15 = OpConstantComposite %14 %10 %11 + %16 = OpConstantComposite %14 %12 %12 + %17 = OpTypeVector %5 3 + %18 = OpConstantComposite %17 %11 %7 %11 + %19 = OpTypeInt 64 1 + %20 = OpConstant %19 0 + %21 = OpConstant %19 10 + %22 = OpTypeVector %19 2 + %23 = OpConstantComposite %22 %21 %20 + %24 = OpTypeFloat 32 + %25 = OpConstant %24 0 + %26 = OpConstant %24 5 + %27 = OpConstant %24 10 + %28 = OpConstant %24 20 + %29 = OpTypeVector %24 3 + %30 = OpConstantComposite %29 %26 %27 %26 + %31 = OpConstantComposite %29 %28 %28 %28 + %32 = OpConstantComposite %29 %27 %25 %27 + %2 = OpFunction %3 None %4 + %33 = OpLabel + %34 = OpCopyObject %5 %11 + OpBranch %35 + %35 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Reminder: the first four parameters of the constructor are the constants + // with values for C, I, S, N respectively. + + // %70 does not correspond to an id in the module. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 70, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %35 is not a constant. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 35, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %27, %28 and %26 are not integer constants, but scalar floats. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 27, 28, 26, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %32, %31 and %30 are not integer constants, but vector floats. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 32, 31, 30, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %18=(10, 0, 10) has 3 components, while %16=(20, 20) and %15=(5, 10) + // have 2. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 18, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %21 has bit width 64, while the width of %12 and %10 is 32. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 21, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %13 has component width 64, while the component width of %16 and %15 is 32. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 13, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %21 (N) is a 64-bit integer, not 32-bit. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 21, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %7 (N) has value 0, so N <= 0. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 7, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %6 (N) has value -1, so N <= 1. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 6, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %13 (N) has value 33, so N > 32. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 6, 13, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // C(%11)=10, I(%12)=20, S(%10)=5, N(%8)=1, so C=I-S*N does not hold, as + // 20-5*1=15. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 11, 12, 10, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // C(%15)=(5, 10), I(%16)=(20, 20), S(%15)=(5, 10), N(%8)=1, so C=I-S*N does + // not hold, as (20, 20)-1*(5, 10) = (15, 10). + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 15, 16, 15, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, + MissingConstantsOrBoolType) { + { + // The shader is missing the boolean type. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %20 = OpConstant %5 0 + %6 = OpConstant %5 1 + %7 = OpConstant %5 2 + %8 = OpConstant %5 5 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + } + { + // The shader is missing a 32-bit integer 0 constant. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %20 = OpTypeBool + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 1 + %7 = OpConstant %5 2 + %8 = OpConstant %5 5 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + } + { + // The shader is missing a 32-bit integer 1 constant. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %20 = OpTypeBool + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 0 + %7 = OpConstant %5 2 + %8 = OpConstant %5 5 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + } +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Simple) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + %22 = OpPhi %7 %12 %14 + OpSelectionMerge %16 None + OpBranchConditional %6 %17 %18 + %17 = OpLabel + %23 = OpPhi %7 %13 %15 + OpBranch %18 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + OpBranch %19 + %19 = OpLabel + OpLoopMerge %20 %19 None + OpBranchConditional %6 %20 %19 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + OpBranch %24 + %24 = OpLabel + OpLoopMerge %27 %25 None + OpBranch %25 + %25 = OpLabel + OpBranch %26 + %26 = OpLabel + OpBranchConditional %6 %24 %27 + %27 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Block %14 has no predecessors. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 14, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %18 has more than one predecessor. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 18, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %16 is a merge block. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 16, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %25 is a continue block. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 25, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %19 has more than one predecessor. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 19, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %20 is a merge block. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 20, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Id %20 is supposed to be fresh, but it is not. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 20, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Id %100 is used twice. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 100, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Id %100 is used twice. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 100) + .IsApplicable(context.get(), transformation_context)); + + // Only the last id (for the additional block) is optional, so the other ones + // cannot be 0. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 0, 101, 102, 103, 104, 105, 106, 100) + .IsApplicable(context.get(), transformation_context)); + + // This transformation will create a synonym of constant %12 from a 1-block + // loop. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // This transformation will create a synonym of constant %12 from a 2-block + // loop. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 17, 107, 108, 109, 110, 111, 112, 113, 114); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(107, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // This transformation will create a synonym of constant %12 from a 2-block + // loop. + auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 26, 115, 116, 117, 118, 119, 120, 121, 0); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(115, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %14 %105 %101 + %103 = OpPhi %7 %13 %14 %104 %101 + %104 = OpISub %7 %103 %11 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %10 + OpLoopMerge %15 %101 None + OpBranchConditional %106 %101 %15 + %15 = OpLabel + %100 = OpPhi %7 %104 %101 + %22 = OpPhi %7 %12 %101 + OpSelectionMerge %16 None + OpBranchConditional %6 %108 %18 + %108 = OpLabel + %109 = OpPhi %7 %8 %15 %112 %114 + %110 = OpPhi %7 %13 %15 %111 %114 + OpLoopMerge %17 %114 None + OpBranch %114 + %114 = OpLabel + %111 = OpISub %7 %110 %11 + %112 = OpIAdd %7 %109 %9 + %113 = OpSLessThan %5 %112 %10 + OpBranchConditional %113 %108 %17 + %17 = OpLabel + %107 = OpPhi %7 %111 %114 + %23 = OpPhi %7 %13 %114 + OpBranch %18 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + OpBranch %19 + %19 = OpLabel + OpLoopMerge %20 %19 None + OpBranchConditional %6 %20 %19 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + OpBranch %24 + %24 = OpLabel + OpLoopMerge %27 %25 None + OpBranch %25 + %25 = OpLabel + OpBranch %116 + %116 = OpLabel + %117 = OpPhi %7 %8 %25 %120 %116 + %118 = OpPhi %7 %13 %25 %119 %116 + %119 = OpISub %7 %118 %11 + %120 = OpIAdd %7 %117 %9 + %121 = OpSLessThan %5 %120 %10 + OpLoopMerge %26 %116 None + OpBranchConditional %121 %116 %26 + %26 = OpLabel + %115 = OpPhi %7 %119 %116 + OpBranchConditional %6 %24 %27 + %27 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, + DifferentSignednessAndVectors) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %14 5 + %17 = OpConstant %14 10 + %18 = OpConstant %14 20 + %19 = OpTypeVector %7 2 + %20 = OpTypeVector %14 2 + %21 = OpConstantComposite %19 %12 %8 + %22 = OpConstantComposite %20 %17 %15 + %23 = OpConstantComposite %19 %13 %12 + %24 = OpConstantComposite %19 %11 %11 + %2 = OpFunction %3 None %4 + %25 = OpLabel + OpBranch %26 + %26 = OpLabel + OpBranch %27 + %27 = OpLabel + OpBranch %28 + %28 = OpLabel + OpBranch %29 + %29 = OpLabel + OpBranch %30 + %30 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // These tests check that the transformation is applicable and is applied + // correctly with integers, scalar and vectors, of different signedness. + + // %12 is a signed integer, %18 and %16 are unsigned integers. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 18, 16, 10, 26, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %12 and %11 are signed integers, %18 is an unsigned integer. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 18, 11, 10, 27, 108, 109, 110, 111, 112, 113, 114, 0); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(108, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %17, %18 and %16 are all signed integers. + auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym( + 17, 18, 16, 10, 28, 115, 116, 117, 118, 119, 120, 121, 0); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(17, {}), MakeDataDescriptor(115, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %22 is an unsigned integer vector, %23 and %24 are signed integer vectors. + auto transformation4 = TransformationAddLoopToCreateIntConstantSynonym( + 22, 23, 24, 10, 29, 122, 123, 124, 125, 126, 127, 128, 0); + ASSERT_TRUE( + transformation4.IsApplicable(context.get(), transformation_context)); + transformation4.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(22, {}), MakeDataDescriptor(122, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %21, %23 and %24 are all signed integer vectors. + auto transformation5 = TransformationAddLoopToCreateIntConstantSynonym( + 21, 23, 24, 10, 30, 129, 130, 131, 132, 133, 134, 135, 0); + ASSERT_TRUE( + transformation5.IsApplicable(context.get(), transformation_context)); + transformation5.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(21, {}), MakeDataDescriptor(129, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %14 5 + %17 = OpConstant %14 10 + %18 = OpConstant %14 20 + %19 = OpTypeVector %7 2 + %20 = OpTypeVector %14 2 + %21 = OpConstantComposite %19 %12 %8 + %22 = OpConstantComposite %20 %17 %15 + %23 = OpConstantComposite %19 %13 %12 + %24 = OpConstantComposite %19 %11 %11 + %2 = OpFunction %3 None %4 + %25 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %25 %105 %101 + %103 = OpPhi %14 %18 %25 %104 %101 + %104 = OpISub %14 %103 %16 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %10 + OpLoopMerge %26 %101 None + OpBranchConditional %106 %101 %26 + %26 = OpLabel + %100 = OpPhi %14 %104 %101 + OpBranch %109 + %109 = OpLabel + %110 = OpPhi %7 %8 %26 %113 %109 + %111 = OpPhi %14 %18 %26 %112 %109 + %112 = OpISub %14 %111 %11 + %113 = OpIAdd %7 %110 %9 + %114 = OpSLessThan %5 %113 %10 + OpLoopMerge %27 %109 None + OpBranchConditional %114 %109 %27 + %27 = OpLabel + %108 = OpPhi %14 %112 %109 + OpBranch %116 + %116 = OpLabel + %117 = OpPhi %7 %8 %27 %120 %116 + %118 = OpPhi %14 %18 %27 %119 %116 + %119 = OpISub %14 %118 %16 + %120 = OpIAdd %7 %117 %9 + %121 = OpSLessThan %5 %120 %10 + OpLoopMerge %28 %116 None + OpBranchConditional %121 %116 %28 + %28 = OpLabel + %115 = OpPhi %14 %119 %116 + OpBranch %123 + %123 = OpLabel + %124 = OpPhi %7 %8 %28 %127 %123 + %125 = OpPhi %19 %23 %28 %126 %123 + %126 = OpISub %19 %125 %24 + %127 = OpIAdd %7 %124 %9 + %128 = OpSLessThan %5 %127 %10 + OpLoopMerge %29 %123 None + OpBranchConditional %128 %123 %29 + %29 = OpLabel + %122 = OpPhi %19 %126 %123 + OpBranch %130 + %130 = OpLabel + %131 = OpPhi %7 %8 %29 %134 %130 + %132 = OpPhi %19 %23 %29 %133 %130 + %133 = OpISub %19 %132 %24 + %134 = OpIAdd %7 %131 %9 + %135 = OpSLessThan %5 %134 %10 + OpLoopMerge %30 %130 None + OpBranchConditional %135 %130 %30 + %30 = OpLabel + %129 = OpPhi %19 %133 %130 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, 64BitConstants) { + std::string shader = R"( + OpCapability Shader + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpTypeInt 64 1 + %12 = OpConstant %11 5 + %13 = OpConstant %11 10 + %14 = OpConstant %11 20 + %15 = OpTypeVector %11 2 + %16 = OpConstantComposite %15 %13 %13 + %17 = OpConstantComposite %15 %14 %14 + %18 = OpConstantComposite %15 %12 %12 + %2 = OpFunction %3 None %4 + %19 = OpLabel + OpBranch %20 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // These tests check that the transformation can be applied, and is applied + // correctly, to 64-bit integer (scalar and vector) constants. + + // 64-bit scalar integers. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 13, 14, 12, 10, 20, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(13, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // 64-bit vector integers. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 16, 17, 18, 10, 21, 107, 108, 109, 110, 111, 112, 113, 0); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(16, {}), MakeDataDescriptor(107, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpTypeInt 64 1 + %12 = OpConstant %11 5 + %13 = OpConstant %11 10 + %14 = OpConstant %11 20 + %15 = OpTypeVector %11 2 + %16 = OpConstantComposite %15 %13 %13 + %17 = OpConstantComposite %15 %14 %14 + %18 = OpConstantComposite %15 %12 %12 + %2 = OpFunction %3 None %4 + %19 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %19 %105 %101 + %103 = OpPhi %11 %14 %19 %104 %101 + %104 = OpISub %11 %103 %12 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %10 + OpLoopMerge %20 %101 None + OpBranchConditional %106 %101 %20 + %20 = OpLabel + %100 = OpPhi %11 %104 %101 + OpBranch %108 + %108 = OpLabel + %109 = OpPhi %7 %8 %20 %112 %108 + %110 = OpPhi %15 %17 %20 %111 %108 + %111 = OpISub %15 %110 %18 + %112 = OpIAdd %7 %109 %9 + %113 = OpSLessThan %5 %112 %10 + OpLoopMerge %21 %108 None + OpBranchConditional %113 %108 %21 + %21 = OpLabel + %107 = OpPhi %15 %111 %108 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Underflow) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 20 + %12 = OpConstant %7 -4 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 214748365 + %15 = OpConstant %13 4294967256 + %2 = OpFunction %3 None %4 + %16 = OpLabel + OpBranch %17 + %17 = OpLabel + OpBranch %18 + %18 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // These tests check that underflows are taken into consideration when + // deciding if transformation is applicable. + + // Subtracting 2147483648 20 times from 32-bit integer 0 underflows 2 times + // and the result is equivalent to -4. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 8, 14, 11, 17, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // Subtracting 20 twice from 0 underflows and gives the unsigned integer + // 4294967256. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 15, 8, 11, 10, 18, 107, 108, 109, 110, 111, 112, 113, 0); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(15, {}), MakeDataDescriptor(107, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 20 + %12 = OpConstant %7 -4 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 214748365 + %15 = OpConstant %13 4294967256 + %2 = OpFunction %3 None %4 + %16 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %16 %105 %101 + %103 = OpPhi %7 %8 %16 %104 %101 + %104 = OpISub %7 %103 %14 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %11 + OpLoopMerge %17 %101 None + OpBranchConditional %106 %101 %17 + %17 = OpLabel + %100 = OpPhi %7 %104 %101 + OpBranch %108 + %108 = OpLabel + %109 = OpPhi %7 %8 %17 %112 %108 + %110 = OpPhi %7 %8 %17 %111 %108 + %111 = OpISub %7 %110 %11 + %112 = OpIAdd %7 %109 %9 + %113 = OpSLessThan %5 %112 %10 + OpLoopMerge %18 %108 None + OpBranchConditional %113 %108 %18 + %18 = OpLabel + %107 = OpPhi %7 %111 %108 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools